@st1ggy/linter-config 7.3.0 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +88 -7
  2. package/data/oxlint-config-inventory.json +2091 -0
  3. package/data/oxlint-rule-map.json +91859 -0
  4. package/data/oxlint-source-inventory.json +107728 -0
  5. package/docs/OXLINT_RULE_MAPPING.md +2968 -0
  6. package/docs/RULE_MAPPING.md +15 -0
  7. package/package.json +55 -3
  8. package/scripts/README.md +33 -1
  9. package/scripts/init-astro-ox.sh +4 -0
  10. package/scripts/init-common-ox.sh +4 -0
  11. package/scripts/init-next-ox.sh +4 -0
  12. package/scripts/init-react-ox.sh +4 -0
  13. package/scripts/init-solid-ox.sh +4 -0
  14. package/scripts/init-svelte-ox.sh +4 -0
  15. package/scripts/linter-init-core.mjs +80 -20
  16. package/scripts/linter-init.mjs +24 -32
  17. package/src/oxlint/configs/config.astro.js +4092 -0
  18. package/src/oxlint/configs/config.common.js +3209 -0
  19. package/src/oxlint/configs/config.next.js +3592 -0
  20. package/src/oxlint/configs/config.react.js +3503 -0
  21. package/src/oxlint/configs/config.solid.js +4986 -0
  22. package/src/oxlint/configs/config.svelte.js +3301 -0
  23. package/src/oxlint/create-config.js +40 -0
  24. package/src/oxlint/index.d.ts +49 -0
  25. package/src/oxlint/oxlint.config.astro.js +4 -0
  26. package/src/oxlint/oxlint.config.common.js +4 -0
  27. package/src/oxlint/oxlint.config.next.js +4 -0
  28. package/src/oxlint/oxlint.config.react.js +4 -0
  29. package/src/oxlint/oxlint.config.solid.js +4 -0
  30. package/src/oxlint/oxlint.config.svelte.js +4 -0
  31. package/src/oxlint/plugins/astro.js +41 -0
  32. package/src/oxlint/plugins/eslint-core.js +5 -0
  33. package/src/oxlint/plugins/fresh-source-code.js +30 -0
  34. package/src/oxlint/plugins/import-x.js +38 -0
  35. package/src/oxlint/plugins/next.js +1 -0
  36. package/src/oxlint/plugins/prettier.js +1 -0
  37. package/src/oxlint/plugins/react-hooks.js +1 -0
  38. package/src/oxlint/plugins/react.js +1 -0
  39. package/src/oxlint/plugins/solid.js +1 -0
  40. package/src/oxlint/plugins/sonarjs.js +5 -0
  41. package/src/oxlint/plugins/stylistic.js +1 -0
  42. package/src/oxlint/plugins/svelte.js +42 -0
  43. package/src/oxlint/plugins/typescript.js +1 -0
  44. package/src/oxlint/plugins/unicorn.js +5 -0
  45. package/src/oxlint/stacks.js +11 -0
  46. package/scripts/solid-config.test.mjs +0 -134
