code-auditor-mcp 2.6.2 → 3.0.1
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 +23 -196
- 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 +15 -6
- 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/functionScanner.js
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Function Scanner
|
|
3
3
|
* Scans directories for functions using AST parsing
|
|
4
|
+
*
|
|
5
|
+
* ## Migration: TypeScript Compiler API → tree-sitter
|
|
6
|
+
*
|
|
7
|
+
* All `ts.Node`/`ts.SourceFile`/`ts.SyntaxKind` usage replaced with tree-sitter `ASTNode`
|
|
8
|
+
* and adapterBridge utilities. The old `import * as ts from 'typescript'` is removed.
|
|
4
9
|
*/
|
|
5
10
|
import { discoverFiles } from './utils/fileDiscovery.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { getImports, getImportsDetailed, extractIdentifierUsage, getReExports } from './utils/astUtils.js';
|
|
11
|
+
import { findNodesByKind, getLineAndColumn, getImports, getImportsDetailed, extractIdentifierUsage, getReExports } from './utils/astUtils.js';
|
|
12
|
+
import { parseFile, walkAST, hasModifier, isExported } from './languages/adapterBridge.js';
|
|
9
13
|
import { isReactComponent, detectComponentType, getComponentName, extractHooks, extractPropTypes } from './utils/reactDetection.js';
|
|
10
14
|
import { buildImportMap, extractFunctionCalls, getLocalFunctionNames, normalizeCallTarget } from './utils/dependencyExtractor.js';
|
|
11
|
-
import
|
|
15
|
+
import { readFile } from 'fs/promises';
|
|
12
16
|
import path from 'path';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helpers
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
/** Get raw text from a tree-sitter node (stored on ASTNode.raw). */
|
|
21
|
+
function rawText(node) {
|
|
22
|
+
return node.raw?.text ?? '';
|
|
23
|
+
}
|
|
24
|
+
/** Find the first child of a given type. */
|
|
25
|
+
function findChildOfType(node, type) {
|
|
26
|
+
return node.children?.find(c => c.type === type);
|
|
27
|
+
}
|
|
28
|
+
/** Check if a node type is a variable/lexical declaration. */
|
|
29
|
+
function isVariableDecl(type) {
|
|
30
|
+
return type === 'lexical_declaration' || type === 'variable_declaration';
|
|
31
|
+
}
|
|
13
32
|
/**
|
|
14
33
|
* Scan directory for functions
|
|
15
34
|
*/
|
|
@@ -47,27 +66,27 @@ export async function scanDirectoryForFunctions(dirPath, options) {
|
|
|
47
66
|
*/
|
|
48
67
|
export async function extractFunctionsFromFile(filePath, options) {
|
|
49
68
|
const functions = [];
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
|
|
69
|
+
// Read content and parse
|
|
70
|
+
const content = await readFile(filePath, 'utf-8');
|
|
71
|
+
const ast = parseFile(filePath, content);
|
|
72
|
+
if (!ast)
|
|
53
73
|
return functions;
|
|
54
|
-
|
|
55
|
-
const sourceFile = parseResult.sourceFile;
|
|
74
|
+
const root = ast.root;
|
|
56
75
|
// Get file dependencies
|
|
57
|
-
const imports = getImports(
|
|
76
|
+
const imports = getImports(root);
|
|
58
77
|
const dependencies = imports
|
|
59
78
|
.map(imp => imp.moduleSpecifier)
|
|
60
79
|
.filter(spec => !spec.startsWith('.') && !spec.startsWith('/'))
|
|
61
80
|
.filter((v, i, a) => a.indexOf(v) === i); // Unique only
|
|
62
81
|
// Build import map for dependency tracking
|
|
63
|
-
const importMap = buildImportMap(
|
|
64
|
-
const detailedImports = getImportsDetailed(
|
|
65
|
-
const localFunctions = getLocalFunctionNames(
|
|
82
|
+
const importMap = buildImportMap(root);
|
|
83
|
+
const detailedImports = getImportsDetailed(root);
|
|
84
|
+
const localFunctions = getLocalFunctionNames(root);
|
|
66
85
|
// Track import usage across the file
|
|
67
86
|
const importNames = new Set(detailedImports.map(imp => imp.localName));
|
|
68
|
-
const fileUsageMap = extractIdentifierUsage(
|
|
87
|
+
const fileUsageMap = extractIdentifierUsage(root, content, importNames);
|
|
69
88
|
// Track re-exports - these imports are used even if not referenced in code
|
|
70
|
-
const reExports = getReExports(
|
|
89
|
+
const reExports = getReExports(root);
|
|
71
90
|
for (const reExport of reExports) {
|
|
72
91
|
// Find imports that match re-exported names
|
|
73
92
|
for (const imp of detailedImports) {
|
|
@@ -91,260 +110,263 @@ export async function extractFunctionsFromFile(filePath, options) {
|
|
|
91
110
|
}
|
|
92
111
|
}
|
|
93
112
|
// Find all function declarations
|
|
94
|
-
const functionDeclarations = findNodesByKind(
|
|
113
|
+
const functionDeclarations = findNodesByKind(root, 'function_declaration');
|
|
95
114
|
for (const func of functionDeclarations) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
115
|
+
const nameNode = findChildOfType(func, 'identifier');
|
|
116
|
+
if (!nameNode)
|
|
117
|
+
continue;
|
|
118
|
+
const { line } = getLineAndColumn(func);
|
|
119
|
+
// Extract function calls
|
|
120
|
+
const body = findChildOfType(func, 'statement_block');
|
|
121
|
+
const functionCalls = body ? extractFunctionCalls(body, content, importMap) : [];
|
|
122
|
+
const normalizedCalls = functionCalls.map(call => normalizeCallTarget(call.callee, filePath, localFunctions));
|
|
123
|
+
// Track which imports this function uses
|
|
124
|
+
const functionUsageMap = extractIdentifierUsage(func, content, importNames);
|
|
125
|
+
const usedImports = Array.from(functionUsageMap.keys());
|
|
126
|
+
// Apply unused imports configuration
|
|
127
|
+
const config = options?.unusedImportsConfig;
|
|
128
|
+
let unusedImports = detailedImports
|
|
129
|
+
.filter(imp => {
|
|
130
|
+
// Skip side-effect imports - they're never "unused"
|
|
131
|
+
if (imp.importType === 'side-effect')
|
|
132
|
+
return false;
|
|
133
|
+
// Check if import is used in this function OR at module level
|
|
134
|
+
if (functionUsageMap.has(imp.localName) || fileUsageMap.has(imp.localName))
|
|
135
|
+
return false;
|
|
136
|
+
// Apply type-only configuration
|
|
137
|
+
if (!config?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
138
|
+
return false;
|
|
139
|
+
// Apply ignore patterns
|
|
140
|
+
if (config?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
141
|
+
return false;
|
|
142
|
+
return true;
|
|
143
|
+
})
|
|
144
|
+
.map(imp => imp.localName);
|
|
145
|
+
// Get parameter count from formal_parameters
|
|
146
|
+
const params = findChildOfType(func, 'formal_parameters');
|
|
147
|
+
const paramCount = params?.children?.filter(c => c.type === 'required_parameter' || c.type === 'optional_parameter' || c.type === 'rest_parameter').length ?? 0;
|
|
148
|
+
functions.push({
|
|
149
|
+
name: rawText(nameNode),
|
|
150
|
+
filePath,
|
|
151
|
+
lineNumber: line,
|
|
152
|
+
language: getLanguageFromPath(filePath),
|
|
153
|
+
dependencies,
|
|
154
|
+
purpose: `Function ${rawText(nameNode)} implementation`,
|
|
155
|
+
context: `Located in ${path.basename(filePath)}`,
|
|
156
|
+
metadata: {
|
|
157
|
+
kind: 'function',
|
|
158
|
+
isAsync: hasModifier(func, 'async'),
|
|
159
|
+
isExported: isExported(func),
|
|
160
|
+
parameterCount: paramCount,
|
|
161
|
+
functionCalls: normalizedCalls,
|
|
162
|
+
usedImports,
|
|
163
|
+
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
164
|
+
body: body ? rawText(body) : undefined
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Find arrow functions assigned to variables
|
|
169
|
+
const varStmts = [
|
|
170
|
+
...findNodesByKind(root, 'lexical_declaration'),
|
|
171
|
+
...findNodesByKind(root, 'variable_declaration')
|
|
172
|
+
];
|
|
173
|
+
for (const varStmt of varStmts) {
|
|
174
|
+
for (const varDecl of varStmt.children ?? []) {
|
|
175
|
+
if (varDecl.type !== 'variable_declarator')
|
|
176
|
+
continue;
|
|
177
|
+
const nameNode = findChildOfType(varDecl, 'identifier');
|
|
178
|
+
const arrowFunc = varDecl.children?.find(c => c.type === 'arrow_function');
|
|
179
|
+
if (!nameNode || !arrowFunc)
|
|
180
|
+
continue;
|
|
181
|
+
const { line } = getLineAndColumn(varDecl);
|
|
99
182
|
// Extract function calls
|
|
100
|
-
const
|
|
183
|
+
const body = findChildOfType(arrowFunc, 'statement_block');
|
|
184
|
+
const functionCalls = body ? extractFunctionCalls(body, content, importMap) : [];
|
|
101
185
|
const normalizedCalls = functionCalls.map(call => normalizeCallTarget(call.callee, filePath, localFunctions));
|
|
102
186
|
// Track which imports this function uses
|
|
103
|
-
const functionUsageMap =
|
|
104
|
-
extractIdentifierUsage(funcDecl, sourceFile, importNames) : new Map();
|
|
187
|
+
const functionUsageMap = extractIdentifierUsage(arrowFunc, content, importNames);
|
|
105
188
|
const usedImports = Array.from(functionUsageMap.keys());
|
|
106
189
|
// Apply unused imports configuration
|
|
107
|
-
const
|
|
190
|
+
const fConfig = options?.unusedImportsConfig;
|
|
108
191
|
let unusedImports = detailedImports
|
|
109
192
|
.filter(imp => {
|
|
110
|
-
// Skip side-effect imports - they're never "unused"
|
|
111
193
|
if (imp.importType === 'side-effect')
|
|
112
194
|
return false;
|
|
113
|
-
// Check if import is used in this function OR at module level
|
|
114
195
|
if (functionUsageMap.has(imp.localName) || fileUsageMap.has(imp.localName))
|
|
115
196
|
return false;
|
|
116
|
-
|
|
117
|
-
if (!config?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
197
|
+
if (!fConfig?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
118
198
|
return false;
|
|
119
|
-
|
|
120
|
-
if (config?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
199
|
+
if (fConfig?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
121
200
|
return false;
|
|
122
201
|
return true;
|
|
123
202
|
})
|
|
124
203
|
.map(imp => imp.localName);
|
|
204
|
+
const arrowParams = findChildOfType(arrowFunc, 'formal_parameters');
|
|
205
|
+
const arrowParamCount = arrowParams?.children?.filter(c => c.type === 'required_parameter' || c.type === 'optional_parameter' || c.type === 'rest_parameter').length ?? 0;
|
|
125
206
|
functions.push({
|
|
126
|
-
name:
|
|
207
|
+
name: rawText(nameNode),
|
|
127
208
|
filePath,
|
|
128
209
|
lineNumber: line,
|
|
129
210
|
language: getLanguageFromPath(filePath),
|
|
130
211
|
dependencies,
|
|
131
|
-
purpose: `
|
|
132
|
-
context: `
|
|
212
|
+
purpose: `Arrow function ${rawText(nameNode)}`,
|
|
213
|
+
context: `Defined in ${path.basename(filePath)}`,
|
|
133
214
|
metadata: {
|
|
134
|
-
kind: '
|
|
135
|
-
isAsync:
|
|
136
|
-
isExported:
|
|
137
|
-
parameterCount:
|
|
215
|
+
kind: 'arrow',
|
|
216
|
+
isAsync: hasModifier(arrowFunc, 'async'),
|
|
217
|
+
isExported: isExported(varStmt),
|
|
218
|
+
parameterCount: arrowParamCount,
|
|
138
219
|
functionCalls: normalizedCalls,
|
|
139
220
|
usedImports,
|
|
140
221
|
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
141
|
-
body:
|
|
222
|
+
body: body ? rawText(body) : undefined
|
|
142
223
|
}
|
|
143
224
|
});
|
|
144
225
|
}
|
|
145
226
|
}
|
|
146
|
-
// Find arrow functions assigned to variables
|
|
147
|
-
const variableStatements = findNodesByKind(sourceFile, ts.SyntaxKind.VariableStatement);
|
|
148
|
-
for (const varStmt of variableStatements) {
|
|
149
|
-
const varDeclarations = varStmt.declarationList.declarations;
|
|
150
|
-
for (const varDecl of varDeclarations) {
|
|
151
|
-
if (varDecl.initializer &&
|
|
152
|
-
varDecl.initializer.kind === ts.SyntaxKind.ArrowFunction &&
|
|
153
|
-
ts.isIdentifier(varDecl.name)) {
|
|
154
|
-
const { line } = getLineAndColumn(sourceFile, varDecl.getStart());
|
|
155
|
-
const arrowFunc = varDecl.initializer;
|
|
156
|
-
const varStmtNode = varStmt;
|
|
157
|
-
// Extract function calls
|
|
158
|
-
const functionCalls = arrowFunc.body ? extractFunctionCalls(arrowFunc.body, sourceFile, importMap) : [];
|
|
159
|
-
const normalizedCalls = functionCalls.map(call => normalizeCallTarget(call.callee, filePath, localFunctions));
|
|
160
|
-
// Track which imports this function uses
|
|
161
|
-
const functionUsageMap = arrowFunc ?
|
|
162
|
-
extractIdentifierUsage(arrowFunc, sourceFile, importNames) : new Map();
|
|
163
|
-
const usedImports = Array.from(functionUsageMap.keys());
|
|
164
|
-
// Apply unused imports configuration
|
|
165
|
-
const config = options?.unusedImportsConfig;
|
|
166
|
-
let unusedImports = detailedImports
|
|
167
|
-
.filter(imp => {
|
|
168
|
-
// Skip side-effect imports - they're never "unused"
|
|
169
|
-
if (imp.importType === 'side-effect')
|
|
170
|
-
return false;
|
|
171
|
-
// Check if import is used in this function OR at module level
|
|
172
|
-
if (functionUsageMap.has(imp.localName) || fileUsageMap.has(imp.localName))
|
|
173
|
-
return false;
|
|
174
|
-
// Apply type-only configuration
|
|
175
|
-
if (!config?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
176
|
-
return false;
|
|
177
|
-
// Apply ignore patterns
|
|
178
|
-
if (config?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
179
|
-
return false;
|
|
180
|
-
return true;
|
|
181
|
-
})
|
|
182
|
-
.map(imp => imp.localName);
|
|
183
|
-
functions.push({
|
|
184
|
-
name: varDecl.name.text,
|
|
185
|
-
filePath,
|
|
186
|
-
lineNumber: line,
|
|
187
|
-
language: getLanguageFromPath(filePath),
|
|
188
|
-
dependencies,
|
|
189
|
-
purpose: `Arrow function ${varDecl.name.text}`,
|
|
190
|
-
context: `Defined in ${path.basename(filePath)}`,
|
|
191
|
-
metadata: {
|
|
192
|
-
kind: 'arrow',
|
|
193
|
-
isAsync: arrowFunc.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false,
|
|
194
|
-
isExported: varStmtNode.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) || false,
|
|
195
|
-
parameterCount: arrowFunc.parameters.length,
|
|
196
|
-
functionCalls: normalizedCalls,
|
|
197
|
-
usedImports,
|
|
198
|
-
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
199
|
-
body: arrowFunc.body ? arrowFunc.body.getText(sourceFile) : undefined
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
227
|
// Find class methods
|
|
206
|
-
const classDeclarations = findNodesByKind(
|
|
228
|
+
const classDeclarations = findNodesByKind(root, 'class_declaration');
|
|
207
229
|
for (const classDecl of classDeclarations) {
|
|
208
|
-
const
|
|
209
|
-
const
|
|
230
|
+
const classNameNode = findChildOfType(classDecl, 'identifier');
|
|
231
|
+
const className = classNameNode ? rawText(classNameNode) : 'AnonymousClass';
|
|
232
|
+
const classBody = findChildOfType(classDecl, 'class_body');
|
|
233
|
+
const methods = classBody?.children?.filter(m => m.type === 'method_definition') ?? [];
|
|
210
234
|
for (const method of methods) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
const methodNameNode = findChildOfType(method, 'identifier');
|
|
236
|
+
if (!methodNameNode)
|
|
237
|
+
continue;
|
|
238
|
+
const { line } = getLineAndColumn(method);
|
|
239
|
+
// Extract function calls
|
|
240
|
+
const body = findChildOfType(method, 'statement_block');
|
|
241
|
+
const functionCalls = body ? extractFunctionCalls(body, content, importMap) : [];
|
|
242
|
+
const normalizedCalls = functionCalls.map(call => normalizeCallTarget(call.callee, filePath, localFunctions));
|
|
243
|
+
// Track which imports this method uses
|
|
244
|
+
const functionUsageMap = extractIdentifierUsage(method, content, importNames);
|
|
245
|
+
const usedImports = Array.from(functionUsageMap.keys());
|
|
246
|
+
// Apply unused imports configuration
|
|
247
|
+
const mConfig = options?.unusedImportsConfig;
|
|
248
|
+
let unusedImports = detailedImports
|
|
249
|
+
.filter(imp => {
|
|
250
|
+
if (imp.importType === 'side-effect')
|
|
251
|
+
return false;
|
|
252
|
+
if (functionUsageMap.has(imp.localName) || fileUsageMap.has(imp.localName))
|
|
253
|
+
return false;
|
|
254
|
+
if (!mConfig?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
255
|
+
return false;
|
|
256
|
+
if (mConfig?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
257
|
+
return false;
|
|
258
|
+
return true;
|
|
259
|
+
})
|
|
260
|
+
.map(imp => imp.localName);
|
|
261
|
+
const methodParams = findChildOfType(method, 'formal_parameters');
|
|
262
|
+
const methodParamCount = methodParams?.children?.filter(c => c.type === 'required_parameter' || c.type === 'optional_parameter' || c.type === 'rest_parameter').length ?? 0;
|
|
263
|
+
functions.push({
|
|
264
|
+
name: `${className}.${rawText(methodNameNode)}`,
|
|
265
|
+
filePath,
|
|
266
|
+
lineNumber: line,
|
|
267
|
+
language: getLanguageFromPath(filePath),
|
|
268
|
+
dependencies,
|
|
269
|
+
purpose: `Method ${rawText(methodNameNode)} of class ${className}`,
|
|
270
|
+
context: `Class method in ${path.basename(filePath)}`,
|
|
271
|
+
metadata: {
|
|
272
|
+
kind: 'method',
|
|
273
|
+
className,
|
|
274
|
+
isAsync: hasModifier(method, 'async'),
|
|
275
|
+
isStatic: hasModifier(method, 'static'),
|
|
276
|
+
isPrivate: hasModifier(method, 'private'),
|
|
277
|
+
parameterCount: methodParamCount,
|
|
278
|
+
functionCalls: normalizedCalls,
|
|
279
|
+
usedImports,
|
|
280
|
+
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
281
|
+
body: body ? rawText(body) : undefined
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Check if this is a React file and scan for components
|
|
287
|
+
if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ||
|
|
288
|
+
(filePath.endsWith('.js') && dependencies.includes('react'))) {
|
|
289
|
+
// Walk all nodes for React components
|
|
290
|
+
walkAST(root, (node) => {
|
|
291
|
+
if (!isReactComponent(node))
|
|
292
|
+
return;
|
|
293
|
+
const componentType = detectComponentType(node);
|
|
294
|
+
if (!componentType)
|
|
295
|
+
return;
|
|
296
|
+
const componentName = getComponentName(node);
|
|
297
|
+
const { line } = getLineAndColumn(node);
|
|
298
|
+
const endLine = node.location?.end?.line ?? line;
|
|
299
|
+
// Determine which node to use for prop extraction
|
|
300
|
+
// For arrow functions, use the parent variable declarator
|
|
301
|
+
let nodeForProps = node;
|
|
302
|
+
if (node.type === 'arrow_function' &&
|
|
303
|
+
node.parent && (node.parent.type === 'variable_declarator' ||
|
|
304
|
+
isVariableDecl(node.parent.type))) {
|
|
305
|
+
nodeForProps = node.parent;
|
|
306
|
+
}
|
|
307
|
+
// Track which imports this component uses
|
|
308
|
+
const componentUsageMap = extractIdentifierUsage(node, content, importNames);
|
|
309
|
+
const usedImports = Array.from(componentUsageMap.keys());
|
|
310
|
+
// Apply unused imports configuration
|
|
311
|
+
const cConfig = options?.unusedImportsConfig;
|
|
312
|
+
let unusedImports = detailedImports
|
|
313
|
+
.filter(imp => {
|
|
314
|
+
if (imp.importType === 'side-effect')
|
|
315
|
+
return false;
|
|
316
|
+
if (componentUsageMap.has(imp.localName) || fileUsageMap.has(imp.localName))
|
|
317
|
+
return false;
|
|
318
|
+
if (!cConfig?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
319
|
+
return false;
|
|
320
|
+
if (cConfig?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
321
|
+
return false;
|
|
322
|
+
return true;
|
|
323
|
+
})
|
|
324
|
+
.map(imp => imp.localName);
|
|
325
|
+
// Check if we already indexed this as a regular function
|
|
326
|
+
const existingFunc = functions.find(f => f.name === componentName && f.lineNumber === line);
|
|
327
|
+
if (existingFunc) {
|
|
328
|
+
// Update the existing function with component metadata
|
|
329
|
+
existingFunc.purpose = `React ${componentType} component`;
|
|
330
|
+
existingFunc.metadata = {
|
|
331
|
+
...existingFunc.metadata,
|
|
332
|
+
entityType: 'component',
|
|
333
|
+
componentType,
|
|
334
|
+
props: extractPropTypes(nodeForProps),
|
|
335
|
+
hooks: extractHooks(node),
|
|
336
|
+
jsxElements: extractJSXElements(node),
|
|
337
|
+
isExported: isComponentExported(node),
|
|
338
|
+
complexity: calculateComponentComplexity(node),
|
|
339
|
+
body: getComponentBody(node)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Add new component
|
|
239
344
|
functions.push({
|
|
240
|
-
name:
|
|
345
|
+
name: componentName,
|
|
241
346
|
filePath,
|
|
242
347
|
lineNumber: line,
|
|
348
|
+
startLine: line,
|
|
349
|
+
endLine,
|
|
243
350
|
language: getLanguageFromPath(filePath),
|
|
244
351
|
dependencies,
|
|
245
|
-
purpose: `
|
|
246
|
-
context: `
|
|
352
|
+
purpose: `React ${componentType} component`,
|
|
353
|
+
context: `Located in ${path.basename(filePath)}`,
|
|
247
354
|
metadata: {
|
|
248
|
-
kind: 'method',
|
|
249
|
-
className,
|
|
250
|
-
isAsync: !!method.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword),
|
|
251
|
-
isStatic: !!method.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword),
|
|
252
|
-
isPrivate: !!method.modifiers?.some(m => m.kind === ts.SyntaxKind.PrivateKeyword),
|
|
253
|
-
parameterCount: method.parameters.length,
|
|
254
|
-
functionCalls: normalizedCalls,
|
|
255
|
-
usedImports,
|
|
256
|
-
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
257
|
-
body: method.body ? method.body.getText(sourceFile) : undefined
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// Check if this is a React file and scan for components
|
|
264
|
-
if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ||
|
|
265
|
-
(filePath.endsWith('.js') && dependencies.includes('react'))) {
|
|
266
|
-
// Check all nodes for React components
|
|
267
|
-
const checkNode = (node) => {
|
|
268
|
-
if (isReactComponent(node)) {
|
|
269
|
-
const componentType = detectComponentType(node);
|
|
270
|
-
const componentName = getComponentName(node);
|
|
271
|
-
const { line } = getLineAndColumn(sourceFile, node.getStart());
|
|
272
|
-
const endLine = getLineAndColumn(sourceFile, node.getEnd()).line;
|
|
273
|
-
// Skip if we already indexed this as a regular function
|
|
274
|
-
if (functions.some(f => f.name === componentName && f.lineNumber === line)) {
|
|
275
|
-
// Update the existing function with component metadata
|
|
276
|
-
const existingFunc = functions.find(f => f.name === componentName && f.lineNumber === line);
|
|
277
|
-
existingFunc.purpose = `React ${componentType} component`;
|
|
278
|
-
// For arrow functions, we need to check the parent variable declaration for types
|
|
279
|
-
let nodeForProps = node;
|
|
280
|
-
if (ts.isArrowFunction(node) && ts.isVariableDeclaration(node.parent)) {
|
|
281
|
-
nodeForProps = node.parent;
|
|
282
|
-
}
|
|
283
|
-
existingFunc.metadata = {
|
|
284
|
-
...existingFunc.metadata,
|
|
285
355
|
entityType: 'component',
|
|
286
356
|
componentType,
|
|
287
|
-
props: extractPropTypes(nodeForProps
|
|
288
|
-
hooks: extractHooks(node
|
|
357
|
+
props: extractPropTypes(nodeForProps),
|
|
358
|
+
hooks: extractHooks(node),
|
|
289
359
|
jsxElements: extractJSXElements(node),
|
|
290
360
|
isExported: isComponentExported(node),
|
|
291
361
|
complexity: calculateComponentComplexity(node),
|
|
292
|
-
body: getComponentBody(node,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const usedImports = Array.from(componentUsageMap.keys());
|
|
299
|
-
// Apply unused imports configuration
|
|
300
|
-
const config = options?.unusedImportsConfig;
|
|
301
|
-
let unusedImports = detailedImports
|
|
302
|
-
.filter(imp => {
|
|
303
|
-
// Skip side-effect imports - they're never "unused"
|
|
304
|
-
if (imp.importType === 'side-effect')
|
|
305
|
-
return false;
|
|
306
|
-
// Check if import is used in this component OR at module level
|
|
307
|
-
if (componentUsageMap.has(imp.localName) || fileUsageMap.has(imp.localName))
|
|
308
|
-
return false;
|
|
309
|
-
// Apply type-only configuration
|
|
310
|
-
if (!config?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
311
|
-
return false;
|
|
312
|
-
// Apply ignore patterns
|
|
313
|
-
if (config?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
314
|
-
return false;
|
|
315
|
-
return true;
|
|
316
|
-
})
|
|
317
|
-
.map(imp => imp.localName);
|
|
318
|
-
// Add new component
|
|
319
|
-
functions.push({
|
|
320
|
-
name: componentName,
|
|
321
|
-
filePath,
|
|
322
|
-
lineNumber: line,
|
|
323
|
-
startLine: line,
|
|
324
|
-
endLine: endLine,
|
|
325
|
-
language: getLanguageFromPath(filePath),
|
|
326
|
-
dependencies,
|
|
327
|
-
purpose: `React ${componentType} component`,
|
|
328
|
-
context: `Located in ${path.basename(filePath)}`,
|
|
329
|
-
metadata: {
|
|
330
|
-
entityType: 'component',
|
|
331
|
-
componentType,
|
|
332
|
-
props: extractPropTypes(node, sourceFile),
|
|
333
|
-
hooks: extractHooks(node, sourceFile),
|
|
334
|
-
jsxElements: extractJSXElements(node),
|
|
335
|
-
isExported: isComponentExported(node),
|
|
336
|
-
complexity: calculateComponentComplexity(node),
|
|
337
|
-
body: getComponentBody(node, sourceFile),
|
|
338
|
-
usedImports,
|
|
339
|
-
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
340
|
-
calledBy: []
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
362
|
+
body: getComponentBody(node),
|
|
363
|
+
usedImports,
|
|
364
|
+
unusedImports: unusedImports.length > 0 ? unusedImports : undefined,
|
|
365
|
+
calledBy: []
|
|
366
|
+
}
|
|
367
|
+
});
|
|
344
368
|
}
|
|
345
|
-
|
|
346
|
-
};
|
|
347
|
-
checkNode(sourceFile);
|
|
369
|
+
});
|
|
348
370
|
}
|
|
349
371
|
// Add file-level unused import analysis if configured
|
|
350
372
|
if (options?.unusedImportsConfig?.checkLevel === 'file' && functions.length > 0) {
|
|
@@ -358,17 +380,14 @@ export async function extractFunctionsFromFile(filePath, options) {
|
|
|
358
380
|
}
|
|
359
381
|
}
|
|
360
382
|
// Calculate file-level unused imports
|
|
361
|
-
const
|
|
383
|
+
const flConfig = options.unusedImportsConfig;
|
|
362
384
|
const fileUnusedImports = detailedImports
|
|
363
385
|
.filter(imp => {
|
|
364
|
-
// Check if import is used anywhere in the file
|
|
365
386
|
if (allUsedImports.has(imp.localName))
|
|
366
387
|
return false;
|
|
367
|
-
|
|
368
|
-
if (!config?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
388
|
+
if (!flConfig?.includeTypeOnlyImports && imp.isTypeOnly)
|
|
369
389
|
return false;
|
|
370
|
-
|
|
371
|
-
if (config?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
390
|
+
if (flConfig?.ignorePatterns?.some(pattern => imp.localName.match(new RegExp(pattern))))
|
|
372
391
|
return false;
|
|
373
392
|
return true;
|
|
374
393
|
})
|
|
@@ -394,38 +413,49 @@ export async function extractFunctionsFromFile(filePath, options) {
|
|
|
394
413
|
}
|
|
395
414
|
return functions;
|
|
396
415
|
}
|
|
416
|
+
// ---------------------------------------------------------------------------
|
|
397
417
|
// Helper functions for React component extraction
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
398
419
|
function extractJSXElements(node) {
|
|
399
420
|
const elements = new Set();
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
421
|
+
walkAST(node, (child) => {
|
|
422
|
+
if (child.type === 'jsx_element') {
|
|
423
|
+
const openTag = findChildOfType(child, 'open_tag');
|
|
424
|
+
if (openTag) {
|
|
425
|
+
const tagNameNode = openTag.children?.find(c => c.type === 'identifier' || c.type === 'member_expression');
|
|
426
|
+
if (tagNameNode) {
|
|
427
|
+
if (tagNameNode.type === 'identifier') {
|
|
428
|
+
elements.add(rawText(tagNameNode));
|
|
429
|
+
}
|
|
430
|
+
else if (tagNameNode.type === 'member_expression') {
|
|
431
|
+
elements.add(rawText(tagNameNode));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
407
434
|
}
|
|
408
|
-
|
|
409
|
-
|
|
435
|
+
}
|
|
436
|
+
else if (child.type === 'jsx_self_closing_element') {
|
|
437
|
+
const tagNameNode = child.children?.find(c => c.type === 'identifier' || c.type === 'member_expression');
|
|
438
|
+
if (tagNameNode) {
|
|
439
|
+
if (tagNameNode.type === 'identifier') {
|
|
440
|
+
elements.add(rawText(tagNameNode));
|
|
441
|
+
}
|
|
442
|
+
else if (tagNameNode.type === 'member_expression') {
|
|
443
|
+
elements.add(rawText(tagNameNode));
|
|
444
|
+
}
|
|
410
445
|
}
|
|
411
446
|
}
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
ts.forEachChild(node, visit);
|
|
447
|
+
});
|
|
415
448
|
return Array.from(elements);
|
|
416
449
|
}
|
|
417
450
|
function isComponentExported(node) {
|
|
418
|
-
// Check for export modifier
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
if (modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
422
|
-
return true;
|
|
423
|
-
}
|
|
451
|
+
// Check for export modifier on the node itself
|
|
452
|
+
if (isExported(node)) {
|
|
453
|
+
return true;
|
|
424
454
|
}
|
|
425
455
|
// Check if parent is an export statement
|
|
426
456
|
let parent = node.parent;
|
|
427
457
|
while (parent) {
|
|
428
|
-
if (
|
|
458
|
+
if (parent.type === 'export_statement' || parent.type === 'export_declaration') {
|
|
429
459
|
return true;
|
|
430
460
|
}
|
|
431
461
|
parent = parent.parent;
|
|
@@ -434,29 +464,34 @@ function isComponentExported(node) {
|
|
|
434
464
|
}
|
|
435
465
|
function calculateComponentComplexity(node) {
|
|
436
466
|
let complexity = 1; // Base complexity
|
|
437
|
-
|
|
467
|
+
walkAST(node, (child) => {
|
|
438
468
|
// Control flow statements
|
|
439
|
-
if (
|
|
469
|
+
if (child.type === 'if_statement' || child.type === 'ternary_expression') {
|
|
440
470
|
complexity++;
|
|
441
471
|
}
|
|
442
|
-
else if (
|
|
443
|
-
|
|
472
|
+
else if (child.type === 'for_statement' || child.type === 'for_in_statement' ||
|
|
473
|
+
child.type === 'while_statement') {
|
|
444
474
|
complexity += 2;
|
|
445
475
|
}
|
|
446
|
-
else if (
|
|
447
|
-
|
|
476
|
+
else if (child.type === 'switch_statement') {
|
|
477
|
+
// Count switch_case children in switch body
|
|
478
|
+
const body = findChildOfType(child, 'switch_body');
|
|
479
|
+
if (body) {
|
|
480
|
+
const cases = body.children?.filter(c => c.type === 'switch_case' || c.type === 'switch_default') ?? [];
|
|
481
|
+
complexity += cases.length;
|
|
482
|
+
}
|
|
448
483
|
}
|
|
449
484
|
// Callbacks and event handlers
|
|
450
|
-
if (
|
|
451
|
-
const
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
485
|
+
if (child.type === 'call_expression') {
|
|
486
|
+
const expr = child.children?.[0];
|
|
487
|
+
if (expr?.type === 'member_expression') {
|
|
488
|
+
const propNode = expr.children?.[expr.children.length - 1];
|
|
489
|
+
if (propNode && rawText(propNode) === 'map') {
|
|
490
|
+
complexity++;
|
|
491
|
+
}
|
|
455
492
|
}
|
|
456
493
|
}
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
ts.forEachChild(node, visit);
|
|
494
|
+
});
|
|
460
495
|
return complexity;
|
|
461
496
|
}
|
|
462
497
|
/**
|
|
@@ -488,115 +523,156 @@ export async function scanFunctionsInDirectory(dirPath, options) {
|
|
|
488
523
|
*/
|
|
489
524
|
export class FunctionScanner {
|
|
490
525
|
async scanFunctions(content, filePath, language) {
|
|
491
|
-
// Parse the content directly
|
|
492
526
|
const functions = [];
|
|
493
|
-
//
|
|
494
|
-
const
|
|
527
|
+
// Parse content directly via tree-sitter
|
|
528
|
+
const ast = parseFile(filePath, content);
|
|
529
|
+
if (!ast)
|
|
530
|
+
return functions;
|
|
531
|
+
const root = ast.root;
|
|
495
532
|
// Get file dependencies
|
|
496
|
-
const imports = getImports(
|
|
533
|
+
const imports = getImports(root);
|
|
497
534
|
const dependencies = imports
|
|
498
535
|
.map(imp => imp.moduleSpecifier)
|
|
499
536
|
.filter(spec => !spec.startsWith('.') && !spec.startsWith('/'))
|
|
500
537
|
.filter((v, i, a) => a.indexOf(v) === i); // Unique only
|
|
538
|
+
// Build import map for dependency tracking
|
|
539
|
+
const importMap = buildImportMap(root);
|
|
501
540
|
// Find all function declarations
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (!funcNode.name)
|
|
541
|
+
for (const func of findNodesByKind(root, 'function_declaration')) {
|
|
542
|
+
const nameNode = findChildOfType(func, 'identifier');
|
|
543
|
+
if (!nameNode)
|
|
506
544
|
continue;
|
|
507
|
-
const { line } = getLineAndColumn(
|
|
545
|
+
const { line } = getLineAndColumn(func);
|
|
546
|
+
const params = findChildOfType(func, 'formal_parameters');
|
|
547
|
+
const paramCount = params?.children?.filter(c => c.type === 'required_parameter' || c.type === 'optional_parameter' || c.type === 'rest_parameter').length ?? 0;
|
|
548
|
+
// Build signature: text up to the statement block (before first '{' of body)
|
|
549
|
+
const bodyNode = findChildOfType(func, 'statement_block');
|
|
550
|
+
const fullText = rawText(func);
|
|
551
|
+
const signature = fullText.split('{')[0]?.trim() ?? undefined;
|
|
508
552
|
functions.push({
|
|
509
|
-
name:
|
|
510
|
-
filePath
|
|
553
|
+
name: rawText(nameNode),
|
|
554
|
+
filePath,
|
|
511
555
|
lineNumber: line,
|
|
512
|
-
language
|
|
556
|
+
language,
|
|
513
557
|
dependencies,
|
|
514
|
-
purpose: `Function ${
|
|
558
|
+
purpose: `Function ${rawText(nameNode)} implementation`,
|
|
515
559
|
context: `Located in ${path.basename(filePath)}`,
|
|
516
560
|
metadata: {
|
|
517
561
|
kind: 'function',
|
|
518
|
-
isAsync:
|
|
519
|
-
isExported:
|
|
520
|
-
parameterCount:
|
|
562
|
+
isAsync: hasModifier(func, 'async'),
|
|
563
|
+
isExported: isExported(func),
|
|
564
|
+
parameterCount: paramCount,
|
|
565
|
+
signature,
|
|
566
|
+
body: bodyNode ? rawText(bodyNode) : undefined,
|
|
521
567
|
}
|
|
522
568
|
});
|
|
523
569
|
}
|
|
524
570
|
// Find arrow functions and variable declarations
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
|
|
571
|
+
for (const varStmt of [
|
|
572
|
+
...findNodesByKind(root, 'lexical_declaration'),
|
|
573
|
+
...findNodesByKind(root, 'variable_declaration')
|
|
574
|
+
]) {
|
|
575
|
+
for (const declaration of varStmt.children ?? []) {
|
|
576
|
+
if (declaration.type !== 'variable_declarator')
|
|
577
|
+
continue;
|
|
578
|
+
const nameNode = findChildOfType(declaration, 'identifier');
|
|
579
|
+
const init = declaration.children?.find(c => c.type === 'arrow_function');
|
|
580
|
+
if (!nameNode || !init)
|
|
581
|
+
continue;
|
|
582
|
+
const { line } = getLineAndColumn(declaration);
|
|
583
|
+
const arrowParams = findChildOfType(init, 'formal_parameters');
|
|
584
|
+
const arrowParamCount = arrowParams?.children?.filter(c => c.type === 'required_parameter' || c.type === 'optional_parameter' || c.type === 'rest_parameter').length ?? 0;
|
|
585
|
+
const fullText = rawText(init);
|
|
586
|
+
const signature = fullText.split('{')[0]?.trim() ?? undefined;
|
|
587
|
+
const bodyNode = findChildOfType(init, 'statement_block');
|
|
588
|
+
functions.push({
|
|
589
|
+
name: rawText(nameNode),
|
|
590
|
+
filePath,
|
|
591
|
+
lineNumber: line,
|
|
592
|
+
language,
|
|
593
|
+
dependencies,
|
|
594
|
+
purpose: `Arrow function ${rawText(nameNode)}`,
|
|
595
|
+
context: `Defined in ${path.basename(filePath)}`,
|
|
596
|
+
metadata: {
|
|
597
|
+
kind: 'arrow',
|
|
598
|
+
isAsync: hasModifier(init, 'async'),
|
|
599
|
+
isExported: isExported(varStmt),
|
|
600
|
+
parameterCount: arrowParamCount,
|
|
601
|
+
signature,
|
|
602
|
+
body: bodyNode ? rawText(bodyNode) : undefined,
|
|
603
|
+
}
|
|
604
|
+
});
|
|
550
605
|
}
|
|
551
606
|
}
|
|
552
607
|
// Find class methods
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (!classNode.name)
|
|
608
|
+
for (const classDecl of findNodesByKind(root, 'class_declaration')) {
|
|
609
|
+
const nameNode = findChildOfType(classDecl, 'identifier');
|
|
610
|
+
if (!nameNode)
|
|
557
611
|
continue;
|
|
558
|
-
const className =
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
|
|
612
|
+
const className = rawText(nameNode);
|
|
613
|
+
const classBody = findChildOfType(classDecl, 'class_body');
|
|
614
|
+
for (const member of classBody?.children ?? []) {
|
|
615
|
+
if (member.type !== 'method_definition')
|
|
616
|
+
continue;
|
|
617
|
+
const mNameNode = findChildOfType(member, 'identifier');
|
|
618
|
+
if (!mNameNode)
|
|
619
|
+
continue;
|
|
620
|
+
const { line } = getLineAndColumn(member);
|
|
621
|
+
const methodParams = findChildOfType(member, 'formal_parameters');
|
|
622
|
+
const methodParamCount = methodParams?.children?.filter(c => c.type === 'required_parameter' || c.type === 'optional_parameter' || c.type === 'rest_parameter').length ?? 0;
|
|
623
|
+
const fullText = rawText(member);
|
|
624
|
+
const signature = fullText.split('{')[0]?.trim() ?? undefined;
|
|
625
|
+
const bodyNode = findChildOfType(member, 'statement_block');
|
|
626
|
+
functions.push({
|
|
627
|
+
name: `${className}.${rawText(mNameNode)}`,
|
|
628
|
+
filePath,
|
|
629
|
+
lineNumber: line,
|
|
630
|
+
language,
|
|
631
|
+
dependencies,
|
|
632
|
+
purpose: `Method ${rawText(mNameNode)} of class ${className}`,
|
|
633
|
+
context: `Class method in ${path.basename(filePath)}`,
|
|
634
|
+
metadata: {
|
|
635
|
+
kind: 'method',
|
|
636
|
+
className,
|
|
637
|
+
isAsync: hasModifier(member, 'async'),
|
|
638
|
+
isStatic: hasModifier(member, 'static'),
|
|
639
|
+
isPrivate: hasModifier(member, 'private'),
|
|
640
|
+
parameterCount: methodParamCount,
|
|
641
|
+
signature,
|
|
642
|
+
body: bodyNode ? rawText(bodyNode) : undefined,
|
|
643
|
+
}
|
|
644
|
+
});
|
|
581
645
|
}
|
|
582
646
|
}
|
|
583
647
|
return functions;
|
|
584
648
|
}
|
|
585
649
|
}
|
|
650
|
+
// ---------------------------------------------------------------------------
|
|
586
651
|
// Helper function to get component body
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
652
|
+
// ---------------------------------------------------------------------------
|
|
653
|
+
function getComponentBody(node) {
|
|
654
|
+
if (node.type === 'function_declaration' || node.type === 'function_expression') {
|
|
655
|
+
const body = findChildOfType(node, 'statement_block');
|
|
656
|
+
return body ? rawText(body) : undefined;
|
|
590
657
|
}
|
|
591
|
-
else if (
|
|
592
|
-
|
|
658
|
+
else if (node.type === 'arrow_function') {
|
|
659
|
+
const body = findChildOfType(node, 'statement_block');
|
|
660
|
+
return body ? rawText(body) : undefined;
|
|
593
661
|
}
|
|
594
|
-
else if (
|
|
662
|
+
else if (node.type === 'class_declaration') {
|
|
595
663
|
// For class components, get the render method body
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
664
|
+
const classBody = findChildOfType(node, 'class_body');
|
|
665
|
+
if (!classBody)
|
|
666
|
+
return undefined;
|
|
667
|
+
for (const member of classBody.children ?? []) {
|
|
668
|
+
if (member.type !== 'method_definition')
|
|
669
|
+
continue;
|
|
670
|
+
const mNameNode = findChildOfType(member, 'identifier');
|
|
671
|
+
if (mNameNode && rawText(mNameNode) === 'render') {
|
|
672
|
+
const body = findChildOfType(member, 'statement_block');
|
|
673
|
+
return body ? rawText(body) : undefined;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
600
676
|
}
|
|
601
677
|
return undefined;
|
|
602
678
|
}
|