projscan 0.9.2 → 0.10.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.
Files changed (60) hide show
  1. package/README.md +14 -2
  2. package/dist/analyzers/deadCodeCheck.d.ts +10 -12
  3. package/dist/analyzers/deadCodeCheck.js +41 -69
  4. package/dist/analyzers/deadCodeCheck.js.map +1 -1
  5. package/dist/analyzers/pythonDependencyRiskCheck.d.ts +2 -0
  6. package/dist/analyzers/pythonDependencyRiskCheck.js +114 -0
  7. package/dist/analyzers/pythonDependencyRiskCheck.js.map +1 -0
  8. package/dist/analyzers/pythonLinterCheck.d.ts +2 -0
  9. package/dist/analyzers/pythonLinterCheck.js +119 -0
  10. package/dist/analyzers/pythonLinterCheck.js.map +1 -0
  11. package/dist/analyzers/pythonTestCheck.d.ts +2 -0
  12. package/dist/analyzers/pythonTestCheck.js +97 -0
  13. package/dist/analyzers/pythonTestCheck.js.map +1 -0
  14. package/dist/analyzers/pythonUnusedDependencyCheck.d.ts +2 -0
  15. package/dist/analyzers/pythonUnusedDependencyCheck.js +76 -0
  16. package/dist/analyzers/pythonUnusedDependencyCheck.js.map +1 -0
  17. package/dist/core/codeGraph.d.ts +6 -7
  18. package/dist/core/codeGraph.js +48 -72
  19. package/dist/core/codeGraph.js.map +1 -1
  20. package/dist/core/fileInspector.d.ts +3 -0
  21. package/dist/core/fileInspector.js +47 -2
  22. package/dist/core/fileInspector.js.map +1 -1
  23. package/dist/core/indexCache.js +5 -1
  24. package/dist/core/indexCache.js.map +1 -1
  25. package/dist/core/issueEngine.js +10 -0
  26. package/dist/core/issueEngine.js.map +1 -1
  27. package/dist/core/languages/LanguageAdapter.d.ts +36 -0
  28. package/dist/core/languages/LanguageAdapter.js +2 -0
  29. package/dist/core/languages/LanguageAdapter.js.map +1 -0
  30. package/dist/core/languages/javascriptAdapter.d.ts +2 -0
  31. package/dist/core/languages/javascriptAdapter.js +68 -0
  32. package/dist/core/languages/javascriptAdapter.js.map +1 -0
  33. package/dist/core/languages/pythonAdapter.d.ts +6 -0
  34. package/dist/core/languages/pythonAdapter.js +142 -0
  35. package/dist/core/languages/pythonAdapter.js.map +1 -0
  36. package/dist/core/languages/pythonExports.d.ts +28 -0
  37. package/dist/core/languages/pythonExports.js +169 -0
  38. package/dist/core/languages/pythonExports.js.map +1 -0
  39. package/dist/core/languages/pythonImports.d.ts +22 -0
  40. package/dist/core/languages/pythonImports.js +104 -0
  41. package/dist/core/languages/pythonImports.js.map +1 -0
  42. package/dist/core/languages/pythonManifests.d.ts +34 -0
  43. package/dist/core/languages/pythonManifests.js +344 -0
  44. package/dist/core/languages/pythonManifests.js.map +1 -0
  45. package/dist/core/languages/registry.d.ts +5 -0
  46. package/dist/core/languages/registry.js +30 -0
  47. package/dist/core/languages/registry.js.map +1 -0
  48. package/dist/core/languages/treeSitterLoader.d.ts +14 -0
  49. package/dist/core/languages/treeSitterLoader.js +75 -0
  50. package/dist/core/languages/treeSitterLoader.js.map +1 -0
  51. package/dist/core/searchIndex.js +8 -0
  52. package/dist/core/searchIndex.js.map +1 -1
  53. package/dist/grammars/tree-sitter-python.wasm +0 -0
  54. package/dist/grammars/web-tree-sitter.wasm +0 -0
  55. package/dist/mcp/tools.js +48 -0
  56. package/dist/mcp/tools.js.map +1 -1
  57. package/dist/types.d.ts +2 -0
  58. package/dist/utils/fileWalker.js +14 -0
  59. package/dist/utils/fileWalker.js.map +1 -1
  60. package/package.json +10 -5
