@eslint-config-snapshot/api 0.8.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-config-snapshot/api",
3
- "version": "0.8.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,6 +26,7 @@
26
26
  "dependencies": {
27
27
  "@manypkg/get-packages": "^3.1.0",
28
28
  "cosmiconfig": "^9.0.0",
29
+ "debug": "^4.4.3",
29
30
  "fast-glob": "^3.3.3",
30
31
  "picomatch": "^4.0.3"
31
32
  }
package/src/config.ts CHANGED
@@ -24,7 +24,7 @@ export type SnapshotConfig = {
24
24
  maxFilesPerWorkspace: number
25
25
  includeGlobs: string[]
26
26
  excludeGlobs: string[]
27
- hintGlobs: string[]
27
+ tokenHints?: string[] | string[][]
28
28
  }
29
29
  }
30
30
 
@@ -35,10 +35,9 @@ export const DEFAULT_CONFIG: SnapshotConfig = {
35
35
  groups: [{ name: 'default', match: ['**/*'] }]
36
36
  },
37
37
  sampling: {
38
- maxFilesPerWorkspace: 8,
39
- includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
40
- excludeGlobs: ['**/node_modules/**', '**/dist/**'],
41
- hintGlobs: []
38
+ maxFilesPerWorkspace: 10,
39
+ includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs,md,mdx,json,css}'],
40
+ excludeGlobs: ['**/node_modules/**', '**/dist/**']
42
41
  }
43
42
  }
44
43
 
@@ -130,10 +129,35 @@ export function getConfigScaffold(preset: ConfigPreset = 'minimal'): string {
130
129
  groups: [{ name: 'default', match: ['**/*'] }]
131
130
  },
132
131
  sampling: {
133
- maxFilesPerWorkspace: 8,
134
- includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
132
+ maxFilesPerWorkspace: 10,
133
+ includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs,md,mdx,json,css}'],
135
134
  excludeGlobs: ['**/node_modules/**', '**/dist/**'],
136
- hintGlobs: []
135
+ tokenHints: [
136
+ 'chunk',
137
+ 'conf',
138
+ 'config',
139
+ 'container',
140
+ 'controller',
141
+ 'helpers',
142
+ 'mock',
143
+ 'mocks',
144
+ 'presentation',
145
+ 'repository',
146
+ 'route',
147
+ 'routes',
148
+ 'schema',
149
+ 'setup',
150
+ 'spec',
151
+ 'stories',
152
+ 'style',
153
+ 'styles',
154
+ 'test',
155
+ 'type',
156
+ 'types',
157
+ 'utils',
158
+ 'view',
159
+ 'views'
160
+ ]
137
161
  }
