diff --git a/.lintstagedrc b/.lintstagedrc index a0ad964..118da0a 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,5 +1,5 @@ { - "{src,test}/**/*.{js,jsx}": [ + "{src/**/*.{js,jsx},test/*.{js,jsx}}": [ "prettier --print-width 100 --tab-width 2 --no-semi --single-quote --write", "git add", "eslint" diff --git a/README.md b/README.md index e4314d9..b641e51 100644 --- a/README.md +++ b/README.md @@ -60,29 +60,33 @@ npm run lint:css ### Syntax notes #### Turning rules off from within your CSS -In order for `stylelint-processor-styled-components` to parse your `stylelint-disable` comments (see the [stylelint documentation](https://stylelint.io/user-guide/configuration/#turning-rules-off-from-within-your-css) for all allowed syntax) they must be inside the actual Styled Components CSS as such: +Turning off rules with `stylelint-disable`-like comments (see the [stylelint documentation](https://stylelint.io/user-guide/configuration/#turning-rules-off-from-within-your-css) for all allowed syntax) is fully supported inside and outside of the tagged template literals, do note though that what actually happens behind the scene is that all `stylelint-(disable|enable)` comments are moved into the compiled css that is actually linted, so something like this: + -**Wrong**: ``` -/* stylelint-disable color-named */ +/* stylelint-disable */ import React from 'react'; import styled from 'styled-components'; const Wrapper = styled.div` + /* stylelint-disable */ background-color: red; `; ``` -**Right**: +or even ``` +/* stylelint-disable */ import React from 'react'; import styled from 'styled-components'; const Wrapper = styled.div` - /* stylelint-disable color-named */ + /* stylelint-disable-next-line */ background-color: red; `; ``` +would throw a stylelint error similar to `All rules have already been disabled (CssSyntaxError)`. + #### Interpolation linting We do not currently support linting interpolations as it could be a big performance hit though we aspire to have at least partial support in the future. You can of course lint your own mixins in their separate files, but it won't be linted in context, the implementation currently just inserts relevant dummy values. This, we are afraid, means you won't be able to lint cases such as `declaration-block-no-duplicate-properties` etc. and won't be able to lint outside mixins such as [polished](https://github.com/styled-components/polished). diff --git a/package-lock.json b/package-lock.json index 41d15ee..ba8e4db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1088,7 +1088,6 @@ "requires": { "anymatch": "1.3.0", "async-each": "1.0.1", - "fsevents": "1.1.1", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -2195,961 +2194,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz", - "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=", - "dev": true, - "optional": true, - "requires": { - "nan": "2.6.2", - "node-pre-gyp": "0.6.33" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.2" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.6", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.11.0", - "bundled": true, - "dev": true, - "optional": true - }, - "chalk": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "graceful-readlink": "1.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "0.7.1" - } - }, - "deep-extend": { - "version": "0.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "extend": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.14" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.10", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.5.4" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.10", - "inherits": "2.0.3", - "minimatch": "3.0.3" - } - }, - "gauge": { - "version": "2.7.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.0" - } - }, - "generate-function": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "generate-object-property": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "is-property": "1.0.2" - } - }, - "getpass": { - "version": "0.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.1", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.3", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "2.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chalk": "1.1.3", - "commander": "2.9.0", - "is-my-json-valid": "2.15.0", - "pinkie-promise": "2.0.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.3.1", - "sshpk": "1.10.2" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-my-json-valid": { - "version": "2.15.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" - } - }, - "is-property": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonpointer": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.3.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - } - }, - "mime-db": { - "version": "1.26.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.14", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.26.0" - } - }, - "minimatch": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.6" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "0.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.33", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.0.2", - "rc": "1.1.7", - "request": "2.79.0", - "rimraf": "2.5.4", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.3.0" - } - }, - "nopt": { - "version": "3.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0" - } - }, - "npmlog": { - "version": "4.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.2", - "console-control-strings": "1.1.0", - "gauge": "2.7.3", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "pinkie-promise": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.3.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.1", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.79.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.11.0", - "combined-stream": "1.0.5", - "extend": "3.0.0", - "forever-agent": "0.6.1", - "form-data": "2.1.2", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.14", - "oauth-sign": "0.8.2", - "qs": "6.3.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.4.3", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.5.4", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.10.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.6", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string_decoder": { - "version": "0.10.31", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "supports-color": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.10", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.2.0", - "fstream": "1.0.10", - "fstream-ignore": "1.0.5", - "once": "1.3.3", - "readable-stream": "2.1.5", - "rimraf": "2.5.4", - "tar": "2.2.1", - "uid-number": "0.0.6" - }, - "dependencies": { - "once": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "readable-stream": { - "version": "2.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.4.3", - "bundled": true, - "dev": true, - "optional": true - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "xtend": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "function-bind": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", @@ -5072,13 +4116,6 @@ "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", "dev": true }, - "nan": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", - "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=", - "dev": true, - "optional": true - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", diff --git a/package.json b/package.json index abb7f2c..2b9142e 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "lib/index.js", "scripts": { "lint:eslint": "eslint .", - "lint:prettier": "prettier --list-different --print-width 100 --tab-width 2 --no-semi --single-quote '{src,test}/**/*.{js,jsx}'", - "prettier": "prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote '{src,test}/**/*.{js,jsx}'", + "lint:prettier": "prettier --list-different --print-width 100 --tab-width 2 --no-semi --single-quote '{src/**/*.{js,jsx},test/*.{js,jsx}}'", + "prettier": "prettier --write --print-width 100 --tab-width 2 --no-semi --single-quote '{src/**/*.{js,jsx},test/*.{js,jsx}}'", "precommit": "lint-staged", "test": "jest test/*.test.js", "build": "babel src --out-dir lib", diff --git a/src/parsers/index.js b/src/parsers/index.js index bc55247..7e8d998 100644 --- a/src/parsers/index.js +++ b/src/parsers/index.js @@ -9,6 +9,7 @@ const isStyledImport = require('../utils/styled').isStyledImport const wrapSelector = require('../utils/general').wrapSelector const wrapKeyframes = require('../utils/general').wrapKeyframes const fixIndentation = require('../utils/general').fixIndentation +const isStylelintComment = require('../utils/general').isStylelintComment const getTTLContent = require('../utils/tagged-template-literal.js').getTaggedTemplateLiteralContent const parseImports = require('../utils/parse').parseImports @@ -16,6 +17,7 @@ const getSourceMap = require('../utils/parse').getSourceMap const processStyledComponentsFile = ast => { const extractedCSS = [] + let ignoreRuleComments = [] let importedNames = { default: 'styled', css: 'css', @@ -26,6 +28,13 @@ const processStyledComponentsFile = ast => { traverse(ast, { noScope: true, enter({ node }) { + if (node.type !== 'Program' && node.leadingComments) { + node.leadingComments.forEach(comment => { + if (isStylelintComment(comment.value)) { + ignoreRuleComments.push(`/*${comment.value}*/`) + } + }) + } if (isStyledImport(node)) { importedNames = parseImports(node) return @@ -36,11 +45,19 @@ const processStyledComponentsFile = ast => { const fixedContent = fixIndentation(content).text const wrapperFn = helper === 'keyframes' ? wrapKeyframes : wrapSelector const wrappedContent = wrapperFn(fixedContent) - extractedCSS.push(wrappedContent) + const stylelintCommentsAdded = ignoreRuleComments.length > 0 + ? `${ignoreRuleComments.join('\n')}\n${wrappedContent}` + : wrappedContent + extractedCSS.push(stylelintCommentsAdded) sourceMap = Object.assign( sourceMap, getSourceMap(extractedCSS.join('\n'), wrappedContent, node.loc.start.line) ) + /** + * All queued comments have been added to the file so we don't need to, and actually shouldn't + * add them to the file more than once + */ + ignoreRuleComments = [] } }) diff --git a/src/utils/general.js b/src/utils/general.js index fccb738..ecac2f7 100644 --- a/src/utils/general.js +++ b/src/utils/general.js @@ -61,9 +61,17 @@ const isLastDeclarationCompleted = text => { const wrapSelector = content => `.selector${(count += 1)} {${content}}\n` const wrapKeyframes = content => `@keyframes {${content}}\n` +/** + * The reason we put a \s before .* in the last part of the regex is to make sure we don't + * match stylelint-disable-line and stylelint-disable-next-line or, for now, any future extensions + * as these line specific disables should not be placed outside a css TTL + */ +const isStylelintComment = comment => /^\s*stylelint-(?:enable|disable)(?:\s.*)?$/.test(comment) + exports.wrapKeyframes = wrapKeyframes exports.wrapSelector = wrapSelector exports.fixIndentation = fixIndentation exports.reverseString = reverseString exports.nextNonWhitespaceChar = nextNonWhitespaceChar exports.isLastDeclarationCompleted = isLastDeclarationCompleted +exports.isStylelintComment = isStylelintComment diff --git a/test/fixtures/ignore-rule-comments/alternating-disable-enable.js b/test/fixtures/ignore-rule-comments/alternating-disable-enable.js new file mode 100644 index 0000000..6d151e2 --- /dev/null +++ b/test/fixtures/ignore-rule-comments/alternating-disable-enable.js @@ -0,0 +1,20 @@ +import styled from 'styled-components' + +const Button1 = styled.button` + color: red; +`; + +/* stylelint-disable */ +const Button2 = styled.button` + color: red; +`; + +/* stylelint-enable */ +const Button3 = styled.button` + color: red; +`; + +/* stylelint-disable */ +const Button4 = styled.button` + color: red; +`; diff --git a/test/fixtures/ignore-rule-comments/disable-whole-file.js b/test/fixtures/ignore-rule-comments/disable-whole-file.js new file mode 100644 index 0000000..faf082e --- /dev/null +++ b/test/fixtures/ignore-rule-comments/disable-whole-file.js @@ -0,0 +1,10 @@ +/* stylelint-disable */ +import styled from 'styled-components' + +const Button1 = styled.button` + color: red; +`; + +const Button2 = styled.button` + color: blue; +`; diff --git a/test/fixtures/ignore-rule-comments/mix-in-css-disables.js b/test/fixtures/ignore-rule-comments/mix-in-css-disables.js new file mode 100644 index 0000000..27e3ac5 --- /dev/null +++ b/test/fixtures/ignore-rule-comments/mix-in-css-disables.js @@ -0,0 +1,16 @@ +import styled from 'styled-components' + +const Button1 = styled.button` + color: red; /* stylelint-disable-line */ +`; + +/* stylelint-disable */ +const Button2 = styled.button` + color: red; +`; + +/* stylelint-enable */ +const Button3 = styled.button` + /* stylelint-disable-next-line */ + color: red; +`; diff --git a/test/fixtures/ignore-rule-comments/use-single-line-comments.js b/test/fixtures/ignore-rule-comments/use-single-line-comments.js new file mode 100644 index 0000000..effdde0 --- /dev/null +++ b/test/fixtures/ignore-rule-comments/use-single-line-comments.js @@ -0,0 +1,20 @@ +import styled from 'styled-components' + +const Button1 = styled.button` + color: red; +`; + +// stylelint-disable +const Button2 = styled.button` + color: red; +`; + +// stylelint-enable +const Button3 = styled.button` + color: red; +`; + +// stylelint-disable +const Button4 = styled.button` + color: red; +`; diff --git a/test/fixtures/ignore-rule-comments/use-single-line-disables.js b/test/fixtures/ignore-rule-comments/use-single-line-disables.js new file mode 100644 index 0000000..97288b3 --- /dev/null +++ b/test/fixtures/ignore-rule-comments/use-single-line-disables.js @@ -0,0 +1,18 @@ +import styled from 'styled-components' + +// stylelint-disable-next-line +const Button1 = styled.button` + color: red; +`; + +/* stylelint-disable */ +const Button2 = styled.button` + color: red; +`; + +/* stylelint-enable */ +// stylelint-disable-next-line +/* stylelint-disable-line */ +const Button3 = styled.button` + color: red; +`; diff --git a/test/fixtures/interpolations/valid.js b/test/fixtures/interpolations/valid.js index 12d6b0e..47f1bb6 100644 --- a/test/fixtures/interpolations/valid.js +++ b/test/fixtures/interpolations/valid.js @@ -42,7 +42,6 @@ const Box3 = styled(Box2)` ` // Multiline -// prettier-ignore const Button4 = styled.button` display: block; ${` @@ -53,7 +52,6 @@ const Button4 = styled.button` // Conditional const cond = true -// prettier-ignore const Button5 = styled.button` display: block; ${cond && @@ -65,7 +63,6 @@ const Button5 = styled.button` // Conditional const cond2 = false -// prettier-ignore const Button6 = styled.button` display: block; ${cond2 && @@ -84,7 +81,6 @@ const Button7 = styled.button` ` // Several interpolation statements in a block -// prettier-ignore const Button8 = styled.button` ${`display: block;`} ${`color: ${color};`} @@ -93,7 +89,6 @@ const Button8 = styled.button` // Simple interpolations in one-line css const display = 'block' const colorExpression = 'color: red;' -// prettier-ignore const Button9 = styled.button` display: ${display}; ${colorExpression} ` @@ -102,7 +97,6 @@ const Button9 = styled.button` const display = 'block' const colorExpression = 'color: red;' const backgroundExpression = 'background: blue;' -// prettier-ignore const Button9 = styled.button` display: ${display}; ${colorExpression} ${backgroundExpression} ` diff --git a/test/ignore-rule-comments.test.js b/test/ignore-rule-comments.test.js new file mode 100644 index 0000000..544eeb0 --- /dev/null +++ b/test/ignore-rule-comments.test.js @@ -0,0 +1,192 @@ +const stylelint = require('stylelint') +const path = require('path') + +const processor = path.join(__dirname, '../src/index.js') +const rules = { + 'color-named': 'never' +} + +describe('ignore rule comments', () => { + let fixture + let data + + beforeEach(done => { + stylelint + .lint({ + files: [fixture], + syntax: 'scss', + config: { + processors: [processor], + rules + } + }) + .then(result => { + data = result + done() + }) + .catch(err => { + data = err + done() + }) + }) + + describe('disable-whole-file', () => { + beforeAll(() => { + fixture = path.join(__dirname, './fixtures/ignore-rule-comments/disable-whole-file.js') + }) + + it('should have one result', () => { + expect(data.results.length).toBe(1) + }) + + it('should use the right file', () => { + expect(data.results[0].source).toBe(fixture) + }) + + it('should not have errored', () => { + expect(data.errored).toBe(false) + }) + + it('should not have any warnings', () => { + expect(data.results[0].warnings.length).toBe(0) + }) + }) + + describe('alternating-disable-enable', () => { + beforeAll(() => { + fixture = path.join( + __dirname, + './fixtures/ignore-rule-comments/alternating-disable-enable.js' + ) + }) + + it('should have one result', () => { + expect(data.results.length).toBe(1) + }) + + it('should use the right file', () => { + expect(data.results[0].source).toBe(fixture) + }) + + it('should have exactly 2 warnings', () => { + expect(data.results[0].warnings.length).toBe(2) + }) + + it('should error at exactly the correct places', () => { + const warnings = data.results[0].warnings + + expect(warnings[0].line).toBe(4) + expect(warnings[0].rule).toBe('color-named') + expect(warnings[0].severity).toBe('error') + + expect(warnings[1].line).toBe(14) + expect(warnings[1].rule).toBe('color-named') + expect(warnings[1].severity).toBe('error') + }) + }) + + describe('use-single-line-comments', () => { + beforeAll(() => { + fixture = path.join(__dirname, './fixtures/ignore-rule-comments/use-single-line-comments.js') + }) + + it('should have one result', () => { + expect(data.results.length).toBe(1) + }) + + it('should use the right file', () => { + expect(data.results[0].source).toBe(fixture) + }) + + it('should have exactly 2 warnings', () => { + expect(data.results[0].warnings.length).toBe(2) + }) + + it('should error at exactly the correct places', () => { + const warnings = data.results[0].warnings + + expect(warnings[0].line).toBe(4) + expect(warnings[0].rule).toBe('color-named') + expect(warnings[0].severity).toBe('error') + + expect(warnings[1].line).toBe(14) + expect(warnings[1].rule).toBe('color-named') + expect(warnings[1].severity).toBe('error') + }) + }) + + describe('use-single-line-disables', () => { + beforeAll(() => { + fixture = path.join(__dirname, './fixtures/ignore-rule-comments/use-single-line-disables.js') + }) + + it('should have one result', () => { + expect(data.results.length).toBe(1) + }) + + it('should use the right file', () => { + expect(data.results[0].source).toBe(fixture) + }) + + it('should have exactly 2 warnings', () => { + expect(data.results[0].warnings.length).toBe(2) + }) + + it('should error at exactly the correct places', () => { + const warnings = data.results[0].warnings + + expect(warnings[0].line).toBe(5) + expect(warnings[0].rule).toBe('color-named') + expect(warnings[0].severity).toBe('error') + + expect(warnings[1].line).toBe(17) + expect(warnings[1].rule).toBe('color-named') + expect(warnings[1].severity).toBe('error') + }) + + it('should match css exactly', () => { + // This is mostly to check that the disable comments are inserted as expected + const regex = new RegExp( + '^\\.selector\\d+ {\\n' + + ' color: red;\\n' + + '}\\n' + + '\\n' + + '/\\* stylelint-disable \\*/\\n' + + '\\.selector\\d+ {\\n' + + ' color: red;\\n' + + '}\\n' + + '\\n' + + '/\\* stylelint-enable \\*/\\n' + + '\\.selector\\d+ {\\n' + + ' color: red;\\n' + + '}\\n' + + '$' + ) + // eslint-disable-next-line + const cssOutput = data.results[0]._postcssResult.css + expect(regex.test(cssOutput)).toBe(true) + }) + }) + + describe('mix-in-css-disables', () => { + beforeAll(() => { + fixture = path.join(__dirname, './fixtures/ignore-rule-comments/mix-in-css-disables.js') + }) + + it('should have one result', () => { + expect(data.results.length).toBe(1) + }) + + it('should use the right file', () => { + expect(data.results[0].source).toBe(fixture) + }) + + it('should not have errored', () => { + expect(data.errored).toBe(false) + }) + + it('should not have any warnings', () => { + expect(data.results[0].warnings.length).toBe(0) + }) + }) +}) diff --git a/test/utils.test.js b/test/utils.test.js index 343d82d..12bb5fe 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -2,6 +2,7 @@ const interleave = require('../src/utils/tagged-template-literal').interleave const isLastDeclarationCompleted = require('../src/utils/general').isLastDeclarationCompleted const nextNonWhitespaceChar = require('../src/utils/general').nextNonWhitespaceChar const reverseString = require('../src/utils/general').reverseString +const isStylelintComment = require('../src/utils/general').isStylelintComment describe('utils', () => { describe('interleave', () => { @@ -301,4 +302,30 @@ describe('utils', () => { expect(fn(prevCSS)).toBe(true) }) }) + + describe('isStylelintComment', () => { + const fn = isStylelintComment + + it('should match general block ignores', () => { + expect(fn('stylelint-disable')).toBe(true) + + expect(fn('stylelint-enable')).toBe(true) + }) + + it('should match block ignores with any arguments', () => { + expect(fn('stylelint-enable some-rule')).toBe(true) + + expect(fn('stylelint-disable asdfsafdsa-fdsafd9a0fd9sa0f asfd8af afdsa7f')).toBe(true) + }) + + it("shouldn't match line specific ignores", () => { + expect(fn('stylelint-disable-line')).toBe(false) + + expect(fn('stylelint-disable-next-line')).toBe(false) + }) + + it('should handle whitespace in start and end', () => { + expect(fn(' \tstylelint-disable \t')).toBe(true) + }) + }) })