Skip to content

Commit 3429da5

Browse files
authored
feat: add astro/client-side-ts processor (#292)
* feat: add `astro/client-side-ts` processor * fix * Create tender-laws-rule.md
1 parent 37dfdbb commit 3429da5

File tree

17 files changed

+313
-84
lines changed

17 files changed

+313
-84
lines changed

.changeset/tender-laws-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-astro": minor
3+
---
4+
5+
feat: add `astro/client-side-ts` processor

README.md

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ At least it works fine with a [withastro/docs](https://github.com/withastro/docs
2626
- Linting Astro components using ESLint.
2727
- Find problems with Astro components.
2828
- Apply a consistent code style to Astro components.
29-
- Linting targets include [Frontmatter], [HTML Template], [Dynamic JSX Expressions], [Client-Side Scripts], [Directives], and more.
29+
- Linting targets include [Frontmatter], [HTML Template], [JSX-like Expressions], [Client-Side Scripts], [Directives], and more.
3030
- Check code in real time with the ESLint editor integrations.
3131

32-
[frontmatter]: https://docs.astro.build/ja/core-concepts/astro-components/#the-component-script
33-
[html template]: https://docs.astro.build/ja/core-concepts/astro-components/#the-component-template
34-
[dynamic jsx expressions]: https://docs.astro.build/ja/core-concepts/astro-components/#dynamic-jsx-expressions
35-
[client-side scripts]: https://docs.astro.build/ja/core-concepts/astro-components/#client-side-scripts
36-
[directives]: https://docs.astro.build/ja/reference/directives-reference/
32+
[frontmatter]: https://docs.astro.build/en/core-concepts/astro-components/#the-component-script
33+
[html template]: https://docs.astro.build/en/core-concepts/astro-components/#the-component-template
34+
[JSX-like Expressions]: https://docs.astro.build/en/core-concepts/astro-syntax/#jsx-like-expressions
35+
[client-side scripts]: https://docs.astro.build/en/guides/client-side-scripts/
36+
[directives]: https://docs.astro.build/en/reference/directives-reference/
3737

3838
<!--DOCS_IGNORE_START-->
3939

@@ -170,6 +170,52 @@ module.exports = {
170170
"prettier/prettier": "off",
171171
},
172172
},
173+
{
174+
// Define the configuration for `<script>` tag when using `client-side-ts` processor.
175+
// Script in `<script>` is assigned a virtual file name with the `.js` extension.
176+
files: ["**/*.astro/*.ts", "*.astro/*.ts"],
177+
env: {
178+
browser: true,
179+
es2020: true,
180+
},
181+
parser: "@typescript-eslint/parser",
182+
parserOptions: {
183+
sourceType: "module",
184+
project: null,
185+
},
186+
rules: {
187+
// override/add rules settings here, such as:
188+
// "no-unused-vars": "error"
189+
190+
// If you are using "prettier/prettier" rule,
191+
// you don't need to format inside <script> as it will be formatted as a `.astro` file.
192+
"prettier/prettier": "off",
193+
},
194+
},
195+
// ...
196+
],
197+
}
198+
```
199+
200+
If you are writing client-side scripts in TypeScript and want to use `@typescript-eslint/parser` as the TypeScript parser, you will need to use `client-side-ts` processor and configure it as follows.
201+
202+
```js
203+
module.exports = {
204+
// ...
205+
extends: [
206+
// ...
207+
"plugin:astro/recommended",
208+
],
209+
// ...
210+
overrides: [
211+
{
212+
files: ["*.astro"],
213+
// ...
214+
processor: "astro/client-side-ts", // <- Uses the "client-side-ts" processor.
215+
rules: {
216+
// ...
217+
},
218+
},
173219
// ...
174220
],
175221
}

docs-build/src/components/eslint/scripts/linter.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { rules as pluginRules } from "../../../../../src/utils/rules"
33
import { Linter } from "eslint"
44
import * as astroEslintParser from "astro-eslint-parser"
55
// eslint-disable-next-line n/no-missing-import -- Demo
6-
import { processor } from "../../../../../src/processor/index"
7-
export const { preprocess, postprocess } = processor
6+
import { astroProcessor } from "../../../../../src/processor/index"
7+
export const { preprocess, postprocess } = astroProcessor
88
const linter = new Linter()
99

