├── .editorconfig ├── .gitignore ├── .rollup.js ├── .tape.js ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── INSTALL.md ├── LICENSE.md ├── README.md ├── cli.js ├── index.js ├── package.json ├── src ├── cli.js ├── index.js └── lib │ ├── dangerous │ ├── transform-decl-with-background-position-or-border-spacing.js │ ├── transform-decl-with-background-size.js │ ├── transform-decl-with-line-height.js │ └── transform-selector-with-link.js │ ├── parse-selector.js │ └── safe │ ├── prepend-box-sizing-border-box.js │ ├── transform-decl-with-corner-radius.js │ ├── transform-decl-with-depth-or-z-order.js │ ├── transform-decl-with-display-type.js │ ├── transform-decl-with-marker-style.js │ ├── transform-decl-with-vertical-align-text-middle.js │ ├── transform-decl-with-white-space-no-wrap.js │ ├── transform-decl-with-white-space-overflow-wrap.js │ ├── transform-function-with-rgb-or-hsl-and-fourth-value.js │ └── transform-word-with-current-color.js └── test ├── basic.box-sizing.expect.css ├── basic.css ├── basic.current-color.expect.css ├── basic.expect.css ├── basic.link.expect.css ├── basic.no-unsafe-fixes-with-an-exception.expect.css ├── basic.no-unsafe-fixes.expect.css ├── no-import.css └── no-import.expect.css /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{json,md,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | *.log* 4 | *.result.css 5 | .* 6 | !.editorconfig 7 | !.gitignore 8 | !.rollup.js 9 | !.tape.js 10 | !.travis.yml 11 | /index.* 12 | -------------------------------------------------------------------------------- /.rollup.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | 3 | const isCLI = String(process.env.NODE_ENV).includes('cli'); 4 | 5 | const input = `src/${isCLI ? 'cli' : 'index'}.js`; 6 | const output = isCLI ? { file: 'cli.js', format: 'cjs' } : [ 7 | { file: 'index.js', format: 'cjs', sourcemap: true }, 8 | { file: 'index.mjs', format: 'esm', sourcemap: true } 9 | ] 10 | 11 | export default { 12 | input, 13 | output, 14 | plugins: [ 15 | babel({ 16 | presets: [ 17 | ['@babel/env', { modules: false, targets: { node: 6 } }] 18 | ] 19 | }) 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /.tape.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'basic': { 3 | message: 'supports basic usage' 4 | }, 5 | 'basic:no-unsafe-fixes': { 6 | message: 'supports { useUnsafeFixes: false }', 7 | options: { 8 | useUnsafeFixes: false 9 | } 10 | }, 11 | 'basic:no-unsafe-fixes-with-an-exception': { 12 | message: 'supports { useUnsafeFixes: false }', 13 | options: { 14 | useUnsafeFixes: false, 15 | fixes: { 16 | 'link-pseudo': true 17 | } 18 | } 19 | }, 20 | 'basic:current-color': { 21 | message: 'supports { fixes: { "current-color": false } }', 22 | options: { 23 | fixes: { 24 | 'current-color': false 25 | } 26 | } 27 | }, 28 | 'basic:link': { 29 | message: 'supports { fixes: { "link-pseudo": false } }', 30 | options: { 31 | fixes: { 32 | 'link-pseudo': false 33 | } 34 | } 35 | }, 36 | 'basic:box-sizing': { 37 | message: 'supports { fixes: { "border-box": false } }', 38 | options: { 39 | fixes: { 40 | 'border-box': false 41 | } 42 | } 43 | }, 44 | 'no-import': { 45 | message: 'supports basic usage ( without import )' 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/travis-lint 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 6 7 | 8 | install: 9 | - npm install --ignore-scripts 10 | 11 | script: 12 | - npm test -- -- --ci=true 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes to PostCSS Time Machine 2 | 3 | ### 4.0.0 (December 6, 2018) 4 | 5 | - Added: Support for PostCSS 7 6 | - Added: Support for Node 6 7 | - Replaced: `postcss-value-parser` with `postcss-values-parser` to 2.0.0 (major) 8 | - Updated: `postcss-selector-parser` to 5.0.0-rc.4 (major) 9 | 10 | ### 3.0.0 (May 17, 2016) 11 | 12 | - Added: Support for PostCSS 6 13 | - Added: Support for Node 4 14 | 15 | ### 2.1.0 (March 1, 2017) 16 | 17 | - Added: `depth` property 18 | - Updated: `box-sizing` does not re-orient imports 19 | - Updated: `white-space` transforms 20 | 21 | ### 2.0.1 (December 8, 2016) 22 | 23 | - Updated: Use destructing assignment on plugin options 24 | - Updated: Use template literals 25 | 26 | ### 2.0.0 (December 6, 2016) 27 | 28 | - Added: `line-height` percentage value 29 | - Added: `marker-style` property 30 | - Added: `display-type` property 31 | - Updated: Do not duplicate `background-image` values of `contain` or `cover` 32 | - Updated: boilerplate conventions (Node v6.9.1 LTS) 33 | 34 | ### 1.2.1 (February 6, 2016) 35 | 36 | - Added: Option to disable `box-sizing` feature 37 | - Updated: Project configuration 38 | - Updated: Dependencies, documentation, and tests 39 | 40 | ### 1.2.0 (December 8, 2015) 41 | 42 | - Added: `* { box-sizing: border-box }` rule and declaration is prepended 43 | - Updated: Dependencies, documentation, and tests 44 | 45 | ### 1.1.0 (November 24, 2015) 46 | 47 | - Added: `link` selector becomes `:link`, `:visited` 48 | - Updated: Dependencies, documentation, and tests 49 | 50 | ### 1.0.0 (October 28, 2015) 51 | 52 | - Added: Initial version 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PostCSS Time Machine 2 | 3 | You want to help? You rock! Now, take a moment to be sure your contributions 4 | make sense to everyone else. 5 | 6 | ## Reporting Issues 7 | 8 | Found a problem? Want a new feature? 9 | 10 | - See if your issue or idea has [already been reported]. 11 | - Provide a [reduced test case] or a [live example]. 12 | 13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code. 14 | 15 | ## Submitting Pull Requests 16 | 17 | Pull requests are the greatest contributions, so be sure they are focused in 18 | scope and avoid unrelated commits. 19 | 20 | 1. To begin; [fork this project], clone your fork, and add our upstream. 21 | ```bash 22 | # Clone your fork of the repo into the current directory 23 | git clone git@github.com:YOUR_USER/postcss-time-machine.git 24 | 25 | # Navigate to the newly cloned directory 26 | cd postcss-time-machine 27 | 28 | # Assign the original repo to a remote called "upstream" 29 | git remote add upstream git@github.com:jonathantneal/postcss-time-machine.git 30 | 31 | # Install the tools necessary for testing 32 | npm install 33 | ``` 34 | 35 | 2. Create a branch for your feature or fix: 36 | ```bash 37 | # Move into a new branch for your feature 38 | git checkout -b feature/thing 39 | ``` 40 | ```bash 41 | # Move into a new branch for your fix 42 | git checkout -b fix/something 43 | ``` 44 | 45 | 3. If your code follows our practices, then push your feature branch: 46 | ```bash 47 | # Test current code 48 | npm test 49 | ``` 50 | ```bash 51 | # Push the branch for your new feature 52 | git push origin feature/thing 53 | ``` 54 | ```bash 55 | # Or, push the branch for your update 56 | git push origin update/something 57 | ``` 58 | 59 | That’s it! Now [open a pull request] with a clear title and description. 60 | 61 | [already been reported]: issues 62 | [fork this project]: fork 63 | [live example]: https://codepen.io/pen 64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/ 65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/ 66 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing PostCSS Time Machine 2 | 3 | [PostCSS Time Machine] runs in all Node environments, with special instructions for: 4 | 5 | | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | 6 | | --- | --- | --- | --- | --- | --- | 7 | 8 | ## Node 9 | 10 | Add [PostCSS Time Machine] to your project: 11 | 12 | ```bash 13 | npm install postcss-time-machine --save-dev 14 | ``` 15 | 16 | Use [PostCSS Time Machine] to process your CSS: 17 | 18 | ```js 19 | const postcssTimeMachine = require('postcss-time-machine'); 20 | 21 | postcssTimeMachine.process(YOUR_CSS /*, processOptions, pluginOptions */); 22 | ``` 23 | 24 | Or use it as a [PostCSS] plugin: 25 | 26 | ```js 27 | const postcss = require('postcss'); 28 | const postcssTimeMachine = require('postcss-time-machine'); 29 | 30 | postcss([ 31 | postcssTimeMachine(/* pluginOptions */) 32 | ]).process(YOUR_CSS /*, processOptions */); 33 | ``` 34 | 35 | ## PostCSS CLI 36 | 37 | Add [PostCSS CLI] to your project: 38 | 39 | ```bash 40 | npm install postcss-cli --save-dev 41 | ``` 42 | 43 | Use [PostCSS Time Machine] in your `postcss.config.js` configuration file: 44 | 45 | ```js 46 | const postcssTimeMachine = require('postcss-time-machine'); 47 | 48 | module.exports = { 49 | plugins: [ 50 | postcssTimeMachine(/* pluginOptions */) 51 | ] 52 | } 53 | ``` 54 | 55 | ## Webpack 56 | 57 | Add [PostCSS Loader] to your project: 58 | 59 | ```bash 60 | npm install postcss-loader --save-dev 61 | ``` 62 | 63 | Use [PostCSS Time Machine] in your Webpack configuration: 64 | 65 | ```js 66 | const postcssTimeMachine = require('postcss-time-machine'); 67 | 68 | module.exports = { 69 | module: { 70 | rules: [ 71 | { 72 | test: /\.css$/, 73 | use: [ 74 | 'style-loader', 75 | { loader: 'css-loader', options: { importLoaders: 1 } }, 76 | { loader: 'postcss-loader', options: { 77 | ident: 'postcss', 78 | plugins: () => [ 79 | postcssTimeMachine(/* pluginOptions */) 80 | ] 81 | } } 82 | ] 83 | } 84 | ] 85 | } 86 | } 87 | ``` 88 | 89 | ## Create React App 90 | 91 | Add [React App Rewired] and [React App Rewire PostCSS] to your project: 92 | 93 | ```bash 94 | npm install react-app-rewired react-app-rewire-postcss --save-dev 95 | ``` 96 | 97 | Use [React App Rewire PostCSS] and [PostCSS Time Machine] in your 98 | `config-overrides.js` file: 99 | 100 | ```js 101 | const reactAppRewirePostcss = require('react-app-rewire-postcss'); 102 | const postcssTimeMachine = require('postcss-time-machine'); 103 | 104 | module.exports = config => reactAppRewirePostcss(config, { 105 | plugins: () => [ 106 | postcssTimeMachine(/* pluginOptions */) 107 | ] 108 | }); 109 | ``` 110 | 111 | ## Gulp 112 | 113 | Add [Gulp PostCSS] to your project: 114 | 115 | ```bash 116 | npm install gulp-postcss --save-dev 117 | ``` 118 | 119 | Use [PostCSS Time Machine] in your Gulpfile: 120 | 121 | ```js 122 | const postcss = require('gulp-postcss'); 123 | const postcssTimeMachine = require('postcss-time-machine'); 124 | 125 | gulp.task('css', () => gulp.src('./src/*.css').pipe( 126 | postcss([ 127 | postcssTimeMachine(/* pluginOptions */) 128 | ]) 129 | ).pipe( 130 | gulp.dest('.') 131 | )); 132 | ``` 133 | 134 | ## Grunt 135 | 136 | Add [Grunt PostCSS] to your project: 137 | 138 | ```bash 139 | npm install grunt-postcss --save-dev 140 | ``` 141 | 142 | Use [PostCSS Time Machine] in your Gruntfile: 143 | 144 | ```js 145 | const postcssTimeMachine = require('postcss-time-machine'); 146 | 147 | grunt.loadNpmTasks('grunt-postcss'); 148 | 149 | grunt.initConfig({ 150 | postcss: { 151 | options: { 152 | use: [ 153 | postcssTimeMachine(/* pluginOptions */) 154 | ] 155 | }, 156 | dist: { 157 | src: '*.css' 158 | } 159 | } 160 | }); 161 | ``` 162 | 163 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss 164 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss 165 | [PostCSS]: https://github.com/postcss/postcss 166 | [PostCSS CLI]: https://github.com/postcss/postcss-cli 167 | [PostCSS Loader]: https://github.com/postcss/postcss-loader 168 | [PostCSS Time Machine]: https://github.com/jonathantneal/postcss-time-machine 169 | [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss 170 | [React App Rewired]: https://github.com/timarney/react-app-rewired 171 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # CC0 1.0 Universal 2 | 3 | ## Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an “owner”) of an original work of 8 | authorship and/or a database (each, a “Work”). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific works 12 | (“Commons”) that the public can reliably and without fear of later claims of 13 | infringement build upon, modify, incorporate in other works, reuse and 14 | redistribute as freely as possible in any form whatsoever and for any purposes, 15 | including without limitation commercial purposes. These owners may contribute 16 | to the Commons to promote the ideal of a free culture and the further 17 | production of creative, cultural and scientific works, or to gain reputation or 18 | greater distribution for their Work in part through the use and efforts of 19 | others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation of 22 | additional consideration or compensation, the person associating CC0 with a 23 | Work (the “Affirmer”), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and 25 | publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights (“Copyright and 31 | Related Rights”). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 1. the right to reproduce, adapt, distribute, perform, display, communicate, 34 | and translate a Work; 35 | 2. moral rights retained by the original author(s) and/or performer(s); 36 | 3. publicity and privacy rights pertaining to a person’s image or likeness 37 | depicted in a Work; 38 | 4. rights protecting against unfair competition in regards to a Work, 39 | subject to the limitations in paragraph 4(i), below; 40 | 5. rights protecting the extraction, dissemination, use and reuse of data in 41 | a Work; 42 | 6. database rights (such as those arising under Directive 96/9/EC of the 43 | European Parliament and of the Council of 11 March 1996 on the legal 44 | protection of databases, and under any national implementation thereof, 45 | including any amended or successor version of such directive); and 46 | 7. other similar, equivalent or corresponding rights throughout the world 47 | based on applicable law or treaty, and any national implementations 48 | thereof. 49 | 50 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 51 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 52 | unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright 53 | and Related Rights and associated claims and causes of action, whether now 54 | known or unknown (including existing as well as future claims and causes of 55 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 56 | duration provided by applicable law or treaty (including future time 57 | extensions), (iii) in any current or future medium and for any number of 58 | copies, and (iv) for any purpose whatsoever, including without limitation 59 | commercial, advertising or promotional purposes (the “Waiver”). Affirmer 60 | makes the Waiver for the benefit of each member of the public at large and 61 | to the detriment of Affirmer’s heirs and successors, fully intending that 62 | such Waiver shall not be subject to revocation, rescission, cancellation, 63 | termination, or any other legal or equitable action to disrupt the quiet 64 | enjoyment of the Work by the public as contemplated by Affirmer’s express 65 | Statement of Purpose. 66 | 67 | 3. Public License Fallback. Should any part of the Waiver for any reason be 68 | judged legally invalid or ineffective under applicable law, then the Waiver 69 | shall be preserved to the maximum extent permitted taking into account 70 | Affirmer’s express Statement of Purpose. In addition, to the extent the 71 | Waiver is so judged Affirmer hereby grants to each affected person a 72 | royalty-free, non transferable, non sublicensable, non exclusive, 73 | irrevocable and unconditional license to exercise Affirmer’s Copyright and 74 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 75 | maximum duration provided by applicable law or treaty (including future time 76 | extensions), (iii) in any current or future medium and for any number of 77 | copies, and (iv) for any purpose whatsoever, including without limitation 78 | commercial, advertising or promotional purposes (the “License”). The License 79 | shall be deemed effective as of the date CC0 was applied by Affirmer to the 80 | Work. Should any part of the License for any reason be judged legally 81 | invalid or ineffective under applicable law, such partial invalidity or 82 | ineffectiveness shall not invalidate the remainder of the License, and in 83 | such case Affirmer hereby affirms that he or she will not (i) exercise any 84 | of his or her remaining Copyright and Related Rights in the Work or (ii) 85 | assert any associated claims and causes of action with respect to the Work, 86 | in either case contrary to Affirmer’s express Statement of Purpose. 87 | 88 | 4. Limitations and Disclaimers. 89 | 1. No trademark or patent rights held by Affirmer are waived, abandoned, 90 | surrendered, licensed or otherwise affected by this document. 91 | 2. Affirmer offers the Work as-is and makes no representations or warranties 92 | of any kind concerning the Work, express, implied, statutory or 93 | otherwise, including without limitation warranties of title, 94 | merchantability, fitness for a particular purpose, non infringement, or 95 | the absence of latent or other defects, accuracy, or the present or 96 | absence of errors, whether or not discoverable, all to the greatest 97 | extent permissible under applicable law. 98 | 3. Affirmer disclaims responsibility for clearing rights of other persons 99 | that may apply to the Work or any use thereof, including without 100 | limitation any person’s Copyright and Related Rights in the Work. 101 | Further, Affirmer disclaims responsibility for obtaining any necessary 102 | consents, permissions or other rights required for any use of the Work. 103 | 4. Affirmer understands and acknowledges that Creative Commons is not a 104 | party to this document and has no duty or obligation with respect to this 105 | CC0 or use of the Work. 106 | 107 | For more information, please see 108 | http://creativecommons.org/publicdomain/zero/1.0/. 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostCSS Time Machine [PostCSS][postcss] 2 | 3 | [![NPM Version][npm-img]][npm-url] 4 | [![Build Status][cli-img]][cli-url] 5 | [![Support Chat][git-img]][git-url] 6 | 7 | [PostCSS Time Machine] fixes mistakes in the design of CSS itself, as 8 | [described by the CSSWG](https://wiki.csswg.org/ideas/mistakes). 9 | 10 | They specifically requested that these should be corrected 11 | “*if anyone invents a time machine*”. 12 | 13 | ```bash 14 | npx postcss-time-machine SOURCE.css TRANSFORMED.css 15 | ``` 16 | 17 | ## Safe Fixes 18 | 19 | These fixes do not change the way CSS normally works. They can be individually 20 | disabled by passing their short name into the [`fixes` option](#fixes). 21 | 22 | ### border-box 23 | 24 | > Box-sizing should be `border-box` by default. 25 | 26 | ```css 27 | /* prepended to your css */ 28 | 29 | * { 30 | box-sizing: border-box; 31 | } 32 | ``` 33 | 34 | ### corner-radius 35 | 36 | > `border-radius` should be `corner-radius`. 37 | 38 | ```css 39 | button { 40 | corner-radius: 3px; 41 | } 42 | 43 | /* becomes */ 44 | 45 | button { 46 | border-radius: 3px; 47 | } 48 | ``` 49 | 50 | ### current-color 51 | 52 | > `currentcolor` should be `current-color`. 53 | 54 | ```css 55 | button { 56 | box-shadow: 0 0 5px solid current-color; 57 | } 58 | 59 | /* becomes */ 60 | 61 | button { 62 | box-shadow: 0 0 5px solid currentColor; 63 | } 64 | ``` 65 | 66 | ### display-type 67 | 68 | > The `display` property should be called `display-type`. 69 | 70 | ```css 71 | .some-component { 72 | display-type: grid; 73 | } 74 | 75 | /* becomes */ 76 | 77 | .some-component { 78 | display: grid; 79 | } 80 | ``` 81 | 82 | ### marker-style 83 | 84 | > The `list-style` properties should be called `marker-style`. 85 | 86 | ```css 87 | .georgian-list { 88 | marker-style: square; 89 | } 90 | 91 | /* becomes */ 92 | 93 | .georgian-list { 94 | list-style: square; 95 | } 96 | ``` 97 | 98 | ### no-wrap 99 | 100 | > In `white-space`, `nowrap` should be called `no-wrap`. 101 | 102 | ```css 103 | h1 { 104 | white-space: no-wrap; 105 | } 106 | 107 | /* becomes */ 108 | 109 | h1 { 110 | white-space: nowrap; 111 | } 112 | ``` 113 | 114 | ### overflow-wrap 115 | 116 | > `word-wrap`/`overflow-wrap` should not exist, and `overflow-wrap` should be a 117 | keyword on `white-space`. 118 | 119 | ```css 120 | a { 121 | white-space: overflow-wrap; 122 | } 123 | 124 | /* becomes */ 125 | 126 | a { 127 | word-wrap: break-word; 128 | } 129 | ``` 130 | 131 | ### rgb-hsl 132 | 133 | > `rgb()` and `hsl()` should have an optional fourth *alpha* parameter 134 | (which should use the same format as R, G, and B or S and L). 135 | 136 | ```css 137 | header { 138 | background-color: rgb(0, 0, 255, 102); 139 | color: hsl(170, 50%, 45%, 80%); 140 | } 141 | 142 | /* becomes */ 143 | 144 | header { 145 | background-color: rgba(0, 0, 255, .4); 146 | color: hsla(170, 50%, 45%, .8); 147 | } 148 | ``` 149 | 150 | ### text-middle 151 | 152 | > In `vertical-align`, `middle` should be called `text-middle`. 153 | 154 | ```css 155 | button { 156 | vertical-align: text-middle; 157 | } 158 | 159 | /* becomes */ 160 | 161 | button { 162 | vertical-align: middle; 163 | } 164 | ``` 165 | 166 | ### z-order 167 | 168 | > `z-index` should be called `z-order` or `depth`. 169 | 170 | ```css 171 | aside { 172 | depth: 10; 173 | } 174 | 175 | figure { 176 | z-order: 10; 177 | } 178 | 179 | /* becomes */ 180 | 181 | aside { 182 | z-index: 10; 183 | } 184 | 185 | figure { 186 | z-index: 10; 187 | } 188 | ``` 189 | 190 | ## Unsafe Fixes 191 | 192 | These fixes change the way CSS normally works. They can be individually 193 | enabled or disabled by passing their short name into the 194 | [`fixes` option](#fixes), or by setting the 195 | [`useUnsafeFixes`](#useunsafefixes) option to `false`. 196 | 197 | ### background-position 198 | 199 | > `background-position` and `border-spacing` (all 2-axis properties) should 200 | take *vertical* first, to match with the 4-direction properties like `margin`. 201 | 202 | ```css 203 | body { 204 | background-position: 0% 50%; 205 | } 206 | 207 | table { 208 | border-spacing: 10px 5px; 209 | } 210 | 211 | /* becomes */ 212 | 213 | body { 214 | background-position: 50% 0%; 215 | } 216 | 217 | table { 218 | border-spacing: 5px 10px; 219 | } 220 | ``` 221 | 222 | ### background-size 223 | 224 | > In `background-size`, having one value should duplicate its value, not 225 | default the second one to `auto`. 226 | 227 | ```css 228 | header { 229 | background-size: 75%; 230 | } 231 | 232 | /* becomes */ 233 | 234 | header { 235 | background-size: 75% 75%; 236 | } 237 | ``` 238 | 239 | ### line-height 240 | 241 | > `line-height: ` should compute to the equivalent 242 | `line-height: `, so that it effectively inherits as a percentage not 243 | a length. 244 | 245 | ```css 246 | p { 247 | line-height: 200%; 248 | } 249 | 250 | /* becomes */ 251 | 252 | p { 253 | line-height: 2; 254 | } 255 | ``` 256 | 257 | ### link-pseudo 258 | 259 | > `:link` should have had the `:any-link` semantics all along. 260 | 261 | ```css 262 | :link { 263 | color: blue; 264 | } 265 | 266 | /* becomes */ 267 | 268 | :link, :visited { 269 | color: blue; 270 | } 271 | ``` 272 | 273 | ## Advanced Usage 274 | 275 | Add [PostCSS Time Machine] to your project: 276 | 277 | ```bash 278 | npm install postcss-time-machine --save-dev 279 | ``` 280 | 281 | Use [PostCSS Time Machine] to process your CSS: 282 | 283 | ```js 284 | const postcssTimeMachine = require('postcss-time-machine'); 285 | 286 | postcssTimeMachine.process(YOUR_CSS /*, processOptions, pluginOptions */); 287 | ``` 288 | 289 | Or use it as a [PostCSS] plugin: 290 | 291 | ```js 292 | const postcss = require('postcss'); 293 | const postcssTimeMachine = require('postcss-time-machine'); 294 | 295 | postcss([ 296 | postcssTimeMachine(/* pluginOptions */) 297 | ]).process(YOUR_CSS /*, processOptions */); 298 | ``` 299 | 300 | [PostCSS Time Machine] runs in all Node environments, with special instructions for: 301 | 302 | | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | 303 | | --- | --- | --- | --- | --- | --- | 304 | 305 | ## Options 306 | 307 | ### fixes 308 | 309 | The `fixes` option lets you individually enable or disable individual fixes. 310 | 311 | ```js 312 | postcssTimeMachine({ 313 | fixes: { 314 | 'border-box': false // disables adding * { box-sizing: border-box; } 315 | } 316 | }) 317 | ``` 318 | 319 | ### useUnsafeFixes 320 | 321 | The `useUnsafeFixes` option determines whether unsafe fixes will be applied or 322 | not. Individual features passed into the `fixes` option will override this. By 323 | default, unsafe features are enabled. 324 | 325 | ```js 326 | postcssTimeMachine({ 327 | useUnsafeFixes: false // disables background-position, background-size, and line-height 328 | }) 329 | ``` 330 | 331 | [cli-img]: https://img.shields.io/travis/jonathantneal/postcss-time-machine.svg 332 | [cli-url]: https://travis-ci.org/jonathantneal/postcss-time-machine 333 | [git-img]: https://img.shields.io/badge/support-chat-blue.svg 334 | [git-url]: https://gitter.im/postcss/postcss 335 | [npm-img]: https://img.shields.io/npm/v/postcss-time-machine.svg 336 | [npm-url]: https://www.npmjs.com/package/postcss-time-machine 337 | 338 | [PostCSS]: https://github.com/postcss/postcss 339 | [PostCSS Time Machine]: https://github.com/jonathantneal/postcss-time-machine 340 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var fs = _interopDefault(require('fs')); 6 | var plugin = _interopDefault(require('.')); 7 | 8 | const fileRegExp = /^[\w\/.]+$/; 9 | const argRegExp = /^--(\w+)=("|')?(.+)\2$/; 10 | const relaxedJsonPropRegExp = /(['"])?([a-z0-9A-Z_]+)(['"])?:/g; 11 | const relaxedJsonValueRegExp = /("[a-z0-9A-Z_]+":\s*)(?!true|false|null|\d+)'?([A-z0-9]+)'?([,}])/g; 12 | const argo = process.argv.slice(2).reduce((object, arg) => { 13 | const argMatch = arg.match(argRegExp); 14 | const fileMatch = arg.match(fileRegExp); 15 | 16 | if (argMatch) { 17 | object[argMatch[1]] = argMatch[3]; 18 | } else if (fileMatch) { 19 | if (object.from === '') { 20 | object.from = arg; 21 | } else if (object.to === '') { 22 | object.to = arg; 23 | } 24 | } 25 | 26 | return object; 27 | }, { 28 | from: '', 29 | to: '', 30 | opts: 'null' 31 | }); // get css from command line arguments or stdin 32 | 33 | (argo.from === '' ? getStdin() : readFile(argo.from)).then(css => { 34 | if (argo.from === '' && !css) { 35 | console.log(['PostCSS Time Machine\n', ' Fixes mistakes in the design of CSS itself\n', 'Usage:\n', ' postcss-time-machine source.css transformed.css', ' postcss-time-machine --from=source.css --to=transformed.css --opts={}', ' echo "body:has(:focus) {}" | postcss-time-machine\n'].join('\n')); 36 | process.exit(0); 37 | } 38 | 39 | const pluginOpts = JSON.parse(argo.opts.replace(relaxedJsonPropRegExp, '"$2": ').replace(relaxedJsonValueRegExp, '$1"$2"$3')); 40 | const processOptions = Object.assign({ 41 | from: argo.from, 42 | to: argo.to || argo.from 43 | }, argo.map ? { 44 | map: JSON.parse(argo.map) 45 | } : {}); 46 | const result = plugin.process(css, processOptions, pluginOpts); 47 | 48 | if (argo.to === '') { 49 | return result.css; 50 | } else { 51 | return writeFile(argo.to, result.css).then(() => `CSS was written to "${argo.to}"`); 52 | } 53 | }).catch(error => { 54 | if (Object(error).name === 'CssSyntaxError') { 55 | throw new Error(`PostCSS had trouble reading the file (${error.reason} on line ${error.line}, column ${error.column}).`); 56 | } 57 | 58 | if (Object(error).errno === -2) { 59 | throw new Error(`Sorry, "${error.path}" could not be read.`); 60 | } 61 | 62 | throw error; 63 | }).then(result => { 64 | console.log(result); 65 | process.exit(0); 66 | }, error => { 67 | console.error(Object(error).message || 'Something bad happened and we don’t even know what it was.'); 68 | process.exit(1); 69 | }); 70 | 71 | function readFile(pathname) { 72 | return new Promise((resolve, reject) => { 73 | fs.readFile(pathname, 'utf8', (error, data) => { 74 | if (error) { 75 | reject(error); 76 | } else { 77 | resolve(data); 78 | } 79 | }); 80 | }); 81 | } 82 | 83 | function writeFile(pathname, data) { 84 | return new Promise((resolve, reject) => { 85 | fs.writeFile(pathname, data, (error, content) => { 86 | if (error) { 87 | reject(error); 88 | } else { 89 | resolve(content); 90 | } 91 | }); 92 | }); 93 | } 94 | 95 | function getStdin() { 96 | return new Promise(resolve => { 97 | let data = ''; 98 | 99 | if (process.stdin.isTTY) { 100 | resolve(data); 101 | } else { 102 | process.stdin.setEncoding('utf8'); 103 | process.stdin.on('readable', () => { 104 | let chunk; 105 | 106 | while (chunk = process.stdin.read()) { 107 | data += chunk; 108 | } 109 | }); 110 | process.stdin.on('end', () => { 111 | resolve(data); 112 | }); 113 | } 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var valueParser = _interopDefault(require('postcss-values-parser')); 6 | var selectorParser = _interopDefault(require('postcss-selector-parser')); 7 | var postcss = _interopDefault(require('postcss')); 8 | 9 | function parseSelector(selector) { 10 | let selectorAST; 11 | selectorParser(selectors => { 12 | selectorAST = selectors; 13 | }).processSync(selector); 14 | return selectorAST; 15 | } 16 | 17 | function prependBoxSizingBorderBox(root) { 18 | const input = { 19 | css: '* { box-sizing: border-box }', 20 | file: 'postcss-time-machine' 21 | }; 22 | const anyRule = postcss.rule({ 23 | selector: '*', 24 | source: { 25 | input: input, 26 | start: { 27 | line: 1, 28 | column: 1 29 | } 30 | } 31 | }); 32 | const borderBoxDecl = postcss.decl({ 33 | prop: 'box-sizing', 34 | value: 'border-box', 35 | source: { 36 | input: input, 37 | start: { 38 | line: 1, 39 | column: 4 40 | } 41 | } 42 | }); 43 | anyRule.append(borderBoxDecl); // get the last import at-rule 44 | 45 | let lastImport; 46 | root.nodes.forEach(node => { 47 | if (node.type === 'atrule') { 48 | lastImport = node; 49 | } 50 | }); 51 | 52 | if (lastImport) { 53 | // insert the rule after last import at-rule 54 | lastImport.after(anyRule); 55 | } else { 56 | // otherwise, add the rule first to the tree 57 | root.prepend(anyRule); 58 | } 59 | } 60 | 61 | function transformDeclWithBackgroundPosition(node) { 62 | if (node.type === 'decl' && backgroundPositionRegExp.test(node.prop) && doubleAxisValue.test(node.value)) { 63 | node.value = node.value.replace(doubleAxisValue, '$3$2$1'); 64 | } 65 | } 66 | const backgroundPositionRegExp = /^(background-position|border-spacing)$/i; 67 | const doubleAxisValue = /^([^\s]+)(\s+)([^\s]+)$/; 68 | 69 | function transformWordWithBackgroundSize(node) { 70 | if (node.type === 'decl' && backgroundSizeRegExp.test(node.prop) && singleAxisValueRegExp.test(node.value) && !ignoredValuesRegExp.test(node.value)) { 71 | node.value = node.value.replace(singleAxisValueRegExp, '$& $&'); 72 | } 73 | } 74 | const backgroundSizeRegExp = /^background-size$/i; 75 | const ignoredValuesRegExp = /^(contain|cover)$/i; 76 | const singleAxisValueRegExp = /^[^\s]+$/; 77 | 78 | function transformDeclWithCornerRadius(node) { 79 | if (node.type === 'decl' && cornerRadiusRegExp.test(node.prop)) { 80 | node.prop = 'border-radius'; 81 | } 82 | } 83 | const cornerRadiusRegExp = /^corner-radius$/i; 84 | 85 | function transformDeclWithDepthOrZOrder(node) { 86 | if (node.type === 'decl' && depthOrZOrderRegExp.test(node.prop)) { 87 | node.prop = 'z-index'; 88 | } 89 | } 90 | const depthOrZOrderRegExp = /^(depth|z-order)$/i; 91 | 92 | function transformDeclWithDisplayType(node) { 93 | if (node.type === 'decl' && displayTypeRegExp.test(node.prop)) { 94 | node.prop = 'display'; 95 | } 96 | } 97 | const displayTypeRegExp = /^display-type$/i; 98 | 99 | function transformDeclWithLineHeight(node) { 100 | if (node.type === 'decl' && lineHeightRegExp.test(node.prop) && percentageHeight.test(node.value)) { 101 | node.value = String(node.value.replace(percentageHeight, '$1') / 100); 102 | } 103 | } 104 | const lineHeightRegExp = /^line-height$/i; 105 | const percentageHeight = /^(\d+)%$/; 106 | 107 | function transformDeclWithMarkerStyle(node) { 108 | if (node.type === 'decl' && markerStyleRegExp.test(node.prop)) { 109 | node.prop = 'list-style'; 110 | } 111 | } 112 | const markerStyleRegExp = /^marker-style$/i; 113 | 114 | function transformDeclWithVerticalAlignTextMiddle(node) { 115 | if (node.type === 'decl' && verticalAlignRegExp.test(node.prop) && textMiddleRegExp.test(node.value)) { 116 | node.value = 'middle'; 117 | } 118 | } 119 | const verticalAlignRegExp = /^vertical-align$/i; 120 | const textMiddleRegExp = /^text-middle$/i; 121 | 122 | function transformDeclWithWhiteSpaceNoWrap(node) { 123 | if (node.type === 'decl' && whiteSpaceRegExp.test(node.prop) && noWrapRegExp.test(node.value)) { 124 | node.value = 'nowrap'; 125 | } 126 | } 127 | const whiteSpaceRegExp = /^white-space$/i; 128 | const noWrapRegExp = /^no-wrap$/i; 129 | 130 | function transformDeclWithWhiteSpaceOverflowWrap(node) { 131 | if (node.type === 'decl' && whiteSpaceRegExp$1.test(node.prop) && overflowWrapRegExp.test(node.value)) { 132 | node.prop = 'word-wrap'; 133 | node.value = 'break-word'; 134 | } 135 | } 136 | const whiteSpaceRegExp$1 = /^white-space$/i; 137 | const overflowWrapRegExp = /^overflow-wrap$/i; 138 | 139 | function transformFunctionWithRgbOrHslAndFourthValue(node) { 140 | if (node.type === 'func' && rgbOrHslRegExp.test(node.value) && Object(node.nodes).length === 9) { 141 | node.value += 'a'; 142 | node.nodes[7].value = node.nodes[7].value.replace(/^(\d+)$/, ($0, value) => { 143 | if (node.nodes[7].unit === '%') { 144 | node.nodes[7].unit = ''; 145 | return value / 100; 146 | } else if (node.nodes[7].unit === '') { 147 | return value / 255; 148 | } 149 | 150 | return $0; 151 | }); 152 | } 153 | } 154 | const rgbOrHslRegExp = /^(rgb|hsl)$/i; 155 | 156 | function transformSelectorWithLink(node) { 157 | if (node.type === 'pseudo' && linkRegExp.test(node.value)) { 158 | node.value = ':visited'; 159 | const selector = node.parent; 160 | const list = selector.parent; 161 | const index = list.nodes.indexOf(selector) + 1; 162 | list.nodes.splice(index, 0, selector.clone()); 163 | node.value = ':link'; 164 | } 165 | } 166 | const linkRegExp = /^:link$/i; 167 | 168 | function transformWordWithNoWrap(node) { 169 | if (node.type === 'word' && currentColorRegExp.test(node.value)) { 170 | node.value = 'currentColor'; 171 | } 172 | } 173 | const currentColorRegExp = /^current-color$/i; 174 | 175 | const features = { 176 | 'background-position': { 177 | isSafe: false, 178 | type: 'declaration', 179 | transform: transformDeclWithBackgroundPosition 180 | }, 181 | 'background-size': { 182 | isSafe: false, 183 | type: 'declaration', 184 | transform: transformWordWithBackgroundSize 185 | }, 186 | 'border-box': { 187 | isSafe: true, 188 | type: 'root', 189 | transform: prependBoxSizingBorderBox 190 | }, 191 | 'corner-radius': { 192 | isSafe: true, 193 | type: 'declaration', 194 | transform: transformDeclWithCornerRadius 195 | }, 196 | 'current-color': { 197 | isSafe: true, 198 | type: 'value', 199 | transform: transformWordWithNoWrap 200 | }, 201 | 'display-type': { 202 | isSafe: true, 203 | type: 'declaration', 204 | transform: transformDeclWithDisplayType 205 | }, 206 | 'line-height': { 207 | isSafe: false, 208 | type: 'declaration', 209 | transform: transformDeclWithLineHeight 210 | }, 211 | 'link-pseudo': { 212 | isSafe: false, 213 | type: 'selector', 214 | transform: transformSelectorWithLink 215 | }, 216 | 'marker-style': { 217 | isSafe: true, 218 | type: 'declaration', 219 | transform: transformDeclWithMarkerStyle 220 | }, 221 | 'no-wrap': { 222 | isSafe: true, 223 | type: 'declaration', 224 | transform: transformDeclWithWhiteSpaceNoWrap 225 | }, 226 | 'overflow-wrap': { 227 | isSafe: true, 228 | type: 'declaration', 229 | transform: transformDeclWithWhiteSpaceOverflowWrap 230 | }, 231 | 'rgb-hsl': { 232 | isSafe: true, 233 | type: 'value', 234 | transform: transformFunctionWithRgbOrHslAndFourthValue 235 | }, 236 | 'text-middle': { 237 | isSafe: true, 238 | type: 'declaration', 239 | transform: transformDeclWithVerticalAlignTextMiddle 240 | }, 241 | 'z-order': { 242 | isSafe: true, 243 | type: 'declaration', 244 | transform: transformDeclWithDepthOrZOrder 245 | } 246 | }; 247 | const featuresKeys = Object.keys(features); 248 | var index = postcss.plugin('postcss-time-machine', rawopts => { 249 | const opts = { 250 | fixes: Object(Object(rawopts).fixes), 251 | useUnsafeFixes: 'useUnsafeFixes' in Object(rawopts) ? Boolean(rawopts.useUnsafeFixes) : true 252 | }; 253 | return root => { 254 | root.walk(node => { 255 | if (node.type === 'rule') { 256 | // transform by rule selector 257 | const selectorAST = parseSelector(node.selector); 258 | selectorAST.walk(selectorNode => { 259 | transformNode(selectorNode, 'selector', opts); 260 | }); 261 | const modifiedSelector = String(selectorAST); 262 | 263 | if (node.selector !== modifiedSelector) { 264 | node.selector = modifiedSelector; 265 | } 266 | } else if (node.type === 'decl') { 267 | // transform by decl prop 268 | transformNode(node, 'declaration', opts); // transform by decl value 269 | 270 | const valueAST = valueParser(node.value).parse(); 271 | valueAST.walk(valueNode => { 272 | transformNode(valueNode, 'value', opts); 273 | }); 274 | const modifiedValue = String(valueAST); 275 | 276 | if (node.value !== modifiedValue) { 277 | node.value = modifiedValue; 278 | } 279 | } 280 | }); // prepend box-sizing 281 | 282 | transformNode(root, 'root', opts); 283 | }; 284 | }); 285 | 286 | function transformNode(node, type, opts) { 287 | featuresKeys.forEach(key => { 288 | const feature = features[key]; 289 | const shouldRunTransfrom = feature.type === type && (Boolean(opts.fixes[key]) || !(key in opts.fixes) && (feature.isSafe || opts.useUnsafeFixes)); 290 | 291 | if (shouldRunTransfrom) { 292 | feature.transform(node); 293 | } 294 | }); 295 | } 296 | 297 | module.exports = index; 298 | //# sourceMappingURL=index.js.map 299 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-time-machine", 3 | "version": "4.0.0", 4 | "description": "Fix mistakes in the design of CSS itself", 5 | "author": "Jonathan Neal ", 6 | "license": "CC0-1.0", 7 | "repository": "jonathantneal/postcss-time-machine", 8 | "homepage": "https://github.com/jonathantneal/postcss-time-machine#readme", 9 | "bugs": "https://github.com/jonathantneal/postcss-time-machine/issues", 10 | "main": "index.js", 11 | "module": "index.mjs", 12 | "bin": { 13 | "postcss-time-machine": "cli.js" 14 | }, 15 | "files": [ 16 | "cli.js", 17 | "index.js", 18 | "index.js.map", 19 | "index.mjs", 20 | "index.mjs.map" 21 | ], 22 | "scripts": { 23 | "prepublish": "npm test", 24 | "pretest:cli": "cross-env NODE_ENV=cli rollup -c .rollup.js --silent", 25 | "pretest:postcss": "rollup -c .rollup.js --silent", 26 | "test": "npm run test:js && npm run test:cli && npm run test:postcss", 27 | "test:cli": "node cli test/basic.css test/basic.expect.css", 28 | "test:js": "eslint src/*.js --cache --ignore-path .gitignore --quiet", 29 | "test:postcss": "postcss-tape" 30 | }, 31 | "engines": { 32 | "node": ">=6.0.0" 33 | }, 34 | "dependencies": { 35 | "postcss": "^7.0.6", 36 | "postcss-selector-parser": "^5.0.0-rc.4", 37 | "postcss-values-parser": "^2.0.0" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.1.6", 41 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 42 | "@babel/preset-env": "^7.1.6", 43 | "babel-eslint": "^10.0.1", 44 | "cross-env": "^5.2.0", 45 | "eslint": "^5.9.0", 46 | "eslint-config-dev": "^2.0.0", 47 | "postcss-tape": "^3.0.0-rc.2", 48 | "pre-commit": "^1.2.2", 49 | "rollup": "^0.67.3", 50 | "rollup-plugin-babel": "^4.0.3" 51 | }, 52 | "eslintConfig": { 53 | "extends": "dev", 54 | "parser": "babel-eslint" 55 | }, 56 | "keywords": [ 57 | "postcss", 58 | "css", 59 | "postcss-plugin", 60 | "csswg", 61 | "mistakes", 62 | "fixes", 63 | "corrections", 64 | "box-sizings", 65 | "white-spaces", 66 | "vertical-aligns", 67 | "currents", 68 | "colors", 69 | "backgrounds", 70 | "positions", 71 | "sizes", 72 | "corners", 73 | "radius", 74 | "depths", 75 | "z-index", 76 | "vertical", 77 | "align", 78 | "text", 79 | "middle", 80 | "white", 81 | "spaces", 82 | "nowraps", 83 | "break", 84 | "words", 85 | "rgba", 86 | "hsla", 87 | "links", 88 | "any-link", 89 | "visited" 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import plugin from '.'; 3 | 4 | // get process and plugin options from the command line 5 | const fileRegExp = /^[\w\/.]+$/; 6 | const argRegExp = /^--(\w+)=("|')?(.+)\2$/; 7 | const relaxedJsonPropRegExp = /(['"])?([a-z0-9A-Z_]+)(['"])?:/g; 8 | const relaxedJsonValueRegExp = /("[a-z0-9A-Z_]+":\s*)(?!true|false|null|\d+)'?([A-z0-9]+)'?([,}])/g; 9 | const argo = process.argv.slice(2).reduce( 10 | (object, arg) => { 11 | const argMatch = arg.match(argRegExp); 12 | const fileMatch = arg.match(fileRegExp); 13 | 14 | if (argMatch) { 15 | object[argMatch[1]] = argMatch[3]; 16 | } else if (fileMatch) { 17 | if (object.from === '') { 18 | object.from = arg; 19 | } else if (object.to === '') { 20 | object.to = arg; 21 | } 22 | } 23 | 24 | return object; 25 | }, 26 | { from: '', to: '', opts: 'null' } 27 | ); 28 | 29 | // get css from command line arguments or stdin 30 | (argo.from === '' ? getStdin() : readFile(argo.from)) 31 | .then(css => { 32 | if (argo.from === '' && !css) { 33 | console.log([ 34 | 'PostCSS Time Machine\n', 35 | ' Fixes mistakes in the design of CSS itself\n', 36 | 'Usage:\n', 37 | ' postcss-time-machine source.css transformed.css', 38 | ' postcss-time-machine --from=source.css --to=transformed.css --opts={}', 39 | ' echo "body:has(:focus) {}" | postcss-time-machine\n' 40 | ].join('\n')); 41 | 42 | process.exit(0); 43 | } 44 | 45 | const pluginOpts = JSON.parse( 46 | argo.opts 47 | .replace(relaxedJsonPropRegExp, '"$2": ') 48 | .replace(relaxedJsonValueRegExp, '$1"$2"$3') 49 | ); 50 | const processOptions = Object.assign({ from: argo.from, to: argo.to || argo.from }, argo.map ? { map: JSON.parse(argo.map) } : {}); 51 | 52 | const result = plugin.process(css, processOptions, pluginOpts); 53 | 54 | if (argo.to === '') { 55 | return result.css; 56 | } else { 57 | return writeFile(argo.to, result.css).then( 58 | () => `CSS was written to "${argo.to}"` 59 | ) 60 | } 61 | }).catch( 62 | error => { 63 | if (Object(error).name === 'CssSyntaxError') { 64 | throw new Error(`PostCSS had trouble reading the file (${error.reason} on line ${error.line}, column ${error.column}).`); 65 | } 66 | 67 | if (Object(error).errno === -2) { 68 | throw new Error(`Sorry, "${error.path}" could not be read.`); 69 | } 70 | 71 | throw error; 72 | } 73 | ).then( 74 | result => { 75 | console.log(result); 76 | 77 | process.exit(0); 78 | }, 79 | error => { 80 | console.error(Object(error).message || 'Something bad happened and we don’t even know what it was.'); 81 | 82 | process.exit(1); 83 | } 84 | ); 85 | 86 | function readFile(pathname) { 87 | return new Promise((resolve, reject) => { 88 | fs.readFile(pathname, 'utf8', (error, data) => { 89 | if (error) { 90 | reject(error); 91 | } else { 92 | resolve(data); 93 | } 94 | }); 95 | }); 96 | } 97 | 98 | function writeFile(pathname, data) { 99 | return new Promise((resolve, reject) => { 100 | fs.writeFile(pathname, data, (error, content) => { 101 | if (error) { 102 | reject(error); 103 | } else { 104 | resolve(content); 105 | } 106 | }); 107 | }); 108 | } 109 | 110 | function getStdin() { 111 | return new Promise(resolve => { 112 | let data = ''; 113 | 114 | if (process.stdin.isTTY) { 115 | resolve(data); 116 | } else { 117 | process.stdin.setEncoding('utf8'); 118 | 119 | process.stdin.on('readable', () => { 120 | let chunk; 121 | 122 | while (chunk = process.stdin.read()) { 123 | data += chunk; 124 | } 125 | }); 126 | 127 | process.stdin.on('end', () => { 128 | resolve(data); 129 | }); 130 | } 131 | }); 132 | } 133 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import valueParser from 'postcss-values-parser'; 3 | import selectorParser from './lib/parse-selector'; 4 | 5 | import prependBoxSizingBorderBox from './lib/safe/prepend-box-sizing-border-box'; 6 | 7 | import transformDeclWithBackgroundPositionOrBorderSpacing from './lib/dangerous/transform-decl-with-background-position-or-border-spacing'; 8 | import transformDeclWithBackgroundSize from './lib/dangerous/transform-decl-with-background-size'; 9 | import transformDeclWithCornerRadius from './lib/safe/transform-decl-with-corner-radius'; 10 | import transformDeclWithDepthOrZOrder from './lib/safe/transform-decl-with-depth-or-z-order'; 11 | import transformDeclWithDisplayType from './lib/safe/transform-decl-with-display-type'; 12 | import transformDeclWithLineHeight from './lib/dangerous/transform-decl-with-line-height'; 13 | import transformDeclWithMarkerStyle from './lib/safe/transform-decl-with-marker-style'; 14 | import transformDeclWithVerticalAlignTextMiddle from './lib/safe/transform-decl-with-vertical-align-text-middle'; 15 | import transformDeclWithWhiteSpaceNoWrap from './lib/safe/transform-decl-with-white-space-no-wrap'; 16 | import transformDeclWithWhiteSpaceOverflowWrap from './lib/safe/transform-decl-with-white-space-overflow-wrap'; 17 | import transformFunctionWithRgbOrHslAndFourthValue from './lib/safe/transform-function-with-rgb-or-hsl-and-fourth-value'; 18 | import transformSelectorWithLink from './lib/dangerous/transform-selector-with-link'; 19 | import transformWordWithCurrentColor from './lib/safe/transform-word-with-current-color'; 20 | 21 | const features = { 22 | 'background-position': { 23 | isSafe: false, 24 | type: 'declaration', 25 | transform: transformDeclWithBackgroundPositionOrBorderSpacing 26 | }, 27 | 'background-size': { 28 | isSafe: false, 29 | type: 'declaration', 30 | transform: transformDeclWithBackgroundSize 31 | }, 32 | 'border-box': { 33 | isSafe: true, 34 | type: 'root', 35 | transform: prependBoxSizingBorderBox 36 | }, 37 | 'corner-radius': { 38 | isSafe: true, 39 | type: 'declaration', 40 | transform: transformDeclWithCornerRadius 41 | }, 42 | 'current-color': { 43 | isSafe: true, 44 | type: 'value', 45 | transform: transformWordWithCurrentColor 46 | }, 47 | 'display-type': { 48 | isSafe: true, 49 | type: 'declaration', 50 | transform: transformDeclWithDisplayType 51 | }, 52 | 'line-height': { 53 | isSafe: false, 54 | type: 'declaration', 55 | transform: transformDeclWithLineHeight 56 | }, 57 | 'link-pseudo': { 58 | isSafe: false, 59 | type: 'selector', 60 | transform: transformSelectorWithLink 61 | }, 62 | 'marker-style': { 63 | isSafe: true, 64 | type: 'declaration', 65 | transform: transformDeclWithMarkerStyle 66 | }, 67 | 'no-wrap': { 68 | isSafe: true, 69 | type: 'declaration', 70 | transform: transformDeclWithWhiteSpaceNoWrap 71 | }, 72 | 'overflow-wrap': { 73 | isSafe: true, 74 | type: 'declaration', 75 | transform: transformDeclWithWhiteSpaceOverflowWrap 76 | }, 77 | 'rgb-hsl': { 78 | isSafe: true, 79 | type: 'value', 80 | transform: transformFunctionWithRgbOrHslAndFourthValue 81 | }, 82 | 'text-middle': { 83 | isSafe: true, 84 | type: 'declaration', 85 | transform: transformDeclWithVerticalAlignTextMiddle 86 | }, 87 | 'z-order': { 88 | isSafe: true, 89 | type: 'declaration', 90 | transform: transformDeclWithDepthOrZOrder 91 | } 92 | }; 93 | const featuresKeys = Object.keys(features); 94 | 95 | export default postcss.plugin('postcss-time-machine', rawopts => { 96 | const opts = { 97 | fixes: Object(Object(rawopts).fixes), 98 | useUnsafeFixes: 'useUnsafeFixes' in Object(rawopts) ? Boolean(rawopts.useUnsafeFixes) : true 99 | }; 100 | 101 | return root => { 102 | root.walk(node => { 103 | if (node.type === 'rule') { 104 | // transform by rule selector 105 | const selectorAST = selectorParser(node.selector); 106 | 107 | selectorAST.walk(selectorNode => { 108 | transformNode(selectorNode, 'selector', opts); 109 | }); 110 | 111 | const modifiedSelector = String(selectorAST); 112 | 113 | if (node.selector !== modifiedSelector) { 114 | node.selector = modifiedSelector; 115 | } 116 | } else if (node.type === 'decl') { 117 | // transform by decl prop 118 | transformNode(node, 'declaration', opts); 119 | 120 | // transform by decl value 121 | const valueAST = valueParser(node.value).parse(); 122 | 123 | valueAST.walk(valueNode => { 124 | transformNode(valueNode, 'value', opts); 125 | }); 126 | 127 | const modifiedValue = String(valueAST); 128 | 129 | if (node.value !== modifiedValue) { 130 | node.value = modifiedValue; 131 | } 132 | } 133 | }); 134 | 135 | // prepend box-sizing 136 | transformNode(root, 'root', opts); 137 | }; 138 | }); 139 | 140 | function transformNode(node, type, opts) { 141 | featuresKeys.forEach(key => { 142 | const feature = features[key]; 143 | const shouldRunTransfrom = 144 | feature.type === type && 145 | ( 146 | Boolean(opts.fixes[key]) || 147 | !(key in opts.fixes) && 148 | (feature.isSafe || opts.useUnsafeFixes) 149 | ); 150 | 151 | if (shouldRunTransfrom) { 152 | feature.transform(node); 153 | } 154 | }); 155 | } 156 | -------------------------------------------------------------------------------- /src/lib/dangerous/transform-decl-with-background-position-or-border-spacing.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithBackgroundPosition(node) { 2 | if (node.type === 'decl' && backgroundPositionRegExp.test(node.prop) && doubleAxisValue.test(node.value)) { 3 | node.value = node.value.replace(doubleAxisValue, '$3$2$1'); 4 | } 5 | } 6 | 7 | const backgroundPositionRegExp = /^(background-position|border-spacing)$/i; 8 | const doubleAxisValue = /^([^\s]+)(\s+)([^\s]+)$/; 9 | -------------------------------------------------------------------------------- /src/lib/dangerous/transform-decl-with-background-size.js: -------------------------------------------------------------------------------- 1 | export default function transformWordWithBackgroundSize(node) { 2 | if (node.type === 'decl' && backgroundSizeRegExp.test(node.prop) && singleAxisValueRegExp.test(node.value) && !ignoredValuesRegExp.test(node.value)) { 3 | node.value = node.value.replace(singleAxisValueRegExp, '$& $&'); 4 | } 5 | } 6 | 7 | const backgroundSizeRegExp = /^background-size$/i; 8 | const ignoredValuesRegExp = /^(contain|cover)$/i; 9 | const singleAxisValueRegExp = /^[^\s]+$/; 10 | -------------------------------------------------------------------------------- /src/lib/dangerous/transform-decl-with-line-height.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithLineHeight(node) { 2 | if (node.type === 'decl' && lineHeightRegExp.test(node.prop) && percentageHeight.test(node.value)) { 3 | node.value = String(node.value.replace(percentageHeight, '$1') / 100); 4 | } 5 | } 6 | 7 | const lineHeightRegExp = /^line-height$/i; 8 | const percentageHeight = /^(\d+)%$/; 9 | -------------------------------------------------------------------------------- /src/lib/dangerous/transform-selector-with-link.js: -------------------------------------------------------------------------------- 1 | export default function transformSelectorWithLink(node) { 2 | if (node.type === 'pseudo' && linkRegExp.test(node.value)) { 3 | node.value = ':visited'; 4 | 5 | const selector = node.parent; 6 | const list = selector.parent; 7 | const index = list.nodes.indexOf(selector) + 1; 8 | 9 | list.nodes.splice(index, 0, selector.clone()); 10 | 11 | node.value = ':link'; 12 | } 13 | } 14 | 15 | const linkRegExp = /^:link$/i; 16 | -------------------------------------------------------------------------------- /src/lib/parse-selector.js: -------------------------------------------------------------------------------- 1 | import selectorParser from 'postcss-selector-parser'; 2 | 3 | export default function parseSelector(selector) { 4 | let selectorAST; 5 | 6 | selectorParser(selectors => { 7 | selectorAST = selectors; 8 | }).processSync(selector); 9 | 10 | return selectorAST 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/safe/prepend-box-sizing-border-box.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | 3 | export default function prependBoxSizingBorderBox(root) { 4 | const input = { 5 | css: '* { box-sizing: border-box }', 6 | file: 'postcss-time-machine' 7 | }; 8 | 9 | const anyRule = postcss.rule({ 10 | selector: '*', 11 | source: { 12 | input: input, 13 | start: { 14 | line: 1, 15 | column: 1 16 | } 17 | } 18 | }); 19 | const borderBoxDecl = postcss.decl({ 20 | prop: 'box-sizing', 21 | value: 'border-box', 22 | source: { 23 | input: input, 24 | start: { 25 | line: 1, 26 | column: 4 27 | } 28 | } 29 | }); 30 | 31 | anyRule.append(borderBoxDecl); 32 | 33 | // get the last import at-rule 34 | let lastImport; 35 | 36 | root.nodes.forEach(node => { 37 | if (node.type === 'atrule') { 38 | lastImport = node; 39 | } 40 | }); 41 | 42 | if (lastImport) { 43 | // insert the rule after last import at-rule 44 | lastImport.after(anyRule); 45 | } else { 46 | // otherwise, add the rule first to the tree 47 | root.prepend(anyRule); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-corner-radius.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithCornerRadius(node) { 2 | if (node.type === 'decl' && cornerRadiusRegExp.test(node.prop)) { 3 | node.prop = 'border-radius'; 4 | } 5 | } 6 | 7 | const cornerRadiusRegExp = /^corner-radius$/i; 8 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-depth-or-z-order.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithDepthOrZOrder(node) { 2 | if (node.type === 'decl' && depthOrZOrderRegExp.test(node.prop)) { 3 | node.prop = 'z-index'; 4 | } 5 | } 6 | 7 | const depthOrZOrderRegExp = /^(depth|z-order)$/i; 8 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-display-type.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithDisplayType(node) { 2 | if (node.type === 'decl' && displayTypeRegExp.test(node.prop)) { 3 | node.prop = 'display'; 4 | } 5 | } 6 | 7 | const displayTypeRegExp = /^display-type$/i; 8 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-marker-style.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithMarkerStyle(node) { 2 | if (node.type === 'decl' && markerStyleRegExp.test(node.prop)) { 3 | node.prop = 'list-style'; 4 | } 5 | } 6 | 7 | const markerStyleRegExp = /^marker-style$/i; 8 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-vertical-align-text-middle.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithVerticalAlignTextMiddle(node) { 2 | if (node.type === 'decl' && verticalAlignRegExp.test(node.prop) && textMiddleRegExp.test(node.value)) { 3 | node.value = 'middle'; 4 | } 5 | } 6 | 7 | const verticalAlignRegExp = /^vertical-align$/i; 8 | const textMiddleRegExp = /^text-middle$/i; 9 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-white-space-no-wrap.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithWhiteSpaceNoWrap(node) { 2 | if (node.type === 'decl' && whiteSpaceRegExp.test(node.prop) && noWrapRegExp.test(node.value)) { 3 | node.value = 'nowrap'; 4 | } 5 | } 6 | 7 | const whiteSpaceRegExp = /^white-space$/i; 8 | const noWrapRegExp = /^no-wrap$/i; 9 | -------------------------------------------------------------------------------- /src/lib/safe/transform-decl-with-white-space-overflow-wrap.js: -------------------------------------------------------------------------------- 1 | export default function transformDeclWithWhiteSpaceOverflowWrap(node) { 2 | if (node.type === 'decl' && whiteSpaceRegExp.test(node.prop) && overflowWrapRegExp.test(node.value)) { 3 | node.prop = 'word-wrap'; 4 | node.value = 'break-word'; 5 | } 6 | } 7 | 8 | const whiteSpaceRegExp = /^white-space$/i; 9 | const overflowWrapRegExp = /^overflow-wrap$/i; 10 | -------------------------------------------------------------------------------- /src/lib/safe/transform-function-with-rgb-or-hsl-and-fourth-value.js: -------------------------------------------------------------------------------- 1 | export default function transformFunctionWithRgbOrHslAndFourthValue(node) { 2 | if (node.type === 'func' && rgbOrHslRegExp.test(node.value) && Object(node.nodes).length === 9) { 3 | node.value += 'a'; 4 | 5 | node.nodes[7].value = node.nodes[7].value.replace(/^(\d+)$/, ($0, value) => { 6 | if (node.nodes[7].unit === '%') { 7 | node.nodes[7].unit = ''; 8 | 9 | return value / 100; 10 | } else if (node.nodes[7].unit === '') { 11 | return value / 255; 12 | } 13 | 14 | return $0; 15 | }); 16 | } 17 | } 18 | 19 | const rgbOrHslRegExp = /^(rgb|hsl)$/i; 20 | -------------------------------------------------------------------------------- /src/lib/safe/transform-word-with-current-color.js: -------------------------------------------------------------------------------- 1 | export default function transformWordWithNoWrap(node) { 2 | if (node.type === 'word' && currentColorRegExp.test(node.value)) { 3 | node.value = 'currentColor'; 4 | } 5 | } 6 | 7 | const currentColorRegExp = /^current-color$/i; 8 | -------------------------------------------------------------------------------- /test/basic.box-sizing.expect.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | .no-wrap { 4 | white-space: nowrap; 5 | } 6 | 7 | .vertical-align { 8 | vertical-align: middle; 9 | } 10 | 11 | .background-size { 12 | background-size: 25% 75%; 13 | background-size: 50% 50%; 14 | background-size: cover; 15 | } 16 | 17 | .background-position { 18 | background-position: 100% 0%; 19 | } 20 | 21 | .border-spacing { 22 | border-spacing: 100% 0%; 23 | } 24 | 25 | .depth { 26 | z-index: 10; 27 | } 28 | 29 | .z-order { 30 | z-index: 10; 31 | } 32 | 33 | .overflow-wrap { 34 | word-wrap: break-word; 35 | } 36 | 37 | .corner-radius { 38 | border-radius: 1em; 39 | } 40 | 41 | .current-color { 42 | color: currentColor; 43 | } 44 | 45 | .rgb { 46 | color: rgba(255, 0, 0, 1); 47 | } 48 | 49 | .hsl { 50 | color: hsla(120, 60%, 70%, 0.5); 51 | } 52 | 53 | .line-height { 54 | line-height: 2; 55 | } 56 | 57 | .marker-style { 58 | list-style: square; 59 | } 60 | 61 | .display-type { 62 | display: block; 63 | } 64 | 65 | :link,:visited {} 66 | 67 | a:link,a:visited {} 68 | 69 | a:not(a:link,a:visited) {} 70 | -------------------------------------------------------------------------------- /test/basic.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | .no-wrap { 4 | white-space: no-wrap; 5 | } 6 | 7 | .vertical-align { 8 | vertical-align: text-middle; 9 | } 10 | 11 | .background-size { 12 | background-size: 25% 75%; 13 | background-size: 50%; 14 | background-size: cover; 15 | } 16 | 17 | .background-position { 18 | background-position: 0% 100%; 19 | } 20 | 21 | .border-spacing { 22 | border-spacing: 0% 100%; 23 | } 24 | 25 | .depth { 26 | depth: 10; 27 | } 28 | 29 | .z-order { 30 | z-order: 10; 31 | } 32 | 33 | .overflow-wrap { 34 | white-space: overflow-wrap; 35 | } 36 | 37 | .corner-radius { 38 | corner-radius: 1em; 39 | } 40 | 41 | .current-color { 42 | color: current-color; 43 | } 44 | 45 | .rgb { 46 | color: rgb(255, 0, 0, 255); 47 | } 48 | 49 | .hsl { 50 | color: hsl(120, 60%, 70%, 50%); 51 | } 52 | 53 | .line-height { 54 | line-height: 200%; 55 | } 56 | 57 | .marker-style { 58 | marker-style: square; 59 | } 60 | 61 | .display-type { 62 | display-type: block; 63 | } 64 | 65 | :link {} 66 | 67 | a:link {} 68 | 69 | a:not(a:link) {} 70 | -------------------------------------------------------------------------------- /test/basic.current-color.expect.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | *{ 4 | box-sizing: border-box; 5 | } 6 | 7 | .no-wrap { 8 | white-space: nowrap; 9 | } 10 | 11 | .vertical-align { 12 | vertical-align: middle; 13 | } 14 | 15 | .background-size { 16 | background-size: 25% 75%; 17 | background-size: 50% 50%; 18 | background-size: cover; 19 | } 20 | 21 | .background-position { 22 | background-position: 100% 0%; 23 | } 24 | 25 | .border-spacing { 26 | border-spacing: 100% 0%; 27 | } 28 | 29 | .depth { 30 | z-index: 10; 31 | } 32 | 33 | .z-order { 34 | z-index: 10; 35 | } 36 | 37 | .overflow-wrap { 38 | word-wrap: break-word; 39 | } 40 | 41 | .corner-radius { 42 | border-radius: 1em; 43 | } 44 | 45 | .current-color { 46 | color: current-color; 47 | } 48 | 49 | .rgb { 50 | color: rgba(255, 0, 0, 1); 51 | } 52 | 53 | .hsl { 54 | color: hsla(120, 60%, 70%, 0.5); 55 | } 56 | 57 | .line-height { 58 | line-height: 2; 59 | } 60 | 61 | .marker-style { 62 | list-style: square; 63 | } 64 | 65 | .display-type { 66 | display: block; 67 | } 68 | 69 | :link,:visited {} 70 | 71 | a:link,a:visited {} 72 | 73 | a:not(a:link,a:visited) {} 74 | -------------------------------------------------------------------------------- /test/basic.expect.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | *{ 4 | box-sizing: border-box; 5 | } 6 | 7 | .no-wrap { 8 | white-space: nowrap; 9 | } 10 | 11 | .vertical-align { 12 | vertical-align: middle; 13 | } 14 | 15 | .background-size { 16 | background-size: 25% 75%; 17 | background-size: 50% 50%; 18 | background-size: cover; 19 | } 20 | 21 | .background-position { 22 | background-position: 100% 0%; 23 | } 24 | 25 | .border-spacing { 26 | border-spacing: 100% 0%; 27 | } 28 | 29 | .depth { 30 | z-index: 10; 31 | } 32 | 33 | .z-order { 34 | z-index: 10; 35 | } 36 | 37 | .overflow-wrap { 38 | word-wrap: break-word; 39 | } 40 | 41 | .corner-radius { 42 | border-radius: 1em; 43 | } 44 | 45 | .current-color { 46 | color: currentColor; 47 | } 48 | 49 | .rgb { 50 | color: rgba(255, 0, 0, 1); 51 | } 52 | 53 | .hsl { 54 | color: hsla(120, 60%, 70%, 0.5); 55 | } 56 | 57 | .line-height { 58 | line-height: 2; 59 | } 60 | 61 | .marker-style { 62 | list-style: square; 63 | } 64 | 65 | .display-type { 66 | display: block; 67 | } 68 | 69 | :link,:visited {} 70 | 71 | a:link,a:visited {} 72 | 73 | a:not(a:link,a:visited) {} 74 | -------------------------------------------------------------------------------- /test/basic.link.expect.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | *{ 4 | box-sizing: border-box; 5 | } 6 | 7 | .no-wrap { 8 | white-space: nowrap; 9 | } 10 | 11 | .vertical-align { 12 | vertical-align: middle; 13 | } 14 | 15 | .background-size { 16 | background-size: 25% 75%; 17 | background-size: 50% 50%; 18 | background-size: cover; 19 | } 20 | 21 | .background-position { 22 | background-position: 100% 0%; 23 | } 24 | 25 | .border-spacing { 26 | border-spacing: 100% 0%; 27 | } 28 | 29 | .depth { 30 | z-index: 10; 31 | } 32 | 33 | .z-order { 34 | z-index: 10; 35 | } 36 | 37 | .overflow-wrap { 38 | word-wrap: break-word; 39 | } 40 | 41 | .corner-radius { 42 | border-radius: 1em; 43 | } 44 | 45 | .current-color { 46 | color: currentColor; 47 | } 48 | 49 | .rgb { 50 | color: rgba(255, 0, 0, 1); 51 | } 52 | 53 | .hsl { 54 | color: hsla(120, 60%, 70%, 0.5); 55 | } 56 | 57 | .line-height { 58 | line-height: 2; 59 | } 60 | 61 | .marker-style { 62 | list-style: square; 63 | } 64 | 65 | .display-type { 66 | display: block; 67 | } 68 | 69 | :link {} 70 | 71 | a:link {} 72 | 73 | a:not(a:link) {} 74 | -------------------------------------------------------------------------------- /test/basic.no-unsafe-fixes-with-an-exception.expect.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | *{ 4 | box-sizing: border-box; 5 | } 6 | 7 | .no-wrap { 8 | white-space: nowrap; 9 | } 10 | 11 | .vertical-align { 12 | vertical-align: middle; 13 | } 14 | 15 | .background-size { 16 | background-size: 25% 75%; 17 | background-size: 50%; 18 | background-size: cover; 19 | } 20 | 21 | .background-position { 22 | background-position: 0% 100%; 23 | } 24 | 25 | .border-spacing { 26 | border-spacing: 0% 100%; 27 | } 28 | 29 | .depth { 30 | z-index: 10; 31 | } 32 | 33 | .z-order { 34 | z-index: 10; 35 | } 36 | 37 | .overflow-wrap { 38 | word-wrap: break-word; 39 | } 40 | 41 | .corner-radius { 42 | border-radius: 1em; 43 | } 44 | 45 | .current-color { 46 | color: currentColor; 47 | } 48 | 49 | .rgb { 50 | color: rgba(255, 0, 0, 1); 51 | } 52 | 53 | .hsl { 54 | color: hsla(120, 60%, 70%, 0.5); 55 | } 56 | 57 | .line-height { 58 | line-height: 200%; 59 | } 60 | 61 | .marker-style { 62 | list-style: square; 63 | } 64 | 65 | .display-type { 66 | display: block; 67 | } 68 | 69 | :link,:visited {} 70 | 71 | a:link,a:visited {} 72 | 73 | a:not(a:link,a:visited) {} 74 | -------------------------------------------------------------------------------- /test/basic.no-unsafe-fixes.expect.css: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Roboto'; 2 | 3 | *{ 4 | box-sizing: border-box; 5 | } 6 | 7 | .no-wrap { 8 | white-space: nowrap; 9 | } 10 | 11 | .vertical-align { 12 | vertical-align: middle; 13 | } 14 | 15 | .background-size { 16 | background-size: 25% 75%; 17 | background-size: 50%; 18 | background-size: cover; 19 | } 20 | 21 | .background-position { 22 | background-position: 0% 100%; 23 | } 24 | 25 | .border-spacing { 26 | border-spacing: 0% 100%; 27 | } 28 | 29 | .depth { 30 | z-index: 10; 31 | } 32 | 33 | .z-order { 34 | z-index: 10; 35 | } 36 | 37 | .overflow-wrap { 38 | word-wrap: break-word; 39 | } 40 | 41 | .corner-radius { 42 | border-radius: 1em; 43 | } 44 | 45 | .current-color { 46 | color: currentColor; 47 | } 48 | 49 | .rgb { 50 | color: rgba(255, 0, 0, 1); 51 | } 52 | 53 | .hsl { 54 | color: hsla(120, 60%, 70%, 0.5); 55 | } 56 | 57 | .line-height { 58 | line-height: 200%; 59 | } 60 | 61 | .marker-style { 62 | list-style: square; 63 | } 64 | 65 | .display-type { 66 | display: block; 67 | } 68 | 69 | :link {} 70 | 71 | a:link {} 72 | 73 | a:not(a:link) {} 74 | -------------------------------------------------------------------------------- /test/no-import.css: -------------------------------------------------------------------------------- 1 | .no-wrap { 2 | white-space: no-wrap; 3 | } 4 | 5 | .vertical-align { 6 | vertical-align: text-middle; 7 | } 8 | 9 | .background-size { 10 | background-size: 25% 75%; 11 | background-size: 50%; 12 | background-size: cover; 13 | } 14 | 15 | .background-position { 16 | background-position: 0% 100%; 17 | } 18 | 19 | .border-spacing { 20 | border-spacing: 0% 100%; 21 | } 22 | 23 | .depth { 24 | depth: 10; 25 | } 26 | 27 | .z-order { 28 | z-order: 10; 29 | } 30 | 31 | .overflow-wrap { 32 | white-space: overflow-wrap; 33 | } 34 | 35 | .corner-radius { 36 | corner-radius: 1em; 37 | } 38 | 39 | .current-color { 40 | color: current-color; 41 | } 42 | 43 | .rgb { 44 | color: rgb(255, 0, 0, 255); 45 | } 46 | 47 | .hsl { 48 | color: hsl(120, 60%, 70%, 50%); 49 | } 50 | 51 | .line-height { 52 | line-height: 200%; 53 | } 54 | 55 | .marker-style { 56 | marker-style: square; 57 | } 58 | 59 | .display-type { 60 | display-type: block; 61 | } 62 | 63 | :link {} 64 | 65 | a:link {} 66 | 67 | a:not(a:link) {} 68 | -------------------------------------------------------------------------------- /test/no-import.expect.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | .no-wrap { 6 | white-space: nowrap; 7 | } 8 | 9 | .vertical-align { 10 | vertical-align: middle; 11 | } 12 | 13 | .background-size { 14 | background-size: 25% 75%; 15 | background-size: 50% 50%; 16 | background-size: cover; 17 | } 18 | 19 | .background-position { 20 | background-position: 100% 0%; 21 | } 22 | 23 | .border-spacing { 24 | border-spacing: 100% 0%; 25 | } 26 | 27 | .depth { 28 | z-index: 10; 29 | } 30 | 31 | .z-order { 32 | z-index: 10; 33 | } 34 | 35 | .overflow-wrap { 36 | word-wrap: break-word; 37 | } 38 | 39 | .corner-radius { 40 | border-radius: 1em; 41 | } 42 | 43 | .current-color { 44 | color: currentColor; 45 | } 46 | 47 | .rgb { 48 | color: rgba(255, 0, 0, 1); 49 | } 50 | 51 | .hsl { 52 | color: hsla(120, 60%, 70%, 0.5); 53 | } 54 | 55 | .line-height { 56 | line-height: 2; 57 | } 58 | 59 | .marker-style { 60 | list-style: square; 61 | } 62 | 63 | .display-type { 64 | display: block; 65 | } 66 | 67 | :link,:visited {} 68 | 69 | a:link,a:visited {} 70 | 71 | a:not(a:link,a:visited) {} 72 | --------------------------------------------------------------------------------