diff --git a/.changeset/curly-tigers-destroy.md b/.changeset/curly-tigers-destroy.md new file mode 100644 index 000000000..90be91bae --- /dev/null +++ b/.changeset/curly-tigers-destroy.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: add `svelte/no-trailing-spaces` rule diff --git a/docs/rules.md b/docs/rules.md index cec9eb373..97379b6ed 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -79,6 +79,7 @@ These rules extend the rules provided by ESLint itself to work well in Svelte: | Rule ID | Description | | |:--------|:------------|:---| | [svelte/no-inner-declarations](./rules/no-inner-declarations.md) | disallow variable or `function` declarations in nested blocks | :star: | +| [svelte/no-trailing-spaces](./rules/no-trailing-spaces.md) | disallow trailing whitespace at the end of lines | :wrench: | ## System diff --git a/docs/rules/no-trailing-spaces.md b/docs/rules/no-trailing-spaces.md new file mode 100644 index 000000000..7a5bc2502 --- /dev/null +++ b/docs/rules/no-trailing-spaces.md @@ -0,0 +1,83 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/no-trailing-spaces" +description: "disallow trailing whitespace at the end of lines" +--- + +# svelte/no-trailing-spaces + +> disallow trailing whitespace at the end of lines + +- :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 extends the base ESLint's [no-trailing-spaces] rule. The [no-trailing-spaces] rule does not understand HTML comments and will report trailing whitespace in HTML comments when using `ignoreComments` option. +This rule supports HTML comments generated by [svelte-eslint-parser]. + +[svelte-eslint-parser]: https://github.com/ota-meshi/svelte-eslint-parser + + + + + + +```svelte + + + +
+ Text +
+ + +
+ Text +
+``` + + + +
+ +## :wrench: Options + +```jsonc +{ + "no-trailing-spaces": "off", // Don't need ESLint's no-trailing-spaces rule, so turn it off. + "svelte/no-trailing-spaces": [ + "error", + { + "skipBlankLines": false, + "ignoreComments": false + } + ] +} +``` + +- `skipBlankLines` ... If `true`, allows trailing whitespace on empty lines. +- `ignoreComments` ... If `true`, allows trailing whitespace in comments. + +Same as [no-trailing-spaces] rule option. See [here](https://eslint.org/docs/rules/no-trailing-spaces#options) for details. + +## :couple: Related rules + +- [no-trailing-spaces] + +[no-trailing-spaces]: https://eslint.org/docs/rules/no-trailing-spaces + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-trailing-spaces.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-trailing-spaces.ts) + +Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-trailing-spaces) diff --git a/src/configs/prettier.ts b/src/configs/prettier.ts index 20ea0c6a7..f33faf067 100644 --- a/src/configs/prettier.ts +++ b/src/configs/prettier.ts @@ -14,6 +14,7 @@ export = { "svelte/max-attributes-per-line": "off", "svelte/mustache-spacing": "off", "svelte/no-spaces-around-equal-signs-in-attribute": "off", + "svelte/no-trailing-spaces": "off", "svelte/shorthand-attribute": "off", "svelte/shorthand-directive": "off", }, diff --git a/src/rules/no-trailing-spaces.ts b/src/rules/no-trailing-spaces.ts new file mode 100644 index 000000000..367530d1f --- /dev/null +++ b/src/rules/no-trailing-spaces.ts @@ -0,0 +1,104 @@ +import type { AST } from "svelte-eslint-parser" +import { createRule } from "../utils" + +export default createRule("no-trailing-spaces", { + meta: { + type: "layout", + docs: { + description: "disallow trailing whitespace at the end of lines", + category: "Extension Rules", + recommended: false, + extensionRule: "no-trailing-spaces", + conflictWithPrettier: true, + }, + fixable: "whitespace", + schema: [ + { + type: "object", + properties: { + skipBlankLines: { type: "boolean" }, + ignoreComments: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + messages: { + trailingSpace: "Trailing spaces not allowed.", + }, + }, + create(context) { + const options: + | { skipBlankLines?: boolean; ignoreComments?: boolean } + | undefined = context.options[0] + const skipBlankLines = options?.skipBlankLines || false + const ignoreComments = options?.ignoreComments || false + + const sourceCode = context.getSourceCode() + + const ignoreLineNumbers = new Set() + if (ignoreComments) { + for (const { type, loc } of sourceCode.getAllComments()) { + const endLine = type === "Block" ? loc.end.line - 1 : loc.end.line + for (let i = loc.start.line; i <= endLine; i++) { + ignoreLineNumbers.add(i) + } + } + } + + /** + * Reports a given location. + */ + function report(loc: AST.SourceLocation) { + context.report({ + loc, + messageId: "trailingSpace", + fix(fixer) { + return fixer.removeRange([ + sourceCode.getIndexFromLoc(loc.start), + sourceCode.getIndexFromLoc(loc.end), + ]) + }, + }) + } + + /** + * Collects the location of the given node as the ignore line numbers. + */ + function collectIgnoreLineNumbers({ loc }: { loc: AST.SourceLocation }) { + const endLine = loc.end.line - 1 + for (let i = loc.start.line; i <= endLine; i++) { + ignoreLineNumbers.add(i) + } + } + + return { + TemplateElement: collectIgnoreLineNumbers, + ...(ignoreComments + ? { + SvelteHTMLComment: collectIgnoreLineNumbers, + } + : {}), + "Program:exit"() { + const lines = sourceCode.lines + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex] + if (skipBlankLines && !line.trim()) { + continue + } + const lineNumber = lineIndex + 1 + if (ignoreLineNumbers.has(lineNumber)) { + continue + } + const trimmed = line.trimEnd() + if (trimmed === line) { + continue + } + report({ + start: { line: lineNumber, column: trimmed.length }, + end: { line: lineNumber, column: line.length }, + }) + } + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 06382d328..9efb8fcae 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -23,6 +23,7 @@ import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-prope import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute" import noStoreAsync from "../rules/no-store-async" import noTargetBlank from "../rules/no-target-blank" +import noTrailingSpaces from "../rules/no-trailing-spaces" import noUnknownStyleDirectiveProperty from "../rules/no-unknown-style-directive-property" import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore" import noUselessMustaches from "../rules/no-useless-mustaches" @@ -62,6 +63,7 @@ export const rules = [ noSpacesAroundEqualSignsInAttribute, noStoreAsync, noTargetBlank, + noTrailingSpaces, noUnknownStyleDirectiveProperty, noUnusedSvelteIgnore, noUselessMustaches, diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/_config.json b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/_config.json new file mode 100644 index 000000000..601018651 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/_config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignoreComments": true + } + ] +} diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-errors.yaml b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-errors.yaml new file mode 100644 index 000000000..efd882a53 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-errors.yaml @@ -0,0 +1,36 @@ +- message: Trailing spaces not allowed. + line: 5 + column: 2 + suggestions: null +- message: Trailing spaces not allowed. + line: 9 + column: 6 + suggestions: null +- message: Trailing spaces not allowed. + line: 14 + column: 1 + suggestions: null +- message: Trailing spaces not allowed. + line: 23 + column: 12 + suggestions: null +- message: Trailing spaces not allowed. + line: 26 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 27 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 30 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 33 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 34 + column: 23 + suggestions: null diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-input.svelte b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-input.svelte new file mode 100644 index 000000000..560ca0245 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-input.svelte @@ -0,0 +1,35 @@ + + + + + + + Text + + + + Text + + + + Text + + + Text + diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-output.svelte b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-output.svelte new file mode 100644 index 000000000..fd532b434 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/ignoreComments/test01-output.svelte @@ -0,0 +1,35 @@ + + + + + + + Text + + + + Text + + + + Text + + + Text + diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/_config.json b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/_config.json new file mode 100644 index 000000000..2d8ee3650 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/_config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "skipBlankLines": true + } + ] +} diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-errors.yaml b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-errors.yaml new file mode 100644 index 000000000..8cbaf818b --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-errors.yaml @@ -0,0 +1,56 @@ +- message: Trailing spaces not allowed. + line: 5 + column: 2 + suggestions: null +- message: Trailing spaces not allowed. + line: 6 + column: 18 + suggestions: null +- message: Trailing spaces not allowed. + line: 7 + column: 6 + suggestions: null +- message: Trailing spaces not allowed. + line: 8 + column: 19 + suggestions: null +- message: Trailing spaces not allowed. + line: 9 + column: 6 + suggestions: null +- message: Trailing spaces not allowed. + line: 11 + column: 20 + suggestions: null +- message: Trailing spaces not allowed. + line: 18 + column: 5 + suggestions: null +- message: Trailing spaces not allowed. + line: 19 + column: 15 + suggestions: null +- message: Trailing spaces not allowed. + line: 23 + column: 12 + suggestions: null +- message: Trailing spaces not allowed. + line: 26 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 27 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 30 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 33 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 34 + column: 23 + suggestions: null diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-input.svelte b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-input.svelte new file mode 100644 index 000000000..560ca0245 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-input.svelte @@ -0,0 +1,35 @@ + + + + + + + Text + + + + Text + + + + Text + + + Text + diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-output.svelte b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-output.svelte new file mode 100644 index 000000000..0ffc6b7fd --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/skipBlankLines/test01-output.svelte @@ -0,0 +1,35 @@ + + + + + + + Text + + + + Text + + + + Text + + + Text + diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/test01-errors.yaml b/tests/fixtures/rules/no-trailing-spaces/invalid/test01-errors.yaml new file mode 100644 index 000000000..5aecca0f6 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/test01-errors.yaml @@ -0,0 +1,60 @@ +- message: Trailing spaces not allowed. + line: 5 + column: 2 + suggestions: null +- message: Trailing spaces not allowed. + line: 6 + column: 18 + suggestions: null +- message: Trailing spaces not allowed. + line: 7 + column: 6 + suggestions: null +- message: Trailing spaces not allowed. + line: 8 + column: 19 + suggestions: null +- message: Trailing spaces not allowed. + line: 9 + column: 6 + suggestions: null +- message: Trailing spaces not allowed. + line: 11 + column: 20 + suggestions: null +- message: Trailing spaces not allowed. + line: 14 + column: 1 + suggestions: null +- message: Trailing spaces not allowed. + line: 18 + column: 5 + suggestions: null +- message: Trailing spaces not allowed. + line: 19 + column: 15 + suggestions: null +- message: Trailing spaces not allowed. + line: 23 + column: 12 + suggestions: null +- message: Trailing spaces not allowed. + line: 26 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 27 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 30 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 33 + column: 7 + suggestions: null +- message: Trailing spaces not allowed. + line: 34 + column: 23 + suggestions: null diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/test01-input.svelte b/tests/fixtures/rules/no-trailing-spaces/invalid/test01-input.svelte new file mode 100644 index 000000000..560ca0245 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/test01-input.svelte @@ -0,0 +1,35 @@ + + + + + + + Text + + + + Text + + + + Text + + + Text + diff --git a/tests/fixtures/rules/no-trailing-spaces/invalid/test01-output.svelte b/tests/fixtures/rules/no-trailing-spaces/invalid/test01-output.svelte new file mode 100644 index 000000000..ff13f70f3 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/invalid/test01-output.svelte @@ -0,0 +1,35 @@ + + + + + + + Text + + + + Text + + + + Text + + + Text + diff --git a/tests/fixtures/rules/no-trailing-spaces/valid/test01-input.svelte b/tests/fixtures/rules/no-trailing-spaces/valid/test01-input.svelte new file mode 100644 index 000000000..0cef4d689 --- /dev/null +++ b/tests/fixtures/rules/no-trailing-spaces/valid/test01-input.svelte @@ -0,0 +1,30 @@ + + + + + + Text + + + + Text + + + + Text diff --git a/tests/src/rules/no-trailing-spaces.ts b/tests/src/rules/no-trailing-spaces.ts new file mode 100644 index 000000000..a8607d380 --- /dev/null +++ b/tests/src/rules/no-trailing-spaces.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/no-trailing-spaces" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "no-trailing-spaces", + rule as any, + loadTestCases("no-trailing-spaces"), +)