├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── inch.json
├── package-lock.json
├── package-scripts.js
├── package.json
├── src
├── index.js
└── inherit.js
└── test
├── .eslintrc
├── fixtures
├── attribute.css
├── attribute.out.css
├── button-base.css
├── button.css
├── button.out.css
├── chain.css
├── chain.out.css
├── class.css
├── class.out.css
├── clearfix.css
├── clearfix.out.css
├── clearfix.zoom.css
├── clearfix.zoom.out.css
├── combined.css
├── combined.out.css
├── complex-sequence.css
├── complex-sequence.out.css
├── extend.css
├── import.css
├── import.out.css
├── keyframes.css
├── keyframes.out.css
├── media.css
├── media.disjoint.css
├── media.disjoint.out.css
├── media.out.css
├── mismatch-atrules.css
├── missing-selector.css
├── multiple.css
├── multiple.out.css
├── nested-rules.css
├── nested-rules.out.css
├── placeholder.css
├── placeholder.out.css
├── pseudo.css
├── pseudo.out.css
├── sequence.css
├── sequence.out.css
├── substring.css
├── substring.out.css
├── tag.css
├── tag.out.css
├── unordered.css
└── unordered.out.css
└── inherit.test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": ["add-module-exports"],
4 | "env": {
5 | "test": {
6 | "plugins": [ "istanbul" ]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | docs.json
4 | *.log
5 | npm-debug.log*
6 | .DS_Store
7 | /lib
8 | /bin
9 | inch.json
10 | /coverage
11 | /.nyc_output
12 | .*
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true,
7 | "mocha": true,
8 | },
9 | "rules": {
10 | "no-param-reassign": [2, {
11 | "props": false
12 | }],
13 | "react/require-extension": 0,
14 | "no-underscore-dangle": 0,
15 | },
16 | "plugins": [
17 | "import",
18 | ],
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | docs.json
4 | *.log
5 | npm-debug.log*
6 | .DS_Store
7 | /lib
8 | /bin
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # node-waf configuration
28 | .lock-wscript
29 |
30 | # Compiled binary addons (http://nodejs.org/api/addons.html)
31 | build/Release
32 |
33 | # Dependency directories
34 | node_modules
35 | jspm_packages
36 |
37 | # Optional npm cache directory
38 | .npm
39 |
40 | # Optional REPL history
41 | .node_repl_history
42 |
43 | /coverage
44 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | test/
3 | .*
4 | coverage
5 | inch.json
6 | docs.json
7 | node_modules/
8 | npm-debug.log
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | script:
5 | - npm start -- validate
6 | after_success:
7 | - npm start report-coverage
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## Unreleased
7 |
8 | * fixed pseudo class issue and updated test.
9 | * rewrote plugin to use postcss api better.
10 | * switched tests to ava.
11 |
12 | ## [2.0.3] - 2016-08-10
13 |
14 | * Fixed issue with removing place holders.
15 |
16 | ## [2.0.1] - 2016-08-10
17 |
18 | * Fixed `main` file.
19 | * Adjusted README.md.
20 |
21 | ## [2.0.0] - 2016-08-09
22 |
23 | * Switched to es2015 for source code.
24 | * Switched to NPM as build script.
25 | * Made plugin use postcss friendly things like `appendAfter` and `clone`.
26 | * Removed rework dependencies.
27 | * Improved documentation (README.md).
28 |
29 | ## [1.0.0] - 2015-10-17
30 |
31 | **Note:** This release will be deleted as it doesn't adhere to [postcss plugin guidelines](https://github.com/postcss/postcss/blob/master/docs/guidelines/plugin.md) and needs to be replaced.
32 |
33 | ### Added
34 |
35 | * Put a Hack of a solution together by wrapping the rework plugin in a postcss plugin. Does not use sourcemapping or anything useful.
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PostCSS Inherit
2 |
3 | [](https://travis-ci.org/GarthDB/postcss-inherit) [](https://codeclimate.com/github/GarthDB/postcss-inherit) [](https://codeclimate.com/github/GarthDB/postcss-inherit) [](https://codecov.io/gh/GarthDB/postcss-inherit) [](https://david-dm.org/GarthDB/postcss-inherit) [](http://inch-ci.org/github/GarthDB/postcss-inherit) [](https://badge.fury.io/js/postcss-inherit)
4 |
5 | ---
6 |
7 |
10 |
11 | Inherit plugin for [PostCSS](https://github.com/postcss/postcss). Allows you to inherit all the rules associated with a given selector. Modeled after [rework-inherit](https://github.com/reworkcss/rework-inherit).
12 |
13 | ## API
14 |
15 | ```js
16 | var postcss = require('postcss');
17 | var inherit = require('postcss-inherit')
18 |
19 | postcss([ inherit ])
20 | .process(css, { from: 'src/app.css', to: 'app.css' })
21 | .then(function (result) {
22 | fs.writeFileSync('app.css', result.css);
23 | if ( result.map ) fs.writeFileSync('app.css.map', result.map);
24 | });
25 | ```
26 |
27 | ### Inherit(options{})
28 |
29 | Option parameters:
30 |
31 | * `propertyRegExp` - Regular expression to match the "inherit" at-rule.
32 | By default, it is `/^(inherit|extend)s?:?$/i`, so it matches "inherit", "inherits", "extend", and "extends".
33 | For example, if you only want to allow the `extend` keyword,
34 | set the regular expression to `/^extend$/`.
35 |
36 | ## Examples
37 |
38 | ### Regular inherit
39 |
40 | ```css
41 | .gray {
42 | color: gray;
43 | }
44 |
45 | .text {
46 | @inherit: .gray;
47 | }
48 | ```
49 |
50 | yields:
51 |
52 | ```css
53 | .gray,
54 | .text {
55 | color: gray;
56 | }
57 | ```
58 |
59 | ### Multiple inherit
60 |
61 | Inherit multiple selectors at the same time.
62 |
63 | ```css
64 | .gray {
65 | color: gray;
66 | }
67 |
68 | .black {
69 | color: black;
70 | }
71 |
72 | .button {
73 | @inherit: .gray, .black;
74 | }
75 | ```
76 |
77 | yields:
78 |
79 | ```css
80 | .gray,
81 | .button {
82 | color: gray;
83 | }
84 |
85 | .black,
86 | .button {
87 | color: black;
88 | }
89 | ```
90 |
91 | ### Placeholders
92 |
93 | Any selector that includes a `%` is considered a placeholder.
94 | Placeholders will not be output in the final CSS.
95 |
96 | ```css
97 | %gray {
98 | color: gray;
99 | }
100 |
101 | .text {
102 | @inherit: %gray;
103 | }
104 | ```
105 |
106 | yields:
107 |
108 | ```css
109 | .text {
110 | color: gray;
111 | }
112 | ```
113 |
114 | ### Partial selectors
115 |
116 | If you inherit a selector,
117 | all rules that include that selector will be included as well.
118 |
119 | ```css
120 | div button span {
121 | color: red;
122 | }
123 |
124 | div button {
125 | color: green;
126 | }
127 |
128 | button span {
129 | color: pink;
130 | }
131 |
132 | .button {
133 | @inherit: button;
134 | }
135 |
136 | .link {
137 | @inherit: div button;
138 | }
139 | ```
140 |
141 | yields:
142 |
143 | ```css
144 | div button span,
145 | div .button span,
146 | .link span {
147 | color: red;
148 | }
149 |
150 | div button,
151 | div .button,
152 | .link {
153 | color: green;
154 | }
155 |
156 | button span,
157 | .button span {
158 | color: pink;
159 | }
160 | ```
161 |
162 | ### Chained inheritance
163 |
164 | ```css
165 | .button {
166 | background-color: gray;
167 | }
168 |
169 | .button-large {
170 | @inherit: .button;
171 | padding: 10px;
172 | }
173 |
174 | .button-large-red {
175 | @inherit: .button-large;
176 | color: red;
177 | }
178 | ```
179 |
180 | yields:
181 |
182 | ```css
183 | .button,
184 | .button-large,
185 | .button-large-red {
186 | background-color: gray;
187 | }
188 |
189 | .button-large,
190 | .button-large-red {
191 | padding: 10px;
192 | }
193 |
194 | .button-large-red {
195 | color: red;
196 | }
197 | ```
198 |
199 | ### Media Queries
200 |
201 | Inheriting from inside a media query will create a copy of the declarations.
202 | It will act like a "mixin".
203 | Thus, with `%`placeholders, you won't have to use mixins at all.
204 | Each type of media query will need its own declaration,
205 | so there will be some inevitable repetition.
206 |
207 | ```css
208 | .gray {
209 | color: gray
210 | }
211 |
212 | @media (min-width: 320px) {
213 | .button {
214 | @inherit: .gray;
215 | }
216 | }
217 |
218 | @media (min-width: 320px) {
219 | .link {
220 | @inherit: .gray;
221 | }
222 | }
223 | ```
224 |
225 | yields:
226 |
227 | ```css
228 | .gray {
229 | color: gray;
230 | }
231 |
232 | @media (min-width: 320px) {
233 | .button,
234 | .link {
235 | color: gray;
236 | }
237 | }
238 | ```
239 |
240 | ### Limitations
241 |
242 | * When in a media query, you can only inherit rules from root, or rules contained in a media query with the same parameters.
243 |
--------------------------------------------------------------------------------
/inch.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "excluded": [
4 | "!regexp:/^lib/"
5 | ]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/package-scripts.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | scripts: {
3 | test: {
4 | default: {
5 | script: 'nyc ava',
6 | description: 'Runs AVA with nyc (which is configured in package.json)',
7 | },
8 | watch: {
9 | description: 'Run AVA in watch mode',
10 | script: 'ava -w',
11 | },
12 | },
13 | build: {
14 | description: 'Transpiles es2015 in src to lib for publishing',
15 | script: 'babel -d lib/ src/',
16 | },
17 | lint: {
18 | description: 'lint the entire project',
19 | script: 'eslint .',
20 | },
21 | reportCoverage: {
22 | description: 'Report coverage stats to codecov. This should be run after the `test` script',
23 | script: 'nyc report -r lcovonly && codecov',
24 | },
25 | docs: {
26 | description: 'Checks Atomdoc to make sure it shows the most helpful information',
27 | script: 'atomdoc src',
28 | },
29 | validate: {
30 | description: 'This runs several scripts to make sure things look good before committing',
31 | script: 'nps lint build test',
32 | },
33 | },
34 | options: {
35 | silent: false,
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postcss-inherit",
3 | "description": "PostCSS plugin inherit rules from other selectors",
4 | "main": "./lib/index.js",
5 | "scripts": {
6 | "start": "nps",
7 | "test": "nyc ava",
8 | "prepare": "nps build"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/GarthDB/postcss-inherit.git"
13 | },
14 | "keywords": [
15 | "postcss",
16 | "css",
17 | "postcss-plugin",
18 | "inherit",
19 | "extend"
20 | ],
21 | "ava": {
22 | "files": [
23 | "test/*.js"
24 | ],
25 | "require": [
26 | "babel-register",
27 | "babel-polyfill"
28 | ],
29 | "babel": "inherit"
30 | },
31 | "nyc": {
32 | "include": [
33 | "./src/*.js"
34 | ],
35 | "require": [
36 | "babel-register"
37 | ],
38 | "sourceMap": false,
39 | "instrument": false
40 | },
41 | "author": "Garth Braithwaite (http://garthdb.com)",
42 | "license": "Apache-2.0",
43 | "bugs": {
44 | "url": "https://github.com/garthdb/postcss-inherit/issues"
45 | },
46 | "homepage": "https://github.com/garthdb/postcss-inherit#readme",
47 | "dependencies": {
48 | "debug": "^3.1.0",
49 | "postcss": "^6.0.22",
50 | "postcss-inherit-parser": "^0.2.0"
51 | },
52 | "devDependencies": {
53 | "atomdoc-cli": "^1.1.1",
54 | "ava": "^0.25.0",
55 | "babel-cli": "^6.26.0",
56 | "babel-core": "^6.26.3",
57 | "babel-plugin-add-module-exports": "^0.2.1",
58 | "babel-plugin-istanbul": "^4.1.6",
59 | "babel-polyfill": "^6.26.0",
60 | "babel-preset-env": "^1.6.1",
61 | "babel-register": "^6.26.0",
62 | "codecov": "^3.0.2",
63 | "eslint": "^4.19.1",
64 | "eslint-config-airbnb": "^16.1.0",
65 | "eslint-plugin-import": "^2.11.0",
66 | "eslint-plugin-jsx-a11y": "^6.0.3",
67 | "eslint-plugin-react": "^7.8.2",
68 | "nps": "^5.9.0",
69 | "nyc": "^11.7.3",
70 | "perfectionist": "^2.4.0",
71 | "postcss-import": "^11.1.0",
72 | "topcoat-utils": "^1.0.0"
73 | },
74 | "config": {
75 | "commitizen": {
76 | "path": "./node_modules/cz-customizable"
77 | }
78 | },
79 | "version": "4.1.0"
80 | }
81 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss';
2 | import Inherit from './inherit';
3 | /**
4 | * Public: PostCSS plugin allows you to inherit all the rules associated with a given selector.
5 | *
6 | * Returns a [PostCSS Plugin](http://api.postcss.org/postcss.html#.plugin) {Function}
7 | */
8 | export default postcss.plugin(
9 | 'postcss-inherit',
10 | (opts = {}) =>
11 | css =>
12 | new Inherit(css, opts),
13 | );
14 |
--------------------------------------------------------------------------------
/src/inherit.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('postcss-inherit');
2 |
3 | /**
4 | * Private: checks if a node is a decendant of an [AtRule](http://api.postcss.org/AtRule.html).
5 | *
6 | * * `node` {Object} PostCSS Node to check.
7 | *
8 | * Returns {Boolean} of false, or {String} of AtRule params if true.
9 | */
10 | function _isAtruleDescendant(node) {
11 | let { parent } = node;
12 | let descended = false;
13 |
14 | while (parent && parent.type !== 'root') {
15 | if (parent.type === 'atrule') {
16 | descended = parent.params;
17 | }
18 | ({ parent } = parent);
19 | }
20 | return descended;
21 | }
22 | /**
23 | * Private: checks string to see if it has the placeholder syntax (starts with %)
24 | *
25 | * * `val` a {String} intended for inherit value or rule name.
26 | *
27 | * Returns {Boolean}
28 | */
29 | function _isPlaceholder(val) {
30 | return val[0] === '%';
31 | }
32 | /**
33 | * Private: gets a string ready to use in a regular expression.
34 | *
35 | * * `str` {String} to escape RegExp reserved characters.
36 | *
37 | * Returns {String} for use in a regualr expression.
38 | */
39 | function _escapeRegExp(str) {
40 | return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
41 | }
42 | /**
43 | * Private: creates a regular expression used to find rules that match inherit property.
44 | *
45 | * * `val` {String} inherit property to use to find selectors that contain it.
46 | *
47 | * Returns {RegExp} used for finding rules that match inherit property.
48 | */
49 | function _matchRegExp(val) {
50 | const expression = `${_escapeRegExp(val)}($|\\s|\\>|\\+|~|\\:|\\[)`;
51 | let expressionPrefix = '(^|\\s|\\>|\\+|~)';
52 | if (_isPlaceholder(val)) {
53 | // We just want to match an empty group here to preserve the arguments we
54 | // may be expecting in a RegExp match.
55 | expressionPrefix = '()';
56 | }
57 | return new RegExp(expressionPrefix + expression, 'g');
58 | }
59 | /**
60 | * Private: creates a regular expression used to replace selector with inherit property inserted
61 | *
62 | * * `val` {String} inherit property to use to replace selectors that contain it.
63 | *
64 | * Returns {RegExp} used to replace selector with inherit property inserted
65 | */
66 | function _replaceRegExp(val) {
67 | const operatorRegex = /(::?|\[)/g;
68 | const newVal = (val.match(operatorRegex)) ? val.substring(0, val.search(operatorRegex)) : val;
69 | return _matchRegExp(newVal);
70 | }
71 | /**
72 | * Private: replaces selector with inherit property inserted.
73 | *
74 | * * `matchedSelector` {String} selector of the rule that matches the inherit value
75 | * * `val` {String} value of the inherit property
76 | * * `selector` {String} selector of the rule that contains the inherit declaration.
77 | *
78 | * Returns {String} new selector.
79 | */
80 | function _replaceSelector(matchedSelector, val, selector) {
81 | return matchedSelector.replace(_replaceRegExp(val), (_, first, last) =>
82 | first + selector + last);
83 | }
84 | /**
85 | * Private: turns a portion of a selector into a placeholder (adding a %)
86 | *
87 | * * `selector` {String} of the selector to replace
88 | * * `value` {String} portion of the selector to convert into a placeholder
89 | *
90 | * Returns the transformed selector string {String}
91 | */
92 | function _makePlaceholder(selector, value) {
93 | return selector.replace(_replaceRegExp(value), (_, first, last) =>
94 | `${first}%${_.trim()}${last}`);
95 | }
96 | /**
97 | * Private: splits selectors divided by a comma
98 | *
99 | * * `selector` {String} comma delimited selectors.
100 | *
101 | * Returns {Array} of selector {Strings}
102 | */
103 | function _parseSelectors(selector) {
104 | return selector.split(',').map(x => x.trim());
105 | }
106 | /**
107 | * Private: reassembles an array of selectors, usually split by `_parseSelectors`
108 | *
109 | * * `selectors` {Array} of selector {Strings}
110 | *
111 | * Returns selector {String}
112 | */
113 | function _assembleSelectors(selectors) {
114 | return selectors.join(',\n');
115 | }
116 | /**
117 | * Private: checks if value is already contained in a nested object.
118 | *
119 | * * `object` {Object} that might contain the value
120 | * * `key` {String} key of the nested object that might contain the value
121 | * * `value` {String} to check for
122 | *
123 | * Returns {Boolean}
124 | */
125 | function _mediaMatch(object, key, value) {
126 | if (!{}.hasOwnProperty.call(object, key)) {
127 | return false;
128 | }
129 | return Boolean(object[key].indexOf(value) >= 0);
130 | }
131 | /**
132 | * Private: removes PostCSS Node and all parents if left empty after removal.
133 | *
134 | * * `node` {Object} PostCSS Node to check.
135 | */
136 | function _removeParentsIfEmpty(node) {
137 | let currentNode = node.parent;
138 | node.remove();
139 | while (!currentNode.nodes.length) {
140 | const { parent } = currentNode;
141 | currentNode.remove();
142 | currentNode = parent;
143 | }
144 | }
145 | /**
146 | * Private: use a regex to see if something is contained in array values.
147 | *
148 | * * `array` {Array} that might contain a match
149 | * * `regex` {RegExp} used to test values of `array`
150 | *
151 | * Returns {Int} index of array that matched.
152 | */
153 | function _findInArray(array, regex) {
154 | let result = -1;
155 | array.forEach((value, index) => {
156 | if (regex.test(value)) result = index;
157 | });
158 | return result;
159 | }
160 | /**
161 | * Private: removes whitespace (like `trim()`) and quotes at beginning and extend.
162 | *
163 | * * `paramStr` {String} to clean (params property of at AtRule)
164 | *
165 | * Returns cleaned {String}
166 | */
167 | function _cleanParams(paramStr) {
168 | const regexp = /(^(?:(?:\s*")|(?:\s*')))|((?:(?:"\s*)|(?:'\s*))$)/g;
169 | return paramStr.replace(regexp, '');
170 | }
171 | /**
172 | * Private: copies rule from one location to another.
173 | * Used to copy rules from root that match inherit value in a PostCSS AtRule.
174 | * Rule copied before the rule that contains the inherit declaration.
175 | *
176 | * * `originRule` {Object} PostCSS Rule (in the atRule) that contains inherit declaration
177 | * * `targetRule` {Object} PostCSS Rule (in root) that matches inherit property
178 | *
179 | * Returns copied {Object} PostCSS Rule.
180 | */
181 | function _copyRule(originRule, targetRule) {
182 | const newRule = targetRule.cloneBefore();
183 | originRule.before(newRule);
184 | return newRule;
185 | }
186 | /**
187 | * Private: appends selector from originRule to matching rules.
188 | * Does not return a value, but it transforms the PostCSS AST.
189 | *
190 | * * `originSelector` {String} selector from originRule to append
191 | * * `targetRule` {Object} PostCSS Rule that matched value.
192 | * Will have originSelector appended to it
193 | * * `value` {String} inherit declaration value
194 | */
195 | function _appendSelector(originSelector, targetRule, value) {
196 | const originSelectors = _parseSelectors(originSelector);
197 | let targetRuleSelectors = _parseSelectors(targetRule.selector);
198 | targetRuleSelectors.forEach((targetRuleSelector) => {
199 | [].push.apply(targetRuleSelectors, originSelectors.map(newOriginSelector =>
200 | _replaceSelector(targetRuleSelector, value, newOriginSelector)));
201 | });
202 | // removes duplicate selectors
203 | targetRuleSelectors = [...new Set(targetRuleSelectors)];
204 | targetRule.selector = _assembleSelectors(targetRuleSelectors);
205 | }
206 | /**
207 | * Inherit Class
208 | */
209 | export default class Inherit {
210 | /**
211 | * Public: Inherit class constructor. Does not return a value, but it transforms the PostCSS AST.
212 | *
213 | * * `css` {Object} PostCSS AST that will be transformed by the inherit plugin
214 | * * `opts` {Object} of inherit plugin specific options
215 | * * `propertyRegExp` {RegExp} to use for AtRule name (defaults to use inherit(s)/extend(s)).
216 | *
217 | * ## Examples
218 | *
219 | * ```js
220 | * export default postcss.plugin('postcss-inherit',
221 | * (opts = {}) =>
222 | * css =>
223 | * new Inherit(css, opts)
224 | * );
225 | * ```
226 | */
227 | constructor(css, opts) {
228 | this.root = css;
229 | this.matches = {};
230 | this.propertyRegExp = opts.propertyRegExp || /^(inherit|extend)s?:?$/i;
231 | this.root.walkAtRules('media', (atRule) => {
232 | this._atRuleInheritsFromRoot(atRule);
233 | });
234 | this.root.walkAtRules(this.propertyRegExp, (importRule) => {
235 | const rule = importRule.parent;
236 | const importValue = _cleanParams(importRule.params);
237 | _parseSelectors(importValue).forEach((value) => {
238 | this._inheritRule(value, rule, importRule);
239 | });
240 | _removeParentsIfEmpty(importRule);
241 | });
242 | this._removePlaceholders();
243 | }
244 | /**
245 | * Private: copies rules from root when inherited in an atRule descendant.
246 | * Does not return a value, but it transforms the PostCSS AST.
247 | *
248 | * * `atRule` {Object} PostCSS AtRule
249 | */
250 | _atRuleInheritsFromRoot(atRule) {
251 | atRule.walkAtRules(this.propertyRegExp, (importRule) => {
252 | const originRule = importRule.parent;
253 | const importValue = _cleanParams(importRule.params);
254 | const originAtParams = _isAtruleDescendant(originRule);
255 | const newValueArray = [];
256 | _parseSelectors(importValue).forEach((value) => {
257 | const targetSelector = value;
258 | let newValue = value;
259 | this.root.walkRules((rule) => {
260 | if (!_matchRegExp(targetSelector).test(rule.selector)) return;
261 | const targetAtParams = _isAtruleDescendant(rule);
262 | if (!targetAtParams) {
263 | newValue = `%${value}`;
264 | } else {
265 | return;
266 | }
267 | if (!_mediaMatch(this.matches, originAtParams, targetSelector)) {
268 | const newRule = _copyRule(originRule, rule);
269 | newRule.selector = _makePlaceholder(newRule.selector, targetSelector);
270 | this.matches[originAtParams] = this.matches[originAtParams] || [];
271 | this.matches[originAtParams].push(targetSelector);
272 | this.matches[originAtParams] = [...new Set(this.matches[originAtParams])];
273 | }
274 | });
275 | newValueArray.push(newValue);
276 | });
277 | importRule.params = newValueArray.join(', ');
278 | });
279 | }
280 | /**
281 | * Private: Finds selectors that match value and add the selector for the originRule as needed.
282 | * Does not return a value, but it transforms the PostCSS AST.
283 | *
284 | * * `value` {String} inherit declaration value
285 | * * `originRule` {Object} the PostCSS Rule that contains the inherit declaration
286 | * * `decl` {Object} the original inherit PostCSS Declaration
287 | */
288 | _inheritRule(value, originRule, decl) {
289 | const originSelector = originRule.selector;
290 | const originAtParams = originRule.atParams || _isAtruleDescendant(originRule);
291 | const targetSelector = value;
292 | let matched = false;
293 | let differentLevelMatched = false;
294 | this.root.walkRules((rule) => {
295 | if (_findInArray(_parseSelectors(rule.selector), _matchRegExp(targetSelector)) === -1) return;
296 | const targetRule = rule;
297 | const targetAtParams = targetRule.atParams || _isAtruleDescendant(targetRule);
298 | if (targetAtParams === originAtParams) {
299 | debug('extend %j with %j', originSelector, targetSelector);
300 | _appendSelector(originSelector, targetRule, targetSelector);
301 | matched = true;
302 | } else {
303 | differentLevelMatched = true;
304 | }
305 | });
306 | if (!matched) {
307 | if (differentLevelMatched) {
308 | throw decl.error(`Could not find rule that matched ${value} in the same atRule.`);
309 | } else {
310 | throw decl.error(`Could not find rule that matched ${value}.`);
311 | }
312 | }
313 | }
314 | /**
315 | * Private: after processing inherits, this method is used to remove all placeholder rules.
316 | * Does not return a value, but it transforms the PostCSS AST.
317 | */
318 | _removePlaceholders() {
319 | this.root.walkRules(/^%|\s+%|\w%\w/, (rule) => {
320 | const selectors = _parseSelectors(rule.selector);
321 | const newSelectors = selectors.filter(selector =>
322 | (selector.indexOf('%') === -1));
323 | if (!newSelectors.length) {
324 | rule.remove();
325 | } else {
326 | rule.selector = _assembleSelectors(newSelectors);
327 | }
328 | });
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true,
7 | "mocha": true,
8 | },
9 | "rules": {
10 | "no-param-reassign": [2, {
11 | "props": false
12 | }],
13 | import/no-extraneous-dependencies: ['error', {'devDependencies': true}],
14 | "no-console": 0
15 | },
16 | "plugins": [
17 | "import",
18 | ],
19 | }
20 |
--------------------------------------------------------------------------------
/test/fixtures/attribute.css:
--------------------------------------------------------------------------------
1 | %form-element[disabled] {
2 | cursor: not-allowed;
3 | }
4 |
5 | input, select, textarea {
6 | @inherit %form-element;
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/attribute.out.css:
--------------------------------------------------------------------------------
1 | input[disabled],
2 | select[disabled],
3 | textarea[disabled] {
4 | cursor: not-allowed;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/button-base.css:
--------------------------------------------------------------------------------
1 | @import "topcoat-utils";
2 |
3 | %button {
4 | @extend: %inline-block;
5 | @extend: %reset-box-model;
6 | @extend: %reset-base;
7 | @extend: %reset-cursor;
8 | @extend: %ellipsis;
9 | text-decoration: none;
10 | }
11 |
12 | %button--quiet {
13 | @extend: %reset-quiet;
14 | }
15 |
16 | %button--disabled {
17 | @extend: %disabled;
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixtures/button.css:
--------------------------------------------------------------------------------
1 | @import "button-base.css";
2 |
3 | .button {
4 | @extend: %button;
5 | }
6 |
7 | .button--quiet {
8 | @extend: %button--quiet;
9 | }
10 |
11 | .button--disabled {
12 | @extend: %button--disabled;
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixtures/button.out.css:
--------------------------------------------------------------------------------
1 | .button {
2 | padding: 0;
3 | margin: 0;
4 | font: inherit;
5 | color: inherit;
6 | background: transparent;
7 | border: none;
8 | }
9 |
10 | .button--quiet {
11 | background: transparent;
12 | border: 1px solid transparent;
13 | box-shadow: none;
14 | }
15 |
16 | .button {
17 | cursor: default;
18 | user-select: none;
19 | }
20 |
21 | .button {
22 | box-sizing: border-box;
23 | background-clip: padding-box;
24 | }
25 |
26 | .button {
27 | white-space: nowrap;
28 | overflow: hidden;
29 | }
30 |
31 | .button {
32 | position: relative;
33 | display: inline-block;
34 | vertical-align: top;
35 | }
36 |
37 | .button {
38 | text-overflow: ellipsis;
39 | }
40 |
41 | .button--disabled {
42 | opacity: 0.3;
43 | cursor: default;
44 | pointer-events: none;
45 | }
46 |
47 | .button {
48 | text-decoration: none;
49 | }
50 |
--------------------------------------------------------------------------------
/test/fixtures/chain.css:
--------------------------------------------------------------------------------
1 | .a {
2 | color: red;
3 | }
4 |
5 | .b {
6 | @inherit .a;
7 | }
8 |
9 | .c {
10 | @inherit .b;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/chain.out.css:
--------------------------------------------------------------------------------
1 | .a,
2 | .b,
3 | .c {
4 | color: red;
5 | }
--------------------------------------------------------------------------------
/test/fixtures/class.css:
--------------------------------------------------------------------------------
1 | .button {
2 | width: 100%;
3 | }
4 |
5 | .smaller-button {
6 | @extend .button;
7 | max-width: 250px;
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/class.out.css:
--------------------------------------------------------------------------------
1 | .button,
2 | .smaller-button {
3 | width: 100%;
4 | }
5 |
6 | .smaller-button {
7 | max-width: 250px;
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/clearfix.css:
--------------------------------------------------------------------------------
1 | %clearfix:before,
2 | %clearfix:after {
3 | content: " ";
4 | display: table;
5 | }
6 |
7 | %clearfix:after {
8 | clear: both;
9 | }
10 |
11 | .clearfix {
12 | @inherits: %clearfix;
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixtures/clearfix.out.css:
--------------------------------------------------------------------------------
1 | .clearfix:before,
2 | .clearfix:after {
3 | content: " ";
4 | display: table;
5 | }
6 |
7 | .clearfix:after {
8 | clear: both;
9 | }
--------------------------------------------------------------------------------
/test/fixtures/clearfix.zoom.css:
--------------------------------------------------------------------------------
1 | %clearfix {
2 | *zoom: 1;
3 | }
4 |
5 | %clearfix:before,
6 | %clearfix:after {
7 | content: " ";
8 | display: table;
9 | }
10 |
11 | %clearfix:after {
12 | clear: both;
13 | }
14 |
15 | .clearfix {
16 | @inherits: %clearfix;
17 | }
18 |
--------------------------------------------------------------------------------
/test/fixtures/clearfix.zoom.out.css:
--------------------------------------------------------------------------------
1 | .clearfix {
2 | *zoom: 1;
3 | }
4 |
5 | .clearfix:before,
6 | .clearfix:after {
7 | content: " ";
8 | display: table;
9 | }
10 |
11 | .clearfix:after {
12 | clear: both;
13 | }
--------------------------------------------------------------------------------
/test/fixtures/combined.css:
--------------------------------------------------------------------------------
1 | %dark-button {
2 | color: black;
3 | }
4 |
5 | %dark-button + %dark-button {
6 | color: green;
7 | }
8 |
9 | .button-group %dark-button {
10 | color: red;
11 | }
12 |
13 | .some-button {
14 | @inherits: %dark-button;
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixtures/combined.out.css:
--------------------------------------------------------------------------------
1 | .some-button {
2 | color: black;
3 | }
4 |
5 | .some-button + .some-button {
6 | color: green;
7 | }
8 |
9 | .button-group .some-button {
10 | color: red;
11 | }
--------------------------------------------------------------------------------
/test/fixtures/complex-sequence.css:
--------------------------------------------------------------------------------
1 | %icon {
2 | width: 50px;
3 | height: 50px;
4 | display: inline-block;
5 | }
6 |
7 | .icon-link%icon {
8 | display: inline;
9 | width: auto;
10 | }
11 |
12 | table .icon-link%icon {
13 | height: 80px;
14 | }
15 |
16 | .red-icon {
17 | @inherits: %icon;
18 | background-color: red;
19 | }
20 |
21 | .blue-icon {
22 | @inherits: %icon;
23 | background-color: blue;
24 | }
25 |
26 | .yellow-icon {
27 | @inherits: %icon;
28 | background-color: yellow;
29 | }
30 |
--------------------------------------------------------------------------------
/test/fixtures/complex-sequence.out.css:
--------------------------------------------------------------------------------
1 | .red-icon,
2 | .blue-icon,
3 | .yellow-icon {
4 | width: 50px;
5 | height: 50px;
6 | display: inline-block;
7 | }
8 |
9 | .icon-link.red-icon,
10 | .icon-link.blue-icon,
11 | .icon-link.yellow-icon {
12 | display: inline;
13 | width: auto;
14 | }
15 |
16 | table .icon-link.red-icon,
17 | table .icon-link.blue-icon,
18 | table .icon-link.yellow-icon {
19 | height: 80px;
20 | }
21 |
22 | .red-icon {
23 | background-color: red;
24 | }
25 |
26 | .blue-icon {
27 | background-color: blue;
28 | }
29 |
30 | .yellow-icon {
31 | background-color: yellow;
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/test/fixtures/extend.css:
--------------------------------------------------------------------------------
1 | .a {
2 | color: red;
3 | }
4 |
5 | .b {
6 | @foo .a;
7 | }
8 |
9 | .c {
10 | @foo .b;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/import.css:
--------------------------------------------------------------------------------
1 | @import "test/fixtures/attribute.css";
2 |
3 | submit {
4 | @inherit: %form-element;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/import.out.css:
--------------------------------------------------------------------------------
1 | input[disabled],
2 | select[disabled],
3 | textarea[disabled],
4 | submit[disabled] {
5 | cursor: not-allowed;
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixtures/keyframes.css:
--------------------------------------------------------------------------------
1 | @keyframes fontbulger {
2 | from {
3 | font-size: 10px;
4 | }
5 | 30% {
6 | font-size: 15px;
7 | }
8 | to {
9 | font-size: 12px;
10 | }
11 | }
12 |
13 | #box {
14 | animation: fontbulger 2s infinite;
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixtures/keyframes.out.css:
--------------------------------------------------------------------------------
1 | @keyframes fontbulger {
2 | from {
3 | font-size: 10px;
4 | }
5 |
6 | 30% {
7 | font-size: 15px;
8 | }
9 |
10 | to {
11 | font-size: 12px;
12 | }
13 | }
14 |
15 | #box {
16 | animation: fontbulger 2s infinite;
17 | }
18 |
--------------------------------------------------------------------------------
/test/fixtures/media.css:
--------------------------------------------------------------------------------
1 | .gray {
2 | color: gray;
3 | }
4 |
5 | @media (min-width: 320px) {
6 | .button {
7 | @inherit .gray;
8 | }
9 | }
10 |
11 | @media (min-width: 320px) {
12 | .new-button {
13 | @inherit .button;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixtures/media.disjoint.css:
--------------------------------------------------------------------------------
1 | .gray {
2 | color: gray;
3 | }
4 |
5 | @media (min-width: 320px) {
6 | .button1 {
7 | @inherit .gray;
8 | }
9 | }
10 |
11 | .black {
12 | color: black;
13 | }
14 |
15 | @media (min-width: 320px) {
16 | .button2 {
17 | @inherit .gray;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/fixtures/media.disjoint.out.css:
--------------------------------------------------------------------------------
1 | .gray {
2 | color: gray;
3 | }
4 |
5 | @media (min-width: 320px) {
6 | .button1,
7 | .button2 {
8 | color: gray;
9 | }
10 | }
11 |
12 | .black {
13 | color: black;
14 | }
--------------------------------------------------------------------------------
/test/fixtures/media.out.css:
--------------------------------------------------------------------------------
1 | .gray {
2 | color: gray;
3 | }
4 |
5 | @media (min-width: 320px) {
6 | .button,
7 | .new-button {
8 | color: gray;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixtures/mismatch-atrules.css:
--------------------------------------------------------------------------------
1 | .button {
2 | @inherit .gray;
3 | }
4 |
5 | @media (min-width: 320px) {
6 | .gray {
7 | color: gray;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixtures/missing-selector.css:
--------------------------------------------------------------------------------
1 | %form-element[disabled] {
2 | cursor: not-allowed;
3 | }
4 |
5 | input, select, textarea {
6 | @inherit %form;
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/multiple.css:
--------------------------------------------------------------------------------
1 | .gray {
2 | color: gray;
3 | }
4 |
5 | .red {
6 | color: red;
7 | }
8 |
9 | .button {
10 | @inherit .gray, .red;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/multiple.out.css:
--------------------------------------------------------------------------------
1 | .gray,
2 | .button {
3 | color: gray;
4 | }
5 |
6 | .red,
7 | .button {
8 | color: red;
9 | }
--------------------------------------------------------------------------------
/test/fixtures/nested-rules.css:
--------------------------------------------------------------------------------
1 | .red {
2 | color: red;
3 | .gray {
4 | color: gray;
5 | }
6 | }
7 |
8 | .button {
9 | @inherit: .gray;
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixtures/nested-rules.out.css:
--------------------------------------------------------------------------------
1 | .red {
2 | color: red;
3 |
4 | .gray,
5 | .button {
6 | color: gray;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/placeholder.css:
--------------------------------------------------------------------------------
1 | %form-element {
2 | cursor: not-allowed;
3 | }
4 |
5 | %extra-placeholder {
6 | cursor: pointer;
7 | }
8 |
9 | input {
10 | @inherit: %form-element;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/placeholder.out.css:
--------------------------------------------------------------------------------
1 | input {
2 | cursor: not-allowed;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/pseudo.css:
--------------------------------------------------------------------------------
1 | .button:active {
2 | border: 1px solid blue;
3 | }
4 |
5 | .button::before {
6 | content: '';
7 | }
8 |
9 | .button--large {
10 | @extend: .button:active;
11 | }
12 |
13 | .button--quiet {
14 | @extend: .button::before;
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixtures/pseudo.out.css:
--------------------------------------------------------------------------------
1 | .button:active,
2 | .button--large:active {
3 | border: 1px solid blue;
4 | }
5 |
6 | .button::before,
7 | .button--quiet::before {
8 | content: '';
9 | }
10 |
--------------------------------------------------------------------------------
/test/fixtures/sequence.css:
--------------------------------------------------------------------------------
1 | %red-button {
2 | color: red;
3 | }
4 |
5 | .fake-button%red-button {
6 | display: block;
7 | }
8 |
9 | .small-red-button {
10 | @inherit: %red-button;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/sequence.out.css:
--------------------------------------------------------------------------------
1 | .small-red-button {
2 | color: red;
3 | }
4 |
5 | .fake-button.small-red-button {
6 | display: block;
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/substring.css:
--------------------------------------------------------------------------------
1 | %placeholder > button {
2 | color: green;
3 | }
4 |
5 | div %placeholder > button {
6 | color: red;
7 | }
8 |
9 | .button {
10 | @inherit: %placeholder > button;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/substring.out.css:
--------------------------------------------------------------------------------
1 | .button {
2 | color: green;
3 | }
4 |
5 | div .button {
6 | color: red;
7 | }
--------------------------------------------------------------------------------
/test/fixtures/tag.css:
--------------------------------------------------------------------------------
1 | em {
2 | text-decoration: bold;
3 | }
4 |
5 | a em {
6 | color: red;
7 | }
8 |
9 | em > span {
10 | color: yellow;
11 | }
12 |
13 | .emphasis {
14 | text-decoration: bold;
15 | }
16 |
17 | .them {
18 | color: gray;
19 | }
20 |
21 | .test {
22 | @inherit: em;
23 | }
24 |
--------------------------------------------------------------------------------
/test/fixtures/tag.out.css:
--------------------------------------------------------------------------------
1 | em,
2 | .test {
3 | text-decoration: bold;
4 | }
5 |
6 | a em,
7 | a .test {
8 | color: red;
9 | }
10 |
11 | em > span,
12 | .test > span {
13 | color: yellow;
14 | }
15 |
16 | .emphasis {
17 | text-decoration: bold;
18 | }
19 |
20 | .them {
21 | color: gray;
22 | }
--------------------------------------------------------------------------------
/test/fixtures/unordered.css:
--------------------------------------------------------------------------------
1 | .a {
2 | @inherit: .b;
3 | }
4 |
5 | .b {
6 | color: red;
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/unordered.out.css:
--------------------------------------------------------------------------------
1 | .b,
2 | .a {
3 | color: red;
4 | }
--------------------------------------------------------------------------------
/test/inherit.test.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss';
2 | import test from 'ava';
3 | import fs from 'fs';
4 | import perfectionist from 'perfectionist';
5 | import inheritParser from 'postcss-inherit-parser';
6 | import importAt from 'postcss-import';
7 | import inherit from '../src/index';
8 |
9 | function runInherit(input, opts) {
10 | return postcss([
11 | inherit(opts),
12 | perfectionist({ indentSize: 2, maxAtRuleLength: false, maxSelectorLength: 1 }),
13 | ]).process(input, { from: undefined });
14 | }
15 |
16 | function read(file) {
17 | return fs.readFileSync(`./test/fixtures/${file}.css`, 'utf8').trim();
18 | }
19 |
20 | test('should handle a placeholder', (t) => {
21 | const output = read('placeholder.out');
22 | return runInherit(read('placeholder')).then((result) => {
23 | t.deepEqual(result.css.trim(), output);
24 | });
25 | });
26 |
27 | test('should extend a basic class', (t) => {
28 | const output = read('class.out');
29 | return runInherit(read('class')).then((result) => {
30 | t.deepEqual(result.css.trim(), output);
31 | });
32 | });
33 |
34 | test('should handle attribute selectors', (t) => {
35 | const output = read('attribute.out');
36 | return runInherit(read('attribute')).then((result) => {
37 | t.deepEqual(result.css.trim(), output);
38 | });
39 | });
40 |
41 | test('should clearfix', (t) => {
42 | const output = read('clearfix.out');
43 | return runInherit(read('clearfix')).then((result) => {
44 | t.deepEqual(result.css.trim(), output);
45 | });
46 | });
47 |
48 | test('should clearfix zoom', (t) => {
49 | const output = read('clearfix.zoom.out');
50 | return runInherit(read('clearfix.zoom')).then((result) => {
51 | t.deepEqual(result.css.trim(), output);
52 | });
53 | });
54 |
55 | test('should combine inherits', (t) => {
56 | const output = read('combined.out');
57 | return runInherit(read('combined')).then((result) => {
58 | t.deepEqual(result.css.trim(), output);
59 | });
60 | });
61 |
62 | test('should inherit through media', (t) => {
63 | const output = read('media.out');
64 | return runInherit(read('media')).then((result) => {
65 | t.deepEqual(result.css.trim(), output);
66 | });
67 | });
68 |
69 | test('should inherit disjoint media', (t) => {
70 | const output = read('media.disjoint.out');
71 | return runInherit(read('media.disjoint')).then((result) => {
72 | t.deepEqual(result.css.trim(), output);
73 | });
74 | });
75 |
76 | test('should inherit substring', (t) => {
77 | const output = read('substring.out');
78 | return runInherit(read('substring')).then((result) => {
79 | t.deepEqual(result.css.trim(), output);
80 | });
81 | });
82 |
83 | test('should inherit multiple selectors', (t) => {
84 | const output = read('multiple.out');
85 | return runInherit(read('multiple')).then((result) => {
86 | t.deepEqual(result.css.trim(), output);
87 | });
88 | });
89 |
90 | test('should inherit a tag', (t) => {
91 | const output = read('tag.out');
92 | return runInherit(read('tag')).then((result) => {
93 | t.deepEqual(result.css.trim(), output);
94 | });
95 | });
96 |
97 | test('should chain inheritance', (t) => {
98 | const output = read('chain.out');
99 | return runInherit(read('chain')).then((result) => {
100 | t.deepEqual(result.css.trim(), output);
101 | });
102 | });
103 |
104 | test('should inherit out of order', (t) => {
105 | const output = read('unordered.out');
106 | return runInherit(read('unordered')).then((result) => {
107 | t.deepEqual(result.css.trim(), output);
108 | });
109 | });
110 |
111 | test('should sequence inheritance (e.g. .one.two%three)', (t) => {
112 | const output = read('sequence.out');
113 | return runInherit(read('sequence')).then((result) => {
114 | t.deepEqual(result.css.trim(), output);
115 | });
116 | });
117 |
118 | test('should sequence complex inheritance (e.g. .one.two%three)', (t) => {
119 | const output = read('complex-sequence.out');
120 | return runInherit(read('complex-sequence')).then((result) => {
121 | t.deepEqual(result.css.trim(), output);
122 | });
123 | });
124 |
125 | test('should extend regexp', (t) => {
126 | const output = read('chain.out');
127 | return runInherit(read('extend'), { propertyRegExp: /^foo:?$/ }).then((result) => {
128 | t.deepEqual(result.css.trim(), output);
129 | });
130 | });
131 |
132 | test('should extend pseudo class', (t) => {
133 | const output = read('pseudo.out');
134 | return runInherit(read('pseudo')).then((result) => {
135 | t.deepEqual(result.css.trim(), output);
136 | });
137 | });
138 |
139 | test('should throw an error when missing a selector', t => t.throws(runInherit(read('missing-selector')), /Could not find rule that matched %form\./));
140 |
141 | test('should throw an error when atrules don\'t match', t => t.throws(
142 | runInherit(read('mismatch-atrules')),
143 | /Could not find rule that matched \.gray in the same atRule\./,
144 | ));
145 |
146 | test('should work after another plugin', (t) => {
147 | const inputcss = read('import');
148 | const output = read('import.out');
149 | return postcss([importAt(), inherit()]).process(inputcss, { from: undefined })
150 | .then((result) => {
151 | t.deepEqual(result.css.trim(), output);
152 | })
153 | .catch(console.log);
154 | });
155 |
156 | test('should create a component', (t) => {
157 | const inputcss = read('button');
158 | const output = read('button.out');
159 | return postcss([importAt(), inherit()])
160 | .process(inputcss, { from: './test/fixtures/button.css' })
161 | .then((result) => {
162 | t.deepEqual(result.css.trim(), output);
163 | })
164 | .catch(console.log);
165 | });
166 |
167 | test('should work with old inheritParser', (t) => {
168 | const inputcss = read('placeholder');
169 | const output = read('placeholder.out');
170 | return postcss([importAt(), inherit()])
171 | .process(inputcss, { from: undefined, parser: inheritParser })
172 | .then((result) => {
173 | t.deepEqual(result.css.trim(), output);
174 | })
175 | .catch(console.log);
176 | });
177 |
178 | test('should process nested rules', (t) => {
179 | const output = read('nested-rules.out');
180 | return runInherit(read('nested-rules')).then((result) => {
181 | t.deepEqual(result.css.trim(), output);
182 | });
183 | });
184 |
185 | test('should not lose keyframes', (t) => {
186 | const output = read('keyframes.out');
187 | return runInherit(read('keyframes')).then((result) => {
188 | t.deepEqual(result.css.trim(), output);
189 | });
190 | });
191 |
--------------------------------------------------------------------------------