├── .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 | [![Build Status](https://travis-ci.org/GarthDB/postcss-inherit.svg?branch=master)](https://travis-ci.org/GarthDB/postcss-inherit) [![Code Climate](https://codeclimate.com/github/GarthDB/postcss-inherit/badges/gpa.svg)](https://codeclimate.com/github/GarthDB/postcss-inherit) [![Issue Count](https://codeclimate.com/github/GarthDB/postcss-inherit/badges/issue_count.svg)](https://codeclimate.com/github/GarthDB/postcss-inherit) [![codecov](https://codecov.io/gh/GarthDB/postcss-inherit/branch/master/graph/badge.svg)](https://codecov.io/gh/GarthDB/postcss-inherit) [![Dependency Status](https://david-dm.org/GarthDB/postcss-inherit.svg)](https://david-dm.org/GarthDB/postcss-inherit) [![Inline docs](http://inch-ci.org/github/GarthDB/postcss-inherit.svg?branch=master)](http://inch-ci.org/github/GarthDB/postcss-inherit) [![npm version](https://badge.fury.io/js/postcss-inherit.svg)](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 | --------------------------------------------------------------------------------