@@ -0,0 +1,68 @@
1
+ import path from 'node:path';
2
+ import { parseSource } from '../ast.js';
3
+ const JS_EXTENSIONS = new Set([
4
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts',
5
+ ]);
6
+ const RESOLUTION_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
7
+ const NODE_BUILTINS = new Set([
8
+ 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto',
9
+ 'dgram', 'dns', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector',
10
+ 'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline',
11
+ 'repl', 'stream', 'string_decoder', 'sys', 'timers', 'tls', 'trace_events', 'tty', 'url', 'util',
12
+ 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
13
+ ]);
14
+ export const javascriptAdapter = {
15
+ id: 'javascript',
16
+ extensions: JS_EXTENSIONS,
17
+ sourceExtensions: JS_EXTENSIONS,
18
+ barrelBasenames: new Set(['index']),
19
+ parse(filePath, content) {
20
+ return parseSource(filePath, content);
21
+ },
22
+ resolveImport(importingFile, source, graphFiles) {
23
+ if (!(source.startsWith('.') || source.startsWith('/')))
24
+ return null;
25
+ const importingDir = path.posix.dirname(importingFile);
26
+ const base = path.posix.normalize(path.posix.join(importingDir, source));
27
+ if (graphFiles.has(base))
28
+ return base;
29
+ for (const ext of RESOLUTION_EXTS) {
30
+ if (graphFiles.has(base + ext))
31
+ return base + ext;
32
+ }
33
+ for (const ext of RESOLUTION_EXTS) {
34
+ const barrel = `${base}/index${ext}`;
35
+ if (graphFiles.has(barrel))
36
+ return barrel;
37
+ }
38
+ if (base.endsWith('.js')) {
39
+ const trimmed = base.slice(0, -3);
40
+ if (graphFiles.has(`${trimmed}.ts`))
41
+ return `${trimmed}.ts`;
42
+ if (graphFiles.has(`${trimmed}.tsx`))
43
+ return `${trimmed}.tsx`;
44
+ }
45
+ return null;
46
+ },
47
+ toPackageName(specifier) {
48
+ if (!specifier)
49
+ return null;
50
+ if (specifier.startsWith('.') || specifier.startsWith('/'))
51
+ return null;
52
+ if (specifier.startsWith('node:'))
53
+ return null;
54
+ if (NODE_BUILTINS.has(specifier))
55
+ return null;
56
+ if (specifier.startsWith('@')) {
57
+ const segments = specifier.split('/');
58
+ if (segments.length < 2)
59
+ return null;
60
+ return `${segments[0]}/${segments[1]}`;
61
+ }
62
+ return specifier.split('/')[0];
63
+ },
64
+ preparePackageRoots(_rootPath, _files) {
65
+ return {};
66
+ },
67
+ };
68
+ //# sourceMappingURL=javascriptAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"javascriptAdapter.js","sourceRoot":"","sources":["../../../src/core/languages/javascriptAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { LanguageAdapter } from './LanguageAdapter.js';
2
+ /**
3
+ * Python adapter skeleton. This bring-up stage only proves the parser loads
4
+ * and produces a tree. Imports/exports extraction follows in subsequent tasks.
5
+ */
6
+ export declare const pythonAdapter: LanguageAdapter;
@@ -0,0 +1,142 @@
1
+ import path from 'node:path';
2
+ import { createParserFor } from './treeSitterLoader.js';
3
+ import { extractPythonImports } from './pythonImports.js';
4
+ import { extractPythonExports } from './pythonExports.js';
5
+ import { detectPythonProject } from './pythonManifests.js';
6
+ // Pinned grammar: tree-sitter-python 0.25.0. See treeSitterLoader.ts.
7
+ const PY_EXTENSIONS = new Set(['.py', '.pyw', '.pyi']);
8
+ const MAX_PY_FILE = 1024 * 1024;
9
+ let parserPromise = null;
10
+ async function getParser() {
11
+ if (!parserPromise)
12
+ parserPromise = createParserFor('tree-sitter-python.wasm');
13
+ return parserPromise;
14
+ }
15
+ /**
16
+ * Python adapter skeleton. This bring-up stage only proves the parser loads
17
+ * and produces a tree. Imports/exports extraction follows in subsequent tasks.
18
+ */
19
+ export const pythonAdapter = {
20
+ id: 'python',
21
+ extensions: PY_EXTENSIONS,
22
+ sourceExtensions: new Set(['.py', '.pyw']),
23
+ barrelBasenames: new Set(['__init__']),
24
+ maxFileSize: MAX_PY_FILE,
25
+ async parse(_filePath, content) {
26
+ try {
27
+ const parser = await getParser();
28
+ const tree = parser.parse(content);
29
+ if (!tree || !tree.rootNode) {
30
+ return {
31
+ ok: false,
32
+ reason: 'tree-sitter returned null tree',
33
+ imports: [],
34
+ exports: [],
35
+ callSites: [],
36
+ lineCount: content ? content.split('\n').length : 0,
37
+ };
38
+ }
39
+ const imports = extractPythonImports(tree.rootNode);
40
+ const exports = extractPythonExports(tree.rootNode);
41
+ return {
42
+ ok: true,
43
+ imports,
44
+ exports,
45
+ callSites: [],
46
+ lineCount: content ? content.split('\n').length : 0,
47
+ };
48
+ }
49
+ catch (err) {
50
+ const msg = err instanceof Error ? err.message : String(err);
51
+ return {
52
+ ok: false,
53
+ reason: `python parse failure: ${msg.slice(0, 120)}`,
54
+ imports: [],
55
+ exports: [],
56
+ callSites: [],
57
+ lineCount: content ? content.split('\n').length : 0,
58
+ };
59
+ }
60
+ },
61
+ resolveImport(importingFile, source, graphFiles, context) {
62
+ return resolvePythonImport(importingFile, source, graphFiles, context);
63
+ },
64
+ toPackageName(source) {
65
+ if (!source)
66
+ return null;
67
+ if (source.startsWith('.'))
68
+ return null;
69
+ // Package name is the first dotted segment, normalized for comparison.
70
+ return source.split('.')[0].toLowerCase();
71
+ },
72
+ async preparePackageRoots(rootPath, files) {
73
+ const info = await detectPythonProject(rootPath, files);
74
+ return {
75
+ packageRoots: info?.packageRoots ?? [],
76
+ meta: info ? { pythonProject: info } : undefined,
77
+ };
78
+ },
79
+ };
80
+ /**
81
+ * Resolve a Python import to a repo-local file path, or null if it refers to
82
+ * a third-party package or otherwise cannot be resolved.
83
+ *
84
+ * Algorithm summary:
85
+ * 1. Absolute (`from pkg.mod.sub import x`): for each packageRoot, probe
86
+ * `<root>/pkg/mod/sub.py` then `<root>/pkg/mod/sub/__init__.py`.
87
+ * 2. Relative (`from . import x` / `from .sibling import y`): count leading
88
+ * dots, walk that many parents from the importing file's directory,
89
+ * then append the remaining segments.
90
+ * 3. If nothing matches, return null (caller classifies as third-party).
91
+ */
92
+ function resolvePythonImport(importingFile, source, graphFiles, context) {
93
+ const packageRoots = context.packageRoots ?? ['.'];
94
+ if (source.startsWith('.')) {
95
+ const m = /^(\.+)(.*)$/.exec(source);
96
+ if (!m)
97
+ return null;
98
+ const dotCount = m[1].length;
99
+ const remainder = m[2]; // may start with a `.` separator? No - the `.`s are already captured.
100
+ const importingDir = path.posix.dirname(importingFile);
101
+ // dotCount === 1 means "current package"; 2 means "one level up".
102
+ let dir = importingDir;
103
+ for (let i = 0; i < dotCount - 1; i++) {
104
+ dir = path.posix.dirname(dir);
105
+ if (dir === '.' || dir === '' || dir === '/') {
106
+ dir = '';
107
+ break;
108
+ }
109
+ }
110
+ const segments = remainder ? remainder.split('.').filter(Boolean) : [];
111
+ const base = segments.length > 0 ? [dir, ...segments].filter(Boolean).join('/') : dir;
112
+ return probeModuleOrPackage(base, graphFiles);
113
+ }
114
+ // Absolute import.
115
+ const segments = source.split('.').filter(Boolean);
116
+ if (segments.length === 0)
117
+ return null;
118
+ for (const root of packageRoots) {
119
+ const rootSegs = root === '.' || root === '' ? [] : root.split('/').filter(Boolean);
120
+ const base = [...rootSegs, ...segments].join('/');
121
+ const hit = probeModuleOrPackage(base, graphFiles);
122
+ if (hit)
123
+ return hit;
124
+ }
125
+ return null;
126
+ }
127
+ function probeModuleOrPackage(base, graphFiles) {
128
+ if (!base)
129
+ return null;
130
+ // Try `base.py` first (more specific), then `base/__init__.py`.
131
+ const asModule = `${base}.py`;
132
+ if (graphFiles.has(asModule))
133
+ return asModule;
134
+ const asInit = `${base}/__init__.py`;
135
+ if (graphFiles.has(asInit))
136
+ return asInit;
137
+ const asPyw = `${base}.pyw`;
138
+ if (graphFiles.has(asPyw))
139
+ return asPyw;
140
+ return null;
141
+ }
142
+ //# sourceMappingURL=pythonAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonAdapter.js","sourceRoot":"","sources":["../../../src/core/languages/pythonAdapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { AstExport } from '../ast.js';
2
+ interface TsNode {
3
+ type: string;
4
+ text: string;
5
+ startPosition: {
6
+ row: number;
7
+ };
8
+ namedChildren: TsNode[];
9
+ firstNamedChild?: TsNode | null;
10
+ childForFieldName?(name: string): TsNode | null;
11
+ }
12
+ /**
13
+ * Extract "exports" from a tree-sitter-python AST.
14
+ *
15
+ * Python has no language-level export concept. projscan defines exports as
16
+ * the top-level public API of a module:
17
+ * - top-level `def` / `async def` / `class`
18
+ * - top-level assignments whose LHS is a plain identifier
19
+ * - names brought in via `from .mod import foo` (re-exports)
20
+ *
21
+ * Filtered: single-underscore-prefixed names (`_private`), dunder names other
22
+ * than ones that appear in `__all__`. If `__all__` is declared at the top
23
+ * level as a list/tuple of string literals, it is authoritative: only names
24
+ * in `__all__` are exports (regardless of underscore), and names not in
25
+ * `__all__` are NOT exports.
26
+ */
27
+ export declare function extractPythonExports(root: TsNode): AstExport[];
28
+ export {};
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Extract "exports" from a tree-sitter-python AST.
3
+ *
4
+ * Python has no language-level export concept. projscan defines exports as
5
+ * the top-level public API of a module:
6
+ * - top-level `def` / `async def` / `class`
7
+ * - top-level assignments whose LHS is a plain identifier
8
+ * - names brought in via `from .mod import foo` (re-exports)
9
+ *
10
+ * Filtered: single-underscore-prefixed names (`_private`), dunder names other
11
+ * than ones that appear in `__all__`. If `__all__` is declared at the top
12
+ * level as a list/tuple of string literals, it is authoritative: only names
13
+ * in `__all__` are exports (regardless of underscore), and names not in
14
+ * `__all__` are NOT exports.
15
+ */
16
+ export function extractPythonExports(root) {
17
+ const allNames = readDunderAll(root);
18
+ const found = [];
19
+ for (const child of root.namedChildren) {
20
+ switch (child.type) {
21
+ case 'function_definition': {
22
+ const nameNode = nameOfFunctionOrClass(child);
23
+ if (nameNode) {
24
+ found.push({
25
+ name: nameNode.text,
26
+ kind: 'function',
27
+ typeOnly: false,
28
+ line: child.startPosition.row + 1,
29
+ });
30
+ }
31
+ break;
32
+ }
33
+ case 'class_definition': {
34
+ const nameNode = nameOfFunctionOrClass(child);
35
+ if (nameNode) {
36
+ found.push({
37
+ name: nameNode.text,
38
+ kind: 'class',
39
+ typeOnly: false,
40
+ line: child.startPosition.row + 1,
41
+ });
42
+ }
43
+ break;
44
+ }
45
+ case 'decorated_definition': {
46
+ // `@decorator\ndef foo(): ...` → the real def/class is a child.
47
+ const inner = child.namedChildren.find((c) => c.type === 'function_definition' || c.type === 'class_definition');
48
+ if (inner) {
49
+ const nameNode = nameOfFunctionOrClass(inner);
50
+ if (nameNode) {
51
+ found.push({
52
+ name: nameNode.text,
53
+ kind: inner.type === 'class_definition' ? 'class' : 'function',
54
+ typeOnly: false,
55
+ line: child.startPosition.row + 1,
56
+ });
57
+ }
58
+ }
59
+ break;
60
+ }
61
+ case 'expression_statement': {
62
+ collectAssignmentTargets(child, found);
63
+ break;
64
+ }
65
+ case 'import_from_statement': {
66
+ // Re-exports: `from .mod import foo` makes `foo` accessible as a
67
+ // module-level name. Honor `__all__` gatekeeping below.
68
+ collectFromImportRebindings(child, found);
69
+ break;
70
+ }
71
+ default:
72
+ break;
73
+ }
74
+ }
75
+ if (allNames) {
76
+ const set = new Set(allNames);
77
+ return found.filter((e) => set.has(e.name));
78
+ }
79
+ return found.filter((e) => !e.name.startsWith('_'));
80
+ }
81
+ function nameOfFunctionOrClass(node) {
82
+ const named = node.childForFieldName?.('name');
83
+ if (named)
84
+ return named;
85
+ // Fallback: first named identifier child.
86
+ return node.namedChildren.find((c) => c.type === 'identifier') ?? null;
87
+ }
88
+ function collectAssignmentTargets(exprStmt, out) {
89
+ const assignment = exprStmt.namedChildren.find((c) => c.type === 'assignment');
90
+ if (!assignment)
91
+ return;
92
+ const line = exprStmt.startPosition.row + 1;
93
+ const lhs = assignment.namedChildren[0];
94
+ if (!lhs)
95
+ return;
96
+ if (lhs.type === 'identifier') {
97
+ out.push({ name: lhs.text, kind: 'variable', typeOnly: false, line });
98
+ }
99
+ else if (lhs.type === 'pattern_list' || lhs.type === 'tuple_pattern') {
100
+ for (const part of lhs.namedChildren) {
101
+ if (part.type === 'identifier') {
102
+ out.push({ name: part.text, kind: 'variable', typeOnly: false, line });
103
+ }
104
+ }
105
+ }
106
+ }
107
+ function collectFromImportRebindings(node, out) {
108
+ const line = node.startPosition.row + 1;
109
+ const children = node.namedChildren;
110
+ if (children.length < 2)
111
+ return;
112
+ for (let i = 1; i < children.length; i++) {
113
+ const c = children[i];
114
+ if (c.type === 'dotted_name') {
115
+ out.push({ name: c.text.split('.').pop(), kind: 'unknown', typeOnly: false, line });
116
+ }
117
+ else if (c.type === 'aliased_import') {
118
+ const alias = c.namedChildren.find((g) => g.type === 'identifier');
119
+ if (alias)
120
+ out.push({ name: alias.text, kind: 'unknown', typeOnly: false, line });
121
+ }
122
+ // wildcard_import: `from x import *` does not produce known names.
123
+ }
124
+ }
125
+ /**
126
+ * Read `__all__ = [...]` or `__all__ = (...)` if declared at the top level.
127
+ * Returns the list of string-literal entries, or null if __all__ is absent
128
+ * or declared with a non-literal value (e.g. `__all__ = compute()`).
129
+ */
130
+ function readDunderAll(root) {
131
+ for (const child of root.namedChildren) {
132
+ if (child.type !== 'expression_statement')
133
+ continue;
134
+ const assignment = child.namedChildren.find((c) => c.type === 'assignment');
135
+ if (!assignment)
136
+ continue;
137
+ const lhs = assignment.namedChildren[0];
138
+ if (!lhs || lhs.type !== 'identifier' || lhs.text !== '__all__')
139
+ continue;
140
+ const rhs = assignment.namedChildren[assignment.namedChildren.length - 1];
141
+ if (!rhs)
142
+ continue;
143
+ if (rhs.type !== 'list' && rhs.type !== 'tuple')
144
+ return null;
145
+ const names = [];
146
+ for (const item of rhs.namedChildren) {
147
+ if (item.type === 'string') {
148
+ const unwrapped = unwrapStringLiteral(item);
149
+ if (unwrapped !== null)
150
+ names.push(unwrapped);
151
+ }
152
+ }
153
+ return names;
154
+ }
155
+ return null;
156
+ }
157
+ function unwrapStringLiteral(node) {
158
+ // `"foo"` → "foo". tree-sitter-python represents this as a `string` node
159
+ // whose content is `string_content` text; for our needs, trimming quotes is
160
+ // good enough - supports 'foo', "foo", '''foo''', """foo""" consistently.
161
+ const raw = node.text;
162
+ const m = /^[uUbBrRfF]?(["'])(?:\1\1)?((?:\\.|(?!\1\1\1|\1).)*?)(?:\1\1)?\1$/s.exec(raw);
163
+ if (m)
164
+ return m[2];
165
+ // Fallback: strip leading/trailing quote characters generically.
166
+ const stripped = raw.replace(/^[uUbBrRfF]?['"]+/, '').replace(/['"]+$/, '');
167
+ return stripped || null;
168
+ }
169
+ //# sourceMappingURL=pythonExports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonExports.js","sourceRoot":"","sources":["../../../src/core/languages/pythonExports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { AstImport } from '../ast.js';
2
+ /** Minimal tree-sitter node surface we depend on. */
3
+ interface TsNode {
4
+ type: string;
5
+ text: string;
6
+ startPosition: {
7
+ row: number;
8
+ };
9
+ namedChildren: TsNode[];
10
+ firstNamedChild?: TsNode | null;
11
+ childForFieldName?(name: string): TsNode | null;
12
+ }
13
+ /**
14
+ * Extract import edges from a tree-sitter-python AST.
15
+ *
16
+ * Traversal: walk top-level statements in the `module` node, and descend into
17
+ * `try_statement` bodies so that conditional imports (e.g. `try: import x
18
+ * except ImportError: import y as x`) are captured. Function/class-scoped
19
+ * imports are intentionally ignored - they match JS/TS module-level semantics.
20
+ */
21
+ export declare function extractPythonImports(root: TsNode): AstImport[];
22
+ export {};
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Extract import edges from a tree-sitter-python AST.
3
+ *
4
+ * Traversal: walk top-level statements in the `module` node, and descend into
5
+ * `try_statement` bodies so that conditional imports (e.g. `try: import x
6
+ * except ImportError: import y as x`) are captured. Function/class-scoped
7
+ * imports are intentionally ignored - they match JS/TS module-level semantics.
8
+ */
9
+ export function extractPythonImports(root) {
10
+ const imports = [];
11
+ visit(root, imports);
12
+ return imports;
13
+ }
14
+ // Types we descend through to find conditional top-level imports. Does NOT
15
+ // include function/class bodies - imports scoped inside a function are local
16
+ // variables, not module-level dependencies (mirrors JS/TS semantics).
17
+ const DESCEND_TYPES = new Set([
18
+ 'try_statement',
19
+ 'if_statement',
20
+ 'elif_clause',
21
+ 'else_clause',
22
+ 'except_clause',
23
+ 'finally_clause',
24
+ 'with_statement',
25
+ 'with_clause',
26
+ 'block',
27
+ ]);
28
+ function visit(node, out) {
29
+ for (const child of node.namedChildren) {
30
+ switch (child.type) {
31
+ case 'import_statement':
32
+ handleImport(child, out);
33
+ break;
34
+ case 'import_from_statement':
35
+ handleImportFrom(child, out);
36
+ break;
37
+ case 'future_import_statement':
38
+ // Language pragma, not a real dependency.
39
+ break;
40
+ default:
41
+ if (DESCEND_TYPES.has(child.type))
42
+ visit(child, out);
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ function handleImport(node, out) {
48
+ // `import a` | `import a.b.c` | `import a as b` | `import a, b`
49
+ const line = node.startPosition.row + 1;
50
+ for (const child of node.namedChildren) {
51
+ if (child.type === 'dotted_name') {
52
+ out.push({ source: child.text, kind: 'static', specifiers: [], typeOnly: false, line });
53
+ }
54
+ else if (child.type === 'aliased_import') {
55
+ const inner = child.namedChildren.find((c) => c.type === 'dotted_name');
56
+ if (inner) {
57
+ out.push({ source: inner.text, kind: 'static', specifiers: [], typeOnly: false, line });
58
+ }
59
+ }
60
+ }
61
+ }
62
+ function handleImportFrom(node, out) {
63
+ const line = node.startPosition.row + 1;
64
+ const children = node.namedChildren;
65
+ if (children.length === 0)
66
+ return;
67
+ // First child is either dotted_name (absolute) or relative_import.
68
+ const head = children[0];
69
+ let source;
70
+ if (head.type === 'relative_import') {
71
+ source = head.text.trim();
72
+ }
73
+ else if (head.type === 'dotted_name') {
74
+ source = head.text;
75
+ }
76
+ else {
77
+ return;
78
+ }
79
+ const specifiers = [];
80
+ let sawWildcard = false;
81
+ for (let i = 1; i < children.length; i++) {
82
+ const c = children[i];
83
+ if (c.type === 'wildcard_import') {
84
+ sawWildcard = true;
85
+ break;
86
+ }
87
+ if (c.type === 'dotted_name') {
88
+ specifiers.push(c.text);
89
+ }
90
+ else if (c.type === 'aliased_import') {
91
+ const original = c.namedChildren.find((g) => g.type === 'dotted_name');
92
+ if (original)
93
+ specifiers.push(original.text);
94
+ }
95
+ }
96
+ out.push({
97
+ source,
98
+ kind: 'static',
99
+ specifiers: sawWildcard ? ['*'] : specifiers,
100
+ typeOnly: false,
101
+ line,
102
+ });
103
+ }
104
+ //# sourceMappingURL=pythonImports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonImports.js","sourceRoot":"","sources":["../../../src/core/languages/pythonImports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { FileEntry } from '../../types.js';
2
+ /**
3
+ * Lightweight parsing of Python project manifests. We do NOT pull a TOML
4
+ * parser - regex-based extraction is good enough for the 80% case where
5
+ * projects use standard layouts. More sophisticated parsing is deferred.
6
+ */
7
+ export interface PythonDeclaredDep {
8
+ name: string;
9
+ /** Raw version spec from the manifest (may be empty if unpinned). */
10
+ versionSpec: string;
11
+ /** Which file declared this dep. */
12
+ source: string;
13
+ /** 1-based line in the source file, or 0 if unknown. */
14
+ line: number;
15
+ /** main (runtime) vs dev (test/lint groups). */
16
+ scope: 'main' | 'dev';
17
+ }
18
+ export interface PythonProjectInfo {
19
+ /** Directories under which `from pkg import ...` should resolve. */
20
+ packageRoots: string[];
21
+ /** pyproject.toml / setup.py / setup.cfg path (relative to repo root), if any. */
22
+ manifestFiles: string[];
23
+ /** Declared dependencies across all manifests. */
24
+ declared: PythonDeclaredDep[];
25
+ /** Lockfiles present (any of poetry.lock, Pipfile.lock, pdm.lock, uv.lock, conda-lock.yml, requirements.txt with pins). */
26
+ hasLockfile: boolean;
27
+ }
28
+ export declare function detectPythonProject(rootPath: string, files: FileEntry[]): Promise<PythonProjectInfo | null>;
29
+ export declare function parsePyproject(content: string): PythonDeclaredDep[];
30
+ export declare function parseRequirements(content: string, sourceFile: string, scope: 'main' | 'dev'): PythonDeclaredDep[];
31
+ export declare function splitPep508(spec: string): {
32
+ name: string;
33
+ versionSpec: string;
34
+ };