Repository: postcss/autoprefixer
Pull request #1027 (2018-04-27 20:00:00)

"autoprefixer: ignore next" support
#1024 Added support for comment "autoprefixer: ignore next"


Patch:
diff --git a/README.md b/README.md
index 1d051c8..bf2ee91 100644
--- a/README.md
+++ b/README.md
@@ -95,8 +95,8 @@ a {
 ## Browsers
 
 Autoprefixer uses [Browserslist], so you can specify the browsers
-you want to target in your project by queries like `last 2 versions, not dead`
-or `> 5%` (see [Best Practises](https://github.com/browserslist/browserslist#best-practices)).
+you want to target in your project by queries like `> 5%`
+(see [Best Practises]).
 
 The best way to provide browsers is `.browserslistrc` config
 or `package.json` with `browserslist` key. Put it in your project root.
@@ -110,6 +110,7 @@ and default value.
 
 [Browserslist docs]: https://github.com/ai/browserslist#queries
 [babel-preset-env]:  https://github.com/babel/babel-preset-env
+[Best Practises]:    https://github.com/browserslist/browserslist#best-practices
 [Browserslist]:      https://github.com/ai/browserslist
 [Stylelint]:         http://stylelint.io/
 
@@ -522,12 +523,25 @@ b {
     /* autoprefixer: off */
     transition: 1s; /* it will not be prefixed */
 }
+
+c {
+    /* autoprefixer: ignore next */
+    transition: 1s; /* it will not be prefixed */
+    border-radius: 10px; /* it will be prefixed */
+}
 ```
 
 Control comments disable Autoprefixer within the whole rule in which
 you place it. In the above example, Autoprefixer will be disabled
 in the entire `b` rule scope, not only after the comment.
 
+For single rule / property exlusions should be used folowing comment:
+```css
+/* autoprefixer: ignore next */
+```
+Autoprefixer will be disabled only for  `transition` property 
+in the `c` rule scope.
+
 You can also use comments recursively:
 
 ```css
@@ -555,11 +569,6 @@ autoprefixer({ cascade: false })
 
 Available options are:
 
-* `browsers` (array): list of browsers query (like `last 2 versions, not dead`),
-  which are supported in your project. We recommend to use `browserslist`
-  config or `browserslist` key in `package.json`, rather than this option
-  to share browsers with other tools. See [Browserslist docs] for available
-  queries and default value.
 * `env` (string): environment for Browserslist.
 * `cascade` (boolean): should Autoprefixer use Visual Cascade,
   if CSS is uncompressed. Default: `true`
@@ -575,6 +584,11 @@ Available options are:
   properties. Default is `false`.
 * `stats` (object): custom [usage statistics] for `> 10% in my stats`
   browsers query.
+* `browsers` (array): list of queries for target browsers. Try to not use it.
+  The best preactive is to use `.browserslistrc` config
+  or `browserslist` key in `package.json` to share target browsers
+  with Babel, ESLint and Stylelint. See [Browserslist docs]
+  for available queries and default value.
 
 Plugin object has `info()` method for debugging purpose.
 
diff --git a/lib/processor.js b/lib/processor.js
index 9132b40..8eaa53f 100644
--- a/lib/processor.js
+++ b/lib/processor.js
@@ -342,7 +342,19 @@ class Processor {
         if (node._autoprefixerDisabled !== undefined) {
             return node._autoprefixerDisabled;
         }
+        if (node.parent) {
+            const previousNode = node.prev();
+            if (previousNode &&
+                previousNode.type === 'comment' &&
+                /(!\s*)?autoprefixer:\s*ignore\s+next/i
+                    .test(previousNode.text)) {
+                node._autoprefixerDisabled = true;
+                node._autoprefixerOnlySelfDisabled = true;
+                return node._autoprefixerDisabled;
+            }
+        }
 
+        let value = null;
         if (node.nodes) {
             let status = undefined;
             node.each(i => {
@@ -361,19 +373,24 @@ class Processor {
                 }
             });
 
-            let value = false;
             if (status !== undefined) {
                 value = !status;
-            } else if (node.parent) {
-                value = this.disabled(node.parent, result);
             }
-
-            node._autoprefixerDisabled = value;
-            return node._autoprefixerDisabled;
-        } else {
-            node._autoprefixerDisabled = this.disabled(node.parent, result);
-            return node._autoprefixerDisabled;
         }
+        if (!node.nodes || value === null) {
+            if (node.parent) {
+                const isParentDisabled = this.disabled(node.parent, result);
+                if (node.parent._autoprefixerOnlySelfDisabled === true) {
+                    value = false;
+                } else {
+                    value = isParentDisabled;
+                }
+            } else {
+                value = false;
+            }
+        }
+        node._autoprefixerDisabled = value;
+        return node._autoprefixerDisabled;
     }
 
     /**


Test patch:
diff --git a/test/autoprefixer.test.js b/test/autoprefixer.test.js
index 2bcf357..ea69017 100644
--- a/test/autoprefixer.test.js
+++ b/test/autoprefixer.test.js
@@ -202,24 +202,25 @@ it('passes statistics to Browserslist', () => {
         .toMatch(/Browsers:\n\s\sChrome: 11\n\s\sIE: 11\n/);
 });
 
-it('prefixes values',                () => test('values'));
-it('prefixes @keyframes',            () => test('keyframes'));
-it('prefixes @viewport',             () => test('viewport'));
-it('prefixes selectors',             () => test('selectors'));
-it('prefixes resolution query',      () => test('resolution'));
-it('removes common mistakes',        () => test('mistakes'));
-it('reads notes for prefixes',       () => test('notes'));
-it('keeps vendor-specific hacks',    () => test('vendor-hack'));
-it('keeps values with vendor hacks', () => test('value-hack'));
-it('works with comments',            () => test('comments'));
-it('uses visual cascade',            () => test('cascade'));
-it('works with properties near',     () => test('double'));
-it('checks prefixed in hacks',       () => test('check-down'));
-it('normalize cascade after remove', () => test('uncascade'));
-it('prefix decls in @supports',      () => test('supports'));
-it('saves declaration style',        () => test('style'));
-it('uses control comments',          () => test('disabled'));
-it('has actual example in docs',     () => test('example'));
+it('prefixes values',                    () => test('values'));
+it('prefixes @keyframes',                () => test('keyframes'));
+it('prefixes @viewport',                 () => test('viewport'));
+it('prefixes selectors',                 () => test('selectors'));
+it('prefixes resolution query',          () => test('resolution'));
+it('removes common mistakes',            () => test('mistakes'));
+it('reads notes for prefixes',           () => test('notes'));
+it('keeps vendor-specific hacks',        () => test('vendor-hack'));
+it('keeps values with vendor hacks',     () => test('value-hack'));
+it('works with comments',                () => test('comments'));
+it('uses visual cascade',                () => test('cascade'));
+it('works with properties near',         () => test('double'));
+it('checks prefixed in hacks',           () => test('check-down'));
+it('normalize cascade after remove',     () => test('uncascade'));
+it('prefix decls in @supports',          () => test('supports'));
+it('saves declaration style',            () => test('style'));
+it('uses control comments',              () => test('ignore-next'));
+it('uses "ignore next" control comment', () => test('disabled'));
+it('has actual example in docs',         () => test('example'));
 
 it('uses control comments to whole scope', () => {
     const input  = read('scope');
diff --git a/test/cases/ignore-next.css b/test/cases/ignore-next.css
new file mode 100644
index 0000000..13daffe
--- /dev/null
+++ b/test/cases/ignore-next.css
@@ -0,0 +1,23 @@
+it_ignore-property {
+  /* autoprefixer: ignore next */
+  mask: none;
+  background: linear-gradient(to bottom, white, black);
+}
+
+it_ignore-property_nospaces-comment {
+  /* autoprefixer:ignore next */
+  mask: none;
+  background: linear-gradient(to bottom, white, black);
+}
+
+/* autoprefixer: ignore next */
+it_ignore-rule::placeholder {
+  mask: none;
+}
+
+/* autoprefixer: ignore next */
+@media (min-resolution: 2dppx) {
+  it_ignore-at-rule::placeholder {
+      mask: none;
+  }
+}
diff --git a/test/cases/ignore-next.out.css b/test/cases/ignore-next.out.css
new file mode 100644
index 0000000..f198d3c
--- /dev/null
+++ b/test/cases/ignore-next.out.css
@@ -0,0 +1,33 @@
+it_ignore-property {
+  /* autoprefixer: ignore next */
+  mask: none;
+  background: -webkit-linear-gradient(top, white, black);
+  background: -o-linear-gradient(top, white, black);
+  background: linear-gradient(to bottom, white, black);
+}
+
+it_ignore-property_nospaces-comment {
+  /* autoprefixer:ignore next */
+  mask: none;
+  background: -webkit-linear-gradient(top, white, black);
+  background: -o-linear-gradient(top, white, black);
+  background: linear-gradient(to bottom, white, black);
+}
+
+/* autoprefixer: ignore next */
+it_ignore-rule::placeholder {
+  -webkit-mask: none;
+          mask: none;
+}
+
+/* autoprefixer: ignore next */
+@media (min-resolution: 2dppx) {
+  it_ignore-at-rule::-webkit-input-placeholder {
+      -webkit-mask: none;
+              mask: none;
+  }
+  it_ignore-at-rule::placeholder {
+      -webkit-mask: none;
+              mask: none;
+  }
+}