@bfra.me/workspace-analyzer 0.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/README.md +402 -0
- package/lib/chunk-4LSFAAZW.js +1 -0
- package/lib/chunk-JDF7DQ4V.js +27 -0
- package/lib/chunk-WOJ4C7N7.js +7122 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +318 -0
- package/lib/index.d.ts +3701 -0
- package/lib/index.js +1262 -0
- package/lib/types/index.d.ts +146 -0
- package/lib/types/index.js +28 -0
- package/package.json +89 -0
- package/src/analyzers/analyzer.ts +201 -0
- package/src/analyzers/architectural-analyzer.ts +304 -0
- package/src/analyzers/build-config-analyzer.ts +334 -0
- package/src/analyzers/circular-import-analyzer.ts +463 -0
- package/src/analyzers/config-consistency-analyzer.ts +335 -0
- package/src/analyzers/dead-code-analyzer.ts +565 -0
- package/src/analyzers/duplicate-code-analyzer.ts +626 -0
- package/src/analyzers/duplicate-dependency-analyzer.ts +381 -0
- package/src/analyzers/eslint-config-analyzer.ts +281 -0
- package/src/analyzers/exports-field-analyzer.ts +324 -0
- package/src/analyzers/index.ts +388 -0
- package/src/analyzers/large-dependency-analyzer.ts +535 -0
- package/src/analyzers/package-json-analyzer.ts +349 -0
- package/src/analyzers/peer-dependency-analyzer.ts +275 -0
- package/src/analyzers/tree-shaking-analyzer.ts +623 -0
- package/src/analyzers/tsconfig-analyzer.ts +382 -0
- package/src/analyzers/unused-dependency-analyzer.ts +356 -0
- package/src/analyzers/version-alignment-analyzer.ts +308 -0
- package/src/api/analyze-workspace.ts +245 -0
- package/src/api/index.ts +11 -0
- package/src/cache/cache-manager.ts +495 -0
- package/src/cache/cache-schema.ts +247 -0
- package/src/cache/change-detector.ts +169 -0
- package/src/cache/file-hasher.ts +65 -0
- package/src/cache/index.ts +47 -0
- package/src/cli/commands/analyze.ts +240 -0
- package/src/cli/commands/index.ts +5 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/types.ts +65 -0
- package/src/cli/ui.ts +213 -0
- package/src/cli.ts +9 -0
- package/src/config/defaults.ts +183 -0
- package/src/config/index.ts +81 -0
- package/src/config/loader.ts +270 -0
- package/src/config/merger.ts +229 -0
- package/src/config/schema.ts +263 -0
- package/src/core/incremental-analyzer.ts +462 -0
- package/src/core/index.ts +34 -0
- package/src/core/orchestrator.ts +416 -0
- package/src/graph/dependency-graph.ts +408 -0
- package/src/graph/index.ts +19 -0
- package/src/index.ts +417 -0
- package/src/parser/config-parser.ts +491 -0
- package/src/parser/import-extractor.ts +340 -0
- package/src/parser/index.ts +54 -0
- package/src/parser/typescript-parser.ts +95 -0
- package/src/performance/bundle-estimator.ts +444 -0
- package/src/performance/index.ts +27 -0
- package/src/reporters/console-reporter.ts +355 -0
- package/src/reporters/index.ts +49 -0
- package/src/reporters/json-reporter.ts +273 -0
- package/src/reporters/markdown-reporter.ts +349 -0
- package/src/reporters/reporter.ts +399 -0
- package/src/rules/builtin-rules.ts +709 -0
- package/src/rules/index.ts +52 -0
- package/src/rules/rule-engine.ts +409 -0
- package/src/scanner/index.ts +18 -0
- package/src/scanner/workspace-scanner.ts +403 -0
- package/src/types/index.ts +176 -0
- package/src/types/result.ts +19 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/pattern-matcher.ts +48 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExportsFieldAnalyzer - Validates package.json exports against actual source structure.
|
|
3
|
+
*
|
|
4
|
+
* Detects issues such as:
|
|
5
|
+
* - Exports pointing to non-existent files
|
|
6
|
+
* - Missing exports for existing source files
|
|
7
|
+
* - Invalid export conditions
|
|
8
|
+
* - Inconsistent export patterns
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {ParsedPackageJson} from '../parser/config-parser'
|
|
12
|
+
import type {WorkspacePackage} from '../scanner/workspace-scanner'
|
|
13
|
+
import type {Issue, IssueLocation} from '../types/index'
|
|
14
|
+
import type {Result} from '../types/result'
|
|
15
|
+
import type {AnalysisContext, Analyzer, AnalyzerError, AnalyzerMetadata} from './analyzer'
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs/promises'
|
|
18
|
+
import path from 'node:path'
|
|
19
|
+
|
|
20
|
+
import {ok} from '@bfra.me/es/result'
|
|
21
|
+
|
|
22
|
+
import {createIssue, filterIssues} from './analyzer'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Configuration options specific to ExportsFieldAnalyzer.
|
|
26
|
+
*/
|
|
27
|
+
export interface ExportsFieldAnalyzerOptions {
|
|
28
|
+
/** Whether to check if export paths resolve to existing files */
|
|
29
|
+
readonly validatePaths?: boolean
|
|
30
|
+
/** Whether to check export condition order */
|
|
31
|
+
readonly checkConditionOrder?: boolean
|
|
32
|
+
/** Whether to suggest missing exports for source files */
|
|
33
|
+
readonly suggestMissingExports?: boolean
|
|
34
|
+
/** Common output directories to check for built files */
|
|
35
|
+
readonly outputDirs?: readonly string[]
|
|
36
|
+
/** Package names exempt from checks */
|
|
37
|
+
readonly exemptPackages?: readonly string[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_OPTIONS: ExportsFieldAnalyzerOptions = {
|
|
41
|
+
validatePaths: true,
|
|
42
|
+
checkConditionOrder: true,
|
|
43
|
+
suggestMissingExports: false,
|
|
44
|
+
outputDirs: ['lib', 'dist', 'build', 'out'],
|
|
45
|
+
exemptPackages: [],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const METADATA: AnalyzerMetadata = {
|
|
49
|
+
id: 'exports-field',
|
|
50
|
+
name: 'Exports Field Analyzer',
|
|
51
|
+
description: 'Validates package.json exports against actual source file structure',
|
|
52
|
+
categories: ['configuration'],
|
|
53
|
+
defaultSeverity: 'warning',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Standard export conditions in recommended order.
|
|
58
|
+
*/
|
|
59
|
+
const CONDITION_ORDER = ['types', 'import', 'require', 'default'] as const
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates an ExportsFieldAnalyzer instance.
|
|
63
|
+
*/
|
|
64
|
+
export function createExportsFieldAnalyzer(options: ExportsFieldAnalyzerOptions = {}): Analyzer {
|
|
65
|
+
const resolvedOptions = {...DEFAULT_OPTIONS, ...options}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
metadata: METADATA,
|
|
69
|
+
analyze: async (context: AnalysisContext): Promise<Result<readonly Issue[], AnalyzerError>> => {
|
|
70
|
+
const issues: Issue[] = []
|
|
71
|
+
|
|
72
|
+
for (const pkg of context.packages) {
|
|
73
|
+
if (isExemptPackage(pkg.name, resolvedOptions.exemptPackages)) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pkgJson = pkg.packageJson as ParsedPackageJson
|
|
78
|
+
|
|
79
|
+
if (pkgJson.exports === undefined) {
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const packageIssues = await analyzePackageExports(pkg, pkgJson, resolvedOptions)
|
|
84
|
+
issues.push(...packageIssues)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return ok(filterIssues(issues, context.config))
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isExemptPackage(name: string, exemptPackages: readonly string[] | undefined): boolean {
|
|
93
|
+
return exemptPackages?.includes(name) ?? false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createLocation(filePath: string): IssueLocation {
|
|
97
|
+
return {filePath}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function analyzePackageExports(
|
|
101
|
+
pkg: WorkspacePackage,
|
|
102
|
+
pkgJson: ParsedPackageJson,
|
|
103
|
+
options: ExportsFieldAnalyzerOptions,
|
|
104
|
+
): Promise<Issue[]> {
|
|
105
|
+
const issues: Issue[] = []
|
|
106
|
+
const exports = pkgJson.exports
|
|
107
|
+
|
|
108
|
+
if (exports === undefined) {
|
|
109
|
+
return issues
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Analyze each export entry
|
|
113
|
+
const exportEntries = normalizeExports(exports)
|
|
114
|
+
|
|
115
|
+
for (const [exportPath, conditions] of exportEntries) {
|
|
116
|
+
// Check condition order
|
|
117
|
+
if (options.checkConditionOrder && typeof conditions === 'object' && conditions !== null) {
|
|
118
|
+
const orderIssues = checkConditionOrder(
|
|
119
|
+
pkg,
|
|
120
|
+
exportPath,
|
|
121
|
+
conditions as Record<string, unknown>,
|
|
122
|
+
)
|
|
123
|
+
issues.push(...orderIssues)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate export paths
|
|
127
|
+
if (options.validatePaths) {
|
|
128
|
+
const pathIssues = await validateExportPaths(pkg, exportPath, conditions, options)
|
|
129
|
+
issues.push(...pathIssues)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for package.json export (required for many tools)
|
|
134
|
+
issues.push(...checkPackageJsonExport(pkg, exports))
|
|
135
|
+
|
|
136
|
+
return issues
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Normalizes exports to a consistent Map format.
|
|
141
|
+
*/
|
|
142
|
+
function normalizeExports(exports: Record<string, unknown>): Map<string, unknown> {
|
|
143
|
+
const normalized = new Map<string, unknown>()
|
|
144
|
+
|
|
145
|
+
for (const [key, value] of Object.entries(exports)) {
|
|
146
|
+
normalized.set(key, value)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return normalized
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Checks if export conditions are in the recommended order.
|
|
154
|
+
*/
|
|
155
|
+
function checkConditionOrder(
|
|
156
|
+
pkg: WorkspacePackage,
|
|
157
|
+
exportPath: string,
|
|
158
|
+
conditions: Record<string, unknown>,
|
|
159
|
+
): Issue[] {
|
|
160
|
+
const issues: Issue[] = []
|
|
161
|
+
const conditionKeys = Object.keys(conditions)
|
|
162
|
+
|
|
163
|
+
// Find the positions of standard conditions
|
|
164
|
+
const positions = new Map<string, number>()
|
|
165
|
+
for (const [index, key] of conditionKeys.entries()) {
|
|
166
|
+
if ((CONDITION_ORDER as readonly string[]).includes(key)) {
|
|
167
|
+
positions.set(key, index)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check if types comes before import/require
|
|
172
|
+
const typesPos = positions.get('types')
|
|
173
|
+
const importPos = positions.get('import')
|
|
174
|
+
const requirePos = positions.get('require')
|
|
175
|
+
|
|
176
|
+
if (typesPos !== undefined) {
|
|
177
|
+
if (importPos !== undefined && typesPos > importPos) {
|
|
178
|
+
issues.push(
|
|
179
|
+
createIssue({
|
|
180
|
+
id: 'exports-types-after-import',
|
|
181
|
+
title: 'Export types condition should come before import',
|
|
182
|
+
description: `Package "${pkg.name}" export "${exportPath}" has "types" after "import" condition`,
|
|
183
|
+
severity: 'warning',
|
|
184
|
+
category: 'configuration',
|
|
185
|
+
location: createLocation(pkg.packageJsonPath),
|
|
186
|
+
suggestion: 'Move "types" condition before "import" for proper TypeScript resolution',
|
|
187
|
+
metadata: {exportPath, conditions: conditionKeys},
|
|
188
|
+
}),
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (requirePos !== undefined && typesPos > requirePos) {
|
|
193
|
+
issues.push(
|
|
194
|
+
createIssue({
|
|
195
|
+
id: 'exports-types-after-require',
|
|
196
|
+
title: 'Export types condition should come before require',
|
|
197
|
+
description: `Package "${pkg.name}" export "${exportPath}" has "types" after "require" condition`,
|
|
198
|
+
severity: 'warning',
|
|
199
|
+
category: 'configuration',
|
|
200
|
+
location: createLocation(pkg.packageJsonPath),
|
|
201
|
+
suggestion: 'Move "types" condition before "require" for proper TypeScript resolution',
|
|
202
|
+
metadata: {exportPath, conditions: conditionKeys},
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return issues
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Validates that export paths resolve to existing files.
|
|
213
|
+
*/
|
|
214
|
+
async function validateExportPaths(
|
|
215
|
+
pkg: WorkspacePackage,
|
|
216
|
+
exportPath: string,
|
|
217
|
+
conditions: unknown,
|
|
218
|
+
options: ExportsFieldAnalyzerOptions,
|
|
219
|
+
): Promise<Issue[]> {
|
|
220
|
+
const issues: Issue[] = []
|
|
221
|
+
|
|
222
|
+
const paths = extractPaths(conditions)
|
|
223
|
+
|
|
224
|
+
for (const filePath of paths) {
|
|
225
|
+
// Skip non-file paths
|
|
226
|
+
if (!filePath.startsWith('./')) {
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const absolutePath = path.join(pkg.packagePath, filePath)
|
|
231
|
+
const exists = await fileExists(absolutePath)
|
|
232
|
+
|
|
233
|
+
if (!exists) {
|
|
234
|
+
// Check if this looks like a build output
|
|
235
|
+
const isBuildOutput = (options.outputDirs ?? []).some(dir => filePath.startsWith(`./${dir}/`))
|
|
236
|
+
|
|
237
|
+
if (isBuildOutput) {
|
|
238
|
+
// Build outputs might not exist yet - info level
|
|
239
|
+
issues.push(
|
|
240
|
+
createIssue({
|
|
241
|
+
id: 'exports-build-output-missing',
|
|
242
|
+
title: 'Export path points to non-existent build output',
|
|
243
|
+
description: `Package "${pkg.name}" export "${exportPath}" points to "${filePath}" which does not exist (may need build)`,
|
|
244
|
+
severity: 'info',
|
|
245
|
+
category: 'configuration',
|
|
246
|
+
location: createLocation(pkg.packageJsonPath),
|
|
247
|
+
suggestion: 'Run build to generate the file, or verify the path is correct',
|
|
248
|
+
metadata: {exportPath, filePath},
|
|
249
|
+
}),
|
|
250
|
+
)
|
|
251
|
+
} else {
|
|
252
|
+
// Source file missing - warning level
|
|
253
|
+
issues.push(
|
|
254
|
+
createIssue({
|
|
255
|
+
id: 'exports-path-missing',
|
|
256
|
+
title: 'Export path points to non-existent file',
|
|
257
|
+
description: `Package "${pkg.name}" export "${exportPath}" points to "${filePath}" which does not exist`,
|
|
258
|
+
severity: 'warning',
|
|
259
|
+
category: 'configuration',
|
|
260
|
+
location: createLocation(pkg.packageJsonPath),
|
|
261
|
+
suggestion: 'Verify the export path is correct or create the missing file',
|
|
262
|
+
metadata: {exportPath, filePath},
|
|
263
|
+
}),
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return issues
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Extracts all file paths from an export condition structure.
|
|
274
|
+
*/
|
|
275
|
+
function extractPaths(value: unknown): string[] {
|
|
276
|
+
const paths: string[] = []
|
|
277
|
+
|
|
278
|
+
if (typeof value === 'string') {
|
|
279
|
+
paths.push(value)
|
|
280
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
281
|
+
for (const v of Object.values(value)) {
|
|
282
|
+
paths.push(...extractPaths(v))
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return paths
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Checks if package.json is exported (required by many tools).
|
|
291
|
+
*/
|
|
292
|
+
function checkPackageJsonExport(pkg: WorkspacePackage, exports: Record<string, unknown>): Issue[] {
|
|
293
|
+
const issues: Issue[] = []
|
|
294
|
+
|
|
295
|
+
const hasPackageJsonExport =
|
|
296
|
+
exports['./package.json'] !== undefined || exports['./package.json'] === './package.json'
|
|
297
|
+
|
|
298
|
+
if (!hasPackageJsonExport) {
|
|
299
|
+
issues.push(
|
|
300
|
+
createIssue({
|
|
301
|
+
id: 'exports-no-package-json',
|
|
302
|
+
title: 'Missing package.json export',
|
|
303
|
+
description: `Package "${pkg.name}" does not export "./package.json"`,
|
|
304
|
+
severity: 'info',
|
|
305
|
+
category: 'configuration',
|
|
306
|
+
location: createLocation(pkg.packageJsonPath),
|
|
307
|
+
suggestion: 'Add "./package.json": "./package.json" to exports for tool compatibility',
|
|
308
|
+
}),
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return issues
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
316
|
+
try {
|
|
317
|
+
await fs.access(filePath)
|
|
318
|
+
return true
|
|
319
|
+
} catch {
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export {METADATA as exportsFieldAnalyzerMetadata}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyzer registry and exports for workspace analysis.
|
|
3
|
+
*
|
|
4
|
+
* Provides a plugin architecture for registering and managing analyzers,
|
|
5
|
+
* with built-in analyzers for configuration validation and dependency analysis.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {Analyzer, AnalyzerRegistration} from './analyzer'
|
|
9
|
+
|
|
10
|
+
import {createArchitecturalAnalyzer} from './architectural-analyzer'
|
|
11
|
+
import {createBuildConfigAnalyzer} from './build-config-analyzer'
|
|
12
|
+
import {createCircularImportAnalyzer} from './circular-import-analyzer'
|
|
13
|
+
import {createConfigConsistencyAnalyzer} from './config-consistency-analyzer'
|
|
14
|
+
import {createDeadCodeAnalyzer} from './dead-code-analyzer'
|
|
15
|
+
import {createDuplicateCodeAnalyzer} from './duplicate-code-analyzer'
|
|
16
|
+
import {createDuplicateDependencyAnalyzer} from './duplicate-dependency-analyzer'
|
|
17
|
+
import {createEslintConfigAnalyzer} from './eslint-config-analyzer'
|
|
18
|
+
import {createExportsFieldAnalyzer} from './exports-field-analyzer'
|
|
19
|
+
import {createLargeDependencyAnalyzer} from './large-dependency-analyzer'
|
|
20
|
+
import {createPackageJsonAnalyzer} from './package-json-analyzer'
|
|
21
|
+
import {createPeerDependencyAnalyzer} from './peer-dependency-analyzer'
|
|
22
|
+
import {createTreeShakingBlockerAnalyzer} from './tree-shaking-analyzer'
|
|
23
|
+
import {createTsconfigAnalyzer} from './tsconfig-analyzer'
|
|
24
|
+
import {createUnusedDependencyAnalyzer} from './unused-dependency-analyzer'
|
|
25
|
+
import {createVersionAlignmentAnalyzer} from './version-alignment-analyzer'
|
|
26
|
+
|
|
27
|
+
export type {
|
|
28
|
+
AnalysisContext,
|
|
29
|
+
Analyzer,
|
|
30
|
+
AnalyzerError,
|
|
31
|
+
AnalyzerFactory,
|
|
32
|
+
AnalyzerMetadata,
|
|
33
|
+
AnalyzerOptions,
|
|
34
|
+
AnalyzerRegistration,
|
|
35
|
+
} from './analyzer'
|
|
36
|
+
export {createIssue, filterIssues, meetsMinSeverity, shouldAnalyzeCategory} from './analyzer'
|
|
37
|
+
|
|
38
|
+
export type {ArchitecturalAnalyzerOptions} from './architectural-analyzer'
|
|
39
|
+
export {architecturalAnalyzerMetadata, createArchitecturalAnalyzer} from './architectural-analyzer'
|
|
40
|
+
|
|
41
|
+
export type {BuildConfigAnalyzerOptions} from './build-config-analyzer'
|
|
42
|
+
export {buildConfigAnalyzerMetadata, createBuildConfigAnalyzer} from './build-config-analyzer'
|
|
43
|
+
|
|
44
|
+
// Dependency analysis exports
|
|
45
|
+
export type {CircularImportAnalyzerOptions} from './circular-import-analyzer'
|
|
46
|
+
export {
|
|
47
|
+
circularImportAnalyzerMetadata,
|
|
48
|
+
computeCycleStats,
|
|
49
|
+
createCircularImportAnalyzer,
|
|
50
|
+
generateCycleVisualization,
|
|
51
|
+
} from './circular-import-analyzer'
|
|
52
|
+
|
|
53
|
+
export type {
|
|
54
|
+
CircularImportStats,
|
|
55
|
+
CycleEdge,
|
|
56
|
+
CycleNode,
|
|
57
|
+
CycleVisualization,
|
|
58
|
+
} from './circular-import-analyzer'
|
|
59
|
+
export type {ConfigConsistencyAnalyzerOptions} from './config-consistency-analyzer'
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
configConsistencyAnalyzerMetadata,
|
|
63
|
+
createConfigConsistencyAnalyzer,
|
|
64
|
+
} from './config-consistency-analyzer'
|
|
65
|
+
|
|
66
|
+
// Performance analysis exports (Phase 6)
|
|
67
|
+
export type {DeadCodeAnalyzerOptions, DeadCodeStats, ExportedSymbol} from './dead-code-analyzer'
|
|
68
|
+
export {
|
|
69
|
+
computeDeadCodeStats,
|
|
70
|
+
createDeadCodeAnalyzer,
|
|
71
|
+
deadCodeAnalyzerMetadata,
|
|
72
|
+
} from './dead-code-analyzer'
|
|
73
|
+
|
|
74
|
+
export type {
|
|
75
|
+
CodeFingerprint,
|
|
76
|
+
DuplicateCodeAnalyzerOptions,
|
|
77
|
+
DuplicatePattern,
|
|
78
|
+
} from './duplicate-code-analyzer'
|
|
79
|
+
export {createDuplicateCodeAnalyzer, duplicateCodeAnalyzerMetadata} from './duplicate-code-analyzer'
|
|
80
|
+
|
|
81
|
+
export type {DuplicateDependencyAnalyzerOptions} from './duplicate-dependency-analyzer'
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
computeDuplicateStats,
|
|
85
|
+
createDuplicateDependencyAnalyzer,
|
|
86
|
+
duplicateDependencyAnalyzerMetadata,
|
|
87
|
+
} from './duplicate-dependency-analyzer'
|
|
88
|
+
export type {DuplicateDependencyStats} from './duplicate-dependency-analyzer'
|
|
89
|
+
|
|
90
|
+
export type {EslintConfigAnalyzerOptions} from './eslint-config-analyzer'
|
|
91
|
+
export {createEslintConfigAnalyzer, eslintConfigAnalyzerMetadata} from './eslint-config-analyzer'
|
|
92
|
+
|
|
93
|
+
export type {ExportsFieldAnalyzerOptions} from './exports-field-analyzer'
|
|
94
|
+
export {createExportsFieldAnalyzer, exportsFieldAnalyzerMetadata} from './exports-field-analyzer'
|
|
95
|
+
|
|
96
|
+
export type {LargeDependencyAnalyzerOptions} from './large-dependency-analyzer'
|
|
97
|
+
export {
|
|
98
|
+
createLargeDependencyAnalyzer,
|
|
99
|
+
getKnownLargePackages,
|
|
100
|
+
getPackageInfo,
|
|
101
|
+
largeDependencyAnalyzerMetadata,
|
|
102
|
+
} from './large-dependency-analyzer'
|
|
103
|
+
|
|
104
|
+
export type {PackageJsonAnalyzerOptions} from './package-json-analyzer'
|
|
105
|
+
export {createPackageJsonAnalyzer, packageJsonAnalyzerMetadata} from './package-json-analyzer'
|
|
106
|
+
export type {PeerDependencyAnalyzerOptions} from './peer-dependency-analyzer'
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
createPeerDependencyAnalyzer,
|
|
110
|
+
peerDependencyAnalyzerMetadata,
|
|
111
|
+
} from './peer-dependency-analyzer'
|
|
112
|
+
|
|
113
|
+
export type {
|
|
114
|
+
TreeShakingBlocker,
|
|
115
|
+
TreeShakingBlockerAnalyzerOptions,
|
|
116
|
+
TreeShakingBlockerType,
|
|
117
|
+
} from './tree-shaking-analyzer'
|
|
118
|
+
export {
|
|
119
|
+
createTreeShakingBlockerAnalyzer,
|
|
120
|
+
treeShakingBlockerAnalyzerMetadata,
|
|
121
|
+
} from './tree-shaking-analyzer'
|
|
122
|
+
|
|
123
|
+
export type {TsconfigAnalyzerOptions} from './tsconfig-analyzer'
|
|
124
|
+
|
|
125
|
+
export {createTsconfigAnalyzer, tsconfigAnalyzerMetadata} from './tsconfig-analyzer'
|
|
126
|
+
export type {UnusedDependencyAnalyzerOptions} from './unused-dependency-analyzer'
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
aggregatePackageImports,
|
|
130
|
+
createUnusedDependencyAnalyzer,
|
|
131
|
+
unusedDependencyAnalyzerMetadata,
|
|
132
|
+
} from './unused-dependency-analyzer'
|
|
133
|
+
export type {VersionAlignmentAnalyzerOptions} from './version-alignment-analyzer'
|
|
134
|
+
export {
|
|
135
|
+
createVersionAlignmentAnalyzer,
|
|
136
|
+
versionAlignmentAnalyzerMetadata,
|
|
137
|
+
} from './version-alignment-analyzer'
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Registry for managing analyzer registrations.
|
|
141
|
+
*/
|
|
142
|
+
export interface AnalyzerRegistry {
|
|
143
|
+
/** Register an analyzer */
|
|
144
|
+
readonly register: (id: string, registration: AnalyzerRegistration) => void
|
|
145
|
+
/** Unregister an analyzer */
|
|
146
|
+
readonly unregister: (id: string) => boolean
|
|
147
|
+
/** Get a registered analyzer */
|
|
148
|
+
readonly get: (id: string) => AnalyzerRegistration | undefined
|
|
149
|
+
/** Get all registered analyzers */
|
|
150
|
+
readonly getAll: () => Map<string, AnalyzerRegistration>
|
|
151
|
+
/** Get enabled analyzers sorted by priority */
|
|
152
|
+
readonly getEnabled: () => Analyzer[]
|
|
153
|
+
/** Check if an analyzer is registered */
|
|
154
|
+
readonly has: (id: string) => boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Creates a new analyzer registry.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const registry = createAnalyzerRegistry()
|
|
163
|
+
*
|
|
164
|
+
* // Register a custom analyzer
|
|
165
|
+
* registry.register('my-analyzer', {
|
|
166
|
+
* analyzer: createMyAnalyzer(),
|
|
167
|
+
* enabled: true,
|
|
168
|
+
* priority: 50,
|
|
169
|
+
* })
|
|
170
|
+
*
|
|
171
|
+
* // Get all enabled analyzers
|
|
172
|
+
* const analyzers = registry.getEnabled()
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export function createAnalyzerRegistry(): AnalyzerRegistry {
|
|
176
|
+
const registrations = new Map<string, AnalyzerRegistration>()
|
|
177
|
+
|
|
178
|
+
function resolveAnalyzer(registration: AnalyzerRegistration): Analyzer {
|
|
179
|
+
if (typeof registration.analyzer === 'function') {
|
|
180
|
+
return registration.analyzer()
|
|
181
|
+
}
|
|
182
|
+
return registration.analyzer
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
register(id: string, registration: AnalyzerRegistration): void {
|
|
187
|
+
registrations.set(id, registration)
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
unregister(id: string): boolean {
|
|
191
|
+
return registrations.delete(id)
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
get(id: string): AnalyzerRegistration | undefined {
|
|
195
|
+
return registrations.get(id)
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
getAll(): Map<string, AnalyzerRegistration> {
|
|
199
|
+
return new Map(registrations)
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
getEnabled(): Analyzer[] {
|
|
203
|
+
const enabled = Array.from(registrations.entries())
|
|
204
|
+
.filter(([_id, reg]) => reg.enabled)
|
|
205
|
+
.sort((a, b) => a[1].priority - b[1].priority)
|
|
206
|
+
.map(([_id, reg]) => resolveAnalyzer(reg))
|
|
207
|
+
|
|
208
|
+
return enabled
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
has(id: string): boolean {
|
|
212
|
+
return registrations.has(id)
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Built-in analyzer IDs.
|
|
219
|
+
*/
|
|
220
|
+
export const BUILTIN_ANALYZER_IDS = {
|
|
221
|
+
PACKAGE_JSON: 'package-json',
|
|
222
|
+
TSCONFIG: 'tsconfig',
|
|
223
|
+
ESLINT_CONFIG: 'eslint-config',
|
|
224
|
+
BUILD_CONFIG: 'build-config',
|
|
225
|
+
CONFIG_CONSISTENCY: 'config-consistency',
|
|
226
|
+
VERSION_ALIGNMENT: 'version-alignment',
|
|
227
|
+
EXPORTS_FIELD: 'exports-field',
|
|
228
|
+
// Dependency analyzers
|
|
229
|
+
UNUSED_DEPENDENCY: 'unused-dependency',
|
|
230
|
+
CIRCULAR_IMPORT: 'circular-import',
|
|
231
|
+
PEER_DEPENDENCY: 'peer-dependency',
|
|
232
|
+
DUPLICATE_DEPENDENCY: 'duplicate-dependency',
|
|
233
|
+
// Architectural analyzer
|
|
234
|
+
ARCHITECTURAL: 'architectural',
|
|
235
|
+
// Performance analyzers (Phase 6)
|
|
236
|
+
DEAD_CODE: 'dead-code',
|
|
237
|
+
DUPLICATE_CODE: 'duplicate-code',
|
|
238
|
+
LARGE_DEPENDENCY: 'large-dependency',
|
|
239
|
+
TREE_SHAKING_BLOCKER: 'tree-shaking-blocker',
|
|
240
|
+
} as const
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Creates a registry with all built-in analyzers registered.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* const registry = createDefaultRegistry()
|
|
248
|
+
*
|
|
249
|
+
* // Disable a specific analyzer
|
|
250
|
+
* const registration = registry.get('eslint-config')
|
|
251
|
+
* if (registration) {
|
|
252
|
+
* registry.register('eslint-config', {...registration, enabled: false})
|
|
253
|
+
* }
|
|
254
|
+
*
|
|
255
|
+
* // Run enabled analyzers
|
|
256
|
+
* const analyzers = registry.getEnabled()
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
export function createDefaultRegistry(): AnalyzerRegistry {
|
|
260
|
+
const registry = createAnalyzerRegistry()
|
|
261
|
+
|
|
262
|
+
// Register built-in analyzers with default priorities
|
|
263
|
+
registry.register(BUILTIN_ANALYZER_IDS.PACKAGE_JSON, {
|
|
264
|
+
analyzer: createPackageJsonAnalyzer(),
|
|
265
|
+
enabled: true,
|
|
266
|
+
priority: 10,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
registry.register(BUILTIN_ANALYZER_IDS.TSCONFIG, {
|
|
270
|
+
analyzer: createTsconfigAnalyzer(),
|
|
271
|
+
enabled: true,
|
|
272
|
+
priority: 20,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
registry.register(BUILTIN_ANALYZER_IDS.ESLINT_CONFIG, {
|
|
276
|
+
analyzer: createEslintConfigAnalyzer(),
|
|
277
|
+
enabled: true,
|
|
278
|
+
priority: 30,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
registry.register(BUILTIN_ANALYZER_IDS.BUILD_CONFIG, {
|
|
282
|
+
analyzer: createBuildConfigAnalyzer(),
|
|
283
|
+
enabled: true,
|
|
284
|
+
priority: 40,
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
registry.register(BUILTIN_ANALYZER_IDS.CONFIG_CONSISTENCY, {
|
|
288
|
+
analyzer: createConfigConsistencyAnalyzer(),
|
|
289
|
+
enabled: true,
|
|
290
|
+
priority: 50,
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
registry.register(BUILTIN_ANALYZER_IDS.VERSION_ALIGNMENT, {
|
|
294
|
+
analyzer: createVersionAlignmentAnalyzer(),
|
|
295
|
+
enabled: true,
|
|
296
|
+
priority: 60,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
registry.register(BUILTIN_ANALYZER_IDS.EXPORTS_FIELD, {
|
|
300
|
+
analyzer: createExportsFieldAnalyzer(),
|
|
301
|
+
enabled: true,
|
|
302
|
+
priority: 70,
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Dependency analyzers
|
|
306
|
+
registry.register(BUILTIN_ANALYZER_IDS.UNUSED_DEPENDENCY, {
|
|
307
|
+
analyzer: createUnusedDependencyAnalyzer(),
|
|
308
|
+
enabled: true,
|
|
309
|
+
priority: 80,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
registry.register(BUILTIN_ANALYZER_IDS.CIRCULAR_IMPORT, {
|
|
313
|
+
analyzer: createCircularImportAnalyzer(),
|
|
314
|
+
enabled: true,
|
|
315
|
+
priority: 90,
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
registry.register(BUILTIN_ANALYZER_IDS.PEER_DEPENDENCY, {
|
|
319
|
+
analyzer: createPeerDependencyAnalyzer(),
|
|
320
|
+
enabled: true,
|
|
321
|
+
priority: 100,
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
registry.register(BUILTIN_ANALYZER_IDS.DUPLICATE_DEPENDENCY, {
|
|
325
|
+
analyzer: createDuplicateDependencyAnalyzer(),
|
|
326
|
+
enabled: true,
|
|
327
|
+
priority: 110,
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// Architectural analyzer
|
|
331
|
+
registry.register(BUILTIN_ANALYZER_IDS.ARCHITECTURAL, {
|
|
332
|
+
analyzer: createArchitecturalAnalyzer(),
|
|
333
|
+
enabled: true,
|
|
334
|
+
priority: 120,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// Performance analyzers (Phase 6)
|
|
338
|
+
registry.register(BUILTIN_ANALYZER_IDS.DEAD_CODE, {
|
|
339
|
+
analyzer: createDeadCodeAnalyzer(),
|
|
340
|
+
enabled: true,
|
|
341
|
+
priority: 130,
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
registry.register(BUILTIN_ANALYZER_IDS.DUPLICATE_CODE, {
|
|
345
|
+
analyzer: createDuplicateCodeAnalyzer(),
|
|
346
|
+
enabled: true,
|
|
347
|
+
priority: 140,
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
registry.register(BUILTIN_ANALYZER_IDS.LARGE_DEPENDENCY, {
|
|
351
|
+
analyzer: createLargeDependencyAnalyzer(),
|
|
352
|
+
enabled: true,
|
|
353
|
+
priority: 150,
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
registry.register(BUILTIN_ANALYZER_IDS.TREE_SHAKING_BLOCKER, {
|
|
357
|
+
analyzer: createTreeShakingBlockerAnalyzer(),
|
|
358
|
+
enabled: true,
|
|
359
|
+
priority: 160,
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
return registry
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* All built-in analyzer factories for direct use.
|
|
367
|
+
*/
|
|
368
|
+
export const builtinAnalyzers = {
|
|
369
|
+
packageJson: createPackageJsonAnalyzer,
|
|
370
|
+
tsconfig: createTsconfigAnalyzer,
|
|
371
|
+
eslintConfig: createEslintConfigAnalyzer,
|
|
372
|
+
buildConfig: createBuildConfigAnalyzer,
|
|
373
|
+
configConsistency: createConfigConsistencyAnalyzer,
|
|
374
|
+
versionAlignment: createVersionAlignmentAnalyzer,
|
|
375
|
+
exportsField: createExportsFieldAnalyzer,
|
|
376
|
+
// Dependency analyzers
|
|
377
|
+
unusedDependency: createUnusedDependencyAnalyzer,
|
|
378
|
+
circularImport: createCircularImportAnalyzer,
|
|
379
|
+
peerDependency: createPeerDependencyAnalyzer,
|
|
380
|
+
duplicateDependency: createDuplicateDependencyAnalyzer,
|
|
381
|
+
// Architectural analyzer
|
|
382
|
+
architectural: createArchitecturalAnalyzer,
|
|
383
|
+
// Performance analyzers (Phase 6)
|
|
384
|
+
deadCode: createDeadCodeAnalyzer,
|
|
385
|
+
duplicateCode: createDuplicateCodeAnalyzer,
|
|
386
|
+
largeDependency: createLargeDependencyAnalyzer,
|
|
387
|
+
treeShakingBlocker: createTreeShakingBlockerAnalyzer,
|
|
388
|
+
} as const
|