code-auditor-mcp 2.6.2 → 3.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/README.md +319 -147
- package/dist/analyzers/analyzerUtils.d.ts +13 -11
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +22 -56
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/dataAccessAnalyzer.js +50 -24
- package/dist/analyzers/dataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +4 -1
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +210 -111
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/dryAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/dryAnalyzer.js +190 -58
- package/dist/analyzers/dryAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts +20 -0
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/invariantsAnalyzer.js +148 -0
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +2 -2
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/schemaAnalyzer.d.ts +2 -0
- package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/schemaAnalyzer.js +306 -147
- package/dist/analyzers/schemaAnalyzer.js.map +1 -1
- package/dist/analyzers/solidAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/solidAnalyzer.js +145 -119
- package/dist/analyzers/solidAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +3 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +41 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +8 -8
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +6 -6
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +367 -24
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +740 -18
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB-enhanced.d.ts +35 -13
- package/dist/codeIndexDB-enhanced.d.ts.map +1 -1
- package/dist/codeIndexDB-enhanced.js +311 -214
- package/dist/codeIndexDB-enhanced.js.map +1 -1
- package/dist/{codeIndexDb.d.ts → codeIndexDB.d.ts} +51 -224
- package/dist/codeIndexDB.d.ts.map +1 -0
- package/dist/codeIndexDB.js +2042 -0
- package/dist/codeIndexDB.js.map +1 -0
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts +14 -8
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +136 -113
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +10 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/fingerprint.d.ts +15 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +32 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/functionScanner.d.ts +5 -0
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +417 -341
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AiderConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/invariants/index.d.ts +9 -0
- package/dist/invariants/index.d.ts.map +1 -0
- package/dist/invariants/index.js +4 -0
- package/dist/invariants/index.js.map +1 -0
- package/dist/invariants/invariant-rules.schema.json +137 -0
- package/dist/invariants/ruleEngine.d.ts +32 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -0
- package/dist/invariants/ruleEngine.js +536 -0
- package/dist/invariants/ruleEngine.js.map +1 -0
- package/dist/invariants/ruleValidator.d.ts +17 -0
- package/dist/invariants/ruleValidator.d.ts.map +1 -0
- package/dist/invariants/ruleValidator.js +264 -0
- package/dist/invariants/ruleValidator.js.map +1 -0
- package/dist/invariants/types.d.ts +78 -0
- package/dist/invariants/types.d.ts.map +1 -0
- package/dist/invariants/types.js +5 -0
- package/dist/invariants/types.js.map +1 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +1 -1
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +102 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -0
- package/dist/languages/adapterBridge.js +382 -0
- package/dist/languages/adapterBridge.js.map +1 -0
- package/dist/languages/go/GoAdapter.d.ts +37 -26
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +560 -281
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/index.d.ts +5 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +11 -9
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +43 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -0
- package/dist/languages/tree-sitter/converter.js +150 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -0
- package/dist/languages/tree-sitter/parser.d.ts +36 -0
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -0
- package/dist/languages/tree-sitter/parser.js +114 -0
- package/dist/languages/tree-sitter/parser.js.map +1 -0
- package/dist/languages/types.d.ts +11 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +92 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +951 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts +58 -22
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +172 -10
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +21 -21
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +130 -126
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.js +1 -1
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +1376 -1181
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +4 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +49 -2
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/native/ast-grep-napi.darwin-arm64.node +0 -0
- package/dist/native/ast-grep-napi.darwin-x64.node +0 -0
- package/dist/native/ast-grep-napi.linux-arm64-gnu.node +0 -0
- package/dist/native/ast-grep-napi.linux-arm64-musl.node +0 -0
- package/dist/native/ast-grep-napi.linux-x64-gnu.node +0 -0
- package/dist/native/ast-grep-napi.linux-x64-musl.node +0 -0
- package/dist/native/ast-grep-napi.win32-x64-msvc.node +0 -0
- package/dist/native-bootstrap.d.ts +15 -0
- package/dist/native-bootstrap.d.ts.map +1 -0
- package/dist/native-bootstrap.js +75 -0
- package/dist/native-bootstrap.js.map +1 -0
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/reportGenerator.js +4 -0
- package/dist/reporting/reportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts +39 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -0
- package/dist/reporting/sarifReportGenerator.js +229 -0
- package/dist/reporting/sarifReportGenerator.js.map +1 -0
- package/dist/search/QueryParser.d.ts +41 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +200 -19
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +4 -4
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts +15 -2
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js +16 -1
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/SchemaParser.d.ts.map +1 -1
- package/dist/services/SchemaParser.js +5 -8
- package/dist/services/SchemaParser.js.map +1 -1
- package/dist/services/auditJobService.d.ts +1 -1
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +1 -1
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/tool-registry.d.ts +79 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +139 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/types/projectTask.d.ts +4 -0
- package/dist/types/projectTask.d.ts.map +1 -1
- package/dist/types.d.ts +26 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +31 -38
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +138 -196
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +87 -58
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +852 -581
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/componentPatterns.js.map +1 -1
- package/dist/utils/componentResponsibility.d.ts +13 -9
- package/dist/utils/componentResponsibility.d.ts.map +1 -1
- package/dist/utils/componentResponsibility.js +236 -206
- package/dist/utils/componentResponsibility.js.map +1 -1
- package/dist/utils/dependencyExtractor.d.ts +11 -6
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +200 -119
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/fileDiscovery.d.ts +1 -1
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/jsDocParser.d.ts +27 -30
- package/dist/utils/jsDocParser.d.ts.map +1 -1
- package/dist/utils/jsDocParser.js +271 -207
- package/dist/utils/jsDocParser.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +27 -12
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +331 -219
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/workers/auditWorker.js +17 -3
- package/dist/workers/auditWorker.js.map +1 -1
- package/package.json +54 -51
- package/dist/adapters/GoAdapter.d.ts +0 -53
- package/dist/adapters/GoAdapter.d.ts.map +0 -1
- package/dist/adapters/GoAdapter.js +0 -611
- package/dist/adapters/GoAdapter.js.map +0 -1
- package/dist/adapters/GoConfig.d.ts +0 -35
- package/dist/adapters/GoConfig.d.ts.map +0 -1
- package/dist/adapters/GoConfig.js +0 -155
- package/dist/adapters/GoConfig.js.map +0 -1
- package/dist/adapters/LanguageAdapter.d.ts +0 -135
- package/dist/adapters/LanguageAdapter.d.ts.map +0 -1
- package/dist/adapters/LanguageAdapter.js +0 -47
- package/dist/adapters/LanguageAdapter.js.map +0 -1
- package/dist/adapters/TypeScriptAdapter.d.ts +0 -43
- package/dist/adapters/TypeScriptAdapter.d.ts.map +0 -1
- package/dist/adapters/TypeScriptAdapter.js +0 -386
- package/dist/adapters/TypeScriptAdapter.js.map +0 -1
- package/dist/adapters/UniversalAnalyzer.d.ts +0 -42
- package/dist/adapters/UniversalAnalyzer.d.ts.map +0 -1
- package/dist/adapters/UniversalAnalyzer.js +0 -103
- package/dist/adapters/UniversalAnalyzer.js.map +0 -1
- package/dist/adapters/UniversalSOLIDAnalyzer.d.ts +0 -48
- package/dist/adapters/UniversalSOLIDAnalyzer.d.ts.map +0 -1
- package/dist/adapters/UniversalSOLIDAnalyzer.js +0 -178
- package/dist/adapters/UniversalSOLIDAnalyzer.js.map +0 -1
- package/dist/adapters/solidAnalyzerCompat.d.ts +0 -21
- package/dist/adapters/solidAnalyzerCompat.d.ts.map +0 -1
- package/dist/adapters/solidAnalyzerCompat.js +0 -78
- package/dist/adapters/solidAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/dataAccessAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/dataAccessAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/dataAccessAnalyzerCompat.js +0 -59
- package/dist/analyzers/dataAccessAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/documentationAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/documentationAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/documentationAnalyzerCompat.js +0 -52
- package/dist/analyzers/documentationAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/dryAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/dryAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/dryAnalyzerCompat.js +0 -44
- package/dist/analyzers/dryAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/schemaAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/schemaAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/schemaAnalyzerCompat.js +0 -69
- package/dist/analyzers/schemaAnalyzerCompat.js.map +0 -1
- package/dist/analyzers/solidAnalyzerCompat.d.ts +0 -20
- package/dist/analyzers/solidAnalyzerCompat.d.ts.map +0 -1
- package/dist/analyzers/solidAnalyzerCompat.js +0 -78
- package/dist/analyzers/solidAnalyzerCompat.js.map +0 -1
- package/dist/codeIndexDb.d.ts.map +0 -1
- package/dist/codeIndexDb.js +0 -2102
- package/dist/codeIndexDb.js.map +0 -1
- package/dist/languages/typescript/TypeScriptAdapter.d.ts +0 -108
- package/dist/languages/typescript/TypeScriptAdapter.d.ts.map +0 -1
- package/dist/languages/typescript/TypeScriptAdapter.js +0 -567
- package/dist/languages/typescript/TypeScriptAdapter.js.map +0 -1
- package/dist/languages/typescript/test-extraction.d.ts +0 -2
- package/dist/languages/typescript/test-extraction.d.ts.map +0 -1
- package/dist/languages/typescript/test-extraction.js +0 -36
- package/dist/languages/typescript/test-extraction.js.map +0 -1
- package/dist/mcp-hybrid.d.ts +0 -10
- package/dist/mcp-hybrid.d.ts.map +0 -1
- package/dist/mcp-hybrid.js +0 -41
- package/dist/mcp-hybrid.js.map +0 -1
- package/dist/mcp-index.d.ts +0 -13
- package/dist/mcp-index.d.ts.map +0 -1
- package/dist/mcp-index.js +0 -136
- package/dist/mcp-index.js.map +0 -1
- package/dist/mcp-standalone.d.ts +0 -3
- package/dist/mcp-standalone.d.ts.map +0 -1
- package/dist/mcp-standalone.js +0 -1324
- package/dist/mcp-standalone.js.map +0 -1
- package/dist/mcp-ui-server.d.ts +0 -27
- package/dist/mcp-ui-server.d.ts.map +0 -1
- package/dist/mcp-ui-server.js +0 -484
- package/dist/mcp-ui-server.js.map +0 -1
- package/dist/test-multi-strategy-search.d.ts +0 -3
- package/dist/test-multi-strategy-search.d.ts.map +0 -1
- package/dist/test-multi-strategy-search.js +0 -170
- package/dist/test-multi-strategy-search.js.map +0 -1
package/dist/utils/astUtils.js
CHANGED
|
@@ -1,769 +1,1040 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AST Utility Functions
|
|
3
|
-
* Provides helper functions for working with
|
|
3
|
+
* Provides helper functions for working with tree-sitter AST nodes
|
|
4
|
+
*
|
|
5
|
+
* ## Migration: TypeScript Compiler API → tree-sitter
|
|
6
|
+
*
|
|
7
|
+
* All functions now use tree-sitter `ASTNode` via the `adapterBridge` facade.
|
|
8
|
+
* The `ts.SourceFile` parameter has been replaced with `ASTNode` (root node).
|
|
9
|
+
* For source-code-dependent operations, `sourceCode: string` is passed separately.
|
|
4
10
|
*/
|
|
5
|
-
import
|
|
11
|
+
import { walkAST, findNodes, getNodeText as bridgeGetNodeText, getLineAndColumn as bridgeGetLineAndColumn, isExported as bridgeIsExported, calculateComplexity as bridgeCalculateComplexity, hasModifier, } from '../languages/adapterBridge.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/** Get raw text from a tree-sitter node (stored on ASTNode.raw). */
|
|
16
|
+
function rawText(node) {
|
|
17
|
+
return node.raw?.text ?? '';
|
|
18
|
+
}
|
|
19
|
+
/** Find the first child of a given type. */
|
|
20
|
+
function findChildOfType(node, type) {
|
|
21
|
+
return node.children?.find(c => c.type === type);
|
|
22
|
+
}
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Node finding (re-exports from adapterBridge)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
6
26
|
/**
|
|
7
|
-
* Find all nodes
|
|
27
|
+
* Find all nodes matching a predicate in the AST subtree.
|
|
28
|
+
* Replacement for the old `findNodesByKind<T>(node, SyntaxKind)`.
|
|
8
29
|
*/
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
function visit(node) {
|
|
12
|
-
if (node.kind === kind) {
|
|
13
|
-
results.push(node);
|
|
14
|
-
}
|
|
15
|
-
ts.forEachChild(node, visit);
|
|
16
|
-
}
|
|
17
|
-
visit(node);
|
|
18
|
-
return results;
|
|
30
|
+
export function findNodesByType(root, predicate) {
|
|
31
|
+
return findNodes(root, predicate);
|
|
19
32
|
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Text extraction (re-exports from adapterBridge)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
20
36
|
/**
|
|
21
|
-
* Get the text content of a node
|
|
37
|
+
* Get the text content of a node.
|
|
38
|
+
* Uses sourceCode if provided; falls back to raw tree-sitter text.
|
|
22
39
|
*/
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
export { bridgeGetNodeText as getNodeText };
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Position helpers (re-exports from adapterBridge)
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
26
44
|
/**
|
|
27
|
-
* Get line and column number for a
|
|
45
|
+
* Get line and column number for a tree-sitter ASTNode.
|
|
28
46
|
*/
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
export { bridgeGetLineAndColumn as getLineAndColumn };
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Export checks (re-exports from adapterBridge)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
export { bridgeIsExported as isExported };
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Complexity (re-exports from adapterBridge)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export { bridgeCalculateComplexity as calculateComplexity };
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Import extraction
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
33
59
|
/**
|
|
34
|
-
* Extract import statements from
|
|
60
|
+
* Extract import statements from an AST root node.
|
|
61
|
+
* Uses tree-sitter import_statement structure:
|
|
62
|
+
* import_statement → import_clause? → (identifier | named_imports) → string
|
|
35
63
|
*/
|
|
36
|
-
export function getImports(
|
|
64
|
+
export function getImports(root) {
|
|
37
65
|
const imports = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
const importNodes = findNodes(root, n => n.type === 'import_statement');
|
|
67
|
+
for (const node of importNodes) {
|
|
68
|
+
// Module specifier (the string literal at the end)
|
|
69
|
+
const moduleNode = findChildOfType(node, 'string');
|
|
70
|
+
if (!moduleNode)
|
|
71
|
+
continue;
|
|
72
|
+
const moduleSpecifier = rawText(moduleNode).replace(/^["']|["']$/g, '');
|
|
73
|
+
const importedNames = [];
|
|
74
|
+
const importClause = findChildOfType(node, 'import_clause');
|
|
75
|
+
let isTypeOnly = false;
|
|
76
|
+
if (importClause) {
|
|
77
|
+
// Check for `type` keyword in import clause
|
|
78
|
+
const raw = importClause.raw;
|
|
79
|
+
isTypeOnly = raw?.children?.some(c => !c.isNamed && c.type === 'type') ?? false;
|
|
80
|
+
// Default import (identifier child of import_clause that's not 'type' modifier)
|
|
81
|
+
const defaultId = importClause.children?.find(c => c.type === 'identifier');
|
|
82
|
+
if (defaultId) {
|
|
83
|
+
importedNames.push(rawText(defaultId));
|
|
84
|
+
}
|
|
85
|
+
// Named imports
|
|
86
|
+
const namedImports = findChildOfType(importClause, 'named_imports');
|
|
87
|
+
if (namedImports) {
|
|
88
|
+
for (const child of namedImports.children ?? []) {
|
|
89
|
+
if (child.type !== 'import_specifier')
|
|
90
|
+
continue;
|
|
91
|
+
// The identifier children — last one is the local name
|
|
92
|
+
const ids = child.children?.filter(c => c.type === 'identifier') ?? [];
|
|
93
|
+
if (ids.length > 0) {
|
|
94
|
+
importedNames.push(rawText(ids[ids.length - 1]));
|
|
57
95
|
}
|
|
58
96
|
}
|
|
59
97
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
// Namespace import
|
|
99
|
+
const namespaceImport = findChildOfType(importClause, 'namespace_import');
|
|
100
|
+
if (namespaceImport) {
|
|
101
|
+
const nsId = findChildOfType(namespaceImport, 'identifier');
|
|
102
|
+
if (nsId) {
|
|
103
|
+
importedNames.push(`* as ${rawText(nsId)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
67
106
|
}
|
|
68
|
-
|
|
107
|
+
const { line } = bridgeGetLineAndColumn(node);
|
|
108
|
+
imports.push({ moduleSpecifier, importedNames, isTypeOnly, line });
|
|
109
|
+
}
|
|
69
110
|
return imports;
|
|
70
111
|
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Export extraction
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
71
115
|
/**
|
|
72
|
-
* Extract export statements from
|
|
116
|
+
* Extract export statements from an AST root node.
|
|
117
|
+
* Uses tree-sitter export_statement structure.
|
|
73
118
|
*/
|
|
74
|
-
export function getExports(
|
|
119
|
+
export function getExports(root) {
|
|
75
120
|
const exports = [];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
121
|
+
// export declarations: export { name1, name2 }
|
|
122
|
+
const exportNodes = findNodes(root, n => n.type === 'export_statement' || n.type === 'export_declaration');
|
|
123
|
+
for (const node of exportNodes) {
|
|
124
|
+
const { line } = bridgeGetLineAndColumn(node);
|
|
125
|
+
// Check for type-only exports
|
|
126
|
+
const raw = node.raw;
|
|
127
|
+
const isTypeOnly = raw?.children?.some(c => !c.isNamed && c.type === 'type') ?? false;
|
|
128
|
+
// export clause with named exports
|
|
129
|
+
const exportClause = findChildOfType(node, 'export_clause');
|
|
130
|
+
if (exportClause) {
|
|
131
|
+
const namedExports = findChildOfType(exportClause, 'named_exports');
|
|
132
|
+
if (namedExports) {
|
|
133
|
+
for (const child of namedExports.children ?? []) {
|
|
134
|
+
if (child.type !== 'export_specifier')
|
|
135
|
+
continue;
|
|
136
|
+
const ids = child.children?.filter(c => c.type === 'identifier') ?? [];
|
|
137
|
+
if (ids.length > 0) {
|
|
138
|
+
exports.push({
|
|
139
|
+
name: rawText(ids[ids.length - 1]),
|
|
140
|
+
isDefault: false,
|
|
141
|
+
isTypeOnly,
|
|
142
|
+
line
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
89
146
|
}
|
|
90
147
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
148
|
+
// Check for `default` keyword — export default X
|
|
149
|
+
const isDefault = raw?.children?.some(c => !c.isNamed && c.type === 'default') ?? false;
|
|
150
|
+
if (isDefault) {
|
|
151
|
+
const exported = node.children?.find(c => c.type === 'identifier' || c.type === 'function_declaration' ||
|
|
152
|
+
c.type === 'class_declaration' || c.type === 'call_expression');
|
|
153
|
+
if (exported) {
|
|
154
|
+
const nameNode = findChildOfType(exported, 'identifier');
|
|
155
|
+
exports.push({
|
|
156
|
+
name: nameNode ? rawText(nameNode) : 'default',
|
|
157
|
+
isDefault: true,
|
|
158
|
+
isTypeOnly,
|
|
159
|
+
line
|
|
160
|
+
});
|
|
161
|
+
}
|
|
99
162
|
}
|
|
100
|
-
}
|
|
163
|
+
}
|
|
101
164
|
return exports;
|
|
102
165
|
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Function finding
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
103
169
|
/**
|
|
104
|
-
*
|
|
170
|
+
* Find all function declarations in the AST.
|
|
105
171
|
*/
|
|
106
|
-
export function
|
|
107
|
-
return (
|
|
172
|
+
export function findFunctions(root) {
|
|
173
|
+
return findNodes(root, n => n.type === 'function_declaration');
|
|
108
174
|
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Class finding
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
109
178
|
/**
|
|
110
|
-
* Find all
|
|
179
|
+
* Find all class declarations in the AST.
|
|
111
180
|
*/
|
|
112
|
-
export function
|
|
113
|
-
return
|
|
181
|
+
export function findClasses(root) {
|
|
182
|
+
return findNodes(root, n => n.type === 'class_declaration');
|
|
114
183
|
}
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// AST node inspection
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
115
187
|
/**
|
|
116
|
-
*
|
|
117
|
-
*/
|
|
118
|
-
export function findClasses(sourceFile) {
|
|
119
|
-
return findNodesByKind(sourceFile, ts.SyntaxKind.ClassDeclaration);
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Get AST node for inspection
|
|
188
|
+
* Get AST node for inspection/debugging.
|
|
123
189
|
*/
|
|
124
190
|
export function getASTNode(node) {
|
|
125
191
|
return {
|
|
126
|
-
|
|
127
|
-
text: node
|
|
128
|
-
children: node.
|
|
192
|
+
type: node.type,
|
|
193
|
+
text: rawText(node),
|
|
194
|
+
children: (node.children ?? []).map(child => getASTNode(child))
|
|
129
195
|
};
|
|
130
196
|
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Decorator checks
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
131
200
|
/**
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
export function calculateComplexity(node) {
|
|
135
|
-
let complexity = 1; // Base complexity
|
|
136
|
-
function visit(node) {
|
|
137
|
-
switch (node.kind) {
|
|
138
|
-
case ts.SyntaxKind.IfStatement:
|
|
139
|
-
case ts.SyntaxKind.WhileStatement:
|
|
140
|
-
case ts.SyntaxKind.ForStatement:
|
|
141
|
-
case ts.SyntaxKind.ForInStatement:
|
|
142
|
-
case ts.SyntaxKind.ForOfStatement:
|
|
143
|
-
case ts.SyntaxKind.ConditionalExpression:
|
|
144
|
-
case ts.SyntaxKind.CatchClause:
|
|
145
|
-
case ts.SyntaxKind.CaseClause:
|
|
146
|
-
complexity++;
|
|
147
|
-
break;
|
|
148
|
-
case ts.SyntaxKind.BinaryExpression:
|
|
149
|
-
const binary = node;
|
|
150
|
-
if (binary.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
151
|
-
binary.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
152
|
-
complexity++;
|
|
153
|
-
}
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
ts.forEachChild(node, visit);
|
|
157
|
-
}
|
|
158
|
-
if (node.body) {
|
|
159
|
-
visit(node.body);
|
|
160
|
-
}
|
|
161
|
-
return complexity;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Check if a node has a specific decorator
|
|
201
|
+
* Check if a node has a specific decorator.
|
|
202
|
+
* Tree-sitter parses decorators as `decorator` nodes.
|
|
165
203
|
*/
|
|
166
204
|
export function hasDecorator(node, decoratorName) {
|
|
167
|
-
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
const decorators = ts.getDecorators(node);
|
|
171
|
-
if (!decorators) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
205
|
+
const decorators = findNodes(node, n => n.type === 'decorator');
|
|
174
206
|
return decorators.some(decorator => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
// decorator → call_expression → identifier (e.g., @Component())
|
|
208
|
+
const callExpr = findChildOfType(decorator, 'call_expression');
|
|
209
|
+
if (callExpr) {
|
|
210
|
+
const callee = callExpr.children?.[0];
|
|
211
|
+
if (callee?.type === 'identifier' && rawText(callee) === decoratorName) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// decorator → identifier (e.g., @deprecated)
|
|
216
|
+
const id = findChildOfType(decorator, 'identifier');
|
|
217
|
+
if (id && rawText(id) === decoratorName) {
|
|
218
|
+
return true;
|
|
178
219
|
}
|
|
179
|
-
return
|
|
180
|
-
decorator.expression.text === decoratorName;
|
|
220
|
+
return false;
|
|
181
221
|
});
|
|
182
222
|
}
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Class method extraction
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
183
226
|
/**
|
|
184
|
-
* Get method names from a class
|
|
227
|
+
* Get method names from a class declaration node.
|
|
228
|
+
* Tree-sitter: class_declaration → class_body → method_definition / public_field_definition
|
|
185
229
|
*/
|
|
186
230
|
export function getClassMethods(classNode) {
|
|
187
231
|
const methods = [];
|
|
188
|
-
classNode
|
|
189
|
-
|
|
190
|
-
|
|
232
|
+
const classBody = findChildOfType(classNode, 'class_body');
|
|
233
|
+
if (!classBody)
|
|
234
|
+
return methods;
|
|
235
|
+
for (const member of classBody.children ?? []) {
|
|
236
|
+
if (member.type === 'method_definition' || member.type === 'public_field_definition') {
|
|
237
|
+
const nameNode = member.children?.find(c => c.type === 'identifier' || c.type === 'property_identifier');
|
|
238
|
+
if (nameNode) {
|
|
239
|
+
methods.push(rawText(nameNode));
|
|
240
|
+
}
|
|
191
241
|
}
|
|
192
|
-
}
|
|
242
|
+
}
|
|
193
243
|
return methods;
|
|
194
244
|
}
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Line counting
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
195
248
|
/**
|
|
196
|
-
* Count lines of code (excluding comments and empty lines)
|
|
249
|
+
* Count lines of code (excluding comments and empty lines).
|
|
250
|
+
* Takes source text directly instead of ts.SourceFile.
|
|
197
251
|
*/
|
|
198
|
-
export function countLinesOfCode(
|
|
199
|
-
const
|
|
200
|
-
const lines = text.split('\n');
|
|
252
|
+
export function countLinesOfCode(sourceCode) {
|
|
253
|
+
const lines = sourceCode.split('\n');
|
|
201
254
|
let count = 0;
|
|
202
255
|
for (const line of lines) {
|
|
203
256
|
const trimmed = line.trim();
|
|
204
|
-
if (trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*')) {
|
|
257
|
+
if (trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.startsWith('*')) {
|
|
205
258
|
count++;
|
|
206
259
|
}
|
|
207
260
|
}
|
|
208
261
|
return count;
|
|
209
262
|
}
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Variable declaration finding
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
210
266
|
/**
|
|
211
|
-
* Find all variable declarations
|
|
267
|
+
* Find all variable declarations in the AST.
|
|
268
|
+
* Tree-sitter: variable_declarator nodes.
|
|
212
269
|
*/
|
|
213
|
-
export function findVariableDeclarations(
|
|
214
|
-
|
|
215
|
-
function visit(node) {
|
|
216
|
-
if (ts.isVariableDeclaration(node)) {
|
|
217
|
-
declarations.push(node);
|
|
218
|
-
}
|
|
219
|
-
ts.forEachChild(node, visit);
|
|
220
|
-
}
|
|
221
|
-
visit(sourceFile);
|
|
222
|
-
return declarations;
|
|
270
|
+
export function findVariableDeclarations(root) {
|
|
271
|
+
return findNodes(root, n => n.type === 'variable_declarator');
|
|
223
272
|
}
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Async function check
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
224
276
|
/**
|
|
225
|
-
* Check if a function is async
|
|
277
|
+
* Check if a function node is async.
|
|
226
278
|
*/
|
|
227
279
|
export function isAsyncFunction(node) {
|
|
228
|
-
return
|
|
280
|
+
return hasModifier(node, 'async');
|
|
229
281
|
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Parameter count
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
230
285
|
/**
|
|
231
|
-
* Get parameter count for a function
|
|
286
|
+
* Get parameter count for a function/method node.
|
|
287
|
+
* Tree-sitter: formal_parameters → required_parameter / optional_parameter.
|
|
232
288
|
*/
|
|
233
289
|
export function getParameterCount(node) {
|
|
234
|
-
|
|
290
|
+
const params = findChildOfType(node, 'formal_parameters');
|
|
291
|
+
if (!params)
|
|
292
|
+
return 0;
|
|
293
|
+
let count = 0;
|
|
294
|
+
for (const child of params.children ?? []) {
|
|
295
|
+
if (child.type === 'required_parameter' || child.type === 'optional_parameter' ||
|
|
296
|
+
child.type === 'rest_parameter') {
|
|
297
|
+
count++;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return count;
|
|
235
301
|
}
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Type alias finding
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
236
305
|
/**
|
|
237
|
-
* Find all type aliases
|
|
306
|
+
* Find all type aliases in the AST.
|
|
307
|
+
* Tree-sitter: type_alias_declaration nodes.
|
|
238
308
|
*/
|
|
239
|
-
export function findTypeAliases(
|
|
240
|
-
return
|
|
309
|
+
export function findTypeAliases(root) {
|
|
310
|
+
return findNodes(root, n => n.type === 'type_alias_declaration');
|
|
241
311
|
}
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// Interface finding
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
242
315
|
/**
|
|
243
|
-
* Find all
|
|
316
|
+
* Find all interface declarations in the AST.
|
|
317
|
+
* Tree-sitter: interface_declaration nodes.
|
|
244
318
|
*/
|
|
245
|
-
export function findInterfaces(
|
|
246
|
-
return
|
|
319
|
+
export function findInterfaces(root) {
|
|
320
|
+
return findNodes(root, n => n.type === 'interface_declaration');
|
|
247
321
|
}
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// File parsing
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
248
325
|
/**
|
|
249
|
-
* Parse a TypeScript file
|
|
326
|
+
* Parse a TypeScript file and return an AST.
|
|
327
|
+
* Uses the adapterBridge parseFile function.
|
|
250
328
|
*/
|
|
251
329
|
export async function parseTypeScriptFile(filePath) {
|
|
330
|
+
// Dynamic import to avoid circular dependency
|
|
331
|
+
const { parseFile } = await import('../languages/adapterBridge.js');
|
|
252
332
|
const fs = await import('fs').then(m => m.promises);
|
|
253
333
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
skipLibCheck: true,
|
|
260
|
-
skipDefaultLibCheck: true,
|
|
261
|
-
allowJs: true,
|
|
262
|
-
checkJs: false,
|
|
263
|
-
strict: false,
|
|
264
|
-
noResolve: true,
|
|
265
|
-
noLib: false,
|
|
266
|
-
moduleResolution: ts.ModuleResolutionKind.NodeJs
|
|
267
|
-
};
|
|
268
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
269
|
-
// For now, just return the source file without full type checking
|
|
270
|
-
// This avoids the need for a full TypeScript compiler host setup
|
|
271
|
-
const errors = [];
|
|
272
|
-
// Check for basic syntax errors by walking the AST
|
|
273
|
-
function checkSyntax(node) {
|
|
274
|
-
if (node.kind === ts.SyntaxKind.Unknown) {
|
|
275
|
-
errors.push({
|
|
276
|
-
file: sourceFile,
|
|
277
|
-
start: node.getStart(),
|
|
278
|
-
length: node.getWidth(),
|
|
279
|
-
messageText: 'Syntax error',
|
|
280
|
-
category: ts.DiagnosticCategory.Error,
|
|
281
|
-
code: 1000
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
ts.forEachChild(node, checkSyntax);
|
|
334
|
+
const ast = parseFile(filePath, content);
|
|
335
|
+
if (!ast) {
|
|
336
|
+
const failLocation = { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
|
|
337
|
+
const failAst = { root: { type: 'source_file', range: [0, 0], location: failLocation, raw: null }, language: 'unknown', filePath, errors: [{ message: 'Failed to parse file', location: failLocation, severity: 'error' }] };
|
|
338
|
+
return { ast: failAst, errors: [{ message: 'Failed to parse file', line: 0, column: 0 }] };
|
|
285
339
|
}
|
|
286
|
-
|
|
287
|
-
return { sourceFile, errors };
|
|
340
|
+
return { ast, errors: ast.errors ?? [] };
|
|
288
341
|
}
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Detailed imports
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
289
345
|
/**
|
|
290
|
-
* Enhanced version of getImports that returns detailed ImportMapping[]
|
|
346
|
+
* Enhanced version of getImports that returns detailed ImportMapping[].
|
|
347
|
+
* Uses tree-sitter import_statement traversal.
|
|
291
348
|
*/
|
|
292
|
-
export function getImportsDetailed(
|
|
349
|
+
export function getImportsDetailed(root) {
|
|
293
350
|
const imports = [];
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
351
|
+
const importNodes = findNodes(root, n => n.type === 'import_statement');
|
|
352
|
+
for (const node of importNodes) {
|
|
353
|
+
const moduleNode = findChildOfType(node, 'string');
|
|
354
|
+
if (!moduleNode)
|
|
355
|
+
continue;
|
|
356
|
+
const moduleSpecifier = rawText(moduleNode).replace(/^["']|["']$/g, '');
|
|
357
|
+
const importClause = findChildOfType(node, 'import_clause');
|
|
358
|
+
if (!importClause) {
|
|
359
|
+
// Side-effect import (no import clause)
|
|
360
|
+
imports.push({
|
|
361
|
+
localName: `[side-effect]::${moduleSpecifier}`,
|
|
362
|
+
importedName: '[side-effect]',
|
|
363
|
+
modulePath: moduleSpecifier,
|
|
364
|
+
importType: 'namespace', // side-effect imports treated as namespace
|
|
365
|
+
isTypeOnly: false
|
|
366
|
+
});
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const raw = importClause.raw;
|
|
370
|
+
const isTypeOnly = raw?.children?.some(c => !c.isNamed && c.type === 'type') ?? false;
|
|
371
|
+
// Default import
|
|
372
|
+
const defaultId = importClause.children?.find(c => c.type === 'identifier');
|
|
373
|
+
if (defaultId) {
|
|
374
|
+
imports.push({
|
|
375
|
+
localName: rawText(defaultId),
|
|
376
|
+
importedName: 'default',
|
|
377
|
+
modulePath: moduleSpecifier,
|
|
378
|
+
importType: 'default',
|
|
379
|
+
isTypeOnly: isTypeOnly || false
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
// Named imports
|
|
383
|
+
const namedImports = findChildOfType(importClause, 'named_imports');
|
|
384
|
+
if (namedImports) {
|
|
385
|
+
for (const child of namedImports.children ?? []) {
|
|
386
|
+
if (child.type !== 'import_specifier')
|
|
387
|
+
continue;
|
|
388
|
+
const identifiers = child.children?.filter(c => c.type === 'identifier') ?? [];
|
|
389
|
+
if (identifiers.length === 0)
|
|
390
|
+
continue;
|
|
391
|
+
const localName = rawText(identifiers[identifiers.length - 1]);
|
|
392
|
+
const importedName = identifiers.length > 1 ? rawText(identifiers[0]) : localName;
|
|
393
|
+
imports.push({
|
|
394
|
+
localName,
|
|
395
|
+
importedName,
|
|
396
|
+
modulePath: moduleSpecifier,
|
|
397
|
+
importType: 'named',
|
|
398
|
+
isTypeOnly: isTypeOnly || false
|
|
399
|
+
});
|
|
332
400
|
}
|
|
333
|
-
|
|
334
|
-
|
|
401
|
+
}
|
|
402
|
+
// Namespace import
|
|
403
|
+
const nsImport = findChildOfType(importClause, 'namespace_import');
|
|
404
|
+
if (nsImport) {
|
|
405
|
+
const nsId = findChildOfType(nsImport, 'identifier');
|
|
406
|
+
if (nsId) {
|
|
335
407
|
imports.push({
|
|
336
|
-
localName:
|
|
337
|
-
importedName: '
|
|
408
|
+
localName: rawText(nsId),
|
|
409
|
+
importedName: '*',
|
|
338
410
|
modulePath: moduleSpecifier,
|
|
339
|
-
importType: '
|
|
340
|
-
isTypeOnly: false
|
|
411
|
+
importType: 'namespace',
|
|
412
|
+
isTypeOnly: isTypeOnly || false
|
|
341
413
|
});
|
|
342
414
|
}
|
|
343
415
|
}
|
|
344
|
-
}
|
|
416
|
+
}
|
|
345
417
|
return imports;
|
|
346
418
|
}
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Re-exports
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
347
422
|
/**
|
|
348
|
-
* Get re-exports from a source file
|
|
423
|
+
* Get re-exports from a source file.
|
|
424
|
+
* Tree-sitter: export_statement → string (module specifier).
|
|
349
425
|
*/
|
|
350
|
-
export function getReExports(
|
|
426
|
+
export function getReExports(root) {
|
|
351
427
|
const reExports = [];
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
428
|
+
const exportNodes = findNodes(root, n => n.type === 'export_statement' || n.type === 'export_declaration');
|
|
429
|
+
for (const node of exportNodes) {
|
|
430
|
+
// Check if there's a module specifier (export { x } from './y')
|
|
431
|
+
const moduleNode = findChildOfType(node, 'string');
|
|
432
|
+
if (!moduleNode)
|
|
433
|
+
continue;
|
|
434
|
+
const moduleSpecifier = rawText(moduleNode).replace(/^["']|["']$/g, '');
|
|
435
|
+
const exportClause = findChildOfType(node, 'export_clause');
|
|
436
|
+
if (exportClause) {
|
|
437
|
+
const namedExports = findChildOfType(exportClause, 'named_exports');
|
|
438
|
+
if (namedExports) {
|
|
439
|
+
for (const child of namedExports.children ?? []) {
|
|
440
|
+
if (child.type !== 'export_specifier')
|
|
441
|
+
continue;
|
|
442
|
+
const identifiers = child.children?.filter(c => c.type === 'identifier') ?? [];
|
|
443
|
+
if (identifiers.length > 0) {
|
|
444
|
+
const name = identifiers.length > 1
|
|
445
|
+
? rawText(identifiers[0])
|
|
446
|
+
: rawText(identifiers[identifiers.length - 1]);
|
|
447
|
+
reExports.push({ name, module: moduleSpecifier });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
370
450
|
}
|
|
371
451
|
}
|
|
372
|
-
|
|
452
|
+
else if (!exportClause) {
|
|
453
|
+
// export * from './module' — no export clause
|
|
454
|
+
reExports.push({ name: '*', module: moduleSpecifier });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
373
457
|
return reExports;
|
|
374
458
|
}
|
|
459
|
+
// ---------------------------------------------------------------------------
|
|
460
|
+
// Identifier usage extraction
|
|
461
|
+
// ---------------------------------------------------------------------------
|
|
462
|
+
/**
|
|
463
|
+
* Check whether an identifier node is used in a type-only position.
|
|
464
|
+
*
|
|
465
|
+
* Tree-sitter equivalence mapping for the original TS API checks:
|
|
466
|
+
*
|
|
467
|
+
* | TS API check | Tree-sitter equivalent |
|
|
468
|
+
* |------------------------------------|----------------------------------------|
|
|
469
|
+
* | ts.isTypeNode(p) | p.type === 'type_annotation' |
|
|
470
|
+
* | ts.isTypeReferenceNode(p) | p.type === 'type_reference' |
|
|
471
|
+
* | ts.isTypeQueryNode(p) | p.type === 'typeof_expression' |
|
|
472
|
+
* | ts.isQualifiedName(p) | p.type === 'qualified_name' |
|
|
473
|
+
* | ts.isExpressionWithTypeArguments(p)| p.type === 'generic_type' |
|
|
474
|
+
* | ts.isPropertyAccessExpression(p) | p.type === 'member_expression' |
|
|
475
|
+
* | ts.isHeritageClause(p) | p.type === 'heritage_clause' |
|
|
476
|
+
* | ts.isInterfaceDeclaration(p) | p.type === 'interface_declaration' |
|
|
477
|
+
* | ts.isTypeAliasDeclaration(p) | p.type === 'type_alias_declaration' |
|
|
478
|
+
* | ts.isClassDeclaration(p) | p.type === 'class_declaration' |
|
|
479
|
+
* | ts.isTypeParameterDeclaration(p) | p.type === 'type_parameter' |
|
|
480
|
+
* | ts.isVariableDeclaration(p) | p.type === 'variable_declarator' |
|
|
481
|
+
* | ts.isAsExpression(p) | p.type === 'as_expression' |
|
|
482
|
+
* | ts.isTypeAssertionExpression(p) | p.type === 'type_assertion' |
|
|
483
|
+
* | ts.isSatisfiesExpression(p) | (not directly; check parent) |
|
|
484
|
+
* | ts.isTypeReferenceNode(p) | p.type === 'type_identifier' |
|
|
485
|
+
* | ts.isUnionTypeNode(p) | p.type === 'union_type' |
|
|
486
|
+
* | ts.isIntersectionTypeNode(p) | p.type === 'intersection_type' |
|
|
487
|
+
* | ts.isConditionalTypeNode(p) | p.type === 'conditional_type' |
|
|
488
|
+
* | ts.isMappedTypeNode(p) | p.type === 'mapped_type_clause' |
|
|
489
|
+
* | ts.isIndexSignatureDeclaration(p) | p.type === 'index_signature' |
|
|
490
|
+
* | ts.isTypePredicateNode(p) | p.type === 'type_predicate' |
|
|
491
|
+
* | ts.isMethodDeclaration(p) | p.type === 'method_definition' |
|
|
492
|
+
* | ts.isMethodSignature(p) | p.type === 'method_signature' |
|
|
493
|
+
* | ts.isPropertyDeclaration(p) | p.type === 'class_property' |
|
|
494
|
+
* | ts.isPropertySignature(p) | p.type === 'property_signature' |
|
|
495
|
+
* | ts.isGetAccessorDeclaration(p) | p.type === 'get_accessor' |
|
|
496
|
+
* | ts.isFunctionDeclaration(p) | p.type === 'function_declaration' |
|
|
497
|
+
* | ts.isArrowFunction(p) | p.type === 'arrow_function' |
|
|
498
|
+
* | ts.isFunctionExpression(p) | p.type === 'function_expression' |
|
|
499
|
+
* | ts.isParameter(p) | p.type === 'required_parameter' |
|
|
500
|
+
* | ts.isCallExpression(p) | p.type === 'call_expression' |
|
|
501
|
+
* | ts.isNewExpression(p) | p.type === 'new_expression' |
|
|
502
|
+
* | ts.isTaggedTemplateExpression(p) | p.type === 'tagged_template_literal' |
|
|
503
|
+
* | ts.isImportDeclaration(p) | p.type === 'import_statement' |
|
|
504
|
+
* | ts.isImportSpecifier(p) | p.type === 'import_specifier' |
|
|
505
|
+
* | ts.isImportClause(p) | p.type === 'import_clause' |
|
|
506
|
+
* | ts.isNamedImports(p) | p.type === 'named_imports' |
|
|
507
|
+
* | ts.isExportSpecifier(p) | p.type === 'export_specifier' |
|
|
508
|
+
* | ts.isJsxElement(p) | p.type === 'jsx_element' |
|
|
509
|
+
* | ts.isJsxSelfClosingElement(p) | p.type === 'jsx_self_closing_element' |
|
|
510
|
+
* | ts.isDecorator(p) | p.type === 'decorator' |
|
|
511
|
+
* | ts.isSpreadElement(p) | p.type === 'spread_element' |
|
|
512
|
+
* | ts.isSpreadAssignment(p) | p.type === 'spread_element' |
|
|
513
|
+
* | ts.isShorthandPropertyAssignment(p)| p.type === 'shorthand_property_identifier' |
|
|
514
|
+
* | ts.isPropertyAssignment(p) | p.type === 'pair' with 'property_identifier' child |
|
|
515
|
+
* | ts.isIdentifier(p) | p.type === 'identifier' |
|
|
516
|
+
* | ts.isElementAccessExpression(p) | p.type === 'subscript_expression' |
|
|
517
|
+
*/
|
|
518
|
+
/** Set of tree-sitter node types that represent type positions */
|
|
519
|
+
const TYPE_NODE_TYPES = new Set([
|
|
520
|
+
'type_annotation',
|
|
521
|
+
'type_identifier',
|
|
522
|
+
'generic_type',
|
|
523
|
+
'qualified_name',
|
|
524
|
+
'union_type',
|
|
525
|
+
'intersection_type',
|
|
526
|
+
'conditional_type',
|
|
527
|
+
'mapped_type_clause',
|
|
528
|
+
'index_signature',
|
|
529
|
+
'type_predicate',
|
|
530
|
+
'predefined_type',
|
|
531
|
+
'string_type',
|
|
532
|
+
'number_type',
|
|
533
|
+
'boolean_type',
|
|
534
|
+
'object_type',
|
|
535
|
+
'array_type',
|
|
536
|
+
'tuple_type',
|
|
537
|
+
'function_type',
|
|
538
|
+
'constructor_type',
|
|
539
|
+
'typeof_expression',
|
|
540
|
+
'template_type',
|
|
541
|
+
'literal_type',
|
|
542
|
+
'lookup_type',
|
|
543
|
+
'this_type',
|
|
544
|
+
'optional_type',
|
|
545
|
+
'rest_type',
|
|
546
|
+
]);
|
|
547
|
+
/** Set of declaration node types that can have heritage clauses or type params */
|
|
548
|
+
const DECLARATION_TYPES = new Set([
|
|
549
|
+
'function_declaration',
|
|
550
|
+
'method_definition',
|
|
551
|
+
'method_signature',
|
|
552
|
+
'arrow_function',
|
|
553
|
+
'function_expression',
|
|
554
|
+
'variable_declarator',
|
|
555
|
+
'class_declaration',
|
|
556
|
+
'interface_declaration',
|
|
557
|
+
'type_alias_declaration',
|
|
558
|
+
]);
|
|
559
|
+
/** Set of node types that represent spreads */
|
|
560
|
+
const SPREAD_TYPES = new Set(['spread_element', 'rest_parameter']);
|
|
561
|
+
/**
|
|
562
|
+
* Recursively check if the identifier node is contained within the given type node.
|
|
563
|
+
*/
|
|
564
|
+
function isNodeInTypePosition(identifier, typeNode) {
|
|
565
|
+
let found = false;
|
|
566
|
+
function checkNode(node) {
|
|
567
|
+
if (found)
|
|
568
|
+
return;
|
|
569
|
+
if (node === identifier) {
|
|
570
|
+
found = true;
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
for (const child of node.children ?? []) {
|
|
574
|
+
checkNode(child);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
checkNode(typeNode);
|
|
578
|
+
return found;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Check if an identifier node is used only as a type.
|
|
582
|
+
* Uses tree-sitter node type checks instead of TS API's `isTypeNode` etc.
|
|
583
|
+
*/
|
|
584
|
+
function isTypeOnlyUsage(identifier) {
|
|
585
|
+
const parent = identifier.parent;
|
|
586
|
+
if (!parent)
|
|
587
|
+
return false;
|
|
588
|
+
// Direct type position — parent is a type_annotation, type_reference, etc.
|
|
589
|
+
if (TYPE_NODE_TYPES.has(parent.type)) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
// Type query: `typeof X`
|
|
593
|
+
if (parent.type === 'typeof_expression') {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
// Qualified name in type position
|
|
597
|
+
if (parent.type === 'qualified_name' && parent.children?.[0] === identifier) {
|
|
598
|
+
return isTypeOnlyUsage(parent);
|
|
599
|
+
}
|
|
600
|
+
// Generic type arguments: `SomeType<X>` where X is the identifier
|
|
601
|
+
if (parent.type === 'generic_type' && parent.children?.[0] === identifier) {
|
|
602
|
+
const heritageClause = parent.parent;
|
|
603
|
+
if (heritageClause?.type === 'heritage_clause') {
|
|
604
|
+
const raw = heritageClause.raw;
|
|
605
|
+
const isExtends = raw?.children?.some(c => !c.isNamed && c.type === 'extends');
|
|
606
|
+
if (isExtends) {
|
|
607
|
+
const interfaceNode = heritageClause.parent;
|
|
608
|
+
if (interfaceNode?.type === 'interface_declaration') {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// `namespace.Type` in type position — check if left side is identifier
|
|
615
|
+
if (parent.type === 'member_expression' && parent.children?.[0] === identifier) {
|
|
616
|
+
const grandParent = parent.parent;
|
|
617
|
+
if (grandParent?.type === 'generic_type') {
|
|
618
|
+
const heritageClause = grandParent.parent;
|
|
619
|
+
if (heritageClause?.type === 'heritage_clause') {
|
|
620
|
+
const raw = heritageClause.raw;
|
|
621
|
+
const isExtends = raw?.children?.some(c => !c.isNamed && c.type === 'extends');
|
|
622
|
+
if (isExtends) {
|
|
623
|
+
const interfaceNode = heritageClause.parent;
|
|
624
|
+
if (interfaceNode?.type === 'interface_declaration') {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Type alias: `type X = SomeType`
|
|
632
|
+
if (parent.type === 'type_alias_declaration') {
|
|
633
|
+
const typeAnnotation = findChildOfType(parent, 'type_annotation');
|
|
634
|
+
if (typeAnnotation) {
|
|
635
|
+
return isNodeInTypePosition(identifier, typeAnnotation);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Class implements: `class X implements SomeType`
|
|
639
|
+
if (parent.type === 'class_declaration') {
|
|
640
|
+
for (const child of parent.children ?? []) {
|
|
641
|
+
if (child.type !== 'heritage_clause')
|
|
642
|
+
continue;
|
|
643
|
+
const raw = child.raw;
|
|
644
|
+
const isImplements = raw?.children?.some(c => !c.isNamed && c.type === 'implements');
|
|
645
|
+
if (!isImplements)
|
|
646
|
+
continue;
|
|
647
|
+
for (const typeNode of child.children ?? []) {
|
|
648
|
+
if (typeNode === identifier)
|
|
649
|
+
return true;
|
|
650
|
+
if (typeNode.type === 'member_expression' && typeNode.children?.[0] === identifier)
|
|
651
|
+
return true;
|
|
652
|
+
if (typeNode.type === 'generic_type' && typeNode.children?.[0] === identifier)
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// Generic constraints: `function test<T extends SomeType>()`
|
|
658
|
+
if (parent.type === 'type_parameter') {
|
|
659
|
+
const constraint = findChildOfType(parent, 'type_annotation'); // or constraint
|
|
660
|
+
if (constraint) {
|
|
661
|
+
return isNodeInTypePosition(identifier, constraint);
|
|
662
|
+
}
|
|
663
|
+
// Also check for generic_type in constraint position
|
|
664
|
+
for (const child of parent.children ?? []) {
|
|
665
|
+
if (child.type !== 'identifier' && child.type !== 'type_parameter') {
|
|
666
|
+
if (isNodeInTypePosition(identifier, child))
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// Type annotations in variable declarations: `const x: SomeType = ...`
|
|
672
|
+
if (parent.type === 'variable_declarator') {
|
|
673
|
+
const typeAnnot = findChildOfType(parent, 'type_annotation');
|
|
674
|
+
if (typeAnnot && isNodeInTypePosition(identifier, typeAnnot))
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
// Type assertions: `value as SomeType` or `<SomeType>value`
|
|
678
|
+
if (parent.type === 'as_expression') {
|
|
679
|
+
const typeNode = parent.children?.find(c => c.type !== 'identifier' && c.type !== 'as');
|
|
680
|
+
if (typeNode && isNodeInTypePosition(identifier, typeNode))
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
if (parent.type === 'type_assertion') {
|
|
684
|
+
const typeNode = findChildOfType(parent, 'type_annotation');
|
|
685
|
+
if (typeNode && isNodeInTypePosition(identifier, typeNode))
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
// Type parameters/arguments: `Array<SomeType>`, `Promise<SomeType>`
|
|
689
|
+
if (parent.type === 'type_identifier' || parent.type === 'generic_type') {
|
|
690
|
+
const typeArgs = findChildOfType(parent, 'type_arguments');
|
|
691
|
+
if (typeArgs) {
|
|
692
|
+
for (const arg of typeArgs.children ?? []) {
|
|
693
|
+
if (isNodeInTypePosition(identifier, arg))
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Return type annotations: `function test(): SomeType`
|
|
699
|
+
if (DECLARATION_TYPES.has(parent.type)) {
|
|
700
|
+
const typeAnnot = findChildOfType(parent, 'type_annotation');
|
|
701
|
+
if (typeAnnot && isNodeInTypePosition(identifier, typeAnnot))
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
// Parameter type annotations: `function test(x: SomeType)`
|
|
705
|
+
if (parent.type === 'required_parameter' || parent.type === 'optional_parameter') {
|
|
706
|
+
const typeAnnot = findChildOfType(parent, 'type_annotation');
|
|
707
|
+
if (typeAnnot && isNodeInTypePosition(identifier, typeAnnot))
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
// Property type annotations: `{ prop: SomeType }` or `class { prop: SomeType }`
|
|
711
|
+
if (parent.type === 'property_signature' || parent.type === 'class_property') {
|
|
712
|
+
const typeAnnot = findChildOfType(parent, 'type_annotation');
|
|
713
|
+
if (typeAnnot && isNodeInTypePosition(identifier, typeAnnot))
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
// Index signature: `{ [key: string]: SomeType }`
|
|
717
|
+
if (parent.type === 'index_signature') {
|
|
718
|
+
const typeAnnot = findChildOfType(parent, 'type_annotation');
|
|
719
|
+
if (typeAnnot && isNodeInTypePosition(identifier, typeAnnot))
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
// Mapped type constraint or type
|
|
723
|
+
if (parent.type === 'mapped_type_clause') {
|
|
724
|
+
const typeParam = findChildOfType(parent, 'type_parameter');
|
|
725
|
+
if (typeParam) {
|
|
726
|
+
const constraint = findChildOfType(typeParam, 'type_annotation');
|
|
727
|
+
if (constraint && isNodeInTypePosition(identifier, constraint))
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
const typeAnnot = findChildOfType(parent, 'type_annotation');
|
|
731
|
+
if (typeAnnot && isNodeInTypePosition(identifier, typeAnnot))
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
// Conditional types: `T extends SomeType ? X : Y`
|
|
735
|
+
if (parent.type === 'conditional_type') {
|
|
736
|
+
for (const child of parent.children ?? []) {
|
|
737
|
+
if (isNodeInTypePosition(identifier, child))
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Union and intersection types
|
|
742
|
+
if (parent.type === 'union_type' || parent.type === 'intersection_type') {
|
|
743
|
+
for (const child of parent.children ?? []) {
|
|
744
|
+
if (isNodeInTypePosition(identifier, child))
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Type predicate: `function isX(value: any): value is SomeType`
|
|
749
|
+
if (parent.type === 'type_predicate') {
|
|
750
|
+
const annot = findChildOfType(parent, 'type_annotation');
|
|
751
|
+
if (annot && isNodeInTypePosition(identifier, annot))
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
// Walk up: if parent is itself in a type position, check further up
|
|
755
|
+
if (parent.parent) {
|
|
756
|
+
// Heritage clauses
|
|
757
|
+
if (parent.type === 'generic_type' && parent.parent.type === 'heritage_clause') {
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
// Type arguments in call expressions: `func<SomeType>()`
|
|
761
|
+
if (parent.parent.type === 'call_expression') {
|
|
762
|
+
const typeArgs = findChildOfType(parent.parent, 'type_arguments');
|
|
763
|
+
if (typeArgs) {
|
|
764
|
+
for (const arg of typeArgs.children ?? []) {
|
|
765
|
+
if (isNodeInTypePosition(identifier, arg))
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// Type arguments in new expressions: `new Class<SomeType>()`
|
|
771
|
+
if (parent.parent.type === 'new_expression') {
|
|
772
|
+
const typeArgs = findChildOfType(parent.parent, 'type_arguments');
|
|
773
|
+
if (typeArgs) {
|
|
774
|
+
for (const arg of typeArgs.children ?? []) {
|
|
775
|
+
if (isNodeInTypePosition(identifier, arg))
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// Type arguments in tagged template expressions
|
|
781
|
+
if (parent.parent.type === 'tagged_template_literal') {
|
|
782
|
+
const typeArgs = findChildOfType(parent.parent, 'type_arguments');
|
|
783
|
+
if (typeArgs) {
|
|
784
|
+
for (const arg of typeArgs.children ?? []) {
|
|
785
|
+
if (isNodeInTypePosition(identifier, arg))
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
375
793
|
/**
|
|
376
|
-
* Extract identifier usage to track which imports are used
|
|
794
|
+
* Extract identifier usage to track which imports are used.
|
|
795
|
+
* Uses tree-sitter AST traversal with walkAST.
|
|
377
796
|
*/
|
|
378
|
-
export function extractIdentifierUsage(
|
|
797
|
+
export function extractIdentifierUsage(root, sourceCode, importNames) {
|
|
379
798
|
const usageMap = new Map();
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
if (
|
|
383
|
-
const name = node
|
|
799
|
+
walkAST(root, (node) => {
|
|
800
|
+
// --- identifiers ---
|
|
801
|
+
if (node.type === 'identifier') {
|
|
802
|
+
const name = rawText(node);
|
|
384
803
|
if (importNames.has(name)) {
|
|
385
|
-
// Check if this identifier should be counted as usage
|
|
386
804
|
let shouldCount = true;
|
|
387
|
-
// Skip if
|
|
388
|
-
let
|
|
389
|
-
while (
|
|
390
|
-
if (
|
|
391
|
-
|
|
805
|
+
// Skip if part of an import declaration
|
|
806
|
+
let p = node.parent;
|
|
807
|
+
while (p) {
|
|
808
|
+
if (p.type === 'import_statement' || p.type === 'import_specifier' ||
|
|
809
|
+
p.type === 'import_clause' || p.type === 'named_imports') {
|
|
392
810
|
shouldCount = false;
|
|
393
811
|
break;
|
|
394
812
|
}
|
|
395
|
-
|
|
813
|
+
p = p.parent;
|
|
396
814
|
}
|
|
397
|
-
// For
|
|
398
|
-
if (shouldCount &&
|
|
399
|
-
|
|
400
|
-
shouldCount = node.parent.expression === node;
|
|
815
|
+
// For member expressions, only count the leftmost (object) identifier
|
|
816
|
+
if (shouldCount && node.parent?.type === 'member_expression') {
|
|
817
|
+
shouldCount = node.parent.children?.[0] === node;
|
|
401
818
|
}
|
|
402
|
-
//
|
|
403
|
-
if (shouldCount &&
|
|
404
|
-
shouldCount = node.parent.
|
|
819
|
+
// For subscript expressions (element access), only count expression side
|
|
820
|
+
if (shouldCount && node.parent?.type === 'subscript_expression') {
|
|
821
|
+
shouldCount = node.parent.children?.[0] === node;
|
|
405
822
|
}
|
|
406
823
|
if (shouldCount) {
|
|
407
|
-
const { line } =
|
|
824
|
+
const { line } = bridgeGetLineAndColumn(node);
|
|
408
825
|
const existing = usageMap.get(name) || {
|
|
409
826
|
usageType: 'direct',
|
|
410
827
|
usageCount: 0,
|
|
411
|
-
lineNumbers: []
|
|
828
|
+
lineNumbers: [],
|
|
412
829
|
};
|
|
413
830
|
existing.usageCount++;
|
|
414
|
-
existing.lineNumbers.push(line
|
|
415
|
-
// Determine usage type - comprehensive type usage detection
|
|
831
|
+
existing.lineNumbers.push(line);
|
|
416
832
|
if (isTypeOnlyUsage(node)) {
|
|
417
833
|
existing.usageType = 'type';
|
|
418
834
|
}
|
|
419
|
-
else if (
|
|
835
|
+
else if (node.parent?.type === 'export_specifier') {
|
|
420
836
|
existing.usageType = 'reexport';
|
|
421
837
|
}
|
|
422
838
|
usageMap.set(name, existing);
|
|
423
839
|
}
|
|
424
840
|
}
|
|
425
841
|
}
|
|
426
|
-
//
|
|
427
|
-
else if (
|
|
428
|
-
const expr = node.
|
|
429
|
-
if (
|
|
430
|
-
const { line } =
|
|
431
|
-
const existing = usageMap.get(expr
|
|
842
|
+
// --- spread elements ---
|
|
843
|
+
else if (SPREAD_TYPES.has(node.type)) {
|
|
844
|
+
const expr = node.children?.find(c => c.type !== '...');
|
|
845
|
+
if (expr?.type === 'identifier' && importNames.has(rawText(expr))) {
|
|
846
|
+
const { line } = bridgeGetLineAndColumn(expr);
|
|
847
|
+
const existing = usageMap.get(rawText(expr)) || {
|
|
432
848
|
usageType: 'direct',
|
|
433
849
|
usageCount: 0,
|
|
434
|
-
lineNumbers: []
|
|
850
|
+
lineNumbers: [],
|
|
435
851
|
};
|
|
436
852
|
existing.usageCount++;
|
|
437
|
-
existing.lineNumbers.push(line
|
|
438
|
-
usageMap.set(expr
|
|
853
|
+
existing.lineNumbers.push(line);
|
|
854
|
+
usageMap.set(rawText(expr), existing);
|
|
439
855
|
}
|
|
440
856
|
}
|
|
441
|
-
//
|
|
442
|
-
else if (
|
|
443
|
-
|
|
444
|
-
if (
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
existing.lineNumbers.push(line + 1);
|
|
453
|
-
usageMap.set(tagName.text, existing);
|
|
857
|
+
// --- JSX elements: <Button /> or <Button.Primary /> ---
|
|
858
|
+
else if (node.type === 'jsx_element' || node.type === 'jsx_self_closing_element') {
|
|
859
|
+
let tagNameNode;
|
|
860
|
+
if (node.type === 'jsx_element') {
|
|
861
|
+
const openTag = findChildOfType(node, 'open_tag');
|
|
862
|
+
if (openTag) {
|
|
863
|
+
tagNameNode = openTag.children?.find(c => c.type === 'identifier' || c.type === 'member_expression');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
tagNameNode = node.children?.find(c => c.type === 'identifier' || c.type === 'member_expression');
|
|
454
868
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
const existing = usageMap.get(tagName.expression.text) || {
|
|
869
|
+
if (tagNameNode) {
|
|
870
|
+
if (tagNameNode.type === 'identifier' && importNames.has(rawText(tagNameNode))) {
|
|
871
|
+
const { line } = bridgeGetLineAndColumn(tagNameNode);
|
|
872
|
+
const existing = usageMap.get(rawText(tagNameNode)) || {
|
|
460
873
|
usageType: 'direct',
|
|
461
874
|
usageCount: 0,
|
|
462
|
-
lineNumbers: []
|
|
875
|
+
lineNumbers: [],
|
|
463
876
|
};
|
|
464
877
|
existing.usageCount++;
|
|
465
|
-
existing.lineNumbers.push(line
|
|
466
|
-
usageMap.set(
|
|
878
|
+
existing.lineNumbers.push(line);
|
|
879
|
+
usageMap.set(rawText(tagNameNode), existing);
|
|
880
|
+
}
|
|
881
|
+
else if (tagNameNode.type === 'member_expression') {
|
|
882
|
+
const leftmost = tagNameNode.children?.[0];
|
|
883
|
+
if (leftmost?.type === 'identifier' && importNames.has(rawText(leftmost))) {
|
|
884
|
+
const { line } = bridgeGetLineAndColumn(leftmost);
|
|
885
|
+
const existing = usageMap.get(rawText(leftmost)) || {
|
|
886
|
+
usageType: 'direct',
|
|
887
|
+
usageCount: 0,
|
|
888
|
+
lineNumbers: [],
|
|
889
|
+
};
|
|
890
|
+
existing.usageCount++;
|
|
891
|
+
existing.lineNumbers.push(line);
|
|
892
|
+
usageMap.set(rawText(leftmost), existing);
|
|
893
|
+
}
|
|
467
894
|
}
|
|
468
895
|
}
|
|
469
896
|
}
|
|
470
|
-
//
|
|
471
|
-
else if (
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
897
|
+
// --- decorators: @withAuth ---
|
|
898
|
+
else if (node.type === 'decorator') {
|
|
899
|
+
// decorator → identifier (e.g., @deprecated)
|
|
900
|
+
const id = findChildOfType(node, 'identifier');
|
|
901
|
+
if (id && importNames.has(rawText(id))) {
|
|
902
|
+
const { line } = bridgeGetLineAndColumn(id);
|
|
903
|
+
const existing = usageMap.get(rawText(id)) || {
|
|
476
904
|
usageType: 'direct',
|
|
477
905
|
usageCount: 0,
|
|
478
|
-
lineNumbers: []
|
|
906
|
+
lineNumbers: [],
|
|
479
907
|
};
|
|
480
908
|
existing.usageCount++;
|
|
481
|
-
existing.lineNumbers.push(line
|
|
482
|
-
usageMap.set(
|
|
909
|
+
existing.lineNumbers.push(line);
|
|
910
|
+
usageMap.set(rawText(id), existing);
|
|
483
911
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
912
|
+
// decorator → call_expression → identifier (e.g., @Component())
|
|
913
|
+
const callExpr = findChildOfType(node, 'call_expression');
|
|
914
|
+
if (callExpr) {
|
|
915
|
+
const callee = callExpr.children?.[0];
|
|
916
|
+
if (callee?.type === 'identifier' && importNames.has(rawText(callee))) {
|
|
917
|
+
const { line } = bridgeGetLineAndColumn(callee);
|
|
918
|
+
const existing = usageMap.get(rawText(callee)) || {
|
|
919
|
+
usageType: 'direct',
|
|
920
|
+
usageCount: 0,
|
|
921
|
+
lineNumbers: [],
|
|
922
|
+
};
|
|
923
|
+
existing.usageCount++;
|
|
924
|
+
existing.lineNumbers.push(line);
|
|
925
|
+
usageMap.set(rawText(callee), existing);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// --- Object literal property assignments: { key: ImportedValue } ---
|
|
930
|
+
else if (node.type === 'pair') {
|
|
931
|
+
const key = node.children?.find(c => c.type === 'property_identifier');
|
|
932
|
+
const value = node.children?.find(c => c.type === 'identifier' && c !== key);
|
|
933
|
+
if (value && importNames.has(rawText(value))) {
|
|
934
|
+
const { line } = bridgeGetLineAndColumn(value);
|
|
935
|
+
const existing = usageMap.get(rawText(value)) || {
|
|
488
936
|
usageType: 'direct',
|
|
489
937
|
usageCount: 0,
|
|
490
|
-
lineNumbers: []
|
|
938
|
+
lineNumbers: [],
|
|
491
939
|
};
|
|
492
940
|
existing.usageCount++;
|
|
493
|
-
existing.lineNumbers.push(line
|
|
494
|
-
usageMap.set(
|
|
941
|
+
existing.lineNumbers.push(line);
|
|
942
|
+
usageMap.set(rawText(value), existing);
|
|
495
943
|
}
|
|
496
944
|
}
|
|
497
|
-
//
|
|
498
|
-
else if (
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
945
|
+
// --- Shorthand property assignments: { ComponentA, ComponentB } ---
|
|
946
|
+
else if (node.type === 'shorthand_property_identifier') {
|
|
947
|
+
// The value node is a reference to an imported name
|
|
948
|
+
const ref = node.children?.find(c => c.type === 'identifier');
|
|
949
|
+
if (ref && importNames.has(rawText(ref))) {
|
|
950
|
+
const { line } = bridgeGetLineAndColumn(ref);
|
|
951
|
+
const existing = usageMap.get(rawText(ref)) || {
|
|
503
952
|
usageType: 'direct',
|
|
504
953
|
usageCount: 0,
|
|
505
|
-
lineNumbers: []
|
|
954
|
+
lineNumbers: [],
|
|
506
955
|
};
|
|
507
956
|
existing.usageCount++;
|
|
508
|
-
existing.lineNumbers.push(line
|
|
509
|
-
usageMap.set(
|
|
957
|
+
existing.lineNumbers.push(line);
|
|
958
|
+
usageMap.set(rawText(ref), existing);
|
|
510
959
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(node.name.getStart());
|
|
516
|
-
const existing = usageMap.get(node.name.text) || {
|
|
960
|
+
else if (ref && importNames.has(rawText(ref))) {
|
|
961
|
+
// shorthand_property_identifier might itself be just text
|
|
962
|
+
const { line } = bridgeGetLineAndColumn(node);
|
|
963
|
+
const existing = usageMap.get(rawText(node)) || {
|
|
517
964
|
usageType: 'direct',
|
|
518
965
|
usageCount: 0,
|
|
519
|
-
lineNumbers: []
|
|
966
|
+
lineNumbers: [],
|
|
520
967
|
};
|
|
521
968
|
existing.usageCount++;
|
|
522
|
-
existing.lineNumbers.push(line
|
|
523
|
-
usageMap.set(node
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
ts.forEachChild(node, visit);
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Comprehensive check for type-only usage patterns
|
|
530
|
-
*/
|
|
531
|
-
function isTypeOnlyUsage(node) {
|
|
532
|
-
let parent = node.parent;
|
|
533
|
-
// Direct type node checks
|
|
534
|
-
if (ts.isTypeNode(parent) || ts.isTypeReferenceNode(parent)) {
|
|
535
|
-
return true;
|
|
536
|
-
}
|
|
537
|
-
// Type query (typeof X)
|
|
538
|
-
if (ts.isTypeQueryNode(parent)) {
|
|
539
|
-
return true;
|
|
540
|
-
}
|
|
541
|
-
// Qualified name in type position
|
|
542
|
-
if (ts.isQualifiedName(parent) && parent.left === node) {
|
|
543
|
-
return isTypeOnlyUsage(parent);
|
|
544
|
-
}
|
|
545
|
-
// Interface extension: interface X extends BaseType
|
|
546
|
-
// Check if this identifier is used in an interface extends clause
|
|
547
|
-
if (ts.isExpressionWithTypeArguments(parent) && parent.expression === node) {
|
|
548
|
-
const heritageClause = parent.parent;
|
|
549
|
-
if (ts.isHeritageClause(heritageClause) && heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
550
|
-
const interfaceDecl = heritageClause.parent;
|
|
551
|
-
if (ts.isInterfaceDeclaration(interfaceDecl)) {
|
|
552
|
-
return true;
|
|
553
|
-
}
|
|
969
|
+
existing.lineNumbers.push(line);
|
|
970
|
+
usageMap.set(rawText(node), existing);
|
|
554
971
|
}
|
|
555
972
|
}
|
|
556
|
-
|
|
557
|
-
if (ts.isPropertyAccessExpression(parent) && parent.expression === node) {
|
|
558
|
-
const grandParent = parent.parent;
|
|
559
|
-
if (ts.isExpressionWithTypeArguments(grandParent) && grandParent.expression === parent) {
|
|
560
|
-
const heritageClause = grandParent.parent;
|
|
561
|
-
if (ts.isHeritageClause(heritageClause) && heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
562
|
-
const interfaceDecl = heritageClause.parent;
|
|
563
|
-
if (ts.isInterfaceDeclaration(interfaceDecl)) {
|
|
564
|
-
return true;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
// Type alias: type X = BaseType | BaseType & Other
|
|
570
|
-
if (ts.isTypeAliasDeclaration(parent) && parent.type) {
|
|
571
|
-
return isNodeInTypePosition(node, parent.type);
|
|
572
|
-
}
|
|
573
|
-
// Class implements: class X implements BaseType
|
|
574
|
-
if (ts.isClassDeclaration(parent) && parent.heritageClauses) {
|
|
575
|
-
for (const clause of parent.heritageClauses) {
|
|
576
|
-
if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
|
|
577
|
-
for (const type of clause.types) {
|
|
578
|
-
if (type.expression === node ||
|
|
579
|
-
(ts.isPropertyAccessExpression(type.expression) && type.expression.expression === node)) {
|
|
580
|
-
return true;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
// Generic constraints: function test<T extends BaseType>()
|
|
587
|
-
if (ts.isTypeParameterDeclaration(parent) && parent.constraint) {
|
|
588
|
-
return isNodeInTypePosition(node, parent.constraint);
|
|
589
|
-
}
|
|
590
|
-
// Type annotations in variable declarations: const x: BaseType = ...
|
|
591
|
-
if (ts.isVariableDeclaration(parent) && parent.type) {
|
|
592
|
-
return isNodeInTypePosition(node, parent.type);
|
|
593
|
-
}
|
|
594
|
-
// Type assertions: value as BaseType or <BaseType>value
|
|
595
|
-
if (ts.isAsExpression(parent) && parent.type) {
|
|
596
|
-
return isNodeInTypePosition(node, parent.type);
|
|
597
|
-
}
|
|
598
|
-
if (ts.isTypeAssertionExpression(parent) && parent.type) {
|
|
599
|
-
return isNodeInTypePosition(node, parent.type);
|
|
600
|
-
}
|
|
601
|
-
// Satisfies expressions: expression satisfies Type
|
|
602
|
-
if (ts.isSatisfiesExpression && ts.isSatisfiesExpression(parent) && parent.type) {
|
|
603
|
-
return isNodeInTypePosition(node, parent.type);
|
|
604
|
-
}
|
|
605
|
-
// Type parameters/arguments: Array<BaseType>, Promise<BaseType>
|
|
606
|
-
if (ts.isTypeReferenceNode(parent) && parent.typeArguments) {
|
|
607
|
-
for (const arg of parent.typeArguments) {
|
|
608
|
-
if (isNodeInTypePosition(node, arg)) {
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
// Return type annotations: function test(): BaseType
|
|
614
|
-
if ((ts.isFunctionDeclaration(parent) ||
|
|
615
|
-
ts.isMethodDeclaration(parent) ||
|
|
616
|
-
ts.isArrowFunction(parent) ||
|
|
617
|
-
ts.isFunctionExpression(parent) ||
|
|
618
|
-
ts.isGetAccessorDeclaration(parent) ||
|
|
619
|
-
ts.isMethodSignature(parent)) &&
|
|
620
|
-
parent.type) {
|
|
621
|
-
return isNodeInTypePosition(node, parent.type);
|
|
622
|
-
}
|
|
623
|
-
// Parameter type annotations: function test(x: BaseType)
|
|
624
|
-
if (ts.isParameter(parent) && parent.type) {
|
|
625
|
-
return isNodeInTypePosition(node, parent.type);
|
|
626
|
-
}
|
|
627
|
-
// Property type annotations: { prop: BaseType } or class { prop: BaseType }
|
|
628
|
-
if ((ts.isPropertyDeclaration(parent) ||
|
|
629
|
-
ts.isPropertySignature(parent)) &&
|
|
630
|
-
parent.type) {
|
|
631
|
-
return isNodeInTypePosition(node, parent.type);
|
|
632
|
-
}
|
|
633
|
-
// Index signature types: { [key: string]: BaseType }
|
|
634
|
-
if (ts.isIndexSignatureDeclaration(parent) && parent.type) {
|
|
635
|
-
return isNodeInTypePosition(node, parent.type);
|
|
636
|
-
}
|
|
637
|
-
// Mapped type constraint or type: { [K in keyof BaseType]: ... }
|
|
638
|
-
if (ts.isMappedTypeNode(parent)) {
|
|
639
|
-
if (parent.typeParameter && parent.typeParameter.constraint) {
|
|
640
|
-
return isNodeInTypePosition(node, parent.typeParameter.constraint);
|
|
641
|
-
}
|
|
642
|
-
if (parent.type) {
|
|
643
|
-
return isNodeInTypePosition(node, parent.type);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// Conditional types: T extends BaseType ? X : Y
|
|
647
|
-
if (ts.isConditionalTypeNode(parent)) {
|
|
648
|
-
return isNodeInTypePosition(node, parent.checkType) ||
|
|
649
|
-
isNodeInTypePosition(node, parent.extendsType) ||
|
|
650
|
-
isNodeInTypePosition(node, parent.trueType) ||
|
|
651
|
-
isNodeInTypePosition(node, parent.falseType);
|
|
652
|
-
}
|
|
653
|
-
// Union and intersection types: BaseType | Other, BaseType & Other
|
|
654
|
-
if (ts.isUnionTypeNode(parent) || ts.isIntersectionTypeNode(parent)) {
|
|
655
|
-
for (const type of parent.types) {
|
|
656
|
-
if (isNodeInTypePosition(node, type)) {
|
|
657
|
-
return true;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
// Type predicate: function isX(value: any): value is BaseType
|
|
662
|
-
if (ts.isTypePredicateNode(parent) && parent.type) {
|
|
663
|
-
return isNodeInTypePosition(node, parent.type);
|
|
664
|
-
}
|
|
665
|
-
// Check if we need to traverse up the tree
|
|
666
|
-
if (parent.parent) {
|
|
667
|
-
// For nested type structures, check if parent is in type position
|
|
668
|
-
if (ts.isTypeNode(parent)) {
|
|
669
|
-
return true;
|
|
670
|
-
}
|
|
671
|
-
// Check grandparent for certain patterns
|
|
672
|
-
const grandparent = parent.parent;
|
|
673
|
-
// Heritage clauses (extends/implements) with property access
|
|
674
|
-
if (ts.isExpressionWithTypeArguments(parent) &&
|
|
675
|
-
ts.isHeritageClause(grandparent)) {
|
|
676
|
-
return true;
|
|
677
|
-
}
|
|
678
|
-
// Type arguments in call expressions: func<BaseType>()
|
|
679
|
-
if (ts.isCallExpression(grandparent) &&
|
|
680
|
-
grandparent.typeArguments) {
|
|
681
|
-
for (const arg of grandparent.typeArguments) {
|
|
682
|
-
if (isNodeInTypePosition(node, arg)) {
|
|
683
|
-
return true;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
// Type arguments in new expressions: new Class<BaseType>()
|
|
688
|
-
if (ts.isNewExpression(grandparent) &&
|
|
689
|
-
grandparent.typeArguments) {
|
|
690
|
-
for (const arg of grandparent.typeArguments) {
|
|
691
|
-
if (isNodeInTypePosition(node, arg)) {
|
|
692
|
-
return true;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
// Tagged template type arguments: tag<BaseType>`...`
|
|
697
|
-
if (ts.isTaggedTemplateExpression(grandparent) &&
|
|
698
|
-
grandparent.typeArguments) {
|
|
699
|
-
for (const arg of grandparent.typeArguments) {
|
|
700
|
-
if (isNodeInTypePosition(node, arg)) {
|
|
701
|
-
return true;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Helper to check if a node is within a type node
|
|
710
|
-
*/
|
|
711
|
-
function isNodeInTypePosition(identifier, typeNode) {
|
|
712
|
-
let found = false;
|
|
713
|
-
function checkNode(node) {
|
|
714
|
-
if (found)
|
|
715
|
-
return;
|
|
716
|
-
if (node === identifier) {
|
|
717
|
-
found = true;
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
ts.forEachChild(node, checkNode);
|
|
721
|
-
}
|
|
722
|
-
checkNode(typeNode);
|
|
723
|
-
return found;
|
|
724
|
-
}
|
|
725
|
-
visit(node);
|
|
973
|
+
});
|
|
726
974
|
return usageMap;
|
|
727
975
|
}
|
|
976
|
+
// ---------------------------------------------------------------------------
|
|
977
|
+
// Local function check
|
|
978
|
+
// ---------------------------------------------------------------------------
|
|
728
979
|
/**
|
|
729
|
-
* Check if a function name is defined locally in the file
|
|
980
|
+
* Check if a function name is defined locally in the file.
|
|
981
|
+
* Uses tree-sitter instead of TS API.
|
|
730
982
|
*/
|
|
731
|
-
export function isLocalFunction(name,
|
|
983
|
+
export function isLocalFunction(name, root) {
|
|
732
984
|
let found = false;
|
|
733
|
-
|
|
985
|
+
walkAST(root, (node) => {
|
|
734
986
|
if (found)
|
|
735
987
|
return;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
decl.name.text === name && decl.initializer &&
|
|
743
|
-
(ts.isFunctionExpression(decl.initializer) || ts.isArrowFunction(decl.initializer))) {
|
|
744
|
-
found = true;
|
|
745
|
-
}
|
|
746
|
-
});
|
|
988
|
+
// function declarations
|
|
989
|
+
if (node.type === 'function_declaration') {
|
|
990
|
+
const nameNode = findChildOfType(node, 'identifier');
|
|
991
|
+
if (nameNode && rawText(nameNode) === name) {
|
|
992
|
+
found = true;
|
|
993
|
+
}
|
|
747
994
|
}
|
|
748
|
-
|
|
749
|
-
|
|
995
|
+
// variable declarations with arrow functions or function expressions
|
|
996
|
+
if (node.type === 'variable_declarator') {
|
|
997
|
+
const nameNode = findChildOfType(node, 'identifier');
|
|
998
|
+
const initializer = node.children?.find(c => c.type === 'arrow_function' || c.type === 'function_expression');
|
|
999
|
+
if (nameNode && initializer && rawText(nameNode) === name) {
|
|
1000
|
+
found = true;
|
|
1001
|
+
}
|
|
750
1002
|
}
|
|
751
|
-
|
|
752
|
-
|
|
1003
|
+
// class declarations
|
|
1004
|
+
if (node.type === 'class_declaration') {
|
|
1005
|
+
const nameNode = findChildOfType(node, 'identifier');
|
|
1006
|
+
if (nameNode && rawText(nameNode) === name) {
|
|
1007
|
+
found = true;
|
|
1008
|
+
}
|
|
753
1009
|
}
|
|
754
|
-
}
|
|
755
|
-
visit(sourceFile);
|
|
1010
|
+
});
|
|
756
1011
|
return found;
|
|
757
1012
|
}
|
|
1013
|
+
// ---------------------------------------------------------------------------
|
|
1014
|
+
// Call target normalization
|
|
1015
|
+
// ---------------------------------------------------------------------------
|
|
758
1016
|
/**
|
|
759
|
-
* Normalize a function call target for consistent naming
|
|
1017
|
+
* Normalize a function call target for consistent naming.
|
|
1018
|
+
* No TypeScript dependency — pure string manipulation.
|
|
760
1019
|
*/
|
|
761
1020
|
export function normalizeCallTarget(callee, filePath) {
|
|
762
|
-
// If it already has a module path separator, return as-is
|
|
763
1021
|
if (callee.includes('#') || callee.includes('.')) {
|
|
764
1022
|
return callee;
|
|
765
1023
|
}
|
|
766
|
-
// Otherwise, prefix with file path
|
|
767
1024
|
return `${filePath}#${callee}`;
|
|
768
1025
|
}
|
|
1026
|
+
// ---------------------------------------------------------------------------
|
|
1027
|
+
// findNodesByKind — backwards compatible alias
|
|
1028
|
+
// ---------------------------------------------------------------------------
|
|
1029
|
+
/**
|
|
1030
|
+
* Backward-compatible findNodesByKind.
|
|
1031
|
+
* For code that used `findNodesByKind<ts.CallExpression>(sourceFile, ts.SyntaxKind.CallExpression)`,
|
|
1032
|
+
* the equivalent is `findNodesByKind(root, 'call_expression')`.
|
|
1033
|
+
*
|
|
1034
|
+
* Note: the type parameter is retained only for backward compatibility with
|
|
1035
|
+
* the generic-based call pattern; tree-sitter uses string types, not SyntaxKind enums.
|
|
1036
|
+
*/
|
|
1037
|
+
export function findNodesByKind(root, nodeType) {
|
|
1038
|
+
return findNodes(root, n => n.type === nodeType);
|
|
1039
|
+
}
|
|
769
1040
|
//# sourceMappingURL=astUtils.js.map
|