@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,52 @@
|
|
|
1
|
+
// @fitness-ignore-file unbounded-memory -- reads pyproject.toml manifest; bounded by standard Python project metadata
|
|
2
|
+
/**
|
|
3
|
+
* Python cacheKey implementation.
|
|
4
|
+
*
|
|
5
|
+
* Produces `py-${pythonVersion}-${pyprojectContentHash || 'no-config'}`.
|
|
6
|
+
*
|
|
7
|
+
* The content-fingerprint half (`no-config` / `missing:` / `unreadable:` /
|
|
8
|
+
* sha256-prefix) is the byte-identical `hashConfig` contract shared with
|
|
9
|
+
* go/java/rust; Python imports it from
|
|
10
|
+
* `@opensip-cli/graph-adapter-common` and layers a best-effort
|
|
11
|
+
* "Python version" on top (DEC-4). The version comes from a
|
|
12
|
+
* `requires-python` line in `pyproject.toml` (PEP 621) — a string like
|
|
13
|
+
* `>=3.10,<4.0`, sanitized; absent → the literal `unknown`. It is a CACHE
|
|
14
|
+
* INVALIDATION key, not a source of truth — its only job is to flip when
|
|
15
|
+
* the toolchain intent changes.
|
|
16
|
+
*
|
|
17
|
+
* Per contract invariant I-6 the function is purely a function of
|
|
18
|
+
* `(pyproject content)`. Per I-8 we emit `py-`, distinct from the other
|
|
19
|
+
* adapters' prefixes.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { hashConfig } from '@opensip-cli/graph-adapter-common';
|
|
23
|
+
// Anchored to start-of-line; horizontal whitespace ([\t ]) and the
|
|
24
|
+
// inner `[^"'\n]` keep matching linear. Using `\s` would cross
|
|
25
|
+
// newlines and let pathological inputs explore O(n^2) prefixes.
|
|
26
|
+
const REQUIRES_PYTHON_RE = /^[\t ]*requires-python[\t ]*=[\t ]*["']([^"'\n]+)["']/m;
|
|
27
|
+
export function cacheKey(input) {
|
|
28
|
+
const configHash = hashConfig(input.configPathAbs);
|
|
29
|
+
const pythonVersion = readPythonVersion(input.configPathAbs);
|
|
30
|
+
return `py-${pythonVersion}-${configHash}`;
|
|
31
|
+
}
|
|
32
|
+
function readPythonVersion(configPathAbs) {
|
|
33
|
+
if (configPathAbs === undefined || configPathAbs.length === 0)
|
|
34
|
+
return 'unknown';
|
|
35
|
+
if (!existsSync(configPathAbs))
|
|
36
|
+
return 'unknown';
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = readFileSync(configPathAbs, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* v8 ignore next */
|
|
43
|
+
return 'unknown';
|
|
44
|
+
}
|
|
45
|
+
const match = REQUIRES_PYTHON_RE.exec(content);
|
|
46
|
+
return match ? sanitize(match[1] ?? 'unknown') : 'unknown';
|
|
47
|
+
}
|
|
48
|
+
function sanitize(s) {
|
|
49
|
+
// Keep cache-key strings shell- and filename-safe.
|
|
50
|
+
return s.replaceAll(/[^A-Za-z0-9._+-]/g, '_').slice(0, 32);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cache-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-key.js","sourceRoot":"","sources":["../src/cache-key.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAI/D,mEAAmE;AACnE,+DAA+D;AAC/D,gEAAgE;AAChE,MAAM,kBAAkB,GAAG,wDAAwD,CAAC;AAEpF,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACnD,MAAM,aAAa,GAAG,iBAAiB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC7D,OAAO,MAAM,aAAa,IAAI,UAAU,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,iBAAiB,CAAC,aAAiC;IAC1D,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChF,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;QACpB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACzB,mDAAmD;IACnD,OAAO,CAAC,CAAC,UAAU,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python file discovery — Stage 0 for the Python adapter.
|
|
3
|
+
*
|
|
4
|
+
* Strategy:
|
|
5
|
+
* 1. If `pyproject.toml` is present, use it as the language config
|
|
6
|
+
* anchor for cacheKey. We do NOT parse `[tool.opensip-graph].include`;
|
|
7
|
+
* that's a future enhancement. We just record the file path.
|
|
8
|
+
* 2. If `setup.py` is present (and no pyproject.toml), use it as the
|
|
9
|
+
* anchor instead.
|
|
10
|
+
* 3. Walk the project tree collecting all `.py` files, excluding
|
|
11
|
+
* common non-source directories (`.venv`, `venv`, `__pycache__`,
|
|
12
|
+
* `.tox`, `node_modules`, `dist`, `build`, `.eggs`).
|
|
13
|
+
*
|
|
14
|
+
* The collect-loop / realpath-dedup / config-precedence scaffolding lives
|
|
15
|
+
* in `@opensip-cli/graph-adapter-common`; this module supplies only the
|
|
16
|
+
* Python-specific inputs.
|
|
17
|
+
*/
|
|
18
|
+
export declare const discoverFiles: (input: import("@opensip-cli/graph").DiscoverInput) => import("@opensip-cli/graph").DiscoverOutput;
|
|
19
|
+
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAiBH,eAAO,MAAM,aAAa,oGAKxB,CAAC"}
|
package/dist/discover.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python file discovery — Stage 0 for the Python adapter.
|
|
3
|
+
*
|
|
4
|
+
* Strategy:
|
|
5
|
+
* 1. If `pyproject.toml` is present, use it as the language config
|
|
6
|
+
* anchor for cacheKey. We do NOT parse `[tool.opensip-graph].include`;
|
|
7
|
+
* that's a future enhancement. We just record the file path.
|
|
8
|
+
* 2. If `setup.py` is present (and no pyproject.toml), use it as the
|
|
9
|
+
* anchor instead.
|
|
10
|
+
* 3. Walk the project tree collecting all `.py` files, excluding
|
|
11
|
+
* common non-source directories (`.venv`, `venv`, `__pycache__`,
|
|
12
|
+
* `.tox`, `node_modules`, `dist`, `build`, `.eggs`).
|
|
13
|
+
*
|
|
14
|
+
* The collect-loop / realpath-dedup / config-precedence scaffolding lives
|
|
15
|
+
* in `@opensip-cli/graph-adapter-common`; this module supplies only the
|
|
16
|
+
* Python-specific inputs.
|
|
17
|
+
*/
|
|
18
|
+
import { createDiscover } from '@opensip-cli/graph-adapter-common';
|
|
19
|
+
const EXCLUDED_DIR_GLOBS = [
|
|
20
|
+
'**/.venv/**',
|
|
21
|
+
'**/venv/**',
|
|
22
|
+
'**/__pycache__/**',
|
|
23
|
+
'**/.tox/**',
|
|
24
|
+
'**/node_modules/**',
|
|
25
|
+
'**/dist/**',
|
|
26
|
+
'**/build/**',
|
|
27
|
+
'**/.eggs/**',
|
|
28
|
+
];
|
|
29
|
+
const CONFIG_CANDIDATES = ['pyproject.toml', 'setup.py'];
|
|
30
|
+
export const discoverFiles = createDiscover({
|
|
31
|
+
extension: 'py',
|
|
32
|
+
excludedDirGlobs: EXCLUDED_DIR_GLOBS,
|
|
33
|
+
configCandidates: CONFIG_CANDIDATES,
|
|
34
|
+
languageId: 'python',
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEnE,MAAM,kBAAkB,GAAsB;IAC5C,aAAa;IACb,YAAY;IACZ,mBAAmB;IACnB,YAAY;IACZ,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,aAAa;CACd,CAAC;AAEF,MAAM,iBAAiB,GAAsB,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;AAE5E,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;IAC1C,SAAS,EAAE,IAAI;IACf,gBAAgB,EAAE,kBAAkB;IACpC,gBAAgB,EAAE,iBAAiB;IACnC,UAAU,EAAE,QAAQ;CACrB,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @opensip-cli/graph — Python language adapter.
|
|
3
|
+
*
|
|
4
|
+
* Lands in PR 5 of plan docs/plans/10-graph-language-pluggability.md.
|
|
5
|
+
* Exposes `pythonGraphAdapter`, a `GraphLanguageAdapter` backed by
|
|
6
|
+
* tree-sitter-python.
|
|
7
|
+
*
|
|
8
|
+
* Per-rule fidelity (plan §6 row "Tree-sitter adapter"): mostly
|
|
9
|
+
* `'medium'` confidence on calls; `'low'` when multiple catalog
|
|
10
|
+
* entries share a simple name. The adapter has no symbol table.
|
|
11
|
+
*
|
|
12
|
+
* File layout mirrors `lang-typescript/`:
|
|
13
|
+
* discoverFiles → ./discover.ts
|
|
14
|
+
* parseProject → ./parse.ts
|
|
15
|
+
* walkProject → ./walk.ts
|
|
16
|
+
* resolveCallSites → ./resolve.ts
|
|
17
|
+
* cacheKey → ./cache-key.ts
|
|
18
|
+
* ruleHints → ./rule-hints.ts
|
|
19
|
+
*
|
|
20
|
+
* Production graph adapters are forbidden from importing `web-tree-sitter`
|
|
21
|
+
* directly; the ADR-0010 restricted-import rule in the shared ESLint config
|
|
22
|
+
* enforces it.
|
|
23
|
+
*/
|
|
24
|
+
import { cacheKey as pythonCacheKey } from './cache-key.js';
|
|
25
|
+
import { resolveCallSites as pythonResolveCallSites } from './resolve.js';
|
|
26
|
+
import { walkProject as pythonWalkProject } from './walk.js';
|
|
27
|
+
export declare const pythonGraphAdapter: {
|
|
28
|
+
id: string;
|
|
29
|
+
fileExtensions: string[];
|
|
30
|
+
displayName: string;
|
|
31
|
+
discoverFiles: (input: import("@opensip-cli/graph").DiscoverInput) => import("@opensip-cli/graph").DiscoverOutput;
|
|
32
|
+
parseProject: (input: import("@opensip-cli/graph").ParseInput) => import("@opensip-cli/graph").ParseOutput<import("@opensip-cli/graph-adapter-common").TreeSitterParsedProject>;
|
|
33
|
+
walkProject: typeof pythonWalkProject;
|
|
34
|
+
resolveCallSites: typeof pythonResolveCallSites;
|
|
35
|
+
cacheKey: typeof pythonCacheKey;
|
|
36
|
+
ruleHints: import("@opensip-cli/graph").RuleHints;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Discovery contract: external adapter packs export `adapter` (the
|
|
40
|
+
* GraphLanguageAdapter) and `metadata`. The CLI bootstrap registers
|
|
41
|
+
* `adapter` into the adapter registry after a successful `import()`.
|
|
42
|
+
*/
|
|
43
|
+
export { pythonGraphAdapter as adapter };
|
|
44
|
+
export declare const metadata: {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly displayName: string;
|
|
47
|
+
readonly fileExtensions: string[];
|
|
48
|
+
};
|
|
49
|
+
export type { PythonParsedProject } from './parse.js';
|
|
50
|
+
export { pythonRuleHints } from './rule-hints.js';
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG5D,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAK7D,eAAO,MAAM,kBAAkB;;;;;;;;;;CAUsB,CAAC;AAEtD;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,IAAI,OAAO,EAAE,CAAC;AACzC,eAAO,MAAM,QAAQ;;;;CAIX,CAAC;AAMX,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @opensip-cli/graph — Python language adapter.
|
|
3
|
+
*
|
|
4
|
+
* Lands in PR 5 of plan docs/plans/10-graph-language-pluggability.md.
|
|
5
|
+
* Exposes `pythonGraphAdapter`, a `GraphLanguageAdapter` backed by
|
|
6
|
+
* tree-sitter-python.
|
|
7
|
+
*
|
|
8
|
+
* Per-rule fidelity (plan §6 row "Tree-sitter adapter"): mostly
|
|
9
|
+
* `'medium'` confidence on calls; `'low'` when multiple catalog
|
|
10
|
+
* entries share a simple name. The adapter has no symbol table.
|
|
11
|
+
*
|
|
12
|
+
* File layout mirrors `lang-typescript/`:
|
|
13
|
+
* discoverFiles → ./discover.ts
|
|
14
|
+
* parseProject → ./parse.ts
|
|
15
|
+
* walkProject → ./walk.ts
|
|
16
|
+
* resolveCallSites → ./resolve.ts
|
|
17
|
+
* cacheKey → ./cache-key.ts
|
|
18
|
+
* ruleHints → ./rule-hints.ts
|
|
19
|
+
*
|
|
20
|
+
* Production graph adapters are forbidden from importing `web-tree-sitter`
|
|
21
|
+
* directly; the ADR-0010 restricted-import rule in the shared ESLint config
|
|
22
|
+
* enforces it.
|
|
23
|
+
*/
|
|
24
|
+
import { cacheKey as pythonCacheKey } from './cache-key.js';
|
|
25
|
+
import { discoverFiles as pythonDiscoverFiles } from './discover.js';
|
|
26
|
+
import { parseProject as pythonParseProject } from './parse.js';
|
|
27
|
+
import { resolveCallSites as pythonResolveCallSites } from './resolve.js';
|
|
28
|
+
import { pythonRuleHints } from './rule-hints.js';
|
|
29
|
+
import { walkProject as pythonWalkProject } from './walk.js';
|
|
30
|
+
export const pythonGraphAdapter = {
|
|
31
|
+
id: 'python',
|
|
32
|
+
fileExtensions: ['.py'],
|
|
33
|
+
displayName: 'Python',
|
|
34
|
+
discoverFiles: pythonDiscoverFiles,
|
|
35
|
+
parseProject: pythonParseProject,
|
|
36
|
+
walkProject: pythonWalkProject,
|
|
37
|
+
resolveCallSites: pythonResolveCallSites,
|
|
38
|
+
cacheKey: pythonCacheKey,
|
|
39
|
+
ruleHints: pythonRuleHints,
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Discovery contract: external adapter packs export `adapter` (the
|
|
43
|
+
* GraphLanguageAdapter) and `metadata`. The CLI bootstrap registers
|
|
44
|
+
* `adapter` into the adapter registry after a successful `import()`.
|
|
45
|
+
*/
|
|
46
|
+
export { pythonGraphAdapter as adapter };
|
|
47
|
+
export const metadata = {
|
|
48
|
+
id: pythonGraphAdapter.id,
|
|
49
|
+
displayName: pythonGraphAdapter.displayName,
|
|
50
|
+
fileExtensions: pythonGraphAdapter.fileExtensions,
|
|
51
|
+
};
|
|
52
|
+
export { pythonRuleHints } from './rule-hints.js';
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,aAAa,IAAI,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,YAAY,IAAI,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAK7D,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,EAAE,EAAE,QAAQ;IACZ,cAAc,EAAE,CAAC,KAAK,CAAC;IACvB,WAAW,EAAE,QAAQ;IACrB,aAAa,EAAE,mBAAmB;IAClC,YAAY,EAAE,kBAAkB;IAChC,WAAW,EAAE,iBAAiB;IAC9B,gBAAgB,EAAE,sBAAsB;IACxC,QAAQ,EAAE,cAAc;IACxB,SAAS,EAAE,eAAe;CACyB,CAAC;AAEtD;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,IAAI,OAAO,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,EAAE,EAAE,kBAAkB,CAAC,EAAE;IACzB,WAAW,EAAE,kBAAkB,CAAC,WAAW;IAC3C,cAAc,EAAE,kBAAkB,CAAC,cAAc;CACzC,CAAC;AAOX,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python parseProject — consumes `@opensip-cli/lang-python` (ADR-0010).
|
|
3
|
+
*
|
|
4
|
+
* `lang-python` is the canonical Python parse substrate: it owns the vendored
|
|
5
|
+
* tree-sitter-python grammar and produces the `{ tree, source }` parsed-file
|
|
6
|
+
* shape. The graph adapter no longer loads a grammar of its own; it binds the
|
|
7
|
+
* shared `createParseProjectFromAdapter` driver to `pythonAdapter`. This
|
|
8
|
+
* mirrors `graph-typescript`'s dependency on `lang-typescript`, generalizing
|
|
9
|
+
* the `graph-* → lang-*` edge to Python. The parsed-project shape and the
|
|
10
|
+
* downstream walk/resolve are unchanged.
|
|
11
|
+
*/
|
|
12
|
+
import { type TreeSitterParsedFile, type TreeSitterParsedProject } from '@opensip-cli/graph-adapter-common';
|
|
13
|
+
/** Parsed Python source file: tree-sitter parse tree plus original source text. */
|
|
14
|
+
export type PythonParsedFile = TreeSitterParsedFile;
|
|
15
|
+
/**
|
|
16
|
+
* Parsed Python project: map of normalized file path → {@link PythonParsedFile}.
|
|
17
|
+
* Keyed by the absolute, realpath-normalized file path from discover.
|
|
18
|
+
*/
|
|
19
|
+
export type PythonParsedProject = TreeSitterParsedProject<PythonParsedFile>;
|
|
20
|
+
/** Parses every Python source file in the input set into a {@link PythonParsedProject}. */
|
|
21
|
+
export declare const parseProject: (input: import("@opensip-cli/graph").ParseInput) => import("@opensip-cli/graph").ParseOutput<TreeSitterParsedProject>;
|
|
22
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC7B,MAAM,mCAAmC,CAAC;AAG3C,mFAAmF;AACnF,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;AAE5E,2FAA2F;AAC3F,eAAO,MAAM,YAAY,uHAA+C,CAAC"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python parseProject — consumes `@opensip-cli/lang-python` (ADR-0010).
|
|
3
|
+
*
|
|
4
|
+
* `lang-python` is the canonical Python parse substrate: it owns the vendored
|
|
5
|
+
* tree-sitter-python grammar and produces the `{ tree, source }` parsed-file
|
|
6
|
+
* shape. The graph adapter no longer loads a grammar of its own; it binds the
|
|
7
|
+
* shared `createParseProjectFromAdapter` driver to `pythonAdapter`. This
|
|
8
|
+
* mirrors `graph-typescript`'s dependency on `lang-typescript`, generalizing
|
|
9
|
+
* the `graph-* → lang-*` edge to Python. The parsed-project shape and the
|
|
10
|
+
* downstream walk/resolve are unchanged.
|
|
11
|
+
*/
|
|
12
|
+
import { createParseProjectFromAdapter, } from '@opensip-cli/graph-adapter-common';
|
|
13
|
+
import { pythonAdapter } from '@opensip-cli/lang-python';
|
|
14
|
+
/** Parses every Python source file in the input set into a {@link PythonParsedProject}. */
|
|
15
|
+
export const parseProject = createParseProjectFromAdapter(pythonAdapter);
|
|
16
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,6BAA6B,GAG9B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,2FAA2F;AAC3F,MAAM,CAAC,MAAM,YAAY,GAAG,6BAA6B,CAAC,aAAa,CAAC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python resolveCallSites — name-based catalog lookup.
|
|
3
|
+
*
|
|
4
|
+
* Tree-sitter has no symbol table, so we resolve by simple name. For
|
|
5
|
+
* each call site:
|
|
6
|
+
*
|
|
7
|
+
* 1. Decode the called expression. Three shapes matter:
|
|
8
|
+
* - `foo(args)` → call target is identifier `foo`
|
|
9
|
+
* - `obj.method(args)` → call target is attribute `method`
|
|
10
|
+
* - `mod.submod.fn(args)` → call target is attribute `fn`
|
|
11
|
+
* Other shapes (`(lambda)()`, subscript calls) are treated as
|
|
12
|
+
* unresolved.
|
|
13
|
+
*
|
|
14
|
+
* 2. Look up matching catalog entries by simple name. Confidence
|
|
15
|
+
* ladder:
|
|
16
|
+
* - 0 matches → `to: []`, resolution `'unknown'`, confidence `'low'`
|
|
17
|
+
* - 1 match → `to: [hash]`, resolution `'static'`, confidence `'medium'`
|
|
18
|
+
* - N matches → `to: [allHashes]`, resolution `'method-dispatch'`,
|
|
19
|
+
* confidence `'low'` (multiple candidates means we
|
|
20
|
+
* can't disambiguate without a symbol table)
|
|
21
|
+
*
|
|
22
|
+
* Confidence is mostly `'medium'`, never `'high'` — that's the
|
|
23
|
+
* intrinsic price of name-based resolution. The plan §6 fidelity table
|
|
24
|
+
* documents this (`orphan-subtree`: medium for tree-sitter adapters).
|
|
25
|
+
*
|
|
26
|
+
* Creation edges (lambda) emit a static high-confidence edge directly,
|
|
27
|
+
* mirroring lang-typescript's semantics.
|
|
28
|
+
*
|
|
29
|
+
* Per I-4: this function does NOT mutate the input catalog. It builds
|
|
30
|
+
* a `bodyHash → CallEdge[]` map and returns it.
|
|
31
|
+
*/
|
|
32
|
+
import type { PythonParsedProject } from './parse.js';
|
|
33
|
+
import type { ResolveInput, ResolveOutput } from '@opensip-cli/graph';
|
|
34
|
+
export declare function resolveCallSites(input: ResolveInput<PythonParsedProject>): ResolveOutput;
|
|
35
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAaH,OAAO,KAAK,EAAoB,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,KAAK,EAOV,YAAY,EACZ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAkB5B,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,mBAAmB,CAAC,GAAG,aAAa,CA2CxF"}
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python resolveCallSites — name-based catalog lookup.
|
|
3
|
+
*
|
|
4
|
+
* Tree-sitter has no symbol table, so we resolve by simple name. For
|
|
5
|
+
* each call site:
|
|
6
|
+
*
|
|
7
|
+
* 1. Decode the called expression. Three shapes matter:
|
|
8
|
+
* - `foo(args)` → call target is identifier `foo`
|
|
9
|
+
* - `obj.method(args)` → call target is attribute `method`
|
|
10
|
+
* - `mod.submod.fn(args)` → call target is attribute `fn`
|
|
11
|
+
* Other shapes (`(lambda)()`, subscript calls) are treated as
|
|
12
|
+
* unresolved.
|
|
13
|
+
*
|
|
14
|
+
* 2. Look up matching catalog entries by simple name. Confidence
|
|
15
|
+
* ladder:
|
|
16
|
+
* - 0 matches → `to: []`, resolution `'unknown'`, confidence `'low'`
|
|
17
|
+
* - 1 match → `to: [hash]`, resolution `'static'`, confidence `'medium'`
|
|
18
|
+
* - N matches → `to: [allHashes]`, resolution `'method-dispatch'`,
|
|
19
|
+
* confidence `'low'` (multiple candidates means we
|
|
20
|
+
* can't disambiguate without a symbol table)
|
|
21
|
+
*
|
|
22
|
+
* Confidence is mostly `'medium'`, never `'high'` — that's the
|
|
23
|
+
* intrinsic price of name-based resolution. The plan §6 fidelity table
|
|
24
|
+
* documents this (`orphan-subtree`: medium for tree-sitter adapters).
|
|
25
|
+
*
|
|
26
|
+
* Creation edges (lambda) emit a static high-confidence edge directly,
|
|
27
|
+
* mirroring lang-typescript's semantics.
|
|
28
|
+
*
|
|
29
|
+
* Per I-4: this function does NOT mutate the input catalog. It builds
|
|
30
|
+
* a `bodyHash → CallEdge[]` map and returns it.
|
|
31
|
+
*/
|
|
32
|
+
import { dirname, posix } from 'node:path';
|
|
33
|
+
import { logger } from '@opensip-cli/core';
|
|
34
|
+
import { appendEdge, createMutableStats, pushCreationEdge, truncateForCallEdge, } from '@opensip-cli/graph';
|
|
35
|
+
import { buildNameIndex } from '@opensip-cli/graph-adapter-common';
|
|
36
|
+
function pythonPosition(node, file) {
|
|
37
|
+
return {
|
|
38
|
+
line: node.startPosition.row + 1,
|
|
39
|
+
column: node.startPosition.column,
|
|
40
|
+
text: file.source.slice(node.startIndex, node.endIndex),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function resolveCallSites(input) {
|
|
44
|
+
logger.info({ evt: 'graph.edges.start', module: 'graph:edges:python' });
|
|
45
|
+
const byName = buildNameIndex(input.catalog.functions);
|
|
46
|
+
const edgesByOwner = new Map();
|
|
47
|
+
const stats = createMutableStats();
|
|
48
|
+
const sink = { edgesByOwner, stats };
|
|
49
|
+
for (const r of input.callSites) {
|
|
50
|
+
const node = r.nodeRef;
|
|
51
|
+
const file = r.sourceFileRef;
|
|
52
|
+
if (r.kind === 'creation') {
|
|
53
|
+
if (r.childHash === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
pushCreationEdge(pythonPosition(node, file), r.ownerHash, r.childHash, sink);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
pushCallEdge(node, file, r.ownerHash, byName, sink);
|
|
59
|
+
}
|
|
60
|
+
const finalStats = {
|
|
61
|
+
totalCallSites: stats.totalCallSites,
|
|
62
|
+
resolvedHigh: stats.resolvedHigh,
|
|
63
|
+
resolvedMedium: stats.resolvedMedium,
|
|
64
|
+
resolvedLow: stats.resolvedLow,
|
|
65
|
+
unresolved: stats.unresolved,
|
|
66
|
+
};
|
|
67
|
+
logger.info({
|
|
68
|
+
evt: 'graph.edges.complete',
|
|
69
|
+
module: 'graph:edges:python',
|
|
70
|
+
...finalStats,
|
|
71
|
+
});
|
|
72
|
+
// Phase 4 (DEC-498): resolve dependency sites if any. Mirrors the TS
|
|
73
|
+
// adapter's resolveDependencies pattern adapted to Python's path-based
|
|
74
|
+
// module discovery (no symbol table, no tsconfig resolution).
|
|
75
|
+
const dependenciesByOwner = input.dependencySites && input.dependencySites.length > 0
|
|
76
|
+
? resolveDependencies(input.dependencySites, input.catalog)
|
|
77
|
+
: undefined;
|
|
78
|
+
return dependenciesByOwner === undefined
|
|
79
|
+
? { edgesByOwner, stats: finalStats }
|
|
80
|
+
: { edgesByOwner, dependenciesByOwner, stats: finalStats };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve Python import sites to target module-init bodyHashes. Imports
|
|
84
|
+
* resolving to an in-catalog `.py` (or `__init__.py`) source file map to
|
|
85
|
+
* that file's module-init occurrence; imports resolving to standard-
|
|
86
|
+
* library or third-party packages (outside the catalog) produce
|
|
87
|
+
* unresolved `DependencyEdge` entries with `to: []` and the raw specifier
|
|
88
|
+
* carried in `specifier` for downstream attribution.
|
|
89
|
+
*
|
|
90
|
+
* Python lacks a tsconfig-equivalent resolver, so this implements the
|
|
91
|
+
* standard CPython search-path rules constrained to the project:
|
|
92
|
+
*
|
|
93
|
+
* - Absolute dotted (`foo.bar`) → `foo/bar.py` or `foo/bar/__init__.py`
|
|
94
|
+
* relative to the project root.
|
|
95
|
+
* - Relative (`.foo`, `..pkg.sub`) → resolve relative to the importing
|
|
96
|
+
* file's directory, walking up one level per leading dot beyond the
|
|
97
|
+
* first (which means the current package).
|
|
98
|
+
*
|
|
99
|
+
* Phase 4 of opensip's substrate consolidation (DEC-498).
|
|
100
|
+
*/
|
|
101
|
+
function resolveDependencies(sites, catalog) {
|
|
102
|
+
// Build filePath → module-init bodyHash map. Catalog occurrences carry
|
|
103
|
+
// project-relative POSIX filePath; module-init kind is filtered.
|
|
104
|
+
const moduleInitByFilePath = new Map();
|
|
105
|
+
for (const occs of Object.values(catalog.functions)) {
|
|
106
|
+
if (!occs)
|
|
107
|
+
continue;
|
|
108
|
+
for (const o of occs) {
|
|
109
|
+
if (o.kind === 'module-init')
|
|
110
|
+
moduleInitByFilePath.set(o.filePath, o.bodyHash);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const out = new Map();
|
|
114
|
+
for (const site of sites) {
|
|
115
|
+
const file = site.sourceFileRef;
|
|
116
|
+
const importerFilePath = pythonFilePathOf(file, catalog, site.ownerHash);
|
|
117
|
+
const to = resolvePythonModuleSpecifier(site.specifier, importerFilePath, moduleInitByFilePath);
|
|
118
|
+
const edge = {
|
|
119
|
+
to,
|
|
120
|
+
line: site.line,
|
|
121
|
+
column: site.column,
|
|
122
|
+
specifier: site.specifier,
|
|
123
|
+
};
|
|
124
|
+
const existing = out.get(site.ownerHash);
|
|
125
|
+
if (existing === undefined) {
|
|
126
|
+
out.set(site.ownerHash, [edge]);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
existing.push(edge);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Recover the project-relative filePath of the importer from the catalog
|
|
136
|
+
* (looking it up via the owner's module-init bodyHash). This avoids
|
|
137
|
+
* threading the projectDirAbs / absolute path through dependency
|
|
138
|
+
* resolution — the catalog already knows.
|
|
139
|
+
*/
|
|
140
|
+
function pythonFilePathOf(_file, catalog, ownerHash) {
|
|
141
|
+
for (const occs of Object.values(catalog.functions)) {
|
|
142
|
+
if (!occs)
|
|
143
|
+
continue;
|
|
144
|
+
for (const o of occs) {
|
|
145
|
+
if (o.bodyHash === ownerHash)
|
|
146
|
+
return o.filePath;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/* v8 ignore next */
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve one Python import specifier to its target module-init
|
|
154
|
+
* bodyHash(es). Returns `[]` when no project file matches (external
|
|
155
|
+
* package, stdlib, or an unresolvable relative import that walks above
|
|
156
|
+
* the project root).
|
|
157
|
+
*
|
|
158
|
+
* Handled shapes:
|
|
159
|
+
* - `foo` → `foo.py` or `foo/__init__.py`
|
|
160
|
+
* - `foo.bar` → `foo/bar.py` or `foo/bar/__init__.py`
|
|
161
|
+
* - `.sibling` → same package → `sibling.py` / `sibling/__init__.py`
|
|
162
|
+
* - `..pkg.sub` → up one package → `pkg/sub.py` / `pkg/sub/__init__.py`
|
|
163
|
+
*
|
|
164
|
+
* NOT handled:
|
|
165
|
+
* - PEP 420 namespace packages (we don't enumerate directories without
|
|
166
|
+
* an `__init__.py`).
|
|
167
|
+
* - `sys.path` extensions configured in `pyproject.toml` (`[tool.…]`
|
|
168
|
+
* `src` layouts where the package root isn't the project root).
|
|
169
|
+
* Files outside the project tree always resolve to external (`[]`).
|
|
170
|
+
* - `importlib.import_module(...)`, `__import__(...)`, conditional /
|
|
171
|
+
* nested imports inside function bodies (walker only emits top-level
|
|
172
|
+
* imports).
|
|
173
|
+
*/
|
|
174
|
+
function resolvePythonModuleSpecifier(specifier, importerFilePath, moduleInitByFilePath) {
|
|
175
|
+
const leadingDots = countLeadingDots(specifier);
|
|
176
|
+
if (leadingDots === 0) {
|
|
177
|
+
return resolveAbsoluteModule(specifier, moduleInitByFilePath);
|
|
178
|
+
}
|
|
179
|
+
if (importerFilePath === null) {
|
|
180
|
+
/* v8 ignore next */
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return resolveRelativeModule(specifier, leadingDots, importerFilePath, moduleInitByFilePath);
|
|
184
|
+
}
|
|
185
|
+
function countLeadingDots(specifier) {
|
|
186
|
+
let n = 0;
|
|
187
|
+
while (n < specifier.length && specifier[n] === '.')
|
|
188
|
+
n++;
|
|
189
|
+
return n;
|
|
190
|
+
}
|
|
191
|
+
function resolveAbsoluteModule(specifier, moduleInitByFilePath) {
|
|
192
|
+
return lookupModuleCandidates(specifier.split('.'), moduleInitByFilePath);
|
|
193
|
+
}
|
|
194
|
+
function resolveRelativeModule(specifier, leadingDots, importerFilePath, moduleInitByFilePath) {
|
|
195
|
+
// `from .x import y` — one dot → same package directory.
|
|
196
|
+
// `from ..x import y` — two dots → parent package directory.
|
|
197
|
+
// CPython: N dots = walk up (N - 1) directories from the importer's package.
|
|
198
|
+
const importerDir = dirname(importerFilePath); // POSIX, project-relative
|
|
199
|
+
let baseDir = importerDir;
|
|
200
|
+
for (let i = 1; i < leadingDots; i++) {
|
|
201
|
+
const parent = posix.dirname(baseDir);
|
|
202
|
+
if (parent === baseDir) {
|
|
203
|
+
// Walked above the project root → unresolvable.
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
baseDir = parent;
|
|
207
|
+
}
|
|
208
|
+
const remainder = specifier.slice(leadingDots); // may be '' for `from . import x`
|
|
209
|
+
const segments = remainder.length > 0 ? remainder.split('.') : [];
|
|
210
|
+
// baseDir of '.' means project root; treat as empty prefix.
|
|
211
|
+
const prefix = baseDir === '.' || baseDir === '' ? [] : baseDir.split('/');
|
|
212
|
+
return lookupModuleCandidates([...prefix, ...segments], moduleInitByFilePath);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Given dotted module segments (e.g. `['foo', 'bar']`), try the two
|
|
216
|
+
* canonical file forms: `foo/bar.py` and `foo/bar/__init__.py`. Returns
|
|
217
|
+
* the matching module-init bodyHash in a single-element array, or `[]`.
|
|
218
|
+
*/
|
|
219
|
+
function lookupModuleCandidates(segments, moduleInitByFilePath) {
|
|
220
|
+
if (segments.length === 0)
|
|
221
|
+
return [];
|
|
222
|
+
const joined = segments.join('/');
|
|
223
|
+
const candidates = [`${joined}.py`, `${joined}/__init__.py`];
|
|
224
|
+
for (const candidate of candidates) {
|
|
225
|
+
const hash = moduleInitByFilePath.get(candidate);
|
|
226
|
+
if (hash !== undefined)
|
|
227
|
+
return [hash];
|
|
228
|
+
}
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
function pushCallEdge(node, file, ownerHash, byName, sink) {
|
|
232
|
+
const { edgesByOwner, stats } = sink;
|
|
233
|
+
stats.totalCallSites++;
|
|
234
|
+
const target = extractCallTargetName(node);
|
|
235
|
+
const pos = pythonPosition(node, file);
|
|
236
|
+
const truncated = truncateForCallEdge(pos.text);
|
|
237
|
+
const discarded = isReturnValueDiscarded(node);
|
|
238
|
+
const edge = buildPythonCallEdge(target, byName, {
|
|
239
|
+
line: pos.line,
|
|
240
|
+
column: pos.column,
|
|
241
|
+
text: truncated,
|
|
242
|
+
discarded,
|
|
243
|
+
});
|
|
244
|
+
appendEdge(edgesByOwner, ownerHash, edge);
|
|
245
|
+
stats.apply(edge);
|
|
246
|
+
}
|
|
247
|
+
function buildPythonCallEdge(target, byName, loc) {
|
|
248
|
+
if (target === null) {
|
|
249
|
+
return { to: [], ...loc, resolution: 'unknown', confidence: 'low' };
|
|
250
|
+
}
|
|
251
|
+
const matches = byName.get(target);
|
|
252
|
+
if (!matches || matches.length === 0) {
|
|
253
|
+
return { to: [], ...loc, resolution: 'unknown', confidence: 'low' };
|
|
254
|
+
}
|
|
255
|
+
if (matches.length === 1) {
|
|
256
|
+
return { to: [...matches], ...loc, resolution: 'static', confidence: 'medium' };
|
|
257
|
+
}
|
|
258
|
+
return { to: [...matches], ...loc, resolution: 'method-dispatch', confidence: 'low' };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Decode a `call` node's target into a simple name. Returns null when
|
|
262
|
+
* we don't recognize the shape (subscript call, lambda call, etc.) —
|
|
263
|
+
* those become unresolved edges.
|
|
264
|
+
*/
|
|
265
|
+
function extractCallTargetName(node) {
|
|
266
|
+
// tree-sitter-python `call` has a `function` field for the callee.
|
|
267
|
+
const fn = node.childForFieldName('function');
|
|
268
|
+
if (!fn)
|
|
269
|
+
return null;
|
|
270
|
+
if (fn.type === 'identifier')
|
|
271
|
+
return fn.text;
|
|
272
|
+
if (fn.type === 'attribute') {
|
|
273
|
+
const attr = fn.childForFieldName('attribute');
|
|
274
|
+
return attr ? attr.text : null;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The call's return value is discarded when the call expression is
|
|
280
|
+
* the entire expression of an expression_statement. Mirrors
|
|
281
|
+
* lang-typescript's logic for the `no-side-effect-path` rule.
|
|
282
|
+
*/
|
|
283
|
+
function isReturnValueDiscarded(node) {
|
|
284
|
+
let parent = node.parent;
|
|
285
|
+
while (parent) {
|
|
286
|
+
if (parent.type === 'parenthesized_expression' || parent.type === 'await') {
|
|
287
|
+
parent = parent.parent;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
return parent.type === 'expression_statement';
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAenE,SAAS,cAAc,CACrB,IAAU,EACV,IAAsB;IAMtB,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;QAChC,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,MAAM;QACjC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC;KACxD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAwC;IACvE,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAC;IACnD,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAa,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAE/C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,OAAe,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,aAAiC,CAAC;QACjD,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC1B,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS;gBAAE,SAAS;YACxC,gBAAgB,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC7E,SAAS;QACX,CAAC;QACD,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,UAAU,GAAoB;QAClC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC;IAEF,MAAM,CAAC,IAAI,CAAC;QACV,GAAG,EAAE,sBAAsB;QAC3B,MAAM,EAAE,oBAAoB;QAC5B,GAAG,UAAU;KACd,CAAC,CAAC;IAEH,qEAAqE;IACrE,uEAAuE;IACvE,8DAA8D;IAC9D,MAAM,mBAAmB,GACvB,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC;QACvD,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC;QAC3D,CAAC,CAAC,SAAS,CAAC;IAEhB,OAAO,mBAAmB,KAAK,SAAS;QACtC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE;QACrC,CAAC,CAAC,EAAE,YAAY,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,mBAAmB,CAC1B,KAAsC,EACtC,OAAgB;IAEhB,uEAAuE;IACvE,iEAAiE;IACjE,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa;gBAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAA4B,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAiC,CAAC;QACpD,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACzE,MAAM,EAAE,GAAG,4BAA4B,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,EAAE,oBAAoB,CAAC,CAAC;QAChG,MAAM,IAAI,GAAmB;YAC3B,EAAE;YACF,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;QACF,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CACvB,KAAuB,EACvB,OAAgB,EAChB,SAAiB;IAEjB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS;gBAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;QAClD,CAAC;IACH,CAAC;IACD,oBAAoB;IACpB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAS,4BAA4B,CACnC,SAAiB,EACjB,gBAA+B,EAC/B,oBAAiD;IAEjD,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,qBAAqB,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAC9B,oBAAoB;QACpB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,qBAAqB,CAAC,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,oBAAoB,CAAC,CAAC;AAC/F,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAiB;IACzC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,CAAC,EAAE,CAAC;IACzD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,qBAAqB,CAC5B,SAAiB,EACjB,oBAAiD;IAEjD,OAAO,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,oBAAoB,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,qBAAqB,CAC5B,SAAiB,EACjB,WAAmB,EACnB,gBAAwB,EACxB,oBAAiD;IAEjD,yDAAyD;IACzD,6DAA6D;IAC7D,6EAA6E;IAC7E,MAAM,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,0BAA0B;IACzE,IAAI,OAAO,GAAG,WAAW,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,gDAAgD;YAChD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;IACD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,kCAAkC;IAClF,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,4DAA4D;IAC5D,MAAM,MAAM,GAAG,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3E,OAAO,sBAAsB,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,QAAQ,CAAC,EAAE,oBAAoB,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAC7B,QAA2B,EAC3B,oBAAiD;IAEjD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,CAAC,GAAG,MAAM,KAAK,EAAE,GAAG,MAAM,cAAc,CAAC,CAAC;IAC7D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,YAAY,CACnB,IAAU,EACV,IAAsB,EACtB,SAAiB,EACjB,MAA8C,EAC9C,IAAc;IAEd,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,cAAc,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;IAE/C,MAAM,IAAI,GAAa,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE;QACzD,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,IAAI,EAAE,SAAS;QACf,SAAS;KACV,CAAC,CAAC;IACH,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AASD,SAAS,mBAAmB,CAC1B,MAAqB,EACrB,MAA8C,EAC9C,GAAgB;IAEhB,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACtE,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACtE,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IAClF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AACxF,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,IAAU;IACvC,mEAAmE;IACnE,MAAM,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,EAAE,CAAC,IAAI,CAAC;IAC7C,IAAI,EAAE,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,IAAU;IACxC,IAAI,MAAM,GAAgB,IAAI,CAAC,MAAM,CAAC;IACtC,OAAO,MAAM,EAAE,CAAC;QACd,IAAI,MAAM,CAAC,IAAI,KAAK,0BAA0B,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1E,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACvB,SAAS;QACX,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,KAAK,sBAAsB,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python rule hints — declares language-specific signals for rules.
|
|
3
|
+
*
|
|
4
|
+
* Lands in PR 5 of plan docs/plans/10-graph-language-pluggability.md.
|
|
5
|
+
* Each hint maps a generic rule input ("what counts as a test file?",
|
|
6
|
+
* "what's a side-effect primitive?") onto Python conventions.
|
|
7
|
+
*
|
|
8
|
+
* Conservative on purpose: high-precision, low-recall. Rule authors may
|
|
9
|
+
* extend the lists over time as false negatives surface in practice.
|
|
10
|
+
*/
|
|
11
|
+
import type { RuleHints } from '@opensip-cli/graph';
|
|
12
|
+
export declare const pythonRuleHints: RuleHints;
|
|
13
|
+
//# sourceMappingURL=rule-hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-hints.d.ts","sourceRoot":"","sources":["../src/rule-hints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAyDpD,eAAO,MAAM,eAAe,EAAE,SAK7B,CAAC"}
|