From 39a124353c2259fabf7ea72f7babeb2bb01a7cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Vosp=C4=9Bl?= Date: Thu, 21 Jul 2022 11:25:38 +0200 Subject: [PATCH 1/4] feat: add `svelte/html-self-closing` Moved to separate PR from ota-meshi/eslint-plugin-svelte#186 --- README.md | 1 + docs/rules.md | 1 + docs/rules/html-self-closing.md | 78 ++++++++++ src/configs/prettier.ts | 1 + src/rules/html-self-closing.ts | 146 ++++++++++++++++++ src/utils/rules.ts | 2 + src/utils/template-utils.ts | 24 +++ src/utils/void-elements.ts | 20 +++ .../invalid/component-never/_config.json | 9 ++ .../component-never-errors.json | 7 + .../component-never-input.svelte | 4 + .../component-never-output.svelte | 4 + .../invalid/normal-ignore/_config.json | 9 ++ .../normal-ignore/normal-any-errors.json | 7 + .../normal-ignore/normal-any-input.svelte | 6 + .../normal-ignore/normal-any-output.svelte | 6 + .../invalid/normal-never/_config.json | 9 ++ .../normal-never/component-never-errors.json | 7 + .../normal-never/component-never-input.svelte | 4 + .../component-never-output.svelte | 4 + .../invalid/test01-errors.json | 17 ++ .../invalid/test01-input.svelte | 6 + .../invalid/test01-output.svelte | 6 + .../invalid/void-never/_config.json | 9 ++ .../invalid/void-never/void-never-errors.json | 7 + .../void-never/void-never-input.svelte | 4 + .../void-never/void-never-output.svelte | 4 + .../valid/test01-input.svelte | 5 + tests/src/rules/html-self-closing.ts | 12 ++ 29 files changed, 419 insertions(+) create mode 100644 docs/rules/html-self-closing.md create mode 100644 src/rules/html-self-closing.ts create mode 100644 src/utils/template-utils.ts create mode 100644 src/utils/void-elements.ts create mode 100644 tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-errors.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-input.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-output.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/test01-errors.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/test01-input.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/test01-output.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-errors.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-input.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-output.svelte create mode 100644 tests/fixtures/rules/html-self-closing/valid/test01-input.svelte create mode 100644 tests/src/rules/html-self-closing.ts diff --git a/README.md b/README.md index b2e746eeb..f59be5c8b 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ These rules relate to style guidelines, and are therefore quite subjective: |:--------|:------------|:---| | [svelte/first-attribute-linebreak](https://ota-meshi.github.io/eslint-plugin-svelte/rules/first-attribute-linebreak/) | enforce the location of first attribute | :wrench: | | [svelte/html-quotes](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-quotes/) | enforce quotes style of HTML attributes | :wrench: | +| [svelte/html-self-closing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-self-closing/) | enforce self-closing style | :wrench: | | [svelte/indent](https://ota-meshi.github.io/eslint-plugin-svelte/rules/indent/) | enforce consistent indentation | :wrench: | | [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: | | [svelte/mustache-spacing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/mustache-spacing/) | enforce unified spacing in mustache | :wrench: | diff --git a/docs/rules.md b/docs/rules.md index 70a940c90..5813dcb5c 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -53,6 +53,7 @@ These rules relate to style guidelines, and are therefore quite subjective: |:--------|:------------|:---| | [svelte/first-attribute-linebreak](./rules/first-attribute-linebreak.md) | enforce the location of first attribute | :wrench: | | [svelte/html-quotes](./rules/html-quotes.md) | enforce quotes style of HTML attributes | :wrench: | +| [svelte/html-self-closing](./rules/html-self-closing.md) | enforce self-closing style | :wrench: | | [svelte/indent](./rules/indent.md) | enforce consistent indentation | :wrench: | | [svelte/max-attributes-per-line](./rules/max-attributes-per-line.md) | enforce the maximum number of attributes per line | :wrench: | | [svelte/mustache-spacing](./rules/mustache-spacing.md) | enforce unified spacing in mustache | :wrench: | diff --git a/docs/rules/html-self-closing.md b/docs/rules/html-self-closing.md new file mode 100644 index 000000000..d5350720f --- /dev/null +++ b/docs/rules/html-self-closing.md @@ -0,0 +1,78 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/html-self-closing" +description: "enforce self-closing style" +--- + +# svelte/html-self-closing + +> enforce self-closing style + +- :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 + +You can choose either two styles for elements without content + +- always: `
` +- never: `
` + + + + + + +```svelte + + + +
+

