@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,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import statement extractor for analyzing module dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Extracts static imports, dynamic imports, and require() calls from TypeScript/JavaScript
|
|
5
|
+
* source files for dependency analysis.
|
|
6
|
+
*
|
|
7
|
+
* Uses ts-morph types (SourceFile, SyntaxKind) which are provided as a peer dependency
|
|
8
|
+
* via @bfra.me/doc-sync's transitive dependency on ts-morph.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {SourceFile} from 'ts-morph'
|
|
12
|
+
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
|
|
15
|
+
import {SyntaxKind} from 'ts-morph'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type of import statement.
|
|
19
|
+
*/
|
|
20
|
+
export type ImportType = 'static' | 'dynamic' | 'require' | 'type-only'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Represents an extracted import statement.
|
|
24
|
+
*/
|
|
25
|
+
export interface ExtractedImport {
|
|
26
|
+
/** The module specifier (path or package name) */
|
|
27
|
+
readonly moduleSpecifier: string
|
|
28
|
+
/** Type of import */
|
|
29
|
+
readonly type: ImportType
|
|
30
|
+
/** Imported names (for named imports) */
|
|
31
|
+
readonly importedNames?: readonly string[]
|
|
32
|
+
/** Default import name (if present) */
|
|
33
|
+
readonly defaultImport?: string
|
|
34
|
+
/** Namespace import name (if present) */
|
|
35
|
+
readonly namespaceImport?: string
|
|
36
|
+
/** Whether this is a side-effect only import */
|
|
37
|
+
readonly isSideEffectOnly: boolean
|
|
38
|
+
/** Whether the import is from a relative path */
|
|
39
|
+
readonly isRelative: boolean
|
|
40
|
+
/** Whether the import is from a workspace package */
|
|
41
|
+
readonly isWorkspacePackage: boolean
|
|
42
|
+
/** Line number in the source file */
|
|
43
|
+
readonly line: number
|
|
44
|
+
/** Column number in the source file */
|
|
45
|
+
readonly column: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Result of extracting imports from a source file.
|
|
50
|
+
*/
|
|
51
|
+
export interface ImportExtractionResult {
|
|
52
|
+
/** All extracted imports */
|
|
53
|
+
readonly imports: readonly ExtractedImport[]
|
|
54
|
+
/** The source file path */
|
|
55
|
+
readonly filePath: string
|
|
56
|
+
/** External package dependencies (non-relative, non-workspace) */
|
|
57
|
+
readonly externalDependencies: readonly string[]
|
|
58
|
+
/** Workspace package dependencies */
|
|
59
|
+
readonly workspaceDependencies: readonly string[]
|
|
60
|
+
/** Relative imports within the package */
|
|
61
|
+
readonly relativeImports: readonly string[]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for import extraction.
|
|
66
|
+
*/
|
|
67
|
+
export interface ImportExtractorOptions {
|
|
68
|
+
/** Workspace package name prefixes (e.g., ['@bfra.me/']) */
|
|
69
|
+
readonly workspacePrefixes?: readonly string[]
|
|
70
|
+
/** Include type-only imports */
|
|
71
|
+
readonly includeTypeImports?: boolean
|
|
72
|
+
/** Include dynamic imports */
|
|
73
|
+
readonly includeDynamicImports?: boolean
|
|
74
|
+
/** Include require() calls */
|
|
75
|
+
readonly includeRequireCalls?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const DEFAULT_OPTIONS: Required<ImportExtractorOptions> = {
|
|
79
|
+
workspacePrefixes: ['@bfra.me/'],
|
|
80
|
+
includeTypeImports: true,
|
|
81
|
+
includeDynamicImports: true,
|
|
82
|
+
includeRequireCalls: true,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Extracts all imports from a TypeScript/JavaScript source file.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const project = createProject()
|
|
91
|
+
* const sourceFile = project.addSourceFileAtPath('./src/index.ts')
|
|
92
|
+
* const result = extractImports(sourceFile)
|
|
93
|
+
*
|
|
94
|
+
* for (const imp of result.imports) {
|
|
95
|
+
* console.log(`Import: ${imp.moduleSpecifier} (${imp.type})`)
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function extractImports(
|
|
100
|
+
sourceFile: SourceFile,
|
|
101
|
+
options?: ImportExtractorOptions,
|
|
102
|
+
): ImportExtractionResult {
|
|
103
|
+
const opts = {...DEFAULT_OPTIONS, ...options}
|
|
104
|
+
const imports: ExtractedImport[] = []
|
|
105
|
+
const filePath = sourceFile.getFilePath()
|
|
106
|
+
|
|
107
|
+
// Extract static import declarations
|
|
108
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
109
|
+
const moduleSpecifier = importDecl.getModuleSpecifierValue()
|
|
110
|
+
const isTypeOnly = importDecl.isTypeOnly()
|
|
111
|
+
|
|
112
|
+
if (isTypeOnly && !opts.includeTypeImports) {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const {line, column} = sourceFile.getLineAndColumnAtPos(importDecl.getStart())
|
|
117
|
+
|
|
118
|
+
const importedNames: string[] = []
|
|
119
|
+
let defaultImport: string | undefined
|
|
120
|
+
let namespaceImport: string | undefined
|
|
121
|
+
|
|
122
|
+
const defaultImportNode = importDecl.getDefaultImport()
|
|
123
|
+
if (defaultImportNode !== undefined) {
|
|
124
|
+
defaultImport = defaultImportNode.getText()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const namespaceImportNode = importDecl.getNamespaceImport()
|
|
128
|
+
if (namespaceImportNode !== undefined) {
|
|
129
|
+
namespaceImport = namespaceImportNode.getText()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const namedImport of importDecl.getNamedImports()) {
|
|
133
|
+
importedNames.push(namedImport.getName())
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const isSideEffectOnly =
|
|
137
|
+
defaultImport === undefined && namespaceImport === undefined && importedNames.length === 0
|
|
138
|
+
|
|
139
|
+
imports.push({
|
|
140
|
+
moduleSpecifier,
|
|
141
|
+
type: isTypeOnly ? 'type-only' : 'static',
|
|
142
|
+
importedNames: importedNames.length > 0 ? importedNames : undefined,
|
|
143
|
+
defaultImport,
|
|
144
|
+
namespaceImport,
|
|
145
|
+
isSideEffectOnly,
|
|
146
|
+
isRelative: isRelativeImport(moduleSpecifier),
|
|
147
|
+
isWorkspacePackage: isWorkspacePackageImport(moduleSpecifier, opts.workspacePrefixes),
|
|
148
|
+
line,
|
|
149
|
+
column,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Extract dynamic imports
|
|
154
|
+
if (opts.includeDynamicImports) {
|
|
155
|
+
sourceFile.forEachDescendant(node => {
|
|
156
|
+
if (node.getKind() === SyntaxKind.CallExpression) {
|
|
157
|
+
const callExpr = node.asKind(SyntaxKind.CallExpression)
|
|
158
|
+
if (callExpr === undefined) return
|
|
159
|
+
|
|
160
|
+
const expr = callExpr.getExpression()
|
|
161
|
+
if (expr.getKind() === SyntaxKind.ImportKeyword) {
|
|
162
|
+
const args = callExpr.getArguments()
|
|
163
|
+
if (args.length > 0) {
|
|
164
|
+
const firstArg = args[0]
|
|
165
|
+
if (firstArg !== undefined && firstArg.getKind() === SyntaxKind.StringLiteral) {
|
|
166
|
+
const stringLiteral = firstArg.asKind(SyntaxKind.StringLiteral)
|
|
167
|
+
if (stringLiteral !== undefined) {
|
|
168
|
+
const moduleSpecifier = stringLiteral.getLiteralValue()
|
|
169
|
+
const {line: dynamicLine, column: dynamicColumn} = sourceFile.getLineAndColumnAtPos(
|
|
170
|
+
node.getStart(),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
imports.push({
|
|
174
|
+
moduleSpecifier,
|
|
175
|
+
type: 'dynamic',
|
|
176
|
+
isSideEffectOnly: false,
|
|
177
|
+
isRelative: isRelativeImport(moduleSpecifier),
|
|
178
|
+
isWorkspacePackage: isWorkspacePackageImport(
|
|
179
|
+
moduleSpecifier,
|
|
180
|
+
opts.workspacePrefixes,
|
|
181
|
+
),
|
|
182
|
+
line: dynamicLine,
|
|
183
|
+
column: dynamicColumn,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extract require() calls
|
|
194
|
+
if (opts.includeRequireCalls) {
|
|
195
|
+
sourceFile.forEachDescendant(node => {
|
|
196
|
+
if (node.getKind() === SyntaxKind.CallExpression) {
|
|
197
|
+
const callExpr = node.asKind(SyntaxKind.CallExpression)
|
|
198
|
+
if (callExpr === undefined) return
|
|
199
|
+
|
|
200
|
+
const expr = callExpr.getExpression()
|
|
201
|
+
if (expr.getKind() === SyntaxKind.Identifier && expr.getText() === 'require') {
|
|
202
|
+
const args = callExpr.getArguments()
|
|
203
|
+
if (args.length > 0) {
|
|
204
|
+
const firstArg = args[0]
|
|
205
|
+
if (firstArg !== undefined && firstArg.getKind() === SyntaxKind.StringLiteral) {
|
|
206
|
+
const stringLiteral = firstArg.asKind(SyntaxKind.StringLiteral)
|
|
207
|
+
if (stringLiteral !== undefined) {
|
|
208
|
+
const moduleSpecifier = stringLiteral.getLiteralValue()
|
|
209
|
+
const {line: requireLine, column: requireColumn} = sourceFile.getLineAndColumnAtPos(
|
|
210
|
+
node.getStart(),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
imports.push({
|
|
214
|
+
moduleSpecifier,
|
|
215
|
+
type: 'require',
|
|
216
|
+
isSideEffectOnly: false,
|
|
217
|
+
isRelative: isRelativeImport(moduleSpecifier),
|
|
218
|
+
isWorkspacePackage: isWorkspacePackageImport(
|
|
219
|
+
moduleSpecifier,
|
|
220
|
+
opts.workspacePrefixes,
|
|
221
|
+
),
|
|
222
|
+
line: requireLine,
|
|
223
|
+
column: requireColumn,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Categorize imports
|
|
234
|
+
const externalDependencies: string[] = []
|
|
235
|
+
const workspaceDependencies: string[] = []
|
|
236
|
+
const relativeImports: string[] = []
|
|
237
|
+
|
|
238
|
+
for (const imp of imports) {
|
|
239
|
+
const pkgName = getPackageNameFromSpecifier(imp.moduleSpecifier)
|
|
240
|
+
|
|
241
|
+
if (imp.isRelative) {
|
|
242
|
+
if (!relativeImports.includes(imp.moduleSpecifier)) {
|
|
243
|
+
relativeImports.push(imp.moduleSpecifier)
|
|
244
|
+
}
|
|
245
|
+
} else if (imp.isWorkspacePackage) {
|
|
246
|
+
if (!workspaceDependencies.includes(pkgName)) {
|
|
247
|
+
workspaceDependencies.push(pkgName)
|
|
248
|
+
}
|
|
249
|
+
} else if (!externalDependencies.includes(pkgName)) {
|
|
250
|
+
externalDependencies.push(pkgName)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
imports,
|
|
256
|
+
filePath,
|
|
257
|
+
externalDependencies,
|
|
258
|
+
workspaceDependencies,
|
|
259
|
+
relativeImports,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Checks if a module specifier is a relative import.
|
|
265
|
+
*/
|
|
266
|
+
export function isRelativeImport(moduleSpecifier: string): boolean {
|
|
267
|
+
return moduleSpecifier.startsWith('.') || moduleSpecifier.startsWith('/')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Checks if a module specifier is a workspace package import.
|
|
272
|
+
*/
|
|
273
|
+
export function isWorkspacePackageImport(
|
|
274
|
+
moduleSpecifier: string,
|
|
275
|
+
workspacePrefixes: readonly string[],
|
|
276
|
+
): boolean {
|
|
277
|
+
return workspacePrefixes.some(prefix => moduleSpecifier.startsWith(prefix))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Extracts the package name from a module specifier.
|
|
282
|
+
*
|
|
283
|
+
* For scoped packages like '@scope/pkg/path', returns '@scope/pkg'.
|
|
284
|
+
* For unscoped packages like 'lodash/fp', returns 'lodash'.
|
|
285
|
+
*/
|
|
286
|
+
export function getPackageNameFromSpecifier(moduleSpecifier: string): string {
|
|
287
|
+
if (isRelativeImport(moduleSpecifier)) {
|
|
288
|
+
return moduleSpecifier
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Scoped package
|
|
292
|
+
if (moduleSpecifier.startsWith('@')) {
|
|
293
|
+
const parts = moduleSpecifier.split('/')
|
|
294
|
+
if (parts.length >= 2) {
|
|
295
|
+
return `${parts[0]}/${parts[1]}`
|
|
296
|
+
}
|
|
297
|
+
return moduleSpecifier
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Unscoped package
|
|
301
|
+
const slashIndex = moduleSpecifier.indexOf('/')
|
|
302
|
+
if (slashIndex > 0) {
|
|
303
|
+
return moduleSpecifier.slice(0, slashIndex)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return moduleSpecifier
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Resolves a relative import to an absolute file path.
|
|
311
|
+
*/
|
|
312
|
+
export function resolveRelativeImport(fromFile: string, moduleSpecifier: string): string {
|
|
313
|
+
const fromDir = path.dirname(fromFile)
|
|
314
|
+
return path.resolve(fromDir, moduleSpecifier)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Gets unique package dependencies from imports.
|
|
319
|
+
*/
|
|
320
|
+
export function getUniqueDependencies(results: readonly ImportExtractionResult[]): {
|
|
321
|
+
readonly external: readonly string[]
|
|
322
|
+
readonly workspace: readonly string[]
|
|
323
|
+
} {
|
|
324
|
+
const external = new Set<string>()
|
|
325
|
+
const workspace = new Set<string>()
|
|
326
|
+
|
|
327
|
+
for (const result of results) {
|
|
328
|
+
for (const dep of result.externalDependencies) {
|
|
329
|
+
external.add(dep)
|
|
330
|
+
}
|
|
331
|
+
for (const dep of result.workspaceDependencies) {
|
|
332
|
+
workspace.add(dep)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
external: [...external].sort(),
|
|
338
|
+
workspace: [...workspace].sort(),
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser module exports.
|
|
3
|
+
*
|
|
4
|
+
* Provides unified access to all parsing utilities for TypeScript source files,
|
|
5
|
+
* configuration files, and import extraction.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Configuration parser exports
|
|
9
|
+
export {
|
|
10
|
+
getAllDependencies,
|
|
11
|
+
parsePackageJson,
|
|
12
|
+
parsePackageJsonContent,
|
|
13
|
+
parseTsConfig,
|
|
14
|
+
parseTsConfigContent,
|
|
15
|
+
resolveTsConfigExtends,
|
|
16
|
+
} from './config-parser'
|
|
17
|
+
export type {
|
|
18
|
+
ConfigError,
|
|
19
|
+
ConfigErrorCode,
|
|
20
|
+
ParsedPackageJson,
|
|
21
|
+
ParsedTsConfig,
|
|
22
|
+
TsCompilerOptions,
|
|
23
|
+
TsProjectReference,
|
|
24
|
+
} from './config-parser'
|
|
25
|
+
|
|
26
|
+
// Import extractor exports
|
|
27
|
+
export {
|
|
28
|
+
extractImports,
|
|
29
|
+
getPackageNameFromSpecifier,
|
|
30
|
+
getUniqueDependencies,
|
|
31
|
+
isRelativeImport,
|
|
32
|
+
isWorkspacePackageImport,
|
|
33
|
+
resolveRelativeImport,
|
|
34
|
+
} from './import-extractor'
|
|
35
|
+
export type {
|
|
36
|
+
ExtractedImport,
|
|
37
|
+
ImportExtractionResult,
|
|
38
|
+
ImportExtractorOptions,
|
|
39
|
+
ImportType,
|
|
40
|
+
} from './import-extractor'
|
|
41
|
+
|
|
42
|
+
// TypeScript parser exports
|
|
43
|
+
export {
|
|
44
|
+
createProject,
|
|
45
|
+
getAllSourceFiles,
|
|
46
|
+
getSourceFile,
|
|
47
|
+
isJavaScriptFile,
|
|
48
|
+
isSourceFile,
|
|
49
|
+
isTypeScriptFile,
|
|
50
|
+
parseSourceContent,
|
|
51
|
+
parseSourceFile,
|
|
52
|
+
parseSourceFiles,
|
|
53
|
+
} from './typescript-parser'
|
|
54
|
+
export type {ParseError, ParseErrorCode, TypeScriptParserOptions} from './typescript-parser'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript AST parser utilities for workspace analysis.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports core parsing utilities from @bfra.me/doc-sync/parsers and provides
|
|
5
|
+
* additional workspace-analyzer-specific helpers for multi-file analysis.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {ParseError} from '@bfra.me/doc-sync/types'
|
|
9
|
+
import type {Result} from '@bfra.me/es/result'
|
|
10
|
+
import type {Project, SourceFile} from 'ts-morph'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createProject as createDocSyncProject,
|
|
14
|
+
parseSourceFile as parseDocSyncSourceFile,
|
|
15
|
+
} from '@bfra.me/doc-sync/parsers'
|
|
16
|
+
import {err, ok} from '@bfra.me/es/result'
|
|
17
|
+
|
|
18
|
+
// Re-export core TypeScript parsing utilities from @bfra.me/doc-sync
|
|
19
|
+
export {createProject, parseSourceContent, parseSourceFile} from '@bfra.me/doc-sync/parsers'
|
|
20
|
+
|
|
21
|
+
export type {TypeScriptParserOptions} from '@bfra.me/doc-sync/parsers'
|
|
22
|
+
|
|
23
|
+
// Re-export ParseError type for consumers
|
|
24
|
+
export type {ParseError, ParseErrorCode} from '@bfra.me/doc-sync/types'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets the source file at a path, creating the project if needed.
|
|
28
|
+
*
|
|
29
|
+
* Convenience function for one-off file parsing without manually managing a project.
|
|
30
|
+
*/
|
|
31
|
+
export function getSourceFile(
|
|
32
|
+
filePath: string,
|
|
33
|
+
options?: {tsConfigPath?: string; compilerOptions?: Record<string, unknown>},
|
|
34
|
+
): Result<SourceFile, ParseError> {
|
|
35
|
+
const project = createDocSyncProject(options)
|
|
36
|
+
return parseDocSyncSourceFile(project, filePath)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parses multiple source files into a single project.
|
|
41
|
+
*
|
|
42
|
+
* More efficient than parsing files individually when analyzing many files,
|
|
43
|
+
* as the project can share type resolution and compiler settings.
|
|
44
|
+
*/
|
|
45
|
+
export function parseSourceFiles(
|
|
46
|
+
filePaths: readonly string[],
|
|
47
|
+
options?: {tsConfigPath?: string; compilerOptions?: Record<string, unknown>},
|
|
48
|
+
): Result<Project, ParseError> {
|
|
49
|
+
const project = createDocSyncProject(options)
|
|
50
|
+
|
|
51
|
+
for (const filePath of filePaths) {
|
|
52
|
+
try {
|
|
53
|
+
project.addSourceFileAtPath(filePath)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return err({
|
|
56
|
+
code: 'FILE_NOT_FOUND',
|
|
57
|
+
message: `Failed to add source file: ${filePath}`,
|
|
58
|
+
filePath,
|
|
59
|
+
cause: error,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return ok(project)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets all source files from a project.
|
|
69
|
+
*/
|
|
70
|
+
export function getAllSourceFiles(project: Project): readonly SourceFile[] {
|
|
71
|
+
return project.getSourceFiles()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks if a file path represents a TypeScript file.
|
|
76
|
+
*/
|
|
77
|
+
export function isTypeScriptFile(filePath: string): boolean {
|
|
78
|
+
const ext = filePath.toLowerCase()
|
|
79
|
+
return ext.endsWith('.ts') || ext.endsWith('.tsx') || ext.endsWith('.mts') || ext.endsWith('.cts')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Checks if a file path represents a JavaScript file.
|
|
84
|
+
*/
|
|
85
|
+
export function isJavaScriptFile(filePath: string): boolean {
|
|
86
|
+
const ext = filePath.toLowerCase()
|
|
87
|
+
return ext.endsWith('.js') || ext.endsWith('.jsx') || ext.endsWith('.mjs') || ext.endsWith('.cjs')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Checks if a file path is a parseable source file (TypeScript or JavaScript).
|
|
92
|
+
*/
|
|
93
|
+
export function isSourceFile(filePath: string): boolean {
|
|
94
|
+
return isTypeScriptFile(filePath) || isJavaScriptFile(filePath)
|
|
95
|
+
}
|