@opensip-cli/graph-python 0.1.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 (76) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +31 -0
  4. package/dist/__tests__/body-digest.test.d.ts +18 -0
  5. package/dist/__tests__/body-digest.test.d.ts.map +1 -0
  6. package/dist/__tests__/body-digest.test.js +61 -0
  7. package/dist/__tests__/body-digest.test.js.map +1 -0
  8. package/dist/__tests__/branch-coverage.test.d.ts +19 -0
  9. package/dist/__tests__/branch-coverage.test.d.ts.map +1 -0
  10. package/dist/__tests__/branch-coverage.test.js +292 -0
  11. package/dist/__tests__/branch-coverage.test.js.map +1 -0
  12. package/dist/__tests__/cache-key.test.d.ts +8 -0
  13. package/dist/__tests__/cache-key.test.d.ts.map +1 -0
  14. package/dist/__tests__/cache-key.test.js +55 -0
  15. package/dist/__tests__/cache-key.test.js.map +1 -0
  16. package/dist/__tests__/depends-on-emission.test.d.ts +21 -0
  17. package/dist/__tests__/depends-on-emission.test.d.ts.map +1 -0
  18. package/dist/__tests__/depends-on-emission.test.js +189 -0
  19. package/dist/__tests__/depends-on-emission.test.js.map +1 -0
  20. package/dist/__tests__/discover.test.d.ts +9 -0
  21. package/dist/__tests__/discover.test.d.ts.map +1 -0
  22. package/dist/__tests__/discover.test.js +64 -0
  23. package/dist/__tests__/discover.test.js.map +1 -0
  24. package/dist/__tests__/parse.test.d.ts +8 -0
  25. package/dist/__tests__/parse.test.d.ts.map +1 -0
  26. package/dist/__tests__/parse.test.js +37 -0
  27. package/dist/__tests__/parse.test.js.map +1 -0
  28. package/dist/__tests__/resolve.test.d.ts +11 -0
  29. package/dist/__tests__/resolve.test.d.ts.map +1 -0
  30. package/dist/__tests__/resolve.test.js +176 -0
  31. package/dist/__tests__/resolve.test.js.map +1 -0
  32. package/dist/__tests__/walk-shapes.test.d.ts +15 -0
  33. package/dist/__tests__/walk-shapes.test.d.ts.map +1 -0
  34. package/dist/__tests__/walk-shapes.test.js +156 -0
  35. package/dist/__tests__/walk-shapes.test.js.map +1 -0
  36. package/dist/__tests__/walk.test.d.ts +10 -0
  37. package/dist/__tests__/walk.test.d.ts.map +1 -0
  38. package/dist/__tests__/walk.test.js +71 -0
  39. package/dist/__tests__/walk.test.js.map +1 -0
  40. package/dist/body-digest.d.ts +18 -0
  41. package/dist/body-digest.d.ts.map +1 -0
  42. package/dist/body-digest.js +88 -0
  43. package/dist/body-digest.js.map +1 -0
  44. package/dist/cache-key.d.ts +22 -0
  45. package/dist/cache-key.d.ts.map +1 -0
  46. package/dist/cache-key.js +52 -0
  47. package/dist/cache-key.js.map +1 -0
  48. package/dist/discover.d.ts +19 -0
  49. package/dist/discover.d.ts.map +1 -0
  50. package/dist/discover.js +36 -0
  51. package/dist/discover.js.map +1 -0
  52. package/dist/index.d.ts +51 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +53 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/parse.d.ts +22 -0
  57. package/dist/parse.d.ts.map +1 -0
  58. package/dist/parse.js +16 -0
  59. package/dist/parse.js.map +1 -0
  60. package/dist/resolve.d.ts +35 -0
  61. package/dist/resolve.d.ts.map +1 -0
  62. package/dist/resolve.js +294 -0
  63. package/dist/resolve.js.map +1 -0
  64. package/dist/rule-hints.d.ts +13 -0
  65. package/dist/rule-hints.d.ts.map +1 -0
  66. package/dist/rule-hints.js +70 -0
  67. package/dist/rule-hints.js.map +1 -0
  68. package/dist/walk-dependencies.d.ts +27 -0
  69. package/dist/walk-dependencies.d.ts.map +1 -0
  70. package/dist/walk-dependencies.js +152 -0
  71. package/dist/walk-dependencies.js.map +1 -0
  72. package/dist/walk.d.ts +42 -0
  73. package/dist/walk.d.ts.map +1 -0
  74. package/dist/walk.js +281 -0
  75. package/dist/walk.js.map +1 -0
  76. package/package.json +54 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Branch-coverage tests for lang-python/resolve.ts.
