Skip to content

Commit

Permalink
feat: add support for flat config (#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Mar 18, 2024
1 parent 22d25ca commit d597a69
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-apricots-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-svelte": minor
---

feat: add support for flat config
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ module.exports = {
'mdx/code-blocks': true
}
},
{
files: ['*.md/**', '**/*.md/**'],
rules: {
'n/no-missing-import': 'off'
}
},
{
files: ['*.mjs'],
parserOptions: {
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,38 @@ npm install --save-dev eslint eslint-plugin-svelte svelte

### Configuration

#### For ESLint>=v9 Config (Flat Config)

Use `eslint.config.js` file to configure rules. See also: <https://eslint.org/docs/latest/use/configure/configuration-files-new>.

Example **eslint.config.js**:

```mjs
import eslintPluginSvelte from 'eslint-plugin-svelte';
export default [
// add more generic rule sets here, such as:
// js.configs.recommended,
...eslintPluginSvelte.configs['flat/recommended'],
{
rules: {
// override/add rules settings here, such as:
// 'svelte/rule-name': 'error'
}
}
];
```

This plugin provides configs:

- `eslintPluginSvelte.configs['flat/base']` ... Configuration to enable correct Svelte parsing.
- `eslintPluginSvelte.configs['flat/recommended']` ... Above, plus rules to prevent errors or unintended behavior.
- `eslintPluginSvelte.configs['flat/prettier']` ... Turns off rules that may conflict with [Prettier](https://prettier.io/) (You still need to configure prettier to work with svelte yourself, for example by using [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte).).
- `eslintPluginSvelte.configs['flat/all']` ... All rules. This configuration is not recommended for production use because it changes with every minor and major version of `eslint-plugin-svelte`. Use it at your own risk.

See [the rule list](https://sveltejs.github.io/eslint-plugin-svelte/rules/) to get the `rules` that this plugin provides.

#### Legacy Config (ESLint<v9)

Use `.eslintrc.*` file to configure rules. See also: <https://eslint.org/docs/user-guide/configuring>.

Example **.eslintrc.js**:
Expand Down
32 changes: 32 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,38 @@ npm install --save-dev eslint eslint-plugin-svelte svelte

### Configuration

#### For ESLint>=v9 Config (Flat Config)

Use `eslint.config.js` file to configure rules. See also: <https://eslint.org/docs/latest/use/configure/configuration-files-new>.

Example **eslint.config.js**:

```mjs
import eslintPluginSvelte from 'eslint-plugin-svelte';
export default [
// add more generic rule sets here, such as:
// js.configs.recommended,
...eslintPluginSvelte.configs['flat/recommended'],
{
rules: {
// override/add rules settings here, such as:
// 'svelte/rule-name': 'error'
}
}
];
```

This plugin provides configs:

- `eslintPluginSvelte.configs['flat/base']` ... Configuration to enable correct Svelte parsing.
- `eslintPluginSvelte.configs['flat/recommended']` ... Above, plus rules to prevent errors or unintended behavior.
- `eslintPluginSvelte.configs['flat/prettier']` ... Turns off rules that may conflict with [Prettier](https://prettier.io/) (You still need to configure prettier to work with svelte yourself, for example by using [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte).).
- `eslintPluginSvelte.configs['flat/all']` ... All rules. This configuration is not recommended for production use because it changes with every minor and major version of `eslint-plugin-svelte`. Use it at your own risk.

See [the rule list](./rules.md) to get the `rules` that this plugin provides.

#### Legacy Config (ESLint<v9)

Use `.eslintrc.*` file to configure rules. See also: <https://eslint.org/docs/user-guide/configuring>.

Example **.eslintrc.js**:
Expand Down
18 changes: 18 additions & 0 deletions src/configs/flat/all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { rules } from '../../utils/rules';
import base from './base';
export default [
...base,
{
rules: Object.fromEntries(
rules
.map((rule) => [`svelte/${rule.meta.docs.ruleName}`, 'error'])
.filter(
([ruleName]) =>
![
// Does not work without options.
'svelte/no-restricted-html-elements'
].includes(ruleName)
)
)
}
];
30 changes: 30 additions & 0 deletions src/configs/flat/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "pnpm run update"
import type { ESLint } from 'eslint';
export default [
{
files: ['*.svelte', '**/*.svelte'],
plugins: {
get svelte(): ESLint.Plugin {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
return require('../../index');
}
},
languageOptions: {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore
parser: require('svelte-eslint-parser')
},
rules: {
// ESLint core rules known to cause problems with `.svelte`.
'no-inner-declarations': 'off', // The AST generated by svelte-eslint-parser will false positives in it rule because the root node of the script is not the `Program`.
// "no-irregular-whitespace": "off",
// Self assign is one of way to update reactive value in Svelte.
'no-self-assign': 'off',

// eslint-plugin-svelte rules
'svelte/comment-directive': 'error',
'svelte/system': 'error'
}
}
];
23 changes: 23 additions & 0 deletions src/configs/flat/prettier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "pnpm run update"
import base from './base';
export default [
...base,
{
rules: {
// eslint-plugin-svelte rules
'svelte/first-attribute-linebreak': 'off',
'svelte/html-closing-bracket-spacing': 'off',
'svelte/html-quotes': 'off',
'svelte/html-self-closing': 'off',
'svelte/indent': 'off',
'svelte/max-attributes-per-line': 'off',
'svelte/mustache-spacing': 'off',
'svelte/no-spaces-around-equal-signs-in-attribute': 'off',
'svelte/no-trailing-spaces': 'off',
'svelte/shorthand-attribute': 'off',
'svelte/shorthand-directive': 'off'
}
}
];
26 changes: 26 additions & 0 deletions src/configs/flat/recommended.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// IMPORTANT!
// This file has been automatically generated,
// in order to update its content execute "pnpm run update"
import base from './base';
export default [
...base,
{
rules: {
// eslint-plugin-svelte rules
'svelte/comment-directive': 'error',
'svelte/no-at-debug-tags': 'warn',
'svelte/no-at-html-tags': 'error',
'svelte/no-dupe-else-if-blocks': 'error',
'svelte/no-dupe-style-properties': 'error',
'svelte/no-dynamic-slot-name': 'error',
'svelte/no-inner-declarations': 'error',
'svelte/no-not-function-handler': 'error',
'svelte/no-object-in-text-mustaches': 'error',
'svelte/no-shorthand-style-property-overrides': 'error',
'svelte/no-unknown-style-directive-property': 'error',
'svelte/no-unused-svelte-ignore': 'error',
'svelte/system': 'error',
'svelte/valid-compile': 'error'
}
}
];
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import base from './configs/base';
import recommended from './configs/recommended';
import prettier from './configs/prettier';
import all from './configs/all';
import flatBase from './configs/flat/base';
import flatRecommended from './configs/flat/recommended';
import flatPrettier from './configs/flat/prettier';
import flatAll from './configs/flat/all';
import * as processor from './processor';
import * as meta from './meta';

const configs = {
base,
recommended,
prettier,
all
all,
'flat/base': flatBase,
'flat/recommended': flatRecommended,
'flat/prettier': flatPrettier,
'flat/all': flatAll
};

const rules = ruleList.reduce(
Expand Down
30 changes: 27 additions & 3 deletions tests/src/configs/all.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import assert from 'assert';
import semver from 'semver';
import plugin from '../../../src/index';
import { LegacyESLint } from '../../utils/eslint-compat';
import { LegacyESLint, ESLint } from '../../utils/eslint-compat';

describe('`all` config', () => {
it('`all` config should work. ', async () => {
it('legacy `all` config should work. ', async () => {
const code = `<script>const a = 1, b = 2;</script>{@html a+b}`;

const linter = new LegacyESLint({
Expand All @@ -22,10 +23,33 @@ describe('`all` config', () => {
const messages = result[0].messages;

assert.deepStrictEqual(
messages.map((m) => ({ ruleId: m.ruleId, line: m.line })),
messages.map((m) => ({ ruleId: m.ruleId, line: m.line, message: m.message })),
[
{
ruleId: 'svelte/no-at-html-tags',
message: '`{@html}` can lead to XSS attack.',
line: 1
}
]
);
});
it('`all` config should work. ', async () => {
if (semver.satisfies(ESLint.version, '<8.0.0')) return;
const code = `<script>const a = 1, b = 2;</script>{@html a+b}`;

const linter = new ESLint({
overrideConfigFile: true as never,
overrideConfig: plugin.configs['flat/all'] as never
});
const result = await linter.lintText(code, { filePath: 'test.svelte' });
const messages = result[0].messages;

assert.deepStrictEqual(
messages.map((m) => ({ ruleId: m.ruleId, line: m.line, message: m.message })),
[
{
ruleId: 'svelte/no-at-html-tags',
message: '`{@html}` can lead to XSS attack.',
line: 1
}
]
Expand Down
58 changes: 58 additions & 0 deletions tests/src/configs/recommended.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import assert from 'assert';
import semver from 'semver';
import plugin from '../../../src/index';
import { LegacyESLint, ESLint } from '../../utils/eslint-compat';

describe('`all` config', () => {
it('legacy `all` config should work. ', async () => {
const code = `<script>const a = 1, b = 2;</script>{@html a+b}`;

const linter = new LegacyESLint({
plugins: {
svelte: plugin as never
},
baseConfig: {
parserOptions: {
ecmaVersion: 2020
},
extends: ['plugin:svelte/recommended']
},
useEslintrc: false
});
const result = await linter.lintText(code, { filePath: 'test.svelte' });
const messages = result[0].messages;

assert.deepStrictEqual(
messages.map((m) => ({ ruleId: m.ruleId, line: m.line, message: m.message })),
[
{
ruleId: 'svelte/no-at-html-tags',
message: '`{@html}` can lead to XSS attack.',
line: 1
}
]
);
});
it('`all` config should work. ', async () => {
if (semver.satisfies(ESLint.version, '<8.0.0')) return;
const code = `<script>const a = 1, b = 2;</script>{@html a+b}`;

const linter = new ESLint({
overrideConfigFile: true as never,
overrideConfig: plugin.configs['flat/recommended'] as never
});
const result = await linter.lintText(code, { filePath: 'test.svelte' });
const messages = result[0].messages;

assert.deepStrictEqual(
messages.map((m) => ({ ruleId: m.ruleId, line: m.line, message: m.message })),
[
{
ruleId: 'svelte/no-at-html-tags',
message: '`{@html}` can lead to XSS attack.',
line: 1
}
]
);
});
});

0 comments on commit d597a69

Please sign in to comment.