diff --git a/src/parsers/importParser.ts b/src/parsers/importParser.ts new file mode 100644 index 0000000..d7346f3 --- /dev/null +++ b/src/parsers/importParser.ts @@ -0,0 +1,144 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import type * as TSESTree from '@typescript-eslint/typescript-estree'; +import { createASTUtils } from '../utils/astUtils'; +import { TypeReference } from '../types'; + +export interface ImportParserOptions { + resolvePaths?: boolean; + basePath?: string; +} + +export interface ParsedImport { + moduleName: string; + filePath: string; + defaultImport?: string; + namespaceImport?: string; + namedImports: NamedImport[]; + reExports: string[]; + isExternal: boolean; +} + +export interface NamedImport { + localName: string; + importedName: string; +} + +export class ImportParser { + private astUtils: ReturnType; + private options: ImportParserOptions; + + constructor(options: ImportParserOptions = {}) { + this.astUtils = createASTUtils(); + this.options = { + resolvePaths: options.resolvePaths ?? true, + basePath: options.basePath ?? process.cwd() + }; + } + + parse(source: string, filePath: string): ParsedImport[] { + const ast = this.astUtils.parse(source, filePath); + const imports = this.astUtils.getImports(ast); + + return imports.map((imp) => this.parseImportDeclaration(imp, filePath)); + } + + private parseImportDeclaration(node: TSESTree.ImportDeclaration, filePath: string): ParsedImport { + const moduleName = typeof node.source.value === 'string' ? node.source.value : ''; + const isExternal = this.isExternalModule(moduleName); + + const namedImports: NamedImport[] = []; + let defaultImport: string | undefined; + let namespaceImport: string | undefined; + + if (node.specifiers && node.specifiers.length > 0) { + for (const specifier of node.specifiers) { + if (specifier.type === 'ImportSpecifier') { + namedImports.push({ + localName: specifier.local.name, + importedName: specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value + }); + } else if (specifier.type === 'ImportDefaultSpecifier') { + defaultImport = specifier.local.name; + } else if (specifier.type === 'ImportNamespaceSpecifier') { + namespaceImport = specifier.local.name; + } + } + } + + return { + moduleName, + filePath, + defaultImport, + namespaceImport, + namedImports, + reExports: [], + isExternal + }; + } + + private isExternalModule(moduleName: string): boolean { + return ( + moduleName.startsWith('@') || + !moduleName.startsWith('.') && + !moduleName.startsWith('/') && + !moduleName.startsWith('http://') && + !moduleName.startsWith('https://') + ); + } + + extractImportedTypes(source: string, filePath: string): TypeReference[] { + const ast = this.astUtils.parse(source, filePath); + const typeRefs = this.astUtils.findNodes( + ast, + (n): n is TSESTree.TSTypeReference => n.type === 'TSTypeReference' + ); + + return typeRefs.map((ref) => ({ + name: ref.typeName.type === 'Identifier' ? ref.typeName.name : '', + module: undefined, + isExternal: false, + location: { + filePath, + startLine: ref.loc?.start?.line ?? 0, + startColumn: ref.loc?.start?.column ?? 0, + endLine: ref.loc?.end?.line ?? 0, + endColumn: ref.loc?.end?.column ?? 0 + } + })); + } + + resolveImportPath(moduleName: string, fromFile: string): string { + if (this.isExternalModule(moduleName)) { + return moduleName; + } + + const baseDir = path.dirname(fromFile); + let resolvedPath = moduleName; + + if (!path.extname(resolvedPath)) { + const tsPath = path.join(baseDir, `${resolvedPath}.ts`); + const tsxPath = path.join(baseDir, `${resolvedPath}.tsx`); + const dtsPath = path.join(baseDir, `${resolvedPath}.d.ts`); + const indexPath = path.join(baseDir, resolvedPath, 'index.ts'); + + if (fs.existsSync(tsPath)) { + resolvedPath = tsPath; + } else if (fs.existsSync(tsxPath)) { + resolvedPath = tsxPath; + } else if (fs.existsSync(dtsPath)) { + resolvedPath = dtsPath; + } else if (fs.existsSync(indexPath)) { + resolvedPath = indexPath; + } else { + resolvedPath = path.join(baseDir, resolvedPath); + } + } + + return path.resolve(baseDir, resolvedPath); + } +} + +export function createImportParser(options?: ImportParserOptions): ImportParser { + return new ImportParser(options); +}