@tuomashatakka/eslint-config 2.6.2 → 3.1.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/.github/workflows/ci.yml +23 -0
- package/.github/workflows/publish.yml +45 -0
- package/AGENTS.md +29 -0
- package/bun.lock +60 -102
- package/eslint.config.mjs +1 -0
- package/index.mjs +7 -21
- package/package.json +11 -19
- package/plugins/no-inline-types/index.mjs +11 -0
- package/plugins/no-inline-types/rules/no-inline-multiline-types.mjs +181 -0
- package/plugins/omit/index.mjs +8 -0
- package/plugins/omit/rules/omit-unnecessary-parens-brackets.mjs +329 -0
- package/plugins/omit/utils.mjs +91 -0
- package/plugins/react-strict/index.mjs +19 -0
- package/plugins/react-strict/rules/jsx-prop-layout.mjs +100 -0
- package/plugins/react-strict/rules/no-complex-jsx-map.mjs +66 -0
- package/plugins/react-strict/rules/no-jsx-value-calculations.mjs +99 -0
- package/plugins/react-strict/rules/no-nested-divs.mjs +59 -0
- package/plugins/react-strict/rules/no-style-prop.mjs +43 -0
- package/plugins/react-strict/rules/prefer-no-use-effect.mjs +26 -0
- package/plugins/whitespaced/index.mjs +15 -0
- package/plugins/whitespaced/rules/aligned-assignments.mjs +385 -0
- package/plugins/whitespaced/rules/block-padding.mjs +289 -0
- package/plugins/whitespaced/rules/class-property-grouping.mjs +370 -0
- package/plugins/whitespaced/rules/consistent-line-spacing.mjs +266 -0
- package/plugins/whitespaced/rules/multiline-format.mjs +533 -0
- package/rules.mjs +101 -95
- package/test/fixtures/basic-javascript.js +5 -4
- package/test/fixtures/complex-patterns.ts +9 -7
- package/test/fixtures/edge-cases.js +12 -7
- package/test/fixtures/jsx-formatting.jsx +5 -4
- package/test/fixtures/omit-parens.invalid.ts +12 -0
- package/test/fixtures/omit-parens.valid.ts +13 -0
- package/test/fixtures/react-component.tsx +7 -6
- package/test/fixtures/react-strict.invalid.tsx +31 -0
- package/test/fixtures/react-strict.valid.tsx +76 -0
- package/test/fixtures/whitespaced-docstring.invalid.ts +10 -0
- package/test/fixtures/whitespaced-docstring.valid.ts +16 -0
- package/test/fixtures/whitespaced-members.invalid.ts +22 -0
- package/test/fixtures/whitespaced-members.valid.ts +13 -0
- package/test/fixtures/whitespaced-multiline.invalid.ts +8 -0
- package/test/fixtures/whitespaced-multiline.valid.ts +15 -0
- package/test/fixtures/whitespaced-types.valid.ts +5 -0
- package/test/fixtures/whitespaced.valid.ts +45 -0
- package/test/format-cases.mjs +13 -14
- package/test/test-runner.mjs +128 -47
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const shortName = 'hello'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const longValue = 'world'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DataProcessor {
|
|
12
|
+
static defaultConfig = { verbose: false }
|
|
13
|
+
private name: string
|
|
14
|
+
private data: string[]
|
|
15
|
+
constructor (name: string) {
|
|
16
|
+
this.name = name
|
|
17
|
+
|
|
18
|
+
this.data = []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process (input: string) {
|
|
22
|
+
const trimmed = input.trim()
|
|
23
|
+
|
|
24
|
+
const processed = trimmed.toLowerCase()
|
|
25
|
+
|
|
26
|
+
this.data.push(processed)
|
|
27
|
+
|
|
28
|
+
return processed
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
function formatOutput (value: string): string {
|
|
34
|
+
const prefix = '[OUT]'
|
|
35
|
+
const result = `${prefix} ${value}`
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function parseInput (raw: string): string[] {
|
|
41
|
+
return raw.split('\n').filter(Boolean)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export { DataProcessor, formatOutput, parseInput }
|
package/test/format-cases.mjs
CHANGED
|
@@ -9,20 +9,15 @@ import { fileURLToPath } from 'url'
|
|
|
9
9
|
const execAsync = promisify(exec)
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Format testing tool for ESLint configuration
|
|
14
|
-
* Tests that the configuration produces consistent formatting results
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
12
|
|
|
18
13
|
class FormatTester {
|
|
19
14
|
constructor () {
|
|
20
15
|
this.fixturesDir = path.join(__dirname, 'fixtures')
|
|
21
|
-
this.configPath
|
|
22
|
-
this.results
|
|
16
|
+
this.configPath = path.join(__dirname, '../index.mjs')
|
|
17
|
+
this.results = {
|
|
23
18
|
tested: 0,
|
|
24
19
|
formatted: 0,
|
|
25
|
-
errors: []
|
|
20
|
+
errors: [],
|
|
26
21
|
}
|
|
27
22
|
}
|
|
28
23
|
|
|
@@ -101,7 +96,7 @@ class FormatTester {
|
|
|
101
96
|
})
|
|
102
97
|
this.results.errors.push({
|
|
103
98
|
file: filename,
|
|
104
|
-
errors: fileResult.messages.filter(m => m.severity === 2)
|
|
99
|
+
errors: fileResult.messages.filter(m => m.severity === 2),
|
|
105
100
|
})
|
|
106
101
|
}
|
|
107
102
|
else if (warningCount > 0)
|
|
@@ -117,7 +112,7 @@ class FormatTester {
|
|
|
117
112
|
console.log(` ❌ Format test failed: ${error.message}`)
|
|
118
113
|
this.results.errors.push({
|
|
119
114
|
file: filename,
|
|
120
|
-
error: error.message
|
|
115
|
+
error: error.message,
|
|
121
116
|
})
|
|
122
117
|
}
|
|
123
118
|
}
|
|
@@ -158,20 +153,23 @@ name: 'test',
|
|
|
158
153
|
value: 42,
|
|
159
154
|
description: 'A test object'
|
|
160
155
|
}`,
|
|
161
|
-
rules: [ '@stylistic/key-spacing' ]
|
|
156
|
+
rules: [ '@stylistic/key-spacing' ],
|
|
162
157
|
},
|
|
163
158
|
{
|
|
164
159
|
name: 'Array formatting',
|
|
165
160
|
before: `const arr = [ 1, 2, 3, 4, 5 ]`,
|
|
166
|
-
rules: [ '@stylistic/array-bracket-spacing' ]
|
|
161
|
+
rules: [ '@stylistic/array-bracket-spacing' ],
|
|
167
162
|
},
|
|
168
163
|
{
|
|
169
164
|
name: 'Function spacing',
|
|
170
165
|
before: `function test( param1,param2 ){
|
|
171
166
|
return param1+param2
|
|
172
167
|
}`,
|
|
173
|
-
rules: [
|
|
174
|
-
|
|
168
|
+
rules: [
|
|
169
|
+
'@stylistic/space-before-function-paren',
|
|
170
|
+
'@stylistic/space-infix-ops',
|
|
171
|
+
],
|
|
172
|
+
},
|
|
175
173
|
]
|
|
176
174
|
|
|
177
175
|
examples.forEach(example => {
|
|
@@ -194,4 +192,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
194
192
|
await tester.runFormatTests()
|
|
195
193
|
}
|
|
196
194
|
|
|
195
|
+
|
|
197
196
|
export default FormatTester
|
package/test/test-runner.mjs
CHANGED
|
@@ -9,32 +9,32 @@ import { fileURLToPath } from 'url'
|
|
|
9
9
|
const execAsync = promisify(exec)
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Test runner for ESLint configuration
|
|
14
|
-
* Validates that the config works with various code samples
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
12
|
|
|
18
13
|
class ESLintConfigTester {
|
|
19
14
|
constructor () {
|
|
20
15
|
this.fixturesDir = path.join(__dirname, 'fixtures')
|
|
21
|
-
this.configPath
|
|
22
|
-
this.results
|
|
16
|
+
this.configPath = path.join(__dirname, '../index.mjs')
|
|
17
|
+
this.results = {
|
|
23
18
|
passed: 0,
|
|
24
19
|
failed: 0,
|
|
25
|
-
errors: []
|
|
20
|
+
errors: [],
|
|
26
21
|
}
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
async runTests () {
|
|
30
|
-
console.log('
|
|
25
|
+
console.log('\n Running ESLint configuration tests...\n')
|
|
31
26
|
|
|
32
27
|
try {
|
|
33
28
|
const fixtures = await readdir(this.fixturesDir)
|
|
34
|
-
const testFiles = fixtures.filter(file =>
|
|
29
|
+
const testFiles = fixtures.filter(file =>
|
|
30
|
+
file.endsWith('.js') ||
|
|
31
|
+
file.endsWith('.jsx') ||
|
|
32
|
+
file.endsWith('.ts') ||
|
|
33
|
+
file.endsWith('.tsx') ||
|
|
34
|
+
file.endsWith('.mjs'))
|
|
35
35
|
|
|
36
36
|
if (testFiles.length === 0) {
|
|
37
|
-
console.log('
|
|
37
|
+
console.log(' No test fixtures found in test/fixtures/')
|
|
38
38
|
return
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -44,25 +44,50 @@ class ESLintConfigTester {
|
|
|
44
44
|
this.printSummary()
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
|
-
console.error('
|
|
47
|
+
console.error(' Test runner failed:', error.message)
|
|
48
48
|
process.exit(1)
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
|
|
53
|
+
getFixtureMode (filename) {
|
|
54
|
+
if (filename.includes('.valid.'))
|
|
55
|
+
return 'valid'
|
|
56
|
+
|
|
57
|
+
if (filename.includes('.invalid.'))
|
|
58
|
+
return 'invalid'
|
|
59
|
+
|
|
60
|
+
return 'standard'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async parseExpectedWarnings (filepath) {
|
|
65
|
+
const content = await readFile(filepath, 'utf8')
|
|
66
|
+
const lines = content.split('\n')
|
|
67
|
+
const expected = []
|
|
68
|
+
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const match = line.match(/(?:\/\/|{\s*\/\*)\s*expect-warning:\s*([^\s*]+)/)
|
|
71
|
+
|
|
72
|
+
if (match)
|
|
73
|
+
expected.push(match[1].trim())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return expected
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
52
80
|
async testFile (filename) {
|
|
53
81
|
const filepath = path.join(this.fixturesDir, filename)
|
|
82
|
+
const mode = this.getFixtureMode(filename)
|
|
54
83
|
|
|
55
84
|
try {
|
|
56
|
-
console.log(
|
|
85
|
+
console.log(` Testing ${filename} [${mode}]...`)
|
|
57
86
|
|
|
58
|
-
|
|
59
|
-
const { stdout, stderr } = await execAsync(
|
|
87
|
+
const { stdout } = await execAsync(
|
|
60
88
|
`npx eslint "${filepath}" --config "${this.configPath}" --format json`,
|
|
61
|
-
{ cwd: path.join(__dirname, '..') }
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if (stderr && stderr.trim())
|
|
65
|
-
throw new Error(`ESLint stderr: ${stderr}`)
|
|
89
|
+
{ cwd: path.join(__dirname, '..'), maxBuffer: 1024 * 1024 }
|
|
90
|
+
).catch(e => ({ stdout: e.stdout, stderr: e.stderr }))
|
|
66
91
|
|
|
67
92
|
const results = JSON.parse(stdout)
|
|
68
93
|
const fileResult = results[0]
|
|
@@ -72,56 +97,112 @@ class ESLintConfigTester {
|
|
|
72
97
|
|
|
73
98
|
const errorCount = fileResult.errorCount
|
|
74
99
|
const warningCount = fileResult.warningCount
|
|
100
|
+
const messages = fileResult.messages
|
|
75
101
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.results.errors.push({
|
|
84
|
-
file: filename,
|
|
85
|
-
errors: fileResult.messages.filter(m => m.severity === 2)
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
console.log(` ✅ 0 errors, ${warningCount} warnings`)
|
|
90
|
-
this.results.passed++
|
|
91
|
-
}
|
|
102
|
+
if (mode === 'valid')
|
|
103
|
+
return this.assertValid(filename, errorCount, warningCount, messages)
|
|
104
|
+
|
|
105
|
+
if (mode === 'invalid')
|
|
106
|
+
return await this.assertInvalid(filename, filepath, errorCount, warningCount, messages)
|
|
107
|
+
|
|
108
|
+
return this.assertStandard(filename, errorCount, warningCount, messages)
|
|
92
109
|
}
|
|
93
110
|
catch (error) {
|
|
94
|
-
console.log(`
|
|
111
|
+
console.log(` FAIL: ${error.message}`)
|
|
112
|
+
this.results.failed++
|
|
113
|
+
this.results.errors.push({ file: filename, error: error.message })
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
assertStandard (filename, errorCount, warningCount, messages) {
|
|
119
|
+
if (errorCount > 0) {
|
|
120
|
+
console.log(` FAIL — ${errorCount} errors, ${warningCount} warnings`)
|
|
121
|
+
messages.filter(m => m.severity === 2).forEach(msg =>
|
|
122
|
+
console.log(` Error: ${msg.message} (${msg.ruleId}) [line ${msg.line}]`))
|
|
123
|
+
this.results.failed++
|
|
124
|
+
this.results.errors.push({
|
|
125
|
+
file: filename,
|
|
126
|
+
errors: messages.filter(m => m.severity === 2),
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log(` PASS — 0 errors, ${warningCount} warnings`)
|
|
131
|
+
this.results.passed++
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
assertValid (filename, errorCount, warningCount, messages) {
|
|
137
|
+
if (errorCount > 0 || warningCount > 0) {
|
|
138
|
+
console.log(` FAIL — expected 0 issues, got ${errorCount} errors, ${warningCount} warnings`)
|
|
139
|
+
messages.forEach(msg =>
|
|
140
|
+
console.log(` ${msg.severity === 2 ? 'Error' : 'Warn'}: ${msg.message} (${msg.ruleId}) [line ${msg.line}]`))
|
|
141
|
+
this.results.failed++
|
|
142
|
+
this.results.errors.push({ file: filename, errors: messages })
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(` PASS — clean (0 errors, 0 warnings)`)
|
|
146
|
+
this.results.passed++
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async assertInvalid (filename, filepath, errorCount, warningCount, messages) {
|
|
152
|
+
const expected = await this.parseExpectedWarnings(filepath)
|
|
153
|
+
const warningRuleIds = messages.filter(m => m.severity === 1).map(m => m.ruleId)
|
|
154
|
+
|
|
155
|
+
if (errorCount > 0) {
|
|
156
|
+
console.log(` FAIL — unexpected errors found`)
|
|
157
|
+
messages.filter(m => m.severity === 2).forEach(msg =>
|
|
158
|
+
console.log(` Error: ${msg.message} (${msg.ruleId}) [line ${msg.line}]`))
|
|
159
|
+
this.results.failed++
|
|
160
|
+
this.results.errors.push({
|
|
161
|
+
file: filename,
|
|
162
|
+
errors: messages.filter(m => m.severity === 2),
|
|
163
|
+
})
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const missing = expected.filter(rule => !warningRuleIds.includes(rule))
|
|
168
|
+
|
|
169
|
+
if (missing.length > 0) {
|
|
170
|
+
console.log(` FAIL — expected warnings not found: ${missing.join(', ')}`)
|
|
95
171
|
this.results.failed++
|
|
96
172
|
this.results.errors.push({
|
|
97
173
|
file: filename,
|
|
98
|
-
error:
|
|
174
|
+
error: `Missing expected warnings: ${missing.join(', ')}`,
|
|
99
175
|
})
|
|
100
176
|
}
|
|
177
|
+
else {
|
|
178
|
+
console.log(` PASS — ${warningCount} expected warnings from ${expected.length} rules`)
|
|
179
|
+
this.results.passed++
|
|
180
|
+
}
|
|
101
181
|
}
|
|
102
182
|
|
|
183
|
+
|
|
103
184
|
printSummary () {
|
|
104
|
-
console.log('\n
|
|
105
|
-
console.log(
|
|
106
|
-
console.log(
|
|
185
|
+
console.log('\n Test Summary:')
|
|
186
|
+
console.log(` Passed: ${this.results.passed}`)
|
|
187
|
+
console.log(` Failed: ${this.results.failed}`)
|
|
107
188
|
|
|
108
189
|
if (this.results.failed > 0) {
|
|
109
|
-
console.log('\n
|
|
110
|
-
this.results.errors.forEach(error =>
|
|
111
|
-
console.log(`
|
|
112
|
-
})
|
|
190
|
+
console.log('\n Failures:')
|
|
191
|
+
this.results.errors.forEach(error =>
|
|
192
|
+
console.log(` ${error.file}: ${error.error || 'ESLint errors'}`))
|
|
113
193
|
process.exit(1)
|
|
114
194
|
}
|
|
115
195
|
else
|
|
116
|
-
console.log('\n
|
|
196
|
+
console.log('\n All tests passed!\n')
|
|
117
197
|
}
|
|
118
198
|
}
|
|
119
199
|
|
|
120
200
|
|
|
121
|
-
// Run tests if this file is executed directly
|
|
122
201
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
123
202
|
const tester = new ESLintConfigTester()
|
|
203
|
+
|
|
124
204
|
tester.runTests()
|
|
125
205
|
}
|
|
126
206
|
|
|
207
|
+
|
|
127
208
|
export default ESLintConfigTester
|