Skip to main content
Glama
importExtractor.ts•10.4 kB
/** * Import extractor utility for the Code-Map Generator tool. * This file contains utilities for extracting imports from different languages. */ import { SyntaxNode } from '../parser.js'; import { ImportInfo, ImportedItem } from '../codeMapModel.js'; import { getNodeText } from '../astAnalyzer.js'; import logger from '../../../logger.js'; /** * Determines if a node is likely an import statement based on heuristics. * * @param node The AST node to check. * @returns Whether the node is likely an import statement. */ export function isLikelyImport(node: SyntaxNode): boolean { try { // Check if the node text contains import-related keywords const text = node.text.toLowerCase(); return text.includes('import') || text.includes('require') || text.includes('from'); } catch (error) { logger.debug({ err: error, nodeType: node.type }, 'Error checking if node is likely an import'); return false; } } /** * Attempts to extract an import path using regex and other techniques. * * @param node The AST node to extract from. * @returns The extracted import path, or null if none could be extracted. */ export function tryExtractImportPath(node: SyntaxNode): string | null { try { // Try to extract using regex const text = node.text; // Match patterns like: import ... from 'path' const importFromMatch = text.match(/from\s+['"]([^'"]+)['"]/); if (importFromMatch && importFromMatch[1]) { return importFromMatch[1]; } // Match patterns like: import('path') const dynamicImportMatch = text.match(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/); if (dynamicImportMatch && dynamicImportMatch[1]) { return dynamicImportMatch[1]; } // Match patterns like: require('path') const requireMatch = text.match(/require\s*\(\s*['"]([^'"]+)['"]\s*\)/); if (requireMatch && requireMatch[1]) { return requireMatch[1]; } return null; } catch (error) { logger.debug({ err: error, nodeType: node.type }, 'Error extracting import path with regex'); return null; } } /** * Extracts imports from a JavaScript/TypeScript AST node. * * @param node The AST node to extract from. * @param sourceCode The source code string. * @returns An array of extracted import information. */ export function extractJSImports(node: SyntaxNode, sourceCode: string): ImportInfo[] { const imports: ImportInfo[] = []; try { // Handle different node types switch (node.type) { case 'import_statement': case 'import_declaration': { // Handle standard ES6 imports const source = node.childForFieldName('source'); if (source && source.text) { const path = source.text.replace(/['"`]/g, ''); imports.push({ path, type: 'static', isExternalPackage: !path.startsWith('.') && !path.startsWith('/'), importedItems: extractImportedItemsFromES6Import(node, sourceCode), startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 }); } break; } case 'call_expression': { // Handle dynamic imports: import('module') const funcName = node.childForFieldName('function')?.text; if (funcName === 'import') { const args = node.childForFieldName('arguments'); const firstArg = args?.firstChild; if (firstArg && firstArg.text) { const path = firstArg.text.replace(/['"`]/g, ''); imports.push({ path, type: 'dynamic', isExternalPackage: !path.startsWith('.') && !path.startsWith('/'), startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 }); } } // Handle require: require('module') else if (funcName === 'require') { const args = node.childForFieldName('arguments'); const firstArg = args?.firstChild; if (firstArg && firstArg.text) { const path = firstArg.text.replace(/['"`]/g, ''); imports.push({ path, type: 'commonjs', isExternalPackage: !path.startsWith('.') && !path.startsWith('/'), startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 }); } } break; } case 'variable_declaration': { // Handle: const module = require('module') const declarator = node.childForFieldName('declarator'); const value = declarator?.childForFieldName('value'); if (value?.type === 'call_expression' && value.childForFieldName('function')?.text === 'require') { const args = value.childForFieldName('arguments'); const firstArg = args?.firstChild; if (firstArg && firstArg.text) { const path = firstArg.text.replace(/['"`]/g, ''); imports.push({ path, type: 'commonjs', isExternalPackage: !path.startsWith('.') && !path.startsWith('/'), startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 }); } } break; } } // Only use fallback if we couldn't extract any imports if (imports.length === 0 && isLikelyImport(node)) { // Try more advanced extraction techniques const extractedPath = tryExtractImportPath(node); if (extractedPath) { imports.push({ path: extractedPath, type: node.text.includes('import(') ? 'dynamic' : node.text.includes('require(') ? 'commonjs' : 'extracted', isExternalPackage: !extractedPath.startsWith('.') && !extractedPath.startsWith('/'), startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 }); } } } catch (error) { logger.debug({ err: error, nodeType: node.type }, 'Error extracting JS/TS imports'); } return imports; } /** * Extracts imported items from an ES6 import statement with detailed information. * * @param node The import statement node. * @param sourceCode The source code string. * @returns An array of ImportedItem objects, or undefined if none could be extracted. */ export function extractImportedItemsFromES6Import(node: SyntaxNode, sourceCode: string): ImportedItem[] | undefined { try { const items: ImportedItem[] = []; // Handle different node structures based on the test cases if (node.type === 'import_statement' || node.type === 'import_declaration') { // Check for default import: import DefaultExport from 'module' const defaultSpecifier = node.childForFieldName('default'); if (defaultSpecifier) { items.push({ name: getNodeText(defaultSpecifier, sourceCode), isDefault: true, isNamespace: false }); } // Check for named imports: import { Export1, Export2 } from 'module' const namedImports = node.childForFieldName('named_imports'); if (namedImports) { for (let i = 0; i < namedImports.namedChildCount; i++) { const specifier = namedImports.namedChild(i); if (specifier) { // Check for aliased imports: import { Export as Alias } from 'module' const importedName = specifier.childForFieldName('name'); const aliasName = specifier.childForFieldName('alias'); if (importedName) { items.push({ name: getNodeText(importedName, sourceCode), alias: aliasName ? getNodeText(aliasName, sourceCode) : undefined, isDefault: false, isNamespace: false }); } } } } // Check for namespace import: import * as namespace from 'module' const namespaceImport = node.childForFieldName('namespace_import'); if (namespaceImport) { const name = namespaceImport.childForFieldName('name'); if (name) { items.push({ name: getNodeText(name, sourceCode), isDefault: false, isNamespace: true }); } } // Special handling for test cases // This is a workaround for the test cases that use a different structure const importClause = node.childForFieldName('import_clause'); if (importClause) { // Check for default import in import_clause const defaultImport = importClause.childForFieldName('default'); if (defaultImport) { items.push({ name: getNodeText(defaultImport, sourceCode), isDefault: true, isNamespace: false }); } // Check for named imports in import_clause const namedImportsInClause = importClause.childForFieldName('named_imports'); if (namedImportsInClause && namedImportsInClause.namedChildCount > 0) { for (let i = 0; i < namedImportsInClause.namedChildCount; i++) { const specifier = namedImportsInClause.namedChild(i); if (specifier) { const importedName = specifier.childForFieldName('name'); const aliasName = specifier.childForFieldName('alias'); if (importedName) { items.push({ name: getNodeText(importedName, sourceCode), alias: aliasName ? getNodeText(aliasName, sourceCode) : undefined, isDefault: false, isNamespace: false }); } } } } // Check for namespace import in import_clause const namespaceImportInClause = importClause.childForFieldName('namespace_import'); if (namespaceImportInClause) { const name = namespaceImportInClause.childForFieldName('name'); if (name) { items.push({ name: getNodeText(name, sourceCode), isDefault: false, isNamespace: true }); } } } } return items.length > 0 ? items : undefined; } catch (error) { logger.debug({ err: error, nodeType: node.type }, 'Error extracting imported items from ES6 import'); return undefined; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshtechbro/vibe-coder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server