Repository: ota-meshi/eslint-plugin-svelte
Pull request #186 (2022-07-12 23:03:43)

feat: add `svelte/html-closing-bracket-spacing`
Added `html-closing-bracket-spacing` rule, which can be seen in [`eslint-plugin-vue`](https://github.com/vuejs/eslint-plugin-vue)

## Related PRs

feat: add `svelte/html-self-closing` #190 
feat: add `svelte/no-spaces-around-equal-signs-in-attribute` #191

## `svelte/html-closing-bracket-spacing`
A simple rule for enforcing a space before closing a tag
```html
<div/>
<!-- gets fixed to -->
<div />
```

## Coverage
100% test coverage.

## ~~Notes & questions~~
* ~~This is my first time contributing to this project and creating eslint plugins in general, so I'm sorry if some parts of my code aren't as good.~~
* ~~How should I document the rules? Is there a script to automatically generate the documentation, or should I create a new markdown file inside `docs/rules`? (This information should probably be added to contributing section in README)~~

ota-meshi: Thank you for this PR!
You need to add documents that describes the rules you added. After adding the files, run `yarn update` to update the header and footer.
marekvospel: @ota-meshi I've added the docs, fixed the merge conflicts and both the docs build and lint should be passing now!
ota-meshi: Thank you for adding the documentation! I'm busy this week so I'll probably check it next week.
marekvospel: I had extra time today, so I have added `svelte/html-closing-bracket-spacing` rule!
```html
<div/>
# gets fixed to
<div />
```
It's similar to to `svelte/no-spaces-around-equal-signs-in-attribute `, but only has to check the end of the tag, so it's a bit simpler rule.

The test also has 100% coverage

I've also fixed some minor problems in my docs (such as since version being 0.2.2, which is already released, or mistyped `<div />` 

Patch:
diff --git a/README.md b/README.md
index b2e746e..339c54c 100644
--- a/README.md
+++ b/README.md
@@ -292,10 +292,12 @@ These rules relate to style guidelines, and are therefore quite subjective:
 | Rule ID | Description |    |
 |:--------|:------------|:---|
 | [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-closing-bracket-spacing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-closing-bracket-spacing/) | require or disallow a space before tag's closing brackets | :wrench: |
 | [svelte/html-quotes](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-quotes/) | enforce quotes style of HTML attributes | :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: |
+| [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-svelte-kit/src/lib/components/ESLintPlayground.svelte b/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte
index 2cdf76e..42460c9 100644
--- a/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte
+++ b/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte
@@ -211,6 +211,10 @@
 </div>
 
 <style>
+  :global(.main-content) {
+    max-width: 100% !important;
+  }
+
   .playground-root {
     height: calc(100vh - 180px);
   }
diff --git a/docs/rules.md b/docs/rules.md
index 70a940c..900cdcc 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -52,10 +52,12 @@ These rules relate to style guidelines, and are therefore quite subjective:
 | Rule ID | Description |    |
 |:--------|:------------|:---|
 | [svelte/first-attribute-linebreak](./rules/first-attribute-linebreak.md) | enforce the location of first attribute | :wrench: |
+| [svelte/html-closing-bracket-spacing](./rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets | :wrench: |
 | [svelte/html-quotes](./rules/html-quotes.md) | enforce quotes style of HTML attributes | :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: |
+| [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/html-closing-bracket-spacing.md b/docs/rules/html-closing-bracket-spacing.md
new file mode 100644
index 0000000..b514cdc
--- /dev/null
+++ b/docs/rules/html-closing-bracket-spacing.md
@@ -0,0 +1,77 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/html-closing-bracket-spacing"
+description: "require or disallow a space before tag's closing brackets"
+---
+
+# svelte/html-closing-bracket-spacing
+
+> require or disallow a space before tag's closing brackets
+
+- :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
+
+You can choose either two styles for spacing before closing bracket
+
+- always: `<div />`
+- never: `<div/>`
+
+<ESLintCodeBlock fix>
+
+<!-- prettier-ignore-start -->
+<!--eslint-skip-->
+
+```svelte
+<script>
+  /* eslint svelte/html-closing-bracket-spacing: "error" */
+</script>
+
+<!-- ✓ GOOD -->
+<div />
+<p>Hello</p>
+<div
+ >
+</div>
+
+<!-- ✗ BAD -->
+<div/>
+<p >Hello</p >
+<div  >
+</div >
+```
+
+<!-- prettier-ignore-end -->
+
+</ESLintCodeBlock>
+
+## :wrench: Options
+
+```json
+{
+  "svelte/html-closing-bracket-spacing": [
+    "error",
+    {
+      "startTag": "never", // or "always" or "ignore"
+      "endTag": "never", // or "always" or "ignore"
+      "selfClosingTag": "always" // or "never" or "ignore"
+    }
+  ]
+}
+```
+
+- `startTag` (`"never"` by default)... Spacing in start tags
+- `endTag` (`"never"` by default)... Spacing in end tags
+- `selfClosingTag` (`"always"` by default)... Spacing in self closing tags
+
+Every option can be set to
+- "always" (`<div />`)
+- "never" (`<div/>`)
+- "ignore" (either `<div />` or `<div/>`)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/html-closing-bracket-spacing.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/html-closing-bracket-spacing.ts)
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 881be7b..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,9 +106,9 @@
     "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": "^2.1.0",
+    "eslint-plugin-markdown": "^3.0.0",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-node-dependencies": "^0.9.0",
     "eslint-plugin-prettier": "^4.0.0",
diff --git a/src/configs/prettier.ts b/src/configs/prettier.ts
index 6c0eba3..756aba2 100644
--- a/src/configs/prettier.ts
+++ b/src/configs/prettier.ts
@@ -7,10 +7,12 @@ export = {
   rules: {
     // eslint-plugin-svelte rules
     "svelte/first-attribute-linebreak": "off",
+    "svelte/html-closing-bracket-spacing": "off",
     "svelte/html-quotes": "off",
     "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/html-closing-bracket-spacing.ts b/src/rules/html-closing-bracket-spacing.ts
new file mode 100644
index 0000000..a69b163
--- /dev/null
+++ b/src/rules/html-closing-bracket-spacing.ts
@@ -0,0 +1,109 @@
+import { createRule } from "../utils"
+import type { AST } from "svelte-eslint-parser"
+
+export default createRule("html-closing-bracket-spacing", {
+  meta: {
+    docs: {
+      description: "require or disallow a space before tag's closing brackets",
+      category: "Stylistic Issues",
+      conflictWithPrettier: true,
+      recommended: false,
+    },
+    schema: [
+      {
+        type: "object",
+        properties: {
+          startTag: {
+            enum: ["always", "never", "ignore"],
+          },
+          endTag: {
+            enum: ["always", "never", "ignore"],
+          },
+          selfClosingTag: {
+            enum: ["always", "never", "ignore"],
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
+    messages: {
+      expectedSpace: "Expected space before '>', but not found.",
+      unexpectedSpace: "Expected no space before '>', but found.",
+    },
+    fixable: "whitespace",
+    type: "layout",
+  },
+  create(ctx) {
+    const options = {
+      startTag: "never",
+      endTag: "never",
+      selfClosingTag: "always",
+      ...ctx.options[0],
+    }
+    const src = ctx.getSourceCode()
+
+    /**
+     * Returns true if string contains newline characters
+     */
+    function containsNewline(string: string): boolean {
+      return string.includes("\n")
+    }
+
+    /**
+     * Report
+     */
+    function report(
+      node: AST.SvelteStartTag | AST.SvelteEndTag,
+      shouldHave: boolean,
+    ) {
+      const tagSrc = src.getText(node)
+      const match = /(\s*)\/?>$/.exec(tagSrc)
+
+      const end = node.range[1]
+      const start = node.range[1] - match![0].length
+      const loc = {
+        start: src.getLocFromIndex(start),
+        end: src.getLocFromIndex(end),
+      }
+
+      ctx.report({
+        loc,
+        messageId: shouldHave ? "expectedSpace" : "unexpectedSpace",
+        *fix(fixer) {
+          if (shouldHave) {
+            yield fixer.insertTextBeforeRange([start, end], " ")
+          } else {
+            const spaces = match![1]
+
+            yield fixer.removeRange([start, start + spaces.length])
+          }
+        },
+      })
+    }
+
+    return {
+      "SvelteStartTag, SvelteEndTag"(
+        node: AST.SvelteStartTag | AST.SvelteEndTag,
+      ) {
+        const tagType =
+          node.type === "SvelteEndTag"
+            ? "endTag"
+            : node.selfClosing
+            ? "selfClosingTag"
+            : "startTag"
+
+        if (options[tagType] === "ignore") return
+
+        const tagSrc = src.getText(node)
+        const match = /(\s*)\/?>$/.exec(tagSrc)
+        if (containsNewline(match![1])) return
+
+        if (options[tagType] === "always" && !match![1]) {
+          report(node, true)
+        } else if (options[tagType] === "never" && match![1]) {
+          report(node, false)
+        }
+      },
+    }
+  },
+})
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..8a2b068 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -2,6 +2,7 @@ import type { RuleModule } from "../types"
 import buttonHasType from "../rules/button-has-type"
 import commentDirective from "../rules/comment-directive"
 import firstAttributeLinebreak from "../rules/first-attribute-linebreak"
+import htmlClosingBracketSpacing from "../rules/html-closing-bracket-spacing"
 import htmlQuotes from "../rules/html-quotes"
 import indent from "../rules/indent"
 import maxAttributesPerLine from "../rules/max-attributes-per-line"
@@ -15,6 +16,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"
@@ -32,6 +34,7 @@ export const rules = [
   buttonHasType,
   commentDirective,
   firstAttributeLinebreak,
+  htmlClosingBracketSpacing,
   htmlQuotes,
   indent,
   maxAttributesPerLine,
@@ -45,6 +48,7 @@ export const rules = [
   noNotFunctionHandler,
   noObjectInTextMustaches,
   noShorthandStylePropertyOverrides,
+  noSpacesAroundEqualSignsInAttribute,
   noTargetBlank,
   noUnknownStyleDirectiveProperty,
   noUnusedSvelteIgnore,


Test patch:
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/_config.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/_config.json
new file mode 100644
index 0000000..e5e1e4d
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/_config.json
@@ -0,0 +1,7 @@
+{
+  "options": [
+    {
+      "selfClosingTag": "ignore"
+    }
+  ]
+}
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-errors.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-errors.json
new file mode 100644
index 0000000..8e0d94b
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-errors.json
@@ -0,0 +1,12 @@
+[
+  {
+    "message": "Expected no space before '>', but found.",
+    "line": 2,
+    "column": 3
+  },
+  {
+    "message": "Expected no space before '>', but found.",
+    "line": 2,
+    "column": 14
+  }
+]
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-input.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-input.svelte
new file mode 100644
index 0000000..0aa1e17
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-input.svelte
@@ -0,0 +1,5 @@
+<!-- prettier-ignore -->
+<p  >Hello</p  >
+<!-- prettier-ignore -->
+<div/>
+<div />
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-output.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-output.svelte
new file mode 100644
index 0000000..ee45233
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/closing-ignore/closing-ignore-output.svelte
@@ -0,0 +1,5 @@
+<!-- prettier-ignore -->
+<p>Hello</p>
+<!-- prettier-ignore -->
+<div/>
+<div />
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/_config.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/_config.json
new file mode 100644
index 0000000..c2b607b
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/_config.json
@@ -0,0 +1,7 @@
+{
+  "options": [
+    {
+      "endTag": "ignore"
+    }
+  ]
+}
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-errors.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-errors.json
new file mode 100644
index 0000000..130ab5d
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-errors.json
@@ -0,0 +1,12 @@
+[
+  {
+    "message": "Expected no space before '>', but found.",
+    "line": 2,
+    "column": 3
+  },
+  {
+    "message": "Expected space before '>', but not found.",
+    "line": 5,
+    "column": 5
+  }
+]
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-input.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-input.svelte
new file mode 100644
index 0000000..97408e4
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-input.svelte
@@ -0,0 +1,5 @@
+<!-- prettier-ignore -->
+<p  >Hello</p  >
+<p>Hi</p>
+<!-- prettier-ignore -->
+<div/>
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-output.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-output.svelte
new file mode 100644
index 0000000..8b8916c
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/end-ignore/end-ignore-output.svelte
@@ -0,0 +1,5 @@
+<!-- prettier-ignore -->
+<p>Hello</p  >
+<p>Hi</p>
+<!-- prettier-ignore -->
+<div />
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/_config.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/_config.json
new file mode 100644
index 0000000..e0a9668
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/_config.json
@@ -0,0 +1,7 @@
+{
+  "options": [
+    {
+      "startTag": "ignore"
+    }
+  ]
+}
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-errors.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-errors.json
new file mode 100644
index 0000000..bb1c24a
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-errors.json
@@ -0,0 +1,12 @@
+[
+  {
+    "message": "Expected no space before '>', but found.",
+    "line": 2,
+    "column": 14
+  },
+  {
+    "message": "Expected space before '>', but not found.",
+    "line": 5,
+    "column": 5
+  }
+]
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-input.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-input.svelte
new file mode 100644
index 0000000..97408e4
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-input.svelte
@@ -0,0 +1,5 @@
+<!-- prettier-ignore -->
+<p  >Hello</p  >
+<p>Hi</p>
+<!-- prettier-ignore -->
+<div/>
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-output.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-output.svelte
new file mode 100644
index 0000000..644df5d
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/start-ignore/start-ignore-output.svelte
@@ -0,0 +1,5 @@
+<!-- prettier-ignore -->
+<p  >Hello</p>
+<p>Hi</p>
+<!-- prettier-ignore -->
+<div />
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-errors.json b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-errors.json
new file mode 100644
index 0000000..bababc3
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-errors.json
@@ -0,0 +1,17 @@
+[
+  {
+    "message": "Expected no space before '>', but found.",
+    "line": 2,
+    "column": 3
+  },
+  {
+    "message": "Expected no space before '>', but found.",
+    "line": 2,
+    "column": 14
+  },
+  {
+    "message": "Expected space before '>', but not found.",
+    "line": 4,
+    "column": 5
+  }
+]
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-input.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-input.svelte
new file mode 100644
index 0000000..7b0b711
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-input.svelte
@@ -0,0 +1,10 @@
+<!-- prettier-ignore -->
+<p  >Hello</p  >
+<!-- prettier-ignore -->
+<div/>
+<!-- prettier-ignore -->
+<div
+>
+</div
+
+>
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-output.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-output.svelte
new file mode 100644
index 0000000..2cbd696
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/invalid/test-01-output.svelte
@@ -0,0 +1,10 @@
+<!-- prettier-ignore -->
+<p>Hello</p>
+<!-- prettier-ignore -->
+<div />
+<!-- prettier-ignore -->
+<div
+>
+</div
+
+>
diff --git a/tests/fixtures/rules/html-closing-bracket-spacing/valid/test-01-input.svelte b/tests/fixtures/rules/html-closing-bracket-spacing/valid/test-01-input.svelte
new file mode 100644
index 0000000..e40adb0
--- /dev/null
+++ b/tests/fixtures/rules/html-closing-bracket-spacing/valid/test-01-input.svelte
@@ -0,0 +1,11 @@
+<p>Hello</p>
+<div />
+<!-- prettier-ignore -->
+<div
+  />
+<!-- prettier-ignore -->
+<div
+  >
+</div
+
+>
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/html-closing-bracket-spacing.ts b/tests/src/rules/html-closing-bracket-spacing.ts
new file mode 100644
index 0000000..80cbe45
--- /dev/null
+++ b/tests/src/rules/html-closing-bracket-spacing.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/html-closing-bracket-spacing"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: "module",
+  },
+})
+
+tester.run(
+  "html-closing-bracket-spacing",
+  rule as any,
+  loadTestCases("html-closing-bracket-spacing"),
+)
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"),
+)