Skip to content

Commit

Permalink
feat: add astro/no-exports-from-components rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Apr 29, 2024
1 parent cb83527 commit 9c068cd
Show file tree
Hide file tree
Showing 26 changed files with 322 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. ||

Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. ||

Expand Down
41 changes: 41 additions & 0 deletions docs/rules/no-exports-from-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: "astro/no-exports-from-components"
description: "disallow value export"
---

# astro/no-exports-from-components

> disallow value export
- ❗ <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>

## 📖 Rule Details

This rule reports value exports from Astro components.
The use of typed exports are still allowed.

<ESLintCodeBlock>

<!--eslint-skip-->

```astro
---
/* eslint astro/no-exports-from-components: "error" */
/* ✓ GOOD */
export type A = number | boolean
/* ✗ BAD */
export const x = 42
---
```

</ESLintCodeBlock>

## 🔧 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)
68 changes: 68 additions & 0 deletions src/rules/no-exports-from-components.ts
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 10 in src/rules/no-exports-from-components.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'todo' comment: 'TODO: Switch to recommended: true, in...'

Check warning on line 10 in src/rules/no-exports-from-components.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'todo' comment: 'TODO: Switch to recommended: true, in...'
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",
})
}
},
}
},
})
2 changes: 2 additions & 0 deletions src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -26,6 +27,7 @@ export const rules = [
noDeprecatedAstroFetchcontent,
noDeprecatedAstroResolve,
noDeprecatedGetentrybyslug,
noExportsFromComponents,
noSetHtmlDirective,
noSetTextDirective,
noUnusedCssSelector,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"languageOptions": {
"parserOptions": {
"parser": "@typescript-eslint/parser"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Exporting values from components is not allowed.",
"line": 2,
"column": 1
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
export * from "./foo.js"
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Exporting values from components is not allowed.",
"line": 2,
"column": 16
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
export default {}
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Exporting values from components is not allowed.",
"line": 4,
"column": 16
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
export default function fn(): boolean
export default function fn(a: string): string
export default function fn(a?: string): string | boolean {}
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"message": "Exporting values from components is not allowed.",
"line": 2,
"column": 16
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
export default class A {
private x(): void {}
}
---
Original file line number Diff line number Diff line change
@@ -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
}
]
Original file line number Diff line number Diff line change
@@ -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 {}
---
Original file line number Diff line number Diff line change
@@ -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
}
]
Original file line number Diff line number Diff line change
@@ -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 }
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"languageOptions": {
"parserOptions": {
"parser": "@typescript-eslint/parser"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
export type * from "./foo.js"
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
export default interface A {
a: string
}
---
Original file line number Diff line number Diff line change
@@ -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,
}
---
Original file line number Diff line number Diff line change
@@ -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 }
---
16 changes: 16 additions & 0 deletions tests/src/rules/no-exports-from-components.ts
Original file line number Diff line number Diff line change
@@ -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"),
)
8 changes: 7 additions & 1 deletion tests/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
)
}
Expand Down
2 changes: 1 addition & 1 deletion tools/update-rulesets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
},
Expand Down

0 comments on commit 9c068cd

Please sign in to comment.