3
+ *
4
+ * Exercises the resolution ladder by feeding small Python fixtures
5
+ * through the full discover/parse/walk/resolve pipeline. Each test
6
+ * targets one of the call-target shapes (identifier, attribute,
7
+ * subscript/lambda) and one rung of the confidence ladder
8
+ * (unknown / static / method-dispatch).
9
+ */
10
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
14
+ import { pythonGraphAdapter } from '../index.js';
15
+ function runPipeline(dir) {
16
+ const discovery = pythonGraphAdapter.discoverFiles({ cwd: dir });
17
+ const parsed = pythonGraphAdapter.parseProject({
18
+ projectDirAbs: discovery.projectDirAbs,
19
+ files: discovery.files,
20
+ compilerOptions: discovery.compilerOptions,
21
+ resolutionMode: 'exact',
22
+ });
23
+ const walk = pythonGraphAdapter.walkProject({
24
+ project: parsed.project,
25
+ projectDirAbs: discovery.projectDirAbs,
26
+ files: discovery.files,
27
+ });
28
+ const catalog = {
29
+ version: '3.0',
30
+ tool: 'graph',
31
+ language: 'python',
32
+ cacheKey: 'test',
33
+ builtAt: new Date().toISOString(),
34
+ functions: walk.occurrences,
35
+ };
36
+ const resolved = pythonGraphAdapter.resolveCallSites({
37
+ project: parsed.project,
38
+ catalog,
39
+ callSites: walk.callSites,
40
+ projectDirAbs: discovery.projectDirAbs,
41
+ resolutionMode: 'exact',
42
+ });
43
+ return {
44
+ occurrences: walk.occurrences,
45
+ edgesByOwner: resolved.edgesByOwner,
46
+ stats: resolved.stats,
47
+ };
48
+ }
49
+ function flattenEdges(edgesByOwner) {
50
+ const out = [];
51
+ for (const edges of edgesByOwner.values()) {
52
+ for (const e of edges)
53
+ out.push(e);
54
+ }
55
+ return out;
56
+ }
57
+ describe('lang-python resolve.ts — branches', () => {
58
+ let dir;
59
+ beforeEach(() => {
60
+ dir = mkdtempSync(join(tmpdir(), 'graph-python-resolve-'));
61
+ });
62
+ afterEach(() => {
63
+ rmSync(dir, { recursive: true, force: true });
64
+ });
65
+ it('resolves a direct identifier call to a single static medium-confidence edge', () => {
66
+ writeFileSync(join(dir, 'main.py'), `def helper(x):\n return x\n\ndef entry():\n return helper(1)\n`, 'utf8');
67
+ const { edgesByOwner, stats } = runPipeline(dir);
68
+ const all = flattenEdges(edgesByOwner);
69
+ const helperCall = all.find((e) => e.text.startsWith('helper('));
70
+ expect(helperCall).toBeDefined();
71
+ expect(helperCall?.resolution).toBe('static');
72
+ expect(helperCall?.confidence).toBe('medium');
73
+ expect(helperCall?.to).toHaveLength(1);
74
+ expect(stats.resolvedMedium).toBeGreaterThan(0);
75
+ });
76
+ it('resolves attribute calls (obj.method()) by attribute name', () => {
77
+ writeFileSync(join(dir, 'main.py'), `class G:\n def greet(self, who):\n return who\n\ndef use():\n g = G()\n return g.greet("x")\n`, 'utf8');
78
+ const { edgesByOwner } = runPipeline(dir);
79
+ const all = flattenEdges(edgesByOwner);
80
+ // g.greet("x") should resolve by attribute name 'greet' to the method
81
+ const greetCall = all.find((e) => e.text.includes('g.greet'));
82
+ expect(greetCall).toBeDefined();
83
+ expect(greetCall?.resolution).toBe('static');
84
+ expect(greetCall?.to).toHaveLength(1);
85
+ });
86
+ it('emits method-dispatch low-confidence when multiple catalog entries share a name', () => {
87
+ writeFileSync(join(dir, 'a.py'), `class A:\n def run(self):\n return 1\n`, 'utf8');
88
+ writeFileSync(join(dir, 'b.py'), `class B:\n def run(self):\n return 2\n`, 'utf8');
89
+ writeFileSync(join(dir, 'use.py'), `def use(obj):\n return obj.run()\n`, 'utf8');
90
+ const { edgesByOwner, stats } = runPipeline(dir);
91
+ const all = flattenEdges(edgesByOwner);
92
+ const runCall = all.find((e) => e.text.includes('obj.run'));
93
+ expect(runCall).toBeDefined();
94
+ expect(runCall?.resolution).toBe('method-dispatch');
95
+ expect(runCall?.confidence).toBe('low');
96
+ expect(runCall?.to.length).toBeGreaterThanOrEqual(2);
97
+ expect(stats.resolvedLow).toBeGreaterThan(0);
98
+ });
99
+ it('emits unknown/low when calling an unknown name', () => {
100
+ writeFileSync(join(dir, 'main.py'), `def entry():\n return missing_name(1)\n`, 'utf8');
101
+ const { edgesByOwner, stats } = runPipeline(dir);
102
+ const all = flattenEdges(edgesByOwner);
103
+ const missing = all.find((e) => e.text.includes('missing_name'));
104
+ expect(missing).toBeDefined();
105
+ expect(missing?.resolution).toBe('unknown');
106
+ expect(missing?.confidence).toBe('low');
107
+ expect(missing?.to).toHaveLength(0);
108
+ expect(stats.unresolved).toBeGreaterThan(0);
109
+ });
110
+ it('emits unknown for non-identifier/attribute call shapes (lambda IIFE)', () => {
111
+ writeFileSync(join(dir, 'main.py'), `def entry():\n return (lambda x: x)(1)\n`, 'utf8');
112
+ const { edgesByOwner } = runPipeline(dir);
113
+ const all = flattenEdges(edgesByOwner);
114
+ // The (lambda...)(1) is a call whose target is a parenthesized
115
+ // lambda — neither identifier nor attribute → unknown.
116
+ expect(all.some((e) => e.resolution === 'unknown')).toBe(true);
117
+ });
118
+ it('emits unknown for subscript calls (e.g. fns[0]())', () => {
119
+ writeFileSync(join(dir, 'main.py'), `def entry(fns):\n return fns[0]()\n`, 'utf8');
120
+ const { edgesByOwner } = runPipeline(dir);
121
+ const all = flattenEdges(edgesByOwner);
122
+ expect(all.some((e) => e.resolution === 'unknown')).toBe(true);
123
+ });
124
+ it('marks return value as discarded for expression-statement calls', () => {
125
+ writeFileSync(join(dir, 'main.py'), `def helper(x):\n return x\n\ndef entry():\n helper(1)\n return helper(2)\n`, 'utf8');
126
+ const { edgesByOwner } = runPipeline(dir);
127
+ const all = flattenEdges(edgesByOwner);
128
+ const helperEdges = all.filter((e) => e.text.startsWith('helper('));
129
+ // One should be discarded (statement-level), one should not (in return)
130
+ expect(helperEdges.some((e) => e.discarded === true)).toBe(true);
131
+ expect(helperEdges.some((e) => e.discarded === false)).toBe(true);
132
+ });
133
+ it('treats parenthesized statement-level calls as discarded', () => {
134
+ writeFileSync(join(dir, 'main.py'), `def helper():\n return 1\n\ndef entry():\n (helper())\n`, 'utf8');
135
+ const { edgesByOwner } = runPipeline(dir);
136
+ const all = flattenEdges(edgesByOwner);
137
+ const helperEdges = all.filter((e) => e.text.startsWith('helper('));
138
+ expect(helperEdges.some((e) => e.discarded === true)).toBe(true);
139
+ });
140
+ it('treats await-wrapped statement-level calls as discarded', () => {
141
+ writeFileSync(join(dir, 'main.py'), `async def helper():\n return 1\n\nasync def entry():\n await helper()\n`, 'utf8');
142
+ const { edgesByOwner } = runPipeline(dir);
143
+ const all = flattenEdges(edgesByOwner);
144
+ const helperEdges = all.filter((e) => e.text.startsWith('helper('));
145
+ expect(helperEdges.some((e) => e.discarded === true)).toBe(true);
146
+ });
147
+ it('emits a creation edge for lambdas nested in a parent function', () => {
148
+ writeFileSync(join(dir, 'main.py'), `def entry():\n f = lambda n: n + 1\n return f(2)\n`, 'utf8');
149
+ const { edgesByOwner, stats } = runPipeline(dir);
150
+ // creation edges record resolution='static' confidence='high' (per
151
+ // pushCreationEdge in @opensip-cli/graph)
152
+ const all = flattenEdges(edgesByOwner);
153
+ expect(all.some((e) => e.resolution === 'static' && e.confidence === 'high')).toBe(true);
154
+ expect(stats.resolvedHigh).toBeGreaterThan(0);
155
+ });
156
+ it('skips synthetic/module-init names from the name index (names starting with <)', () => {
157
+ // <module-init> names start with '<' and should not be lookup targets
158
+ writeFileSync(join(dir, 'a.py'), `def f():\n return 1\n`, 'utf8');
159
+ writeFileSync(join(dir, 'b.py'), `def caller():\n return f()\n`, 'utf8');
160
+ const { edgesByOwner } = runPipeline(dir);
161
+ const all = flattenEdges(edgesByOwner);
162
+ // The synthetic names like <module-init:a.py> exist as keys in
163
+ // occurrences but should never appear as call targets. Just ensure
164
+ // f() resolves successfully — its presence proves the index works
165
+ // even when synthetic names coexist.
166
+ const fCall = all.find((e) => e.text.startsWith('f('));
167
+ expect(fCall?.resolution).toBe('static');
168
+ });
169
+ it('produces stable stats counters that sum to totalCallSites', () => {
170
+ writeFileSync(join(dir, 'main.py'), `def known():\n return 1\n\ndef entry():\n known()\n unknown_fn()\n return 1\n`, 'utf8');
171
+ const { stats } = runPipeline(dir);
172
+ expect(stats.totalCallSites).toBe(stats.resolvedHigh + stats.resolvedMedium + stats.resolvedLow + stats.unresolved);
173
+ expect(stats.totalCallSites).toBeGreaterThanOrEqual(2);
174
+ });
175
+ });
176
+ //# sourceMappingURL=resolve.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.test.js","sourceRoot":"","sources":["../../src/__tests__/resolve.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Function/parameter-shape coverage tests for lang-python/walk.ts.
3
+ *
4
+ * Specifically targets:
5
+ * - Typed parameters / default parameters / typed-default parameters.
6
+ * - Methods inside classes (kind: 'method').
7
+ * - `__init__` (kind: 'constructor').
8
+ * - Lambdas as siblings of a function (visited but not nested in a
9
+ * function frame).
10
+ * - Test-file detection (tests/ folder + test_*.py + _test.py).
11
+ * - Generated-file detection (dist/, build/, .generated.).
12
+ * - Visibility ('exported' vs 'module-local').
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=walk-shapes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk-shapes.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/walk-shapes.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Function/parameter-shape coverage tests for lang-python/walk.ts.
3
+ *
4
+ * Specifically targets:
5
+ * - Typed parameters / default parameters / typed-default parameters.
6
+ * - Methods inside classes (kind: 'method').
7
+ * - `__init__` (kind: 'constructor').
8
+ * - Lambdas as siblings of a function (visited but not nested in a
9
+ * function frame).
10
+ * - Test-file detection (tests/ folder + test_*.py + _test.py).
11
+ * - Generated-file detection (dist/, build/, .generated.).
12
+ * - Visibility ('exported' vs 'module-local').
13
+ */
14
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
18
+ import { pythonGraphAdapter } from '../index.js';
19
+ import { isTestFile } from '../walk.js';
20
+ function runWalk(dir) {
21
+ const discovery = pythonGraphAdapter.discoverFiles({ cwd: dir });
22
+ const parsed = pythonGraphAdapter.parseProject({
23
+ projectDirAbs: discovery.projectDirAbs,
24
+ files: discovery.files,
25
+ compilerOptions: discovery.compilerOptions,
26
+ resolutionMode: 'exact',
27
+ });
28
+ return pythonGraphAdapter.walkProject({
29
+ project: parsed.project,
30
+ projectDirAbs: discovery.projectDirAbs,
31
+ files: discovery.files,
32
+ });
33
+ }
34
+ describe('lang-python walk.ts — function/param shapes', () => {
35
+ let dir;
36
+ beforeEach(() => {
37
+ dir = mkdtempSync(join(tmpdir(), 'graph-python-shapes-'));
38
+ });
39
+ afterEach(() => {
40
+ rmSync(dir, { recursive: true, force: true });
41
+ });
42
+ it('extracts typed, default, and typed-default parameters', () => {
43
+ writeFileSync(join(dir, 'main.py'), `def f(x: int, y=1, z: str = "a", *args, **kwargs):\n return (x, y, z, args, kwargs)\n`, 'utf8');
44
+ const walk = runWalk(dir);
45
+ const f = walk.occurrences.f?.[0];
46
+ expect(f).toBeDefined();
47
+ const names = f?.params.map((p) => p.name) ?? [];
48
+ expect(names).toContain('x');
49
+ expect(names).toContain('y');
50
+ expect(names).toContain('z');
51
+ // typed_default → optional
52
+ const z = f?.params.find((p) => p.name === 'z');
53
+ expect(z?.optional).toBe(true);
54
+ // default_parameter → optional
55
+ const y = f?.params.find((p) => p.name === 'y');
56
+ expect(y?.optional).toBe(true);
57
+ // typed_parameter (no default) → required
58
+ const x = f?.params.find((p) => p.name === 'x');
59
+ expect(x?.optional).toBe(false);
60
+ });
61
+ it('classifies methods and __init__ correctly', () => {
62
+ writeFileSync(join(dir, 'main.py'), `class C:\n def __init__(self, p):\n self.p = p\n\n def m(self):\n return self.p\n`, 'utf8');
63
+ const walk = runWalk(dir);
64
+ const init = walk.occurrences.__init__?.[0];
65
+ const m = walk.occurrences.m?.[0];
66
+ expect(init?.kind).toBe('constructor');
67
+ expect(init?.enclosingClass).toBe('C');
68
+ expect(m?.kind).toBe('method');
69
+ expect(m?.enclosingClass).toBe('C');
70
+ });
71
+ it('marks names starting with underscore as module-local visibility', () => {
72
+ writeFileSync(join(dir, 'main.py'), `def _private():\n return 1\n\ndef public():\n return 2\n`, 'utf8');
73
+ const walk = runWalk(dir);
74
+ expect(walk.occurrences._private?.[0]?.visibility).toBe('module-local');
75
+ expect(walk.occurrences.public?.[0]?.visibility).toBe('exported');
76
+ });
77
+ it('records a lambda occurrence with arrow kind and creation edge to enclosing function', () => {
78
+ writeFileSync(join(dir, 'main.py'), `def outer():\n f = lambda n: n + 1\n return f(2)\n`, 'utf8');
79
+ const walk = runWalk(dir);
80
+ const arrowNames = Object.keys(walk.occurrences).filter((k) => k.startsWith('<arrow:'));
81
+ expect(arrowNames.length).toBe(1);
82
+ const arrow = walk.occurrences[arrowNames[0]]?.[0];
83
+ expect(arrow?.kind).toBe('arrow');
84
+ // The lambda's params field comes from extractParamsFromField('parameters')
85
+ expect(arrow?.params.map((p) => p.name)).toContain('n');
86
+ // A creation call site should be recorded for the lambda
87
+ const creation = walk.callSites.find((c) => c.kind === 'creation');
88
+ expect(creation).toBeDefined();
89
+ expect(creation?.childHash).toBe(arrow?.bodyHash);
90
+ });
91
+ it('treats lambdas at module scope as their own owner (no creation edge to themselves)', () => {
92
+ // Lambda assigned at module level: parent frame is module-init.
93
+ // The creation edge IS emitted because module-init's bodyHash !=
94
+ // the lambda's bodyHash.
95
+ writeFileSync(join(dir, 'main.py'), `add = lambda a, b: a + b\n`, 'utf8');
96
+ const walk = runWalk(dir);
97
+ const arrowNames = Object.keys(walk.occurrences).filter((k) => k.startsWith('<arrow:'));
98
+ expect(arrowNames.length).toBe(1);
99
+ const creation = walk.callSites.find((c) => c.kind === 'creation');
100
+ expect(creation).toBeDefined();
101
+ });
102
+ it('flags tests/ files as inTestFile', () => {
103
+ mkdirSync(join(dir, 'tests'), { recursive: true });
104
+ writeFileSync(join(dir, 'tests/test_foo.py'), `def test_a():\n assert True\n`, 'utf8');
105
+ const walk = runWalk(dir);
106
+ const a = walk.occurrences.test_a?.[0];
107
+ expect(a?.inTestFile).toBe(true);
108
+ });
109
+ it('flags _test.py files as inTestFile', () => {
110
+ writeFileSync(join(dir, 'foo_test.py'), `def t():\n assert True\n`, 'utf8');
111
+ const walk = runWalk(dir);
112
+ const t = walk.occurrences.t?.[0];
113
+ expect(t?.inTestFile).toBe(true);
114
+ });
115
+ it('flags .generated. files as definedInGenerated', () => {
116
+ writeFileSync(join(dir, 'foo.generated.py'), `def x():\n return 1\n`, 'utf8');
117
+ const walk = runWalk(dir);
118
+ const x = walk.occurrences.x?.[0];
119
+ expect(x?.definedInGenerated).toBe(true);
120
+ });
121
+ it('includes synthetic <module-init> occurrence for each file', () => {
122
+ writeFileSync(join(dir, 'a.py'), `def foo():\n return 1\n`, 'utf8');
123
+ const walk = runWalk(dir);
124
+ const moduleInitKeys = Object.keys(walk.occurrences).filter((k) => k.startsWith('<module-init:'));
125
+ expect(moduleInitKeys.length).toBe(1);
126
+ const moduleInit = walk.occurrences[moduleInitKeys[0]]?.[0];
127
+ expect(moduleInit?.kind).toBe('module-init');
128
+ expect(moduleInit?.line).toBe(1);
129
+ });
130
+ it('skips files in input.files that are missing from project.files', () => {
131
+ writeFileSync(join(dir, 'a.py'), `def f():\n return 1\n`, 'utf8');
132
+ const discovery = pythonGraphAdapter.discoverFiles({ cwd: dir });
133
+ const parsed = pythonGraphAdapter.parseProject({
134
+ projectDirAbs: discovery.projectDirAbs,
135
+ files: discovery.files,
136
+ compilerOptions: discovery.compilerOptions,
137
+ resolutionMode: 'exact',
138
+ });
139
+ // Pass an extra file that wasn't parsed — walkProject should skip it.
140
+ const walk = pythonGraphAdapter.walkProject({
141
+ project: parsed.project,
142
+ projectDirAbs: discovery.projectDirAbs,
143
+ files: [...discovery.files, join(dir, 'nonexistent.py')],
144
+ });
145
+ expect(Object.keys(walk.occurrences)).toContain('f');
146
+ expect(walk.parseErrors).toEqual([]);
147
+ });
148
+ it('exposes isTestFile predicate that detects tests/ paths and test_*.py / _test.py names', () => {
149
+ expect(isTestFile('tests/test_a.py')).toBe(true);
150
+ expect(isTestFile('src/test_foo.py')).toBe(true);
151
+ expect(isTestFile('src/foo_test.py')).toBe(true);
152
+ expect(isTestFile('src/foo.py')).toBe(false);
153
+ expect(isTestFile('test/foo.py')).toBe(true); // singular `test/` matches /tests?\//
154
+ });
155
+ });
156
+ //# sourceMappingURL=walk-shapes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk-shapes.test.js","sourceRoot":"","sources":["../../src/__tests__/walk-shapes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Branch-coverage tests for lang-python/walk.ts.
3
+ *
4
+ * Drives the full Python adapter (discover + parse + walk) over
5
+ * fixtures that include `#` comments and string literals. These
6
+ * exercise the comment-stripping helpers used by the body-hash
7
+ * normalizer.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=walk.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/walk.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Branch-coverage tests for lang-python/walk.ts.
3
+ *
4
+ * Drives the full Python adapter (discover + parse + walk) over
5
+ * fixtures that include `#` comments and string literals. These
6
+ * exercise the comment-stripping helpers used by the body-hash
7
+ * normalizer.
8
+ */
9
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
13
+ import { pythonGraphAdapter } from '../index.js';
14
+ describe('lang-python walk.ts — comment-stripping branches', () => {
15
+ let dir;
16
+ beforeEach(() => {
17
+ dir = mkdtempSync(join(tmpdir(), 'graph-python-walk-'));
18
+ });
19
+ afterEach(() => {
20
+ rmSync(dir, { recursive: true, force: true });
21
+ });
22
+ it('walks a Python file with hash comments without error', () => {
23
+ writeFileSync(join(dir, 'main.py'), `# hello\ndef with_hash_comment():\n # inner\n return 1\n`, 'utf8');
24
+ const discovery = pythonGraphAdapter.discoverFiles({ cwd: dir });
25
+ const parsed = pythonGraphAdapter.parseProject({
26
+ projectDirAbs: discovery.projectDirAbs,
27
+ files: discovery.files,
28
+ compilerOptions: discovery.compilerOptions,
29
+ resolutionMode: 'exact',
30
+ });
31
+ const walk = pythonGraphAdapter.walkProject({
32
+ project: parsed.project,
33
+ projectDirAbs: discovery.projectDirAbs,
34
+ files: discovery.files,
35
+ });
36
+ expect(Object.keys(walk.occurrences)).toContain('with_hash_comment');
37
+ });
38
+ it('preserves string literals containing # characters', () => {
39
+ writeFileSync(join(dir, 'main.py'), `def with_string():\n s = "not # a comment"\n return s\n`, 'utf8');
40
+ const discovery = pythonGraphAdapter.discoverFiles({ cwd: dir });
41
+ const parsed = pythonGraphAdapter.parseProject({
42
+ projectDirAbs: discovery.projectDirAbs,
43
+ files: discovery.files,
44
+ compilerOptions: discovery.compilerOptions,
45
+ resolutionMode: 'exact',
46
+ });
47
+ const walk = pythonGraphAdapter.walkProject({
48
+ project: parsed.project,
49
+ projectDirAbs: discovery.projectDirAbs,
50
+ files: discovery.files,
51
+ });
52
+ expect(Object.keys(walk.occurrences)).toContain('with_string');
53
+ });
54
+ it('handles triple-quoted docstrings', () => {
55
+ writeFileSync(join(dir, 'main.py'), `def with_docstring():\n """A docstring\n multi-line.\n """\n return 1\n`, 'utf8');
56
+ const discovery = pythonGraphAdapter.discoverFiles({ cwd: dir });
57
+ const parsed = pythonGraphAdapter.parseProject({
58
+ projectDirAbs: discovery.projectDirAbs,
59
+ files: discovery.files,
60
+ compilerOptions: discovery.compilerOptions,
61
+ resolutionMode: 'exact',
62
+ });
63
+ const walk = pythonGraphAdapter.walkProject({
64
+ project: parsed.project,
65
+ projectDirAbs: discovery.projectDirAbs,
66
+ files: discovery.files,
67
+ });
68
+ expect(Object.keys(walk.occurrences)).toContain('with_docstring');
69
+ });
70
+ });
71
+ //# sourceMappingURL=walk.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.test.js","sourceRoot":"","sources":["../../src/__tests__/walk.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Body normalization + SHA-256 digest for Python.
3
+ *
4
+ * Extracted from `walk.ts` to keep that module focused on AST traversal
5
+ * and occurrence construction. The body-digest defines the catalog's
6
+ * `bodyHash` contract: walk produces it; resolve consumes it.
7
+ *
8
+ * Normalization differs from C-family adapters in two places:
9
+ * - Real function bodies also strip a leading docstring (line-oriented
10
+ * conservative detection: only the common leading-triple-quote
11
+ * case).
12
+ * - Synthetic bodies (module-init) skip the docstring strip — the
13
+ * synthetic text isn't a function body.
14
+ */
15
+ import { type BodyDigest } from '@opensip-cli/graph';
16
+ export declare function digestPythonBody(text: string): BodyDigest;
17
+ export declare function digestSyntheticBody(text: string): BodyDigest;
18
+ //# sourceMappingURL=body-digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-digest.d.ts","sourceRoot":"","sources":["../src/body-digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGpF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAEzD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAE5D"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @fileoverview Body normalization + SHA-256 digest for Python.
3
+ *
4
+ * Extracted from `walk.ts` to keep that module focused on AST traversal
5
+ * and occurrence construction. The body-digest defines the catalog's
6
+ * `bodyHash` contract: walk produces it; resolve consumes it.
7
+ *
8
+ * Normalization differs from C-family adapters in two places:
9
+ * - Real function bodies also strip a leading docstring (line-oriented
10
+ * conservative detection: only the common leading-triple-quote
11
+ * case).
12
+ * - Synthetic bodies (module-init) skip the docstring strip — the
13
+ * synthetic text isn't a function body.
14
+ */
15
+ import { hashBody, normalizeWhitespace } from '@opensip-cli/graph';
16
+ import { skipToEndOfLine } from '@opensip-cli/graph-adapter-common';
17
+ export function digestPythonBody(text) {
18
+ return hashBody(normalizePythonBody(text));
19
+ }
20
+ export function digestSyntheticBody(text) {
21
+ return hashBody(normalizeWhitespace(stripPythonComments(text)));
22
+ }
23
+ /**
24
+ * Strip Python `#` comments and leading-of-body docstrings, then
25
+ * collapse whitespace. Docstring detection is line-oriented and
26
+ * conservative: a line containing only a triple-quoted string at the
27
+ * top of the body is removed. This is good enough for the v1 contract;
28
+ * a parse-tree-driven version is a follow-up if FP rates demand it.
29
+ */
30
+ function normalizePythonBody(text) {
31
+ return normalizeWhitespace(stripPythonComments(stripLeadingDocstring(text)));
32
+ }
33
+ function stripPythonComments(text) {
34
+ // Walk character-by-character, respecting string literals (so `#`
35
+ // inside a string is preserved). Python strings are wrapped by `'`,
36
+ // `"`, or triple-quoted variants.
37
+ let out = '';
38
+ let i = 0;
39
+ while (i < text.length) {
40
+ const c = text[i];
41
+ if (c === '#') {
42
+ i = skipToEndOfLine(text, i);
43
+ continue;
44
+ }
45
+ if (c === '"' || c === "'") {
46
+ const next = consumeStringLiteral(text, i, c);
47
+ out += next.text;
48
+ i = next.index;
49
+ continue;
50
+ }
51
+ out += c;
52
+ i++;
53
+ }
54
+ return out;
55
+ }
56
+ /* v8 ignore start */
57
+ function consumeStringLiteral(text, start, quote) {
58
+ const triple = text.slice(start, start + 3) === `${quote}${quote}${quote}`;
59
+ const close = triple ? `${quote}${quote}${quote}` : quote;
60
+ let i = start + (triple ? 3 : 1);
61
+ let buf = text.slice(start, i);
62
+ while (i < text.length) {
63
+ if (text[i] === '\\' && i + 1 < text.length) {
64
+ buf += text.slice(i, i + 2);
65
+ i += 2;
66
+ continue;
67
+ }
68
+ if (text.slice(i, i + close.length) === close) {
69
+ buf += close;
70
+ i += close.length;
71
+ break;
72
+ }
73
+ buf += text[i];
74
+ i++;
75
+ }
76
+ return { text: buf, index: i };
77
+ }
78
+ /* v8 ignore stop */
79
+ function stripLeadingDocstring(text) {
80
+ // Match an optional whitespace prefix followed by a triple-quoted
81
+ // string, optionally followed by a newline. Conservative — only
82
+ // handles the common case at the start of the function/module body.
83
+ const match = /^\s*(?:[ru]?(?:'''[\s\S]*?'''|"""[\s\S]*?"""))\s*\n/i.exec(text);
84
+ if (match)
85
+ return text.slice(match[0].length);
86
+ return text;
87
+ }
88
+ //# sourceMappingURL=body-digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-digest.js","sourceRoot":"","sources":["../src/body-digest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Python cacheKey implementation.
3
+ *
4
+ * Produces `py-${pythonVersion}-${pyprojectContentHash || 'no-config'}`.
5
+ *
6
+ * The content-fingerprint half (`no-config` / `missing:` / `unreadable:` /
7
+ * sha256-prefix) is the byte-identical `hashConfig` contract shared with
8
+ * go/java/rust; Python imports it from
9
+ * `@opensip-cli/graph-adapter-common` and layers a best-effort
10
+ * "Python version" on top (DEC-4). The version comes from a
11
+ * `requires-python` line in `pyproject.toml` (PEP 621) — a string like
12
+ * `>=3.10,<4.0`, sanitized; absent → the literal `unknown`. It is a CACHE
13
+ * INVALIDATION key, not a source of truth — its only job is to flip when
14
+ * the toolchain intent changes.
15
+ *
16
+ * Per contract invariant I-6 the function is purely a function of
17
+ * `(pyproject content)`. Per I-8 we emit `py-`, distinct from the other
18
+ * adapters' prefixes.
19
+ */
20
+ import type { CacheKeyInput } from '@opensip-cli/graph';
21
+ export declare function cacheKey(input: CacheKeyInput): string;
22
+ //# sourceMappingURL=cache-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-key.d.ts","sourceRoot":"","sources":["../src/cache-key.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAIrD"}