diff --git a/.changeset/onwarn.md b/.changeset/onwarn.md new file mode 100644 index 000000000..3054ff559 --- /dev/null +++ b/.changeset/onwarn.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat: improve `svelte/valid-compile` to use `svelte.config.js`'s `onwarn` from the parser. diff --git a/docs/rules/valid-compile.md b/docs/rules/valid-compile.md index 920d072ba..1efdf6f49 100644 --- a/docs/rules/valid-compile.md +++ b/docs/rules/valid-compile.md @@ -37,6 +37,45 @@ This rule uses Svelte compiler to check the source code. Note that we exclude reports for some checks, such as `missing-declaration`, and `dynamic-slot-name`, which you can check with different ESLint rules. +### Using `svelte.config.js` + +If you want to suppress messages using [`onwarn` like `vite-plugin-svelte`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#onwarn), Use `eslint.config.js` and specify the information in `svelte.config.js` in your parser configuration. + +```js +import svelteConfig from './svelte.config.js'; +export default [ + // ... + { + files: ['**/*.svelte', '*.svelte'], + languageOptions: { + parserOptions: { + svelteConfig: svelteConfig + } + } + } +]; +``` + +See also [User Guide > Specify `svelte.config.js`](../user-guide.md#specify-svelte-config-js) + +#### onwarn + +This rule can use [`onwarn` like `vite-plugin-svelte`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#onwarn). + +Example: + +```js +// svelte.config.js +export default { + onwarn: (warning, handler) => { + if (warning.code === 'a11y-distracting-elements') return; + if (warning.code === 'a11y_distracting_elements') return; // for Svelte v5 + + handler(warning); + } +}; +``` + ## :wrench: Options ```json diff --git a/packages/eslint-plugin-svelte/src/rules/valid-compile.ts b/packages/eslint-plugin-svelte/src/rules/valid-compile.ts index e56e6ec7b..e202164f3 100644 --- a/packages/eslint-plugin-svelte/src/rules/valid-compile.ts +++ b/packages/eslint-plugin-svelte/src/rules/valid-compile.ts @@ -1,5 +1,5 @@ import { createRule } from '../utils'; -import type { Warning } from '../shared/svelte-compile-warns'; +import type { SvelteCompileWarnings, Warning } from '../shared/svelte-compile-warns'; import { getSvelteCompileWarnings } from '../shared/svelte-compile-warns'; import { getSourceCode } from '../utils/compat'; @@ -23,9 +23,21 @@ export default createRule('valid-compile', { type: 'problem' }, create(context) { - if (!getSourceCode(context).parserServices.isSvelte) { + const sourceCode = getSourceCode(context); + if (!sourceCode.parserServices.isSvelte) { return {}; } + const onwarn = sourceCode.parserServices.svelteParseContext?.svelteConfig?.onwarn; + + const transform: (warning: Warning) => Warning | null = onwarn + ? (warning) => { + if (!warning.code) return warning; + let result: Warning | null = null; + onwarn(warning, (reportWarn) => (result = reportWarn)); + return result; + } + : (warning) => warning; + const ignoreWarnings = Boolean(context.options[0]?.ignoreWarnings); const ignores = [ @@ -39,17 +51,21 @@ export default createRule('valid-compile', { /** * report */ - function report(warnings: Warning[]) { + function report({ warnings, kind }: SvelteCompileWarnings) { for (const warn of warnings) { if (warn.code && ignores.includes(warn.code)) { continue; } + const reportWarn = kind === 'warn' ? transform(warn) : warn; + if (!reportWarn) { + continue; + } context.report({ loc: { - start: warn.start || warn.end || { line: 1, column: 0 }, - end: warn.end || warn.start || { line: 1, column: 0 } + start: reportWarn.start || reportWarn.end || { line: 1, column: 0 }, + end: reportWarn.end || reportWarn.start || { line: 1, column: 0 } }, - message: `${warn.message}${warn.code ? `(${warn.code})` : ''}` + message: `${reportWarn.message}${reportWarn.code ? `(${reportWarn.code})` : ''}` }); } } @@ -60,7 +76,7 @@ export default createRule('valid-compile', { if (ignoreWarnings && result.kind === 'warn') { return; } - report(result.warnings); + report(result); } }; } diff --git a/packages/eslint-plugin-svelte/src/shared/svelte-compile-warns/index.ts b/packages/eslint-plugin-svelte/src/shared/svelte-compile-warns/index.ts index d5e3bcad4..01cc5449b 100644 --- a/packages/eslint-plugin-svelte/src/shared/svelte-compile-warns/index.ts +++ b/packages/eslint-plugin-svelte/src/shared/svelte-compile-warns/index.ts @@ -1,5 +1,4 @@ import type { AST } from 'svelte-eslint-parser'; -import type {} from 'svelte'; // FIXME: Workaround to get type information for "svelte/compiler" import * as compiler from 'svelte/compiler'; import type { SourceMapMappings } from '@jridgewell/sourcemap-codec'; import { decode } from '@jridgewell/sourcemap-codec'; @@ -60,16 +59,25 @@ export type Loc = { start?: { line: number; column: number; + character: number; }; end?: { line: number; column: number; + character: number; }; }; -export type Warning = { - code?: string; - message: string; -} & Loc; +export type Warning = ( + | { + code: string; + message: string; + } + | { + code?: undefined; + message: string; + } +) & + Loc; /** * Get svelte compile warnings @@ -228,51 +236,24 @@ function getSvelteCompileWarningsWithoutCache(context: RuleContext): SvelteCompi } } - public remapLocs(points: { - start?: { - line: number; - column: number; - }; - end?: { - line: number; - column: number; - }; - }): { - start?: { - line: number; - column: number; - }; - end?: { - line: number; - column: number; - }; - } { + public remapLocs(points: Loc): Loc { const mapIndexes = this.mapIndexes; const locs = (this.locs = this.locs ?? new LinesAndColumns(this.code)); - let start: - | { - line: number; - column: number; - } - | undefined = undefined; - let end: - | { - line: number; - column: number; - } - | undefined = undefined; + let start: Loc['start'] | undefined = undefined; + let end: Loc['end'] | undefined = undefined; if (points.start) { const index = locs.getIndexFromLoc(points.start); const remapped = remapIndex(index); if (remapped) { - start = sourceCode.getLocFromIndex(remapped); + start = { ...sourceCode.getLocFromIndex(remapped), character: remapped }; } } if (points.end) { const index = locs.getIndexFromLoc(points.end); const remapped = remapIndex(index - 1 /* include index */); if (remapped) { - end = sourceCode.getLocFromIndex(remapped + 1 /* restore */); + const character = remapped + 1; /* restore */ + end = { ...sourceCode.getLocFromIndex(character), character }; } } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/_config.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/_config.js new file mode 100644 index 000000000..ffe2c0ded --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/_config.js @@ -0,0 +1,20 @@ +/** + * @typedef {import("svelte/compiler").Warning} Warning + */ +module.exports = { + languageOptions: { + parserOptions: { + svelteConfig: { + /** + * @param {Warning} warning + * @param {(warning: Warning) => void} handler + * @returns {void} + */ + onwarn(warning, handler) { + // transform code + handler({ ...warning, code: 'foo' }); + } + } + } + } +}; diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-errors.yaml new file mode 100644 index 000000000..cda864620 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-errors.yaml @@ -0,0 +1,8 @@ +- message: '`` element should have an alt attribute(foo)' + line: 5 + column: 1 + suggestions: null +- message: Avoid using autofocus(foo) + line: 5 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-input.svelte new file mode 100644 index 000000000..ba1d8baf5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-input.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-errors.yaml new file mode 100644 index 000000000..96be2047d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-errors.yaml @@ -0,0 +1,8 @@ +- message: 'A11y: element should have an alt attribute(foo)' + line: 5 + column: 1 + suggestions: null +- message: 'A11y: Avoid using autofocus(foo)' + line: 5 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-input.svelte new file mode 100644 index 000000000..ba1d8baf5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-input.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-requirements.json new file mode 100644 index 000000000..b650cc296 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-custom-warn/a11y-svelte4-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": "^3.0.0 || ^4.0.0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/_config.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/_config.js new file mode 100644 index 000000000..293325f3a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/_config.js @@ -0,0 +1,24 @@ +/** + * @typedef {import("svelte/compiler").Warning} Warning + */ +module.exports = { + languageOptions: { + parserOptions: { + svelteConfig: { + /** + * @param {Warning} warning + * @param {(warning: Warning) => void} handler + * @returns {void} + */ + onwarn(warning, handler) { + if ( + warning.code === 'a11y_missing_attribute' || + warning.code === 'a11y-missing-attribute' + ) + return; + handler(warning); + } + } + } + } +}; diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-errors.yaml new file mode 100644 index 000000000..00fe33d44 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-errors.yaml @@ -0,0 +1,4 @@ +- message: Avoid using autofocus(a11y_autofocus) + line: 5 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-input.svelte new file mode 100644 index 000000000..ba1d8baf5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-input.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-errors.yaml new file mode 100644 index 000000000..2b5f040ec --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-errors.yaml @@ -0,0 +1,4 @@ +- message: 'A11y: Avoid using autofocus(a11y-autofocus)' + line: 5 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-input.svelte new file mode 100644 index 000000000..ba1d8baf5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-input.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-requirements.json new file mode 100644 index 000000000..b650cc296 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/invalid/svelte-config-ignore/a11y-svelte4-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": "^3.0.0 || ^4.0.0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/valid/svelte-config-ignore/_config.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/valid/svelte-config-ignore/_config.js new file mode 100644 index 000000000..293325f3a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/valid/svelte-config-ignore/_config.js @@ -0,0 +1,24 @@ +/** + * @typedef {import("svelte/compiler").Warning} Warning + */ +module.exports = { + languageOptions: { + parserOptions: { + svelteConfig: { + /** + * @param {Warning} warning + * @param {(warning: Warning) => void} handler + * @returns {void} + */ + onwarn(warning, handler) { + if ( + warning.code === 'a11y_missing_attribute' || + warning.code === 'a11y-missing-attribute' + ) + return; + handler(warning); + } + } + } + } +}; diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/valid/svelte-config-ignore/a11y-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/valid/svelte-config-ignore/a11y-input.svelte new file mode 100644 index 000000000..d7954e785 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/valid-compile/valid/svelte-config-ignore/a11y-input.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/eslint-plugin-svelte/tests/utils/utils.ts b/packages/eslint-plugin-svelte/tests/utils/utils.ts index 202a97896..91663b2d3 100644 --- a/packages/eslint-plugin-svelte/tests/utils/utils.ts +++ b/packages/eslint-plugin-svelte/tests/utils/utils.ts @@ -289,12 +289,16 @@ function getConfig(ruleName: string, inputFile: string) { const filename = inputFile.slice(inputFile.indexOf(ruleName)); const code = fs.readFileSync(inputFile, 'utf8'); let config; - let configFile: string = inputFile.replace(/input\.[a-z]+$/u, 'config.json'); - if (!fs.existsSync(configFile)) { - configFile = path.join(path.dirname(inputFile), '_config.json'); - } - if (fs.existsSync(configFile)) { - config = JSON.parse(fs.readFileSync(configFile, 'utf8')); + let configFile = [ + inputFile.replace(/input\.[a-z]+$/u, 'config.json'), + path.join(path.dirname(inputFile), '_config.json'), + inputFile.replace(/input\.[a-z]+$/u, 'config.js'), + path.join(path.dirname(inputFile), '_config.js') + ].find((f) => fs.existsSync(f)); + if (configFile) { + config = configFile.endsWith('.js') + ? require(configFile) + : JSON.parse(fs.readFileSync(configFile, 'utf8')); } const parser = path.extname(filename) === '.svelte'