Repository: ota-meshi/eslint-plugin-astro
Pull request #322 (2024-03-12 21:49:45)

feat: add `missing-directive-value` rule 
According to Astro docs, `client:only` directive is required to have a value which represent the correct framework so I think it would be useful to have a rule about this to remind ourselves about this.

Astro docs reference: https://docs.astro.build/en/reference/directives-reference/#clientonly

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 5d88ae032c35f003b5da91c5df6b3847c30526fa

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/oanaOM/eslint-plugin-astro/new/missing-directive-value-rule?filename=.changeset/fresh-camels-tell.md&value=---%0A%22eslint-plugin-astro%22%3A%20patch%0A---%0A%0Awip%3A%20add%20new%20rule%0A)


ota-meshi: I noticed that I haven't written any instructions on how to contribute to this repository.
I added CONTRIBUTING.md earlier, so please check it out.

https://github.com/ota-meshi/eslint-plugin-astro/blob/main/CONTRIBUTING.md
oanaOM: thanks for sharing that @ota-meshi! Sorry for my delayed reply, I think this PR is ready for your review, please.

Patch:
diff --git a/.changeset/slimy-pillows-change.md b/.changeset/slimy-pillows-change.md
new file mode 100644
index 0000000..0c086dc
--- /dev/null
+++ b/.changeset/slimy-pillows-change.md
@@ -0,0 +1,5 @@
+---
+"eslint-plugin-astro": minor
+---
+
+feat add `astro/missing-client-only-directive-value` rule
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3bd74e2..7267faf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -34,7 +34,13 @@ npm run test
 
 This is an [ESLint](http://eslint.org) plugin. Documentation for the APIs that it uses can be found on ESLint's [Working with Plugins](http://eslint.org/docs/developer-guide/working-with-plugins) page.
 
-This plugin is used to lint itself. The style is checked when `npm run lint` is run, and the build will fail if there are any linting errors. You can use `npm run eslint-fix` to fix some linting errors.
+### Linting the source code
+
+```sh
+npm run lint
+```
+
+The style is checked when `npm run lint` is run, and the build will fail if there are any linting errors. You can use `npm run eslint-fix` to fix some linting errors.
 
 ### Other Development Tools
 
diff --git a/README.md b/README.md
index 2b61c20..df9ca6f 100644
--- a/README.md
+++ b/README.md
@@ -377,6 +377,7 @@ These rules relate to possible syntax or logic errors in Astro component code:
 
 | Rule ID | Description |    |
 |:--------|:------------|:---|
+| [astro/missing-client-only-directive-value](https://ota-meshi.github.io/eslint-plugin-astro/rules/missing-client-only-directive-value/) | the client:only directive is missing the correct component's framework value | ⭐ |
 | [astro/no-conflict-set-directives](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-conflict-set-directives/) | disallow conflicting set directives and child contents | ⭐ |
 | [astro/no-deprecated-astro-canonicalurl](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-deprecated-astro-canonicalurl/) | disallow using deprecated `Astro.canonicalURL` | ⭐ |
 | [astro/no-deprecated-astro-fetchcontent](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-deprecated-astro-fetchcontent/) | disallow using deprecated `Astro.fetchContent()` | ⭐🔧 |
diff --git a/docs/rules.md b/docs/rules.md
index d3b156b..7e604ba 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -13,6 +13,7 @@ These rules relate to possible syntax or logic errors in Astro component code:
 
 | Rule ID | Description |    |
 |:--------|:------------|:---|
+| [astro/missing-client-only-directive-value](./rules/missing-client-only-directive-value.md) | the client:only directive is missing the correct component's framework value | ⭐ |
 | [astro/no-conflict-set-directives](./rules/no-conflict-set-directives.md) | disallow conflicting set directives and child contents | ⭐ |
 | [astro/no-deprecated-astro-canonicalurl](./rules/no-deprecated-astro-canonicalurl.md) | disallow using deprecated `Astro.canonicalURL` | ⭐ |
 | [astro/no-deprecated-astro-fetchcontent](./rules/no-deprecated-astro-fetchcontent.md) | disallow using deprecated `Astro.fetchContent()` | ⭐🔧 |
diff --git a/docs/rules/missing-client-only-directive-value.md b/docs/rules/missing-client-only-directive-value.md
new file mode 100644
index 0000000..fe62413
--- /dev/null
+++ b/docs/rules/missing-client-only-directive-value.md
@@ -0,0 +1,49 @@
+---
+title: "astro/missing-client-only-directive-value"
+description: "the client:only directive is missing the correct component's framework value"
+---
+
+# astro/missing-client-only-directive-value
+
+> the client:only directive is missing the correct component's framework value
+
+- ❗ <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
+- ⚙ This rule is included in `"plugin:astro/recommended"`.
+
+## 📖 Rule Details
+
+This rule reports not setting a value for the `client:only` directive.
+
+<ESLintCodeBlock>
+
+<!--eslint-skip-->
+
+```astro
+---
+/* eslint astro/missing-client-only-directive-value: "error" */
+
+---
+
+{/* ✓ GOOD */}
+<SomeSvelteComponent client:only="svelte" />
+
+{/* ✗ BAD */}
+<SomeSvelteComponent client:only />
+```
+
+</ESLintCodeBlock>
+
+## 🔧 Options
+
+Nothing.
+
+
+## 📚 Further Reading
+
+- [Astro Documentation | Template Directives Reference > client:only](https://docs.astro.build/en/reference/directives-reference/#clientonly)
+
+## 🔍 Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/src/rules/missing-client-only-directive-value.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/tests/src/rules/missing-client-only-directive-value.ts)
+- [Test fixture sources](https://github.com/ota-meshi/eslint-plugin-astro/tree/main/tests/fixtures/rules/missing-client-only-directive-value)
diff --git a/package.json b/package.json
index d1f12f7..e6b6e58 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
     "eslint-plugin-astro": "^0.32.0",
     "eslint-plugin-eslint-comments": "^3.2.0",
     "eslint-plugin-eslint-plugin": "^5.0.0",
-    "eslint-plugin-json-schema-validator": "^4.8.1",
+    "eslint-plugin-json-schema-validator": "^5.0.0",
     "eslint-plugin-jsonc": "^2.2.1",
     "eslint-plugin-jsx-a11y": "^6.5.1",
     "eslint-plugin-markdown": "^3.0.0",
diff --git a/src/configs/flat/recommended.ts b/src/configs/flat/recommended.ts
index 6dca72a..08bd802 100644
--- a/src/configs/flat/recommended.ts
+++ b/src/configs/flat/recommended.ts
@@ -7,6 +7,7 @@ export default [
   {
     rules: {
       // eslint-plugin-astro rules
+      "astro/missing-client-only-directive-value": "error",
       "astro/no-conflict-set-directives": "error",
       "astro/no-deprecated-astro-canonicalurl": "error",
       "astro/no-deprecated-astro-fetchcontent": "error",
diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts
index 87b267f..f029a8b 100644
--- a/src/configs/recommended.ts
+++ b/src/configs/recommended.ts
@@ -9,6 +9,7 @@ export = {
   extends: [baseExtend],
   rules: {
     // eslint-plugin-astro rules
+    "astro/missing-client-only-directive-value": "error",
     "astro/no-conflict-set-directives": "error",
     "astro/no-deprecated-astro-canonicalurl": "error",
     "astro/no-deprecated-astro-fetchcontent": "error",
diff --git a/src/rules/missing-client-only-directive-value.ts b/src/rules/missing-client-only-directive-value.ts
new file mode 100644
index 0000000..4684223
--- /dev/null
+++ b/src/rules/missing-client-only-directive-value.ts
@@ -0,0 +1,50 @@
+import type { AST } from "astro-eslint-parser"
+import { createRule } from "../utils"
+import { getAttributeName, getStaticAttributeValue } from "../utils/ast-utils"
+import { getSourceCode } from "../utils/compat"
+
+export default createRule("missing-client-only-directive-value", {
+  meta: {
+    docs: {
+      description:
+        "the client:only directive is missing the correct component's framework value",
+      category: "Possible Errors",
+      recommended: true,
+    },
+    schema: [],
+    messages: {
+      missingValue: "`client:only` directive is missing a value",
+    },
+    type: "problem",
+  },
+  create(context) {
+    const sourceCode = getSourceCode(context)
+    if (!sourceCode.parserServices.isAstro) {
+      return {}
+    }
+
+    /** VerifyDirectiveValue */
+    function verifyDirectiveValue(
+      attr: AST.JSXAttribute | AST.AstroTemplateLiteralAttribute,
+    ) {
+      const directiveName = getAttributeName(attr)
+      const directiveValue = getStaticAttributeValue(attr)
+
+      if (directiveName !== "client:only") return
+
+      if (directiveValue !== null) {
+        return
+      }
+
+      context.report({
+        node: attr.name,
+        messageId: "missingValue",
+      })
+    }
+
+    return {
+      JSXAttribute: verifyDirectiveValue,
+      AstroTemplateLiteralAttribute: verifyDirectiveValue,
+    }
+  },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index 76b2bb5..a0aa7e6 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -2,6 +2,7 @@
 // This file has been automatically generated,
 // in order to update its content execute "npm run update"
 import type { RuleModule } from "../types"
+import missingClientOnlyDirectiveValue from "../rules/missing-client-only-directive-value"
 import noConflictSetDirectives from "../rules/no-conflict-set-directives"
 import noDeprecatedAstroCanonicalurl from "../rules/no-deprecated-astro-canonicalurl"
 import noDeprecatedAstroFetchcontent from "../rules/no-deprecated-astro-fetchcontent"
@@ -19,6 +20,7 @@ import validCompile from "../rules/valid-compile"
 import { buildA11yRules } from "../a11y"
 
 export const rules = [
+  missingClientOnlyDirectiveValue,
   noConflictSetDirectives,
   noDeprecatedAstroCanonicalurl,
   noDeprecatedAstroFetchcontent,


Test patch:
diff --git a/tests/fixtures/rules/missing-client-only-directive-value/SomeSvelteComponent.svelte b/tests/fixtures/rules/missing-client-only-directive-value/SomeSvelteComponent.svelte
new file mode 100644
index 0000000..8e5d313
--- /dev/null
+++ b/tests/fixtures/rules/missing-client-only-directive-value/SomeSvelteComponent.svelte
@@ -0,0 +1,6 @@
+<script>
+</script>
+
+<main>
+  <h1>Hello Svelte!</h1>
+</main>
diff --git a/tests/fixtures/rules/missing-client-only-directive-value/invalid/missing-client-only-directive-value-errors.json b/tests/fixtures/rules/missing-client-only-directive-value/invalid/missing-client-only-directive-value-errors.json
new file mode 100644
index 0000000..e821bb6
--- /dev/null
+++ b/tests/fixtures/rules/missing-client-only-directive-value/invalid/missing-client-only-directive-value-errors.json
@@ -0,0 +1,7 @@
+[
+  {
+    "message": "`client:only` directive is missing a value",
+    "line": 5,
+    "column": 22
+  }
+]
diff --git a/tests/fixtures/rules/missing-client-only-directive-value/invalid/missing-client-only-directive-value-input.astro b/tests/fixtures/rules/missing-client-only-directive-value/invalid/missing-client-only-directive-value-input.astro
new file mode 100644
index 0000000..1808b8b
--- /dev/null
+++ b/tests/fixtures/rules/missing-client-only-directive-value/invalid/missing-client-only-directive-value-input.astro
@@ -0,0 +1,5 @@
+---
+import SomeSvelteComponent from "../SomeSvelteComponent.svelte"
+---
+
+<SomeSvelteComponent client:only />
diff --git a/tests/fixtures/rules/missing-client-only-directive-value/valid/client-only-value.astro b/tests/fixtures/rules/missing-client-only-directive-value/valid/client-only-value.astro
new file mode 100644
index 0000000..a1aa6a9
--- /dev/null
+++ b/tests/fixtures/rules/missing-client-only-directive-value/valid/client-only-value.astro
@@ -0,0 +1,9 @@
+---
+import SomeSvelteComponent from "../SomeSvelteComponent.svelte"
+let string = "svelte"
+---
+
+<!-- prettier-ignore-start -->
+<SomeSvelteComponent client:only={string} />
+<SomeSvelteComponent client:idle />
+<!-- prettier-ignore-end -->
diff --git a/tests/src/rules/missing-client-only-directive-value.ts b/tests/src/rules/missing-client-only-directive-value.ts
new file mode 100644
index 0000000..6563abf
--- /dev/null
+++ b/tests/src/rules/missing-client-only-directive-value.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "../../utils/eslint-compat"
+import rule from "../../../src/rules/missing-client-only-directive-value"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+  languageOptions: {
+    ecmaVersion: 2020,
+    sourceType: "module",
+  },
+})
+
+tester.run(
+  "missing-client-only-directive-value",
+  rule as any,
+  loadTestCases("missing-client-only-directive-value"),
+)