From 6ff0720ffe8279e3fb712a7ad0a9aafbc7aa4707 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 14 Jan 2022 17:27:38 +0900 Subject: [PATCH] Add shorthand-directive rule --- README.md | 1 + docs/rules.md | 1 + docs/rules/shorthand-attribute.md | 6 + docs/rules/shorthand-directive.md | 73 ++++++++++ src/rules/shorthand-directive.ts | 126 ++++++++++++++++++ src/utils/ast-utils.ts | 14 +- src/utils/rules.ts | 2 + .../invalid/always/_config.json | 3 + .../invalid/always/test01-errors.json | 27 ++++ .../invalid/always/test01-input.svelte | 23 ++++ .../invalid/always/test01-output.svelte | 23 ++++ .../invalid/never/_config.json | 3 + .../invalid/never/test01-errors.json | 17 +++ .../invalid/never/test01-input.svelte | 18 +++ .../invalid/never/test01-output.svelte | 18 +++ .../invalid/test01-errors.json | 17 +++ .../invalid/test01-input.svelte | 18 +++ .../invalid/test01-output.svelte | 18 +++ .../valid/always/_config.json | 3 + .../valid/always/test01-input.svelte | 10 ++ .../valid/never/_config.json | 3 + .../valid/never/test01-input.svelte | 13 ++ .../valid/test01-input.svelte | 17 +++ tests/src/rules/shorthand-directive.ts | 16 +++ 24 files changed, 466 insertions(+), 4 deletions(-) create mode 100644 docs/rules/shorthand-directive.md create mode 100644 src/rules/shorthand-directive.ts create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/always/_config.json create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/never/_config.json create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/valid/always/_config.json create mode 100644 tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/valid/never/_config.json create mode 100644 tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte create mode 100644 tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte create mode 100644 tests/src/rules/shorthand-directive.ts diff --git a/README.md b/README.md index c3a2bb31d..7c624e732 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | [@ota-meshi/svelte/prefer-class-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: | | [@ota-meshi/svelte/prefer-style-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: | | [@ota-meshi/svelte/shorthand-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: | +| [@ota-meshi/svelte/shorthand-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-directive/) | enforce use of shorthand syntax in directives | :wrench: | | [@ota-meshi/svelte/spaced-html-comment](https://ota-meshi.github.io/eslint-plugin-svelte/rules/spaced-html-comment/) | enforce consistent spacing after the `` in a HTML comment | :wrench: | ## Extension Rules diff --git a/docs/rules.md b/docs/rules.md index e6ba92f76..0c39ccc66 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -55,6 +55,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | [@ota-meshi/svelte/prefer-class-directive](./rules/prefer-class-directive.md) | require class directives instead of ternary expressions | :wrench: | | [@ota-meshi/svelte/prefer-style-directive](./rules/prefer-style-directive.md) | require style directives instead of style attribute | :wrench: | | [@ota-meshi/svelte/shorthand-attribute](./rules/shorthand-attribute.md) | enforce use of shorthand syntax in attribute | :wrench: | +| [@ota-meshi/svelte/shorthand-directive](./rules/shorthand-directive.md) | enforce use of shorthand syntax in directives | :wrench: | | [@ota-meshi/svelte/spaced-html-comment](./rules/spaced-html-comment.md) | enforce consistent spacing after the `` in a HTML comment | :wrench: | ## Extension Rules diff --git a/docs/rules/shorthand-attribute.md b/docs/rules/shorthand-attribute.md index 4d6606a68..93573afee 100644 --- a/docs/rules/shorthand-attribute.md +++ b/docs/rules/shorthand-attribute.md @@ -54,6 +54,12 @@ This rule enforces the use of the shorthand syntax in attribute. - `"always"` ... Expects that the shorthand will be used whenever possible. This is default. - `"never"` ... Ensures that no shorthand is used in any attribute. +## :couple: Related Rules + +- [@ota-meshi/svelte/shorthand-directive] + +[@ota-meshi/svelte/shorthand-directive]: ./shorthand-directive.md + ## :rocket: Version This rule was introduced in @ota-meshi/eslint-plugin-svelte v0.5.0 diff --git a/docs/rules/shorthand-directive.md b/docs/rules/shorthand-directive.md new file mode 100644 index 000000000..a7b723d38 --- /dev/null +++ b/docs/rules/shorthand-directive.md @@ -0,0 +1,73 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "@ota-meshi/svelte/shorthand-directive" +description: "enforce use of shorthand syntax in directives" +--- + +# @ota-meshi/svelte/shorthand-directive + +> enforce use of shorthand syntax in directives + +- :exclamation: **_This rule has not been released yet._** +- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. + +## :book: Rule Details + +This rule enforces the use of the shorthand syntax in directives. + + + + + + +```svelte + + + + +
...
+
...
+ + + +
...
+
...
+``` + + + +
+ +## :wrench: Options + +```json +{ + "@ota-meshi/svelte/shorthand-directive": [ + "error", + { + "prefer": "always" // "never" + } + ] +} +``` + +- `prefer` + - `"always"` ... Expects that the shorthand will be used whenever possible. This is default. + - `"never"` ... Ensures that no shorthand is used in any directive. + +## :couple: Related Rules + +- [@ota-meshi/svelte/shorthand-attribute] + +[@ota-meshi/svelte/shorthand-attribute]: ./shorthand-directive.md + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/shorthand-directive.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/shorthand-directive.ts) diff --git a/src/rules/shorthand-directive.ts b/src/rules/shorthand-directive.ts new file mode 100644 index 000000000..d9691f29e --- /dev/null +++ b/src/rules/shorthand-directive.ts @@ -0,0 +1,126 @@ +import type { AST } from "svelte-eslint-parser" +import { createRule } from "../utils" +import { getAttributeValueQuoteAndRange } from "../utils/ast-utils" + +export default createRule("shorthand-directive", { + meta: { + docs: { + description: "enforce use of shorthand syntax in directives", + category: "Stylistic Issues", + recommended: false, + }, + fixable: "code", + schema: [ + { + type: "object", + properties: { + prefer: { enum: ["always", "never"] }, + }, + additionalProperties: false, + }, + ], + messages: { + expectedShorthand: "Expected shorthand directive.", + expectedRegular: "Expected regular directive syntax.", + }, + type: "layout", + }, + create(context) { + const sourceCode = context.getSourceCode() + const always: boolean = context.options[0]?.prefer !== "never" + + /** Report for always */ + function reportForAlways( + node: AST.SvelteDirective | AST.SvelteStyleDirective, + ) { + context.report({ + node, + messageId: "expectedShorthand", + *fix(fixer) { + const quoteAndRange = getAttributeValueQuoteAndRange(node, sourceCode) + if (quoteAndRange) { + yield fixer.remove( + sourceCode.getTokenBefore(quoteAndRange.firstToken)!, + ) + yield fixer.removeRange(quoteAndRange.range) + } + }, + }) + } + + /** Report for never */ + function reportForNever( + node: AST.SvelteDirective | AST.SvelteStyleDirective, + ) { + context.report({ + node, + messageId: "expectedRegular", + *fix(fixer) { + yield fixer.insertTextAfter(node.key.name, `={${node.key.name.name}}`) + }, + }) + } + + return { + SvelteDirective(node) { + if (node.kind !== "Binding" && node.kind !== "Class") { + return + } + + const expression = node.expression + if (!expression) { + // Invalid ? + return + } + if ( + expression.type !== "Identifier" || + node.key.name.name !== expression.name + ) { + // Cannot use shorthand + return + } + if (always) { + if (node.key.name.range![0] === expression.range![0]) { + // Use shorthand + return + } + reportForAlways(node) + } else { + if (node.key.name.range![1] < expression.range![0]) { + // Use longform + return + } + reportForNever(node) + } + }, + SvelteStyleDirective(node) { + if (always) { + if (node.shorthand) { + // Use shorthand + return + } + if (node.value.length !== 1) { + // Cannot use shorthand + return + } + const expression = node.value[0] + if ( + expression.type !== "SvelteMustacheTag" || + expression.expression.type !== "Identifier" || + expression.expression.name !== node.key.name.name + ) { + // Cannot use shorthand + return + } + reportForAlways(node) + } else { + if (!node.shorthand) { + // Use longform + return + } + reportForNever(node) + } + }, + } + }, +}) diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 0e3cadce6..89ec22508 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -233,6 +233,8 @@ export function getScope( export type QuoteAndRange = { quote: "unquoted" | "double" | "single" range: [number, number] + firstToken: SvAST.Token | SvAST.Comment + lastToken: SvAST.Token | SvAST.Comment } /** Get the quote and range from given attribute values */ export function getAttributeValueQuoteAndRange( @@ -262,6 +264,8 @@ export function getAttributeValueQuoteAndRange( return { quote: "unquoted", range: [valueFirstToken.range[0], valueLastToken.range[1]], + firstToken: valueFirstToken, + lastToken: valueLastToken, } } else if ( beforeTokens.length > 1 || @@ -280,6 +284,8 @@ export function getAttributeValueQuoteAndRange( return { quote: beforeToken.value === '"' ? "double" : "single", range: [beforeToken.range[0], afterToken.range[1]], + firstToken: beforeToken, + lastToken: afterToken, } } export function getMustacheTokens( @@ -381,11 +387,11 @@ function getAttributeValueRangeTokens( if (!attr.value.length) { return null } - const firstToken = attr.value[0] - const lastToken = attr.value[attr.value.length - 1] + const first = attr.value[0] + const last = attr.value[attr.value.length - 1] return { - firstToken, - lastToken, + firstToken: sourceCode.getFirstToken(first), + lastToken: sourceCode.getLastToken(last), } } const tokens = getMustacheTokens(attr, sourceCode) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index d0b924678..b6cd1b252 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -20,6 +20,7 @@ import noUselessMustaches from "../rules/no-useless-mustaches" import preferClassDirective from "../rules/prefer-class-directive" import preferStyleDirective from "../rules/prefer-style-directive" import shorthandAttribute from "../rules/shorthand-attribute" +import shorthandDirective from "../rules/shorthand-directive" import spacedHtmlComment from "../rules/spaced-html-comment" import system from "../rules/system" import validCompile from "../rules/valid-compile" @@ -46,6 +47,7 @@ export const rules = [ preferClassDirective, preferStyleDirective, shorthandAttribute, + shorthandDirective, spacedHtmlComment, system, validCompile, diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/_config.json b/tests/fixtures/rules/shorthand-directive/invalid/always/_config.json new file mode 100644 index 000000000..78d5d8817 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/always/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "prefer": "always" }] +} diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json new file mode 100644 index 000000000..2547511ae --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json @@ -0,0 +1,27 @@ +[ + { + "message": "Expected shorthand directive.", + "line": 14, + "column": 8 + }, + { + "message": "Expected shorthand directive.", + "line": 16, + "column": 6 + }, + { + "message": "Expected shorthand directive.", + "line": 18, + "column": 6 + }, + { + "message": "Expected shorthand directive.", + "line": 21, + "column": 6 + }, + { + "message": "Expected shorthand directive.", + "line": 23, + "column": 6 + } +] diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte new file mode 100644 index 000000000..0709f9ae5 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte @@ -0,0 +1,23 @@ + + + + +
...
+
...
+ + + + + +
...
+ +
...
+ + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte new file mode 100644 index 000000000..deb7c88b3 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte @@ -0,0 +1,23 @@ + + + + +
...
+
...
+ + + + + +
...
+ +
...
+ + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/_config.json b/tests/fixtures/rules/shorthand-directive/invalid/never/_config.json new file mode 100644 index 000000000..63ecce4db --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/never/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "prefer": "never" }] +} diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json new file mode 100644 index 000000000..063812205 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json @@ -0,0 +1,17 @@ +[ + { + "message": "Expected regular directive syntax.", + "line": 8, + "column": 8 + }, + { + "message": "Expected regular directive syntax.", + "line": 9, + "column": 6 + }, + { + "message": "Expected regular directive syntax.", + "line": 10, + "column": 6 + } +] diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte new file mode 100644 index 000000000..3acf6f137 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte @@ -0,0 +1,18 @@ + + + + +
...
+
...
+ + + + + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte new file mode 100644 index 000000000..73028511b --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte @@ -0,0 +1,18 @@ + + + + +
...
+
...
+ + + + + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json b/tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json new file mode 100644 index 000000000..39baee195 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json @@ -0,0 +1,17 @@ +[ + { + "message": "Expected shorthand directive.", + "line": 14, + "column": 8 + }, + { + "message": "Expected shorthand directive.", + "line": 16, + "column": 6 + }, + { + "message": "Expected shorthand directive.", + "line": 18, + "column": 6 + } +] diff --git a/tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte new file mode 100644 index 000000000..3acf6f137 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte @@ -0,0 +1,18 @@ + + + + +
...
+
...
+ + + + + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte b/tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte new file mode 100644 index 000000000..e276674f9 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte @@ -0,0 +1,18 @@ + + + + +
...
+
...
+ + + + + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/valid/always/_config.json b/tests/fixtures/rules/shorthand-directive/valid/always/_config.json new file mode 100644 index 000000000..78d5d8817 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/valid/always/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "prefer": "always" }] +} diff --git a/tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte new file mode 100644 index 000000000..4a0422106 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte @@ -0,0 +1,10 @@ + + + + +
...
+
...
diff --git a/tests/fixtures/rules/shorthand-directive/valid/never/_config.json b/tests/fixtures/rules/shorthand-directive/valid/never/_config.json new file mode 100644 index 000000000..63ecce4db --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/valid/never/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "prefer": "never" }] +} diff --git a/tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte new file mode 100644 index 000000000..1ff5be5f3 --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte @@ -0,0 +1,13 @@ + + + + + + +
...
+ +
...
diff --git a/tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte new file mode 100644 index 000000000..b16c65a6f --- /dev/null +++ b/tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte @@ -0,0 +1,17 @@ + + + + +
...
+
...
+ + +
...
+
...
+
...
+
...
diff --git a/tests/src/rules/shorthand-directive.ts b/tests/src/rules/shorthand-directive.ts new file mode 100644 index 000000000..8ba5ff082 --- /dev/null +++ b/tests/src/rules/shorthand-directive.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/shorthand-directive" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "shorthand-directive", + rule as any, + loadTestCases("shorthand-directive"), +)