@@ -0,0 +1,40 @@
1
+ import { fileURLToPath } from 'node:url'
2
+
3
+ const pluginFiles = {
4
+ 'eslint-js': 'eslint-core',
5
+ 'stylistic-js': 'stylistic',
6
+ 'import-x-js': 'import-x',
7
+ prettier: 'prettier',
8
+ 'unicorn-js': 'unicorn',
9
+ sonarjs: 'sonarjs',
10
+ 'typescript-js': 'typescript',
11
+ 'react-js': 'react',
12
+ 'react-hooks-js': 'react-hooks',
13
+ 'next-js': 'next',
14
+ solid: 'solid',
15
+ svelte: 'svelte',
16
+ astro: 'astro',
17
+ }
18
+
19
+ export function createOxlintConfig(bundle) {
20
+ return {
21
+ categories: {
22
+ correctness: 'off',
23
+ suspicious: 'off',
24
+ pedantic: 'off',
25
+ perf: 'off',
26
+ style: 'off',
27
+ restriction: 'off',
28
+ nursery: 'off',
29
+ },
30
+ options: { typeAware: true },
31
+ ignorePatterns: ['**/node_modules/**', '**/.git/**'],
32
+ plugins: bundle.plugins,
33
+ settings: bundle.settings,
34
+ jsPlugins: bundle.jsPlugins.map((name) => ({
35
+ name,
36
+ specifier: fileURLToPath(new URL(`plugins/${pluginFiles[name]}.js`, import.meta.url)),
37
+ })),
38
+ overrides: bundle.overrides.map(({ files, profile }) => ({ files, ...bundle.profiles[profile] })),
39
+ }
40
+ }
@@ -0,0 +1,49 @@
1
+ // Keep Oxlint types in a separate ambient declaration file so existing ESLint
2
+ // consumers do not need to install the optional Oxlint peer.
3
+ declare module '@st1ggy/linter-config/common-ox' {
4
+ import type { OxlintConfig } from 'oxlint'
5
+
6
+ const config: OxlintConfig
7
+
8
+ export default config
9
+ }
10
+
11
+ declare module '@st1ggy/linter-config/react-ox' {
12
+ import type { OxlintConfig } from 'oxlint'
13
+
14
+ const config: OxlintConfig
15
+
16
+ export default config
17
+ }
18
+
19
+ declare module '@st1ggy/linter-config/solid-ox' {
20
+ import type { OxlintConfig } from 'oxlint'
21
+
22
+ const config: OxlintConfig
23
+
24
+ export default config
25
+ }
26
+
27
+ declare module '@st1ggy/linter-config/next-ox' {
28
+ import type { OxlintConfig } from 'oxlint'
29
+
30
+ const config: OxlintConfig
31
+
32
+ export default config
33
+ }
34
+
35
+ declare module '@st1ggy/linter-config/svelte-ox' {
36
+ import type { OxlintConfig } from 'oxlint'
37
+
38
+ const config: OxlintConfig
39
+
40
+ export default config
41
+ }
42
+
43
+ declare module '@st1ggy/linter-config/astro-ox' {
44
+ import type { OxlintConfig } from 'oxlint'
45
+
46
+ const config: OxlintConfig
47
+
48
+ export default config
49
+ }
@@ -0,0 +1,4 @@
1
+ import bundle from './configs/config.astro.js'
2
+ import { createOxlintConfig } from './create-config.js'
3
+
4
+ export default createOxlintConfig(bundle)
@@ -0,0 +1,4 @@
1
+ import bundle from './configs/config.common.js'
2
+ import { createOxlintConfig } from './create-config.js'
3
+
4
+ export default createOxlintConfig(bundle)
@@ -0,0 +1,4 @@
1
+ import bundle from './configs/config.next.js'
2
+ import { createOxlintConfig } from './create-config.js'
3
+
4
+ export default createOxlintConfig(bundle)
@@ -0,0 +1,4 @@
1
+ import bundle from './configs/config.react.js'
2
+ import { createOxlintConfig } from './create-config.js'
3
+
4
+ export default createOxlintConfig(bundle)
@@ -0,0 +1,4 @@
1
+ import bundle from './configs/config.solid.js'
2
+ import { createOxlintConfig } from './create-config.js'
3
+
4
+ export default createOxlintConfig(bundle)
@@ -0,0 +1,4 @@
1
+ import bundle from './configs/config.svelte.js'
2
+ import { createOxlintConfig } from './create-config.js'
3
+
4
+ export default createOxlintConfig(bundle)
@@ -0,0 +1,41 @@
1
+ import astro, { rules as astroRules } from 'eslint-plugin-astro'
2
+ import { readFileSync } from 'node:fs'
3
+
4
+ // Only the six script-only rules are enabled. The native linter still parses
5
+ // the JavaScript; this adapter neither creates a TS Program nor parses templates.
6
+ function frontmatterContext(context) {
7
+ const filename = context.physicalFilename ?? context.filename
8
+ const source = readFileSync(filename, 'utf8')
9
+ const frontmatter = /^\u{FEFF}?---[^\S\n]*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(source)?.[1]
10
+
11
+ if (frontmatter === undefined || frontmatter.trim() !== context.sourceCode.text.trim()) {
12
+ return null
13
+ }
14
+
15
+ const sourceCode = Object.create(context.sourceCode)
16
+ const wrapped = Object.create(context)
17
+
18
+ Object.defineProperty(sourceCode, 'parserServices', {
19
+ value: { ...context.sourceCode.parserServices, isAstro: true },
20
+ })
21
+ Object.defineProperty(wrapped, 'sourceCode', { value: sourceCode, configurable: true })
22
+
23
+ return wrapped
24
+ }
25
+
26
+ export default {
27
+ ...astro,
28
+ rules: Object.fromEntries(
29
+ Object.entries(astroRules).map(([name, rule]) => [
30
+ name,
31
+ {
32
+ ...rule,
33
+ create(context) {
34
+ const wrapped = frontmatterContext(context)
35
+
36
+ return wrapped ? rule.create(wrapped) : {}
37
+ },
38
+ },
39
+ ]),
40
+ ),
41
+ }
@@ -0,0 +1,5 @@
1
+ /* eslint-disable sonarjs/deprecation -- Only rule definitions are reused; Oxlint executes them without ESLint. */
2
+
3
+ import { builtinRules } from 'eslint/use-at-your-own-risk'
4
+
5
+ export default { rules: Object.fromEntries(builtinRules) }
@@ -0,0 +1,30 @@
1
+ // Some plugins key file-level caches by SourceCode identity. Give each rule
2
+ // invocation a fresh identity while preserving the native SourceCode methods.
3
+ export function withFreshSourceCode(plugin) {
4
+ return {
5
+ ...plugin,
6
+ rules: Object.fromEntries(
7
+ Object.entries(plugin.rules).map(([name, rule]) => [
8
+ name,
9
+ {
10
+ ...rule,
11
+ create(context) {
12
+ const sourceCode = new Proxy(context.sourceCode, {
13
+ get(target, property) {
14
+ return Reflect.get(target, property, target)
15
+ },
16
+ })
17
+ const wrapped = Object.create(context)
18
+
19
+ Object.defineProperties(wrapped, {
20
+ sourceCode: { value: sourceCode, configurable: true },
21
+ getSourceCode: { value: () => sourceCode, configurable: true },
22
+ })
23
+
24
+ return rule.create(wrapped)
25
+ },
26
+ },
27
+ ]),
28
+ ),
29
+ }
30
+ }
@@ -0,0 +1,38 @@
1
+ import importPlugin, { rules as importRules } from 'eslint-plugin-import-x'
2
+
3
+ function scopedContext(context) {
4
+ const scopes = context.settings['st1ggy/import-scopes'] ?? []
5
+ const selected = scopes.findLast((scope) => context.filename.endsWith(`.${scope.extension}`))
6
+
7
+ if (!selected) {
8
+ return context
9
+ }
10
+
11
+ const defaults = context.settings['st1ggy/import-defaults'] ?? {}
12
+ const settings = { ...context.settings }
13
+
14
+ for (const [name, value] of Object.entries(selected.settings)) {
15
+ if (JSON.stringify(settings[name]) === JSON.stringify(defaults[name])) {
16
+ settings[name] = value
17
+ }
18
+ }
19
+
20
+ const wrapped = Object.create(context)
21
+
22
+ Object.defineProperty(wrapped, 'settings', { value: settings, configurable: true })
23
+
24
+ return wrapped
25
+ }
26
+
27
+ export default {
28
+ ...importPlugin,
29
+ rules: Object.fromEntries(
30
+ Object.entries(importRules).map(([name, rule]) => [
31
+ name,
32
+ {
33
+ ...rule,
34
+ create: (context) => rule.create(scopedContext(context)),
35
+ },
36
+ ]),
37
+ ),
38
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@next/eslint-plugin-next'
@@ -0,0 +1 @@
1
+ export { default } from 'eslint-plugin-prettier'
@@ -0,0 +1 @@
1
+ export { default } from 'eslint-plugin-react-hooks'
@@ -0,0 +1 @@
1
+ export { default } from 'eslint-plugin-react'
@@ -0,0 +1 @@
1
+ export { default } from 'eslint-plugin-solid'
@@ -0,0 +1,5 @@
1
+ import sonarjs from 'eslint-plugin-sonarjs'
2
+
3
+ import { withFreshSourceCode } from './fresh-source-code.js'
4
+
5
+ export default withFreshSourceCode(sonarjs)
@@ -0,0 +1 @@
1
+ export { default } from '@stylistic/eslint-plugin'
@@ -0,0 +1,42 @@
1
+ import svelte from 'eslint-plugin-svelte'
2
+
3
+ const innerDeclarations = svelte.rules['no-inner-declarations']
4
+
5
+ // The source preset treats Svelte scripts as strict modules. Oxlint's extracted
6
+ // import-free chunks use script scope. Preserve the original rule's strictness
7
+ // without modifying the native AST or scope objects used by other rules.
8
+ export default {
9
+ ...svelte,
10
+ rules: {
11
+ ...svelte.rules,
12
+ 'no-inner-declarations': {
13
+ ...innerDeclarations,
14
+ create(context) {
15
+ const sourceCode = Object.create(context.sourceCode)
16
+
17
+ Object.defineProperty(sourceCode, 'getScope', {
18
+ value(node) {
19
+ const scope = context.sourceCode.getScope(node)
20
+
21
+ if (!scope.upper) {
22
+ return scope
23
+ }
24
+
25
+ const upper = Object.create(scope.upper)
26
+ const wrapped = Object.create(scope)
27
+
28
+ Object.defineProperty(upper, 'isStrict', { value: true })
29
+ Object.defineProperty(wrapped, 'upper', { value: upper })
30
+
31
+ return wrapped
32
+ },
33
+ })
34
+ const wrapped = Object.create(context)
35
+
36
+ Object.defineProperty(wrapped, 'sourceCode', { value: sourceCode, configurable: true })
37
+
38
+ return innerDeclarations.create(wrapped)
39
+ },
40
+ },
41
+ },
42
+ }
@@ -0,0 +1 @@
1
+ export { plugin as default } from 'typescript-eslint'
@@ -0,0 +1,5 @@
1
+ import unicorn from 'eslint-plugin-unicorn'
2
+
3
+ import { withFreshSourceCode } from './fresh-source-code.js'
4
+
5
+ export default withFreshSourceCode(unicorn)
@@ -0,0 +1,11 @@
1
+ export const OX_VERSIONS = { oxlint: '1.86.0', 'oxlint-tsgolint': '7.0.2003' }
2
+
3
+ // Only integrations actually used by the corresponding generated bundle.
4
+ export const OX_FRAMEWORK_PACKAGES = {
5
+ common: [],
6
+ react: ['eslint-plugin-react', 'eslint-plugin-react-hooks'],
7
+ solid: ['eslint-plugin-solid'],
8
+ next: ['@next/eslint-plugin-next', 'eslint-plugin-react', 'eslint-plugin-react-hooks'],
9
+ svelte: ['eslint-plugin-svelte', 'prettier-plugin-svelte'],
10
+ astro: ['eslint-plugin-astro', 'prettier-plugin-astro'],
11
+ }
@@ -1,134 +0,0 @@
1
- /* eslint-disable unicorn/no-incorrect-template-string-interpolation -- Fixtures contain literal JSX expressions. */
2
-
3
- import { ESLint } from 'eslint'
4
- import assert from 'node:assert/strict'
5
- import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
6
- import { tmpdir } from 'node:os'
7
- import path from 'node:path'
8
- import test from 'node:test'
9
-
10
- import solidConfig from '@st1ggy/linter-config/eslint-solid'
11
-
12
- import prettierConfig from '../src/prettier/prettier.config.common.js'
13
-
14
- import { resolveStackKey, run, stackPackages } from './linter-init-core.mjs'
15
-
16
- function createProject(context) {
17
- const prefix = path.join(tmpdir(), 'linter-solid-')
18
- const directory = realpathSync(mkdtempSync(prefix))
19
-
20
- context.after(() => rmSync(directory, { recursive: true, force: true }))
21
- writeFileSync(
22
- path.join(directory, 'tsconfig.json'),
23
- JSON.stringify({ compilerOptions: { allowJs: true, jsx: 'preserve', noEmit: true }, include: ['*.jsx', '*.tsx'] }),
24
- )
25
- writeFileSync(path.join(directory, '.prettierrc.json'), JSON.stringify(prettierConfig))
26
-
27
- return directory
28
- }
29
-
30
- async function lintSource(context, extension, source) {
31
- const directory = createProject(context)
32
- const filePath = path.join(directory, `component.${extension}`)
33
-
34
- writeFileSync(filePath, source)
35
- const eslint = new ESLint({
36
- cwd: directory,
37
- overrideConfigFile: true,
38
- overrideConfig: [...solidConfig, { languageOptions: { parserOptions: { tsconfigRootDir: directory } } }],
39
- })
40
- const [result] = await eslint.lintFiles([filePath])
41
-
42
- assert.equal(result.fatalErrorCount, 0, JSON.stringify(result.messages))
43
-
44
- return result.messages
45
- }
46
-
47
- test('Solid preset is available through both public exports', async () => {
48
- const { eslintSolid } = await import('@st1ggy/linter-config')
49
-
50
- assert.equal(eslintSolid, solidConfig)
51
- })
52
-
53
- for (const extension of ['jsx', 'tsx']) {
54
- const annotation = extension === 'tsx' ? ': { name: string }' : ''
55
-
56
- test(`Solid preset accepts reactive props in ${extension}`, async (context) => {
57
- const messages = await lintSource(
58
- context,
59
- extension,
60
- `export const Greeting = (props${annotation}) => <p>{props.name}</p>\n`,
61
- )
62
-
63
- assert.deepEqual(messages, [])
64
- })
65
-
66
- test(`Solid preset reports destructured props in ${extension}`, async (context) => {
67
- const messages = await lintSource(
68
- context,
69
- extension,
70
- `export const Greeting = ({ name }${annotation}) => <p>{name}</p>\n`,
71
- )
72
-
73
- assert.ok(messages.some((message) => message.ruleId === 'solid/no-destructure' && message.severity === 2))
74
- })
75
-
76
- test(`Solid preset reports lost reactivity in ${extension}`, async (context) => {
77
- const messages = await lintSource(
78
- context,
79
- extension,
80
- `export const Greeting = (props${annotation}) => {
81
- const name = props.name
82
-
83
- return <p>{name}</p>
84
- }
85
- `,
86
- )
87
-
88
- assert.ok(messages.some((message) => message.ruleId === 'solid/reactivity' && message.severity === 1))
89
- })
90
-
91
- test(`Solid preset recognizes components used in ${extension}`, async (context) => {
92
- const messages = await lintSource(
93
- context,
94
- extension,
95
- 'const Greeting = () => <p>Hello</p>\n\nexport const App = () => <Greeting />\n',
96
- )
97
-
98
- assert.deepEqual(messages, [])
99
- })
100
- }
101
-
102
- test('Solid stack generates wrappers, preserves them on init and replaces them on migrate', (context) => {
103
- const directory = createProject(context)
104
- const expected = {
105
- 'eslint.config.js': "export { default } from '@st1ggy/linter-config/eslint-solid';\n",
106
- 'prettier.config.js': "export { default } from '@st1ggy/linter-config/prettier-common';\n",
107
- 'stylelint.config.js': "export { default } from '@st1ggy/linter-config/stylelint-scss';\n",
108
- }
109
-
110
- run('init', directory, 'solid', { quiet: true })
111
-
112
- for (const [file, content] of Object.entries(expected)) {
113
- assert.equal(readFileSync(path.join(directory, file), 'utf8'), content)
114
- writeFileSync(path.join(directory, file), '// existing config\n')
115
- }
116
-
117
- run('init', directory, 'solid', { quiet: true })
118
-
119
- for (const file of Object.keys(expected)) {
120
- assert.equal(readFileSync(path.join(directory, file), 'utf8'), '// existing config\n')
121
- }
122
-
123
- run('migrate', directory, 'solid', { quiet: true })
124
-
125
- for (const [file, content] of Object.entries(expected)) {
126
- assert.equal(readFileSync(path.join(directory, file), 'utf8'), content)
127
- }
128
- })
129
-
130
- test('Solid stack selects its integration plugin and rejects conflicting flags', () => {
131
- assert.equal(resolveStackKey({ solid: true }), 'solid')
132
- assert.deepEqual(stackPackages('solid'), ['@st1ggy/linter-config', 'eslint-plugin-solid'])
133
- assert.throws(() => resolveStackKey({ solid: true, react: true }), /Pick at most one stack flag/)
134
- })