@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.
- package/README.md +88 -7
- package/data/oxlint-config-inventory.json +2091 -0
- package/data/oxlint-rule-map.json +91859 -0
- package/data/oxlint-source-inventory.json +107728 -0
- package/docs/OXLINT_RULE_MAPPING.md +2968 -0
- package/docs/RULE_MAPPING.md +15 -0
- package/package.json +55 -3
- package/scripts/README.md +33 -1
- package/scripts/init-astro-ox.sh +4 -0
- package/scripts/init-common-ox.sh +4 -0
- package/scripts/init-next-ox.sh +4 -0
- package/scripts/init-react-ox.sh +4 -0
- package/scripts/init-solid-ox.sh +4 -0
- package/scripts/init-svelte-ox.sh +4 -0
- package/scripts/linter-init-core.mjs +80 -20
- package/scripts/linter-init.mjs +24 -32
- package/src/oxlint/configs/config.astro.js +4092 -0
- package/src/oxlint/configs/config.common.js +3209 -0
- package/src/oxlint/configs/config.next.js +3592 -0
- package/src/oxlint/configs/config.react.js +3503 -0
- package/src/oxlint/configs/config.solid.js +4986 -0
- package/src/oxlint/configs/config.svelte.js +3301 -0
- package/src/oxlint/create-config.js +40 -0
- package/src/oxlint/index.d.ts +49 -0
- package/src/oxlint/oxlint.config.astro.js +4 -0
- package/src/oxlint/oxlint.config.common.js +4 -0
- package/src/oxlint/oxlint.config.next.js +4 -0
- package/src/oxlint/oxlint.config.react.js +4 -0
- package/src/oxlint/oxlint.config.solid.js +4 -0
- package/src/oxlint/oxlint.config.svelte.js +4 -0
- package/src/oxlint/plugins/astro.js +41 -0
- package/src/oxlint/plugins/eslint-core.js +5 -0
- package/src/oxlint/plugins/fresh-source-code.js +30 -0
- package/src/oxlint/plugins/import-x.js +38 -0
- package/src/oxlint/plugins/next.js +1 -0
- package/src/oxlint/plugins/prettier.js +1 -0
- package/src/oxlint/plugins/react-hooks.js +1 -0
- package/src/oxlint/plugins/react.js +1 -0
- package/src/oxlint/plugins/solid.js +1 -0
- package/src/oxlint/plugins/sonarjs.js +5 -0
- package/src/oxlint/plugins/stylistic.js +1 -0
- package/src/oxlint/plugins/svelte.js +42 -0
- package/src/oxlint/plugins/typescript.js +1 -0
- package/src/oxlint/plugins/unicorn.js +5 -0
- package/src/oxlint/stacks.js +11 -0
- 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,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,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 @@
|
|
|
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,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
|
-
})
|