-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import * as ts from 'typescript'; | ||
import * as path from 'path'; | ||
import * as fs from 'fs'; | ||
|
||
interface DependencyAnalyzerOptions { | ||
baseDir: string; | ||
includeNodeModules?: boolean; | ||
includeCssModules?: boolean; | ||
} | ||
|
||
class DependencyAnalyzer { | ||
private readonly baseDir: string; | ||
private readonly includeNodeModules: boolean; | ||
private readonly includeCssModules: boolean; | ||
private readonly cache: Map<string, Set<string>> = new Map(); | ||
|
||
constructor(options: DependencyAnalyzerOptions) { | ||
this.baseDir = options.baseDir; | ||
this.includeNodeModules = options.includeNodeModules ?? false; | ||
this.includeCssModules = options.includeCssModules ?? true; | ||
} | ||
|
||
public analyze(filePath: string): Set<string> { | ||
if (this.cache.has(filePath)) { | ||
return this.cache.get(filePath)!; | ||
} | ||
|
||
const dependencies = new Set<string>(); | ||
const sourceFile = this.createSourceFile(filePath); | ||
|
||
if (!sourceFile) { | ||
return dependencies; | ||
} | ||
|
||
this.visitNode(sourceFile, dependencies); | ||
this.cache.set(filePath, dependencies); | ||
|
||
return dependencies; | ||
} | ||
|
||
private createSourceFile(filePath: string): ts.SourceFile | null { | ||
try { | ||
const content = fs.readFileSync(filePath, 'utf-8'); | ||
return ts.createSourceFile( | ||
filePath, | ||
content, | ||
ts.ScriptTarget.Latest, | ||
true | ||
); | ||
} catch (error) { | ||
console.error(`Error reading file ${filePath}:`, error); | ||
return null; | ||
} | ||
} | ||
|
||
private visitNode(node: ts.Node, dependencies: Set<string>): void { | ||
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { | ||
const moduleSpecifier = node.moduleSpecifier; | ||
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) { | ||
const importPath = moduleSpecifier.text; | ||
this.addDependency(importPath, dependencies); | ||
} | ||
} | ||
|
||
// Check for dynamic imports | ||
if (ts.isCallExpression(node) && | ||
node.expression.kind === ts.SyntaxKind.ImportKeyword) { | ||
const argument = node.arguments[0]; | ||
if (argument && ts.isStringLiteral(argument)) { | ||
this.addDependency(argument.text, dependencies); | ||
} | ||
} | ||
|
||
ts.forEachChild(node, child => this.visitNode(child, dependencies)); | ||
} | ||
|
||
private addDependency(importPath: string, dependencies: Set<string>): void { | ||
// Skip node_modules unless explicitly included | ||
if (!this.includeNodeModules && importPath.startsWith('node_modules')) { | ||
return; | ||
} | ||
|
||
// Handle CSS modules if enabled | ||
if (this.includeCssModules && importPath.endsWith('.css')) { | ||
const resolvedPath = this.resolvePath(importPath); | ||
if (resolvedPath) { | ||
dependencies.add(resolvedPath); | ||
} | ||
return; | ||
} | ||
|
||
// Resolve relative imports | ||
if (importPath.startsWith('.')) { | ||
const resolvedPath = this.resolvePath(importPath); | ||
if (resolvedPath) { | ||
dependencies.add(resolvedPath); | ||
} | ||
} | ||
} | ||
|
||
private resolvePath(importPath: string): string | null { | ||
try { | ||
const extensions = ['.tsx', '.ts', '.jsx', '.js', '.css']; | ||
const resolvedPath = path.resolve(this.baseDir, importPath); | ||
|
||
// Try exact path first | ||
if (fs.existsSync(resolvedPath)) { | ||
return resolvedPath; | ||
} | ||
|
||
// Try with extensions | ||
for (const ext of extensions) { | ||
const pathWithExt = resolvedPath + ext; | ||
if (fs.existsSync(pathWithExt)) { | ||
return pathWithExt; | ||
} | ||
} | ||
|
||
return null; | ||
} catch (error) { | ||
console.error(`Error resolving path ${importPath}:`, error); | ||
return null; | ||
} | ||
} | ||
} | ||
|
||
export { DependencyAnalyzer, type DependencyAnalyzerOptions }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import * as ts from 'typescript'; | ||
import * as path from 'path'; | ||
import * as fs from 'fs'; | ||
|
||
function getLocalFileDependencies(pathToTsxFile: string): string[] { | ||
// Ensure absolute path | ||
const absolutePath = path.resolve(pathToTsxFile); | ||
const baseDir = path.dirname(absolutePath); | ||
|
||
// Read and parse the file | ||
const content = fs.readFileSync(absolutePath, 'utf-8'); | ||
const sourceFile = ts.createSourceFile( | ||
absolutePath, | ||
content, | ||
ts.ScriptTarget.Latest, | ||
true | ||
); | ||
|
||
const dependencies = new Set<string>(); | ||
|
||
// Recursively visit nodes to find imports | ||
function visit(node: ts.Node) { | ||
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { | ||
const moduleSpecifier = node.moduleSpecifier; | ||
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) { | ||
const importPath = moduleSpecifier.text; | ||
// Only process local imports (starting with . or ..) | ||
if (importPath.startsWith('.')) { | ||
resolveAndAddDependency(importPath); | ||
} | ||
} | ||
} | ||
|
||
// Handle dynamic imports | ||
if (ts.isCallExpression(node) && | ||
node.expression.kind === ts.SyntaxKind.ImportKeyword) { | ||
const argument = node.arguments[0]; | ||
if (argument && ts.isStringLiteral(argument)) { | ||
const importPath = argument.text; | ||
if (importPath.startsWith('.')) { | ||
resolveAndAddDependency(importPath); | ||
} | ||
} | ||
} | ||
|
||
ts.forEachChild(node, visit); | ||
} | ||
|
||
// Helper to resolve and add dependency paths | ||
function resolveAndAddDependency(importPath: string) { | ||
const extensions = ['.tsx', '.ts', '.jsx', '.js', '.css', '.scss', '.sass', '.less']; | ||
let resolvedPath = path.resolve(baseDir, importPath); | ||
|
||
// Check if path exists as-is | ||
if (fs.existsSync(resolvedPath)) { | ||
dependencies.add(resolvedPath); | ||
return; | ||
} | ||
|
||
// Try with extensions | ||
for (const ext of extensions) { | ||
const pathWithExt = resolvedPath + ext; | ||
if (fs.existsSync(pathWithExt)) { | ||
dependencies.add(pathWithExt); | ||
return; | ||
} | ||
} | ||
|
||
// Check for index files in directories | ||
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) { | ||
for (const ext of extensions) { | ||
const indexPath = path.join(resolvedPath, `index${ext}`); | ||
if (fs.existsSync(indexPath)) { | ||
dependencies.add(indexPath); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Start the traversal | ||
visit(sourceFile); | ||
|
||
return Array.from(dependencies); | ||
} | ||
|
||
export { getLocalFileDependencies }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters