Skip to content
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>
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="typescript"></style>
- desc: Replace a <script> block with the lang attribute set to "typescript".
output: |
<script lang="typescript"></script>

<style lang="typescript"></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 omitted.
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute omitted.
output: |
<script></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 omitted.
line: 1
column: 1
suggestions: null
suggestions:
- desc: Replace a <script> block with the lang attribute omitted.
output: |
<script></script>
Loading
Loading