Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add fixes for block-lang rule #844

Merged
merged 15 commits into from
Dec 30, 2024
5 changes: 5 additions & 0 deletions .changeset/fifty-parents-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-svelte': minor
---

feat: Added suggestion to the `block-lang` rule.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ These rules relate to better ways of doing things to help you avoid problems:

| Rule ID | Description | |
|:--------|:------------|:---|
| [svelte/block-lang](https://sveltejs.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. | |
| [svelte/block-lang](https://sveltejs.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. | :bulb: |
| [svelte/button-has-type](https://sveltejs.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
| [svelte/no-at-debug-tags](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
| [svelte/no-ignored-unsubscribe](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-ignored-unsubscribe/) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
Expand Down
2 changes: 1 addition & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ These rules relate to better ways of doing things to help you avoid problems:

| Rule ID | Description | |
| :--------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :------- |
| [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. | |
| [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. | :bulb: |
| [svelte/button-has-type](./rules/button-has-type.md) | disallow usage of button without an explicit type attribute | |
| [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: |
| [svelte/no-ignored-unsubscribe](./rules/no-ignored-unsubscribe.md) | disallow ignoring the unsubscribe method returned by the `subscribe()` on Svelte stores. | |
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/block-lang.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ since: 'v2.18.0'

> disallows the use of languages other than those specified in the configuration for the lang attribute of `<script>` and `<style>` blocks.

- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).

## :book: Rule Details

This rule enforces all svelte components to use the same set of languages for their scripts and styles.
Expand Down
92 changes: 85 additions & 7 deletions packages/eslint-plugin-svelte/src/rules/block-lang.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createRule } from '../utils/index.js';
import { getLangValue } from '../utils/ast-utils.js';
import { findAttribute, getLangValue } from '../utils/ast-utils.js';
import type { SvelteScriptElement, SvelteStyleElement } from 'svelte-eslint-parser/lib/ast';
import { getSourceCode } from '../utils/compat.js';
import type { SuggestionReportDescriptor, SourceCode } from '../types.js';

export default createRule('block-lang', {
meta: {
Expand Down Expand Up @@ -54,7 +55,8 @@ export default createRule('block-lang', {
}
],
messages: {},
type: 'suggestion'
type: 'suggestion',
hasSuggestions: true
},
create(context) {
if (!getSourceCode(context).parserServices.isSvelte) {
Expand Down Expand Up @@ -88,7 +90,8 @@ export default createRule('block-lang', {
loc: { line: 1, column: 1 },
message: `The <script> block should be present and its lang attribute should be ${prettyPrintLangs(
allowedScriptLangs
)}.`
)}.`,
suggest: buildAddLangSuggestions(allowedScriptLangs, 'script', getSourceCode(context))
});
}
for (const scriptNode of scriptNodes) {
Expand All @@ -97,16 +100,19 @@ export default createRule('block-lang', {
node: scriptNode,
message: `The lang attribute of the <script> block should be ${prettyPrintLangs(
allowedScriptLangs
)}.`
)}.`,
suggest: buildReplaceLangSuggestions(allowedScriptLangs, scriptNode)
});
}
}
if (styleNodes.length === 0 && enforceStylePresent) {
const sourceCode = getSourceCode(context);
context.report({
loc: { line: 1, column: 1 },
message: `The <style> block should be present and its lang attribute should be ${prettyPrintLangs(
allowedStyleLangs
)}.`
)}.`,
suggest: buildAddLangSuggestions(allowedStyleLangs, 'style', sourceCode)
});
}
for (const styleNode of styleNodes) {
Expand All @@ -115,7 +121,8 @@ export default createRule('block-lang', {
node: styleNode,
message: `The lang attribute of the <style> block should be ${prettyPrintLangs(
allowedStyleLangs
)}.`
)}.`,
suggest: buildReplaceLangSuggestions(allowedStyleLangs, styleNode)
});
}
}
Expand All @@ -124,18 +131,89 @@ export default createRule('block-lang', {
}
});

function buildAddLangSuggestions(
langs: (string | null)[],
tagName: 'script' | 'style',
sourceCode: SourceCode
): SuggestionReportDescriptor[] {
return langs
.filter((lang) => lang != null && lang !== '')
.map((lang) => {
return {
desc: `Add a lang attribute to a <${tagName}> block with the value "${lang}".`,
fix: (fixer) => {
const langAttributeText = getLangAttributeText(lang ?? '', true);
return fixer.insertTextAfterRange(
tagName === 'script' ? [0, 0] : [sourceCode.text.length, sourceCode.text.length],
`<${tagName}${langAttributeText}>\n</${tagName}>\n\n`
);
}
};
});
}

function buildReplaceLangSuggestions(
langs: (string | null)[],
node: SvelteScriptElement | SvelteStyleElement
): SuggestionReportDescriptor[] {
const tagName = node.name.name;
const langAttribute = findAttribute(node, 'lang');
const filteredLangs = langs.filter((lang) => lang != null && lang !== '');

if (filteredLangs.length === 0 && langs.includes(null) && langAttribute !== null) {
return [
{
desc: `Replace a <${tagName}> block with the lang attribute omitted.`,
fix: (fixer) => {
return fixer.remove({
type: langAttribute.type,
range: [langAttribute.range[0] - 1, langAttribute.range[1]]
});
}
}
];
}
return filteredLangs.map((lang) => {
const langAttributeText = getLangAttributeText(lang ?? '', true);
if (langAttribute) {
return {
desc: `Replace a <${tagName}> block with the lang attribute set to "${lang}".`,
fix: (fixer) => {
return fixer.replaceText(langAttribute, langAttributeText.trim());
}
};
}
return {
desc: `Add lang attribute to a <${tagName}> block with the value "${lang}".`,
fix: (fixer) => {
return fixer.insertTextBeforeRange(
[node.startTag.range[0] + tagName.length + 1, 0],
langAttributeText
);
}
};
});
}

/**
* Prints the list of allowed languages, with special handling of the `null` option.
*/
function prettyPrintLangs(langs: (string | null)[]): string {
const hasNull = langs.includes(null);
const nonNullLangs = langs.filter((lang) => lang !== null).map((lang) => `"${lang}"`);
if (nonNullLangs.length === 0) {
// No special behaviour for `hasNull`, because that can never happen.
// No special behavior for `hasNull`, because that can never happen.
return 'omitted';
}
const hasNullText = hasNull ? 'either omitted or ' : '';
const nonNullText =
nonNullLangs.length === 1 ? nonNullLangs[0] : `one of ${nonNullLangs.join(', ')}`;
return hasNullText + nonNullText;
}

/**
* Returns the lang attribute text, with special handling of the `null` lang option with respect to the `prependWhitespace` argument.
*/
function getLangAttributeText(lang: string, prependWhitespace: boolean): string {
return `${prependWhitespace ? ' ' : ''}lang="${lang}"`;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
- message: The lang attribute of the <script> block should be "javascript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Add lang attribute to a <script> block with the value "javascript".
output: |
<script lang="javascript"></script>

<style lang="javascript"></style>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. I will fix this but I need to go outside now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon carefully reviewing the test cases, this is correct.😇

Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "javascript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "javascript".
output: |
<script lang="javascript"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "javascript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Add lang attribute to a <script> block with the value "javascript".
output: |
<script lang="javascript"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "javascript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "javascript".
output: |
<script lang="javascript"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "javascript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "javascript".
output: |
<script lang="javascript"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "js".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "js".
output: |
<script lang="js"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
- message: The lang attribute of the <script> block should be "js".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Add lang attribute to a <script> block with the value "js".
output: |
<script lang="js"></script>

<style lang="js"></style>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "js".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Add lang attribute to a <script> block with the value "js".
output: |
<script lang="js"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "js".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "js".
output: |
<script lang="js"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
- message: The lang attribute of the <script> block should be "js".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "js".
output: |
<script lang="js"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
- message: The lang attribute of the <script> block should be "ts".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script context="module" lang="ts"></script>

<script lang="ts"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
- message: The lang attribute of the <script> block should be "ts".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script context="module" lang="ts"></script>

<script lang="ts"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
- message: The lang attribute of the <script> block should be "ts".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Add lang attribute to a <script> block with the value "ts".
output: |
<script lang="ts" context="module"></script>

<script lang="ts"></script>
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
- message: The lang attribute of the <script> block should be "ts".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Add lang attribute to a <script> block with the value "ts".
output: |
<script lang="ts" context="module"></script>

<script lang="ts"></script>

<style lang="ts"></style>
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
- message: The lang attribute of the <script> block should be "ts".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script context="module" lang="ts"></script>

<script lang="ts"></script>
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@
one of "ts", "typescript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script lang="ts"></script>
- desc: Replace a <script> block with the lang attribute set to "typescript".
output: |
<script lang="typescript"></script>
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@
one of "ts", "typescript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script lang="ts"></script>
- desc: Replace a <script> block with the lang attribute set to "typescript".
output: |
<script lang="typescript"></script>
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
one of "ts", "typescript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script lang="ts"></script>

<style></style>
- desc: Replace a <script> block with the lang attribute set to "typescript".
output: |
<script lang="typescript"></script>

<style></style>
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
one of "ts", "typescript".
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute set to "ts".
output: |
<script lang="ts"></script>

<style lang="ts"></style>
- desc: Replace a <script> block with the lang attribute set to "typescript".
output: |
<script lang="typescript"></script>

<style lang="ts"></style>
Loading
Loading