codeguardian-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/agent/agentTools.d.ts +26 -0
- package/dist/agent/agentTools.d.ts.map +1 -0
- package/dist/agent/agentTools.js +699 -0
- package/dist/agent/agentTools.js.map +1 -0
- package/dist/agent/autoValidator.d.ts +110 -0
- package/dist/agent/autoValidator.d.ts.map +1 -0
- package/dist/agent/autoValidator.js +964 -0
- package/dist/agent/autoValidator.js.map +1 -0
- package/dist/agent/fileWatcher.d.ts +28 -0
- package/dist/agent/fileWatcher.d.ts.map +1 -0
- package/dist/agent/fileWatcher.js +88 -0
- package/dist/agent/fileWatcher.js.map +1 -0
- package/dist/agent/guardianPersistence.d.ts +98 -0
- package/dist/agent/guardianPersistence.d.ts.map +1 -0
- package/dist/agent/guardianPersistence.js +296 -0
- package/dist/agent/guardianPersistence.js.map +1 -0
- package/dist/agent/mcpNotifications.d.ts +38 -0
- package/dist/agent/mcpNotifications.d.ts.map +1 -0
- package/dist/agent/mcpNotifications.js +81 -0
- package/dist/agent/mcpNotifications.js.map +1 -0
- package/dist/analyzers/aiPatterns.d.ts +16 -0
- package/dist/analyzers/aiPatterns.d.ts.map +1 -0
- package/dist/analyzers/aiPatterns.js +103 -0
- package/dist/analyzers/aiPatterns.js.map +1 -0
- package/dist/analyzers/antiPatterns.d.ts +60 -0
- package/dist/analyzers/antiPatterns.d.ts.map +1 -0
- package/dist/analyzers/antiPatterns.js +198 -0
- package/dist/analyzers/antiPatterns.js.map +1 -0
- package/dist/analyzers/builtinTypes.d.ts +18 -0
- package/dist/analyzers/builtinTypes.d.ts.map +1 -0
- package/dist/analyzers/builtinTypes.js +1275 -0
- package/dist/analyzers/builtinTypes.js.map +1 -0
- package/dist/analyzers/complexity.d.ts +14 -0
- package/dist/analyzers/complexity.d.ts.map +1 -0
- package/dist/analyzers/complexity.js +610 -0
- package/dist/analyzers/complexity.js.map +1 -0
- package/dist/analyzers/findingVerifier.d.ts +59 -0
- package/dist/analyzers/findingVerifier.d.ts.map +1 -0
- package/dist/analyzers/findingVerifier.js +1169 -0
- package/dist/analyzers/findingVerifier.js.map +1 -0
- package/dist/analyzers/impactAnalyzer.d.ts +53 -0
- package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/impactAnalyzer.js +152 -0
- package/dist/analyzers/impactAnalyzer.js.map +1 -0
- package/dist/analyzers/languageDetector.d.ts +48 -0
- package/dist/analyzers/languageDetector.d.ts.map +1 -0
- package/dist/analyzers/languageDetector.js +404 -0
- package/dist/analyzers/languageDetector.js.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.js +193 -0
- package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.js +324 -0
- package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.js +429 -0
- package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.js +709 -0
- package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
- package/dist/analyzers/relevanceScorer.d.ts +43 -0
- package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
- package/dist/analyzers/relevanceScorer.js +200 -0
- package/dist/analyzers/relevanceScorer.js.map +1 -0
- package/dist/analyzers/standardLibrary.d.ts +22 -0
- package/dist/analyzers/standardLibrary.d.ts.map +1 -0
- package/dist/analyzers/standardLibrary.js +211 -0
- package/dist/analyzers/standardLibrary.js.map +1 -0
- package/dist/analyzers/symbolGraph.d.ts +30 -0
- package/dist/analyzers/symbolGraph.d.ts.map +1 -0
- package/dist/analyzers/symbolGraph.js +380 -0
- package/dist/analyzers/symbolGraph.js.map +1 -0
- package/dist/analyzers/symbolTable.d.ts +18 -0
- package/dist/analyzers/symbolTable.d.ts.map +1 -0
- package/dist/analyzers/symbolTable.js +176 -0
- package/dist/analyzers/symbolTable.js.map +1 -0
- package/dist/analyzers/typeChecker.d.ts +13 -0
- package/dist/analyzers/typeChecker.d.ts.map +1 -0
- package/dist/analyzers/typeChecker.js +580 -0
- package/dist/analyzers/typeChecker.js.map +1 -0
- package/dist/analyzers/usagePatterns.d.ts +42 -0
- package/dist/analyzers/usagePatterns.d.ts.map +1 -0
- package/dist/analyzers/usagePatterns.js +75 -0
- package/dist/analyzers/usagePatterns.js.map +1 -0
- package/dist/api-contract/context/backend.d.ts +19 -0
- package/dist/api-contract/context/backend.d.ts.map +1 -0
- package/dist/api-contract/context/backend.js +64 -0
- package/dist/api-contract/context/backend.js.map +1 -0
- package/dist/api-contract/context/contract.d.ts +34 -0
- package/dist/api-contract/context/contract.d.ts.map +1 -0
- package/dist/api-contract/context/contract.js +306 -0
- package/dist/api-contract/context/contract.js.map +1 -0
- package/dist/api-contract/context/frontend.d.ts +19 -0
- package/dist/api-contract/context/frontend.d.ts.map +1 -0
- package/dist/api-contract/context/frontend.js +64 -0
- package/dist/api-contract/context/frontend.js.map +1 -0
- package/dist/api-contract/detector.d.ts +28 -0
- package/dist/api-contract/detector.d.ts.map +1 -0
- package/dist/api-contract/detector.js +393 -0
- package/dist/api-contract/detector.js.map +1 -0
- package/dist/api-contract/extractors/python.d.ts +32 -0
- package/dist/api-contract/extractors/python.d.ts.map +1 -0
- package/dist/api-contract/extractors/python.js +521 -0
- package/dist/api-contract/extractors/python.js.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
- package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.js +173 -0
- package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/typescript.d.ts +32 -0
- package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
- package/dist/api-contract/extractors/typescript.js +666 -0
- package/dist/api-contract/extractors/typescript.js.map +1 -0
- package/dist/api-contract/index.d.ts +104 -0
- package/dist/api-contract/index.d.ts.map +1 -0
- package/dist/api-contract/index.js +232 -0
- package/dist/api-contract/index.js.map +1 -0
- package/dist/api-contract/types.d.ts +151 -0
- package/dist/api-contract/types.d.ts.map +1 -0
- package/dist/api-contract/types.js +19 -0
- package/dist/api-contract/types.js.map +1 -0
- package/dist/api-contract/validators/endpoint.d.ts +21 -0
- package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
- package/dist/api-contract/validators/endpoint.js +224 -0
- package/dist/api-contract/validators/endpoint.js.map +1 -0
- package/dist/api-contract/validators/index.d.ts +40 -0
- package/dist/api-contract/validators/index.d.ts.map +1 -0
- package/dist/api-contract/validators/index.js +875 -0
- package/dist/api-contract/validators/index.js.map +1 -0
- package/dist/api-contract/validators/parameter.d.ts +17 -0
- package/dist/api-contract/validators/parameter.d.ts.map +1 -0
- package/dist/api-contract/validators/parameter.js +250 -0
- package/dist/api-contract/validators/parameter.js.map +1 -0
- package/dist/api-contract/validators/type.d.ts +38 -0
- package/dist/api-contract/validators/type.d.ts.map +1 -0
- package/dist/api-contract/validators/type.js +244 -0
- package/dist/api-contract/validators/type.js.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.js +665 -0
- package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
- package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
- package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
- package/dist/context/apiContract/graphqlSupport.js +671 -0
- package/dist/context/apiContract/graphqlSupport.js.map +1 -0
- package/dist/context/apiContract/index.d.ts +14 -0
- package/dist/context/apiContract/index.d.ts.map +1 -0
- package/dist/context/apiContract/index.js +17 -0
- package/dist/context/apiContract/index.js.map +1 -0
- package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
- package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
- package/dist/context/apiContract/webSocketSupport.js +465 -0
- package/dist/context/apiContract/webSocketSupport.js.map +1 -0
- package/dist/context/apiContractContext.d.ts +15 -0
- package/dist/context/apiContractContext.d.ts.map +1 -0
- package/dist/context/apiContractContext.js +979 -0
- package/dist/context/apiContractContext.js.map +1 -0
- package/dist/context/apiContractExtraction.d.ts +52 -0
- package/dist/context/apiContractExtraction.d.ts.map +1 -0
- package/dist/context/apiContractExtraction.js +438 -0
- package/dist/context/apiContractExtraction.js.map +1 -0
- package/dist/context/contextLineage.d.ts +79 -0
- package/dist/context/contextLineage.d.ts.map +1 -0
- package/dist/context/contextLineage.js +259 -0
- package/dist/context/contextLineage.js.map +1 -0
- package/dist/context/contextOrchestrator.d.ts +57 -0
- package/dist/context/contextOrchestrator.d.ts.map +1 -0
- package/dist/context/contextOrchestrator.js +162 -0
- package/dist/context/contextOrchestrator.js.map +1 -0
- package/dist/context/intentTracker.d.ts +73 -0
- package/dist/context/intentTracker.d.ts.map +1 -0
- package/dist/context/intentTracker.js +168 -0
- package/dist/context/intentTracker.js.map +1 -0
- package/dist/context/projectContext.d.ts +219 -0
- package/dist/context/projectContext.d.ts.map +1 -0
- package/dist/context/projectContext.js +1984 -0
- package/dist/context/projectContext.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +260 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/library.d.ts +51 -0
- package/dist/prompts/library.d.ts.map +1 -0
- package/dist/prompts/library.js +65 -0
- package/dist/prompts/library.js.map +1 -0
- package/dist/prompts/templates.d.ts +44 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +97 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/queue/jobPersistence.d.ts +46 -0
- package/dist/queue/jobPersistence.d.ts.map +1 -0
- package/dist/queue/jobPersistence.js +158 -0
- package/dist/queue/jobPersistence.js.map +1 -0
- package/dist/queue/jobQueue.d.ts +116 -0
- package/dist/queue/jobQueue.d.ts.map +1 -0
- package/dist/queue/jobQueue.js +275 -0
- package/dist/queue/jobQueue.js.map +1 -0
- package/dist/queue/validationJob.d.ts +69 -0
- package/dist/queue/validationJob.d.ts.map +1 -0
- package/dist/queue/validationJob.js +435 -0
- package/dist/queue/validationJob.js.map +1 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +328 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/validationReportStore.d.ts +170 -0
- package/dist/resources/validationReportStore.d.ts.map +1 -0
- package/dist/resources/validationReportStore.js +515 -0
- package/dist/resources/validationReportStore.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +102 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/asyncValidation.d.ts +19 -0
- package/dist/tools/asyncValidation.d.ts.map +1 -0
- package/dist/tools/asyncValidation.js +346 -0
- package/dist/tools/asyncValidation.js.map +1 -0
- package/dist/tools/buildContext.d.ts +17 -0
- package/dist/tools/buildContext.d.ts.map +1 -0
- package/dist/tools/buildContext.js +188 -0
- package/dist/tools/buildContext.js.map +1 -0
- package/dist/tools/getDependencyGraph.d.ts +16 -0
- package/dist/tools/getDependencyGraph.d.ts.map +1 -0
- package/dist/tools/getDependencyGraph.js +436 -0
- package/dist/tools/getDependencyGraph.js.map +1 -0
- package/dist/tools/incrementalValidation.d.ts +71 -0
- package/dist/tools/incrementalValidation.d.ts.map +1 -0
- package/dist/tools/incrementalValidation.js +203 -0
- package/dist/tools/incrementalValidation.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/validateCode.d.ts +17 -0
- package/dist/tools/validateCode.d.ts.map +1 -0
- package/dist/tools/validateCode.js +368 -0
- package/dist/tools/validateCode.js.map +1 -0
- package/dist/tools/validateCodeLite.d.ts +2 -0
- package/dist/tools/validateCodeLite.d.ts.map +1 -0
- package/dist/tools/validateCodeLite.js +2 -0
- package/dist/tools/validateCodeLite.js.map +1 -0
- package/dist/tools/validation/builtins.d.ts +92 -0
- package/dist/tools/validation/builtins.d.ts.map +1 -0
- package/dist/tools/validation/builtins.js +2184 -0
- package/dist/tools/validation/builtins.js.map +1 -0
- package/dist/tools/validation/contextualNaming.d.ts +99 -0
- package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
- package/dist/tools/validation/contextualNaming.js +959 -0
- package/dist/tools/validation/contextualNaming.js.map +1 -0
- package/dist/tools/validation/deadCode.d.ts +115 -0
- package/dist/tools/validation/deadCode.d.ts.map +1 -0
- package/dist/tools/validation/deadCode.js +861 -0
- package/dist/tools/validation/deadCode.js.map +1 -0
- package/dist/tools/validation/extractors/index.d.ts +131 -0
- package/dist/tools/validation/extractors/index.d.ts.map +1 -0
- package/dist/tools/validation/extractors/index.js +233 -0
- package/dist/tools/validation/extractors/index.js.map +1 -0
- package/dist/tools/validation/extractors/javascript.d.ts +73 -0
- package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
- package/dist/tools/validation/extractors/javascript.js +1841 -0
- package/dist/tools/validation/extractors/javascript.js.map +1 -0
- package/dist/tools/validation/extractors/python.d.ts +93 -0
- package/dist/tools/validation/extractors/python.d.ts.map +1 -0
- package/dist/tools/validation/extractors/python.js +799 -0
- package/dist/tools/validation/extractors/python.js.map +1 -0
- package/dist/tools/validation/manifest.d.ts +45 -0
- package/dist/tools/validation/manifest.d.ts.map +1 -0
- package/dist/tools/validation/manifest.js +719 -0
- package/dist/tools/validation/manifest.js.map +1 -0
- package/dist/tools/validation/parser.d.ts +58 -0
- package/dist/tools/validation/parser.d.ts.map +1 -0
- package/dist/tools/validation/parser.js +232 -0
- package/dist/tools/validation/parser.js.map +1 -0
- package/dist/tools/validation/registry.d.ts +15 -0
- package/dist/tools/validation/registry.d.ts.map +1 -0
- package/dist/tools/validation/registry.js +169 -0
- package/dist/tools/validation/registry.js.map +1 -0
- package/dist/tools/validation/scoring.d.ts +54 -0
- package/dist/tools/validation/scoring.d.ts.map +1 -0
- package/dist/tools/validation/scoring.js +242 -0
- package/dist/tools/validation/scoring.js.map +1 -0
- package/dist/tools/validation/types.d.ts +120 -0
- package/dist/tools/validation/types.d.ts.map +1 -0
- package/dist/tools/validation/types.js +11 -0
- package/dist/tools/validation/types.js.map +1 -0
- package/dist/tools/validation/unusedLocals.d.ts +36 -0
- package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
- package/dist/tools/validation/unusedLocals.js +333 -0
- package/dist/tools/validation/unusedLocals.js.map +1 -0
- package/dist/tools/validation/validation.d.ts +98 -0
- package/dist/tools/validation/validation.d.ts.map +1 -0
- package/dist/tools/validation/validation.js +1837 -0
- package/dist/tools/validation/validation.js.map +1 -0
- package/dist/types/codeGraph.d.ts +163 -0
- package/dist/types/codeGraph.d.ts.map +1 -0
- package/dist/types/codeGraph.js +9 -0
- package/dist/types/codeGraph.js.map +1 -0
- package/dist/types/symbolGraph.d.ts +68 -0
- package/dist/types/symbolGraph.d.ts.map +1 -0
- package/dist/types/symbolGraph.js +10 -0
- package/dist/types/symbolGraph.js.map +1 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +7 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/fileFilter.d.ts +37 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +91 -0
- package/dist/utils/fileFilter.js.map +1 -0
- package/dist/utils/gitUtils.d.ts +28 -0
- package/dist/utils/gitUtils.d.ts.map +1 -0
- package/dist/utils/gitUtils.js +81 -0
- package/dist/utils/gitUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/serialization.d.ts +25 -0
- package/dist/utils/serialization.d.ts.map +1 -0
- package/dist/utils/serialization.js +53 -0
- package/dist/utils/serialization.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Extractor Module - Python-specific AST Extraction
|
|
3
|
+
*
|
|
4
|
+
* This module extracts symbols, usages, imports, and type references from Python code
|
|
5
|
+
* using Tree-sitter AST parsing. It handles Python-specific syntax including:
|
|
6
|
+
* - Function and class definitions
|
|
7
|
+
* - Method definitions with class context
|
|
8
|
+
* - Decorators
|
|
9
|
+
* - Async functions
|
|
10
|
+
* - Import statements (import and from...import)
|
|
11
|
+
* - Type hints and annotations
|
|
12
|
+
* - Class inheritance
|
|
13
|
+
*
|
|
14
|
+
* @format
|
|
15
|
+
*/
|
|
16
|
+
import { isPythonBuiltin, isPythonBuiltinType } from "../builtins.js";
|
|
17
|
+
// Common project-internal import prefixes for Python
|
|
18
|
+
const INTERNAL_PREFIXES = [
|
|
19
|
+
"app.", "src.", "tests.", "test.", "lib.", "core.", "api.",
|
|
20
|
+
"models.", "services.", "utils.", "helpers.", "config.",
|
|
21
|
+
"schemas.", "routers.", "routes.", "views.", "controllers.",
|
|
22
|
+
"handlers.", "middleware.", "database.", "db.",
|
|
23
|
+
];
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Main Extraction Functions
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Extract all symbol definitions from Python AST.
|
|
29
|
+
* Recursively traverses the AST to find function definitions, class definitions,
|
|
30
|
+
* method definitions, and module-level variable assignments.
|
|
31
|
+
*
|
|
32
|
+
* @param node - The AST node to extract symbols from
|
|
33
|
+
* @param code - The source code string
|
|
34
|
+
* @param filePath - Path to the file being analyzed
|
|
35
|
+
* @param symbols - Array to accumulate extracted symbols
|
|
36
|
+
* @param currentClass - Name of the current class context (for methods), or null
|
|
37
|
+
*/
|
|
38
|
+
export function extractPythonSymbols(node, code, filePath, symbols, currentClass) {
|
|
39
|
+
if (!node)
|
|
40
|
+
return;
|
|
41
|
+
switch (node.type) {
|
|
42
|
+
case "function_definition": {
|
|
43
|
+
const nameNode = node.childForFieldName("name");
|
|
44
|
+
const paramsNode = node.childForFieldName("parameters");
|
|
45
|
+
const decorators = getDecorators(node, code);
|
|
46
|
+
if (nameNode) {
|
|
47
|
+
const name = getText(nameNode, code);
|
|
48
|
+
const params = extractPythonParams(paramsNode, code);
|
|
49
|
+
const isMethod = currentClass !== null;
|
|
50
|
+
const isAsync = node.children.some((c) => c.type === "async" || getText(c, code) === "async");
|
|
51
|
+
// Detect API Routing (Semantic Bridge)
|
|
52
|
+
const routingDecorators = decorators.filter(d => d.includes(".route") ||
|
|
53
|
+
d.includes(".get") ||
|
|
54
|
+
d.includes(".post") ||
|
|
55
|
+
d.includes(".put") ||
|
|
56
|
+
d.includes(".delete") ||
|
|
57
|
+
d.includes(".patch"));
|
|
58
|
+
if (routingDecorators.length > 0) {
|
|
59
|
+
for (const d of routingDecorators) {
|
|
60
|
+
// Extract route pattern: @app.route("/api/user") -> /api/user
|
|
61
|
+
const routeMatch = d.match(/["']([^"']+)["']/);
|
|
62
|
+
if (routeMatch) {
|
|
63
|
+
symbols.push({
|
|
64
|
+
name: routeMatch[1],
|
|
65
|
+
type: "route",
|
|
66
|
+
file: filePath,
|
|
67
|
+
line: node.startPosition.row + 1,
|
|
68
|
+
column: node.startPosition.column,
|
|
69
|
+
scope: name, // Associate with the function handling it
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
symbols.push({
|
|
75
|
+
name,
|
|
76
|
+
type: isMethod ? "method" : "function",
|
|
77
|
+
file: filePath,
|
|
78
|
+
line: node.startPosition.row + 1,
|
|
79
|
+
column: node.startPosition.column,
|
|
80
|
+
params,
|
|
81
|
+
paramCount: params.length,
|
|
82
|
+
scope: currentClass || undefined,
|
|
83
|
+
isAsync,
|
|
84
|
+
decorators,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "class_definition": {
|
|
90
|
+
const nameNode = node.childForFieldName("name");
|
|
91
|
+
const decorators = getDecorators(node, code);
|
|
92
|
+
if (nameNode) {
|
|
93
|
+
const className = getText(nameNode, code);
|
|
94
|
+
symbols.push({
|
|
95
|
+
name: className,
|
|
96
|
+
type: "class",
|
|
97
|
+
file: filePath,
|
|
98
|
+
line: node.startPosition.row + 1,
|
|
99
|
+
column: node.startPosition.column,
|
|
100
|
+
decorators,
|
|
101
|
+
});
|
|
102
|
+
// Process class body with class context
|
|
103
|
+
const bodyNode = node.childForFieldName("body");
|
|
104
|
+
if (bodyNode) {
|
|
105
|
+
for (const child of bodyNode.children) {
|
|
106
|
+
extractPythonSymbols(child, code, filePath, symbols, className);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return; // Don't recurse normally, we handled the body
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "assignment": {
|
|
114
|
+
// Top-level assignments (module variables)
|
|
115
|
+
// In tree-sitter-python, module-level assignments are:
|
|
116
|
+
// module > expression_statement > assignment
|
|
117
|
+
const isModuleLevel = node.parent?.type === "module" ||
|
|
118
|
+
(node.parent?.type === "expression_statement" &&
|
|
119
|
+
node.parent?.parent?.type === "module");
|
|
120
|
+
// Class-level assignments (class attributes)
|
|
121
|
+
const isClassLevel = !isModuleLevel &&
|
|
122
|
+
currentClass !== null &&
|
|
123
|
+
(node.parent?.type === "expression_statement" &&
|
|
124
|
+
node.parent?.parent?.type === "block" &&
|
|
125
|
+
node.parent?.parent?.parent?.type === "class_definition");
|
|
126
|
+
if (isModuleLevel || isClassLevel) {
|
|
127
|
+
const leftNode = node.childForFieldName("left");
|
|
128
|
+
if (leftNode?.type === "identifier") {
|
|
129
|
+
const name = getText(leftNode, code);
|
|
130
|
+
symbols.push({
|
|
131
|
+
name,
|
|
132
|
+
type: "variable",
|
|
133
|
+
file: filePath,
|
|
134
|
+
line: node.startPosition.row + 1,
|
|
135
|
+
column: node.startPosition.column,
|
|
136
|
+
isExported: isModuleLevel,
|
|
137
|
+
scope: isClassLevel ? currentClass || undefined : undefined,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Recurse into children
|
|
145
|
+
for (const child of node.children) {
|
|
146
|
+
extractPythonSymbols(child, code, filePath, symbols, currentClass);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Collect all locally-defined identifier names in a Python AST.
|
|
151
|
+
* This pre-pass collects assignment targets, function parameters, for-loop variables,
|
|
152
|
+
* with-as targets, except-as targets, comprehension variables, and function/class names.
|
|
153
|
+
* Used to prevent false positives when these names appear as references later in the code.
|
|
154
|
+
*
|
|
155
|
+
* @param node - The root AST node to walk
|
|
156
|
+
* @param code - The source code string
|
|
157
|
+
* @param definitions - Set to accumulate locally-defined names
|
|
158
|
+
*/
|
|
159
|
+
export function collectPythonLocalDefinitions(node, code, definitions) {
|
|
160
|
+
if (!node)
|
|
161
|
+
return;
|
|
162
|
+
switch (node.type) {
|
|
163
|
+
case "identifier": {
|
|
164
|
+
const name = getText(node, code);
|
|
165
|
+
const parent = node.parent;
|
|
166
|
+
if (!parent)
|
|
167
|
+
break;
|
|
168
|
+
// Assignment target: x = ...
|
|
169
|
+
if (parent.type === "assignment" && parent.childForFieldName("left")?.id === node.id) {
|
|
170
|
+
definitions.add(name);
|
|
171
|
+
}
|
|
172
|
+
// Augmented assignment: x += ...
|
|
173
|
+
if (parent.type === "augmented_assignment" && parent.childForFieldName("left")?.id === node.id) {
|
|
174
|
+
definitions.add(name);
|
|
175
|
+
}
|
|
176
|
+
// Function/class definition name
|
|
177
|
+
if ((parent.type === "function_definition" || parent.type === "class_definition") &&
|
|
178
|
+
parent.childForFieldName("name")?.id === node.id) {
|
|
179
|
+
definitions.add(name);
|
|
180
|
+
}
|
|
181
|
+
// Function parameters (all types)
|
|
182
|
+
if (parent.type === "parameters" || parent.type === "typed_parameter" ||
|
|
183
|
+
parent.type === "default_parameter" || parent.type === "typed_default_parameter" ||
|
|
184
|
+
parent.type === "list_splat_pattern" || parent.type === "dictionary_splat_pattern") {
|
|
185
|
+
definitions.add(name);
|
|
186
|
+
}
|
|
187
|
+
// Lambda parameters
|
|
188
|
+
if (parent.type === "lambda_parameters") {
|
|
189
|
+
definitions.add(name);
|
|
190
|
+
}
|
|
191
|
+
// For-loop variable: for x in ...
|
|
192
|
+
if (parent.type === "for_statement" && parent.childForFieldName("left")?.id === node.id) {
|
|
193
|
+
definitions.add(name);
|
|
194
|
+
}
|
|
195
|
+
// Tuple/pattern unpacking targets
|
|
196
|
+
if (parent.type === "pattern_list" || parent.type === "tuple_pattern") {
|
|
197
|
+
definitions.add(name);
|
|
198
|
+
}
|
|
199
|
+
// With-as / except-as target
|
|
200
|
+
if (parent.type === "as_pattern_target") {
|
|
201
|
+
definitions.add(name);
|
|
202
|
+
}
|
|
203
|
+
// Comprehension variable
|
|
204
|
+
if (parent.type === "for_in_clause" && parent.childForFieldName("left")?.id === node.id) {
|
|
205
|
+
definitions.add(name);
|
|
206
|
+
}
|
|
207
|
+
// Walrus operator target
|
|
208
|
+
if (parent.type === "named_expression" && parent.childForFieldName("name")?.id === node.id) {
|
|
209
|
+
definitions.add(name);
|
|
210
|
+
}
|
|
211
|
+
// Global/nonlocal declarations
|
|
212
|
+
if (parent.type === "global_statement" || parent.type === "nonlocal_statement") {
|
|
213
|
+
definitions.add(name);
|
|
214
|
+
}
|
|
215
|
+
// Unpacking target in assignment
|
|
216
|
+
if (isUnpackingTarget(node)) {
|
|
217
|
+
definitions.add(name);
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (const child of node.children) {
|
|
223
|
+
if (child) {
|
|
224
|
+
collectPythonLocalDefinitions(child, code, definitions);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Extract all symbol usages from Python AST.
|
|
230
|
+
* Finds function calls, method calls, and attribute access.
|
|
231
|
+
* Skips imported symbols, built-in functions, and locally-defined variables to avoid false positives.
|
|
232
|
+
*
|
|
233
|
+
* @param node - The AST node to extract usages from
|
|
234
|
+
* @param code - The source code string
|
|
235
|
+
* @param usages - Array to accumulate extracted usages
|
|
236
|
+
* @param externalSymbols - Set of imported symbol names to skip
|
|
237
|
+
* @param localDefinitions - Set of locally-defined names to skip (from collectPythonLocalDefinitions)
|
|
238
|
+
*/
|
|
239
|
+
export function extractPythonUsages(node, code, usages, externalSymbols, localDefinitions, isRoot = true) {
|
|
240
|
+
if (!node)
|
|
241
|
+
return;
|
|
242
|
+
// If localDefinitions weren't provided (direct extractor usage), collect them once.
|
|
243
|
+
// This mirrors the behavior of the unified extractor wrapper and prevents false
|
|
244
|
+
// positives for local variables (with-as targets, except-as targets, params, etc.).
|
|
245
|
+
if (isRoot && !localDefinitions) {
|
|
246
|
+
localDefinitions = new Set();
|
|
247
|
+
collectPythonLocalDefinitions(node, code, localDefinitions);
|
|
248
|
+
}
|
|
249
|
+
switch (node.type) {
|
|
250
|
+
case "call": {
|
|
251
|
+
const funcNode = node.childForFieldName("function");
|
|
252
|
+
const argsNode = node.childForFieldName("arguments");
|
|
253
|
+
const argCount = countArgs(argsNode);
|
|
254
|
+
if (funcNode) {
|
|
255
|
+
if (funcNode.type === "identifier") {
|
|
256
|
+
// Simple function call: func()
|
|
257
|
+
const name = getText(funcNode, code);
|
|
258
|
+
if (isPythonBuiltin(name))
|
|
259
|
+
break;
|
|
260
|
+
if (localDefinitions?.has(name))
|
|
261
|
+
break;
|
|
262
|
+
usages.push({
|
|
263
|
+
name,
|
|
264
|
+
type: "call",
|
|
265
|
+
line: node.startPosition.row + 1,
|
|
266
|
+
column: node.startPosition.column,
|
|
267
|
+
code: getLineText(code, node.startPosition.row),
|
|
268
|
+
argCount,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
else if (funcNode.type === "attribute") {
|
|
272
|
+
// Method call: obj.method()
|
|
273
|
+
const objNode = funcNode.childForFieldName("object");
|
|
274
|
+
const attrNode = funcNode.childForFieldName("attribute");
|
|
275
|
+
if (objNode && attrNode) {
|
|
276
|
+
const obj = getText(objNode, code);
|
|
277
|
+
const method = getText(attrNode, code);
|
|
278
|
+
// Only track method calls with simple object references (identifier, attribute, subscript)
|
|
279
|
+
// Skip complex expressions like (end - start).total_seconds() where the "object"
|
|
280
|
+
// is a binary expression — these produce meaningless object names for validation
|
|
281
|
+
const simpleObjTypes = new Set([
|
|
282
|
+
"identifier", "attribute", "subscript", "call",
|
|
283
|
+
"parenthesized_expression",
|
|
284
|
+
]);
|
|
285
|
+
const isSimpleObj = simpleObjTypes.has(objNode.type) &&
|
|
286
|
+
(objNode.type !== "parenthesized_expression" ||
|
|
287
|
+
objNode.namedChildren[0]?.type === "identifier");
|
|
288
|
+
if (isSimpleObj) {
|
|
289
|
+
if (isPythonBuiltin(method))
|
|
290
|
+
break;
|
|
291
|
+
usages.push({
|
|
292
|
+
name: method,
|
|
293
|
+
type: "methodCall",
|
|
294
|
+
object: obj,
|
|
295
|
+
line: node.startPosition.row + 1,
|
|
296
|
+
column: node.startPosition.column,
|
|
297
|
+
code: getLineText(code, node.startPosition.row),
|
|
298
|
+
argCount,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case "identifier": {
|
|
307
|
+
const name = getText(node, code);
|
|
308
|
+
// Builtins are skipped to reduce false positives, BUT imported names should still
|
|
309
|
+
// be allowed through so we can correctly track import usage (prevents unusedImport
|
|
310
|
+
// false positives for patterns like: `from x import settings; settings.env`).
|
|
311
|
+
if (isPythonBuiltin(name) && !externalSymbols.has(name))
|
|
312
|
+
break;
|
|
313
|
+
const parent = node.parent;
|
|
314
|
+
if (!parent)
|
|
315
|
+
break;
|
|
316
|
+
// Skip decorator identifiers: @staticmethod, @app.route(...), etc.
|
|
317
|
+
// These frequently cause false positives when treated as normal references.
|
|
318
|
+
// Decorator metadata is captured separately via symbol extraction (see getDecorators).
|
|
319
|
+
let decoratorAncestor = parent;
|
|
320
|
+
while (decoratorAncestor) {
|
|
321
|
+
if (decoratorAncestor.type === "decorator") {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
decoratorAncestor = decoratorAncestor.parent;
|
|
325
|
+
}
|
|
326
|
+
// Skip if it's an attribute name (obj.NAME) — handled by methodCall extraction
|
|
327
|
+
if (parent.type === "attribute" && parent.childForFieldName("attribute")?.id === node.id)
|
|
328
|
+
break;
|
|
329
|
+
// Skip if it's a function/class definition name
|
|
330
|
+
if ((parent.type === "function_definition" || parent.type === "class_definition") &&
|
|
331
|
+
parent.childForFieldName("name")?.id === node.id)
|
|
332
|
+
break;
|
|
333
|
+
// Skip if it's a parameter definition (function params, lambda params)
|
|
334
|
+
if (parent.type === "parameters" || parent.type === "typed_parameter" || parent.type === "default_parameter" ||
|
|
335
|
+
parent.type === "typed_default_parameter" || parent.type === "list_splat_pattern" || parent.type === "dictionary_splat_pattern" ||
|
|
336
|
+
parent.type === "lambda_parameters")
|
|
337
|
+
break;
|
|
338
|
+
// Skip if it's part of an import statement
|
|
339
|
+
if (parent.type === "dotted_name" || parent.type === "aliased_import" ||
|
|
340
|
+
parent.type === "import_statement" || parent.type === "import_from_statement")
|
|
341
|
+
break;
|
|
342
|
+
// Skip assignment targets (left side of =) — these are definitions, not usages
|
|
343
|
+
if (parent.type === "assignment" && parent.childForFieldName("left")?.id === node.id)
|
|
344
|
+
break;
|
|
345
|
+
if (parent.type === "augmented_assignment" && parent.childForFieldName("left")?.id === node.id)
|
|
346
|
+
break;
|
|
347
|
+
// Skip if it's the target in a for loop (for x in items)
|
|
348
|
+
if (parent.type === "for_statement" && parent.childForFieldName("left")?.id === node.id)
|
|
349
|
+
break;
|
|
350
|
+
// Also handle tuple unpacking in for loops: for k, v in items
|
|
351
|
+
if (parent.type === "pattern_list" || parent.type === "tuple_pattern")
|
|
352
|
+
break;
|
|
353
|
+
// Skip if it's the variable in a with statement (with open() as f)
|
|
354
|
+
// or except clause (except Exception as e)
|
|
355
|
+
// Tree-sitter wraps the alias identifier in an as_pattern_target node
|
|
356
|
+
if (parent.type === "as_pattern_target")
|
|
357
|
+
break;
|
|
358
|
+
if (parent.type === "as_pattern" && parent.childForFieldName("alias")?.id === node.id)
|
|
359
|
+
break;
|
|
360
|
+
if (parent.type === "except_clause")
|
|
361
|
+
break;
|
|
362
|
+
// Skip keyword argument names (func(key=value) — skip "key")
|
|
363
|
+
if (parent.type === "keyword_argument" && parent.childForFieldName("name")?.id === node.id)
|
|
364
|
+
break;
|
|
365
|
+
// Skip comprehension variables (x for x in items)
|
|
366
|
+
if (parent.type === "for_in_clause" && parent.childForFieldName("left")?.id === node.id)
|
|
367
|
+
break;
|
|
368
|
+
// Decorators are intentionally skipped above.
|
|
369
|
+
// Skip if it's the left side of an annotated assignment (x: int = 5)
|
|
370
|
+
if (parent.type === "type" && parent.parent?.type === "assignment")
|
|
371
|
+
break;
|
|
372
|
+
// Skip walrus operator target (:= )
|
|
373
|
+
if (parent.type === "named_expression" && parent.childForFieldName("name")?.id === node.id)
|
|
374
|
+
break;
|
|
375
|
+
// Skip global/nonlocal declarations
|
|
376
|
+
if (parent.type === "global_statement" || parent.type === "nonlocal_statement")
|
|
377
|
+
break;
|
|
378
|
+
// Skip if it's a tuple/list unpacking target in assignment
|
|
379
|
+
if (isUnpackingTarget(node))
|
|
380
|
+
break;
|
|
381
|
+
// Skip locally-defined variables (function params, assignment targets, loop vars, etc.)
|
|
382
|
+
// These are local scope — CodeGuardian only validates project-level symbols
|
|
383
|
+
if (localDefinitions?.has(name))
|
|
384
|
+
break;
|
|
385
|
+
// If we reach here, it's a genuine reference usage
|
|
386
|
+
const exists = usages.some(u => u.line === node.startPosition.row + 1 && u.column === node.startPosition.column);
|
|
387
|
+
if (!exists) {
|
|
388
|
+
usages.push({
|
|
389
|
+
name,
|
|
390
|
+
type: "reference",
|
|
391
|
+
line: node.startPosition.row + 1,
|
|
392
|
+
column: node.startPosition.column,
|
|
393
|
+
code: getLineText(code, node.startPosition.row),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
for (const child of node.children) {
|
|
400
|
+
if (child) {
|
|
401
|
+
extractPythonUsages(child, code, usages, externalSymbols, localDefinitions, false);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Extract all import statements from Python AST.
|
|
407
|
+
* Handles both "import module" and "from module import name" statements.
|
|
408
|
+
* Determines if imports are external (third-party) or internal (project files).
|
|
409
|
+
*
|
|
410
|
+
* @param node - The AST node to extract imports from
|
|
411
|
+
* @param code - The source code string
|
|
412
|
+
* @param imports - Array to accumulate extracted imports
|
|
413
|
+
*/
|
|
414
|
+
export function extractPythonImports(node, code, imports) {
|
|
415
|
+
if (!node)
|
|
416
|
+
return;
|
|
417
|
+
switch (node.type) {
|
|
418
|
+
case "import_statement": {
|
|
419
|
+
// import module OR import module as alias
|
|
420
|
+
for (const child of node.children) {
|
|
421
|
+
if (!child)
|
|
422
|
+
continue;
|
|
423
|
+
if (child.type === "dotted_name") {
|
|
424
|
+
// Simple: import json, import app.models
|
|
425
|
+
const module = getText(child, code);
|
|
426
|
+
const isInternal = module.startsWith(".") ||
|
|
427
|
+
INTERNAL_PREFIXES.some((prefix) => module.startsWith(prefix) || module === prefix.slice(0, -1));
|
|
428
|
+
imports.push({
|
|
429
|
+
module,
|
|
430
|
+
names: [{ imported: module, local: module }],
|
|
431
|
+
isExternal: !isInternal,
|
|
432
|
+
line: node.startPosition.row + 1,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
else if (child.type === "aliased_import") {
|
|
436
|
+
// Aliased: import sqlalchemy as sa
|
|
437
|
+
const nameNode = child.childForFieldName("name");
|
|
438
|
+
const aliasNode = child.childForFieldName("alias");
|
|
439
|
+
if (nameNode) {
|
|
440
|
+
const module = getText(nameNode, code);
|
|
441
|
+
const local = aliasNode ? getText(aliasNode, code) : module;
|
|
442
|
+
const isInternal = module.startsWith(".") ||
|
|
443
|
+
INTERNAL_PREFIXES.some((prefix) => module.startsWith(prefix) || module === prefix.slice(0, -1));
|
|
444
|
+
imports.push({
|
|
445
|
+
module,
|
|
446
|
+
names: [{ imported: module, local }],
|
|
447
|
+
isExternal: !isInternal,
|
|
448
|
+
line: node.startPosition.row + 1,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
case "import_from_statement": {
|
|
456
|
+
// from module import x, y
|
|
457
|
+
const moduleNode = node.childForFieldName("module_name");
|
|
458
|
+
if (moduleNode) {
|
|
459
|
+
const module = getText(moduleNode, code);
|
|
460
|
+
const names = [];
|
|
461
|
+
for (const child of node.children) {
|
|
462
|
+
if (!child)
|
|
463
|
+
continue;
|
|
464
|
+
if (child.type === "dotted_name" && child !== moduleNode) {
|
|
465
|
+
const name = getText(child, code);
|
|
466
|
+
names.push({ imported: name, local: name });
|
|
467
|
+
}
|
|
468
|
+
else if (child.type === "aliased_import") {
|
|
469
|
+
const nameNode = child.childForFieldName("name");
|
|
470
|
+
const aliasNode = child.childForFieldName("alias");
|
|
471
|
+
if (nameNode) {
|
|
472
|
+
const imported = getText(nameNode, code);
|
|
473
|
+
const local = aliasNode ? getText(aliasNode, code) : imported;
|
|
474
|
+
names.push({ imported, local });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Determine if import is external (third-party package) or internal (project file)
|
|
479
|
+
// Internal imports in Python:
|
|
480
|
+
// - Relative imports: from . import x, from .. import x
|
|
481
|
+
// - Common project prefixes: app., src., tests., lib., core., api., models., services., etc.
|
|
482
|
+
const isInternal = module.startsWith(".") ||
|
|
483
|
+
INTERNAL_PREFIXES.some((prefix) => module.startsWith(prefix) || module === prefix.slice(0, -1));
|
|
484
|
+
const isExternal = !isInternal;
|
|
485
|
+
imports.push({
|
|
486
|
+
module,
|
|
487
|
+
names,
|
|
488
|
+
isExternal,
|
|
489
|
+
line: node.startPosition.row + 1,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
for (const child of node.children) {
|
|
496
|
+
if (child) {
|
|
497
|
+
extractPythonImports(child, code, imports);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Extract type references from Python type hints.
|
|
503
|
+
* Handles function parameter annotations, return types, variable annotations,
|
|
504
|
+
* and class inheritance.
|
|
505
|
+
*
|
|
506
|
+
* @param node - The AST node to extract type references from
|
|
507
|
+
* @param code - The source code string
|
|
508
|
+
* @param references - Array to accumulate extracted type references
|
|
509
|
+
*/
|
|
510
|
+
export function extractPythonTypeReferences(node, code, references) {
|
|
511
|
+
if (!node)
|
|
512
|
+
return;
|
|
513
|
+
switch (node.type) {
|
|
514
|
+
// Function parameter type annotation: def func(x: TypeName)
|
|
515
|
+
case "typed_parameter": {
|
|
516
|
+
const typeNode = node.childForFieldName("type");
|
|
517
|
+
if (typeNode) {
|
|
518
|
+
extractPythonTypeNamesFromNode(typeNode, code, references, "typeAnnotation");
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
// Function return type: def func() -> ReturnType
|
|
523
|
+
case "function_definition": {
|
|
524
|
+
const returnTypeNode = node.childForFieldName("return_type");
|
|
525
|
+
if (returnTypeNode) {
|
|
526
|
+
extractPythonTypeNamesFromNode(returnTypeNode, code, references, "returnType");
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
// Variable annotation: x: TypeName = value
|
|
531
|
+
case "type": {
|
|
532
|
+
// This is the type node in an annotated assignment
|
|
533
|
+
extractPythonTypeNamesFromNode(node, code, references, "typeAnnotation");
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
// Class inheritance: class Foo(BaseClass)
|
|
537
|
+
case "argument_list": {
|
|
538
|
+
const parent = node.parent;
|
|
539
|
+
if (parent?.type === "class_definition") {
|
|
540
|
+
for (const child of node.children) {
|
|
541
|
+
if (child && child.type === "identifier") {
|
|
542
|
+
const name = getText(child, code);
|
|
543
|
+
if (!isPythonBuiltinType(name)) {
|
|
544
|
+
references.push({
|
|
545
|
+
name,
|
|
546
|
+
context: "extends",
|
|
547
|
+
line: child.startPosition.row + 1,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Recurse into children
|
|
557
|
+
for (const child of node.children) {
|
|
558
|
+
if (child) {
|
|
559
|
+
extractPythonTypeReferences(child, code, references);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// ============================================================================
|
|
564
|
+
// Helper Functions
|
|
565
|
+
// ============================================================================
|
|
566
|
+
/**
|
|
567
|
+
* Extract parameter names from a Python function parameters node.
|
|
568
|
+
* Filters out 'self' and 'cls' parameters which are implicit in Python methods.
|
|
569
|
+
*
|
|
570
|
+
* @param paramsNode - The parameters node from the function definition
|
|
571
|
+
* @param code - The source code string
|
|
572
|
+
* @returns Array of parameter names (excluding self/cls)
|
|
573
|
+
*/
|
|
574
|
+
export function extractPythonParams(paramsNode, code) {
|
|
575
|
+
if (!paramsNode)
|
|
576
|
+
return [];
|
|
577
|
+
const params = [];
|
|
578
|
+
for (const child of paramsNode.children) {
|
|
579
|
+
if (child.type === "identifier") {
|
|
580
|
+
const name = getText(child, code);
|
|
581
|
+
if (name !== "self" && name !== "cls") {
|
|
582
|
+
params.push(name);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else if (child.type === "typed_parameter" ||
|
|
586
|
+
child.type === "default_parameter") {
|
|
587
|
+
const nameNode = child.childForFieldName("name") || child.children[0];
|
|
588
|
+
if (nameNode) {
|
|
589
|
+
const name = getText(nameNode, code);
|
|
590
|
+
if (name !== "self" && name !== "cls") {
|
|
591
|
+
params.push(name);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return params;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Extract type names from Python type hint nodes.
|
|
600
|
+
* Handles simple types, generic types, union types, and attribute access.
|
|
601
|
+
* Recursively processes complex type expressions.
|
|
602
|
+
*
|
|
603
|
+
* @param node - The type hint node to extract names from
|
|
604
|
+
* @param code - The source code string
|
|
605
|
+
* @param references - Array to accumulate extracted type references
|
|
606
|
+
* @param context - The context in which the type appears
|
|
607
|
+
*/
|
|
608
|
+
export function extractPythonTypeNamesFromNode(node, code, references, context) {
|
|
609
|
+
if (!node)
|
|
610
|
+
return;
|
|
611
|
+
switch (node.type) {
|
|
612
|
+
// Simple type: TypeName
|
|
613
|
+
case "identifier": {
|
|
614
|
+
const name = getText(node, code);
|
|
615
|
+
if (!isPythonBuiltinType(name)) {
|
|
616
|
+
references.push({
|
|
617
|
+
name,
|
|
618
|
+
context,
|
|
619
|
+
line: node.startPosition.row + 1,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
// Generic type: List[TypeName], Dict[K, V], Optional[T]
|
|
625
|
+
case "subscript": {
|
|
626
|
+
const valueNode = node.childForFieldName("value");
|
|
627
|
+
const subscriptNode = node.childForFieldName("subscript");
|
|
628
|
+
if (valueNode) {
|
|
629
|
+
const typeName = getText(valueNode, code);
|
|
630
|
+
if (!isPythonBuiltinType(typeName)) {
|
|
631
|
+
references.push({
|
|
632
|
+
name: typeName,
|
|
633
|
+
context,
|
|
634
|
+
line: valueNode.startPosition.row + 1,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Extract type arguments
|
|
639
|
+
if (subscriptNode) {
|
|
640
|
+
extractPythonTypeNamesFromNode(subscriptNode, code, references, "genericParam");
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
// Union type (Python 3.10+): TypeA | TypeB
|
|
645
|
+
case "binary_operator": {
|
|
646
|
+
const operator = node.children.find((c) => c && getText(c, code) === "|");
|
|
647
|
+
if (operator) {
|
|
648
|
+
const left = node.childForFieldName("left");
|
|
649
|
+
const right = node.childForFieldName("right");
|
|
650
|
+
if (left)
|
|
651
|
+
extractPythonTypeNamesFromNode(left, code, references, context);
|
|
652
|
+
if (right)
|
|
653
|
+
extractPythonTypeNamesFromNode(right, code, references, context);
|
|
654
|
+
}
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
// Tuple of types in subscript: Dict[str, int] -> the "str, int" part
|
|
658
|
+
case "expression_list": {
|
|
659
|
+
for (const child of node.children) {
|
|
660
|
+
if (child && child.type !== ",") {
|
|
661
|
+
extractPythonTypeNamesFromNode(child, code, references, context);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
// Attribute access: module.TypeName
|
|
667
|
+
case "attribute": {
|
|
668
|
+
const name = getText(node, code);
|
|
669
|
+
references.push({
|
|
670
|
+
name,
|
|
671
|
+
context,
|
|
672
|
+
line: node.startPosition.row + 1,
|
|
673
|
+
});
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
// None type
|
|
677
|
+
case "none": {
|
|
678
|
+
// Skip - it's a builtin
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
default: {
|
|
682
|
+
// For other node types, recurse into children
|
|
683
|
+
for (const child of node.children) {
|
|
684
|
+
if (child) {
|
|
685
|
+
extractPythonTypeNamesFromNode(child, code, references, context);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// ============================================================================
|
|
692
|
+
// Internal Helper Functions
|
|
693
|
+
// ============================================================================
|
|
694
|
+
/**
|
|
695
|
+
* Extract text from an AST node using pre-computed indices.
|
|
696
|
+
* This is faster than using substring on each call.
|
|
697
|
+
*
|
|
698
|
+
* @param node - The AST node to extract text from
|
|
699
|
+
* @param code - The source code string
|
|
700
|
+
* @returns The text content of the node
|
|
701
|
+
*/
|
|
702
|
+
function getText(node, code) {
|
|
703
|
+
return code.slice(node.startIndex, node.endIndex);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Get a specific line from code.
|
|
707
|
+
* Uses simple split - caching would require a Map with string keys which has memory implications.
|
|
708
|
+
*
|
|
709
|
+
* @param code - The source code string
|
|
710
|
+
* @param lineIndex - The zero-based line index
|
|
711
|
+
* @returns The trimmed line text, or empty string if line doesn't exist
|
|
712
|
+
*/
|
|
713
|
+
function getLineText(code, lineIndex) {
|
|
714
|
+
const lines = code.split("\n");
|
|
715
|
+
return lines[lineIndex]?.trim() || "";
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Count the number of arguments in a function call.
|
|
719
|
+
* Excludes parentheses and commas from the count.
|
|
720
|
+
*
|
|
721
|
+
* @param argsNode - The arguments node from a call expression
|
|
722
|
+
* @returns The number of arguments
|
|
723
|
+
*/
|
|
724
|
+
function countArgs(argsNode) {
|
|
725
|
+
if (!argsNode)
|
|
726
|
+
return 0;
|
|
727
|
+
let count = 0;
|
|
728
|
+
for (const child of argsNode.children) {
|
|
729
|
+
if (child.type !== "(" && child.type !== ")" && child.type !== ",") {
|
|
730
|
+
count++;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return count;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Check if an identifier node is an unpacking target in an assignment.
|
|
737
|
+
* Handles: a, b = func() / [x, y] = items / (a, b) = items
|
|
738
|
+
* Also handles nested unpacking in for loops: for (a, b) in items
|
|
739
|
+
*
|
|
740
|
+
* @param node - The identifier node to check
|
|
741
|
+
* @returns true if the identifier is an unpacking target (definition, not usage)
|
|
742
|
+
*/
|
|
743
|
+
function isUnpackingTarget(node) {
|
|
744
|
+
let current = node.parent;
|
|
745
|
+
while (current) {
|
|
746
|
+
// If we hit a tuple/list that is the left side of an assignment, it's an unpacking target
|
|
747
|
+
if (current.type === "pattern_list" || current.type === "tuple_pattern" || current.type === "list_pattern") {
|
|
748
|
+
const grandparent = current.parent;
|
|
749
|
+
if (grandparent) {
|
|
750
|
+
if (grandparent.type === "assignment" && grandparent.childForFieldName("left")?.id === current.id)
|
|
751
|
+
return true;
|
|
752
|
+
if (grandparent.type === "for_statement" && grandparent.childForFieldName("left")?.id === current.id)
|
|
753
|
+
return true;
|
|
754
|
+
if (grandparent.type === "for_in_clause" && grandparent.childForFieldName("left")?.id === current.id)
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
// Also check expression_list (Python uses this for tuple unpacking without parens)
|
|
759
|
+
if (current.type === "expression_list") {
|
|
760
|
+
const grandparent = current.parent;
|
|
761
|
+
if (grandparent) {
|
|
762
|
+
if (grandparent.type === "assignment" && grandparent.childForFieldName("left")?.id === current.id)
|
|
763
|
+
return true;
|
|
764
|
+
if (grandparent.type === "for_statement" && grandparent.childForFieldName("left")?.id === current.id)
|
|
765
|
+
return true;
|
|
766
|
+
if (grandparent.type === "for_in_clause" && grandparent.childForFieldName("left")?.id === current.id)
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// Don't traverse too far up
|
|
771
|
+
if (current.type === "assignment" || current.type === "for_statement" || current.type === "for_in_clause" ||
|
|
772
|
+
current.type === "function_definition" || current.type === "class_definition" || current.type === "module") {
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
current = current.parent;
|
|
776
|
+
}
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Get decorators applied to a function or class definition.
|
|
781
|
+
* Looks for a decorated_definition parent node and extracts all decorator children.
|
|
782
|
+
*
|
|
783
|
+
* @param node - The function or class definition node
|
|
784
|
+
* @param code - The source code string
|
|
785
|
+
* @returns Array of decorator strings (including the @ symbol)
|
|
786
|
+
*/
|
|
787
|
+
function getDecorators(node, code) {
|
|
788
|
+
const decorators = [];
|
|
789
|
+
const parent = node.parent;
|
|
790
|
+
if (parent?.type === "decorated_definition") {
|
|
791
|
+
for (const child of parent.children) {
|
|
792
|
+
if (child.type === "decorator") {
|
|
793
|
+
decorators.push(getText(child, code));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return decorators;
|
|
798
|
+
}
|
|
799
|
+
//# sourceMappingURL=python.js.map
|