Repository: ota-meshi/eslint-plugin-svelte
Pull request #191 (2022-07-21 09:46:03)

feat: add `svelte/no-spaces-around-equal-signs-in-attribute`
Moved to separate PR from ota-meshi/eslint-plugin-svelte#186

## `svelte/no-spaces-around-equal-signs-in-attribute`
Also a simple rule which automatically removes spaces around attribute equal signs
```html
<div class ="test" />
<!-- gets fixed to -->
<div class="test" />
```
I had difficulties with this rule, as `SvelteAttribute` has only key and value, but with some regex magic the rule should work.

## Coverage
100% test coverage.


Patch:
diff --git a/README.md b/README.md
index b2e746e..cf840ce 100644
--- a/README.md
+++ b/README.md
@@ -296,6 +296,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
 | [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: |
+| [svelte/no-spaces-around-equal-signs-in-attribute](https://ota-meshi.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://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: |
 | [svelte/prefer-style-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: |
 | [svelte/shorthand-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: |
diff --git a/docs/rules.md b/docs/rules.md
index 70a940c..21f7a79 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -56,6 +56,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
 | [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: |
+| [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: |
 | [svelte/shorthand-attribute](./rules/shorthand-attribute.md) | enforce use of shorthand syntax in attribute | :wrench: |
diff --git a/docs/rules/no-spaces-around-equal-signs-in-attribute.md b/docs/rules/no-spaces-around-equal-signs-in-attribute.md
new file mode 100644
index 0000000..f577ce8
--- /dev/null
+++ b/docs/rules/no-spaces-around-equal-signs-in-attribute.md
@@ -0,0 +1,57 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/no-spaces-around-equal-signs-in-attribute"
+description: "disallow spaces around equal signs in attribute"
+---
+
+# svelte/no-spaces-around-equal-signs-in-attribute
+
+> disallow spaces around equal signs in attribute
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
+- :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 disallows spaces around equal signs in attributes
+
+<ESLintCodeBlock fix>
+
+<!-- prettier-ignore-start -->
+<!--eslint-skip-->
+
+```svelte
+<script>
+  /* eslint svelte/no-spaces-around-equal-signs-in-attribute: "error" */
+</script>
+
+<!-- ✓ GOOD -->
+<div class=""/>
+<p style="color: red;">hi</p>
+<img src="img.png" alt="A photo of a very cute {animal}">
+
+<!-- ✗ BAD -->
+<div class = ""/>
+<p style ="color: red;">hi</p>
+<img src
+    =
+    "img.png" alt   = "A photo of a very cute {animal}">
+```
+
+<!-- prettier-ignore-end -->
+
+</ESLintCodeBlock>
+
+## :wrench: Options
+
+```json
+{
+  "svelte/no-spaces-around-equal-signs-in-attribute": ["error"]
+}
+```
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-spaces-around-equal-signs-in-attribute.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-spaces-around-equal-signs-in-attribute.ts)
diff --git a/package.json b/package.json
index de598ee..91ed6ef 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
     "postcss-load-config": "^3.1.4",
     "postcss-safe-parser": "^6.0.0",
     "sourcemap-codec": "^1.4.8",
-    "svelte-eslint-parser": "^0.16.0"
+    "svelte-eslint-parser": "^0.17.0"
   },
   "devDependencies": {
     "@babel/core": "^7.16.0",
@@ -106,7 +106,7 @@
     "eslint-config-prettier": "^8.3.0",
     "eslint-plugin-eslint-comments": "^3.2.0",
     "eslint-plugin-eslint-plugin": "^5.0.0",
-    "eslint-plugin-json-schema-validator": "^3.0.0",
+    "eslint-plugin-json-schema-validator": "^4.0.0",
     "eslint-plugin-jsonc": "^2.0.0",
     "eslint-plugin-markdown": "^3.0.0",
     "eslint-plugin-node": "^11.1.0",
diff --git a/src/configs/prettier.ts b/src/configs/prettier.ts
index 6c0eba3..58795f6 100644
--- a/src/configs/prettier.ts
+++ b/src/configs/prettier.ts
@@ -11,6 +11,7 @@ export = {
     "svelte/indent": "off",
     "svelte/max-attributes-per-line": "off",
     "svelte/mustache-spacing": "off",
+    "svelte/no-spaces-around-equal-signs-in-attribute": "off",
     "svelte/shorthand-attribute": "off",
     "svelte/shorthand-directive": "off",
   },
diff --git a/src/rules/no-spaces-around-equal-signs-in-attribute.ts b/src/rules/no-spaces-around-equal-signs-in-attribute.ts
new file mode 100644
index 0000000..a0256f2
--- /dev/null
+++ b/src/rules/no-spaces-around-equal-signs-in-attribute.ts
@@ -0,0 +1,74 @@
+import { createRule } from "../utils"
+import type { AST } from "svelte-eslint-parser"
+
+export default createRule("no-spaces-around-equal-signs-in-attribute", {
+  meta: {
+    docs: {
+      description: "disallow spaces around equal signs in attribute",
+      category: "Stylistic Issues",
+      recommended: false,
+      conflictWithPrettier: true,
+    },
+    schema: {},
+    fixable: "whitespace",
+    messages: {
+      noSpaces: "Unexpected spaces found around equal signs.",
+    },
+    type: "layout",
+  },
+  create(ctx) {
+    const source = ctx.getSourceCode()
+
+    /**
+     * Returns source text between attribute key and value, and range of that source
+     */
+    function getAttrEq(
+      node:
+        | AST.SvelteAttribute
+        | AST.SvelteDirective
+        | AST.SvelteStyleDirective
+        | AST.SvelteSpecialDirective,
+    ): [string, AST.Range] {
+      const keyRange = node.key.range
+      const eqSource = /^[\s=]*/u.exec(
+        source.text.slice(keyRange[1], node.range[1]),
+      )![0]
+      const valueStart = keyRange[1] + eqSource.length
+      return [eqSource, [keyRange[1], valueStart]]
+    }
+
+    /**
+     * Returns true if string contains whitespace characters
+     */
+    function containsWhitespace(string: string): boolean {
+      return /\s/u.test(string)
+    }
+
+    return {
+      "SvelteAttribute, SvelteDirective, SvelteStyleDirective, SvelteSpecialDirective"(
+        node:
+          | AST.SvelteAttribute
+          | AST.SvelteDirective
+          | AST.SvelteStyleDirective
+          | AST.SvelteSpecialDirective,
+      ) {
+        const [eqSource, range] = getAttrEq(node)
+
+        if (!containsWhitespace(eqSource)) return
+
+        const loc = {
+          start: source.getLocFromIndex(range[0]),
+          end: source.getLocFromIndex(range[1]),
+        }
+
+        ctx.report({
+          loc,
+          messageId: "noSpaces",
+          *fix(fixer) {
+            yield fixer.replaceTextRange(range, "=")
+          },
+        })
+      },
+    }
+  },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index 19fe075..fad23ea 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -15,6 +15,7 @@ import noInnerDeclarations from "../rules/no-inner-declarations"
 import noNotFunctionHandler from "../rules/no-not-function-handler"
 import noObjectInTextMustaches from "../rules/no-object-in-text-mustaches"
 import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides"
+import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute"
 import noTargetBlank from "../rules/no-target-blank"
 import noUnknownStyleDirectiveProperty from "../rules/no-unknown-style-directive-property"
 import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore"
@@ -45,6 +46,7 @@ export const rules = [
   noNotFunctionHandler,
   noObjectInTextMustaches,
   noShorthandStylePropertyOverrides,
+  noSpacesAroundEqualSignsInAttribute,
   noTargetBlank,
   noUnknownStyleDirectiveProperty,
   noUnusedSvelteIgnore,


Test patch:
diff --git a/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-errors.json b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-errors.json
new file mode 100644
index 0000000..13e58e4
--- /dev/null
+++ b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-errors.json
@@ -0,0 +1,62 @@
+[
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 3,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 4,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 5,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 6,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 8,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 10,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 11,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 12,
+    "column": 11
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 13,
+    "column": 15
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 13,
+    "column": 35
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 14,
+    "column": 17
+  },
+  {
+    "message": "Unexpected spaces found around equal signs.",
+    "line": 16,
+    "column": 10
+  }
+]
diff --git a/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-input.svelte b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-input.svelte
new file mode 100644
index 0000000..fe93f64
--- /dev/null
+++ b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-input.svelte
@@ -0,0 +1,17 @@
+<!-- prettier-ignore -->
+<div>
+  <p class = "h"></p>
+  <p class ="e"></p>
+  <p class=   "l"></p>
+  <p class=
+       "l"></p>
+  <p class
+       = "o"></p>
+  <p class= "="></p>
+  <p class= a></p>
+  <p class= a></p>
+  <p bind:test= {value} bind:test2  = {value} />
+  <p style:width ="10px" />
+  <!-- prettier-ignore -->
+  <p this = {expression} />
+</div>
diff --git a/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-output.svelte b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-output.svelte
new file mode 100644
index 0000000..adbfdf3
--- /dev/null
+++ b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/invalid/test01-output.svelte
@@ -0,0 +1,15 @@
+<!-- prettier-ignore -->
+<div>
+  <p class="h"></p>
+  <p class="e"></p>
+  <p class="l"></p>
+  <p class="l"></p>
+  <p class="o"></p>
+  <p class="="></p>
+  <p class=a></p>
+  <p class=a></p>
+  <p bind:test={value} bind:test2={value} />
+  <p style:width="10px" />
+  <!-- prettier-ignore -->
+  <p this={expression} />
+</div>
diff --git a/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/valid/test-01-input.svelte b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/valid/test-01-input.svelte
new file mode 100644
index 0000000..f3760fe
--- /dev/null
+++ b/tests/fixtures/rules/no-spaces-around-equal-signs-in-attribute/valid/test-01-input.svelte
@@ -0,0 +1,4 @@
+<p class="test" style="" bind:test={value} />
+<p class="test2" style style:width="10px" />
+<!-- prettier-ignore -->
+<p class=p this={expression} />
diff --git a/tests/src/rules/no-spaces-around-equal-signs-in-attribute.ts b/tests/src/rules/no-spaces-around-equal-signs-in-attribute.ts
new file mode 100644
index 0000000..edd7fe8
--- /dev/null
+++ b/tests/src/rules/no-spaces-around-equal-signs-in-attribute.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/no-spaces-around-equal-signs-in-attribute"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: "module",
+  },
+})
+
+tester.run(
+  "no-spaces-around-equal-signs-in-attribute",
+  rule as any,
+  loadTestCases("no-spaces-around-equal-signs-in-attribute"),
+)