diff --git a/README.md b/README.md
index c3a2bb31d..7c624e732 100644
--- a/README.md
+++ b/README.md
@@ -286,6 +286,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
| [@ota-meshi/svelte/prefer-class-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: |
| [@ota-meshi/svelte/prefer-style-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: |
| [@ota-meshi/svelte/shorthand-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: |
+| [@ota-meshi/svelte/shorthand-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-directive/) | enforce use of shorthand syntax in directives | :wrench: |
| [@ota-meshi/svelte/spaced-html-comment](https://ota-meshi.github.io/eslint-plugin-svelte/rules/spaced-html-comment/) | enforce consistent spacing after the `` in a HTML comment | :wrench: |
## Extension Rules
diff --git a/docs/rules.md b/docs/rules.md
index e6ba92f76..0c39ccc66 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -55,6 +55,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
| [@ota-meshi/svelte/prefer-class-directive](./rules/prefer-class-directive.md) | require class directives instead of ternary expressions | :wrench: |
| [@ota-meshi/svelte/prefer-style-directive](./rules/prefer-style-directive.md) | require style directives instead of style attribute | :wrench: |
| [@ota-meshi/svelte/shorthand-attribute](./rules/shorthand-attribute.md) | enforce use of shorthand syntax in attribute | :wrench: |
+| [@ota-meshi/svelte/shorthand-directive](./rules/shorthand-directive.md) | enforce use of shorthand syntax in directives | :wrench: |
| [@ota-meshi/svelte/spaced-html-comment](./rules/spaced-html-comment.md) | enforce consistent spacing after the `` in a HTML comment | :wrench: |
## Extension Rules
diff --git a/docs/rules/shorthand-attribute.md b/docs/rules/shorthand-attribute.md
index 4d6606a68..93573afee 100644
--- a/docs/rules/shorthand-attribute.md
+++ b/docs/rules/shorthand-attribute.md
@@ -54,6 +54,12 @@ This rule enforces the use of the shorthand syntax in attribute.
- `"always"` ... Expects that the shorthand will be used whenever possible. This is default.
- `"never"` ... Ensures that no shorthand is used in any attribute.
+## :couple: Related Rules
+
+- [@ota-meshi/svelte/shorthand-directive]
+
+[@ota-meshi/svelte/shorthand-directive]: ./shorthand-directive.md
+
## :rocket: Version
This rule was introduced in @ota-meshi/eslint-plugin-svelte v0.5.0
diff --git a/docs/rules/shorthand-directive.md b/docs/rules/shorthand-directive.md
new file mode 100644
index 000000000..a7b723d38
--- /dev/null
+++ b/docs/rules/shorthand-directive.md
@@ -0,0 +1,73 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "@ota-meshi/svelte/shorthand-directive"
+description: "enforce use of shorthand syntax in directives"
+---
+
+# @ota-meshi/svelte/shorthand-directive
+
+> enforce use of shorthand syntax in directives
+
+- :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 enforces the use of the shorthand syntax in directives.
+
+
+
+
+
+
+```svelte
+
+
+
+
+...
+...
+
+
+
+...
+...
+```
+
+
+
+
+
+## :wrench: Options
+
+```json
+{
+ "@ota-meshi/svelte/shorthand-directive": [
+ "error",
+ {
+ "prefer": "always" // "never"
+ }
+ ]
+}
+```
+
+- `prefer`
+ - `"always"` ... Expects that the shorthand will be used whenever possible. This is default.
+ - `"never"` ... Ensures that no shorthand is used in any directive.
+
+## :couple: Related Rules
+
+- [@ota-meshi/svelte/shorthand-attribute]
+
+[@ota-meshi/svelte/shorthand-attribute]: ./shorthand-directive.md
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/shorthand-directive.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/shorthand-directive.ts)
diff --git a/src/rules/shorthand-directive.ts b/src/rules/shorthand-directive.ts
new file mode 100644
index 000000000..d9691f29e
--- /dev/null
+++ b/src/rules/shorthand-directive.ts
@@ -0,0 +1,126 @@
+import type { AST } from "svelte-eslint-parser"
+import { createRule } from "../utils"
+import { getAttributeValueQuoteAndRange } from "../utils/ast-utils"
+
+export default createRule("shorthand-directive", {
+ meta: {
+ docs: {
+ description: "enforce use of shorthand syntax in directives",
+ category: "Stylistic Issues",
+ recommended: false,
+ },
+ fixable: "code",
+ schema: [
+ {
+ type: "object",
+ properties: {
+ prefer: { enum: ["always", "never"] },
+ },
+ additionalProperties: false,
+ },
+ ],
+ messages: {
+ expectedShorthand: "Expected shorthand directive.",
+ expectedRegular: "Expected regular directive syntax.",
+ },
+ type: "layout",
+ },
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const always: boolean = context.options[0]?.prefer !== "never"
+
+ /** Report for always */
+ function reportForAlways(
+ node: AST.SvelteDirective | AST.SvelteStyleDirective,
+ ) {
+ context.report({
+ node,
+ messageId: "expectedShorthand",
+ *fix(fixer) {
+ const quoteAndRange = getAttributeValueQuoteAndRange(node, sourceCode)
+ if (quoteAndRange) {
+ yield fixer.remove(
+ sourceCode.getTokenBefore(quoteAndRange.firstToken)!,
+ )
+ yield fixer.removeRange(quoteAndRange.range)
+ }
+ },
+ })
+ }
+
+ /** Report for never */
+ function reportForNever(
+ node: AST.SvelteDirective | AST.SvelteStyleDirective,
+ ) {
+ context.report({
+ node,
+ messageId: "expectedRegular",
+ *fix(fixer) {
+ yield fixer.insertTextAfter(node.key.name, `={${node.key.name.name}}`)
+ },
+ })
+ }
+
+ return {
+ SvelteDirective(node) {
+ if (node.kind !== "Binding" && node.kind !== "Class") {
+ return
+ }
+
+ const expression = node.expression
+ if (!expression) {
+ // Invalid ?
+ return
+ }
+ if (
+ expression.type !== "Identifier" ||
+ node.key.name.name !== expression.name
+ ) {
+ // Cannot use shorthand
+ return
+ }
+ if (always) {
+ if (node.key.name.range![0] === expression.range![0]) {
+ // Use shorthand
+ return
+ }
+ reportForAlways(node)
+ } else {
+ if (node.key.name.range![1] < expression.range![0]) {
+ // Use longform
+ return
+ }
+ reportForNever(node)
+ }
+ },
+ SvelteStyleDirective(node) {
+ if (always) {
+ if (node.shorthand) {
+ // Use shorthand
+ return
+ }
+ if (node.value.length !== 1) {
+ // Cannot use shorthand
+ return
+ }
+ const expression = node.value[0]
+ if (
+ expression.type !== "SvelteMustacheTag" ||
+ expression.expression.type !== "Identifier" ||
+ expression.expression.name !== node.key.name.name
+ ) {
+ // Cannot use shorthand
+ return
+ }
+ reportForAlways(node)
+ } else {
+ if (!node.shorthand) {
+ // Use longform
+ return
+ }
+ reportForNever(node)
+ }
+ },
+ }
+ },
+})
diff --git a/src/utils/ast-utils.ts b/src/utils/ast-utils.ts
index 0e3cadce6..89ec22508 100644
--- a/src/utils/ast-utils.ts
+++ b/src/utils/ast-utils.ts
@@ -233,6 +233,8 @@ export function getScope(
export type QuoteAndRange = {
quote: "unquoted" | "double" | "single"
range: [number, number]
+ firstToken: SvAST.Token | SvAST.Comment
+ lastToken: SvAST.Token | SvAST.Comment
}
/** Get the quote and range from given attribute values */
export function getAttributeValueQuoteAndRange(
@@ -262,6 +264,8 @@ export function getAttributeValueQuoteAndRange(
return {
quote: "unquoted",
range: [valueFirstToken.range[0], valueLastToken.range[1]],
+ firstToken: valueFirstToken,
+ lastToken: valueLastToken,
}
} else if (
beforeTokens.length > 1 ||
@@ -280,6 +284,8 @@ export function getAttributeValueQuoteAndRange(
return {
quote: beforeToken.value === '"' ? "double" : "single",
range: [beforeToken.range[0], afterToken.range[1]],
+ firstToken: beforeToken,
+ lastToken: afterToken,
}
}
export function getMustacheTokens(
@@ -381,11 +387,11 @@ function getAttributeValueRangeTokens(
if (!attr.value.length) {
return null
}
- const firstToken = attr.value[0]
- const lastToken = attr.value[attr.value.length - 1]
+ const first = attr.value[0]
+ const last = attr.value[attr.value.length - 1]
return {
- firstToken,
- lastToken,
+ firstToken: sourceCode.getFirstToken(first),
+ lastToken: sourceCode.getLastToken(last),
}
}
const tokens = getMustacheTokens(attr, sourceCode)
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index d0b924678..b6cd1b252 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -20,6 +20,7 @@ import noUselessMustaches from "../rules/no-useless-mustaches"
import preferClassDirective from "../rules/prefer-class-directive"
import preferStyleDirective from "../rules/prefer-style-directive"
import shorthandAttribute from "../rules/shorthand-attribute"
+import shorthandDirective from "../rules/shorthand-directive"
import spacedHtmlComment from "../rules/spaced-html-comment"
import system from "../rules/system"
import validCompile from "../rules/valid-compile"
@@ -46,6 +47,7 @@ export const rules = [
preferClassDirective,
preferStyleDirective,
shorthandAttribute,
+ shorthandDirective,
spacedHtmlComment,
system,
validCompile,
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/_config.json b/tests/fixtures/rules/shorthand-directive/invalid/always/_config.json
new file mode 100644
index 000000000..78d5d8817
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/always/_config.json
@@ -0,0 +1,3 @@
+{
+ "options": [{ "prefer": "always" }]
+}
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json
new file mode 100644
index 000000000..2547511ae
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-errors.json
@@ -0,0 +1,27 @@
+[
+ {
+ "message": "Expected shorthand directive.",
+ "line": 14,
+ "column": 8
+ },
+ {
+ "message": "Expected shorthand directive.",
+ "line": 16,
+ "column": 6
+ },
+ {
+ "message": "Expected shorthand directive.",
+ "line": 18,
+ "column": 6
+ },
+ {
+ "message": "Expected shorthand directive.",
+ "line": 21,
+ "column": 6
+ },
+ {
+ "message": "Expected shorthand directive.",
+ "line": 23,
+ "column": 6
+ }
+]
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte
new file mode 100644
index 000000000..0709f9ae5
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-input.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
...
+...
+
+
+
+
+
+...
+
+...
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte
new file mode 100644
index 000000000..deb7c88b3
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/always/test01-output.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+...
+...
+
+
+
+
+
+...
+
+...
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/_config.json b/tests/fixtures/rules/shorthand-directive/invalid/never/_config.json
new file mode 100644
index 000000000..63ecce4db
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/never/_config.json
@@ -0,0 +1,3 @@
+{
+ "options": [{ "prefer": "never" }]
+}
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json
new file mode 100644
index 000000000..063812205
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-errors.json
@@ -0,0 +1,17 @@
+[
+ {
+ "message": "Expected regular directive syntax.",
+ "line": 8,
+ "column": 8
+ },
+ {
+ "message": "Expected regular directive syntax.",
+ "line": 9,
+ "column": 6
+ },
+ {
+ "message": "Expected regular directive syntax.",
+ "line": 10,
+ "column": 6
+ }
+]
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte
new file mode 100644
index 000000000..3acf6f137
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-input.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+...
+...
+
+
+
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte
new file mode 100644
index 000000000..73028511b
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/never/test01-output.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+...
+...
+
+
+
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json b/tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json
new file mode 100644
index 000000000..39baee195
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/test01-errors.json
@@ -0,0 +1,17 @@
+[
+ {
+ "message": "Expected shorthand directive.",
+ "line": 14,
+ "column": 8
+ },
+ {
+ "message": "Expected shorthand directive.",
+ "line": 16,
+ "column": 6
+ },
+ {
+ "message": "Expected shorthand directive.",
+ "line": 18,
+ "column": 6
+ }
+]
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte
new file mode 100644
index 000000000..3acf6f137
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/test01-input.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+...
+...
+
+
+
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte b/tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte
new file mode 100644
index 000000000..e276674f9
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/invalid/test01-output.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+...
+...
+
+
+
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/valid/always/_config.json b/tests/fixtures/rules/shorthand-directive/valid/always/_config.json
new file mode 100644
index 000000000..78d5d8817
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/valid/always/_config.json
@@ -0,0 +1,3 @@
+{
+ "options": [{ "prefer": "always" }]
+}
diff --git a/tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte
new file mode 100644
index 000000000..4a0422106
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/valid/always/test01-input.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+...
+...
diff --git a/tests/fixtures/rules/shorthand-directive/valid/never/_config.json b/tests/fixtures/rules/shorthand-directive/valid/never/_config.json
new file mode 100644
index 000000000..63ecce4db
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/valid/never/_config.json
@@ -0,0 +1,3 @@
+{
+ "options": [{ "prefer": "never" }]
+}
diff --git a/tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte
new file mode 100644
index 000000000..1ff5be5f3
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/valid/never/test01-input.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+...
+
+...
diff --git a/tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte b/tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte
new file mode 100644
index 000000000..b16c65a6f
--- /dev/null
+++ b/tests/fixtures/rules/shorthand-directive/valid/test01-input.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+...
+...
+
+
+...
+...
+...
+...
diff --git a/tests/src/rules/shorthand-directive.ts b/tests/src/rules/shorthand-directive.ts
new file mode 100644
index 000000000..8ba5ff082
--- /dev/null
+++ b/tests/src/rules/shorthand-directive.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/shorthand-directive"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "shorthand-directive",
+ rule as any,
+ loadTestCases("shorthand-directive"),
+)