@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.
Files changed (45) hide show
  1. package/.github/workflows/ci.yml +23 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/AGENTS.md +29 -0
  4. package/bun.lock +60 -102
  5. package/eslint.config.mjs +1 -0
  6. package/index.mjs +7 -21
  7. package/package.json +11 -19
  8. package/plugins/no-inline-types/index.mjs +11 -0
  9. package/plugins/no-inline-types/rules/no-inline-multiline-types.mjs +181 -0
  10. package/plugins/omit/index.mjs +8 -0
  11. package/plugins/omit/rules/omit-unnecessary-parens-brackets.mjs +329 -0
  12. package/plugins/omit/utils.mjs +91 -0
  13. package/plugins/react-strict/index.mjs +19 -0
  14. package/plugins/react-strict/rules/jsx-prop-layout.mjs +100 -0
  15. package/plugins/react-strict/rules/no-complex-jsx-map.mjs +66 -0
  16. package/plugins/react-strict/rules/no-jsx-value-calculations.mjs +99 -0
  17. package/plugins/react-strict/rules/no-nested-divs.mjs +59 -0
  18. package/plugins/react-strict/rules/no-style-prop.mjs +43 -0
  19. package/plugins/react-strict/rules/prefer-no-use-effect.mjs +26 -0
  20. package/plugins/whitespaced/index.mjs +15 -0
  21. package/plugins/whitespaced/rules/aligned-assignments.mjs +385 -0
  22. package/plugins/whitespaced/rules/block-padding.mjs +289 -0
  23. package/plugins/whitespaced/rules/class-property-grouping.mjs +370 -0
  24. package/plugins/whitespaced/rules/consistent-line-spacing.mjs +266 -0
  25. package/plugins/whitespaced/rules/multiline-format.mjs +533 -0
  26. package/rules.mjs +101 -95
  27. package/test/fixtures/basic-javascript.js +5 -4
  28. package/test/fixtures/complex-patterns.ts +9 -7
  29. package/test/fixtures/edge-cases.js +12 -7
  30. package/test/fixtures/jsx-formatting.jsx +5 -4
  31. package/test/fixtures/omit-parens.invalid.ts +12 -0
  32. package/test/fixtures/omit-parens.valid.ts +13 -0
  33. package/test/fixtures/react-component.tsx +7 -6
  34. package/test/fixtures/react-strict.invalid.tsx +31 -0
  35. package/test/fixtures/react-strict.valid.tsx +76 -0
  36. package/test/fixtures/whitespaced-docstring.invalid.ts +10 -0
  37. package/test/fixtures/whitespaced-docstring.valid.ts +16 -0
  38. package/test/fixtures/whitespaced-members.invalid.ts +22 -0
  39. package/test/fixtures/whitespaced-members.valid.ts +13 -0
  40. package/test/fixtures/whitespaced-multiline.invalid.ts +8 -0
  41. package/test/fixtures/whitespaced-multiline.valid.ts +15 -0
  42. package/test/fixtures/whitespaced-types.valid.ts +5 -0
  43. package/test/fixtures/whitespaced.valid.ts +45 -0
  44. package/test/format-cases.mjs +13 -14
  45. package/test/test-runner.mjs +128 -47
@@ -0,0 +1,15 @@
1
+ const fruits = [
2
+ 'apple',
3
+ 'banana',
4
+ 'cherry',
5
+ ]
6
+
7
+
8
+ const primes = [
9
+ 2,
10
+ 3,
11
+ 5,
12
+ ]
13
+
14
+
15
+ export { fruits, primes }
@@ -0,0 +1,5 @@
1
+ const shortName: string = 'hello'
2
+ const longNameHere: string = 'world'
3
+ const mediumName: number = 42
4
+
5
+ export { shortName, longNameHere, mediumName }
@@ -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 }
@@ -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 = path.join(__dirname, '../index.mjs')
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: [ '@stylistic/space-before-function-paren', '@stylistic/space-infix-ops' ]
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
@@ -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 = path.join(__dirname, '../index.mjs')
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('🚀 Running ESLint configuration tests...\n')
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 => file.endsWith('.js') || file.endsWith('.tsx') || file.endsWith('.ts'))
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('⚠️ No test fixtures found in test/fixtures/')
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('Test runner failed:', error.message)
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(`📝 Testing ${filename}...`)
85
+ console.log(` Testing ${filename} [${mode}]...`)
57
86
 
58
- // Run ESLint on the test file
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 (errorCount > 0) {
77
- console.log(` ❌ ${errorCount} errors, ${warningCount} warnings`)
78
- fileResult.messages.forEach(msg => {
79
- if (msg.severity === 2)
80
- console.log(` Error: ${msg.message} (${msg.ruleId})`)
81
- })
82
- this.results.failed++
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(` ❌ Test failed: ${error.message}`)
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: error.message
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📊 Test Summary:')
105
- console.log(`✅ Passed: ${this.results.passed}`)
106
- console.log(`❌ Failed: ${this.results.failed}`)
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🔍 Failures:')
110
- this.results.errors.forEach(error => {
111
- console.log(` ${error.file}: ${error.error || 'ESLint errors'}`)
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🎉 All tests passed!')
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