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"),
+)