1010
export const categories = [

docs/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ At least it works fine with a [withastro/docs](https://github.com/withastro/docs
3030
- Linting Astro components using ESLint.
3131
- Find problems with Astro components.
3232
- Apply a consistent code style to Astro components.
33-
- Linting targets include [Frontmatter], [HTML Template], [Dynamic JSX Expressions], [Client-Side Scripts], [Directives], and more.
33+
- Linting targets include [Frontmatter], [HTML Template], [JSX-like Expressions], [Client-Side Scripts], [Directives], and more.
3434
- Check code in real time with the ESLint editor integrations.
3535

36-
[frontmatter]: https://docs.astro.build/ja/core-concepts/astro-components/#the-component-script
37-
[html template]: https://docs.astro.build/ja/core-concepts/astro-components/#the-component-template
38-
[dynamic jsx expressions]: https://docs.astro.build/ja/core-concepts/astro-components/#dynamic-jsx-expressions
39-
[client-side scripts]: https://docs.astro.build/ja/core-concepts/astro-components/#client-side-scripts
40-
[directives]: https://docs.astro.build/ja/reference/directives-reference/
36+
[frontmatter]: https://docs.astro.build/en/core-concepts/astro-components/#the-component-script
37+
[html template]: https://docs.astro.build/en/core-concepts/astro-components/#the-component-template
38+
[JSX-like Expressions]: https://docs.astro.build/en/core-concepts/astro-syntax/#jsx-like-expressions
39+
[client-side scripts]: https://docs.astro.build/en/guides/client-side-scripts/
40+
[directives]: https://docs.astro.build/en/reference/directives-reference/
4141

4242
## 📖 Usage
4343

docs/user-guide.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,52 @@ module.exports = {
126126
"prettier/prettier": "off",
127127
},
128128
},
129+
{
130+
// Define the configuration for `<script>` tag when using `client-side-ts` processor.
131+
// Script in `<script>` is assigned a virtual file name with the `.js` extension.
132+
files: ["**/*.astro/*.ts", "*.astro/*.ts"],
133+
env: {
134+
browser: true,
135+
es2020: true,
136+
},
137+
parser: "@typescript-eslint/parser",
138+
parserOptions: {
139+
sourceType: "module",
140+
project: null,
141+
},
142+
rules: {
143+
// override/add rules settings here, such as:
144+
// "no-unused-vars": "error"
145+
146+
// If you are using "prettier/prettier" rule,
147+
// you don't need to format inside <script> as it will be formatted as a `.astro` file.
148+
"prettier/prettier": "off",
149+
},
150+
},
151+
// ...
152+
],
153+
}
154+
```
155+
156+
If you are writing client-side scripts in TypeScript and want to use `@typescript-eslint/parser` as the TypeScript parser, you will need to use `client-side-ts` processor and configure it as follows.
157+
158+
```js
159+
module.exports = {
160+
// ...
161+
extends: [
162+
// ...
163+
"plugin:astro/recommended",
164+
],
165+
// ...
166+
overrides: [
167+
{
168+
files: ["*.astro"],
169+
// ...
170+
processor: "astro/client-side-ts", // <- Uses the "client-side-ts" processor.
171+
rules: {
172+
// ...
173+
},
174+
},
129175
// ...
130176
],
131177
}
@@ -188,5 +234,3 @@ Example **.vscode/settings.json**:
188234
### Parsing the `.astro` file fails
189235

190236
You should check the [parser configuration](#parser-configuration).
191-
192-
[eslint-plugin-astro3]: https://github.com/astrojs/eslint-plugin-astro3

src/configs/base.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,26 @@ export = {
5050
"prettier/prettier": "off",
5151
},
5252
},
53+
{
54+
// Define the configuration for `<script>` tag when using `client-side-ts` processor.
55+
// Script in `<script>` is assigned a virtual file name with the `.ts` extension.
56+
files: ["**/*.astro/*.ts", "*.astro/*.ts"],
57+
env: {
58+
browser: true,
59+
es2020: true,
60+
},
61+
parser: hasTypescriptEslintParser
62+
? "@typescript-eslint/parser"
63+
: undefined,
64+
parserOptions: {
65+
sourceType: "module",
66+
project: null,
67+
},
68+
rules: {
69+
// If you are using "prettier/prettier" rule,
70+
// you don't need to format inside <script> as it will be formatted as a `.astro` file.
71+
"prettier/prettier": "off",
72+
},
73+
},
5374
],
5475
}

src/configs/has-typescript-eslint-parser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ export let hasTypescriptEslintParser = false
66
try {
77
const cwd = process.cwd()
88
const relativeTo = path.join(cwd, "__placeholder__.js")
9-
createRequire(relativeTo)("@typescript-eslint/parser")
10-
hasTypescriptEslintParser = true
9+
if (createRequire(relativeTo)("@typescript-eslint/parser"))
10+
hasTypescriptEslintParser = true
1111
} catch {
1212
// noop
1313
}

