From 5289c6ae01e11b7f9400bf4b1d49a3fda5730bba Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 13 Jan 2022 15:33:58 +0900 Subject: [PATCH] Add prefer-style-directive rule --- .eslintignore | 1 + README.md | 1 + .../lib/components/ESLintPlayground.svelte | 3 + docs/rules.md | 1 + docs/rules/prefer-class-directive.md | 6 ++ docs/rules/prefer-style-directive.md | 61 ++++++++++++ package.json | 1 + src/rules/prefer-style-directive.ts | 98 +++++++++++++++++++ src/utils/rules.ts | 2 + .../invalid/complex-test01-errors.json | 7 ++ .../invalid/complex-test01-input.svelte | 1 + .../invalid/complex-test01-output.svelte | 1 + .../invalid/test01-errors.json | 7 ++ .../invalid/test01-input.svelte | 10 ++ .../invalid/test01-output.svelte | 10 ++ .../invalid/test02-errors.json | 12 +++ .../invalid/test02-input.svelte | 1 + .../invalid/test02-output.svelte | 1 + .../invalid/test03-errors.json | 7 ++ .../invalid/test03-input.svelte | 1 + .../invalid/test03-output.svelte | 1 + .../valid/test01-input.svelte | 6 ++ tests/src/rules/prefer-style-directive.ts | 16 +++ 23 files changed, 255 insertions(+) create mode 100644 docs/rules/prefer-style-directive.md create mode 100644 src/rules/prefer-style-directive.ts create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-errors.json create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-input.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-output.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test01-errors.json create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test01-input.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test01-output.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test02-errors.json create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test02-input.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test02-output.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test03-errors.json create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test03-input.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/invalid/test03-output.svelte create mode 100644 tests/fixtures/rules/prefer-style-directive/valid/test01-input.svelte create mode 100644 tests/src/rules/prefer-style-directive.ts diff --git a/.eslintignore b/.eslintignore index 797ba184c..6a24eef33 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,6 +11,7 @@ /tests/fixtures/rules/indent/invalid/ts-v5 /tests/fixtures/rules/valid-compile/invalid/ts /tests/fixtures/rules/valid-compile/valid/ts +/tests/fixtures/rules/prefer-style-directive /.svelte-kit /svelte.config-dist.js /build diff --git a/README.md b/README.md index cf26da3b9..c3a2bb31d 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | [@ota-meshi/svelte/max-attributes-per-line](https://ota-meshi.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/) | enforce the maximum number of attributes per line | :wrench: | | [@ota-meshi/svelte/mustache-spacing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/mustache-spacing/) | enforce unified spacing in mustache | :wrench: | | [@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/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: | diff --git a/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte b/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte index 0873f2f16..bca252604 100644 --- a/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte +++ b/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte @@ -24,6 +24,7 @@ lastname: 'Lovelace' }; let current = 'foo'; + let color = 'red'; <` + `/script> @@ -60,6 +61,8 @@ class={current === 'foo' ? 'selected' : ''} on:click="{() => current = 'foo'}" >foo + +
...
` const state = deserializeState( diff --git a/docs/rules.md b/docs/rules.md index 37d621d73..e6ba92f76 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -53,6 +53,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | [@ota-meshi/svelte/max-attributes-per-line](./rules/max-attributes-per-line.md) | enforce the maximum number of attributes per line | :wrench: | | [@ota-meshi/svelte/mustache-spacing](./rules/mustache-spacing.md) | enforce unified spacing in mustache | :wrench: | | [@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/spaced-html-comment](./rules/spaced-html-comment.md) | enforce consistent spacing after the `` in a HTML comment | :wrench: | diff --git a/docs/rules/prefer-class-directive.md b/docs/rules/prefer-class-directive.md index 7cd683172..b6a542b7c 100644 --- a/docs/rules/prefer-class-directive.md +++ b/docs/rules/prefer-class-directive.md @@ -42,6 +42,12 @@ You cannot enforce this style by using [prettier-plugin-svelte]. That is, this r Nothing. +## :couple: Related Rules + +- [@ota-meshi/svelte/prefer-style-directive] + +[@ota-meshi/svelte/prefer-style-directive]: ./prefer-style-directive.md + ## :books: Further Reading - [Svelte - Tutorial > 13. Classes / The class directive](https://svelte.dev/tutorial/classes) diff --git a/docs/rules/prefer-style-directive.md b/docs/rules/prefer-style-directive.md new file mode 100644 index 000000000..103790369 --- /dev/null +++ b/docs/rules/prefer-style-directive.md @@ -0,0 +1,61 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "@ota-meshi/svelte/prefer-style-directive" +description: "require style directives instead of style attribute" +--- + +# @ota-meshi/svelte/prefer-style-directive + +> require style directives instead of style attribute + +- :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 aims to replace a style attribute with the style directive. + +Style directive were added in Svelte v3.46. + + + + + +```svelte + + + +
...
+ + +
...
+``` + +
+ +You cannot enforce this style by using [prettier-plugin-svelte]. That is, this rule does not conflict with [prettier-plugin-svelte] and can be used with [prettier-plugin-svelte]. + +[prettier-plugin-svelte]: https://github.com/sveltejs/prettier-plugin-svelte + +## :wrench: Options + +Nothing. + +## :couple: Related Rules + +- [@ota-meshi/svelte/prefer-class-directive] + +[@ota-meshi/svelte/prefer-class-directive]: ./prefer-class-directive.md + +## :books: Further Reading + +- [Svelte - Docs > style:property](https://svelte.dev/docs#template-syntax-element-directives-style-property) + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/prefer-style-directive.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/prefer-style-directive.ts) diff --git a/package.json b/package.json index 6787d7af4..6803e54bb 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "dependencies": { "debug": "^4.3.1", "eslint-utils": "^3.0.0", + "postcss": "^8.4.5", "sourcemap-codec": "^1.4.8", "svelte-eslint-parser": "^0.11.0" }, diff --git a/src/rules/prefer-style-directive.ts b/src/rules/prefer-style-directive.ts new file mode 100644 index 000000000..2958da4df --- /dev/null +++ b/src/rules/prefer-style-directive.ts @@ -0,0 +1,98 @@ +import type { AST } from "svelte-eslint-parser" +import { parse as parseCss } from "postcss" +import { createRule } from "../utils" + +export default createRule("prefer-style-directive", { + meta: { + docs: { + description: "require style directives instead of style attribute", + category: "Stylistic Issues", + recommended: false, + }, + fixable: "code", + schema: [], + messages: { + unexpected: "Can use style directives instead.", + }, + type: "suggestion", + }, + create(context) { + const sourceCode = context.getSourceCode() + return { + "SvelteStartTag > SvelteAttribute"( + node: AST.SvelteAttribute & { + parent: AST.SvelteStartTag + }, + ) { + if (node.key.name !== "style") { + return + } + const mustacheTags = node.value.filter( + (v) => v.type === "SvelteMustacheTag", + ) + const valueStartIndex = node.value[0].range[0] + const cssCode = node.value + .map((value) => { + if (value.type === "SvelteMustacheTag") { + return "_".repeat(value.range[1] - value.range[0]) + } + return sourceCode.getText(value) + }) + .join("") + const root = parseCss(cssCode) + root.walkDecls((decl) => { + if ( + node.parent.attributes.some( + (attr) => + attr.type === "SvelteStyleDirective" && + attr.key.name.name === decl.prop, + ) + ) { + // has style directive + return + } + + const declRange: AST.Range = [ + valueStartIndex + decl.source!.start!.offset, + valueStartIndex + decl.source!.end!.offset + 1, + ] + if ( + mustacheTags.some( + (tag) => + (tag.range[0] < declRange[0] && declRange[0] < tag.range[1]) || + (tag.range[0] < declRange[1] && declRange[1] < tag.range[1]), + ) + ) { + // intersection + return + } + const declValueStartIndex = + declRange[0] + decl.prop.length + (decl.raws.between || "").length + const declValueRange: AST.Range = [ + declValueStartIndex, + declValueStartIndex + (decl.raws.value?.value || decl.value).length, + ] + + context.report({ + node, + messageId: "unexpected", + *fix(fixer) { + const styleDirective = `style:${ + decl.prop + }="${sourceCode.text.slice(...declValueRange)}"` + if (root.nodes.length === 1 && root.nodes[0] === decl) { + yield fixer.replaceTextRange(node.range, styleDirective) + } else { + yield fixer.removeRange(declRange) + yield fixer.insertTextAfterRange( + node.range, + ` ${styleDirective}`, + ) + } + }, + }) + }) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 68326bfde..d0b924678 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -18,6 +18,7 @@ import noTargetBlank from "../rules/no-target-blank" import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore" 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 spacedHtmlComment from "../rules/spaced-html-comment" import system from "../rules/system" @@ -43,6 +44,7 @@ export const rules = [ noUnusedSvelteIgnore, noUselessMustaches, preferClassDirective, + preferStyleDirective, shorthandAttribute, spacedHtmlComment, system, diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-errors.json b/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-errors.json new file mode 100644 index 000000000..d07093c24 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Can use style directives instead.", + "line": 1, + "column": 6 + } +] diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-input.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-input.svelte new file mode 100644 index 000000000..886a594e0 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-input.svelte @@ -0,0 +1 @@ +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-output.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-output.svelte new file mode 100644 index 000000000..141e90f01 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/complex-test01-output.svelte @@ -0,0 +1 @@ +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test01-errors.json b/tests/fixtures/rules/prefer-style-directive/invalid/test01-errors.json new file mode 100644 index 000000000..433fc031c --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test01-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Can use style directives instead.", + "line": 10, + "column": 6 + } +] diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test01-input.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/test01-input.svelte new file mode 100644 index 000000000..11e76640a --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test01-input.svelte @@ -0,0 +1,10 @@ + + + + +
...
+ + +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test01-output.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/test01-output.svelte new file mode 100644 index 000000000..1c822cf8e --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test01-output.svelte @@ -0,0 +1,10 @@ + + + + +
...
+ + +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test02-errors.json b/tests/fixtures/rules/prefer-style-directive/invalid/test02-errors.json new file mode 100644 index 000000000..ec785109e --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test02-errors.json @@ -0,0 +1,12 @@ +[ + { + "message": "Can use style directives instead.", + "line": 1, + "column": 6 + }, + { + "message": "Can use style directives instead.", + "line": 1, + "column": 6 + } +] diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test02-input.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/test02-input.svelte new file mode 100644 index 000000000..93edb7300 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test02-input.svelte @@ -0,0 +1 @@ +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test02-output.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/test02-output.svelte new file mode 100644 index 000000000..a9984482f --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test02-output.svelte @@ -0,0 +1 @@ +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test03-errors.json b/tests/fixtures/rules/prefer-style-directive/invalid/test03-errors.json new file mode 100644 index 000000000..d07093c24 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test03-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Can use style directives instead.", + "line": 1, + "column": 6 + } +] diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test03-input.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/test03-input.svelte new file mode 100644 index 000000000..99b2d8388 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test03-input.svelte @@ -0,0 +1 @@ +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/invalid/test03-output.svelte b/tests/fixtures/rules/prefer-style-directive/invalid/test03-output.svelte new file mode 100644 index 000000000..c1dedaa09 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/invalid/test03-output.svelte @@ -0,0 +1 @@ +
...
diff --git a/tests/fixtures/rules/prefer-style-directive/valid/test01-input.svelte b/tests/fixtures/rules/prefer-style-directive/valid/test01-input.svelte new file mode 100644 index 000000000..70b1536e1 --- /dev/null +++ b/tests/fixtures/rules/prefer-style-directive/valid/test01-input.svelte @@ -0,0 +1,6 @@ + + + +
...
diff --git a/tests/src/rules/prefer-style-directive.ts b/tests/src/rules/prefer-style-directive.ts new file mode 100644 index 000000000..cd8c4e364 --- /dev/null +++ b/tests/src/rules/prefer-style-directive.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/prefer-style-directive" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "prefer-style-directive", + rule as any, + loadTestCases("prefer-style-directive"), +)