codeguardian-mcp 1.0.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/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/agent/agentTools.d.ts +26 -0
- package/dist/agent/agentTools.d.ts.map +1 -0
- package/dist/agent/agentTools.js +699 -0
- package/dist/agent/agentTools.js.map +1 -0
- package/dist/agent/autoValidator.d.ts +110 -0
- package/dist/agent/autoValidator.d.ts.map +1 -0
- package/dist/agent/autoValidator.js +964 -0
- package/dist/agent/autoValidator.js.map +1 -0
- package/dist/agent/fileWatcher.d.ts +28 -0
- package/dist/agent/fileWatcher.d.ts.map +1 -0
- package/dist/agent/fileWatcher.js +88 -0
- package/dist/agent/fileWatcher.js.map +1 -0
- package/dist/agent/guardianPersistence.d.ts +98 -0
- package/dist/agent/guardianPersistence.d.ts.map +1 -0
- package/dist/agent/guardianPersistence.js +296 -0
- package/dist/agent/guardianPersistence.js.map +1 -0
- package/dist/agent/mcpNotifications.d.ts +38 -0
- package/dist/agent/mcpNotifications.d.ts.map +1 -0
- package/dist/agent/mcpNotifications.js +81 -0
- package/dist/agent/mcpNotifications.js.map +1 -0
- package/dist/analyzers/aiPatterns.d.ts +16 -0
- package/dist/analyzers/aiPatterns.d.ts.map +1 -0
- package/dist/analyzers/aiPatterns.js +103 -0
- package/dist/analyzers/aiPatterns.js.map +1 -0
- package/dist/analyzers/antiPatterns.d.ts +60 -0
- package/dist/analyzers/antiPatterns.d.ts.map +1 -0
- package/dist/analyzers/antiPatterns.js +198 -0
- package/dist/analyzers/antiPatterns.js.map +1 -0
- package/dist/analyzers/builtinTypes.d.ts +18 -0
- package/dist/analyzers/builtinTypes.d.ts.map +1 -0
- package/dist/analyzers/builtinTypes.js +1275 -0
- package/dist/analyzers/builtinTypes.js.map +1 -0
- package/dist/analyzers/complexity.d.ts +14 -0
- package/dist/analyzers/complexity.d.ts.map +1 -0
- package/dist/analyzers/complexity.js +610 -0
- package/dist/analyzers/complexity.js.map +1 -0
- package/dist/analyzers/findingVerifier.d.ts +59 -0
- package/dist/analyzers/findingVerifier.d.ts.map +1 -0
- package/dist/analyzers/findingVerifier.js +1169 -0
- package/dist/analyzers/findingVerifier.js.map +1 -0
- package/dist/analyzers/impactAnalyzer.d.ts +53 -0
- package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/impactAnalyzer.js +152 -0
- package/dist/analyzers/impactAnalyzer.js.map +1 -0
- package/dist/analyzers/languageDetector.d.ts +48 -0
- package/dist/analyzers/languageDetector.d.ts.map +1 -0
- package/dist/analyzers/languageDetector.js +404 -0
- package/dist/analyzers/languageDetector.js.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.js +193 -0
- package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.js +324 -0
- package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.js +429 -0
- package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.js +709 -0
- package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
- package/dist/analyzers/relevanceScorer.d.ts +43 -0
- package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
- package/dist/analyzers/relevanceScorer.js +200 -0
- package/dist/analyzers/relevanceScorer.js.map +1 -0
- package/dist/analyzers/standardLibrary.d.ts +22 -0
- package/dist/analyzers/standardLibrary.d.ts.map +1 -0
- package/dist/analyzers/standardLibrary.js +211 -0
- package/dist/analyzers/standardLibrary.js.map +1 -0
- package/dist/analyzers/symbolGraph.d.ts +30 -0
- package/dist/analyzers/symbolGraph.d.ts.map +1 -0
- package/dist/analyzers/symbolGraph.js +380 -0
- package/dist/analyzers/symbolGraph.js.map +1 -0
- package/dist/analyzers/symbolTable.d.ts +18 -0
- package/dist/analyzers/symbolTable.d.ts.map +1 -0
- package/dist/analyzers/symbolTable.js +176 -0
- package/dist/analyzers/symbolTable.js.map +1 -0
- package/dist/analyzers/typeChecker.d.ts +13 -0
- package/dist/analyzers/typeChecker.d.ts.map +1 -0
- package/dist/analyzers/typeChecker.js +580 -0
- package/dist/analyzers/typeChecker.js.map +1 -0
- package/dist/analyzers/usagePatterns.d.ts +42 -0
- package/dist/analyzers/usagePatterns.d.ts.map +1 -0
- package/dist/analyzers/usagePatterns.js +75 -0
- package/dist/analyzers/usagePatterns.js.map +1 -0
- package/dist/api-contract/context/backend.d.ts +19 -0
- package/dist/api-contract/context/backend.d.ts.map +1 -0
- package/dist/api-contract/context/backend.js +64 -0
- package/dist/api-contract/context/backend.js.map +1 -0
- package/dist/api-contract/context/contract.d.ts +34 -0
- package/dist/api-contract/context/contract.d.ts.map +1 -0
- package/dist/api-contract/context/contract.js +306 -0
- package/dist/api-contract/context/contract.js.map +1 -0
- package/dist/api-contract/context/frontend.d.ts +19 -0
- package/dist/api-contract/context/frontend.d.ts.map +1 -0
- package/dist/api-contract/context/frontend.js +64 -0
- package/dist/api-contract/context/frontend.js.map +1 -0
- package/dist/api-contract/detector.d.ts +28 -0
- package/dist/api-contract/detector.d.ts.map +1 -0
- package/dist/api-contract/detector.js +393 -0
- package/dist/api-contract/detector.js.map +1 -0
- package/dist/api-contract/extractors/python.d.ts +32 -0
- package/dist/api-contract/extractors/python.d.ts.map +1 -0
- package/dist/api-contract/extractors/python.js +521 -0
- package/dist/api-contract/extractors/python.js.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
- package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.js +173 -0
- package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/typescript.d.ts +32 -0
- package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
- package/dist/api-contract/extractors/typescript.js +666 -0
- package/dist/api-contract/extractors/typescript.js.map +1 -0
- package/dist/api-contract/index.d.ts +104 -0
- package/dist/api-contract/index.d.ts.map +1 -0
- package/dist/api-contract/index.js +232 -0
- package/dist/api-contract/index.js.map +1 -0
- package/dist/api-contract/types.d.ts +151 -0
- package/dist/api-contract/types.d.ts.map +1 -0
- package/dist/api-contract/types.js +19 -0
- package/dist/api-contract/types.js.map +1 -0
- package/dist/api-contract/validators/endpoint.d.ts +21 -0
- package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
- package/dist/api-contract/validators/endpoint.js +224 -0
- package/dist/api-contract/validators/endpoint.js.map +1 -0
- package/dist/api-contract/validators/index.d.ts +40 -0
- package/dist/api-contract/validators/index.d.ts.map +1 -0
- package/dist/api-contract/validators/index.js +875 -0
- package/dist/api-contract/validators/index.js.map +1 -0
- package/dist/api-contract/validators/parameter.d.ts +17 -0
- package/dist/api-contract/validators/parameter.d.ts.map +1 -0
- package/dist/api-contract/validators/parameter.js +250 -0
- package/dist/api-contract/validators/parameter.js.map +1 -0
- package/dist/api-contract/validators/type.d.ts +38 -0
- package/dist/api-contract/validators/type.d.ts.map +1 -0
- package/dist/api-contract/validators/type.js +244 -0
- package/dist/api-contract/validators/type.js.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.js +665 -0
- package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
- package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
- package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
- package/dist/context/apiContract/graphqlSupport.js +671 -0
- package/dist/context/apiContract/graphqlSupport.js.map +1 -0
- package/dist/context/apiContract/index.d.ts +14 -0
- package/dist/context/apiContract/index.d.ts.map +1 -0
- package/dist/context/apiContract/index.js +17 -0
- package/dist/context/apiContract/index.js.map +1 -0
- package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
- package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
- package/dist/context/apiContract/webSocketSupport.js +465 -0
- package/dist/context/apiContract/webSocketSupport.js.map +1 -0
- package/dist/context/apiContractContext.d.ts +15 -0
- package/dist/context/apiContractContext.d.ts.map +1 -0
- package/dist/context/apiContractContext.js +979 -0
- package/dist/context/apiContractContext.js.map +1 -0
- package/dist/context/apiContractExtraction.d.ts +52 -0
- package/dist/context/apiContractExtraction.d.ts.map +1 -0
- package/dist/context/apiContractExtraction.js +438 -0
- package/dist/context/apiContractExtraction.js.map +1 -0
- package/dist/context/contextLineage.d.ts +79 -0
- package/dist/context/contextLineage.d.ts.map +1 -0
- package/dist/context/contextLineage.js +259 -0
- package/dist/context/contextLineage.js.map +1 -0
- package/dist/context/contextOrchestrator.d.ts +57 -0
- package/dist/context/contextOrchestrator.d.ts.map +1 -0
- package/dist/context/contextOrchestrator.js +162 -0
- package/dist/context/contextOrchestrator.js.map +1 -0
- package/dist/context/intentTracker.d.ts +73 -0
- package/dist/context/intentTracker.d.ts.map +1 -0
- package/dist/context/intentTracker.js +168 -0
- package/dist/context/intentTracker.js.map +1 -0
- package/dist/context/projectContext.d.ts +219 -0
- package/dist/context/projectContext.d.ts.map +1 -0
- package/dist/context/projectContext.js +1984 -0
- package/dist/context/projectContext.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +260 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/library.d.ts +51 -0
- package/dist/prompts/library.d.ts.map +1 -0
- package/dist/prompts/library.js +65 -0
- package/dist/prompts/library.js.map +1 -0
- package/dist/prompts/templates.d.ts +44 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +97 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/queue/jobPersistence.d.ts +46 -0
- package/dist/queue/jobPersistence.d.ts.map +1 -0
- package/dist/queue/jobPersistence.js +158 -0
- package/dist/queue/jobPersistence.js.map +1 -0
- package/dist/queue/jobQueue.d.ts +116 -0
- package/dist/queue/jobQueue.d.ts.map +1 -0
- package/dist/queue/jobQueue.js +275 -0
- package/dist/queue/jobQueue.js.map +1 -0
- package/dist/queue/validationJob.d.ts +69 -0
- package/dist/queue/validationJob.d.ts.map +1 -0
- package/dist/queue/validationJob.js +435 -0
- package/dist/queue/validationJob.js.map +1 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +328 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/validationReportStore.d.ts +170 -0
- package/dist/resources/validationReportStore.d.ts.map +1 -0
- package/dist/resources/validationReportStore.js +515 -0
- package/dist/resources/validationReportStore.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +102 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/asyncValidation.d.ts +19 -0
- package/dist/tools/asyncValidation.d.ts.map +1 -0
- package/dist/tools/asyncValidation.js +346 -0
- package/dist/tools/asyncValidation.js.map +1 -0
- package/dist/tools/buildContext.d.ts +17 -0
- package/dist/tools/buildContext.d.ts.map +1 -0
- package/dist/tools/buildContext.js +188 -0
- package/dist/tools/buildContext.js.map +1 -0
- package/dist/tools/getDependencyGraph.d.ts +16 -0
- package/dist/tools/getDependencyGraph.d.ts.map +1 -0
- package/dist/tools/getDependencyGraph.js +436 -0
- package/dist/tools/getDependencyGraph.js.map +1 -0
- package/dist/tools/incrementalValidation.d.ts +71 -0
- package/dist/tools/incrementalValidation.d.ts.map +1 -0
- package/dist/tools/incrementalValidation.js +203 -0
- package/dist/tools/incrementalValidation.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/validateCode.d.ts +17 -0
- package/dist/tools/validateCode.d.ts.map +1 -0
- package/dist/tools/validateCode.js +368 -0
- package/dist/tools/validateCode.js.map +1 -0
- package/dist/tools/validateCodeLite.d.ts +2 -0
- package/dist/tools/validateCodeLite.d.ts.map +1 -0
- package/dist/tools/validateCodeLite.js +2 -0
- package/dist/tools/validateCodeLite.js.map +1 -0
- package/dist/tools/validation/builtins.d.ts +92 -0
- package/dist/tools/validation/builtins.d.ts.map +1 -0
- package/dist/tools/validation/builtins.js +2184 -0
- package/dist/tools/validation/builtins.js.map +1 -0
- package/dist/tools/validation/contextualNaming.d.ts +99 -0
- package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
- package/dist/tools/validation/contextualNaming.js +959 -0
- package/dist/tools/validation/contextualNaming.js.map +1 -0
- package/dist/tools/validation/deadCode.d.ts +115 -0
- package/dist/tools/validation/deadCode.d.ts.map +1 -0
- package/dist/tools/validation/deadCode.js +861 -0
- package/dist/tools/validation/deadCode.js.map +1 -0
- package/dist/tools/validation/extractors/index.d.ts +131 -0
- package/dist/tools/validation/extractors/index.d.ts.map +1 -0
- package/dist/tools/validation/extractors/index.js +233 -0
- package/dist/tools/validation/extractors/index.js.map +1 -0
- package/dist/tools/validation/extractors/javascript.d.ts +73 -0
- package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
- package/dist/tools/validation/extractors/javascript.js +1841 -0
- package/dist/tools/validation/extractors/javascript.js.map +1 -0
- package/dist/tools/validation/extractors/python.d.ts +93 -0
- package/dist/tools/validation/extractors/python.d.ts.map +1 -0
- package/dist/tools/validation/extractors/python.js +799 -0
- package/dist/tools/validation/extractors/python.js.map +1 -0
- package/dist/tools/validation/manifest.d.ts +45 -0
- package/dist/tools/validation/manifest.d.ts.map +1 -0
- package/dist/tools/validation/manifest.js +719 -0
- package/dist/tools/validation/manifest.js.map +1 -0
- package/dist/tools/validation/parser.d.ts +58 -0
- package/dist/tools/validation/parser.d.ts.map +1 -0
- package/dist/tools/validation/parser.js +232 -0
- package/dist/tools/validation/parser.js.map +1 -0
- package/dist/tools/validation/registry.d.ts +15 -0
- package/dist/tools/validation/registry.d.ts.map +1 -0
- package/dist/tools/validation/registry.js +169 -0
- package/dist/tools/validation/registry.js.map +1 -0
- package/dist/tools/validation/scoring.d.ts +54 -0
- package/dist/tools/validation/scoring.d.ts.map +1 -0
- package/dist/tools/validation/scoring.js +242 -0
- package/dist/tools/validation/scoring.js.map +1 -0
- package/dist/tools/validation/types.d.ts +120 -0
- package/dist/tools/validation/types.d.ts.map +1 -0
- package/dist/tools/validation/types.js +11 -0
- package/dist/tools/validation/types.js.map +1 -0
- package/dist/tools/validation/unusedLocals.d.ts +36 -0
- package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
- package/dist/tools/validation/unusedLocals.js +333 -0
- package/dist/tools/validation/unusedLocals.js.map +1 -0
- package/dist/tools/validation/validation.d.ts +98 -0
- package/dist/tools/validation/validation.d.ts.map +1 -0
- package/dist/tools/validation/validation.js +1837 -0
- package/dist/tools/validation/validation.js.map +1 -0
- package/dist/types/codeGraph.d.ts +163 -0
- package/dist/types/codeGraph.d.ts.map +1 -0
- package/dist/types/codeGraph.js +9 -0
- package/dist/types/codeGraph.js.map +1 -0
- package/dist/types/symbolGraph.d.ts +68 -0
- package/dist/types/symbolGraph.d.ts.map +1 -0
- package/dist/types/symbolGraph.js +10 -0
- package/dist/types/symbolGraph.js.map +1 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +7 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/fileFilter.d.ts +37 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +91 -0
- package/dist/utils/fileFilter.js.map +1 -0
- package/dist/utils/gitUtils.d.ts +28 -0
- package/dist/utils/gitUtils.d.ts.map +1 -0
- package/dist/utils/gitUtils.js +81 -0
- package/dist/utils/gitUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/serialization.d.ts +25 -0
- package/dist/utils/serialization.d.ts.map +1 -0
- package/dist/utils/serialization.js +53 -0
- package/dist/utils/serialization.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,1841 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript/TypeScript AST Extraction Module
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions to extract symbols, usages, imports, and type references
|
|
5
|
+
* from JavaScript and TypeScript code using Tree-sitter AST parsing.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - Function declarations, arrow functions, class declarations, method definitions
|
|
9
|
+
* - Function calls, method calls, instantiations (new expressions)
|
|
10
|
+
* - ES6 imports, dynamic imports
|
|
11
|
+
* - TypeScript type annotations, generic parameters, type references
|
|
12
|
+
* - Destructuring patterns (object and array)
|
|
13
|
+
*
|
|
14
|
+
* @format
|
|
15
|
+
*/
|
|
16
|
+
import { isJSBuiltin, isTSBuiltinType } from "../builtins.js";
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Symbol Extraction
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Extract symbol definitions from JavaScript/TypeScript AST
|
|
22
|
+
* Finds: functions, classes, methods, variables, interfaces, types, enums
|
|
23
|
+
*/
|
|
24
|
+
export function extractJSSymbols(node, code, filePath, symbols, currentClass, options = {}) {
|
|
25
|
+
if (!node)
|
|
26
|
+
return;
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case "function_declaration":
|
|
29
|
+
case "function_expression": {
|
|
30
|
+
const nameNode = node.childForFieldName("name");
|
|
31
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
32
|
+
if (nameNode) {
|
|
33
|
+
const name = getText(nameNode, code);
|
|
34
|
+
const params = extractJSParams(paramsNode, code, filePath, symbols, options);
|
|
35
|
+
const isAsync = node.children.some((c) => getText(c, code) === "async");
|
|
36
|
+
const isExported = node.parent?.type === "export_statement";
|
|
37
|
+
symbols.push({
|
|
38
|
+
name,
|
|
39
|
+
type: "function",
|
|
40
|
+
file: filePath,
|
|
41
|
+
line: node.startPosition.row + 1,
|
|
42
|
+
column: node.startPosition.column,
|
|
43
|
+
params,
|
|
44
|
+
paramCount: params.length,
|
|
45
|
+
isAsync,
|
|
46
|
+
isExported,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "lexical_declaration":
|
|
52
|
+
case "variable_declaration": {
|
|
53
|
+
// const/let/var declarations
|
|
54
|
+
// Check if this declaration is exported
|
|
55
|
+
const isExported = node.parent?.type === "export_statement";
|
|
56
|
+
for (const child of node.children) {
|
|
57
|
+
if (child.type === "variable_declarator") {
|
|
58
|
+
const nameNode = child.childForFieldName("name");
|
|
59
|
+
const valueNode = child.childForFieldName("value");
|
|
60
|
+
if (nameNode) {
|
|
61
|
+
// Handle object destructuring: const { a, b } = something()
|
|
62
|
+
if (nameNode.type === "object_pattern") {
|
|
63
|
+
extractDestructuredNames(nameNode, code, filePath, symbols, node.startPosition.row + 1);
|
|
64
|
+
}
|
|
65
|
+
// Handle array destructuring: const [a, b] = something()
|
|
66
|
+
else if (nameNode.type === "array_pattern") {
|
|
67
|
+
extractDestructuredNames(nameNode, code, filePath, symbols, node.startPosition.row + 1);
|
|
68
|
+
}
|
|
69
|
+
// Handle simple identifier: const x = something
|
|
70
|
+
else {
|
|
71
|
+
const name = getText(nameNode, code);
|
|
72
|
+
// Check if it's a function-valued initializer
|
|
73
|
+
// Tree-sitter uses different node types across grammars:
|
|
74
|
+
// - JavaScript: `function` / `arrow_function`
|
|
75
|
+
// - Some contexts/grammars may surface `function_expression`
|
|
76
|
+
if (valueNode?.type === "arrow_function" ||
|
|
77
|
+
valueNode?.type === "function" ||
|
|
78
|
+
valueNode?.type === "function_expression") {
|
|
79
|
+
const paramsNode = valueNode.childForFieldName("parameters") || valueNode.childForFieldName("parameter");
|
|
80
|
+
const params = extractJSParams(paramsNode, code, filePath, symbols, options);
|
|
81
|
+
const isAsync = valueNode.children.some((c) => getText(c, code) === "async");
|
|
82
|
+
symbols.push({
|
|
83
|
+
name,
|
|
84
|
+
type: "function",
|
|
85
|
+
file: filePath,
|
|
86
|
+
line: node.startPosition.row + 1,
|
|
87
|
+
column: node.startPosition.column,
|
|
88
|
+
params,
|
|
89
|
+
paramCount: params.length,
|
|
90
|
+
isAsync,
|
|
91
|
+
isExported,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
symbols.push({
|
|
96
|
+
name,
|
|
97
|
+
type: "variable",
|
|
98
|
+
file: filePath,
|
|
99
|
+
line: node.startPosition.row + 1,
|
|
100
|
+
column: node.startPosition.column,
|
|
101
|
+
isExported,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "arrow_function": {
|
|
111
|
+
const pNode = node.childForFieldName("parameter");
|
|
112
|
+
const psNode = node.childForFieldName("parameters");
|
|
113
|
+
if (pNode) {
|
|
114
|
+
if (pNode.type === "identifier") {
|
|
115
|
+
// Single parameter arrow function: x => x + 1
|
|
116
|
+
symbols.push({
|
|
117
|
+
name: getText(pNode, code),
|
|
118
|
+
type: "variable",
|
|
119
|
+
file: filePath,
|
|
120
|
+
line: pNode.startPosition.row + 1,
|
|
121
|
+
column: pNode.startPosition.column,
|
|
122
|
+
isExported: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else if (pNode.type === "object_pattern" ||
|
|
126
|
+
pNode.type === "array_pattern") {
|
|
127
|
+
extractDestructuredNames(pNode, code, filePath, symbols, node.startPosition.row + 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (psNode) {
|
|
131
|
+
extractJSParams(psNode, code, filePath, symbols, options);
|
|
132
|
+
}
|
|
133
|
+
// Continue recursion to process function body, but parameter identifiers
|
|
134
|
+
// will be skipped by the case "identifier" check below
|
|
135
|
+
// IMPORTANT: Use return here, not break, to prevent falling through to
|
|
136
|
+
// the recursive call at the end of extractJSSymbols. The recursive call
|
|
137
|
+
// for arrow_function children is handled by the recursive call to
|
|
138
|
+
// extractJSParams and subsequent processing.
|
|
139
|
+
// Actually, we DO need to recurse into the function body to find nested symbols.
|
|
140
|
+
// The break allows the loop at the end of extractJSSymbols to handle recursion.
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "class_declaration": {
|
|
144
|
+
const nameNode = node.childForFieldName("name");
|
|
145
|
+
if (nameNode) {
|
|
146
|
+
const className = getText(nameNode, code);
|
|
147
|
+
const isExported = node.parent?.type === "export_statement";
|
|
148
|
+
symbols.push({
|
|
149
|
+
name: className,
|
|
150
|
+
type: "class",
|
|
151
|
+
file: filePath,
|
|
152
|
+
line: node.startPosition.row + 1,
|
|
153
|
+
column: node.startPosition.column,
|
|
154
|
+
isExported,
|
|
155
|
+
});
|
|
156
|
+
// Process class body
|
|
157
|
+
const bodyNode = node.childForFieldName("body");
|
|
158
|
+
if (bodyNode) {
|
|
159
|
+
for (const child of bodyNode.children) {
|
|
160
|
+
extractJSSymbols(child, code, filePath, symbols, className, options);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "method_definition": {
|
|
168
|
+
const nameNode = node.childForFieldName("name");
|
|
169
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
170
|
+
if (nameNode) {
|
|
171
|
+
const name = getText(nameNode, code);
|
|
172
|
+
if (name !== "constructor") {
|
|
173
|
+
const params = extractJSParams(paramsNode, code, filePath, symbols, options);
|
|
174
|
+
const isAsync = node.children.some((c) => getText(c, code) === "async");
|
|
175
|
+
// Extract return type for TypeScript methods
|
|
176
|
+
const returnTypeNode = node.childForFieldName("return_type");
|
|
177
|
+
const returnType = returnTypeNode ? getText(returnTypeNode, code) : undefined;
|
|
178
|
+
symbols.push({
|
|
179
|
+
name,
|
|
180
|
+
type: "method",
|
|
181
|
+
file: filePath,
|
|
182
|
+
line: node.startPosition.row + 1,
|
|
183
|
+
column: node.startPosition.column,
|
|
184
|
+
params,
|
|
185
|
+
paramCount: params.length,
|
|
186
|
+
scope: currentClass || undefined,
|
|
187
|
+
isAsync,
|
|
188
|
+
returnType,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
// TypeScript class field declarations (private/public/protected properties)
|
|
195
|
+
// e.g., class Foo { private bar: string = ''; }
|
|
196
|
+
case "public_field_definition":
|
|
197
|
+
case "private_field_definition":
|
|
198
|
+
case "protected_field_definition":
|
|
199
|
+
case "field_definition":
|
|
200
|
+
case "property_definition": {
|
|
201
|
+
const nameNode = node.childForFieldName("name");
|
|
202
|
+
if (nameNode) {
|
|
203
|
+
const name = getText(nameNode, code);
|
|
204
|
+
symbols.push({
|
|
205
|
+
name,
|
|
206
|
+
type: "variable",
|
|
207
|
+
file: filePath,
|
|
208
|
+
line: node.startPosition.row + 1,
|
|
209
|
+
column: node.startPosition.column,
|
|
210
|
+
scope: currentClass || undefined,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
// TypeScript interface declarations
|
|
216
|
+
case "interface_declaration": {
|
|
217
|
+
const nameNode = node.childForFieldName("name");
|
|
218
|
+
if (nameNode) {
|
|
219
|
+
const name = getText(nameNode, code);
|
|
220
|
+
const isExported = node.parent?.type === "export_statement";
|
|
221
|
+
symbols.push({
|
|
222
|
+
name,
|
|
223
|
+
type: "interface",
|
|
224
|
+
file: filePath,
|
|
225
|
+
line: node.startPosition.row + 1,
|
|
226
|
+
column: node.startPosition.column,
|
|
227
|
+
isExported,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
// TypeScript type alias declarations
|
|
233
|
+
case "type_alias_declaration": {
|
|
234
|
+
const nameNode = node.childForFieldName("name");
|
|
235
|
+
if (nameNode) {
|
|
236
|
+
const name = getText(nameNode, code);
|
|
237
|
+
const isExported = node.parent?.type === "export_statement";
|
|
238
|
+
symbols.push({
|
|
239
|
+
name,
|
|
240
|
+
type: "type",
|
|
241
|
+
file: filePath,
|
|
242
|
+
line: node.startPosition.row + 1,
|
|
243
|
+
column: node.startPosition.column,
|
|
244
|
+
isExported,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
// TypeScript enum declarations
|
|
250
|
+
case "enum_declaration": {
|
|
251
|
+
const nameNode = node.childForFieldName("name");
|
|
252
|
+
if (nameNode) {
|
|
253
|
+
const name = getText(nameNode, code);
|
|
254
|
+
const isExported = node.parent?.type === "export_statement";
|
|
255
|
+
symbols.push({
|
|
256
|
+
name,
|
|
257
|
+
type: "variable", // enums are treated as variables
|
|
258
|
+
file: filePath,
|
|
259
|
+
line: node.startPosition.row + 1,
|
|
260
|
+
column: node.startPosition.column,
|
|
261
|
+
isExported,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
// Object literals: const api = { method: () => {} }
|
|
267
|
+
// Extract methods from object literals to support API client patterns
|
|
268
|
+
case "object": {
|
|
269
|
+
// Check if this object is the value of a variable declarator
|
|
270
|
+
// This handles: const timeEntriesApi = { getPending: async () => {} }
|
|
271
|
+
const parent = node.parent;
|
|
272
|
+
let parentVariableName = null;
|
|
273
|
+
if (parent?.type === "variable_declarator") {
|
|
274
|
+
const nameNode = parent.childForFieldName("name");
|
|
275
|
+
if (nameNode?.type === "identifier") {
|
|
276
|
+
parentVariableName = getText(nameNode, code);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Extract all method properties from the object literal
|
|
280
|
+
for (const child of node.children) {
|
|
281
|
+
if (child.type === "pair") {
|
|
282
|
+
const keyNode = child.childForFieldName("key");
|
|
283
|
+
const valueNode = child.childForFieldName("value");
|
|
284
|
+
if (keyNode && valueNode) {
|
|
285
|
+
const keyName = getText(keyNode, code);
|
|
286
|
+
// Check if the value is a function (arrow_function, function, or call_expression that returns a function)
|
|
287
|
+
const isFunctionValue = valueNode.type === "arrow_function" ||
|
|
288
|
+
valueNode.type === "function";
|
|
289
|
+
if (isFunctionValue) {
|
|
290
|
+
const paramsNode = valueNode.childForFieldName("parameters") || valueNode.childForFieldName("parameter");
|
|
291
|
+
// Pass undefined for symbols to avoid registering params as local variables
|
|
292
|
+
// We only want the parameter names for the method signature
|
|
293
|
+
const params = extractJSParams(paramsNode, code, filePath, undefined, options);
|
|
294
|
+
const isAsync = valueNode.children.some((c) => getText(c, code) === "async");
|
|
295
|
+
// Register as a method with scope = parent variable name
|
|
296
|
+
// Note: We don't extract params here - let recursion handle it
|
|
297
|
+
// to avoid duplicate parameter symbols
|
|
298
|
+
symbols.push({
|
|
299
|
+
name: keyName,
|
|
300
|
+
type: "method",
|
|
301
|
+
file: filePath,
|
|
302
|
+
line: child.startPosition.row + 1,
|
|
303
|
+
column: child.startPosition.column,
|
|
304
|
+
params,
|
|
305
|
+
paramCount: params.length,
|
|
306
|
+
scope: parentVariableName || undefined,
|
|
307
|
+
isAsync,
|
|
308
|
+
isExported: parent?.parent?.parent?.type === "export_statement",
|
|
309
|
+
});
|
|
310
|
+
// Recurse into the arrow function to extract its parameter symbols
|
|
311
|
+
// This ensures parameters like (id) in { mutationFn: (id) => ... } are registered
|
|
312
|
+
extractJSSymbols(valueNode, code, filePath, symbols, null, options);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
// Not a function value, recurse to find any nested symbols
|
|
316
|
+
extractJSSymbols(valueNode, code, filePath, symbols, null, options);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Handle shorthand methods: { method() {} }
|
|
321
|
+
else if (child.type === "method_definition") {
|
|
322
|
+
const nameNode = child.childForFieldName("name");
|
|
323
|
+
const paramsNode = child.childForFieldName("parameters");
|
|
324
|
+
if (nameNode) {
|
|
325
|
+
const name = getText(nameNode, code);
|
|
326
|
+
// Pass symbols to register params as local variables
|
|
327
|
+
const params = extractJSParams(paramsNode, code, filePath, symbols, options);
|
|
328
|
+
const isAsync = child.children.some((c) => getText(c, code) === "async");
|
|
329
|
+
// Extract return type for TypeScript methods
|
|
330
|
+
const returnTypeNode = child.childForFieldName("return_type");
|
|
331
|
+
const returnType = returnTypeNode ? getText(returnTypeNode, code) : undefined;
|
|
332
|
+
symbols.push({
|
|
333
|
+
name,
|
|
334
|
+
type: "method",
|
|
335
|
+
file: filePath,
|
|
336
|
+
line: child.startPosition.row + 1,
|
|
337
|
+
column: child.startPosition.column,
|
|
338
|
+
params,
|
|
339
|
+
paramCount: params.length,
|
|
340
|
+
scope: parentVariableName || undefined,
|
|
341
|
+
isAsync,
|
|
342
|
+
isExported: parent?.parent?.parent?.type === "export_statement",
|
|
343
|
+
returnType,
|
|
344
|
+
});
|
|
345
|
+
// Recurse into method body to extract any nested symbols
|
|
346
|
+
const bodyNode = child.childForFieldName("body");
|
|
347
|
+
if (bodyNode) {
|
|
348
|
+
extractJSSymbols(bodyNode, code, filePath, symbols, null, options);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else if (child.type !== "{" && child.type !== "}" && child.type !== ",") {
|
|
353
|
+
// Recurse into other children (but skip punctuation)
|
|
354
|
+
extractJSSymbols(child, code, filePath, symbols, null, options);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Use break (not return) so we don't process the same nodes again in the main recursion
|
|
358
|
+
// But we've already handled all children, so this prevents double-processing
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
// Catch clause parameters: catch (error) { ... }
|
|
362
|
+
case "catch_clause": {
|
|
363
|
+
// Find the parameter node - it's usually the first identifier after 'catch'
|
|
364
|
+
for (const child of node.children) {
|
|
365
|
+
if (child.type === "identifier") {
|
|
366
|
+
symbols.push({
|
|
367
|
+
name: getText(child, code),
|
|
368
|
+
type: "variable",
|
|
369
|
+
file: filePath,
|
|
370
|
+
line: child.startPosition.row + 1,
|
|
371
|
+
column: child.startPosition.column,
|
|
372
|
+
isExported: false,
|
|
373
|
+
});
|
|
374
|
+
break; // Only one catch parameter
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
// Named exports: export { a, b as c }
|
|
380
|
+
case "export_clause": {
|
|
381
|
+
for (const child of node.children) {
|
|
382
|
+
if (child.type === "export_specifier") {
|
|
383
|
+
const nameNode = child.childForFieldName("alias") || child.childForFieldName("name");
|
|
384
|
+
if (nameNode) {
|
|
385
|
+
const name = getText(nameNode, code);
|
|
386
|
+
symbols.push({
|
|
387
|
+
name,
|
|
388
|
+
type: "variable",
|
|
389
|
+
file: filePath,
|
|
390
|
+
line: node.startPosition.row + 1,
|
|
391
|
+
column: node.startPosition.column,
|
|
392
|
+
isExported: true,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
// Function parameters (arrow functions, methods, etc.)
|
|
400
|
+
// NOTE: Parameters are extracted by extractJSParams which is called from
|
|
401
|
+
// function_declaration, arrow_function, and method_definition cases.
|
|
402
|
+
// The function body will be processed by recursion into other children.
|
|
403
|
+
case "formal_parameters":
|
|
404
|
+
case "parameters":
|
|
405
|
+
case "required_parameter":
|
|
406
|
+
case "optional_parameter": {
|
|
407
|
+
// Fall through to recursion - function body will be processed
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const child of node.children) {
|
|
412
|
+
extractJSSymbols(child, code, filePath, symbols, currentClass, options);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// ============================================================================
|
|
416
|
+
// Parameter Extraction
|
|
417
|
+
// ============================================================================
|
|
418
|
+
/**
|
|
419
|
+
* Extract parameter names from a formal_parameters node.
|
|
420
|
+
* Returns the parameter names for the function's params list.
|
|
421
|
+
* For destructured parameters (object/array patterns), also registers the
|
|
422
|
+
* individual variable names as symbols for validation purposes.
|
|
423
|
+
*/
|
|
424
|
+
export function extractJSParams(paramsNode, code, filePath = "", symbols, options = {}) {
|
|
425
|
+
if (!paramsNode)
|
|
426
|
+
return [];
|
|
427
|
+
const params = [];
|
|
428
|
+
for (const child of paramsNode.children) {
|
|
429
|
+
if (child.type === "identifier") {
|
|
430
|
+
const name = getText(child, code);
|
|
431
|
+
params.push(name);
|
|
432
|
+
// ALWAYS register simple parameter identifiers as local symbols.
|
|
433
|
+
// This prevents false positives where function parameters are flagged
|
|
434
|
+
// as "undefinedVariable" when used within the function body.
|
|
435
|
+
if (symbols) {
|
|
436
|
+
symbols.push({
|
|
437
|
+
name,
|
|
438
|
+
type: "variable",
|
|
439
|
+
file: filePath,
|
|
440
|
+
line: child.startPosition.row + 1,
|
|
441
|
+
column: child.startPosition.column,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else if (child.type === "required_parameter" ||
|
|
446
|
+
child.type === "optional_parameter" ||
|
|
447
|
+
child.type === "rest_pattern") {
|
|
448
|
+
// Logic for destructuring or simple names in typed parameters
|
|
449
|
+
// TypeScript: function foo({ x }: Type) or function foo(x: Type)
|
|
450
|
+
const nameNode = child.childForFieldName("pattern") ||
|
|
451
|
+
child.children.find((c) => c.type === "identifier" ||
|
|
452
|
+
c.type === "rest_pattern" ||
|
|
453
|
+
c.type === "object_pattern" ||
|
|
454
|
+
c.type === "array_pattern");
|
|
455
|
+
if (nameNode) {
|
|
456
|
+
if (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") {
|
|
457
|
+
// For destructured params, register the individual names as symbols
|
|
458
|
+
// This is needed for validation to recognize them as defined variables
|
|
459
|
+
if (symbols) {
|
|
460
|
+
extractDestructuredNames(nameNode, code, filePath, symbols, nameNode.startPosition.row + 1);
|
|
461
|
+
}
|
|
462
|
+
// For param list, we use the raw text
|
|
463
|
+
params.push(getText(nameNode, code));
|
|
464
|
+
}
|
|
465
|
+
else if (nameNode.type === "rest_pattern") {
|
|
466
|
+
const id = nameNode.children.find((c) => c.type === "identifier");
|
|
467
|
+
if (id) {
|
|
468
|
+
const name = getText(id, code);
|
|
469
|
+
params.push(name);
|
|
470
|
+
// Rest parameters are registered as symbols
|
|
471
|
+
if (symbols) {
|
|
472
|
+
symbols.push({
|
|
473
|
+
name,
|
|
474
|
+
type: "variable",
|
|
475
|
+
file: filePath,
|
|
476
|
+
line: id.startPosition.row + 1,
|
|
477
|
+
column: id.startPosition.column,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else if (nameNode.type === "identifier") {
|
|
483
|
+
// Handle simple typed parameters: function foo(x: Type)
|
|
484
|
+
const name = getText(nameNode, code);
|
|
485
|
+
params.push(name);
|
|
486
|
+
// ALWAYS register parameter symbols to prevent false positives
|
|
487
|
+
// when validating local variable usage within the function scope
|
|
488
|
+
if (symbols) {
|
|
489
|
+
symbols.push({
|
|
490
|
+
name,
|
|
491
|
+
type: "variable",
|
|
492
|
+
file: filePath,
|
|
493
|
+
line: nameNode.startPosition.row + 1,
|
|
494
|
+
column: nameNode.startPosition.column,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else if (child.type === "object_pattern" || child.type === "array_pattern") {
|
|
501
|
+
// For destructured params at the top level, register the individual names
|
|
502
|
+
if (symbols) {
|
|
503
|
+
extractDestructuredNames(child, code, filePath, symbols, child.startPosition.row + 1);
|
|
504
|
+
}
|
|
505
|
+
params.push(getText(child, code));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return params;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Extract variable names from destructuring patterns
|
|
512
|
+
* Handles: const { a, b } = x and const [a, b] = x
|
|
513
|
+
*/
|
|
514
|
+
export function extractDestructuredNames(node, code, filePath, symbols, line, depth = 0) {
|
|
515
|
+
if (!node || depth > 50)
|
|
516
|
+
return; // Recursion loop guard
|
|
517
|
+
switch (node.type) {
|
|
518
|
+
case "identifier":
|
|
519
|
+
case "shorthand_property_identifier_pattern": {
|
|
520
|
+
const name = getText(node, code);
|
|
521
|
+
symbols.push({
|
|
522
|
+
name,
|
|
523
|
+
type: "variable",
|
|
524
|
+
file: filePath,
|
|
525
|
+
line,
|
|
526
|
+
column: node.startPosition.column,
|
|
527
|
+
});
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case "pair_pattern": {
|
|
531
|
+
// Handle { oldName: newName } - extract newName
|
|
532
|
+
const valueNode = node.childForFieldName("value");
|
|
533
|
+
if (valueNode) {
|
|
534
|
+
extractDestructuredNames(valueNode, code, filePath, symbols, line, depth + 1);
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
case "assignment_pattern": {
|
|
539
|
+
// Handle [a = 1] or {a = 1} - extract the left side
|
|
540
|
+
const leftNode = node.childForFieldName("left");
|
|
541
|
+
if (leftNode) {
|
|
542
|
+
extractDestructuredNames(leftNode, code, filePath, symbols, line, depth + 1);
|
|
543
|
+
}
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case "object_assignment_pattern": {
|
|
547
|
+
// Handle { a = 1 } in object destructuring (with default value)
|
|
548
|
+
for (const child of node.children) {
|
|
549
|
+
// Only extract the identifier being defined, not the default value
|
|
550
|
+
if (child.type === "shorthand_property_identifier_pattern" ||
|
|
551
|
+
child.type === "identifier") {
|
|
552
|
+
extractDestructuredNames(child, code, filePath, symbols, line, depth + 1);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
case "object_pattern":
|
|
558
|
+
case "array_pattern": {
|
|
559
|
+
for (const child of node.children) {
|
|
560
|
+
// Only recurse into relevant pattern parts (skip punctuation like {, }, [, ], commas)
|
|
561
|
+
if (child.type === "pair_pattern" ||
|
|
562
|
+
child.type === "shorthand_property_identifier_pattern" ||
|
|
563
|
+
child.type === "object_pattern" ||
|
|
564
|
+
child.type === "array_pattern" ||
|
|
565
|
+
child.type === "assignment_pattern" ||
|
|
566
|
+
child.type === "object_assignment_pattern" ||
|
|
567
|
+
child.type === "identifier" ||
|
|
568
|
+
child.type === "rest_pattern") {
|
|
569
|
+
extractDestructuredNames(child, code, filePath, symbols, line, depth + 1);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
case "rest_pattern": {
|
|
575
|
+
// Handle ...rest - find the identifier inside
|
|
576
|
+
for (const child of node.children) {
|
|
577
|
+
if (child.type === "identifier") {
|
|
578
|
+
extractDestructuredNames(child, code, filePath, symbols, line, depth + 1);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// ============================================================================
|
|
586
|
+
// Local Definition Collection (for JS/TS)
|
|
587
|
+
// ============================================================================
|
|
588
|
+
/**
|
|
589
|
+
* Collect all locally-defined identifier names from JavaScript/TypeScript code.
|
|
590
|
+
* This includes: variable declarations (const/let/var), function parameters,
|
|
591
|
+
* for-of/for-in loop variables, catch clause variables, destructured names,
|
|
592
|
+
* function/class declaration names.
|
|
593
|
+
*
|
|
594
|
+
* Used by the validator to prevent false positives on local variable references
|
|
595
|
+
* and method calls (e.g., `for (const training of records) { training.status }`)
|
|
596
|
+
*/
|
|
597
|
+
export function collectJSLocalDefinitions(node, code, definitions) {
|
|
598
|
+
if (!node)
|
|
599
|
+
return;
|
|
600
|
+
switch (node.type) {
|
|
601
|
+
case "variable_declarator": {
|
|
602
|
+
const nameNode = node.childForFieldName("name");
|
|
603
|
+
if (nameNode) {
|
|
604
|
+
if (nameNode.type === "identifier") {
|
|
605
|
+
definitions.add(getText(nameNode, code));
|
|
606
|
+
}
|
|
607
|
+
else if (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") {
|
|
608
|
+
collectDestructuredDefinitions(nameNode, code, definitions);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
// For-in/for-of loop variable: for (const x of arr) / for (const x in obj)
|
|
614
|
+
case "for_in_statement": {
|
|
615
|
+
const leftNode = node.childForFieldName("left");
|
|
616
|
+
if (leftNode) {
|
|
617
|
+
if (leftNode.type === "identifier") {
|
|
618
|
+
definitions.add(getText(leftNode, code));
|
|
619
|
+
}
|
|
620
|
+
else if (leftNode.type === "object_pattern" || leftNode.type === "array_pattern") {
|
|
621
|
+
// Destructured loop variable: for (const [key, value] of entries)
|
|
622
|
+
collectDestructuredDefinitions(leftNode, code, definitions);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
// Could be lexical_declaration wrapping the variable
|
|
626
|
+
collectJSLocalDefinitions(leftNode, code, definitions);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
// Catch clause: catch (err) { ... }
|
|
632
|
+
case "catch_clause": {
|
|
633
|
+
const paramNode = node.childForFieldName("parameter");
|
|
634
|
+
if (paramNode) {
|
|
635
|
+
if (paramNode.type === "identifier") {
|
|
636
|
+
definitions.add(getText(paramNode, code));
|
|
637
|
+
}
|
|
638
|
+
else if (paramNode.type === "object_pattern" || paramNode.type === "array_pattern") {
|
|
639
|
+
collectDestructuredDefinitions(paramNode, code, definitions);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
// Function/class declaration names
|
|
645
|
+
case "function_declaration":
|
|
646
|
+
case "class_declaration": {
|
|
647
|
+
const nameNode = node.childForFieldName("name");
|
|
648
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
649
|
+
definitions.add(getText(nameNode, code));
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
// Function parameters
|
|
654
|
+
case "required_parameter":
|
|
655
|
+
case "optional_parameter": {
|
|
656
|
+
const nameNode = node.childForFieldName("pattern") || node.childForFieldName("name");
|
|
657
|
+
if (nameNode) {
|
|
658
|
+
if (nameNode.type === "identifier") {
|
|
659
|
+
definitions.add(getText(nameNode, code));
|
|
660
|
+
}
|
|
661
|
+
else if (nameNode.type === "object_pattern" || nameNode.type === "array_pattern") {
|
|
662
|
+
collectDestructuredDefinitions(nameNode, code, definitions);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
// Rest parameter: ...args
|
|
668
|
+
case "rest_pattern": {
|
|
669
|
+
for (const child of node.children) {
|
|
670
|
+
if (child && child.type === "identifier") {
|
|
671
|
+
definitions.add(getText(child, code));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
// Enum declaration
|
|
677
|
+
case "enum_declaration": {
|
|
678
|
+
const nameNode = node.childForFieldName("name");
|
|
679
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
680
|
+
definitions.add(getText(nameNode, code));
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for (const child of node.children) {
|
|
686
|
+
if (child) {
|
|
687
|
+
collectJSLocalDefinitions(child, code, definitions);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Collect identifier names from destructuring patterns (object/array).
|
|
693
|
+
*/
|
|
694
|
+
function collectDestructuredDefinitions(node, code, definitions) {
|
|
695
|
+
if (!node)
|
|
696
|
+
return;
|
|
697
|
+
for (const child of node.children) {
|
|
698
|
+
if (!child)
|
|
699
|
+
continue;
|
|
700
|
+
if (child.type === "identifier") {
|
|
701
|
+
definitions.add(getText(child, code));
|
|
702
|
+
}
|
|
703
|
+
else if (child.type === "shorthand_property_identifier_pattern") {
|
|
704
|
+
definitions.add(getText(child, code));
|
|
705
|
+
}
|
|
706
|
+
else if (child.type === "pair_pattern") {
|
|
707
|
+
const valueNode = child.childForFieldName("value");
|
|
708
|
+
if (valueNode) {
|
|
709
|
+
if (valueNode.type === "identifier") {
|
|
710
|
+
definitions.add(getText(valueNode, code));
|
|
711
|
+
}
|
|
712
|
+
else if (valueNode.type === "object_pattern" || valueNode.type === "array_pattern") {
|
|
713
|
+
collectDestructuredDefinitions(valueNode, code, definitions);
|
|
714
|
+
}
|
|
715
|
+
else if (valueNode.type === "assignment_pattern") {
|
|
716
|
+
const leftNode = valueNode.childForFieldName("left");
|
|
717
|
+
if (leftNode?.type === "identifier") {
|
|
718
|
+
definitions.add(getText(leftNode, code));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
else if (child.type === "rest_pattern") {
|
|
724
|
+
for (const restChild of child.children) {
|
|
725
|
+
if (restChild?.type === "identifier") {
|
|
726
|
+
definitions.add(getText(restChild, code));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else if (child.type === "assignment_pattern") {
|
|
731
|
+
const leftNode = child.childForFieldName("left");
|
|
732
|
+
if (leftNode?.type === "identifier") {
|
|
733
|
+
definitions.add(getText(leftNode, code));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
else if (child.type === "object_pattern" || child.type === "array_pattern") {
|
|
737
|
+
collectDestructuredDefinitions(child, code, definitions);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// ============================================================================
|
|
742
|
+
// Usage Extraction
|
|
743
|
+
// ============================================================================
|
|
744
|
+
/**
|
|
745
|
+
* Extract symbol usages from JavaScript/TypeScript AST
|
|
746
|
+
* Finds: function calls, method calls, instantiations (new expressions)
|
|
747
|
+
*/
|
|
748
|
+
export function extractJSUsages(node, code, usages, externalSymbols) {
|
|
749
|
+
if (!node)
|
|
750
|
+
return;
|
|
751
|
+
switch (node.type) {
|
|
752
|
+
case "call_expression": {
|
|
753
|
+
const funcNode = node.childForFieldName("function");
|
|
754
|
+
const argsNode = node.childForFieldName("arguments");
|
|
755
|
+
const argCount = countArgs(argsNode);
|
|
756
|
+
if (funcNode) {
|
|
757
|
+
if (funcNode.type === "identifier") {
|
|
758
|
+
const name = getText(funcNode, code);
|
|
759
|
+
// Don't skip imported symbols or built-ins here; we need to track them
|
|
760
|
+
// for unused import detection and proper validation.
|
|
761
|
+
usages.push({
|
|
762
|
+
name,
|
|
763
|
+
type: "call",
|
|
764
|
+
line: node.startPosition.row + 1,
|
|
765
|
+
column: node.startPosition.column,
|
|
766
|
+
code: getLineText(code, node.startPosition.row),
|
|
767
|
+
argCount,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
else if (funcNode.type === "member_expression") {
|
|
771
|
+
const objNode = funcNode.childForFieldName("object");
|
|
772
|
+
const propNode = funcNode.childForFieldName("property");
|
|
773
|
+
if (objNode && propNode) {
|
|
774
|
+
const obj = getText(objNode, code);
|
|
775
|
+
const method = getText(propNode, code);
|
|
776
|
+
// Skip built-in objects (console, window, etc.) but NOT imported symbols
|
|
777
|
+
// We need to track method calls on imported symbols for validation
|
|
778
|
+
// e.g., screen from @testing-library/react shadows the browser's window.screen
|
|
779
|
+
//
|
|
780
|
+
// EXCEPTION: If the builtin is cast to `any` (e.g., `(window as any).foo()`),
|
|
781
|
+
// do NOT skip — the `as any` cast is a deliberate type-safety bypass,
|
|
782
|
+
// which is a strong signal of a stealth hallucination.
|
|
783
|
+
const rawObjText = getText(objNode, code);
|
|
784
|
+
const isCastToAny = rawObjText.includes("as any") || rawObjText.includes("as unknown");
|
|
785
|
+
if (isJSBuiltin(obj) && !externalSymbols.has(obj) && !isCastToAny) {
|
|
786
|
+
// Skip method calls on built-in objects (Array.map, String.split, etc.)
|
|
787
|
+
// These are standard library methods we don't need to validate
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
// Extract the root object from complex expressions like:
|
|
791
|
+
// - (err as Type).property -> err
|
|
792
|
+
// - arr[index].property -> arr[index]
|
|
793
|
+
// - fn().property -> fn()
|
|
794
|
+
const rootObj = getRootObject(objNode, code);
|
|
795
|
+
usages.push({
|
|
796
|
+
name: method,
|
|
797
|
+
type: "methodCall",
|
|
798
|
+
object: rootObj,
|
|
799
|
+
line: node.startPosition.row + 1,
|
|
800
|
+
column: node.startPosition.column,
|
|
801
|
+
code: getLineText(code, node.startPosition.row),
|
|
802
|
+
argCount,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
case "new_expression": {
|
|
811
|
+
const constructorNode = node.childForFieldName("constructor");
|
|
812
|
+
if (constructorNode?.type === "identifier") {
|
|
813
|
+
const name = getText(constructorNode, code);
|
|
814
|
+
usages.push({
|
|
815
|
+
name,
|
|
816
|
+
type: "instantiation",
|
|
817
|
+
line: node.startPosition.row + 1,
|
|
818
|
+
column: node.startPosition.column,
|
|
819
|
+
code: getLineText(code, node.startPosition.row),
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
// Don't recurse into children to avoid extracting the constructor name again as a reference
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
case "identifier":
|
|
826
|
+
case "jsx_identifier":
|
|
827
|
+
case "property_identifier":
|
|
828
|
+
case "type_identifier": {
|
|
829
|
+
const name = getText(node, code);
|
|
830
|
+
// Skip built-in objects that are commonly used as property access base
|
|
831
|
+
// (e.g., console in console.log, window in window.location)
|
|
832
|
+
// These are handled specially and shouldn't be flagged as undefined references
|
|
833
|
+
if (isJSBuiltin(name)) {
|
|
834
|
+
// But only skip if it's being used as an object base (member_expression.object)
|
|
835
|
+
const parent = node.parent;
|
|
836
|
+
if (parent?.type === "member_expression" &&
|
|
837
|
+
parent.childForFieldName("object")?.id === node.id) {
|
|
838
|
+
break; // Skip this identifier
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// Semantic Bridge: Detect potential API calls (fetch('/api/...'))
|
|
842
|
+
if (name === "fetch" ||
|
|
843
|
+
name === "axios" ||
|
|
844
|
+
name === "get" ||
|
|
845
|
+
name === "post") {
|
|
846
|
+
const parentCall = node.parent?.type === "call_expression" ? node.parent
|
|
847
|
+
: node.parent?.parent?.type === "call_expression" ? node.parent.parent
|
|
848
|
+
: null;
|
|
849
|
+
if (parentCall) {
|
|
850
|
+
const argsNode = parentCall.childForFieldName("arguments");
|
|
851
|
+
if (argsNode && argsNode.children.length > 0) {
|
|
852
|
+
const firstArg = argsNode.children.find((c) => c.type === "string" || c.type === "template_string");
|
|
853
|
+
if (firstArg) {
|
|
854
|
+
const url = getText(firstArg, code).replace(/['"`]/g, "");
|
|
855
|
+
if (url.startsWith("/")) {
|
|
856
|
+
const exists = usages.some((u) => u.name === url && u.line === node.startPosition.row + 1);
|
|
857
|
+
// Record API endpoint usage for the Semantic Bridge / symbol graph.
|
|
858
|
+
// IMPORTANT: This is NOT validated as a variable (validateSymbols ignores apiCall).
|
|
859
|
+
if (!exists) {
|
|
860
|
+
usages.push({
|
|
861
|
+
name: url,
|
|
862
|
+
type: "apiCall",
|
|
863
|
+
line: node.startPosition.row + 1,
|
|
864
|
+
column: node.startPosition.column,
|
|
865
|
+
code: getLineText(code, node.startPosition.row),
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
// Only skip standard lowercase JSX tags (div, span, etc.)
|
|
874
|
+
// tree-sitter-typescript/tsx uses plain "identifier" (not "jsx_identifier") for tag names
|
|
875
|
+
// inside jsx_opening_element and jsx_closing_element, so check both node types
|
|
876
|
+
if (node.type === "jsx_identifier" && /^[a-z]/.test(name))
|
|
877
|
+
break;
|
|
878
|
+
if (node.type === "identifier" && /^[a-z]/.test(name)) {
|
|
879
|
+
const p = node.parent;
|
|
880
|
+
if (p?.type === "jsx_opening_element" || p?.type === "jsx_closing_element" || p?.type === "jsx_self_closing_element") {
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Filter out non-usage positions:
|
|
885
|
+
const parent = node.parent;
|
|
886
|
+
if (!parent)
|
|
887
|
+
break;
|
|
888
|
+
// 0. Skip if inside a string literal or JSX text content
|
|
889
|
+
// This prevents false positives like "Notes" in <h3>Comments & Notes</h3>
|
|
890
|
+
// Also skip ERROR nodes inside JSX (parser errors from invalid JSX characters like &)
|
|
891
|
+
let ancestor = parent;
|
|
892
|
+
while (ancestor) {
|
|
893
|
+
// template_substitution (${...}) is real code inside a template string
|
|
894
|
+
// Don't skip identifiers inside substitution expressions
|
|
895
|
+
if (ancestor.type === "template_substitution")
|
|
896
|
+
break;
|
|
897
|
+
if (ancestor.type === "string" ||
|
|
898
|
+
ancestor.type === "template_string" ||
|
|
899
|
+
ancestor.type === "jsx_text" ||
|
|
900
|
+
ancestor.type === "string_fragment") {
|
|
901
|
+
return; // Don't process this identifier at all
|
|
902
|
+
}
|
|
903
|
+
// Skip identifiers inside ERROR nodes that are within JSX elements
|
|
904
|
+
// This happens when JSX contains invalid characters like unescaped &
|
|
905
|
+
if (ancestor.type === "ERROR") {
|
|
906
|
+
// Check if this ERROR is within a JSX element
|
|
907
|
+
let jsxAncestor = ancestor.parent;
|
|
908
|
+
while (jsxAncestor) {
|
|
909
|
+
if (jsxAncestor.type === "jsx_element" ||
|
|
910
|
+
jsxAncestor.type === "jsx_self_closing_element" ||
|
|
911
|
+
jsxAncestor.type === "jsx_expression" ||
|
|
912
|
+
jsxAncestor.type === "jsx_fragment") {
|
|
913
|
+
return; // This is an identifier in invalid JSX text, skip it
|
|
914
|
+
}
|
|
915
|
+
jsxAncestor = jsxAncestor.parent;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
ancestor = ancestor.parent;
|
|
919
|
+
}
|
|
920
|
+
// 1. Handle member expression property access (x.NAME)
|
|
921
|
+
// This is used for method references like: { queryFn: api.getMethod }
|
|
922
|
+
if ((parent.type === "member_expression" ||
|
|
923
|
+
parent.type === "jsx_member_expression") &&
|
|
924
|
+
parent.childForFieldName("property")?.id === node.id) {
|
|
925
|
+
const objNode = parent.childForFieldName("object");
|
|
926
|
+
// Use getRootObject to handle complex expressions like (err as Type).property
|
|
927
|
+
const objName = objNode ? getRootObject(objNode, code) : "";
|
|
928
|
+
// Special case: member_expression inside JSX-misparsed context
|
|
929
|
+
// This happens with: <button aria-label="Close" role="button">
|
|
930
|
+
// where 'role' becomes property_identifier in member_expression inside assignment_expression
|
|
931
|
+
// Check if we're in a JSX-like context
|
|
932
|
+
let isInJsxContext = false;
|
|
933
|
+
let ancestor = parent.parent;
|
|
934
|
+
while (ancestor) {
|
|
935
|
+
if (ancestor.type === "ERROR") {
|
|
936
|
+
// Check if this ERROR looks like it came from JSX parsing
|
|
937
|
+
isInJsxContext = true;
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
// Check for JSX-misparsed patterns
|
|
941
|
+
// Pattern: binary_expression containing type_assertion followed by - followed by assignment_expression
|
|
942
|
+
// This is the structure from: <button aria-label="..." role="...">
|
|
943
|
+
if (ancestor.type === "binary_expression") {
|
|
944
|
+
const grandparent = ancestor.parent;
|
|
945
|
+
if (grandparent) {
|
|
946
|
+
const ancestorIndex = grandparent.children.indexOf(ancestor);
|
|
947
|
+
// Check if binary_expression is at the right position
|
|
948
|
+
if (ancestorIndex >= 0) {
|
|
949
|
+
// Check if binary_expression contains: type_assertion, -, assignment_expression
|
|
950
|
+
const typeAssertChild = ancestor.children.find(c => c.type === "type_assertion");
|
|
951
|
+
const dashChild = ancestor.children.find(c => c.type === "-" || c.text === "-");
|
|
952
|
+
const assignChild = ancestor.children.find(c => c.type === "assignment_expression");
|
|
953
|
+
if (typeAssertChild && dashChild && assignChild) {
|
|
954
|
+
isInJsxContext = true;
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
if (ancestor.type === "jsx_expression" || ancestor.type === "jsx_element") {
|
|
961
|
+
// Real JSX context - don't skip here, let the jsx_attribute check handle it
|
|
962
|
+
isInJsxContext = false;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
ancestor = ancestor.parent;
|
|
966
|
+
}
|
|
967
|
+
if (isInJsxContext) {
|
|
968
|
+
// Skip this property_identifier - it's likely a JSX attribute name
|
|
969
|
+
// that got misparsed as member_expression due to parser confusion
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
// Check if this member_expression is the function of a call_expression.
|
|
973
|
+
// If so, it's already handled by the call_expression case above (line ~892).
|
|
974
|
+
// If NOT, it's just property access (e.g., dashboard.projects, arr[0].name)
|
|
975
|
+
// which should NOT be flagged as a method call.
|
|
976
|
+
const memberParent = parent.parent;
|
|
977
|
+
const isCallTarget = memberParent?.type === "call_expression" &&
|
|
978
|
+
memberParent.childForFieldName("function")?.id === parent.id;
|
|
979
|
+
if (isCallTarget) {
|
|
980
|
+
// Already handled by call_expression case — skip to avoid double-reporting
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
// This is pure property access (not a call), e.g.:
|
|
984
|
+
// - dashboard.projects (API response property)
|
|
985
|
+
// - updated[idx].deliverables (array element property)
|
|
986
|
+
// - integrations[0].provider (indexed property)
|
|
987
|
+
// Only track as methodCall if the object is a known project symbol
|
|
988
|
+
// (e.g., { queryFn: paymentsApi.getPaymentMethods } where paymentsApi is imported).
|
|
989
|
+
// Skip property access on local/dynamic variables to avoid FPs on API responses.
|
|
990
|
+
if (objName === "state") {
|
|
991
|
+
// We ALLOW this to be processed as a usage to catch hallucinations in stores
|
|
992
|
+
}
|
|
993
|
+
else if (!isJSBuiltin(objName) && externalSymbols.has(objName)) {
|
|
994
|
+
// Only track property access on imported/external symbols where we can validate
|
|
995
|
+
usages.push({
|
|
996
|
+
name: name,
|
|
997
|
+
type: "methodCall",
|
|
998
|
+
object: objName,
|
|
999
|
+
line: node.startPosition.row + 1,
|
|
1000
|
+
column: node.startPosition.column,
|
|
1001
|
+
code: getLineText(code, node.startPosition.row),
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
// 2. Skip if it's a field name in an object literal ({ NAME: val })
|
|
1007
|
+
if (parent.type === "pair" && parent.childForFieldName("key")?.id === node.id)
|
|
1008
|
+
break;
|
|
1009
|
+
// 2a. Skip if it's a property name in an interface or type literal
|
|
1010
|
+
// e.g., interface Foo { bar: string } or type Foo = { bar: string }
|
|
1011
|
+
// The property name is a definition, not a usage of an external variable
|
|
1012
|
+
if (parent.type === "property_signature" && parent.childForFieldName("name")?.id === node.id)
|
|
1013
|
+
break;
|
|
1014
|
+
// 2b. Skip if it's a class field declaration (public/private/protected property)
|
|
1015
|
+
// e.g., class Foo { private bar: string }
|
|
1016
|
+
// TypeScript uses public_field_definition, private_field_definition, etc.
|
|
1017
|
+
// Note: property_identifier is used as the name node for class fields
|
|
1018
|
+
if ((parent.type === "property_definition" ||
|
|
1019
|
+
parent.type === "public_field_definition" ||
|
|
1020
|
+
parent.type === "private_field_definition" ||
|
|
1021
|
+
parent.type === "protected_field_definition" ||
|
|
1022
|
+
parent.type === "field_definition") &&
|
|
1023
|
+
parent.childForFieldName("name")?.id === node.id)
|
|
1024
|
+
break;
|
|
1025
|
+
// 2c. Skip if it's a JSX attribute name (e.g., <div className="foo" />)
|
|
1026
|
+
// These are HTML/SVG/React props, not variable references
|
|
1027
|
+
// JSX attributes have the name as the first child (property_identifier or jsx_identifier)
|
|
1028
|
+
if (parent.type === "jsx_attribute" && parent.children[0]?.id === node.id)
|
|
1029
|
+
break;
|
|
1030
|
+
// 2d. Skip property_identifier when it's used as a JSX attribute name
|
|
1031
|
+
// This handles cases where JSX parsing produces different AST structures
|
|
1032
|
+
// e.g., <img src={url} /> where 'src' might be parsed as property_identifier
|
|
1033
|
+
// or <button role="..."> where 'role' is parsed as property_identifier in member_expression
|
|
1034
|
+
if (node.type === "property_identifier") {
|
|
1035
|
+
// Check if this property_identifier is inside a JSX attribute context
|
|
1036
|
+
let ancestor = parent;
|
|
1037
|
+
while (ancestor) {
|
|
1038
|
+
if (ancestor.type === "jsx_attribute") {
|
|
1039
|
+
// This property_identifier is the attribute name
|
|
1040
|
+
if (ancestor.children[0]?.id === node.id ||
|
|
1041
|
+
(ancestor.childForFieldName("name")?.id === node.id)) {
|
|
1042
|
+
return; // Skip - this is a JSX attribute name, not a variable reference
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
// Also check for ERROR nodes that might contain JSX attributes
|
|
1046
|
+
if (ancestor.type === "ERROR") {
|
|
1047
|
+
// Check if this property_identifier is followed by = in an ERROR node
|
|
1048
|
+
// by looking at the parent's siblings
|
|
1049
|
+
const grandparent = ancestor.parent;
|
|
1050
|
+
if (grandparent) {
|
|
1051
|
+
const ancestorIndex = grandparent.children.indexOf(ancestor);
|
|
1052
|
+
if (ancestorIndex >= 0) {
|
|
1053
|
+
const nextAtAncestorLevel = grandparent.children[ancestorIndex + 1];
|
|
1054
|
+
if (nextAtAncestorLevel && nextAtAncestorLevel.type === "=") {
|
|
1055
|
+
return; // This looks like a JSX attribute name in an ERROR node
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
// Special case: property_identifier inside member_expression inside ERROR
|
|
1060
|
+
// This happens with: <button aria-label="Close" role="button">
|
|
1061
|
+
// where 'role' becomes property_identifier in member_expression
|
|
1062
|
+
if (parent.type === "member_expression") {
|
|
1063
|
+
// Check if the member_expression is followed by = in the ERROR node
|
|
1064
|
+
const memberExprIndex = ancestor.children.indexOf(parent);
|
|
1065
|
+
if (memberExprIndex >= 0) {
|
|
1066
|
+
const nextAfterMember = ancestor.children[memberExprIndex + 1];
|
|
1067
|
+
if (nextAfterMember && nextAfterMember.type === "=") {
|
|
1068
|
+
return; // This looks like a JSX attribute name (role=)
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
ancestor = ancestor.parent;
|
|
1074
|
+
}
|
|
1075
|
+
// Additional check: property_identifier in member_expression that's part of
|
|
1076
|
+
// a JSX-like pattern even without ERROR nodes
|
|
1077
|
+
// This handles: <button aria-label="Close menu" role="button">
|
|
1078
|
+
// where 'role' is property_identifier in member_expression in assignment_expression
|
|
1079
|
+
if (parent.type === "member_expression") {
|
|
1080
|
+
// Check if this member_expression has a specific pattern suggesting JSX misparsing
|
|
1081
|
+
// Pattern: string + ?. + property_identifier followed by =
|
|
1082
|
+
// This is "value"?.property = something
|
|
1083
|
+
const objNode = parent.childForFieldName("object");
|
|
1084
|
+
const propNode = parent.childForFieldName("property");
|
|
1085
|
+
if (objNode?.type === "string" && propNode === node) {
|
|
1086
|
+
// The member_expression looks like "string"?.property
|
|
1087
|
+
// Check if parent of member_expression is followed by =
|
|
1088
|
+
const grandparent = parent.parent;
|
|
1089
|
+
if (grandparent) {
|
|
1090
|
+
const parentIndex = grandparent.children.indexOf(parent);
|
|
1091
|
+
if (parentIndex >= 0) {
|
|
1092
|
+
const nextAfterParent = grandparent.children[parentIndex + 1];
|
|
1093
|
+
if (nextAfterParent && nextAfterParent.type === "=") {
|
|
1094
|
+
// This looks like "value"?.attr = value pattern from JSX parsing
|
|
1095
|
+
return; // Skip - this is a JSX attribute name
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// 2e. Skip identifiers/type_identifiers that are actually JSX attribute names
|
|
1103
|
+
// This handles cases where JSX parsing produces type-related AST nodes
|
|
1104
|
+
// e.g., <img src={url} /> where 'src' might be parsed as type_identifier
|
|
1105
|
+
// or <Tag attr={val} /> where 'attr' is an identifier inside an ERROR node
|
|
1106
|
+
if (node.type === "identifier" || node.type === "type_identifier") {
|
|
1107
|
+
// First check: is this identifier directly followed by = in its parent?
|
|
1108
|
+
// This handles: <Tag attr={val} /> where attr is an identifier child of ERROR
|
|
1109
|
+
const nodeIndexInParent = parent.children.indexOf(node);
|
|
1110
|
+
if (nodeIndexInParent >= 0) {
|
|
1111
|
+
const nextSibling = parent.children[nodeIndexInParent + 1];
|
|
1112
|
+
if (nextSibling && (nextSibling.type === "=" ||
|
|
1113
|
+
nextSibling.text === "=")) {
|
|
1114
|
+
return; // Skip - this is attr= pattern (JSX attribute name)
|
|
1115
|
+
}
|
|
1116
|
+
// Handle hyphenated attributes like aria-label, data-testid
|
|
1117
|
+
// Pattern: attr-name= (identifier, -, identifier, =)
|
|
1118
|
+
if (nextSibling && (nextSibling.type === "-" ||
|
|
1119
|
+
nextSibling.text === "-")) {
|
|
1120
|
+
const nextNextSibling = parent.children[nodeIndexInParent + 2];
|
|
1121
|
+
const nextNextNextSibling = parent.children[nodeIndexInParent + 3];
|
|
1122
|
+
if (nextNextSibling && nextNextSibling.type === "identifier") {
|
|
1123
|
+
// Check if after the second part there's an =
|
|
1124
|
+
if (nextNextNextSibling && (nextNextNextSibling.type === "=" ||
|
|
1125
|
+
nextNextNextSibling.text === "=")) {
|
|
1126
|
+
return; // Skip - this is aria-label= pattern (JSX attribute name)
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
// Handle identifiers in type_assertion that are actually JSX attribute names
|
|
1131
|
+
// Pattern: <button aria-label="..."> where 'aria' is parsed as type_assertion's expression
|
|
1132
|
+
// The structure is: type_assertion (type_arguments + identifier) - assignment_expression
|
|
1133
|
+
if (parent.type === "type_assertion") {
|
|
1134
|
+
// Check if type_assertion is followed by - (aria- pattern)
|
|
1135
|
+
const parentIndex = parent.parent?.children.indexOf(parent);
|
|
1136
|
+
if (parentIndex !== undefined && parentIndex >= 0 && parent.parent) {
|
|
1137
|
+
const nextAfterParent = parent.parent.children[parentIndex + 1];
|
|
1138
|
+
if (nextAfterParent && (nextAfterParent.type === "-" ||
|
|
1139
|
+
nextAfterParent.text === "-")) {
|
|
1140
|
+
return; // Skip - this looks like aria-label pattern
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
// Check if this is inside a JSX-like context (ERROR node or type_parameters)
|
|
1146
|
+
// where the identifier is followed by = or has JSX-like structure
|
|
1147
|
+
let ancestor = parent;
|
|
1148
|
+
while (ancestor) {
|
|
1149
|
+
// Check if we're inside what looks like a JSX element
|
|
1150
|
+
if (ancestor.type === "ERROR" || ancestor.type === "type_parameters" ||
|
|
1151
|
+
ancestor.type === "type_arguments") {
|
|
1152
|
+
// For type_identifiers inside type_parameter (e.g., src={val} parsed as type)
|
|
1153
|
+
// Structure: type_parameter -> type_identifier (attr name) -> default_type (=value)
|
|
1154
|
+
if (parent.type === "type_parameter") {
|
|
1155
|
+
// Check if there's a default_type (the =value part) after this type_identifier
|
|
1156
|
+
const nodeIndex = parent.children.indexOf(node);
|
|
1157
|
+
if (nodeIndex >= 0) {
|
|
1158
|
+
const nextSibling = parent.children[nodeIndex + 1];
|
|
1159
|
+
if (nextSibling && nextSibling.type === "default_type") {
|
|
1160
|
+
return; // Skip - this looks like attr={value} pattern
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
// Handle identifiers inside ERROR nodes that are inside type_parameter
|
|
1165
|
+
// This happens with: <Tag type="file" /> where 'type' is parsed as type_identifier
|
|
1166
|
+
// Structure: type_parameter -> ERROR (only child is identifier "type") -> default_type
|
|
1167
|
+
if (parent.type === "ERROR") {
|
|
1168
|
+
const grandparent = parent.parent;
|
|
1169
|
+
// Case 1: ERROR is inside type_parameter, and ERROR only contains this identifier
|
|
1170
|
+
// This is the "type" in <input type="file" ...>
|
|
1171
|
+
if (grandparent && grandparent.type === "type_parameter") {
|
|
1172
|
+
// Check if ERROR only has one child (this identifier)
|
|
1173
|
+
if (parent.children.length === 1 && parent.children[0]?.id === node.id) {
|
|
1174
|
+
// Check if the ERROR is followed by default_type in type_parameter
|
|
1175
|
+
const errorIndex = grandparent.children.indexOf(parent);
|
|
1176
|
+
if (errorIndex >= 0) {
|
|
1177
|
+
const nextAfterError = grandparent.children[errorIndex + 1];
|
|
1178
|
+
if (nextAfterError && nextAfterError.type === "default_type") {
|
|
1179
|
+
return; // Skip - this is a JSX attribute name
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
// Case 2: ERROR is inside default_type, containing multiple identifiers
|
|
1185
|
+
// This is "multiple" or "accept" in <input type="file" multiple accept="..." />
|
|
1186
|
+
const greatGrandparent = grandparent?.parent;
|
|
1187
|
+
if (grandparent?.type === "default_type" && greatGrandparent?.type === "type_parameter") {
|
|
1188
|
+
// This ERROR contains multiple identifiers and/or literal_types
|
|
1189
|
+
// Check if this identifier follows a literal_type (boolean attr after value)
|
|
1190
|
+
const nodeIndex = parent.children.indexOf(node);
|
|
1191
|
+
const prevSibling = nodeIndex > 0 ? parent.children[nodeIndex - 1] : null;
|
|
1192
|
+
if (prevSibling && (prevSibling.type === "literal_type" ||
|
|
1193
|
+
prevSibling.type === "identifier")) {
|
|
1194
|
+
return; // Skip - this looks like a boolean JSX attribute after a value
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
// Check if we're inside a jsx_expression - this is the {value} part, not the attribute name
|
|
1200
|
+
if (ancestor.type === "jsx_expression") {
|
|
1201
|
+
break; // Don't skip - identifiers inside { } are actual variable references
|
|
1202
|
+
}
|
|
1203
|
+
ancestor = ancestor.parent;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
// 2f. Skip identifiers inside nested_identifier (type-level namespace access)
|
|
1207
|
+
// e.g., Express.Multer.File[] — Express and Multer are namespace types, not variable references
|
|
1208
|
+
if (parent.type === "nested_identifier" || parent.type === "nested_type_identifier")
|
|
1209
|
+
break;
|
|
1210
|
+
// 3. Skip if it's a function/class/variable declaration name
|
|
1211
|
+
if ((parent.type === "function_declaration" ||
|
|
1212
|
+
parent.type === "class_declaration" ||
|
|
1213
|
+
parent.type === "variable_declarator" ||
|
|
1214
|
+
parent.type === "method_definition" ||
|
|
1215
|
+
parent.type === "interface_declaration" ||
|
|
1216
|
+
parent.type === "type_alias_declaration") &&
|
|
1217
|
+
parent.childForFieldName("name")?.id === node.id)
|
|
1218
|
+
break;
|
|
1219
|
+
// 3a. Skip if it's the name of a function expression
|
|
1220
|
+
// e.g., return function ProtectedComponent(props: P) { ... }
|
|
1221
|
+
// The 'ProtectedComponent' here is a local name for the function expression,
|
|
1222
|
+
// NOT a reference to an external variable
|
|
1223
|
+
// Note: Tree-sitter uses "function_expression" for function expressions, not "function"
|
|
1224
|
+
if ((parent.type === "function" || parent.type === "function_expression") &&
|
|
1225
|
+
parent.childForFieldName("name")?.id === node.id) {
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
// 3b. Skip TypeScript generic type parameter declarations
|
|
1229
|
+
// e.g., <T> in function foo<T>(...) — T is declared here, not a reference
|
|
1230
|
+
if (parent.type === "type_parameter" || parent.type === "type_parameters")
|
|
1231
|
+
break;
|
|
1232
|
+
// 3c. Skip generic type parameter USAGES (T, P, K, V, etc.) in type positions
|
|
1233
|
+
// These are single-letter uppercase identifiers used in type annotations/arguments
|
|
1234
|
+
// that were declared as generic params, not imported symbols
|
|
1235
|
+
if (node.type === "type_identifier" && /^[A-Z]$/.test(name))
|
|
1236
|
+
break;
|
|
1237
|
+
// 3d. Skip type_identifier that is the property part of a qualified type name
|
|
1238
|
+
// e.g., ErrorInfo in React.ErrorInfo — this is a namespace property, not a standalone reference
|
|
1239
|
+
if (node.type === "type_identifier" && parent.type === "nested_type_identifier")
|
|
1240
|
+
break;
|
|
1241
|
+
// 4. Skip if it's a formal parameter (including rest parameters)
|
|
1242
|
+
if (parent.type === "formal_parameters" ||
|
|
1243
|
+
parent.type === "required_parameter" ||
|
|
1244
|
+
parent.type === "optional_parameter" ||
|
|
1245
|
+
parent.type === "rest_pattern")
|
|
1246
|
+
break;
|
|
1247
|
+
// 5. Skip if it's the property in shorthand ({ name }) - this IS a usage of the outer 'name'
|
|
1248
|
+
// but Tree-sitter treats it as a 'shorthand_property_identifier'.
|
|
1249
|
+
if (parent.type === "shorthand_property_identifier") {
|
|
1250
|
+
// This IS a usage.
|
|
1251
|
+
}
|
|
1252
|
+
// 6. Skip if it's part of an import statement (handled separately)
|
|
1253
|
+
// Note: export_specifier is NOT skipped — re-exports like `export type { Project }`
|
|
1254
|
+
// should count as usages so the original import isn't flagged as unused
|
|
1255
|
+
if (parent.type === "import_specifier")
|
|
1256
|
+
break;
|
|
1257
|
+
// 7. If we reach here, it's likely a reference (usage as a value)
|
|
1258
|
+
// Check if it was already added as a call or instantiation in the same line/col
|
|
1259
|
+
const exists = usages.some((u) => u.line === node.startPosition.row + 1 &&
|
|
1260
|
+
u.column === node.startPosition.column);
|
|
1261
|
+
if (!exists) {
|
|
1262
|
+
usages.push({
|
|
1263
|
+
name,
|
|
1264
|
+
type: "reference",
|
|
1265
|
+
line: node.startPosition.row + 1,
|
|
1266
|
+
column: node.startPosition.column,
|
|
1267
|
+
code: getLineText(code, node.startPosition.row),
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
for (const child of node.children) {
|
|
1274
|
+
if (child) {
|
|
1275
|
+
extractJSUsages(child, code, usages, externalSymbols);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
// ============================================================================
|
|
1280
|
+
// Import Extraction
|
|
1281
|
+
// ============================================================================
|
|
1282
|
+
/**
|
|
1283
|
+
* Extract import statements from JavaScript/TypeScript AST
|
|
1284
|
+
* Handles: ES6 imports, dynamic imports (await import(...))
|
|
1285
|
+
*/
|
|
1286
|
+
export function extractJSImports(node, code, imports) {
|
|
1287
|
+
if (!node)
|
|
1288
|
+
return;
|
|
1289
|
+
if (node.type === "import_statement") {
|
|
1290
|
+
const sourceNode = node.childForFieldName("source");
|
|
1291
|
+
if (sourceNode) {
|
|
1292
|
+
const module = getText(sourceNode, code).replace(/['"]/g, "");
|
|
1293
|
+
const names = [];
|
|
1294
|
+
let isTypeOnly = false;
|
|
1295
|
+
for (const child of node.children) {
|
|
1296
|
+
if (!child)
|
|
1297
|
+
continue;
|
|
1298
|
+
// Detect "import type" - TypeScript specific
|
|
1299
|
+
if (child.type === "type") {
|
|
1300
|
+
isTypeOnly = true;
|
|
1301
|
+
}
|
|
1302
|
+
if (child.type === "import_clause") {
|
|
1303
|
+
for (const clauseChild of child.children) {
|
|
1304
|
+
if (!clauseChild)
|
|
1305
|
+
continue;
|
|
1306
|
+
if (clauseChild.type === "identifier") {
|
|
1307
|
+
// Default import
|
|
1308
|
+
const name = getText(clauseChild, code);
|
|
1309
|
+
names.push({ imported: "default", local: name });
|
|
1310
|
+
}
|
|
1311
|
+
else if (clauseChild.type === "named_imports") {
|
|
1312
|
+
for (const specifier of clauseChild.children) {
|
|
1313
|
+
if (!specifier)
|
|
1314
|
+
continue;
|
|
1315
|
+
if (specifier.type === "import_specifier") {
|
|
1316
|
+
const nameNode = specifier.childForFieldName("name");
|
|
1317
|
+
const aliasNode = specifier.childForFieldName("alias");
|
|
1318
|
+
if (nameNode) {
|
|
1319
|
+
const imported = getText(nameNode, code);
|
|
1320
|
+
const local = aliasNode ? getText(aliasNode, code) : imported;
|
|
1321
|
+
names.push({ imported, local });
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else if (clauseChild.type === "namespace_import") {
|
|
1327
|
+
const nameNode = clauseChild.children.find((c) => c && c.type === "identifier");
|
|
1328
|
+
if (nameNode) {
|
|
1329
|
+
const name = getText(nameNode, code);
|
|
1330
|
+
names.push({ imported: "*", local: name });
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const isExternal = !module.startsWith(".") &&
|
|
1337
|
+
!module.startsWith("@/") &&
|
|
1338
|
+
!module.startsWith("~/");
|
|
1339
|
+
imports.push({
|
|
1340
|
+
module,
|
|
1341
|
+
names,
|
|
1342
|
+
isExternal,
|
|
1343
|
+
line: node.startPosition.row + 1,
|
|
1344
|
+
isTypeOnly,
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
// Handle dynamic imports: await import('...')
|
|
1349
|
+
// These appear as call_expression with "import" as the function
|
|
1350
|
+
// Also handle CommonJS requires: require('...')
|
|
1351
|
+
if (node.type === "call_expression") {
|
|
1352
|
+
const funcNode = node.childForFieldName("function");
|
|
1353
|
+
const funcName = funcNode ? getText(funcNode, code) : "";
|
|
1354
|
+
if (funcName === "import") {
|
|
1355
|
+
const argsNode = node.childForFieldName("arguments");
|
|
1356
|
+
if (argsNode) {
|
|
1357
|
+
// Find the string argument
|
|
1358
|
+
for (const arg of argsNode.children) {
|
|
1359
|
+
if (arg &&
|
|
1360
|
+
(arg.type === "string" || arg.type === "template_string")) {
|
|
1361
|
+
const module = getText(arg, code).replace(/['"`]/g, "");
|
|
1362
|
+
if (module) {
|
|
1363
|
+
// For dynamic imports, we mark all possible named imports
|
|
1364
|
+
// by looking at the destructuring pattern if available
|
|
1365
|
+
const names = [];
|
|
1366
|
+
// Check if this is part of a destructuring assignment
|
|
1367
|
+
// e.g., const { foo, bar } = await import('...')
|
|
1368
|
+
const parent = node.parent;
|
|
1369
|
+
if (parent?.type === "await_expression") {
|
|
1370
|
+
const grandparent = parent.parent;
|
|
1371
|
+
if (grandparent?.type === "variable_declarator") {
|
|
1372
|
+
const nameNode = grandparent.childForFieldName("name");
|
|
1373
|
+
if (nameNode?.type === "object_pattern") {
|
|
1374
|
+
// Extract destructured names
|
|
1375
|
+
for (const prop of nameNode.children) {
|
|
1376
|
+
if (prop?.type === "shorthand_property_identifier_pattern") {
|
|
1377
|
+
const name = getText(prop, code);
|
|
1378
|
+
names.push({ imported: name, local: name });
|
|
1379
|
+
}
|
|
1380
|
+
else if (prop?.type === "pair_pattern") {
|
|
1381
|
+
const keyNode = prop.childForFieldName("key");
|
|
1382
|
+
const valueNode = prop.childForFieldName("value");
|
|
1383
|
+
if (keyNode && valueNode) {
|
|
1384
|
+
names.push({
|
|
1385
|
+
imported: getText(keyNode, code),
|
|
1386
|
+
local: getText(valueNode, code),
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// If no destructuring found, mark as wildcard import
|
|
1395
|
+
if (names.length === 0) {
|
|
1396
|
+
names.push({ imported: "*", local: "*" });
|
|
1397
|
+
}
|
|
1398
|
+
const isExternal = !module.startsWith(".") &&
|
|
1399
|
+
!module.startsWith("@/") &&
|
|
1400
|
+
!module.startsWith("~/");
|
|
1401
|
+
imports.push({
|
|
1402
|
+
module,
|
|
1403
|
+
names,
|
|
1404
|
+
isExternal,
|
|
1405
|
+
line: node.startPosition.row + 1,
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
// CommonJS require('...')
|
|
1413
|
+
if (funcNode?.type === "identifier" && funcName === "require") {
|
|
1414
|
+
const argsNode = node.childForFieldName("arguments");
|
|
1415
|
+
if (argsNode) {
|
|
1416
|
+
const firstArg = argsNode.children.find((c) => c && (c.type === "string" || c.type === "template_string"));
|
|
1417
|
+
if (firstArg) {
|
|
1418
|
+
// Skip non-static template strings (require(`./${x}`))
|
|
1419
|
+
if (firstArg.type === "template_string" &&
|
|
1420
|
+
firstArg.children.some((c) => c?.type === "template_substitution")) {
|
|
1421
|
+
// ignore
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
const module = getText(firstArg, code).replace(/['"`]/g, "");
|
|
1425
|
+
if (module) {
|
|
1426
|
+
const line = node.startPosition.row + 1;
|
|
1427
|
+
const exists = imports.some((i) => i.module === module && i.line === line);
|
|
1428
|
+
if (!exists) {
|
|
1429
|
+
const names = [];
|
|
1430
|
+
// Infer imported names from the assignment pattern:
|
|
1431
|
+
// const x = require('mod')
|
|
1432
|
+
// const { a, b: c } = require('mod')
|
|
1433
|
+
const parent = node.parent;
|
|
1434
|
+
if (parent?.type === "variable_declarator") {
|
|
1435
|
+
const nameNode = parent.childForFieldName("name");
|
|
1436
|
+
if (nameNode?.type === "identifier") {
|
|
1437
|
+
names.push({ imported: "default", local: getText(nameNode, code) });
|
|
1438
|
+
}
|
|
1439
|
+
else if (nameNode?.type === "object_pattern") {
|
|
1440
|
+
for (const prop of nameNode.children) {
|
|
1441
|
+
if (prop?.type === "shorthand_property_identifier_pattern") {
|
|
1442
|
+
const name = getText(prop, code);
|
|
1443
|
+
names.push({ imported: name, local: name });
|
|
1444
|
+
}
|
|
1445
|
+
else if (prop?.type === "pair_pattern") {
|
|
1446
|
+
const keyNode = prop.childForFieldName("key");
|
|
1447
|
+
const valueNode = prop.childForFieldName("value");
|
|
1448
|
+
if (keyNode && valueNode) {
|
|
1449
|
+
names.push({
|
|
1450
|
+
imported: getText(keyNode, code),
|
|
1451
|
+
local: getText(valueNode, code),
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (names.length === 0) {
|
|
1459
|
+
names.push({ imported: "*", local: "*" });
|
|
1460
|
+
}
|
|
1461
|
+
const isExternal = !module.startsWith(".") &&
|
|
1462
|
+
!module.startsWith("@/") &&
|
|
1463
|
+
!module.startsWith("~/");
|
|
1464
|
+
imports.push({
|
|
1465
|
+
module,
|
|
1466
|
+
names,
|
|
1467
|
+
isExternal,
|
|
1468
|
+
line,
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
for (const child of node.children) {
|
|
1478
|
+
if (child) {
|
|
1479
|
+
extractJSImports(child, code, imports);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
// ============================================================================
|
|
1484
|
+
// Type Reference Extraction
|
|
1485
|
+
// ============================================================================
|
|
1486
|
+
/**
|
|
1487
|
+
* Extract type references from JavaScript/TypeScript AST
|
|
1488
|
+
* Finds where types/interfaces are USED (not defined), including:
|
|
1489
|
+
* - Type annotations: `param: TypeName`
|
|
1490
|
+
* - Generic parameters: `Array<TypeName>`, `base.extend<TypeName>`
|
|
1491
|
+
* - Return types: `): TypeName`
|
|
1492
|
+
* - Extends/implements: `extends TypeName`, `implements TypeName`
|
|
1493
|
+
* - Property types in interfaces/types
|
|
1494
|
+
*/
|
|
1495
|
+
export function extractJSTypeReferences(node, code, references) {
|
|
1496
|
+
if (!node)
|
|
1497
|
+
return;
|
|
1498
|
+
switch (node.type) {
|
|
1499
|
+
// Type annotations: `param: TypeName` or `const x: TypeName`
|
|
1500
|
+
case "type_annotation": {
|
|
1501
|
+
extractTypeNamesFromNode(node, code, references, "typeAnnotation");
|
|
1502
|
+
break;
|
|
1503
|
+
}
|
|
1504
|
+
// Generic type arguments: `Array<TypeName>`, `Promise<TypeName>`, `base.extend<TypeName>`
|
|
1505
|
+
case "type_arguments": {
|
|
1506
|
+
extractTypeNamesFromNode(node, code, references, "genericParam");
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
1509
|
+
// Return type: `function(): TypeName`
|
|
1510
|
+
case "return_type": {
|
|
1511
|
+
extractTypeNamesFromNode(node, code, references, "returnType");
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
// Extends clause: `class X extends Y` or `interface X extends Y`
|
|
1515
|
+
case "extends_clause":
|
|
1516
|
+
case "extends_type_clause": {
|
|
1517
|
+
extractTypeNamesFromNode(node, code, references, "extends");
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
// Implements clause: `class X implements Y`
|
|
1521
|
+
case "implements_clause": {
|
|
1522
|
+
extractTypeNamesFromNode(node, code, references, "implements");
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
// Property signature in interface: `prop: TypeName`
|
|
1526
|
+
case "property_signature": {
|
|
1527
|
+
const typeNode = node.childForFieldName("type");
|
|
1528
|
+
if (typeNode) {
|
|
1529
|
+
extractTypeNamesFromNode(typeNode, code, references, "propertyType");
|
|
1530
|
+
}
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
// Index signature: `[key: string]: TypeName`
|
|
1534
|
+
case "index_signature": {
|
|
1535
|
+
const typeNode = node.childForFieldName("type");
|
|
1536
|
+
if (typeNode) {
|
|
1537
|
+
extractTypeNamesFromNode(typeNode, code, references, "propertyType");
|
|
1538
|
+
}
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
// Method signature return type
|
|
1542
|
+
case "method_signature": {
|
|
1543
|
+
const returnTypeNode = node.childForFieldName("return_type");
|
|
1544
|
+
if (returnTypeNode) {
|
|
1545
|
+
extractTypeNamesFromNode(returnTypeNode, code, references, "returnType");
|
|
1546
|
+
}
|
|
1547
|
+
// Also check parameters
|
|
1548
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
1549
|
+
if (paramsNode) {
|
|
1550
|
+
extractJSTypeReferences(paramsNode, code, references);
|
|
1551
|
+
}
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
// Required/optional parameters with type annotations
|
|
1555
|
+
case "required_parameter":
|
|
1556
|
+
case "optional_parameter": {
|
|
1557
|
+
const typeNode = node.childForFieldName("type");
|
|
1558
|
+
if (typeNode) {
|
|
1559
|
+
extractTypeNamesFromNode(typeNode, code, references, "typeAnnotation");
|
|
1560
|
+
}
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
// Recurse into children
|
|
1565
|
+
for (const child of node.children) {
|
|
1566
|
+
if (child) {
|
|
1567
|
+
extractJSTypeReferences(child, code, references);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Extract type names from a type node (handles nested types, unions, etc.)
|
|
1573
|
+
*/
|
|
1574
|
+
export function extractTypeNamesFromNode(node, code, references, context) {
|
|
1575
|
+
if (!node)
|
|
1576
|
+
return;
|
|
1577
|
+
switch (node.type) {
|
|
1578
|
+
// Simple type reference: `TypeName`
|
|
1579
|
+
case "type_identifier": {
|
|
1580
|
+
const name = getText(node, code);
|
|
1581
|
+
// Skip built-in types
|
|
1582
|
+
if (!isTSBuiltinType(name)) {
|
|
1583
|
+
references.push({
|
|
1584
|
+
name,
|
|
1585
|
+
context,
|
|
1586
|
+
line: node.startPosition.row + 1,
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
// Generic type: `Array<T>`, `Promise<T>`, `Map<K, V>`
|
|
1592
|
+
case "generic_type": {
|
|
1593
|
+
const nameNode = node.childForFieldName("name");
|
|
1594
|
+
if (nameNode) {
|
|
1595
|
+
const name = getText(nameNode, code);
|
|
1596
|
+
if (!isTSBuiltinType(name)) {
|
|
1597
|
+
references.push({
|
|
1598
|
+
name,
|
|
1599
|
+
context,
|
|
1600
|
+
line: node.startPosition.row + 1,
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
// Also extract type arguments
|
|
1605
|
+
const argsNode = node.childForFieldName("type_arguments");
|
|
1606
|
+
if (argsNode) {
|
|
1607
|
+
extractTypeNamesFromNode(argsNode, code, references, "genericParam");
|
|
1608
|
+
}
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
// Nested member expression type: `Namespace.TypeName`
|
|
1612
|
+
case "nested_type_identifier": {
|
|
1613
|
+
// Get the full qualified name
|
|
1614
|
+
const name = getText(node, code);
|
|
1615
|
+
references.push({
|
|
1616
|
+
name,
|
|
1617
|
+
context,
|
|
1618
|
+
line: node.startPosition.row + 1,
|
|
1619
|
+
});
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
// Union type: `TypeA | TypeB`
|
|
1623
|
+
case "union_type": {
|
|
1624
|
+
for (const child of node.children) {
|
|
1625
|
+
if (child && child.type !== "|") {
|
|
1626
|
+
extractTypeNamesFromNode(child, code, references, context);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
// Intersection type: `TypeA & TypeB`
|
|
1632
|
+
case "intersection_type": {
|
|
1633
|
+
for (const child of node.children) {
|
|
1634
|
+
if (child && child.type !== "&") {
|
|
1635
|
+
extractTypeNamesFromNode(child, code, references, context);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1640
|
+
// Array type: `TypeName[]`
|
|
1641
|
+
case "array_type": {
|
|
1642
|
+
const elementType = node.children[0];
|
|
1643
|
+
if (elementType) {
|
|
1644
|
+
extractTypeNamesFromNode(elementType, code, references, context);
|
|
1645
|
+
}
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
// Parenthesized type: `(TypeName)`
|
|
1649
|
+
case "parenthesized_type": {
|
|
1650
|
+
for (const child of node.children) {
|
|
1651
|
+
if (child && child.type !== "(" && child.type !== ")") {
|
|
1652
|
+
extractTypeNamesFromNode(child, code, references, context);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
// Type arguments: `<TypeA, TypeB>`
|
|
1658
|
+
case "type_arguments": {
|
|
1659
|
+
for (const child of node.children) {
|
|
1660
|
+
if (child &&
|
|
1661
|
+
child.type !== "<" &&
|
|
1662
|
+
child.type !== ">" &&
|
|
1663
|
+
child.type !== ",") {
|
|
1664
|
+
extractTypeNamesFromNode(child, code, references, "genericParam");
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
break;
|
|
1668
|
+
}
|
|
1669
|
+
// Conditional type: `T extends U ? X : Y`
|
|
1670
|
+
case "conditional_type": {
|
|
1671
|
+
for (const child of node.children) {
|
|
1672
|
+
if (child) {
|
|
1673
|
+
extractTypeNamesFromNode(child, code, references, context);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
// Indexed access type: `T[K]`
|
|
1679
|
+
case "indexed_access_type": {
|
|
1680
|
+
const objectType = node.childForFieldName("object_type");
|
|
1681
|
+
const indexType = node.childForFieldName("index_type");
|
|
1682
|
+
if (objectType) {
|
|
1683
|
+
extractTypeNamesFromNode(objectType, code, references, context);
|
|
1684
|
+
}
|
|
1685
|
+
if (indexType) {
|
|
1686
|
+
extractTypeNamesFromNode(indexType, code, references, context);
|
|
1687
|
+
}
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
// Mapped type: `{ [K in keyof T]: V }`
|
|
1691
|
+
case "mapped_type_clause": {
|
|
1692
|
+
for (const child of node.children) {
|
|
1693
|
+
if (child) {
|
|
1694
|
+
extractTypeNamesFromNode(child, code, references, context);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
break;
|
|
1698
|
+
}
|
|
1699
|
+
// Object type / type literal: `{ prop: Type }`
|
|
1700
|
+
case "object_type": {
|
|
1701
|
+
for (const child of node.children) {
|
|
1702
|
+
if (child) {
|
|
1703
|
+
extractJSTypeReferences(child, code, references);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
// Function type: `(param: Type) => ReturnType`
|
|
1709
|
+
case "function_type": {
|
|
1710
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
1711
|
+
const returnNode = node.childForFieldName("return_type");
|
|
1712
|
+
if (paramsNode) {
|
|
1713
|
+
for (const child of paramsNode.children) {
|
|
1714
|
+
if (child) {
|
|
1715
|
+
extractJSTypeReferences(child, code, references);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (returnNode) {
|
|
1720
|
+
extractTypeNamesFromNode(returnNode, code, references, "returnType");
|
|
1721
|
+
}
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1724
|
+
// Typeof type: `typeof SomeValue`
|
|
1725
|
+
case "typeof_type": {
|
|
1726
|
+
// Skip - this references a value, not a type
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
// Keyof type: `keyof T`
|
|
1730
|
+
case "keyof_type": {
|
|
1731
|
+
const typeArg = node.children.find((c) => c && c.type !== "keyof");
|
|
1732
|
+
if (typeArg) {
|
|
1733
|
+
extractTypeNamesFromNode(typeArg, code, references, context);
|
|
1734
|
+
}
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1737
|
+
default: {
|
|
1738
|
+
// For other node types, recurse into children
|
|
1739
|
+
for (const child of node.children) {
|
|
1740
|
+
if (child) {
|
|
1741
|
+
extractTypeNamesFromNode(child, code, references, context);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
// ============================================================================
|
|
1748
|
+
// Helper Functions
|
|
1749
|
+
// ============================================================================
|
|
1750
|
+
/**
|
|
1751
|
+
* Extract text from an AST node using pre-computed indices (faster than substring on each call)
|
|
1752
|
+
*/
|
|
1753
|
+
function getText(node, code) {
|
|
1754
|
+
return code.slice(node.startIndex, node.endIndex);
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Get a specific line from code
|
|
1758
|
+
* Uses simple split - caching would require a Map with string keys which has memory implications
|
|
1759
|
+
*/
|
|
1760
|
+
function getLineText(code, lineIndex) {
|
|
1761
|
+
const lines = code.split("\n");
|
|
1762
|
+
return lines[lineIndex]?.trim() || "";
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Count arguments in a function call
|
|
1766
|
+
*/
|
|
1767
|
+
function countArgs(argsNode) {
|
|
1768
|
+
if (!argsNode)
|
|
1769
|
+
return 0;
|
|
1770
|
+
let count = 0;
|
|
1771
|
+
for (const child of argsNode.children) {
|
|
1772
|
+
if (child.type !== "(" && child.type !== ")" && child.type !== ",") {
|
|
1773
|
+
count++;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return count;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Extract the root object identifier from a complex expression.
|
|
1780
|
+
* Handles cases like:
|
|
1781
|
+
* - (err as Type) -> err (unwraps parenthesized expressions and type assertions)
|
|
1782
|
+
* - obj.property -> obj
|
|
1783
|
+
* - arr[index] -> arr
|
|
1784
|
+
* - identifier -> identifier
|
|
1785
|
+
*
|
|
1786
|
+
* @param node - The AST node representing the object in a member expression
|
|
1787
|
+
* @param code - The source code string
|
|
1788
|
+
* @returns The root object text (variable name)
|
|
1789
|
+
*/
|
|
1790
|
+
function getRootObject(node, code) {
|
|
1791
|
+
if (!node)
|
|
1792
|
+
return "";
|
|
1793
|
+
// Handle parenthesized expressions: (expr)
|
|
1794
|
+
// This includes type assertions like (err as Error) or (err satisfies Error)
|
|
1795
|
+
if (node.type === "parenthesized_expression") {
|
|
1796
|
+
// Find the inner expression (skip the parentheses)
|
|
1797
|
+
for (const child of node.children) {
|
|
1798
|
+
if (child.type !== "(" && child.type !== ")") {
|
|
1799
|
+
return getRootObject(child, code);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
// Handle type assertion expressions: expr as Type, expr satisfies Type
|
|
1804
|
+
if (node.type === "as_expression" || node.type === "satisfies_expression") {
|
|
1805
|
+
const leftNode = node.childForFieldName("left") || node.children[0];
|
|
1806
|
+
if (leftNode) {
|
|
1807
|
+
return getRootObject(leftNode, code);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
// Handle non-null assertion expressions: expr!
|
|
1811
|
+
if (node.type === "non_null_expression") {
|
|
1812
|
+
const expression = node.children[0];
|
|
1813
|
+
if (expression) {
|
|
1814
|
+
return getRootObject(expression, code);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
// Handle call expressions: fn() - extract the function name
|
|
1818
|
+
if (node.type === "call_expression" || node.type === "new_expression") {
|
|
1819
|
+
const funcNode = node.childForFieldName("function") || node.childForFieldName("constructor");
|
|
1820
|
+
if (funcNode) {
|
|
1821
|
+
return getRootObject(funcNode, code);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
// Handle member expressions: obj.prop - recurse into the object to get the root identifier
|
|
1825
|
+
// This correctly handles multiline chains like db\n.select().from() → "db"
|
|
1826
|
+
// Previously returned getText which included newlines, causing FPs in validation
|
|
1827
|
+
if (node.type === "member_expression") {
|
|
1828
|
+
const objNode = node.childForFieldName("object");
|
|
1829
|
+
if (objNode) {
|
|
1830
|
+
return getRootObject(objNode, code);
|
|
1831
|
+
}
|
|
1832
|
+
return getText(node, code);
|
|
1833
|
+
}
|
|
1834
|
+
// Handle subscript expressions: arr[index] - return the full text
|
|
1835
|
+
if (node.type === "subscript_expression") {
|
|
1836
|
+
return getText(node, code);
|
|
1837
|
+
}
|
|
1838
|
+
// Base case: identifier or simple expression
|
|
1839
|
+
return getText(node, code);
|
|
1840
|
+
}
|
|
1841
|
+
//# sourceMappingURL=javascript.js.map
|