Skip to content

Commit 6039793

Browse files
feat: Added the block-lang rule (#389)
Co-authored-by: ota-meshi <[email protected]>
1 parent f475823 commit 6039793

File tree

159 files changed

+696
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+696
-0
lines changed

.changeset/slimy-donkeys-pump.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-svelte": minor
3+
---
4+
5+
feat: added the `svelte/block-lang` rule

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ These rules relate to better ways of doing things to help you avoid problems:
330330

331331
| Rule ID | Description | |
332332
|:--------|:------------|:---|
333+
| [svelte/block-lang](https://ota-meshi.github.io/eslint-plugin-svelte/rules/block-lang/) | disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks. | |
333334
| [svelte/button-has-type](https://ota-meshi.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
334335
| [svelte/no-at-debug-tags](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
335336
| [svelte/no-reactive-functions](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-functions/) | it's not necessary to define functions in reactive statements | :bulb: |

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ These rules relate to better ways of doing things to help you avoid problems:
4949

5050
| Rule ID | Description | |
5151
|:--------|:------------|:---|
52+
| [svelte/block-lang](./rules/block-lang.md) | disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks. | |
5253
| [svelte/button-has-type](./rules/button-has-type.md) | disallow usage of button without an explicit type attribute | |
5354
| [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: |
5455
| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | it's not necessary to define functions in reactive statements | :bulb: |

docs/rules/block-lang.md

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
pageClass: "rule-details"
3+
sidebarDepth: 0
4+
title: "svelte/block-lang"
5+
description: "disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks."
6+
since: "v2.18.0"
7+
---
8+
9+
# svelte/block-lang
10+
11+
> disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks.
12+
13+
## :book: Rule Details
14+
15+
This rule enforces all svelte components to use the same set of languages for their scripts and styles.
16+
17+
<ESLintCodeBlock>
18+
19+
<!--eslint-skip-->
20+
21+
```svelte
22+
<!-- ✓ GOOD -->
23+
<script lang="ts">
24+
/* eslint svelte/block-lang: {"error", { "script": "ts" }} */
25+
</script>
26+
```
27+
28+
</ESLintCodeBlock>
29+
30+
<ESLintCodeBlock>
31+
32+
<!--eslint-skip-->
33+
34+
```svelte
35+
<!-- ✓ GOOD -->
36+
<script>
37+
/* eslint svelte/block-lang: {"error", { "script": ["ts", null], "style": "scss" }} */
38+
</script>
39+
40+
<style lang="scss">
41+
42+
</style>
43+
```
44+
45+
</ESLintCodeBlock>
46+
47+
<ESLintCodeBlock>
48+
49+
<!--eslint-skip-->
50+
51+
```svelte
52+
<!-- ✗ BAD -->
53+
<script>
54+
/* eslint svelte/block-lang: {"error", { "script": ["ts"] }} */
55+
</script>
56+
```
57+
58+
</ESLintCodeBlock>
59+
60+
## :wrench: Options
61+
62+
```json
63+
{
64+
"svelte/block-lang": [
65+
"error",
66+
{
67+
"enforceScriptPresent": true,
68+
"enforceStylePresent": false,
69+
"script": ["ts", null], // a list of languages or null to signify no language specified
70+
"style": "scss" // same as for script, a single value can be used instead of an array.
71+
}
72+
]
73+
}
74+
```
75+
76+
- `enforceScriptPresent` ... Whether to enforce the presence of a `<script>` block with one of the given languages. This may be useful as for example TypeScript checks some uses of a component if it is defined as being TypeScript. Default `false`.
77+
- `enforceStylePresent` ... Whether to enforce the presence of a `<style>` block with one of the given languages. Default `false`.
78+
- `script` ... A list of languages allowed for the `<script>` block. If `null` is included, no `lang` attribute is also allowed. A plain string or `null` can be used instead of one-item array. Default `null`.
79+
- `style` ... A list of languages allowed for the `<style>` block. If `null` is included, no `lang` attribute is also allowed. A plain string or `null` can be used instead of one-item array. Default `null`.
80+
81+
## :rocket: Version
82+
83+
This rule was introduced in eslint-plugin-svelte v2.18.0
84+
85+
## :mag: Implementation
86+
87+
- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/block-lang.ts)
88+
- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/block-lang.ts)

src/rules/block-lang.ts

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { createRule } from "../utils"
2+
import { getLangValue } from "../utils/ast-utils"
3+
import type {
4+
SvelteScriptElement,
5+
SvelteStyleElement,
6+
} from "svelte-eslint-parser/lib/ast"
7+
8+
export default createRule("block-lang", {
9+
meta: {
10+
docs: {
11+
description:
12+
"disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks.",
13+
category: "Best Practices",
14+
recommended: false,
15+
},
16+
schema: [
17+
{
18+
type: "object",
19+
properties: {
20+
enforceScriptPresent: {
21+
type: "boolean",
22+
},
23+
enforceStylePresent: {
24+
type: "boolean",
25+
},
26+
script: {
27+
oneOf: [
28+
{
29+
type: ["string", "null"],
30+
},
31+
{
32+
type: "array",
33+
items: {
34+
type: ["string", "null"],
35+
},
36+
minItems: 1,
37+
},
38+
],
39+
},
40+
style: {
41+
oneOf: [
42+
{
43+
type: ["string", "null"],
44+
},
45+
{
46+
type: "array",
47+
items: {
48+
type: ["string", "null"],
49+
},
50+
minItems: 1,
51+
},
52+
],
53+
},
54+
},
55+
additionalProperties: false,
56+
},
57+
],
58+
messages: {},
59+
type: "suggestion",
60+
},
61+
create(context) {
62+
const enforceScriptPresent: boolean =
63+
context.options[0]?.enforceScriptPresent ?? false
64+
const enforceStylePresent: boolean =
65+
context.options[0]?.enforceStylePresent ?? false
66+
67+
const scriptOption: string | null | (string | null)[] =
68+
context.options[0]?.script ?? null
69+
const allowedScriptLangs: (string | null)[] = Array.isArray(scriptOption)
70+
? scriptOption
71+
: [scriptOption]
72+
let scriptLang: string | null = null
73+
let scriptNode: SvelteScriptElement | undefined = undefined
74+
75+
const styleOption: string | null | (string | null)[] =
76+
context.options[0]?.style ?? null
77+
const allowedStyleLangs: (string | null)[] = Array.isArray(styleOption)
78+
? styleOption
79+
: [styleOption]
80+
let styleLang: string | null = null
81+
let styleNode: SvelteStyleElement | undefined = undefined
82+
83+
return {
84+
SvelteScriptElement(node) {
85+
scriptNode = node
86+
scriptLang = getLangValue(node)?.toLowerCase() ?? null
87+
},
88+
SvelteStyleElement(node) {
89+
styleNode = node
90+
styleLang = getLangValue(node)?.toLowerCase() ?? null
91+
},
92+
"Program:exit"() {
93+
if (!allowedScriptLangs.includes(scriptLang)) {
94+
if (scriptNode !== undefined) {
95+
context.report({
96+
node: scriptNode,
97+
message: `The lang attribute of the <script> block should be ${prettyPrintLangs(
98+
allowedScriptLangs,
99+
)}.`,
100+
})
101+
}
102+
}
103+
if (scriptNode === undefined && enforceScriptPresent) {
104+
context.report({
105+
loc: { line: 1, column: 1 },
106+
message: `The <script> block should be present and its lang attribute should be ${prettyPrintLangs(
107+
allowedScriptLangs,
108+
)}.`,
109+
})
110+
}
111+
if (!allowedStyleLangs.includes(styleLang)) {
112+
if (styleNode !== undefined) {
113+
context.report({
114+
node: styleNode,
115+
message: `The lang attribute of the <style> block should be ${prettyPrintLangs(
116+
allowedStyleLangs,
117+
)}.`,
118+
})
119+
}
120+
}
121+
if (styleNode === undefined && enforceStylePresent) {
122+
context.report({
123+
loc: { line: 1, column: 1 },
124+
message: `The <style> block should be present and its lang attribute should be ${prettyPrintLangs(
125+
allowedStyleLangs,
126+
)}.`,
127+
})
128+
}
129+
},
130+
}
131+
},
132+
})
133+
134+
/**
135+
* Prints the list of allowed languages, with special handling of the `null` option.
136+
*/
137+
function prettyPrintLangs(langs: (string | null)[]): string {
138+
const hasNull = langs.includes(null)
139+
const nonNullLangs = langs
140+
.filter((lang) => lang !== null)
141+
.map((lang) => `"${lang}"`)
142+
if (nonNullLangs.length === 0) {
143+
// No special behaviour for `hasNull`, because that can never happen.
144+
return "omitted"
145+
}
146+
const hasNullText = hasNull ? "either omitted or " : ""
147+
const nonNullText =
148+
nonNullLangs.length === 1
149+
? nonNullLangs[0]
150+
: `one of ${nonNullLangs.join(", ")}`
151+
return hasNullText + nonNullText
152+
}

src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RuleModule } from "../types"
22
import typescriptEslintNoUnnecessaryCondition from "../rules/@typescript-eslint/no-unnecessary-condition"
3+
import blockLang from "../rules/block-lang"
34
import buttonHasType from "../rules/button-has-type"
45
import commentDirective from "../rules/comment-directive"
56
import derivedHasSameInputsOutputs from "../rules/derived-has-same-inputs-outputs"
@@ -54,6 +55,7 @@ import validPropNamesInKitPages from "../rules/valid-prop-names-in-kit-pages"
5455

5556
export const rules = [
5657
typescriptEslintNoUnnecessaryCondition,
58+
blockLang,
5759
buttonHasType,
5860
commentDirective,
5961
derivedHasSameInputsOutputs,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "enforceScriptPresent": true }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- message: The <script> block should be present and its lang attribute should be
2+
omitted.
3+
line: 1
4+
column: 2
5+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<b>Hello World!</b>
2+
3+
<style></style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "script": ["javascript"], "style": ["javascript", null] }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "javascript".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script></script>
2+
3+
<style lang="javascript"></style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "javascript".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="js"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "javascript".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "javascript".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="ts"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "javascript".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="typescript"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "script": ["js"], "style": ["js", null] }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "js".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="javascript"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "js".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script></script>
2+
3+
<style lang="js"></style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "js".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "js".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="ts"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- message: The lang attribute of the <script> block should be "js".
2+
line: 1
3+
column: 1
4+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="typescript"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"options": [
3+
{
4+
"script": ["ts", "typescript", null],
5+
"style": ["ts", "typescript", null]
6+
}
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- message: The lang attribute of the <script> block should be either omitted or
2+
one of "ts", "typescript".
3+
line: 1
4+
column: 1
5+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="javascript"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- message: The lang attribute of the <script> block should be either omitted or
2+
one of "ts", "typescript".
3+
line: 1
4+
column: 1
5+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<script lang="js"></script>

0 commit comments

Comments
 (0)