diff --git a/.changeset/wise-flies-lay.md b/.changeset/wise-flies-lay.md new file mode 100644 index 000000000..a5360b7b7 --- /dev/null +++ b/.changeset/wise-flies-lay.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: add `no-restricted-html-elements` rule diff --git a/README.md b/README.md index a5661af1c..f512560a2 100644 --- a/README.md +++ b/README.md @@ -367,6 +367,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | [svelte/max-attributes-per-line](https://sveltejs.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/) | enforce the maximum number of attributes per line | :wrench: | | [svelte/mustache-spacing](https://sveltejs.github.io/eslint-plugin-svelte/rules/mustache-spacing/) | enforce unified spacing in mustache | :wrench: | | [svelte/no-extra-reactive-curlies](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-extra-reactive-curlies/) | disallow wrapping single reactive statements in curly braces | :bulb: | +| [svelte/no-restricted-html-elements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-restricted-html-elements/) | disallow specific HTML elements | | | [svelte/no-spaces-around-equal-signs-in-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-spaces-around-equal-signs-in-attribute/) | disallow spaces around equal signs in attribute | :wrench: | | [svelte/prefer-class-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: | | [svelte/prefer-style-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: | diff --git a/docs/rules.md b/docs/rules.md index ef85cb467..644183301 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -80,6 +80,7 @@ These rules relate to style guidelines, and are therefore quite subjective: | [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: | | [svelte/no-extra-reactive-curlies](./rules/no-extra-reactive-curlies.md) | disallow wrapping single reactive statements in curly braces | :bulb: | +| [svelte/no-restricted-html-elements](./rules/no-restricted-html-elements.md) | disallow specific HTML elements | | | [svelte/no-spaces-around-equal-signs-in-attribute](./rules/no-spaces-around-equal-signs-in-attribute.md) | disallow spaces around equal signs in attribute | :wrench: | | [svelte/prefer-class-directive](./rules/prefer-class-directive.md) | require class directives instead of ternary expressions | :wrench: | | [svelte/prefer-style-directive](./rules/prefer-style-directive.md) | require style directives instead of style attribute | :wrench: | diff --git a/docs/rules/no-restricted-html-elements.md b/docs/rules/no-restricted-html-elements.md new file mode 100644 index 000000000..b7cac878b --- /dev/null +++ b/docs/rules/no-restricted-html-elements.md @@ -0,0 +1,107 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/no-restricted-html-elements" +description: "disallow specific HTML elements" +--- + +# svelte/no-restricted-html-elements + +> disallow specific HTML elements + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule reports to usage of resticted HTML elements. + + + + + +```svelte + + + +
+

Hi!

+
+ + +

foo

+ +
+

bar

+
+``` + +
+ +--- + + + + + +```svelte + + + +
+

Hi!

+
+ + +foo + +
+ bar +
+``` + +
+ +## :wrench: Options + +This rule takes a list of strings, where each string is an HTML element name to be restricted: + +```json +{ + "svelte/no-restricted-html-elements": [ + "error", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6" + ] +} +``` + +Alternatively, the rule also accepts objects. + +```json +{ + "svelte/no-restricted-html-elements": [ + "error", + { + "elements": ["h1", "h2", "h3", "h4", "h5", "h6"], + "message": "Prefer use of our custom component" + }, + { + "elements": ["marquee"], + "message": "Do not use deprecated HTML tags" + } + ] +} +``` + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-restricted-html-elements.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-restricted-html-elements.ts) diff --git a/src/rules/no-restricted-html-elements.ts b/src/rules/no-restricted-html-elements.ts new file mode 100644 index 000000000..979333012 --- /dev/null +++ b/src/rules/no-restricted-html-elements.ts @@ -0,0 +1,63 @@ +import { createRule } from "../utils" + +export default createRule("no-restricted-html-elements", { + meta: { + docs: { + description: "disallow specific HTML elements", + category: "Stylistic Issues", + recommended: false, + conflictWithPrettier: false, + }, + schema: { + type: "array", + items: { + oneOf: [ + { type: "string" }, + { + type: "object", + properties: { + elements: { + type: "array", + items: { + type: ["string"], + }, + uniqueItems: true, + minItems: 1, + }, + message: { type: "string", minLength: 1 }, + }, + additionalProperties: false, + minItems: 1, + }, + ], + }, + uniqueItems: true, + minItems: 1, + }, + messages: {}, + type: "suggestion", + }, + create(context) { + return { + SvelteElement(node) { + if (node.kind !== "html") return + const { name } = node + if (name.type !== "SvelteName") return + for (const option of context.options) { + const message = + option.message || + `Unexpected use of forbidden HTML element ${name.name}.` + const elements = option.elements || [option] + for (const element of elements) { + if (element === name.name) { + context.report({ + message, + node: node.startTag, + }) + } + } + } + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 7dac11750..3e4141f65 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -34,6 +34,7 @@ import noObjectInTextMustaches from "../rules/no-object-in-text-mustaches" import noReactiveFunctions from "../rules/no-reactive-functions" import noReactiveLiterals from "../rules/no-reactive-literals" import noReactiveReassign from "../rules/no-reactive-reassign" +import noRestrictedHtmlElements from "../rules/no-restricted-html-elements" import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides" import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute" import noStoreAsync from "../rules/no-store-async" @@ -93,6 +94,7 @@ export const rules = [ noReactiveFunctions, noReactiveLiterals, noReactiveReassign, + noRestrictedHtmlElements, noShorthandStylePropertyOverrides, noSpacesAroundEqualSignsInAttribute, noStoreAsync, diff --git a/tests/fixtures/rules/no-restricted-html-elements/invalid/array-config.json b/tests/fixtures/rules/no-restricted-html-elements/invalid/array-config.json new file mode 100644 index 000000000..e5e624770 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/invalid/array-config.json @@ -0,0 +1,3 @@ +{ + "options": ["h1", "h2", "h3", "h4", "h5", "h6"] +} diff --git a/tests/fixtures/rules/no-restricted-html-elements/invalid/array-errors.yaml b/tests/fixtures/rules/no-restricted-html-elements/invalid/array-errors.yaml new file mode 100644 index 000000000..4242fd1f1 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/invalid/array-errors.yaml @@ -0,0 +1,24 @@ +- message: Unexpected use of forbidden HTML element h1. + line: 1 + column: 1 + suggestions: null +- message: Unexpected use of forbidden HTML element h2. + line: 2 + column: 1 + suggestions: null +- message: Unexpected use of forbidden HTML element h3. + line: 3 + column: 1 + suggestions: null +- message: Unexpected use of forbidden HTML element h4. + line: 4 + column: 1 + suggestions: null +- message: Unexpected use of forbidden HTML element h5. + line: 5 + column: 1 + suggestions: null +- message: Unexpected use of forbidden HTML element h6. + line: 6 + column: 1 + suggestions: null diff --git a/tests/fixtures/rules/no-restricted-html-elements/invalid/array-input.svelte b/tests/fixtures/rules/no-restricted-html-elements/invalid/array-input.svelte new file mode 100644 index 000000000..2caff83b1 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/invalid/array-input.svelte @@ -0,0 +1,6 @@ +

Main Title - H1

+

Subtitle - H2

+

Subsection Title - H3

+

Sub-Subsection Title - H4

+
Minor Title - H5
+
Minor Subtitle - H6
diff --git a/tests/fixtures/rules/no-restricted-html-elements/invalid/object-config.json b/tests/fixtures/rules/no-restricted-html-elements/invalid/object-config.json new file mode 100644 index 000000000..9f76c45b7 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/invalid/object-config.json @@ -0,0 +1,12 @@ +{ + "options": [ + { + "elements": ["h1", "h2", "h3", "h4", "h5", "h6"], + "message": "Prefer use of our custom component" + }, + { + "elements": ["marquee"], + "message": "Do not use deprecated HTML tags" + } + ] +} diff --git a/tests/fixtures/rules/no-restricted-html-elements/invalid/object-errors.yaml b/tests/fixtures/rules/no-restricted-html-elements/invalid/object-errors.yaml new file mode 100644 index 000000000..418419380 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/invalid/object-errors.yaml @@ -0,0 +1,48 @@ +- message: Prefer use of our custom component + line: 1 + column: 1 + suggestions: null +- message: Prefer use of our custom component + line: 2 + column: 1 + suggestions: null +- message: Prefer use of our custom component + line: 3 + column: 1 + suggestions: null +- message: Prefer use of our custom component + line: 4 + column: 1 + suggestions: null +- message: Prefer use of our custom component + line: 5 + column: 1 + suggestions: null +- message: Prefer use of our custom component + line: 6 + column: 1 + suggestions: null +- message: Do not use deprecated HTML tags + line: 8 + column: 1 + suggestions: null +- message: Do not use deprecated HTML tags + line: 10 + column: 1 + suggestions: null +- message: Do not use deprecated HTML tags + line: 12 + column: 1 + suggestions: null +- message: Do not use deprecated HTML tags + line: 19 + column: 3 + suggestions: null +- message: Do not use deprecated HTML tags + line: 23 + column: 3 + suggestions: null +- message: Prefer use of our custom component + line: 27 + column: 3 + suggestions: null diff --git a/tests/fixtures/rules/no-restricted-html-elements/invalid/object-input.svelte b/tests/fixtures/rules/no-restricted-html-elements/invalid/object-input.svelte new file mode 100644 index 000000000..414fc87b0 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/invalid/object-input.svelte @@ -0,0 +1,28 @@ +

Main Title - H1

+

Subtitle - H2

+

Subsection Title - H3

+

Sub-Subsection Title - H4

+
Minor Title - H5
+
Minor Subtitle - H6
+ +This text will scroll from right to left + +This text will scroll from bottom to top + + + This text will bounce + + +
+ This text will scroll from right to left +
+ +
+
Minor Subtitle - H6
+
diff --git a/tests/fixtures/rules/no-restricted-html-elements/valid/array-config.json b/tests/fixtures/rules/no-restricted-html-elements/valid/array-config.json new file mode 100644 index 000000000..e5e624770 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/valid/array-config.json @@ -0,0 +1,3 @@ +{ + "options": ["h1", "h2", "h3", "h4", "h5", "h6"] +} diff --git a/tests/fixtures/rules/no-restricted-html-elements/valid/array-input.svelte b/tests/fixtures/rules/no-restricted-html-elements/valid/array-input.svelte new file mode 100644 index 000000000..29d43d502 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/valid/array-input.svelte @@ -0,0 +1,23 @@ +
This is a title
+
This is a subtitle
+

This is a paragraph. Some sample text goes here.

+
    +
  • This is
  • +
  • a list item
  • +
+ + + + + + + + + +
Table Cell 1Table Cell 2
Table Cell 3Table Cell 4
+This is a hyperlink to example.com +
+
+
+ +
diff --git a/tests/fixtures/rules/no-restricted-html-elements/valid/object-config.json b/tests/fixtures/rules/no-restricted-html-elements/valid/object-config.json new file mode 100644 index 000000000..9f76c45b7 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/valid/object-config.json @@ -0,0 +1,12 @@ +{ + "options": [ + { + "elements": ["h1", "h2", "h3", "h4", "h5", "h6"], + "message": "Prefer use of our custom component" + }, + { + "elements": ["marquee"], + "message": "Do not use deprecated HTML tags" + } + ] +} diff --git a/tests/fixtures/rules/no-restricted-html-elements/valid/object-input.svelte b/tests/fixtures/rules/no-restricted-html-elements/valid/object-input.svelte new file mode 100644 index 000000000..29d43d502 --- /dev/null +++ b/tests/fixtures/rules/no-restricted-html-elements/valid/object-input.svelte @@ -0,0 +1,23 @@ +
This is a title
+
This is a subtitle
+

This is a paragraph. Some sample text goes here.

+
    +
  • This is
  • +
  • a list item
  • +
+ + + + + + + + + +
Table Cell 1Table Cell 2
Table Cell 3Table Cell 4
+This is a hyperlink to example.com +
+
+
+ +
diff --git a/tests/src/rules/no-restricted-html-elements.ts b/tests/src/rules/no-restricted-html-elements.ts new file mode 100644 index 000000000..d27517d22 --- /dev/null +++ b/tests/src/rules/no-restricted-html-elements.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/no-restricted-html-elements" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "no-restricted-html-elements", + rule as any, + loadTestCases("no-restricted-html-elements"), +)