From 39c586e038864a544d9bf48fe75b6adcc0959e16 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 10:59:08 +0000 Subject: [PATCH 01/13] Add @blueprintjs/stylelint-plugin package and no-prefix-literal rule --- .stylelintrc | 4 + packages/stylelint-plugin/.eslintrc.json | 9 ++ packages/stylelint-plugin/README.md | 69 ++++++++ packages/stylelint-plugin/package.json | 33 ++++ packages/stylelint-plugin/src/index.ts | 18 +++ .../src/rules/no-prefix-literal.ts | 147 ++++++++++++++++++ packages/stylelint-plugin/src/tsconfig.json | 9 ++ .../src/utils/checkImportExists.ts | 41 +++++ .../src/utils/insertImport.ts | 79 ++++++++++ .../test/fixtures/contains-bp3.scss | 3 + .../test/fixtures/contains-nested-bp3.scss | 5 + .../fixtures/contains-non-prefix-bp3.scss | 3 + .../test/fixtures/does-not-contain-bp3.scss | 5 + packages/stylelint-plugin/test/index.js | 15 ++ .../test/no-prefix-literal.test.js | 57 +++++++ yarn.lock | 33 ++-- 16 files changed, 521 insertions(+), 9 deletions(-) create mode 100644 packages/stylelint-plugin/.eslintrc.json create mode 100644 packages/stylelint-plugin/README.md create mode 100644 packages/stylelint-plugin/package.json create mode 100644 packages/stylelint-plugin/src/index.ts create mode 100644 packages/stylelint-plugin/src/rules/no-prefix-literal.ts create mode 100644 packages/stylelint-plugin/src/tsconfig.json create mode 100644 packages/stylelint-plugin/src/utils/checkImportExists.ts create mode 100644 packages/stylelint-plugin/src/utils/insertImport.ts create mode 100644 packages/stylelint-plugin/test/fixtures/contains-bp3.scss create mode 100644 packages/stylelint-plugin/test/fixtures/contains-nested-bp3.scss create mode 100644 packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.scss create mode 100644 packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss create mode 100644 packages/stylelint-plugin/test/index.js create mode 100644 packages/stylelint-plugin/test/no-prefix-literal.test.js diff --git a/.stylelintrc b/.stylelintrc index 7eed8ee212..e7b1e64342 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -3,7 +3,11 @@ "stylelint-config-palantir", "stylelint-config-palantir/sass.js" ], + "plugins": [ + "@blueprintjs/stylelint-plugin" + ], "rules": { + "@blueprintjs/no-prefix-literal": true, "declaration-empty-line-before": null, "indentation": [2, { "ignore": ["value"] diff --git a/packages/stylelint-plugin/.eslintrc.json b/packages/stylelint-plugin/.eslintrc.json new file mode 100644 index 0000000000..21c420322e --- /dev/null +++ b/packages/stylelint-plugin/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "root": true, + "extends": ["../../.eslintrc.js"], + "rules": { + "no-duplicate-imports": "off", + "@typescript-eslint/no-duplicate-imports": ["error"] + }, + "ignorePatterns": ["node_modules", "dist", "lib", "fixtures", "coverage", "__snapshots__", "generated"] +} diff --git a/packages/stylelint-plugin/README.md b/packages/stylelint-plugin/README.md new file mode 100644 index 0000000000..b5fb75b90d --- /dev/null +++ b/packages/stylelint-plugin/README.md @@ -0,0 +1,69 @@ + + +# [Blueprint](http://blueprintjs.com/) [stylelint](https://stylelint.io/) plugin + +Blueprint is a React UI toolkit for the web. + +This package contains the [stylelint](https://stylelint.io/) plugin for Blueprint. It provides custom rules which are useful when developing against Blueprint libraries. + +**Key features:** + +- [Blueprint-specific rules](#Rules) for use with `@blueprintjs` components. + +## Installation + +``` +yarn add --dev @blueprintjs/stylelint-plugin +``` + +## Usage + +Simply add this plugin in your `.stylelintrc` file and then pick the rules that you need. The plugin includes Blueprint-specific rules which enforce semantics particular to usage with `@blueprintjs` packages, but does not turn them on by default. + +`.stylelintrc` + +```json +{ + "plugins": [ + "@blueprintjs/stylelint-plugin" + ], + "rules": { + "@blueprintjs/no-prefix-literal": true + } +} +``` + +## Rules + +### `@blueprintjs/no-prefix-literal` (autofixable) + +Enforce usage of the `ns` constant over namespaced string literals. + +The `@blueprintjs` package exports a `ns` CSS variable which contains the prefix for the current version of Blueprint (`bp3` for Blueprint 3, `bp4` for Blueprint 4, and etc). Using the variable instead of hardcoding the prefix means that your code will still work when new major version of Blueprint is released. + +```json +{ + "rules": { + "@blueprintjs/no-prefix-literal": true + } +} +``` + +```diff +-.bp3-button > div { +- border: 1px solid black; +-} ++ @import "~@blueprntjs/core/lib/scss/variables"; ++ ++.#{$ns}-button > div { ++ border: 1px solid black; ++} +``` + +Optional secondary options: + +- `disableFix: boolean` - if true, autofix will be disabled +- `variablesImportPath: { less?: string, sass?: string }` - can be used to configure a custom path for importing Blueprint variables when autofixing. + + +### [Full Documentation](http://blueprintjs.com/docs) | [Source Code](https://github.com/palantir/blueprint) diff --git a/packages/stylelint-plugin/package.json b/packages/stylelint-plugin/package.json new file mode 100644 index 0000000000..c9f9d0a473 --- /dev/null +++ b/packages/stylelint-plugin/package.json @@ -0,0 +1,33 @@ +{ + "name": "@blueprintjs/stylelint-plugin", + "version": "0.0.0", + "description": "Stylelint rules for use with @blueprintjs packages", + "main": "lib/index.js", + "scripts": { + "compile": "tsc -p src/", + "lint": "run-p lint:es", + "lint:es": "es-lint", + "lint-fix": "es-lint --fix", + "test": "mocha test/index.js" + }, + "dependencies": { + "postcss": "^7.0.35", + "postcss-selector-parser": "^6.0.5" + }, + "peerDependencies": { + "stylelint": "^13.0.0" + }, + "devDependencies": { + "@blueprintjs/node-build-scripts": "^1.5.0", + "@types/stylelint": "^9.10.1", + "mocha": "^8.2.1", + "typescript": "^4.1.2" + }, + "repository": { + "type": "git", + "url": "git@github.com:palantir/blueprint.git", + "directory": "packages/stylelint-plugin" + }, + "author": "Palantir Technologies", + "license": "Apache-2.0" +} diff --git a/packages/stylelint-plugin/src/index.ts b/packages/stylelint-plugin/src/index.ts new file mode 100644 index 0000000000..e5445f323a --- /dev/null +++ b/packages/stylelint-plugin/src/index.ts @@ -0,0 +1,18 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +import noPrefixLiteral from "./rules/no-prefix-literal"; + +// eslint-disable-next-line import/no-default-export +export default [noPrefixLiteral]; diff --git a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts new file mode 100644 index 0000000000..b1ed8267ee --- /dev/null +++ b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts @@ -0,0 +1,147 @@ +/* + * Copyright 2021 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Root, Result } from "postcss"; +import parser from "postcss-selector-parser"; +import stylelint from "stylelint"; +import type { Plugin, RuleTesterContext } from "stylelint"; + +import { checkImportExists } from "../utils/checkImportExists"; +import { insertImport } from "../utils/insertImport"; + +const ruleName = "@blueprintjs/no-prefix-literal"; + +const messages = stylelint.utils.ruleMessages(ruleName, { + expected: (unfixed: string, fixed: string) => `Use the \`${fixed}\` variable instead of the \`${unfixed}\` literal`, +}); + +const bannedPrefixes = ["bp1", "bp2", "bp3", "bp4"]; + +interface Options { + disableFix?: boolean; + variablesImportPath?: Partial, string>>; +} + +// eslint-disable-next-line import/no-default-export +export default stylelint.createPlugin(ruleName, (( + enabled: boolean, + options: Options | undefined, + context: RuleTesterContext, +) => (root: Root, result: Result) => { + if (!enabled) { + return; + } + + const validOptions = stylelint.utils.validateOptions( + result, + ruleName, + { + actual: enabled, + optional: false, + possible: [true, false], + }, + { + actual: options, + optional: true, + possible: { + disableFix: [true, false], + variablesImportPath: (obj: unknown) => { + if (typeof obj !== "object" || obj == null) { + return false; + } + // Check that the keys and their values are correct + const allowedKeys = new Set(Object.values(CssSyntax).filter(v => v !== CssSyntax.OTHER)); + return Object.keys(obj).every(key => allowedKeys.has(key) && typeof (obj as any)[key] === "string"); + }, + }, + }, + ); + + if (!validOptions) { + return; + } + + const disableFix = options?.disableFix ?? false; + + const cssSyntax = getCssSyntax(root.source?.input.file || ""); + if (cssSyntax === CssSyntax.OTHER) { + return; + } + + let hasBpVariablesImport: boolean | undefined; // undefined means not checked yet + function assertBpVariablesImportExists(cssSyntaxType: CssSyntax.SASS | CssSyntax.LESS) { + const importPath = options?.variablesImportPath?.[cssSyntaxType] ?? BpVariableImportMap[cssSyntaxType]; + if (hasBpVariablesImport == null) { + hasBpVariablesImport = checkImportExists(root, importPath); + } + if (!hasBpVariablesImport) { + insertImport(root, context, importPath); + hasBpVariablesImport = true; + } + } + + root.walkRules(rule => { + parser(selectors => { + selectors.walkClasses(selector => { + for (const bannedPrefix of bannedPrefixes) { + if (!selector.value.startsWith(`${bannedPrefix}-`)) { + continue; + } + if ((context as any).fix && !disableFix) { + assertBpVariablesImportExists(cssSyntax); + rule.selector = rule.selector.replace(bannedPrefix, BpPrefixVariableMap[cssSyntax]); + } else { + stylelint.utils.report({ + // HACKHACK - offset by one because otherwise the error is reported at a wrong position + index: selector.sourceIndex + 1, + message: messages.expected(bannedPrefix, BpPrefixVariableMap[cssSyntax]), + node: rule, + result, + ruleName, + }); + } + } + }); + }).processSync(rule.selector); + }); +}) as Plugin); + +enum CssSyntax { + SASS = "sass", + LESS = "less", + OTHER = "other", +} + +const BpPrefixVariableMap: Record, string> = { + [CssSyntax.SASS]: "#{$ns}", + [CssSyntax.LESS]: "@{ns}", +}; + +const BpVariableImportMap: Record, string> = { + [CssSyntax.SASS]: "~@blueprntjs/core/lib/scss/variables", + [CssSyntax.LESS]: "~@blueprintjs/core/lib/less/variables", +}; + +/** + * Returns the flavor of the CSS we're dealing with. + */ +function getCssSyntax(fileName: string): CssSyntax { + if (fileName.endsWith(".scss")) { + return CssSyntax.SASS; + } else if (fileName.endsWith(".less")) { + return CssSyntax.LESS; + } + return CssSyntax.OTHER; +} diff --git a/packages/stylelint-plugin/src/tsconfig.json b/packages/stylelint-plugin/src/tsconfig.json new file mode 100644 index 0000000000..3847bd2142 --- /dev/null +++ b/packages/stylelint-plugin/src/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../config/tsconfig.base", + "compilerOptions": { + "lib": ["es6", "dom"], + "module": "commonjs", + "outDir": "../lib", + "target": "ES2015" + } +} diff --git a/packages/stylelint-plugin/src/utils/checkImportExists.ts b/packages/stylelint-plugin/src/utils/checkImportExists.ts new file mode 100644 index 0000000000..4f63b305b8 --- /dev/null +++ b/packages/stylelint-plugin/src/utils/checkImportExists.ts @@ -0,0 +1,41 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +import type { Root } from "postcss"; + +/** + * Returns true if the given import exists in the file, otherwise returns false. + */ +export function checkImportExists(root: Root, importPath: string): boolean { + let hasBpVarsImport = false; + root.walkAtRules(/^import$/i, atRule => { + // `atRule.params` includes quotes around the string, so we strip them. + if (stripQuotes(atRule.params) === importPath) { + hasBpVarsImport = true; + return false; // Stop the iteration + } + return true; + }); + return hasBpVarsImport; +} + +function stripQuotes(str: string): string { + if ( + (str.charAt(0) === '"' && str.charAt(str.length - 1) === '"') || + (str.charAt(0) === "'" && str.charAt(str.length - 1) === "'") + ) { + return str.substr(1, str.length - 2); + } + return str; +} diff --git a/packages/stylelint-plugin/src/utils/insertImport.ts b/packages/stylelint-plugin/src/utils/insertImport.ts new file mode 100644 index 0000000000..4505270336 --- /dev/null +++ b/packages/stylelint-plugin/src/utils/insertImport.ts @@ -0,0 +1,79 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +import postcss, { Root } from "postcss"; +import type { RuleTesterContext } from "stylelint"; + +/** + * Adds an import statement to the file. The import is inserted below the existing imports, and if there are + * no imports present then it's inserted at the top of the file (but below any copyright headers). + */ +export function insertImport(root: Root, context: RuleTesterContext, importPath: string): void { + const newline = (context as any).newline || "\n"; + const ruleOrComment = getLastImport(root) || getCopyrightHeader(root); + if (ruleOrComment != null) { + const importNode = postcss.atRule({ + name: "import", + params: `"${importPath}"`, + raws: { + afterName: " ", + before: ruleOrComment.type === "comment" ? `${newline}${newline}` : newline, + semicolon: true, + }, + }); + root.insertAfter(ruleOrComment, importNode); + } else { + const importNode = postcss.atRule({ + name: "import", + params: `"${importPath}"`, + raws: { + afterName: " ", + before: "", + semicolon: true, + }, + }); + root.prepend(importNode); + // Add space before next block + const nextChild = root.nodes?.[1]; + if (nextChild != null) { + nextChild.raws.before = `${newline}${newline}${nextChild.raws.before || ""}`; + } + } +} + +/** + * Returns the last import node in the file, or undefined if one does not exist + */ +function getLastImport(root: Root): postcss.AtRule | undefined { + let lastImport: postcss.AtRule | undefined; + root.walkAtRules(/^import$/i, atRule => { + lastImport = atRule; + }); + return lastImport; +} + +/** + * Returns the first copyright header in the file, or undefined if one does not exist + */ +function getCopyrightHeader(root: Root): postcss.Comment | undefined { + let copyrightComment: postcss.Comment | undefined; + root.walkComments(comment => { + if (comment.text.toLowerCase().includes("copyright")) { + copyrightComment = comment; + return false; // Stop the iteration + } + return true; + }); + return copyrightComment; +} diff --git a/packages/stylelint-plugin/test/fixtures/contains-bp3.scss b/packages/stylelint-plugin/test/fixtures/contains-bp3.scss new file mode 100644 index 0000000000..bc913e2773 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-bp3.scss @@ -0,0 +1,3 @@ +.bp3-tag { + width: 10px; +} diff --git a/packages/stylelint-plugin/test/fixtures/contains-nested-bp3.scss b/packages/stylelint-plugin/test/fixtures/contains-nested-bp3.scss new file mode 100644 index 0000000000..e314ae2695 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-nested-bp3.scss @@ -0,0 +1,5 @@ +#some-id { + div > .some-class.bp3-button.some-other-class { + width: 10px; + } +} diff --git a/packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.scss b/packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.scss new file mode 100644 index 0000000000..9da0304281 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.scss @@ -0,0 +1,3 @@ +.x-bp3-y > span { + width: 10px; +} diff --git a/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss new file mode 100644 index 0000000000..6fcf6635f4 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss @@ -0,0 +1,5 @@ +.some-class { + .#{$ns}-button { + width: 10px; + } +} diff --git a/packages/stylelint-plugin/test/index.js b/packages/stylelint-plugin/test/index.js new file mode 100644 index 0000000000..a0dc47e513 --- /dev/null +++ b/packages/stylelint-plugin/test/index.js @@ -0,0 +1,15 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +require("./no-prefix-literal.test"); diff --git a/packages/stylelint-plugin/test/no-prefix-literal.test.js b/packages/stylelint-plugin/test/no-prefix-literal.test.js new file mode 100644 index 0000000000..99a30b9b4e --- /dev/null +++ b/packages/stylelint-plugin/test/no-prefix-literal.test.js @@ -0,0 +1,57 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +// eslint-disable-next-line import/no-extraneous-dependencies +const { expect } = require("chai"); +const stylelint = require("stylelint"); + +const config = { + plugins: ["@blueprintjs/stylelint-plugin"], + rules: { + "@blueprintjs/no-prefix-literal": true, + "at-rule-semicolon-newline-after": "always", + }, +}; + +it("Warns when .bp3 is present", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.scss", + config, + }); + expect(result.errored).to.be.true; +}); + +it("Warns when nested .bp3 is present", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-nested-bp3.scss", + config, + }); + expect(result.errored).to.be.true; +}); + +it("Doesn't warn bp3 string is present but not as a prefix", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-non-prefix-bp3.scss", + config, + }); + expect(result.errored).to.be.false; +}); + +it("Doesn't warn when .bp3 is not present", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/does-not-contain-bp3.scss", + config, + }); + expect(result.errored).to.be.false; +}); diff --git a/yarn.lock b/yarn.lock index ae08713d87..f5a2a19a9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1506,6 +1506,13 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== +"@types/stylelint@^9.10.1": + version "9.10.1" + resolved "https://artifactory.palantir.build/artifactory/api/npm/all-npm/@types/stylelint/-/stylelint-9.10.1.tgz#211832381e43fd0774217b59f02ab389d82643ea" + integrity sha1-IRgyOB5D/Qd0IXtZ8CqzidgmQ+o= + dependencies: + postcss "7.x.x" + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" @@ -10106,6 +10113,14 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: uniq "^1.0.1" util-deprecate "^1.0.2" +postcss-selector-parser@^6.0.5: + version "6.0.5" + resolved "https://artifactory.palantir.build/artifactory/api/npm/all-npm/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz#042d74e137db83e6f294712096cb413f5aa612c4" + integrity sha1-BC104Tfbg+bylHEglstBP1qmEsQ= + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-sorting@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/postcss-sorting/-/postcss-sorting-5.0.1.tgz#10d5d0059eea8334dacc820c0121864035bc3f11" @@ -10148,6 +10163,15 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== +postcss@7.x.x, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.31, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.6: + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + postcss@^6.0.14: version "6.0.23" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" @@ -10157,15 +10181,6 @@ postcss@^6.0.14: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.31, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.6: - version "7.0.35" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" - integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - postcss@^8.1.14, postcss@^8.1.4, postcss@^8.2.4: version "8.2.4" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.4.tgz#20a98a39cf303d15129c2865a9ec37eda0031d04" From 77ff434885fab8eb6736e10f2732989d269632e2 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 13:07:41 +0000 Subject: [PATCH 02/13] Fix yarn.lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index f5a2a19a9d..b765305fa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1508,7 +1508,7 @@ "@types/stylelint@^9.10.1": version "9.10.1" - resolved "https://artifactory.palantir.build/artifactory/api/npm/all-npm/@types/stylelint/-/stylelint-9.10.1.tgz#211832381e43fd0774217b59f02ab389d82643ea" + resolved "https://registry.yarnpkg.com/artifactory/api/npm/all-npm/@types/stylelint/-/stylelint-9.10.1.tgz#211832381e43fd0774217b59f02ab389d82643ea" integrity sha1-IRgyOB5D/Qd0IXtZ8CqzidgmQ+o= dependencies: postcss "7.x.x" @@ -10115,7 +10115,7 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: postcss-selector-parser@^6.0.5: version "6.0.5" - resolved "https://artifactory.palantir.build/artifactory/api/npm/all-npm/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz#042d74e137db83e6f294712096cb413f5aa612c4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz#042d74e137db83e6f294712096cb413f5aa612c4" integrity sha1-BC104Tfbg+bylHEglstBP1qmEsQ= dependencies: cssesc "^3.0.0" From 6e7a5a1dee9abc251a13709ad5f31c2c72d7dc91 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 13:09:43 +0000 Subject: [PATCH 03/13] Actually fix yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index b765305fa1..40b8f26b4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1508,7 +1508,7 @@ "@types/stylelint@^9.10.1": version "9.10.1" - resolved "https://registry.yarnpkg.com/artifactory/api/npm/all-npm/@types/stylelint/-/stylelint-9.10.1.tgz#211832381e43fd0774217b59f02ab389d82643ea" + resolved "https://registry.yarnpkg.com/@types/stylelint/-/stylelint-9.10.1.tgz#211832381e43fd0774217b59f02ab389d82643ea" integrity sha1-IRgyOB5D/Qd0IXtZ8CqzidgmQ+o= dependencies: postcss "7.x.x" From 0fa08dd216be2c13cbe626772a64d1e201bac064 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 13:13:13 +0000 Subject: [PATCH 04/13] Deduplicate the lockfile --- yarn.lock | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 40b8f26b4d..771fdfe6bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10103,17 +10103,7 @@ postcss-selector-parser@^3.0.0: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.5: +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.5.tgz#042d74e137db83e6f294712096cb413f5aa612c4" integrity sha1-BC104Tfbg+bylHEglstBP1qmEsQ= From bf52e04cfcfbd62153cec47fe31d4c72a09874ff Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 13:18:58 +0000 Subject: [PATCH 05/13] CR --- packages/stylelint-plugin/src/rules/no-prefix-literal.ts | 4 ++-- packages/stylelint-plugin/test/no-prefix-literal.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts index b1ed8267ee..ebce83c29c 100644 --- a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts +++ b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts @@ -27,7 +27,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, { expected: (unfixed: string, fixed: string) => `Use the \`${fixed}\` variable instead of the \`${unfixed}\` literal`, }); -const bannedPrefixes = ["bp1", "bp2", "bp3", "bp4"]; +const bannedPrefixes = ["bp", "bp3", "bp4"]; interface Options { disableFix?: boolean; @@ -130,7 +130,7 @@ const BpPrefixVariableMap: Record, string> = }; const BpVariableImportMap: Record, string> = { - [CssSyntax.SASS]: "~@blueprntjs/core/lib/scss/variables", + [CssSyntax.SASS]: "~@blueprintjs/core/lib/scss/variables", [CssSyntax.LESS]: "~@blueprintjs/core/lib/less/variables", }; diff --git a/packages/stylelint-plugin/test/no-prefix-literal.test.js b/packages/stylelint-plugin/test/no-prefix-literal.test.js index 99a30b9b4e..c1e58f2c5f 100644 --- a/packages/stylelint-plugin/test/no-prefix-literal.test.js +++ b/packages/stylelint-plugin/test/no-prefix-literal.test.js @@ -32,7 +32,7 @@ it("Warns when .bp3 is present", async () => { expect(result.errored).to.be.true; }); -it("Warns when nested .bp3 is present", async () => { +it("Warns when nested .bp3 is present even when not first selector", async () => { const result = await stylelint.lint({ files: "test/fixtures/contains-nested-bp3.scss", config, From 6d7f5d21e4d9658ac04041db5d8f5f334ad4ced5 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 13:48:49 +0000 Subject: [PATCH 06/13] More comprehensive tests --- .../test/fixtures/contains-bp3-disabled.scss | 4 + .../test/fixtures/contains-bp3.module.scss | 3 + .../fixtures/contains-nested-bp3.module.scss | 5 + .../contains-non-prefix-bp3.module.scss | 3 + .../fixtures/does-not-contain-bp3.module.scss | 5 + .../test/no-prefix-literal.test.js | 97 ++++++++++++++++++- 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 packages/stylelint-plugin/test/fixtures/contains-bp3-disabled.scss create mode 100644 packages/stylelint-plugin/test/fixtures/contains-bp3.module.scss create mode 100644 packages/stylelint-plugin/test/fixtures/contains-nested-bp3.module.scss create mode 100644 packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.module.scss create mode 100644 packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss diff --git a/packages/stylelint-plugin/test/fixtures/contains-bp3-disabled.scss b/packages/stylelint-plugin/test/fixtures/contains-bp3-disabled.scss new file mode 100644 index 0000000000..14f914e532 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-bp3-disabled.scss @@ -0,0 +1,4 @@ +/* stylelint-disable-next-line @blueprintjs/no-prefix-literal */ +:global(.bp3-tag) { + width: 10px; +} diff --git a/packages/stylelint-plugin/test/fixtures/contains-bp3.module.scss b/packages/stylelint-plugin/test/fixtures/contains-bp3.module.scss new file mode 100644 index 0000000000..8f15128a55 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-bp3.module.scss @@ -0,0 +1,3 @@ +:global(.bp3-tag) { + width: 10px; +} diff --git a/packages/stylelint-plugin/test/fixtures/contains-nested-bp3.module.scss b/packages/stylelint-plugin/test/fixtures/contains-nested-bp3.module.scss new file mode 100644 index 0000000000..5d0c8fab07 --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-nested-bp3.module.scss @@ -0,0 +1,5 @@ +#some-id { + div > .some-class:global(.bp3-button):global(.some-other-class) { + width: 10px; + } +} diff --git a/packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.module.scss b/packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.module.scss new file mode 100644 index 0000000000..21e0a03c4a --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/contains-non-prefix-bp3.module.scss @@ -0,0 +1,3 @@ +:global(.x-bp3-y) > span { + width: 10px; +} diff --git a/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss new file mode 100644 index 0000000000..f267c24bef --- /dev/null +++ b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss @@ -0,0 +1,5 @@ +.some-class { + :global(.#{$ns})-button { + width: 10px; + } +} diff --git a/packages/stylelint-plugin/test/no-prefix-literal.test.js b/packages/stylelint-plugin/test/no-prefix-literal.test.js index c1e58f2c5f..4cc4ca3888 100644 --- a/packages/stylelint-plugin/test/no-prefix-literal.test.js +++ b/packages/stylelint-plugin/test/no-prefix-literal.test.js @@ -20,7 +20,6 @@ const config = { plugins: ["@blueprintjs/stylelint-plugin"], rules: { "@blueprintjs/no-prefix-literal": true, - "at-rule-semicolon-newline-after": "always", }, }; @@ -30,6 +29,22 @@ it("Warns when .bp3 is present", async () => { config, }); expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(1); + expect(warnings[0].column).to.be.eq(2); +}); + +it("Warns when .bp3 is present (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.module.scss", + config, + }); + expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(1); + expect(warnings[0].column).to.be.eq(10); }); it("Warns when nested .bp3 is present even when not first selector", async () => { @@ -38,6 +53,22 @@ it("Warns when nested .bp3 is present even when not first selector", async () => config, }); expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(2); + expect(warnings[0].column).to.be.eq(21); +}); + +it("Warns when nested .bp3 is present even when not first selector (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-nested-bp3.module.scss", + config, + }); + expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(2); + expect(warnings[0].column).to.be.eq(29); }); it("Doesn't warn bp3 string is present but not as a prefix", async () => { @@ -48,10 +79,74 @@ it("Doesn't warn bp3 string is present but not as a prefix", async () => { expect(result.errored).to.be.false; }); +it("Doesn't warn bp3 string is present but not as a prefix (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-non-prefix-bp3.module.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); +}); + it("Doesn't warn when .bp3 is not present", async () => { const result = await stylelint.lint({ files: "test/fixtures/does-not-contain-bp3.scss", config, }); expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); +}); + +it("Doesn't warn when .bp3 is not present (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/does-not-contain-bp3.module.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); +}); + +it("Doesn't warn when .bp3 is present but lint rule is disabled", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3-disabled.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); +}); + +it("Accepts a valid secondary config", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.scss", + config: { + plugins: ["@blueprintjs/stylelint-plugin"], + rules: { + "@blueprintjs/no-prefix-literal": [ + true, + { disableFix: true, variablesImportPath: { sass: "some-path" } }, + ], + }, + }, + }); + expect(result.results[0].invalidOptionWarnings.length).to.be.eq(0); +}); + +it("Rejects an invalid secondary config", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.scss", + config: { + plugins: ["@blueprintjs/stylelint-plugin"], + rules: { + "@blueprintjs/no-prefix-literal": [ + true, + { disableFix: "yes", variablesImportPath: { scss: "some-path", somethingElse: "some-other-path" } }, + ], + }, + }, + }); + expect(result.results[0].invalidOptionWarnings.length).to.be.eq(2); }); From 41d5431613d17803d5e84be0eea6801e223ce76d Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 14:38:39 +0000 Subject: [PATCH 07/13] Tests for the autofixer --- .../src/utils/insertImport.ts | 9 +- .../test/checkImportExists.test.js | 60 +++++++++++++ packages/stylelint-plugin/test/index.js | 2 + .../test/insertImport.test.js | 88 +++++++++++++++++++ 4 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 packages/stylelint-plugin/test/checkImportExists.test.js create mode 100644 packages/stylelint-plugin/test/insertImport.test.js diff --git a/packages/stylelint-plugin/src/utils/insertImport.ts b/packages/stylelint-plugin/src/utils/insertImport.ts index 4505270336..d30a43b770 100644 --- a/packages/stylelint-plugin/src/utils/insertImport.ts +++ b/packages/stylelint-plugin/src/utils/insertImport.ts @@ -20,7 +20,7 @@ import type { RuleTesterContext } from "stylelint"; * no imports present then it's inserted at the top of the file (but below any copyright headers). */ export function insertImport(root: Root, context: RuleTesterContext, importPath: string): void { - const newline = (context as any).newline || "\n"; + const newline: string = (context as any).newline || "\n"; const ruleOrComment = getLastImport(root) || getCopyrightHeader(root); if (ruleOrComment != null) { const importNode = postcss.atRule({ @@ -44,10 +44,13 @@ export function insertImport(root: Root, context: RuleTesterContext, importPath: }, }); root.prepend(importNode); - // Add space before next block + // Make sure there are at least two newlines before the next child const nextChild = root.nodes?.[1]; if (nextChild != null) { - nextChild.raws.before = `${newline}${newline}${nextChild.raws.before || ""}`; + const nExistingNewlines = nextChild.raws.before?.split("")?.filter(char => char === newline).length ?? 0; + nextChild.raws.before = `${newline.repeat(Math.max(0, 2 - nExistingNewlines))}${ + nextChild.raws.before || "" + }`; } } } diff --git a/packages/stylelint-plugin/test/checkImportExists.test.js b/packages/stylelint-plugin/test/checkImportExists.test.js new file mode 100644 index 0000000000..26cf004461 --- /dev/null +++ b/packages/stylelint-plugin/test/checkImportExists.test.js @@ -0,0 +1,60 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +// eslint-disable-next-line import/no-extraneous-dependencies +const { expect } = require("chai"); +const postcss = require("postcss"); + +const { checkImportExists } = require("../lib/utils/checkImportExists"); + +it("Returns false if no imports exist", () => { + const root = postcss.parse(`.some-class { width: 10px }`); + expect(checkImportExists(root, "some_path")).to.be.false; +}); + +it("Returns false if imports exist but not the one we want", () => { + const root = postcss.parse(` +@import "some_path1"; +@import "some_path2"; + +.some-class { + width: 10px; +} +`); + expect(checkImportExists(root, "some_path")).to.be.false; +}); + +it("Returns true if our import exists", () => { + const root = postcss.parse(` +@import "some_path1"; +@import "some_path2"; +@import "some_path"; +.some-class { + width: 10px; +} +`); + expect(checkImportExists(root, "some_path")).to.be.true; +}); + +it("Returns true if our import exists, and works with single quotes", () => { + const root = postcss.parse(` +@import 'some_path1'; +@import 'some_path2'; +@import 'some_path'; +.some-class { + width: 10px; +} +`); + expect(checkImportExists(root, "some_path")).to.be.true; +}); diff --git a/packages/stylelint-plugin/test/index.js b/packages/stylelint-plugin/test/index.js index a0dc47e513..9c3a3c5cf2 100644 --- a/packages/stylelint-plugin/test/index.js +++ b/packages/stylelint-plugin/test/index.js @@ -13,3 +13,5 @@ See the License for the specific language governing permissions and limitations under the License.*/ require("./no-prefix-literal.test"); +require("./checkImportExists.test"); +require("./insertImport.test"); diff --git a/packages/stylelint-plugin/test/insertImport.test.js b/packages/stylelint-plugin/test/insertImport.test.js new file mode 100644 index 0000000000..07e97bcb6a --- /dev/null +++ b/packages/stylelint-plugin/test/insertImport.test.js @@ -0,0 +1,88 @@ +/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +// eslint-disable-next-line import/no-extraneous-dependencies +const { expect } = require("chai"); +const postcss = require("postcss"); + +const { insertImport } = require("../lib/utils/insertImport"); + +it("Inserts an import at the top of the file when no imports are present", () => { + const root = postcss.parse(`.some-class { width: 10px }`); + insertImport(root, { newline: "\n" }, "some_path"); + expect(root.toString()).to.be.eq(`@import "some_path"; + +.some-class { width: 10px }`); +}); + +it("Inserts an import below other imports", () => { + const root = postcss.parse(`@import "some_path1"; +.some-class { width: 10px }`); + insertImport(root, { newline: "\n" }, "some_path2"); + expect(root.toString()).to.be.eq(`@import "some_path1"; +@import "some_path2"; +.some-class { width: 10px }`); +}); + +it("Inserts an import below the copyright header if no other imports exist", () => { + const root = postcss.parse(`/* copyright 2021 */ + +.some-class { width: 10px }`); + insertImport(root, { newline: "\n" }, "some_path"); + expect(root.toString()).to.be.eq(`/* copyright 2021 */ + +@import "some_path"; + +.some-class { width: 10px }`); +}); + +it("Inserts an import below other imports if the copyright header exists", () => { + const root = postcss.parse(`/* copyright 2021 */ + +@import "some_path1"; +@import "some_path2"; + +.some-class { width: 10px }`); + insertImport(root, { newline: "\n" }, "some_path3"); + expect(root.toString()).to.be.eq(`/* copyright 2021 */ + +@import "some_path1"; +@import "some_path2"; +@import "some_path3"; + +.some-class { width: 10px }`); +}); + +it("Doesn't treat media queries as imports", () => { + const root = postcss.parse(` +@media only screen and (max-width: 600px) { + body { + background-color: lightblue; + } +} + +.some-class { width: 10px } +`); + insertImport(root, { newline: "\n" }, "some_path"); + expect(root.toString()).to.be.eq(`@import "some_path"; + +@media only screen and (max-width: 600px) { + body { + background-color: lightblue; + } +} + +.some-class { width: 10px } +`); +}); From baed4f02e60599fa6b4da972eedfb31a317e0539 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Wed, 28 Apr 2021 16:04:46 +0000 Subject: [PATCH 08/13] Ignore extension when checking for import --- .../src/rules/no-prefix-literal.ts | 16 +++++++++++----- .../src/utils/checkImportExists.ts | 13 ++++++++----- .../test/checkImportExists.test.js | 10 ++++++++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts index ebce83c29c..aa4a8c527a 100644 --- a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts +++ b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts @@ -83,8 +83,9 @@ export default stylelint.createPlugin(ruleName, (( let hasBpVariablesImport: boolean | undefined; // undefined means not checked yet function assertBpVariablesImportExists(cssSyntaxType: CssSyntax.SASS | CssSyntax.LESS) { const importPath = options?.variablesImportPath?.[cssSyntaxType] ?? BpVariableImportMap[cssSyntaxType]; + const extension = CssExtensionMap[cssSyntaxType]; if (hasBpVariablesImport == null) { - hasBpVariablesImport = checkImportExists(root, importPath); + hasBpVariablesImport = checkImportExists(root, [importPath, `${importPath}.${extension}`]); } if (!hasBpVariablesImport) { insertImport(root, context, importPath); @@ -124,6 +125,11 @@ enum CssSyntax { OTHER = "other", } +const CssExtensionMap: Record, string> = { + [CssSyntax.SASS]: "scss", + [CssSyntax.LESS]: "less", +}; + const BpPrefixVariableMap: Record, string> = { [CssSyntax.SASS]: "#{$ns}", [CssSyntax.LESS]: "@{ns}", @@ -138,10 +144,10 @@ const BpVariableImportMap: Record, string> = * Returns the flavor of the CSS we're dealing with. */ function getCssSyntax(fileName: string): CssSyntax { - if (fileName.endsWith(".scss")) { - return CssSyntax.SASS; - } else if (fileName.endsWith(".less")) { - return CssSyntax.LESS; + for (const cssSyntax of Object.keys(CssExtensionMap)) { + if (fileName.endsWith(`.${CssExtensionMap[cssSyntax as Exclude]}`)) { + return cssSyntax as Exclude; + } } return CssSyntax.OTHER; } diff --git a/packages/stylelint-plugin/src/utils/checkImportExists.ts b/packages/stylelint-plugin/src/utils/checkImportExists.ts index 4f63b305b8..e265625e24 100644 --- a/packages/stylelint-plugin/src/utils/checkImportExists.ts +++ b/packages/stylelint-plugin/src/utils/checkImportExists.ts @@ -16,14 +16,17 @@ import type { Root } from "postcss"; /** * Returns true if the given import exists in the file, otherwise returns false. + * If `importPath` is an array, any of the strings has to match in order fortrue to be returned. */ -export function checkImportExists(root: Root, importPath: string): boolean { +export function checkImportExists(root: Root, importPath: string | string[]): boolean { let hasBpVarsImport = false; root.walkAtRules(/^import$/i, atRule => { - // `atRule.params` includes quotes around the string, so we strip them. - if (stripQuotes(atRule.params) === importPath) { - hasBpVarsImport = true; - return false; // Stop the iteration + for (const path of typeof importPath === "string" ? [importPath] : importPath) { + // `atRule.params` includes quotes around the string, so we strip them. + if (stripQuotes(atRule.params) === path) { + hasBpVarsImport = true; + return false; // Stop the iteration + } } return true; }); diff --git a/packages/stylelint-plugin/test/checkImportExists.test.js b/packages/stylelint-plugin/test/checkImportExists.test.js index 26cf004461..dc87178c3f 100644 --- a/packages/stylelint-plugin/test/checkImportExists.test.js +++ b/packages/stylelint-plugin/test/checkImportExists.test.js @@ -58,3 +58,13 @@ it("Returns true if our import exists, and works with single quotes", () => { `); expect(checkImportExists(root, "some_path")).to.be.true; }); + +it("Can match multiple paths", () => { + const root = postcss.parse(` +@import 'some_path.scss'; +.some-class { + width: 10px; +} +`); + expect(checkImportExists(root, ["some_path", "some_path.scss"])).to.be.true; +}); From 4da713ae3ec7e4d1e823359962096db7de8e2c8f Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Thu, 29 Apr 2021 08:32:50 +0000 Subject: [PATCH 09/13] cr --- packages/stylelint-plugin/.eslintrc.json | 3 +- packages/stylelint-plugin/src/index.ts | 28 +-- .../src/rules/no-prefix-literal.ts | 1 - .../src/utils/checkImportExists.ts | 27 +- .../src/utils/insertImport.ts | 27 +- .../test/checkImportExists.test.js | 75 +++--- packages/stylelint-plugin/test/index.js | 27 +- .../test/insertImport.test.js | 87 ++++--- .../test/no-prefix-literal.test.js | 238 +++++++++--------- 9 files changed, 267 insertions(+), 246 deletions(-) diff --git a/packages/stylelint-plugin/.eslintrc.json b/packages/stylelint-plugin/.eslintrc.json index 21c420322e..e8e15bf480 100644 --- a/packages/stylelint-plugin/.eslintrc.json +++ b/packages/stylelint-plugin/.eslintrc.json @@ -3,7 +3,8 @@ "extends": ["../../.eslintrc.js"], "rules": { "no-duplicate-imports": "off", - "@typescript-eslint/no-duplicate-imports": ["error"] + "@typescript-eslint/no-duplicate-imports": ["error"], + "import/no-default-export": "off" }, "ignorePatterns": ["node_modules", "dist", "lib", "fixtures", "coverage", "__snapshots__", "generated"] } diff --git a/packages/stylelint-plugin/src/index.ts b/packages/stylelint-plugin/src/index.ts index e5445f323a..90614262ac 100644 --- a/packages/stylelint-plugin/src/index.ts +++ b/packages/stylelint-plugin/src/index.ts @@ -1,18 +1,18 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import noPrefixLiteral from "./rules/no-prefix-literal"; -// eslint-disable-next-line import/no-default-export export default [noPrefixLiteral]; diff --git a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts index aa4a8c527a..1aa66d6132 100644 --- a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts +++ b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts @@ -34,7 +34,6 @@ interface Options { variablesImportPath?: Partial, string>>; } -// eslint-disable-next-line import/no-default-export export default stylelint.createPlugin(ruleName, (( enabled: boolean, options: Options | undefined, diff --git a/packages/stylelint-plugin/src/utils/checkImportExists.ts b/packages/stylelint-plugin/src/utils/checkImportExists.ts index e265625e24..5664f46d95 100644 --- a/packages/stylelint-plugin/src/utils/checkImportExists.ts +++ b/packages/stylelint-plugin/src/utils/checkImportExists.ts @@ -1,16 +1,17 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import type { Root } from "postcss"; diff --git a/packages/stylelint-plugin/src/utils/insertImport.ts b/packages/stylelint-plugin/src/utils/insertImport.ts index d30a43b770..8b22fb4e58 100644 --- a/packages/stylelint-plugin/src/utils/insertImport.ts +++ b/packages/stylelint-plugin/src/utils/insertImport.ts @@ -1,16 +1,17 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import postcss, { Root } from "postcss"; import type { RuleTesterContext } from "stylelint"; diff --git a/packages/stylelint-plugin/test/checkImportExists.test.js b/packages/stylelint-plugin/test/checkImportExists.test.js index dc87178c3f..ef4cb5d870 100644 --- a/packages/stylelint-plugin/test/checkImportExists.test.js +++ b/packages/stylelint-plugin/test/checkImportExists.test.js @@ -1,16 +1,17 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // eslint-disable-next-line import/no-extraneous-dependencies const { expect } = require("chai"); @@ -18,53 +19,55 @@ const postcss = require("postcss"); const { checkImportExists } = require("../lib/utils/checkImportExists"); -it("Returns false if no imports exist", () => { - const root = postcss.parse(`.some-class { width: 10px }`); - expect(checkImportExists(root, "some_path")).to.be.false; -}); +describe("checkImportExists", () => { + it("Returns false if no imports exist", () => { + const root = postcss.parse(`.some-class { width: 10px }`); + expect(checkImportExists(root, "some_path")).to.be.false; + }); -it("Returns false if imports exist but not the one we want", () => { - const root = postcss.parse(` + it("Returns false if imports exist but not the one we want", () => { + const root = postcss.parse(` @import "some_path1"; @import "some_path2"; .some-class { width: 10px; } -`); - expect(checkImportExists(root, "some_path")).to.be.false; -}); + `); + expect(checkImportExists(root, "some_path")).to.be.false; + }); -it("Returns true if our import exists", () => { - const root = postcss.parse(` + it("Returns true if our import exists", () => { + const root = postcss.parse(` @import "some_path1"; @import "some_path2"; @import "some_path"; .some-class { width: 10px; } -`); - expect(checkImportExists(root, "some_path")).to.be.true; -}); + `); + expect(checkImportExists(root, "some_path")).to.be.true; + }); -it("Returns true if our import exists, and works with single quotes", () => { - const root = postcss.parse(` + it("Returns true if our import exists, and works with single quotes", () => { + const root = postcss.parse(` @import 'some_path1'; @import 'some_path2'; @import 'some_path'; .some-class { width: 10px; } -`); - expect(checkImportExists(root, "some_path")).to.be.true; -}); + `); + expect(checkImportExists(root, "some_path")).to.be.true; + }); -it("Can match multiple paths", () => { - const root = postcss.parse(` + it("Can match multiple paths", () => { + const root = postcss.parse(` @import 'some_path.scss'; .some-class { width: 10px; } -`); - expect(checkImportExists(root, ["some_path", "some_path.scss"])).to.be.true; + `); + expect(checkImportExists(root, ["some_path", "some_path.scss"])).to.be.true; + }); }); diff --git a/packages/stylelint-plugin/test/index.js b/packages/stylelint-plugin/test/index.js index 9c3a3c5cf2..ba051c71ce 100644 --- a/packages/stylelint-plugin/test/index.js +++ b/packages/stylelint-plugin/test/index.js @@ -1,16 +1,17 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ require("./no-prefix-literal.test"); require("./checkImportExists.test"); diff --git a/packages/stylelint-plugin/test/insertImport.test.js b/packages/stylelint-plugin/test/insertImport.test.js index 07e97bcb6a..70889d1ec6 100644 --- a/packages/stylelint-plugin/test/insertImport.test.js +++ b/packages/stylelint-plugin/test/insertImport.test.js @@ -1,16 +1,17 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // eslint-disable-next-line import/no-extraneous-dependencies const { expect } = require("chai"); @@ -18,54 +19,61 @@ const postcss = require("postcss"); const { insertImport } = require("../lib/utils/insertImport"); -it("Inserts an import at the top of the file when no imports are present", () => { - const root = postcss.parse(`.some-class { width: 10px }`); - insertImport(root, { newline: "\n" }, "some_path"); - expect(root.toString()).to.be.eq(`@import "some_path"; +describe("insertImport", () => { + it("Inserts an import at the top of the file when no imports are present", () => { + const root = postcss.parse(`.some-class { width: 10px }`); + insertImport(root, { newline: "\n" }, "some_path"); + expect(root.toString()).to.be.eq(`@import "some_path"; .some-class { width: 10px }`); -}); + }); -it("Inserts an import below other imports", () => { - const root = postcss.parse(`@import "some_path1"; + it("Inserts an import below other imports", () => { + const root = postcss.parse(` +@import "some_path1"; .some-class { width: 10px }`); - insertImport(root, { newline: "\n" }, "some_path2"); - expect(root.toString()).to.be.eq(`@import "some_path1"; + insertImport(root, { newline: "\n" }, "some_path2"); + expect(root.toString()).to.be.eq(` +@import "some_path1"; @import "some_path2"; .some-class { width: 10px }`); -}); + }); -it("Inserts an import below the copyright header if no other imports exist", () => { - const root = postcss.parse(`/* copyright 2021 */ + it("Inserts an import below the copyright header if no other imports exist", () => { + const root = postcss.parse(` +/* copyright 2021 */ .some-class { width: 10px }`); - insertImport(root, { newline: "\n" }, "some_path"); - expect(root.toString()).to.be.eq(`/* copyright 2021 */ + insertImport(root, { newline: "\n" }, "some_path"); + expect(root.toString()).to.be.eq(` +/* copyright 2021 */ @import "some_path"; .some-class { width: 10px }`); -}); + }); -it("Inserts an import below other imports if the copyright header exists", () => { - const root = postcss.parse(`/* copyright 2021 */ + it("Inserts an import below other imports if the copyright header exists", () => { + const root = postcss.parse(` +/* copyright 2021 */ @import "some_path1"; @import "some_path2"; .some-class { width: 10px }`); - insertImport(root, { newline: "\n" }, "some_path3"); - expect(root.toString()).to.be.eq(`/* copyright 2021 */ + insertImport(root, { newline: "\n" }, "some_path3"); + expect(root.toString()).to.be.eq(` +/* copyright 2021 */ @import "some_path1"; @import "some_path2"; @import "some_path3"; .some-class { width: 10px }`); -}); + }); -it("Doesn't treat media queries as imports", () => { - const root = postcss.parse(` + it("Doesn't treat media queries as imports", () => { + const root = postcss.parse(` @media only screen and (max-width: 600px) { body { background-color: lightblue; @@ -73,9 +81,9 @@ it("Doesn't treat media queries as imports", () => { } .some-class { width: 10px } -`); - insertImport(root, { newline: "\n" }, "some_path"); - expect(root.toString()).to.be.eq(`@import "some_path"; + `); + insertImport(root, { newline: "\n" }, "some_path"); + expect(root.toString()).to.be.eq(`@import "some_path"; @media only screen and (max-width: 600px) { body { @@ -84,5 +92,6 @@ it("Doesn't treat media queries as imports", () => { } .some-class { width: 10px } -`); + `); + }); }); diff --git a/packages/stylelint-plugin/test/no-prefix-literal.test.js b/packages/stylelint-plugin/test/no-prefix-literal.test.js index 4cc4ca3888..7834b8ccbe 100644 --- a/packages/stylelint-plugin/test/no-prefix-literal.test.js +++ b/packages/stylelint-plugin/test/no-prefix-literal.test.js @@ -1,16 +1,17 @@ -/* Copyright 2020 Palantir Technologies, Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.*/ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // eslint-disable-next-line import/no-extraneous-dependencies const { expect } = require("chai"); @@ -23,130 +24,135 @@ const config = { }, }; -it("Warns when .bp3 is present", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-bp3.scss", - config, +describe("no-prefix-literal", () => { + it("Warns when .bp3 is present", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.scss", + config, + }); + expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(1); + expect(warnings[0].column).to.be.eq(2); }); - expect(result.errored).to.be.true; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(1); - expect(warnings[0].line).to.be.eq(1); - expect(warnings[0].column).to.be.eq(2); -}); -it("Warns when .bp3 is present (CSS modules)", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-bp3.module.scss", - config, + it("Warns when .bp3 is present (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.module.scss", + config, + }); + expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(1); + expect(warnings[0].column).to.be.eq(10); }); - expect(result.errored).to.be.true; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(1); - expect(warnings[0].line).to.be.eq(1); - expect(warnings[0].column).to.be.eq(10); -}); -it("Warns when nested .bp3 is present even when not first selector", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-nested-bp3.scss", - config, + it("Warns when nested .bp3 is present even when not first selector", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-nested-bp3.scss", + config, + }); + expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(2); + expect(warnings[0].column).to.be.eq(21); }); - expect(result.errored).to.be.true; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(1); - expect(warnings[0].line).to.be.eq(2); - expect(warnings[0].column).to.be.eq(21); -}); -it("Warns when nested .bp3 is present even when not first selector (CSS modules)", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-nested-bp3.module.scss", - config, + it("Warns when nested .bp3 is present even when not first selector (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-nested-bp3.module.scss", + config, + }); + expect(result.errored).to.be.true; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(1); + expect(warnings[0].line).to.be.eq(2); + expect(warnings[0].column).to.be.eq(29); }); - expect(result.errored).to.be.true; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(1); - expect(warnings[0].line).to.be.eq(2); - expect(warnings[0].column).to.be.eq(29); -}); -it("Doesn't warn bp3 string is present but not as a prefix", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-non-prefix-bp3.scss", - config, + it("Doesn't warn bp3 string is present but not as a prefix", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-non-prefix-bp3.scss", + config, + }); + expect(result.errored).to.be.false; }); - expect(result.errored).to.be.false; -}); -it("Doesn't warn bp3 string is present but not as a prefix (CSS modules)", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-non-prefix-bp3.module.scss", - config, + it("Doesn't warn bp3 string is present but not as a prefix (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-non-prefix-bp3.module.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); }); - expect(result.errored).to.be.false; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(0); -}); -it("Doesn't warn when .bp3 is not present", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/does-not-contain-bp3.scss", - config, + it("Doesn't warn when .bp3 is not present", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/does-not-contain-bp3.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); }); - expect(result.errored).to.be.false; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(0); -}); -it("Doesn't warn when .bp3 is not present (CSS modules)", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/does-not-contain-bp3.module.scss", - config, + it("Doesn't warn when .bp3 is not present (CSS modules)", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/does-not-contain-bp3.module.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); }); - expect(result.errored).to.be.false; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(0); -}); -it("Doesn't warn when .bp3 is present but lint rule is disabled", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-bp3-disabled.scss", - config, + it("Doesn't warn when .bp3 is present but lint rule is disabled", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3-disabled.scss", + config, + }); + expect(result.errored).to.be.false; + const warnings = result.results[0].warnings; + expect(warnings).lengthOf(0); }); - expect(result.errored).to.be.false; - const warnings = result.results[0].warnings; - expect(warnings).lengthOf(0); -}); -it("Accepts a valid secondary config", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-bp3.scss", - config: { - plugins: ["@blueprintjs/stylelint-plugin"], - rules: { - "@blueprintjs/no-prefix-literal": [ - true, - { disableFix: true, variablesImportPath: { sass: "some-path" } }, - ], + it("Accepts a valid secondary config", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.scss", + config: { + plugins: ["@blueprintjs/stylelint-plugin"], + rules: { + "@blueprintjs/no-prefix-literal": [ + true, + { disableFix: true, variablesImportPath: { sass: "some-path" } }, + ], + }, }, - }, + }); + expect(result.results[0].invalidOptionWarnings.length).to.be.eq(0); }); - expect(result.results[0].invalidOptionWarnings.length).to.be.eq(0); -}); -it("Rejects an invalid secondary config", async () => { - const result = await stylelint.lint({ - files: "test/fixtures/contains-bp3.scss", - config: { - plugins: ["@blueprintjs/stylelint-plugin"], - rules: { - "@blueprintjs/no-prefix-literal": [ - true, - { disableFix: "yes", variablesImportPath: { scss: "some-path", somethingElse: "some-other-path" } }, - ], + it("Rejects an invalid secondary config", async () => { + const result = await stylelint.lint({ + files: "test/fixtures/contains-bp3.scss", + config: { + plugins: ["@blueprintjs/stylelint-plugin"], + rules: { + "@blueprintjs/no-prefix-literal": [ + true, + { + disableFix: "yes", + variablesImportPath: { scss: "some-path", somethingElse: "some-other-path" }, + }, + ], + }, }, - }, + }); + expect(result.results[0].invalidOptionWarnings.length).to.be.eq(2); }); - expect(result.results[0].invalidOptionWarnings.length).to.be.eq(2); }); From 35ded27206854d9e35758147052325c315912d54 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Thu, 29 Apr 2021 08:39:11 +0000 Subject: [PATCH 10/13] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b5d8e8193a..b01b48b0e4 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ These packages define development dependencies and contain build configuration. - [![npm](https://img.shields.io/npm/v/@blueprintjs/eslint-plugin.svg?label=@blueprintjs/eslint-plugin)](https://www.npmjs.com/package/@blueprintjs/eslint-plugin) – implementations for custom ESLint rules which enforce best practices for Blueprint usage - [![npm](https://img.shields.io/npm/v/@blueprintjs/karma-build-scripts.svg?label=@blueprintjs/karma-build-scripts)](https://www.npmjs.com/package/@blueprintjs/karma-build-scripts) - [![npm](https://img.shields.io/npm/v/@blueprintjs/node-build-scripts.svg?label=@blueprintjs/node-build-scripts)](https://www.npmjs.com/package/@blueprintjs/node-build-scripts) – various utility scripts for linting, working with CSS variables, and building icons +- [![npm](https://img.shields.io/npm/v/@blueprintjs/stylelint-plugin.svg?label=@blueprintjs/stylelint-plugin)](https://www.npmjs.com/package/@blueprintjs/stylelint-plugin) – implementations for custom stylelint rules which enforce best practices for Blueprint usage - [![npm](https://img.shields.io/npm/v/@blueprintjs/test-commons.svg?label=@blueprintjs/test-commons)](https://www.npmjs.com/package/@blueprintjs/test-commons) – various utility functions used in Blueprint test suites - [![npm](https://img.shields.io/npm/v/@blueprintjs/tslint-config.svg?label=@blueprintjs/tslint-config)](https://www.npmjs.com/package/@blueprintjs/tslint-config) – TSLint configuration used in this repo and recommended for Blueprint-related projects (should be installed by `@blueprintjs/eslint-config`, not directly) - [![npm](https://img.shields.io/npm/v/@blueprintjs/webpack-build-scripts.svg?label=@blueprintjs/webpack-build-scripts)](https://www.npmjs.com/package/@blueprintjs/webpack-build-scripts) From b6b57b9b3352831251548f7d386a6059ca22a5e8 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Thu, 29 Apr 2021 08:49:23 +0000 Subject: [PATCH 11/13] Introduce bp-ns --- packages/core/src/common/_variables.scss | 2 ++ packages/stylelint-plugin/src/rules/no-prefix-literal.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/common/_variables.scss b/packages/core/src/common/_variables.scss index b5f2659f8e..f05ab72cda 100644 --- a/packages/core/src/common/_variables.scss +++ b/packages/core/src/common/_variables.scss @@ -7,6 +7,8 @@ // Namespace appended to the beginning of each CSS class: `.#{$ns}-button`. // Do not quote this value, for Less consumers. $ns: bp3 !default; +// Alias for BP users outside this repo +$bp-ns: $ns; // easily the most important variable, so it comes up top // (so other variables can use it to define themselves) diff --git a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts index 1aa66d6132..1dc98f45a5 100644 --- a/packages/stylelint-plugin/src/rules/no-prefix-literal.ts +++ b/packages/stylelint-plugin/src/rules/no-prefix-literal.ts @@ -130,8 +130,8 @@ const CssExtensionMap: Record, string> = { }; const BpPrefixVariableMap: Record, string> = { - [CssSyntax.SASS]: "#{$ns}", - [CssSyntax.LESS]: "@{ns}", + [CssSyntax.SASS]: "#{$bp-ns}", + [CssSyntax.LESS]: "@{bp-ns}", }; const BpVariableImportMap: Record, string> = { From 513b433c6984800cb8cf308d6565a0cab1474433 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Thu, 29 Apr 2021 09:41:44 +0000 Subject: [PATCH 12/13] Disable autofix --- .stylelintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stylelintrc b/.stylelintrc index e7b1e64342..7a05a8a04e 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -7,7 +7,7 @@ "@blueprintjs/stylelint-plugin" ], "rules": { - "@blueprintjs/no-prefix-literal": true, + "@blueprintjs/no-prefix-literal": [true, { "disableFix": true }], "declaration-empty-line-before": null, "indentation": [2, { "ignore": ["value"] From dede2e47002c5eb0f10ba7aef61f431b8f2a6da5 Mon Sep 17 00:00:00 2001 From: Patrick Szmucer Date: Thu, 29 Apr 2021 15:43:53 +0000 Subject: [PATCH 13/13] ns -> bp-ns --- packages/stylelint-plugin/README.md | 6 +++--- .../test/fixtures/does-not-contain-bp3.module.scss | 2 +- .../test/fixtures/does-not-contain-bp3.scss | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/stylelint-plugin/README.md b/packages/stylelint-plugin/README.md index b5fb75b90d..c092601353 100644 --- a/packages/stylelint-plugin/README.md +++ b/packages/stylelint-plugin/README.md @@ -37,9 +37,9 @@ Simply add this plugin in your `.stylelintrc` file and then pick the rules that ### `@blueprintjs/no-prefix-literal` (autofixable) -Enforce usage of the `ns` constant over namespaced string literals. +Enforce usage of the `bp-ns` constant over namespaced string literals. -The `@blueprintjs` package exports a `ns` CSS variable which contains the prefix for the current version of Blueprint (`bp3` for Blueprint 3, `bp4` for Blueprint 4, and etc). Using the variable instead of hardcoding the prefix means that your code will still work when new major version of Blueprint is released. +The `@blueprintjs` package exports a `bp-ns` CSS variable which contains the prefix for the current version of Blueprint (`bp3` for Blueprint 3, `bp4` for Blueprint 4, and etc). Using the variable instead of hardcoding the prefix means that your code will still work when new major version of Blueprint is released. ```json { @@ -55,7 +55,7 @@ The `@blueprintjs` package exports a `ns` CSS variable which contains the prefix -} + @import "~@blueprntjs/core/lib/scss/variables"; + -+.#{$ns}-button > div { ++.#{$bp-ns}-button > div { + border: 1px solid black; +} ``` diff --git a/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss index f267c24bef..1c0ae59805 100644 --- a/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss +++ b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.module.scss @@ -1,5 +1,5 @@ .some-class { - :global(.#{$ns})-button { + :global(.#{$bp-ns})-button { width: 10px; } } diff --git a/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss index 6fcf6635f4..2d929252a0 100644 --- a/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss +++ b/packages/stylelint-plugin/test/fixtures/does-not-contain-bp3.scss @@ -1,5 +1,5 @@ .some-class { - .#{$ns}-button { + .#{$bp-ns}-button { width: 10px; } }