├── .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]
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 |
--------------------------------------------------------------------------------