138
162
  }\n`
139
163
  }
package/src/diff.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { canonicalizeJson, sortUnique } from './core.js'
2
2
 
3
- import type { SnapshotFile } from './snapshot.js'
3
+ import type { NormalizedRuleEntry } from './extract.js'
4
+ import type { SnapshotFile, SnapshotRuleEntry } from './snapshot.js'
4
5
 
5
6
  export type RuleSeverityChange = {
6
7
  rule: string
@@ -43,37 +44,56 @@ export function diffSnapshots(before: SnapshotFile, after: SnapshotFile): Snapsh
43
44
  for (const name of beforeNames.filter((entry) => afterNames.includes(entry))) {
44
45
  const oldEntry = beforeRules[name]
45
46
  const newEntry = afterRules[name]
47
+ const oldVariants = toVariants(oldEntry)
48
+ const newVariants = toVariants(newEntry)
46
49
 
47
- if (oldEntry[0] !== newEntry[0]) {
50
+ const oldSeverity = summarizeSeveritySet(oldVariants)
51
+ const newSeverity = summarizeSeveritySet(newVariants)
52
+ if (oldSeverity !== newSeverity) {
48
53
  severityChanges.push({
49
54
  rule: name,
50
- before: oldEntry[0],
51
- after: newEntry[0]
55
+ before: oldSeverity,
56
+ after: newSeverity
52
57
  })
53
58
  }
54
59
 
55
- const oldOptions = oldEntry.length > 1 ? canonicalizeJson(oldEntry[1]) : undefined
56
- const newOptions = newEntry.length > 1 ? canonicalizeJson(newEntry[1]) : undefined
60
+ const oldSerialized = JSON.stringify(oldVariants)
61
+ const newSerialized = JSON.stringify(newVariants)
62
+ if (oldSerialized === newSerialized) {
63
+ continue
64
+ }
57
65
 
58
- if (oldEntry[0] === 'off' || newEntry[0] === 'off') {
66
+ const oldIsOnlyOff = oldVariants.every((entry) => entry[0] === 'off')
67
+ const newIsOnlyOff = newVariants.every((entry) => entry[0] === 'off')
68
+ if (oldIsOnlyOff || newIsOnlyOff) {
59
69
  // Treat off->off option removal/addition as removed/introduced config intent.
60
- if (oldEntry[0] === 'off' && newEntry[0] === 'off') {
61
- if (oldOptions !== undefined && newOptions === undefined) {
70
+ if (oldIsOnlyOff && newIsOnlyOff) {
71
+ const oldHasOptions = oldVariants.some((variant) => variant.length > 1)
72
+ const newHasOptions = newVariants.some((variant) => variant.length > 1)
73
+ if (oldHasOptions && !newHasOptions) {
74
+ removedRules.push(name)
75
+ } else if (!oldHasOptions && newHasOptions) {
76
+ introducedRules.push(name)
77
+ } else if (oldVariants.length > newVariants.length) {
62
78
  removedRules.push(name)
63
- } else if (oldOptions === undefined && newOptions !== undefined) {
79
+ } else if (oldVariants.length < newVariants.length) {
64
80
  introducedRules.push(name)
81
+ } else {
82
+ optionChanges.push({
83
+ rule: name,
84
+ before: oldVariants,
85
+ after: newVariants
86
+ })
65
87
  }
66
88
  }
67
89
  continue
68
90
  }
69
91
 
70
- if (JSON.stringify(oldOptions) !== JSON.stringify(newOptions)) {
71
- optionChanges.push({
72
- rule: name,
73
- before: oldOptions,
74
- after: newOptions
75
- })
76
- }
92
+ optionChanges.push({
93
+ rule: name,
94
+ before: oldVariants,
95
+ after: newVariants
96
+ })
77
97
  }
78
98
 
79
99
  const beforeWorkspaces = sortUnique(before.workspaces)
@@ -91,6 +111,20 @@ export function diffSnapshots(before: SnapshotFile, after: SnapshotFile): Snapsh
91
111
  }
92
112
  }
93
113
 
114
+ function toVariants(entry: SnapshotRuleEntry): NormalizedRuleEntry[] {
115
+ if (!Array.isArray(entry[0])) {
116
+ return [canonicalizeJson(entry as NormalizedRuleEntry)]
117
+ }
118
+
119
+ return (entry as NormalizedRuleEntry[]).map((variant) => canonicalizeJson(variant))
120
+ }
121
+
122
+ function summarizeSeveritySet(variants: NormalizedRuleEntry[]): string {
123
+ const severityOrder: Array<'error' | 'warn' | 'off'> = ['error', 'warn', 'off']
124
+ const severities = new Set(variants.map((variant) => variant[0]))
125
+ return severityOrder.filter((severity) => severities.has(severity)).join('|')
126
+ }
127
+
94
128
  export function hasDiff(diff: SnapshotDiff): boolean {
95
129
  return (
96
130
  diff.introducedRules.length > 0 ||
package/src/extract.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import createDebug from 'debug'
1
2
  import { spawnSync } from 'node:child_process'
2
3
  import { existsSync, readFileSync } from 'node:fs'
3
4
  import { createRequire } from 'node:module'
@@ -6,6 +7,8 @@ import { pathToFileURL } from 'node:url'
6
7
 
7
8
  import { canonicalizeJson, normalizeSeverity } from './core.js'
8
9
 
10
+ const debugExtract = createDebug('eslint-config-snapshot:extract')
11
+
9
12
  export type NormalizedRuleEntry = [severity: 'off' | 'warn' | 'error'] | [severity: 'off' | 'warn' | 'error', options: unknown]
10
13
 
11
14
  export type ExtractedWorkspaceRules = Map<string, NormalizedRuleEntry>
@@ -66,12 +69,17 @@ function findPackageRoot(entryAbs: string): string {
66
69
 
67
70
  export function extractRulesFromPrintConfig(workspaceAbs: string, fileAbs: string): ExtractedWorkspaceRules {
68
71
  const eslintBin = resolveEslintBinForWorkspace(workspaceAbs)
72
+ const commandArgs = [eslintBin, '--print-config', fileAbs]
73
+ const startedAt = Date.now()
74
+ debugExtract('spawn: cwd=%s cmd=%s %o', workspaceAbs, process.execPath, commandArgs)
69
75
  const proc = spawnSync(process.execPath, [eslintBin, '--print-config', fileAbs], {
70
76
  cwd: workspaceAbs,
71
77
  encoding: 'utf8'
72
78
  })
79
+ debugExtract('spawn: done status=%s elapsedMs=%d', String(proc.status), Date.now() - startedAt)
73
80
 
74
81
  if (proc.status !== 0) {
82
+ debugExtract('spawn: stderr=%s', proc.stderr.trim())
75
83
  throw new Error(`Failed to run eslint --print-config for ${fileAbs}`)
76
84
  }
77
85
 
@@ -112,6 +120,7 @@ export async function extractRulesForWorkspaceSamples(
112
120
  workspaceAbs: string,
113
121
  fileAbsList: string[]
114
122
  ): Promise<WorkspaceExtractionResult[]> {
123
+ debugExtract('workspace=%s sampleCount=%d', workspaceAbs, fileAbsList.length)
115
124
  const evaluate = await createWorkspaceEvaluator(workspaceAbs)
116
125
  const results: WorkspaceExtractionResult[] = []
117
126
 
@@ -121,10 +130,18 @@ export async function extractRulesForWorkspaceSamples(
121
130
  results.push({ fileAbs, rules })
122
131
  } catch (error: unknown) {
123
132
  const normalizedError = error instanceof Error ? error : new Error(String(error))
133
+ debugExtract('extract failed: workspace=%s file=%s error=%s', workspaceAbs, fileAbs, normalizedError.message)
124
134
  results.push({ fileAbs, error: normalizedError })
125
135
  }
126
136
  }
127
137
 
138
+ debugExtract(
139
+ 'workspace=%s extracted=%d failed=%d',
140
+ workspaceAbs,
141
+ results.filter((entry) => entry.rules !== undefined).length,
142
+ results.filter((entry) => entry.error !== undefined).length
143
+ )
144
+
128
145
  return results
129
146
  }
130
147
 
@@ -142,6 +159,7 @@ async function createWorkspaceEvaluator(
142
159
 
143
160
  const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint
144
161
  if (ESLintClass) {
162
+ debugExtract('workspace=%s evaluator=eslint-api', workspaceAbs)
145
163
  const eslint = new ESLintClass({ cwd: workspaceAbs })
146
164
  return async (fileAbs: string) => {
147
165
  const config = await eslint.calculateConfigForFile(fileAbs)
@@ -156,6 +174,7 @@ async function createWorkspaceEvaluator(
156
174
  // fall through to subprocess-based extractor
157
175
  }
158
176
 
177
+ debugExtract('workspace=%s evaluator=spawn-print-config', workspaceAbs)
159
178
  return (fileAbs: string) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs))
160
179
  }
161
180
 
package/src/index.ts CHANGED
@@ -16,7 +16,7 @@ export {
16
16
  export type { ExtractedWorkspaceRules, NormalizedRuleEntry, WorkspaceExtractionResult } from './extract.js'
17
17
 
18
18
  export { aggregateRules, buildSnapshot, readSnapshotFile, writeSnapshotFile } from './snapshot.js'
19
- export type { SnapshotFile } from './snapshot.js'
19
+ export type { SnapshotFile, SnapshotRuleEntry } from './snapshot.js'
20
20
 
21
21
  export { diffSnapshots, hasDiff } from './diff.js'
22
22
  export type { RuleOptionChange, RuleSeverityChange, SnapshotDiff, WorkspaceMembershipChange } from './diff.js'