Hello

+
+ + + +
+

+
+ +``` + + + + + +## :wrench: Options + +```json +{ + "svelte/html-self-closing": [ + "error", + { + "html": { + "void": "always", // or "always" or "ignore" + "normal": "always", // or "never" or "ignore" + "component": "always" // or "never" or "ignore" + } + } + ] +} +``` + +- `html.void` (`"always"` by default)... Style of HTML void elements +- `html.component` (`"always"` by default)... Style of svelte components +- `html.normal` (`"always"` by default)... Style of other elements + +Every option can be set to +- "always" (`
`) +- "never" (`
`) +- "ignore" (either `
` or `
`) + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/html-self-closing.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/html-self-closing.ts) diff --git a/src/configs/prettier.ts b/src/configs/prettier.ts index 6c0eba387..aed8a0c58 100644 --- a/src/configs/prettier.ts +++ b/src/configs/prettier.ts @@ -8,6 +8,7 @@ export = { // eslint-plugin-svelte rules "svelte/first-attribute-linebreak": "off", "svelte/html-quotes": "off", + "svelte/html-self-closing": "off", "svelte/indent": "off", "svelte/max-attributes-per-line": "off", "svelte/mustache-spacing": "off", diff --git a/src/rules/html-self-closing.ts b/src/rules/html-self-closing.ts new file mode 100644 index 000000000..af3d19c09 --- /dev/null +++ b/src/rules/html-self-closing.ts @@ -0,0 +1,146 @@ +import type { AST } from "svelte-eslint-parser" +import { createRule } from "../utils" +import { + getNodeName, + isCustomComponent, + isVoidHtmlElement, +} from "../utils/template-utils" + +const TYPE_MESSAGES = { + normal: "HTML elements", + void: "HTML void elements", + component: "Svelte custom components", +} + +type ElementTypes = "normal" | "void" | "component" + +export default createRule("html-self-closing", { + meta: { + docs: { + description: "enforce self-closing style", + category: "Stylistic Issues", + recommended: false, + conflictWithPrettier: true, + }, + type: "layout", + fixable: "code", + messages: { + requireClosing: "Require self-closing on {{type}}.", + disallowClosing: "Disallow self-closing on {{type}}.", + }, + schema: [ + { + type: "object", + properties: { + html: { + type: "object", + properties: { + void: { + enum: ["never", "always", "ignore"], + }, + normal: { + enum: ["never", "always", "ignore"], + }, + component: { + enum: ["never", "always", "ignore"], + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + }, + create(ctx) { + const options = { + html: { + void: "always", + normal: "always", + component: "always", + }, + ...ctx.options?.[0], + } + + /** + * Get SvelteElement type. + * If element is custom component "component" is returned + * If element is void element "void" is returned + * otherwise "normal" is returned + */ + function getElementType(node: AST.SvelteElement): ElementTypes { + if (isCustomComponent(node)) return "component" + if (isVoidHtmlElement(node)) return "void" + return "normal" + } + + /** + * Returns true if element has no children, or has only whitespace text + */ + function isElementEmpty(node: AST.SvelteElement): boolean { + if (node.children.length <= 0) return true + + for (const child of node.children) { + if (child.type !== "SvelteText") return false + if (!/^\s*$/.test(child.value)) return false + } + + return true + } + + /** + * Report + */ + function report(node: AST.SvelteElement, close: boolean) { + const elementType = getElementType(node) + + ctx.report({ + node, + messageId: close ? "requireClosing" : "disallowClosing", + data: { + type: TYPE_MESSAGES[elementType], + }, + *fix(fixer) { + if (close) { + for (const child of node.children) { + yield fixer.removeRange(child.range) + } + + yield fixer.insertTextBeforeRange( + [node.startTag.range[1] - 1, node.startTag.range[1]], + "/", + ) + + if (node.endTag) yield fixer.removeRange(node.endTag.range) + } else { + yield fixer.removeRange([ + node.startTag.range[1] - 2, + node.startTag.range[1] - 1, + ]) + + if (!isVoidHtmlElement(node)) + yield fixer.insertTextAfter(node, ``) + } + }, + }) + } + + return { + SvelteElement(node: AST.SvelteElement) { + if (!isElementEmpty(node)) return + + const elementType = getElementType(node) + + const elementTypeOptions = options.html[elementType] + if (elementTypeOptions === "ignore") return + const shouldBeClosed = elementTypeOptions === "always" + + if (shouldBeClosed && !node.startTag.selfClosing) { + report(node, true) + } else if (!shouldBeClosed && node.startTag.selfClosing) { + report(node, false) + } + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 19fe075fd..21a52c3f9 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -3,6 +3,7 @@ import buttonHasType from "../rules/button-has-type" import commentDirective from "../rules/comment-directive" import firstAttributeLinebreak from "../rules/first-attribute-linebreak" import htmlQuotes from "../rules/html-quotes" +import htmlSelfClosing from "../rules/html-self-closing" import indent from "../rules/indent" import maxAttributesPerLine from "../rules/max-attributes-per-line" import mustacheSpacing from "../rules/mustache-spacing" @@ -33,6 +34,7 @@ export const rules = [ commentDirective, firstAttributeLinebreak, htmlQuotes, + htmlSelfClosing, indent, maxAttributesPerLine, mustacheSpacing, diff --git a/src/utils/template-utils.ts b/src/utils/template-utils.ts new file mode 100644 index 000000000..2dbe1a8b6 --- /dev/null +++ b/src/utils/template-utils.ts @@ -0,0 +1,24 @@ +import type { AST } from "svelte-eslint-parser" +import voidElements from "./void-elements" + +/** + * Returns name of SvelteElement + */ +export function getNodeName(node: AST.SvelteElement): string { + return "name" in node.name ? node.name.name : node.name.property.name +} + +/** + * Returns true if Element is custom component + */ +export function isCustomComponent(node: AST.SvelteElement): boolean { + return node.kind === "component" +} + +/** + * Returns true if element is known void element + * {@link https://developer.mozilla.org/en-US/docs/Glossary/Empty_element} + */ +export function isVoidHtmlElement(node: AST.SvelteElement): boolean { + return voidElements.includes(getNodeName(node)) +} diff --git a/src/utils/void-elements.ts b/src/utils/void-elements.ts new file mode 100644 index 000000000..4df980dc6 --- /dev/null +++ b/src/utils/void-elements.ts @@ -0,0 +1,20 @@ +const voidElements = [ + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "keygen", + "link", + "menuitem", + "meta", + "param", + "source", + "track", + "wbr", +] + +export default voidElements diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json new file mode 100644 index 000000000..5c198116d --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json @@ -0,0 +1,9 @@ +{ + "options": [ + { + "html": { + "component": "never" + } + } + ] +} diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json new file mode 100644 index 000000000..c7692bcc9 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Disallow self-closing on Svelte custom components.", + "line": 3, + "column": 3 + } +] diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte new file mode 100644 index 000000000..6c888d0bc --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte @@ -0,0 +1,4 @@ + +
+ +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte new file mode 100644 index 000000000..f90849ac6 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte @@ -0,0 +1,4 @@ + +
+ +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json new file mode 100644 index 000000000..8a13060cb --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json @@ -0,0 +1,9 @@ +{ + "options": [ + { + "html": { + "normal": "ignore" + } + } + ] +} diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json new file mode 100644 index 000000000..95f4dfb19 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Disallow self-closing on HTML void elements.", + "line": 5, + "column": 3 + } +] diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte new file mode 100644 index 000000000..c195a36c1 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte @@ -0,0 +1,6 @@ + +
+
+
+ +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte new file mode 100644 index 000000000..66e0cd246 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte @@ -0,0 +1,6 @@ + +
+
+
+ +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json new file mode 100644 index 000000000..53bea34aa --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json @@ -0,0 +1,9 @@ +{ + "options": [ + { + "html": { + "normal": "never" + } + } + ] +} diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-errors.json new file mode 100644 index 000000000..1d62f7723 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Disallow self-closing on HTML elements.", + "line": 3, + "column": 3 + } +] diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-input.svelte new file mode 100644 index 000000000..5c8a2cf01 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-input.svelte @@ -0,0 +1,4 @@ + +
+
+
diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-output.svelte new file mode 100644 index 000000000..a45145e01 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-never/component-never-output.svelte @@ -0,0 +1,4 @@ + +
+
+
diff --git a/tests/fixtures/rules/html-self-closing/invalid/test01-errors.json b/tests/fixtures/rules/html-self-closing/invalid/test01-errors.json new file mode 100644 index 000000000..7ab1a841f --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/test01-errors.json @@ -0,0 +1,17 @@ +[ + { + "message": "Require self-closing on HTML elements.", + "line": 3, + "column": 3 + }, + { + "message": "Require self-closing on Svelte custom components.", + "line": 4, + "column": 3 + }, + { + "message": "Require self-closing on HTML void elements.", + "line": 5, + "column": 3 + } +] diff --git a/tests/fixtures/rules/html-self-closing/invalid/test01-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/test01-input.svelte new file mode 100644 index 000000000..b8d7b8051 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/test01-input.svelte @@ -0,0 +1,6 @@ + +
+
+ + +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/test01-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/test01-output.svelte new file mode 100644 index 000000000..d8a1286c5 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/test01-output.svelte @@ -0,0 +1,6 @@ + +
+
+ + +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json new file mode 100644 index 000000000..4e7d9b767 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json @@ -0,0 +1,9 @@ +{ + "options": [ + { + "html": { + "void": "never" + } + } + ] +} diff --git a/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-errors.json new file mode 100644 index 000000000..3b61e0b15 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Disallow self-closing on HTML void elements.", + "line": 3, + "column": 3 + } +] diff --git a/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-input.svelte new file mode 100644 index 000000000..42ea6dbf7 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-input.svelte @@ -0,0 +1,4 @@ + +
+ +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-output.svelte new file mode 100644 index 000000000..713cf9f72 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/void-never/void-never-output.svelte @@ -0,0 +1,4 @@ + +
+ +
diff --git a/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte b/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte new file mode 100644 index 000000000..104b5c286 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte @@ -0,0 +1,5 @@ +
+
+
hello
+ +
diff --git a/tests/src/rules/html-self-closing.ts b/tests/src/rules/html-self-closing.ts new file mode 100644 index 000000000..6890e9087 --- /dev/null +++ b/tests/src/rules/html-self-closing.ts @@ -0,0 +1,12 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/html-self-closing" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run("html-self-closing", rule as any, loadTestCases("html-self-closing")) From 60a23c990f28714350de8ed07bf4c4de70e51ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Vosp=C4=9Bl?= Date: Sat, 23 Jul 2022 12:47:03 +0200 Subject: [PATCH 2/4] chore: requested changes --- src/rules/html-self-closing.ts | 15 +++++++++---- src/utils/template-utils.ts | 21 ++++++++++++------- .../component-never-errors.json | 5 +++++ .../component-never-input.svelte | 1 + .../component-never-output.svelte | 1 + .../normal-ignore/normal-any-errors.json | 2 +- .../normal-ignore/normal-any-input.svelte | 2 +- .../normal-ignore/normal-any-output.svelte | 2 +- .../invalid/special-never/_config.json | 7 +++++++ .../special-never/special-never-errors.json | 1 + .../special-never/special-never-input.svelte | 2 ++ .../special-never/special-never-output.svelte | 2 ++ .../valid/test01-input.svelte | 4 ++++ 13 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-input.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte diff --git a/src/rules/html-self-closing.ts b/src/rules/html-self-closing.ts index af3d19c09..c69975981 100644 --- a/src/rules/html-self-closing.ts +++ b/src/rules/html-self-closing.ts @@ -2,7 +2,6 @@ import type { AST } from "svelte-eslint-parser" import { createRule } from "../utils" import { getNodeName, - isCustomComponent, isVoidHtmlElement, } from "../utils/template-utils" @@ -10,9 +9,10 @@ const TYPE_MESSAGES = { normal: "HTML elements", void: "HTML void elements", component: "Svelte custom components", + svelte: "Svelte special elements", } -type ElementTypes = "normal" | "void" | "component" +type ElementTypes = "normal" | "void" | "component" | "svelte" export default createRule("html-self-closing", { meta: { @@ -44,6 +44,9 @@ export default createRule("html-self-closing", { component: { enum: ["never", "always", "ignore"], }, + svelte: { + enum: ["never", "always", "ignore"], + }, }, additionalProperties: false, }, @@ -54,22 +57,26 @@ export default createRule("html-self-closing", { }, create(ctx) { const options = { + ...ctx.options?.[0], html: { void: "always", normal: "always", component: "always", + svelte: "always", + ...ctx.options?.[0]?.html, }, - ...ctx.options?.[0], } /** * Get SvelteElement type. * If element is custom component "component" is returned + * If element is svelte special element such as svelte:self "svelte" is returned * If element is void element "void" is returned * otherwise "normal" is returned */ function getElementType(node: AST.SvelteElement): ElementTypes { - if (isCustomComponent(node)) return "component" + if (node.kind === "component") return "component" + if (node.kind === "special") return "svelte" if (isVoidHtmlElement(node)) return "void" return "normal" } diff --git a/src/utils/template-utils.ts b/src/utils/template-utils.ts index 2dbe1a8b6..e85a77274 100644 --- a/src/utils/template-utils.ts +++ b/src/utils/template-utils.ts @@ -5,14 +5,19 @@ import voidElements from "./void-elements" * Returns name of SvelteElement */ export function getNodeName(node: AST.SvelteElement): string { - return "name" in node.name ? node.name.name : node.name.property.name -} - -/** - * Returns true if Element is custom component - */ -export function isCustomComponent(node: AST.SvelteElement): boolean { - return node.kind === "component" + if ("name" in node.name) { + return node.name.name + } + let object = "" + let currentObject = node.name.object + while ("object" in currentObject) { + object = `${currentObject.property.name}.${object}` + currentObject = currentObject.object + } + if ("name" in currentObject) { + object = `${currentObject.name}.${object}` + } + return object + node.name.property.name } /** diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json index c7692bcc9..a7d505baf 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-errors.json @@ -3,5 +3,10 @@ "message": "Disallow self-closing on Svelte custom components.", "line": 3, "column": 3 + }, + { + "message": "Disallow self-closing on Svelte custom components.", + "line": 4, + "column": 3 } ] diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte index 6c888d0bc..61b1ad263 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-input.svelte @@ -1,4 +1,5 @@
+
diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte index f90849ac6..fcabea330 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/component-never-output.svelte @@ -1,4 +1,5 @@
+
diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json index 95f4dfb19..c14a98697 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-errors.json @@ -1,6 +1,6 @@ [ { - "message": "Disallow self-closing on HTML void elements.", + "message": "Require self-closing on HTML void elements.", "line": 5, "column": 3 } diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte index c195a36c1..14f69ea78 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-input.svelte @@ -2,5 +2,5 @@
- +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte index 66e0cd246..eedf2bb1f 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/normal-any-output.svelte @@ -2,5 +2,5 @@
- +
diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json new file mode 100644 index 000000000..eac888ffa --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json @@ -0,0 +1,7 @@ +{ + "options": [{ + "html": { + "special": "never" + } + }] +} diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json @@ -0,0 +1 @@ +[] diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-input.svelte new file mode 100644 index 000000000..47943f3a6 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-input.svelte @@ -0,0 +1,2 @@ + + diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte new file mode 100644 index 000000000..47943f3a6 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte @@ -0,0 +1,2 @@ + + diff --git a/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte b/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte index 104b5c286..55bbb8893 100644 --- a/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte +++ b/tests/fixtures/rules/html-self-closing/valid/test01-input.svelte @@ -2,4 +2,8 @@
hello
+ {#if true} + + {/if} +
From 4c61301f3995ee76fc184dd455e5799f32658476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Vosp=C4=9Bl?= Date: Sat, 23 Jul 2022 22:21:23 +0200 Subject: [PATCH 3/4] fix: test not passing, lint, edit rule docs --- docs/rules/html-self-closing.md | 12 ++++++++---- src/rules/html-self-closing.ts | 5 +---- .../invalid/special-never/_config.json | 7 ------- .../invalid/special-never/special-never-errors.json | 1 - .../special-never/special-never-output.svelte | 2 -- .../invalid/svelte-never/_config.json | 9 +++++++++ .../invalid/svelte-never/svelte-never-errors.json | 7 +++++++ .../svelte-never-input.svelte} | 0 .../invalid/svelte-never/svelte-never-output.svelte | 2 ++ 9 files changed, 27 insertions(+), 18 deletions(-) delete mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json delete mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json delete mode 100644 tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte create mode 100644 tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json create mode 100644 tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-errors.json rename tests/fixtures/rules/html-self-closing/invalid/{special-never/special-never-input.svelte => svelte-never/svelte-never-input.svelte} (100%) create mode 100644 tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-output.svelte diff --git a/docs/rules/html-self-closing.md b/docs/rules/html-self-closing.md index d5350720f..3a81561c2 100644 --- a/docs/rules/html-self-closing.md +++ b/docs/rules/html-self-closing.md @@ -33,13 +33,15 @@ You can choose either two styles for elements without content

Hello

- + +

- + + ``` @@ -48,7 +50,7 @@ You can choose either two styles for elements without content ## :wrench: Options -```json +```jsonc { "svelte/html-self-closing": [ "error", @@ -56,7 +58,8 @@ You can choose either two styles for elements without content "html": { "void": "always", // or "always" or "ignore" "normal": "always", // or "never" or "ignore" - "component": "always" // or "never" or "ignore" + "component": "always", // or "never" or "ignore" + "svelte": "always" // or "never" or "ignore" } } ] @@ -65,6 +68,7 @@ You can choose either two styles for elements without content - `html.void` (`"always"` by default)... Style of HTML void elements - `html.component` (`"always"` by default)... Style of svelte components +- `html.svelte` (`"always"` by default)... Style of svelte special elements (``, ``) - `html.normal` (`"always"` by default)... Style of other elements Every option can be set to diff --git a/src/rules/html-self-closing.ts b/src/rules/html-self-closing.ts index c69975981..9bc4c9412 100644 --- a/src/rules/html-self-closing.ts +++ b/src/rules/html-self-closing.ts @@ -1,9 +1,6 @@ import type { AST } from "svelte-eslint-parser" import { createRule } from "../utils" -import { - getNodeName, - isVoidHtmlElement, -} from "../utils/template-utils" +import { getNodeName, isVoidHtmlElement } from "../utils/template-utils" const TYPE_MESSAGES = { normal: "HTML elements", diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json deleted file mode 100644 index eac888ffa..000000000 --- a/tests/fixtures/rules/html-self-closing/invalid/special-never/_config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "options": [{ - "html": { - "special": "never" - } - }] -} diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-errors.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte deleted file mode 100644 index 47943f3a6..000000000 --- a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-output.svelte +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json new file mode 100644 index 000000000..8c3e94a84 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json @@ -0,0 +1,9 @@ +{ + "options": [ + { + "html": { + "svelte": "never" + } + } + ] +} diff --git a/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-errors.json b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-errors.json new file mode 100644 index 000000000..6531399aa --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-errors.json @@ -0,0 +1,7 @@ +[ + { + "message": "Disallow self-closing on Svelte special elements.", + "line": 2, + "column": 1 + } +] diff --git a/tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-input.svelte b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-input.svelte similarity index 100% rename from tests/fixtures/rules/html-self-closing/invalid/special-never/special-never-input.svelte rename to tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-input.svelte diff --git a/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-output.svelte b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-output.svelte new file mode 100644 index 000000000..161b1f0a1 --- /dev/null +++ b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/svelte-never-output.svelte @@ -0,0 +1,2 @@ + + From a07898bae437998b471f49ff7378db4d7a825c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Vosp=C4=9Bl?= Date: Wed, 27 Jul 2022 16:38:19 +0200 Subject: [PATCH 4/4] chore(html-self-closing): move options from html object, move utils to ast-utils --- docs/rules/html-self-closing.md | 18 ++++---- src/rules/html-self-closing.ts | 43 ++++++++----------- src/utils/ast-utils.ts | 28 ++++++++++++ src/utils/template-utils.ts | 29 ------------- .../invalid/component-never/_config.json | 4 +- .../invalid/normal-ignore/_config.json | 4 +- .../invalid/normal-never/_config.json | 4 +- .../invalid/svelte-never/_config.json | 4 +- .../invalid/void-never/_config.json | 4 +- 9 files changed, 58 insertions(+), 80 deletions(-) delete mode 100644 src/utils/template-utils.ts diff --git a/docs/rules/html-self-closing.md b/docs/rules/html-self-closing.md index 3a81561c2..45878662a 100644 --- a/docs/rules/html-self-closing.md +++ b/docs/rules/html-self-closing.md @@ -55,21 +55,19 @@ You can choose either two styles for elements without content "svelte/html-self-closing": [ "error", { - "html": { - "void": "always", // or "always" or "ignore" - "normal": "always", // or "never" or "ignore" - "component": "always", // or "never" or "ignore" - "svelte": "always" // or "never" or "ignore" - } + "void": "always", // or "always" or "ignore" + "normal": "always", // or "never" or "ignore" + "component": "always", // or "never" or "ignore" + "svelte": "always" // or "never" or "ignore" } ] } ``` -- `html.void` (`"always"` by default)... Style of HTML void elements -- `html.component` (`"always"` by default)... Style of svelte components -- `html.svelte` (`"always"` by default)... Style of svelte special elements (``, ``) -- `html.normal` (`"always"` by default)... Style of other elements +- `void` (`"always"` by default)... Style of HTML void elements +- `component` (`"always"` by default)... Style of svelte components +- `svelte` (`"always"` by default)... Style of svelte special elements (``, ``) +- `normal` (`"always"` by default)... Style of other elements Every option can be set to - "always" (`
`) diff --git a/src/rules/html-self-closing.ts b/src/rules/html-self-closing.ts index 9bc4c9412..6e10d65e2 100644 --- a/src/rules/html-self-closing.ts +++ b/src/rules/html-self-closing.ts @@ -1,6 +1,6 @@ import type { AST } from "svelte-eslint-parser" import { createRule } from "../utils" -import { getNodeName, isVoidHtmlElement } from "../utils/template-utils" +import { getNodeName, isVoidHtmlElement } from "../utils/ast-utils" const TYPE_MESSAGES = { normal: "HTML elements", @@ -29,23 +29,17 @@ export default createRule("html-self-closing", { { type: "object", properties: { - html: { - type: "object", - properties: { - void: { - enum: ["never", "always", "ignore"], - }, - normal: { - enum: ["never", "always", "ignore"], - }, - component: { - enum: ["never", "always", "ignore"], - }, - svelte: { - enum: ["never", "always", "ignore"], - }, - }, - additionalProperties: false, + void: { + enum: ["never", "always", "ignore"], + }, + normal: { + enum: ["never", "always", "ignore"], + }, + component: { + enum: ["never", "always", "ignore"], + }, + svelte: { + enum: ["never", "always", "ignore"], }, }, additionalProperties: false, @@ -54,14 +48,11 @@ export default createRule("html-self-closing", { }, create(ctx) { const options = { + void: "always", + normal: "always", + component: "always", + svelte: "always", ...ctx.options?.[0], - html: { - void: "always", - normal: "always", - component: "always", - svelte: "always", - ...ctx.options?.[0]?.html, - }, } /** @@ -135,7 +126,7 @@ export default createRule("html-self-closing", { const elementType = getElementType(node) - const elementTypeOptions = options.html[elementType] + const elementTypeOptions = options[elementType] if (elementTypeOptions === "ignore") return const shouldBeClosed = elementTypeOptions === "always" diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts index 8981ca3bb..112e4ff7a 100644 --- a/src/utils/ast-utils.ts +++ b/src/utils/ast-utils.ts @@ -3,6 +3,7 @@ import type * as ESTree from "estree" import type { AST as SvAST } from "svelte-eslint-parser" import * as eslintUtils from "eslint-utils" import type { Scope } from "eslint" +import voidElements from "./void-elements" /** * Checks whether or not the tokens of two given nodes are same. @@ -437,3 +438,30 @@ function getAttributeValueRangeTokens( lastToken: tokens.closeToken, } } + +/** + * Returns name of SvelteElement + */ +export function getNodeName(node: SvAST.SvelteElement): string { + if ("name" in node.name) { + return node.name.name + } + let object = "" + let currentObject = node.name.object + while ("object" in currentObject) { + object = `${currentObject.property.name}.${object}` + currentObject = currentObject.object + } + if ("name" in currentObject) { + object = `${currentObject.name}.${object}` + } + return object + node.name.property.name +} + +/** + * Returns true if element is known void element + * {@link https://developer.mozilla.org/en-US/docs/Glossary/Empty_element} + */ +export function isVoidHtmlElement(node: SvAST.SvelteElement): boolean { + return voidElements.includes(getNodeName(node)) +} diff --git a/src/utils/template-utils.ts b/src/utils/template-utils.ts deleted file mode 100644 index e85a77274..000000000 --- a/src/utils/template-utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { AST } from "svelte-eslint-parser" -import voidElements from "./void-elements" - -/** - * Returns name of SvelteElement - */ -export function getNodeName(node: AST.SvelteElement): string { - if ("name" in node.name) { - return node.name.name - } - let object = "" - let currentObject = node.name.object - while ("object" in currentObject) { - object = `${currentObject.property.name}.${object}` - currentObject = currentObject.object - } - if ("name" in currentObject) { - object = `${currentObject.name}.${object}` - } - return object + node.name.property.name -} - -/** - * Returns true if element is known void element - * {@link https://developer.mozilla.org/en-US/docs/Glossary/Empty_element} - */ -export function isVoidHtmlElement(node: AST.SvelteElement): boolean { - return voidElements.includes(getNodeName(node)) -} diff --git a/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json index 5c198116d..7a7c92aa6 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json +++ b/tests/fixtures/rules/html-self-closing/invalid/component-never/_config.json @@ -1,9 +1,7 @@ { "options": [ { - "html": { - "component": "never" - } + "component": "never" } ] } diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json index 8a13060cb..18bfad55d 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-ignore/_config.json @@ -1,9 +1,7 @@ { "options": [ { - "html": { - "normal": "ignore" - } + "normal": "ignore" } ] } diff --git a/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json index 53bea34aa..0bf73a739 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json +++ b/tests/fixtures/rules/html-self-closing/invalid/normal-never/_config.json @@ -1,9 +1,7 @@ { "options": [ { - "html": { - "normal": "never" - } + "normal": "never" } ] } diff --git a/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json index 8c3e94a84..697ddfa81 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json +++ b/tests/fixtures/rules/html-self-closing/invalid/svelte-never/_config.json @@ -1,9 +1,7 @@ { "options": [ { - "html": { - "svelte": "never" - } + "svelte": "never" } ] } diff --git a/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json b/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json index 4e7d9b767..f395b7ad8 100644 --- a/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json +++ b/tests/fixtures/rules/html-self-closing/invalid/void-never/_config.json @@ -1,9 +1,7 @@ { "options": [ { - "html": { - "void": "never" - } + "void": "never" } ] }