@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.
- package/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +31 -0
- package/dist/__tests__/body-digest.test.d.ts +18 -0
- package/dist/__tests__/body-digest.test.d.ts.map +1 -0
- package/dist/__tests__/body-digest.test.js +61 -0
- package/dist/__tests__/body-digest.test.js.map +1 -0
- package/dist/__tests__/branch-coverage.test.d.ts +19 -0
- package/dist/__tests__/branch-coverage.test.d.ts.map +1 -0
- package/dist/__tests__/branch-coverage.test.js +292 -0
- package/dist/__tests__/branch-coverage.test.js.map +1 -0
- package/dist/__tests__/cache-key.test.d.ts +8 -0
- package/dist/__tests__/cache-key.test.d.ts.map +1 -0
- package/dist/__tests__/cache-key.test.js +55 -0
- package/dist/__tests__/cache-key.test.js.map +1 -0
- package/dist/__tests__/depends-on-emission.test.d.ts +21 -0
- package/dist/__tests__/depends-on-emission.test.d.ts.map +1 -0
- package/dist/__tests__/depends-on-emission.test.js +189 -0
- package/dist/__tests__/depends-on-emission.test.js.map +1 -0
- package/dist/__tests__/discover.test.d.ts +9 -0
- package/dist/__tests__/discover.test.d.ts.map +1 -0
- package/dist/__tests__/discover.test.js +64 -0
- package/dist/__tests__/discover.test.js.map +1 -0
- package/dist/__tests__/parse.test.d.ts +8 -0
- package/dist/__tests__/parse.test.d.ts.map +1 -0
- package/dist/__tests__/parse.test.js +37 -0
- package/dist/__tests__/parse.test.js.map +1 -0
- package/dist/__tests__/resolve.test.d.ts +11 -0
- package/dist/__tests__/resolve.test.d.ts.map +1 -0
- package/dist/__tests__/resolve.test.js +176 -0
- package/dist/__tests__/resolve.test.js.map +1 -0
- package/dist/__tests__/walk-shapes.test.d.ts +15 -0
- package/dist/__tests__/walk-shapes.test.d.ts.map +1 -0
- package/dist/__tests__/walk-shapes.test.js +156 -0
- package/dist/__tests__/walk-shapes.test.js.map +1 -0
- package/dist/__tests__/walk.test.d.ts +10 -0
- package/dist/__tests__/walk.test.d.ts.map +1 -0
- package/dist/__tests__/walk.test.js +71 -0
- package/dist/__tests__/walk.test.js.map +1 -0
- package/dist/body-digest.d.ts +18 -0
- package/dist/body-digest.d.ts.map +1 -0
- package/dist/body-digest.js +88 -0
- package/dist/body-digest.js.map +1 -0
- package/dist/cache-key.d.ts +22 -0
- package/dist/cache-key.d.ts.map +1 -0
- package/dist/cache-key.js +52 -0
- package/dist/cache-key.js.map +1 -0
- package/dist/discover.d.ts +19 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +36 -0
- package/dist/discover.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +22 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +16 -0
- package/dist/parse.js.map +1 -0
- package/dist/resolve.d.ts +35 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +294 -0
- package/dist/resolve.js.map +1 -0
- package/dist/rule-hints.d.ts +13 -0
- package/dist/rule-hints.d.ts.map +1 -0
- package/dist/rule-hints.js +70 -0
- package/dist/rule-hints.js.map +1 -0
- package/dist/walk-dependencies.d.ts +27 -0
- package/dist/walk-dependencies.d.ts.map +1 -0
- package/dist/walk-dependencies.js +152 -0
- package/dist/walk-dependencies.js.map +1 -0
- package/dist/walk.d.ts +42 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +281 -0
- package/dist/walk.js.map +1 -0
- 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"}
|