diff --git a/README.md b/README.md
index c62cc00a..b518cc35 100644
--- a/README.md
+++ b/README.md
@@ -400,6 +400,7 @@ These rules relate to possible syntax or logic errors in Astro component code:
| [astro/no-deprecated-astro-fetchcontent](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-deprecated-astro-fetchcontent/) | disallow using deprecated `Astro.fetchContent()` | ⭐🔧 |
| [astro/no-deprecated-astro-resolve](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-deprecated-astro-resolve/) | disallow using deprecated `Astro.resolve()` | ⭐ |
| [astro/no-deprecated-getentrybyslug](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-deprecated-getentrybyslug/) | disallow using deprecated `getEntryBySlug()` | ⭐ |
+| [astro/no-exports-from-components](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-exports-from-components/) | disallow value export | |
| [astro/no-unused-define-vars-in-style](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-unused-define-vars-in-style/) | disallow unused `define:vars={...}` in `style` tag | ⭐ |
| [astro/valid-compile](https://ota-meshi.github.io/eslint-plugin-astro/rules/valid-compile/) | disallow warnings when compiling. | ⭐ |
diff --git a/docs/rules.md b/docs/rules.md
index 7e604bae..bf91d007 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -19,6 +19,7 @@ These rules relate to possible syntax or logic errors in Astro component code:
| [astro/no-deprecated-astro-fetchcontent](./rules/no-deprecated-astro-fetchcontent.md) | disallow using deprecated `Astro.fetchContent()` | ⭐🔧 |
| [astro/no-deprecated-astro-resolve](./rules/no-deprecated-astro-resolve.md) | disallow using deprecated `Astro.resolve()` | ⭐ |
| [astro/no-deprecated-getentrybyslug](./rules/no-deprecated-getentrybyslug.md) | disallow using deprecated `getEntryBySlug()` | ⭐ |
+| [astro/no-exports-from-components](./rules/no-exports-from-components.md) | disallow value export | |
| [astro/no-unused-define-vars-in-style](./rules/no-unused-define-vars-in-style.md) | disallow unused `define:vars={...}` in `style` tag | ⭐ |
| [astro/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | ⭐ |
diff --git a/docs/rules/no-exports-from-components.md b/docs/rules/no-exports-from-components.md
new file mode 100644
index 00000000..f1f6ebb4
--- /dev/null
+++ b/docs/rules/no-exports-from-components.md
@@ -0,0 +1,41 @@
+---
+title: "astro/no-exports-from-components"
+description: "disallow value export"
+---
+
+# astro/no-exports-from-components
+
+> disallow value export
+
+- ❗ **_This rule has not been released yet._**
+
+## 📖 Rule Details
+
+This rule reports value exports from Astro components.
+The use of typed exports are still allowed.
+
+
+
+
+
+```astro
+---
+/* eslint astro/no-exports-from-components: "error" */
+/* ✓ GOOD */
+export type A = number | boolean
+/* ✗ BAD */
+export const x = 42
+---
+```
+
+
+
+## 🔧 Options
+
+Nothing.
+
+## 🔍 Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/src/rules/no-exports-from-components.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/tests/src/rules/no-exports-from-components.ts)
+- [Test fixture sources](https://github.com/ota-meshi/eslint-plugin-astro/tree/main/tests/fixtures/rules/no-exports-from-components)
diff --git a/src/rules/no-exports-from-components.ts b/src/rules/no-exports-from-components.ts
new file mode 100644
index 00000000..06a06ebe
--- /dev/null
+++ b/src/rules/no-exports-from-components.ts
@@ -0,0 +1,68 @@
+import type { TSESTree } from "@typescript-eslint/types"
+import { createRule } from "../utils"
+import { getSourceCode } from "../utils/compat"
+
+export default createRule("no-exports-from-components", {
+ meta: {
+ docs: {
+ description: "disallow value export",
+ category: "Possible Errors",
+ // TODO: Switch to recommended: true, in next major version
+ recommended: false,
+ },
+ schema: [],
+ messages: {
+ disallowExport: "Exporting values from components is not allowed.",
+ },
+ type: "problem",
+ },
+ create(context) {
+ const sourceCode = getSourceCode(context)
+ if (!sourceCode.parserServices.isAstro) {
+ return {}
+ }
+
+ /**
+ * Verify for export declarations
+ */
+ function verifyDeclaration(
+ node:
+ | TSESTree.ExportDefaultDeclaration["declaration"]
+ | TSESTree.ExportNamedDeclaration["declaration"],
+ ) {
+ if (!node) return
+ if (node.type.startsWith("TS") && !node.type.endsWith("Expression")) {
+ return
+ }
+ context.report({
+ node,
+ messageId: "disallowExport",
+ })
+ }
+
+ return {
+ ExportAllDeclaration(node) {
+ if (node.exportKind === "type") return
+ context.report({
+ node,
+ messageId: "disallowExport",
+ })
+ },
+ ExportDefaultDeclaration(node) {
+ if (node.exportKind === "type") return
+ verifyDeclaration(node.declaration)
+ },
+ ExportNamedDeclaration(node) {
+ if (node.exportKind === "type") return
+ verifyDeclaration(node.declaration)
+ for (const spec of node.specifiers) {
+ if (spec.exportKind === "type") return
+ context.report({
+ node: spec,
+ messageId: "disallowExport",
+ })
+ }
+ },
+ }
+ },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index a0aa7e6d..1cda35a9 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -8,6 +8,7 @@ import noDeprecatedAstroCanonicalurl from "../rules/no-deprecated-astro-canonica
import noDeprecatedAstroFetchcontent from "../rules/no-deprecated-astro-fetchcontent"
import noDeprecatedAstroResolve from "../rules/no-deprecated-astro-resolve"
import noDeprecatedGetentrybyslug from "../rules/no-deprecated-getentrybyslug"
+import noExportsFromComponents from "../rules/no-exports-from-components"
import noSetHtmlDirective from "../rules/no-set-html-directive"
import noSetTextDirective from "../rules/no-set-text-directive"
import noUnusedCssSelector from "../rules/no-unused-css-selector"
@@ -26,6 +27,7 @@ export const rules = [
noDeprecatedAstroFetchcontent,
noDeprecatedAstroResolve,
noDeprecatedGetentrybyslug,
+ noExportsFromComponents,
noSetHtmlDirective,
noSetTextDirective,
noUnusedCssSelector,
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/_config.json b/tests/fixtures/rules/no-exports-from-components/invalid/_config.json
new file mode 100644
index 00000000..0342354a
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/_config.json
@@ -0,0 +1,7 @@
+{
+ "languageOptions": {
+ "parserOptions": {
+ "parser": "@typescript-eslint/parser"
+ }
+ }
+}
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-all-01-errors.json b/tests/fixtures/rules/no-exports-from-components/invalid/export-all-01-errors.json
new file mode 100644
index 00000000..c0648dd6
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-all-01-errors.json
@@ -0,0 +1,7 @@
+[
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 2,
+ "column": 1
+ }
+]
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-all-01-input.astro b/tests/fixtures/rules/no-exports-from-components/invalid/export-all-01-input.astro
new file mode 100644
index 00000000..8c297cd7
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-all-01-input.astro
@@ -0,0 +1,3 @@
+---
+export * from "./foo.js"
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-default-01-errors.json b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-01-errors.json
new file mode 100644
index 00000000..95322300
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-01-errors.json
@@ -0,0 +1,7 @@
+[
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 2,
+ "column": 16
+ }
+]
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-default-01-input.astro b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-01-input.astro
new file mode 100644
index 00000000..557553b9
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-01-input.astro
@@ -0,0 +1,3 @@
+---
+export default {}
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-default-02-errors.json b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-02-errors.json
new file mode 100644
index 00000000..2f96e453
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-02-errors.json
@@ -0,0 +1,7 @@
+[
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 4,
+ "column": 16
+ }
+]
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-default-02-input.astro b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-02-input.astro
new file mode 100644
index 00000000..170dca00
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-02-input.astro
@@ -0,0 +1,5 @@
+---
+export default function fn(): boolean
+export default function fn(a: string): string
+export default function fn(a?: string): string | boolean {}
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-default-03-errors.json b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-03-errors.json
new file mode 100644
index 00000000..95322300
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-03-errors.json
@@ -0,0 +1,7 @@
+[
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 2,
+ "column": 16
+ }
+]
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/export-default-03-input.astro b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-03-input.astro
new file mode 100644
index 00000000..9c2d8d1f
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/export-default-03-input.astro
@@ -0,0 +1,5 @@
+---
+export default class A {
+ private x(): void {}
+}
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/named-export01-errors.json b/tests/fixtures/rules/no-exports-from-components/invalid/named-export01-errors.json
new file mode 100644
index 00000000..79a1da78
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/named-export01-errors.json
@@ -0,0 +1,32 @@
+[
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 2,
+ "column": 10
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 3,
+ "column": 10
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 3,
+ "column": 13
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 4,
+ "column": 8
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 7,
+ "column": 8
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 10,
+ "column": 8
+ }
+]
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/named-export01-input.astro b/tests/fixtures/rules/no-exports-from-components/invalid/named-export01-input.astro
new file mode 100644
index 00000000..f6e9e33a
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/named-export01-input.astro
@@ -0,0 +1,11 @@
+---
+export { A } from "./foo.js"
+export { B, B2 } from "./foo.js"
+export const C = {
+ // ...
+}
+export class D {
+ private x(): void {}
+}
+export function E(): void {}
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/named-export02-errors.json b/tests/fixtures/rules/no-exports-from-components/invalid/named-export02-errors.json
new file mode 100644
index 00000000..f936fb6c
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/named-export02-errors.json
@@ -0,0 +1,32 @@
+[
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 13,
+ "column": 10
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 13,
+ "column": 13
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 13,
+ "column": 16
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 14,
+ "column": 10
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 14,
+ "column": 13
+ },
+ {
+ "message": "Exporting values from components is not allowed.",
+ "line": 14,
+ "column": 16
+ }
+]
diff --git a/tests/fixtures/rules/no-exports-from-components/invalid/named-export02-input.astro b/tests/fixtures/rules/no-exports-from-components/invalid/named-export02-input.astro
new file mode 100644
index 00000000..61e7f34b
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/invalid/named-export02-input.astro
@@ -0,0 +1,15 @@
+---
+import { A } from "./foo.js"
+import { B, B2 } from "./foo.js"
+const C = {
+ // ...
+}
+class D {
+ private x(): void {}
+}
+
+function E(): void {}
+
+export { A, B, B2 }
+export { C, D, E }
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/valid/_config.json b/tests/fixtures/rules/no-exports-from-components/valid/_config.json
new file mode 100644
index 00000000..0342354a
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/valid/_config.json
@@ -0,0 +1,7 @@
+{
+ "languageOptions": {
+ "parserOptions": {
+ "parser": "@typescript-eslint/parser"
+ }
+ }
+}
diff --git a/tests/fixtures/rules/no-exports-from-components/valid/export-all-01-input.astro b/tests/fixtures/rules/no-exports-from-components/valid/export-all-01-input.astro
new file mode 100644
index 00000000..2a3eaf75
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/valid/export-all-01-input.astro
@@ -0,0 +1,3 @@
+---
+export type * from "./foo.js"
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/valid/export-default-01-input.astro b/tests/fixtures/rules/no-exports-from-components/valid/export-default-01-input.astro
new file mode 100644
index 00000000..2221d243
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/valid/export-default-01-input.astro
@@ -0,0 +1,5 @@
+---
+export default interface A {
+ a: string
+}
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/valid/named-export01-input.astro b/tests/fixtures/rules/no-exports-from-components/valid/named-export01-input.astro
new file mode 100644
index 00000000..278ef71a
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/valid/named-export01-input.astro
@@ -0,0 +1,14 @@
+---
+export { type A } from "./foo.js"
+export type { B, B2 } from "./foo.js"
+export type C = {
+ // ...
+}
+export interface D {
+ a(): void
+}
+export const enum E {
+ x = 1,
+ y = 2,
+}
+---
diff --git a/tests/fixtures/rules/no-exports-from-components/valid/named-export02-input.astro b/tests/fixtures/rules/no-exports-from-components/valid/named-export02-input.astro
new file mode 100644
index 00000000..7bf81164
--- /dev/null
+++ b/tests/fixtures/rules/no-exports-from-components/valid/named-export02-input.astro
@@ -0,0 +1,15 @@
+---
+import type { A } from "./foo.js"
+import type { B, B2 } from "./foo.js"
+type C = {
+ // ...
+}
+interface D {
+ a(): void
+}
+
+enum E {}
+
+export type { A, B, B2 }
+export { type C, type D, type E }
+---
diff --git a/tests/src/rules/no-exports-from-components.ts b/tests/src/rules/no-exports-from-components.ts
new file mode 100644
index 00000000..dae5fa21
--- /dev/null
+++ b/tests/src/rules/no-exports-from-components.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "../../utils/eslint-compat"
+import rule from "../../../src/rules/no-exports-from-components"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "no-exports-from-components",
+ rule as any,
+ loadTestCases("no-exports-from-components"),
+)
diff --git a/tests/utils/utils.ts b/tests/utils/utils.ts
index 277c0694..a0fd47e7 100644
--- a/tests/utils/utils.ts
+++ b/tests/utils/utils.ts
@@ -231,8 +231,14 @@ function getConfig(ruleName: string, inputFile: string) {
}
if (config && typeof config === "object") {
return Object.assign(
- { languageOptions: { parser: astroESLintParser } },
+ {},
config,
+ {
+ languageOptions: {
+ parser: astroESLintParser,
+ ...config?.languageOptions,
+ },
+ },
{ code, filename },
)
}
diff --git a/tools/update-rulesets.ts b/tools/update-rulesets.ts
index 0504692e..986e000e 100644
--- a/tools/update-rulesets.ts
+++ b/tools/update-rulesets.ts
@@ -116,7 +116,7 @@ export default [
{
plugins: {
get astro(): ESLint.Plugin {
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- ignore
return (plugin ??= require("../../plugin").plugin)
},
},