diff --git a/CHANGELOG.md b/CHANGELOG.md index e07e624b..f3722fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 5.2.0 + +- Added: `at-use-no-redundant-alias` rule to disallow redundant namespace aliases (#445). + +**Full Changelog**: https://github.com/stylelint-scss/stylelint-scss/compare/v5.1.0...v5.2.0 + # 5.1.0 - Added: `function-disallowed-list` rule support to ban specific built-in functions (#422, #844). diff --git a/README.md b/README.md index dee7946a..37e348a2 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,7 @@ Create the `.stylelintrc.json` config file (or open the existing one), add `styl ```jsonc { - "plugins": [ - "stylelint-scss" - ], + "plugins": ["stylelint-scss"], "rules": { // recommended rules "at-rule-no-unknown": null, @@ -102,6 +100,7 @@ Please also see the [example configs](./docs/examples/) for special cases. ### `@`-use - [`at-use-no-unnamespaced`](./src/rules/at-use-no-unnamespaced/README.md): Disallow `@use` without a namespace (i.e. `@use "..." as *`). +- [`at-use-no-redundant-alias`](./src/rules/at-use-no-redundant-alias/README.md): Disallow redundant namespace aliases (i.e. `@use "foo" as foo`). ### `$`-variable diff --git a/src/rules/at-use-no-redundant-alias/README.md b/src/rules/at-use-no-redundant-alias/README.md new file mode 100644 index 00000000..9ecc447c --- /dev/null +++ b/src/rules/at-use-no-redundant-alias/README.md @@ -0,0 +1,38 @@ +# at-use-no-redundant-alias + +By default, the module's namespace is just the last component of the module’s URL. +This rule is to disallow redundant namespace aliases. + +```scss +@use "foo" as foo; +/** ↑ + * Disallow this */ +``` + +The following patterns are considered warnings: + +```scss +@use "foo" as foo; +``` + +```scss +@use "sass:math" as math; +``` + +```scss +@use "src/corners" as corners; +``` + +The following patterns are _not_ considered warnings: + +```scss +@use "foo" as bar; +``` + +```scss +@use "sass:math"; +``` + +```scss +@use "src/corners" as c; +``` diff --git a/src/rules/at-use-no-redundant-alias/__tests__/index.js b/src/rules/at-use-no-redundant-alias/__tests__/index.js new file mode 100644 index 00000000..601670ac --- /dev/null +++ b/src/rules/at-use-no-redundant-alias/__tests__/index.js @@ -0,0 +1,95 @@ +"use strict"; + +const { messages, ruleName } = require(".."); + +testRule({ + ruleName, + config: [true], + customSyntax: "postcss-scss", + + accept: [ + { + code: ` + @use "foo"; + `, + description: "Default namespace" + }, + { + code: ` + @use "foo/bar" as foo; + `, + description: "Different namespace" + }, + { + code: ` + @use "sass:math" as *; + `, + description: "Without namespace" + }, + { + code: ` + @use "sass:math"; + `, + description: "No explicit namespace" + } + ], + + reject: [ + { + code: ` + @use "foo" as foo; + `, + line: 2, + column: 7, + message: messages.rejected, + description: "Without url" + }, + { + code: ` + @use "src/corners" as corners; + `, + line: 2, + column: 7, + message: messages.rejected, + description: "With url" + }, + { + code: ` + @use "sass:math" as math; + `, + line: 2, + column: 7, + message: messages.rejected, + description: "Built-in module" + }, + { + code: ` + @use "foo" as foo; + `, + line: 2, + column: 7, + message: messages.rejected, + description: "Without space" + }, + { + code: ` + @use "foo" as foo with ($baz: 1px); + `, + line: 2, + column: 7, + message: messages.rejected, + description: "Configured" + }, + { + code: ` + @use "sass:color" as color with ( + $baz: 1px + ); + `, + line: 2, + column: 7, + message: messages.rejected, + description: "Configured multiline" + } + ] +}); diff --git a/src/rules/at-use-no-redundant-alias/index.js b/src/rules/at-use-no-redundant-alias/index.js new file mode 100644 index 00000000..967b74b7 --- /dev/null +++ b/src/rules/at-use-no-redundant-alias/index.js @@ -0,0 +1,53 @@ +"use strict"; + +const { utils } = require("stylelint"); +const namespace = require("../../utils/namespace"); +const ruleUrl = require("../../utils/ruleUrl"); + +const ruleName = namespace("at-use-no-redundant-alias"); + +const messages = utils.ruleMessages(ruleName, { + rejected: "Unnecessary explicit namespace" +}); + +const meta = { + url: ruleUrl(ruleName) +}; + +function getDefaultNamespace(module) { + return module.match(/([^/:]+)$/)[1].replace(/\.[^.]|["]+$/, ""); +} + +function separateEachParams(paramString) { + const parts = paramString.replace(/"/g, "").split(/\s+as\s+|\s+with\s+/); + if (parts.length < 2) return; + return parts; +} + +function rule(actual) { + return (root, result) => { + const validOptions = utils.validateOptions(result, ruleName, { actual }); + + if (!validOptions) { + return; + } + + root.walkAtRules("use", decl => { + const parts = separateEachParams(decl.params); + if (parts && getDefaultNamespace(parts[0]) === parts[1]) { + utils.report({ + message: messages.rejected, + node: decl, + result, + ruleName + }); + } + }); + }; +} + +rule.ruleName = ruleName; +rule.messages = messages; +rule.meta = meta; + +module.exports = rule; diff --git a/src/rules/index.js b/src/rules/index.js index fcdbe995..8355a332 100644 --- a/src/rules/index.js +++ b/src/rules/index.js @@ -23,6 +23,7 @@ const rules = { "at-mixin-pattern": require("./at-mixin-pattern"), "at-rule-conditional-no-parentheses": require("./at-rule-conditional-no-parentheses"), "at-rule-no-unknown": require("./at-rule-no-unknown"), + "at-use-no-redundant-alias": require("./at-use-no-redundant-alias"), "at-use-no-unnamespaced": require("./at-use-no-unnamespaced"), "comment-no-empty": require("./comment-no-empty"), "comment-no-loud": require("./comment-no-loud"),