src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { RuleModule } from "./types"
22
import { rules as ruleList } from "./utils/rules"
3-
import { processor } from "./processor"
3+
import * as processors from "./processor"
44
import { environments } from "./environments"
55
import base from "./configs/base"
66
import recommended from "./configs/recommended"
@@ -38,8 +38,9 @@ export = {
3838
configs,
3939
rules,
4040
processors: {
41-
".astro": processor,
42-
astro: processor,
41+
".astro": processors.astroProcessor,
42+
astro: processors.astroProcessor,
43+
"client-side-ts": processors.clientSideTsProcessor,
4344
},
4445
environments,
4546
}

src/processor/index.ts

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,73 @@ import type { Linter } from "eslint"
44
import { beginShared, terminateShared } from "../shared"
55
import * as meta from "../meta"
66

7-
export const processor: Linter.Processor = {
7+
export const astroProcessor: Linter.Processor = {
88
preprocess(code: string, filename: string) {
9-
if (filename) {
10-
const shared = beginShared(filename)
11-
let parsed: ParseTemplateResult
12-
try {
13-
parsed = parseTemplate(code)
14-
} catch {
15-
// ignore
16-
return [code]
17-
}
18-
19-
parsed.walk(parsed.result.ast, (node) => {
20-
if (
21-
node.type === "element" &&
22-
node.name === "script" &&
23-
node.children.length &&
24-
!node.attributes.some(
25-
({ name, value }) =>
26-
name === "type" && /json$|importmap/i.test(value),
27-
)
28-
) {
29-
shared.addClientScript(code, node, parsed)
30-
}
31-
})
32-
return [code, ...shared.clientScripts.map((cs) => cs.getProcessorFile())]
33-
}
34-
35-
return [code]
9+
return preprocess(code, filename, ".js")
3610
},
37-
postprocess(
38-
[messages, ...blockMessages]: Linter.LintMessage[][],
39-
filename: string,
40-
): Linter.LintMessage[] {
41-
const shared = terminateShared(filename)
42-
if (shared) {
43-
return messages.concat(
44-
...blockMessages.map((m, i) =>
45-
shared.clientScripts[i].remapMessages(m),
46-
),
47-
)
48-
}
11+
postprocess,
12+
supportsAutofix: true,
13+
meta,
14+
}
4915

50-
return messages
16+
export const clientSideTsProcessor: Linter.Processor = {
17+
preprocess(code: string, filename: string) {
18+
return preprocess(code, filename, ".ts")
5119
},
20+
postprocess,
5221
supportsAutofix: true,
53-
meta,
22+
meta: { ...meta, name: "astro/client-side-ts" },
23+
}
24+
25+
/** preprocess */
26+
function preprocess(
27+
code: string,
28+
filename: string,
29+
vartualFileExt: string,
30+
): ReturnType<NonNullable<Linter.Processor["preprocess"]>> {
31+
if (filename) {
32+
const shared = beginShared(filename)
33+
let parsed: ParseTemplateResult
34+
try {
35+
parsed = parseTemplate(code)
36+
} catch {
37+
// ignore
38+
return [code]
39+
}
40+
41+
parsed.walk(parsed.result.ast, (node) => {
42+
if (
43+
node.type === "element" &&
44+
node.name === "script" &&
45+
node.children.length &&
46+
!node.attributes.some(
47+
({ name, value }) =>
48+
name === "type" && /json$|importmap/i.test(value),
49+
)
50+
) {
51+
shared.addClientScript(code, node, parsed)
52+
}
53+
})
54+
return [
55+
code,
56+
...shared.clientScripts.map((cs) => cs.getProcessorFile(vartualFileExt)),
57+
]
58+
}
59+
60+
return [code]
61+
}
62+
63+
/** postprocess */
64+
function postprocess(
65+
[messages, ...blockMessages]: Linter.LintMessage[][],
66+
filename: string,
67+
): Linter.LintMessage[] {
68+
const shared = terminateShared(filename)
69+
if (shared) {
70+
return messages.concat(
71+
...blockMessages.map((m, i) => shared.clientScripts[i].remapMessages(m)),
72+
)
73+
}
74+
75+
return messages
5476
}

src/shared/client-script/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,10 @@ export class ClientScript {
244244
})
245245
}
246246

247-
public getProcessorFile(): Linter.ProcessorFile {
247+
public getProcessorFile(ext: string): Linter.ProcessorFile {
248248
return {
249249
text: this.block.text,
250-
filename: `${this.id}.js`,
250+
filename: `${this.id}${ext}`,
251251
}
252252
}
253253

0 commit comments

Comments
 (0)