@holmes-lab/holmes-kit 0.3.10 → 0.4.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/CHANGELOG.md +51 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/cpg/foundation/ast-store.d.ts +49 -0
- package/dist/holmes/cpg/foundation/ast-store.js +209 -0
- package/dist/holmes/cpg/foundation/cdg.d.ts +31 -0
- package/dist/holmes/cpg/foundation/cdg.js +83 -0
- package/dist/holmes/cpg/foundation/cfg.d.ts +60 -0
- package/dist/holmes/cpg/foundation/cfg.js +617 -0
- package/dist/holmes/cpg/foundation/ddg.d.ts +41 -0
- package/dist/holmes/cpg/foundation/ddg.js +394 -0
- package/dist/holmes/cpg/foundation/language-envelope.d.ts +29 -0
- package/dist/holmes/cpg/foundation/language-envelope.js +131 -0
- package/dist/holmes/cpg/foundation/language-matrix.d.ts +57 -0
- package/dist/holmes/cpg/foundation/language-matrix.js +134 -0
- package/dist/holmes/cpg/foundation/substrate-census.d.ts +36 -0
- package/dist/holmes/cpg/foundation/substrate-census.js +135 -0
- package/dist/holmes/cpg/language-capability.js +21 -3
- package/dist/holmes/cpg/language-parser-walk.js +132 -5
- package/dist/holmes/governance/push-gate.d.ts +3 -0
- package/dist/holmes/governance/push-gate.js +6 -1
- package/grammars/manifest.json +23 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +4 -2
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defUseOf = defUseOf;
|
|
4
|
+
exports.ddgOf = ddgOf;
|
|
5
|
+
exports.ddgViolations = ddgViolations;
|
|
6
|
+
const cfg_1 = require("./cfg");
|
|
7
|
+
/** Identifier positions that are DEF sites, keyed by the parent node type. */
|
|
8
|
+
const PATTERN_TYPES = new Set(['object_pattern', 'array_pattern', 'shorthand_property_identifier_pattern',
|
|
9
|
+
'pair_pattern', 'rest_pattern', 'assignment_pattern']);
|
|
10
|
+
function defUseOf(ast, cfg, fn, source) {
|
|
11
|
+
const kids = (0, cfg_1.childrenIndex)(ast);
|
|
12
|
+
const text = (i) => source.slice(ast.nodes[i].start, ast.nodes[i].end);
|
|
13
|
+
// ── parameters: ENTRY defs (and part of the local namespace).
|
|
14
|
+
const isPython = ast.lang === 'python';
|
|
15
|
+
const params = new Map();
|
|
16
|
+
const bareArrow = ast.nodes[fn.nodeIndex].type === 'arrow_function'
|
|
17
|
+
&& !kids.of(fn.nodeIndex).some((k) => ast.nodes[k].type === 'formal_parameters');
|
|
18
|
+
for (const k of kids.of(fn.nodeIndex)) {
|
|
19
|
+
// Only formal_parameters — a declaration's NAME identifier is not a parameter (measured: the
|
|
20
|
+
// early break on the name swallowed every real parameter of named functions).
|
|
21
|
+
// @implements A-SPEC-510.7 — Python's parameter list node is `parameters`.
|
|
22
|
+
const isParamList = ast.nodes[k].type === 'formal_parameters' || ast.nodes[k].type === 'parameters';
|
|
23
|
+
if (!isParamList && !(bareArrow && ast.nodes[k].type === 'identifier'))
|
|
24
|
+
continue;
|
|
25
|
+
const collect = (n) => {
|
|
26
|
+
const ty = ast.nodes[n].type;
|
|
27
|
+
if (ty === 'identifier' || ty === 'shorthand_property_identifier_pattern')
|
|
28
|
+
params.set(text(n), n);
|
|
29
|
+
if (ty === 'type_annotation')
|
|
30
|
+
return; // types are not values
|
|
31
|
+
for (const c of kids.of(n))
|
|
32
|
+
collect(c);
|
|
33
|
+
};
|
|
34
|
+
collect(k);
|
|
35
|
+
if (isParamList)
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
const defs = new Map();
|
|
39
|
+
const uses = new Map();
|
|
40
|
+
const add = (m, s, name) => {
|
|
41
|
+
if (!m.has(s))
|
|
42
|
+
m.set(s, new Set());
|
|
43
|
+
m.get(s).add(name);
|
|
44
|
+
};
|
|
45
|
+
// Walk one statement's subtree, skipping nested functions (their CFG owns them).
|
|
46
|
+
const stmtSet = new Set();
|
|
47
|
+
for (const b of cfg.blocks)
|
|
48
|
+
for (const s of b.stmts)
|
|
49
|
+
stmtSet.add(s);
|
|
50
|
+
const classify = (stmt) => {
|
|
51
|
+
const walk = (n, defCtx) => {
|
|
52
|
+
const ty = ast.nodes[n].type;
|
|
53
|
+
if (n !== stmt && cfg_1.FUNCTION_TYPES.has(ty))
|
|
54
|
+
return; // nested function boundary
|
|
55
|
+
if (n !== stmt && stmtSet.has(n))
|
|
56
|
+
return; // nested statements are their own rows
|
|
57
|
+
if (ty === 'type_annotation' || ty === 'type_arguments' || ty === 'comment')
|
|
58
|
+
return;
|
|
59
|
+
if (ty === 'identifier' || ty === 'shorthand_property_identifier_pattern') {
|
|
60
|
+
if (defCtx)
|
|
61
|
+
add(defs, stmt, text(n));
|
|
62
|
+
else
|
|
63
|
+
add(uses, stmt, text(n));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (ty === 'property_identifier' || ty === 'statement_identifier' || ty === 'type_identifier')
|
|
67
|
+
return;
|
|
68
|
+
switch (ty) {
|
|
69
|
+
// @implements A-SPEC-510.7 — Python's assignment shapes.
|
|
70
|
+
case 'assignment': {
|
|
71
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
72
|
+
const lhs = named[0];
|
|
73
|
+
if (lhs !== undefined) {
|
|
74
|
+
walk(lhs, ast.nodes[lhs].type === 'identifier' || PATTERN_TYPES.has(ast.nodes[lhs].type)
|
|
75
|
+
|| ast.nodes[lhs].type === 'pattern_list' || ast.nodes[lhs].type === 'tuple_pattern');
|
|
76
|
+
}
|
|
77
|
+
for (const r of named.slice(1))
|
|
78
|
+
walk(r, false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case 'augmented_assignment': {
|
|
82
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
83
|
+
const lhs = named[0];
|
|
84
|
+
if (lhs !== undefined && ast.nodes[lhs].type === 'identifier') {
|
|
85
|
+
add(defs, stmt, text(lhs));
|
|
86
|
+
add(uses, stmt, text(lhs));
|
|
87
|
+
}
|
|
88
|
+
else if (lhs !== undefined)
|
|
89
|
+
walk(lhs, false);
|
|
90
|
+
for (const r of named.slice(1))
|
|
91
|
+
walk(r, false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
case 'as_pattern': {
|
|
95
|
+
// `with x as y` / `except E as y`: the alias is a def, the source an ordinary use.
|
|
96
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
97
|
+
for (let k = 0; k < named.length; k++)
|
|
98
|
+
walk(named[k], k === named.length - 1);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
case 'as_pattern_target': {
|
|
102
|
+
for (const c of kids.of(n))
|
|
103
|
+
walk(c, true);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
case 'variable_declarator': {
|
|
107
|
+
const [name, ...rest] = kids.of(n);
|
|
108
|
+
walk(name, true);
|
|
109
|
+
for (const r of rest)
|
|
110
|
+
walk(r, false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
case 'assignment_expression': {
|
|
114
|
+
const [lhs, ...rest] = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
115
|
+
// Only a bare identifier LHS is a def; a.b / a[i] mutation is NOT (sealed envelope) —
|
|
116
|
+
// its identifiers (a, i) are USES.
|
|
117
|
+
walk(lhs, ast.nodes[lhs].type === 'identifier' || PATTERN_TYPES.has(ast.nodes[lhs].type));
|
|
118
|
+
for (const r of rest)
|
|
119
|
+
walk(r, false);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
case 'augmented_assignment_expression': {
|
|
123
|
+
const [lhs, ...rest] = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
124
|
+
if (ast.nodes[lhs].type === 'identifier') {
|
|
125
|
+
add(defs, stmt, text(lhs));
|
|
126
|
+
add(uses, stmt, text(lhs));
|
|
127
|
+
}
|
|
128
|
+
else
|
|
129
|
+
walk(lhs, false);
|
|
130
|
+
for (const r of rest)
|
|
131
|
+
walk(r, false);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
case 'update_expression': {
|
|
135
|
+
const arg = kids.of(n).find((c) => ast.nodes[c].named);
|
|
136
|
+
if (arg !== undefined && ast.nodes[arg].type === 'identifier') {
|
|
137
|
+
add(defs, stmt, text(arg));
|
|
138
|
+
add(uses, stmt, text(arg));
|
|
139
|
+
}
|
|
140
|
+
else if (arg !== undefined)
|
|
141
|
+
walk(arg, false);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
case 'for_statement': {
|
|
145
|
+
if (!isPython) {
|
|
146
|
+
for (const c of kids.of(n))
|
|
147
|
+
walk(c, false);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Python `for TARGET in ITER:` — the target binds fresh each iteration.
|
|
151
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
152
|
+
if (named[0] !== undefined)
|
|
153
|
+
walk(named[0], true);
|
|
154
|
+
for (const r of named.slice(1))
|
|
155
|
+
walk(r, false);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
case 'for_in_statement': {
|
|
159
|
+
// left is a def (fresh binding per iteration); right and body handled elsewhere/below.
|
|
160
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
161
|
+
walk(named[0], true);
|
|
162
|
+
for (const r of named.slice(1))
|
|
163
|
+
walk(r, false);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
case 'attribute':
|
|
167
|
+
case 'subscript':
|
|
168
|
+
case 'member_expression':
|
|
169
|
+
case 'subscript_expression': {
|
|
170
|
+
const named = kids.of(n).filter((c) => ast.nodes[c].named);
|
|
171
|
+
walk(named[0], false); // the object is a use…
|
|
172
|
+
for (const r of named.slice(1))
|
|
173
|
+
if (ast.nodes[r].type !== 'property_identifier')
|
|
174
|
+
walk(r, false);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
default: {
|
|
178
|
+
const patternDef = defCtx && PATTERN_TYPES.has(ty);
|
|
179
|
+
for (const c of kids.of(n))
|
|
180
|
+
walk(c, patternDef || defCtx && PATTERN_TYPES.has(ty));
|
|
181
|
+
if (!patternDef && defCtx) { /* keep defCtx only through pattern nodes */ }
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
walk(stmt, false);
|
|
187
|
+
};
|
|
188
|
+
for (const s of stmtSet)
|
|
189
|
+
classify(s);
|
|
190
|
+
// catch parameters: a def at the head of the handler body's first statement.
|
|
191
|
+
ast.nodes.forEach((n, i) => {
|
|
192
|
+
if (n.type !== 'catch_clause')
|
|
193
|
+
return;
|
|
194
|
+
const named = kids.of(i).filter((k) => ast.nodes[k].named);
|
|
195
|
+
const param = named.find((k) => ast.nodes[k].type === 'identifier');
|
|
196
|
+
const block = named.find((k) => ast.nodes[k].type === 'statement_block');
|
|
197
|
+
if (param === undefined || block === undefined)
|
|
198
|
+
return;
|
|
199
|
+
const first = kids.of(block).find((k) => stmtSet.has(k));
|
|
200
|
+
if (first !== undefined)
|
|
201
|
+
add(defs, first, text(param));
|
|
202
|
+
});
|
|
203
|
+
// Locals-only filter for uses: names defined somewhere in this function, or parameters.
|
|
204
|
+
const local = new Set(params.keys());
|
|
205
|
+
for (const s of defs.values())
|
|
206
|
+
for (const name of s)
|
|
207
|
+
local.add(name);
|
|
208
|
+
for (const [s, names] of uses) {
|
|
209
|
+
const kept = new Set([...names].filter((n) => local.has(n)));
|
|
210
|
+
if (kept.size === 0)
|
|
211
|
+
uses.delete(s);
|
|
212
|
+
else
|
|
213
|
+
uses.set(s, kept);
|
|
214
|
+
}
|
|
215
|
+
return { defs, uses, params };
|
|
216
|
+
}
|
|
217
|
+
// @implements A-SPEC-510.4
|
|
218
|
+
function ddgOf(ast, cfg, fn, source) {
|
|
219
|
+
if ('unsupported' in cfg)
|
|
220
|
+
return cfg; // no partial DDG over a refused CFG
|
|
221
|
+
const { defs, uses, params } = defUseOf(ast, cfg, fn, source);
|
|
222
|
+
const byName = new Map();
|
|
223
|
+
const notePoint = (name, p) => {
|
|
224
|
+
if (!byName.has(name))
|
|
225
|
+
byName.set(name, new Set());
|
|
226
|
+
byName.get(name).add(p);
|
|
227
|
+
};
|
|
228
|
+
for (const [s, names] of defs)
|
|
229
|
+
for (const name of names)
|
|
230
|
+
notePoint(name, s);
|
|
231
|
+
for (const [name, p] of params)
|
|
232
|
+
notePoint(name, p);
|
|
233
|
+
const nBlocks = cfg.blocks.length;
|
|
234
|
+
const IN = Array.from({ length: nBlocks }, () => new Map());
|
|
235
|
+
const OUT = Array.from({ length: nBlocks }, () => new Map());
|
|
236
|
+
const succ = Array.from({ length: nBlocks }, () => []);
|
|
237
|
+
const pred = Array.from({ length: nBlocks }, () => []);
|
|
238
|
+
const excPred = Array.from({ length: nBlocks }, () => []);
|
|
239
|
+
for (const e of cfg.edges) {
|
|
240
|
+
succ[e.from].push(e.to);
|
|
241
|
+
if (e.kind === 'exception')
|
|
242
|
+
excPred[e.to].push(e.from);
|
|
243
|
+
else
|
|
244
|
+
pred[e.to].push(e.from);
|
|
245
|
+
}
|
|
246
|
+
// ENTRY OUT: parameters.
|
|
247
|
+
for (const [name, p] of params)
|
|
248
|
+
OUT[cfg.entry].set(name, new Set([p]));
|
|
249
|
+
const transfer = (b) => {
|
|
250
|
+
const out = new Map();
|
|
251
|
+
for (const [k, v] of IN[b])
|
|
252
|
+
out.set(k, new Set(v));
|
|
253
|
+
if (b === cfg.entry)
|
|
254
|
+
for (const [name, p] of params)
|
|
255
|
+
out.set(name, new Set([p]));
|
|
256
|
+
for (const s of cfg.blocks[b].stmts) {
|
|
257
|
+
const names = defs.get(s);
|
|
258
|
+
if (names)
|
|
259
|
+
for (const name of names)
|
|
260
|
+
out.set(name, new Set([s])); // kill + gen
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
};
|
|
264
|
+
const eq = (a, b) => {
|
|
265
|
+
if (a.size !== b.size)
|
|
266
|
+
return false;
|
|
267
|
+
for (const [k, v] of a) {
|
|
268
|
+
const w = b.get(k);
|
|
269
|
+
if (!w || w.size !== v.size)
|
|
270
|
+
return false;
|
|
271
|
+
for (const x of v)
|
|
272
|
+
if (!w.has(x))
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
};
|
|
277
|
+
const work = [...Array(nBlocks).keys()];
|
|
278
|
+
while (work.length) {
|
|
279
|
+
const b = work.shift();
|
|
280
|
+
const nin = new Map();
|
|
281
|
+
const fold = (m) => {
|
|
282
|
+
for (const [k, v] of m) {
|
|
283
|
+
if (!nin.has(k))
|
|
284
|
+
nin.set(k, new Set());
|
|
285
|
+
for (const x of v)
|
|
286
|
+
nin.get(k).add(x);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
for (const p of pred[b])
|
|
290
|
+
fold(OUT[p]);
|
|
291
|
+
// Exception predecessors may throw mid-statement: their PRE-state (IN) reaches the handler
|
|
292
|
+
// as well as their post-state (OUT) — union is the conservative may-analysis direction.
|
|
293
|
+
for (const p of excPred[b]) {
|
|
294
|
+
fold(IN[p]);
|
|
295
|
+
fold(OUT[p]);
|
|
296
|
+
}
|
|
297
|
+
IN[b] = nin;
|
|
298
|
+
const nout = transfer(b);
|
|
299
|
+
if (!eq(nout, OUT[b])) {
|
|
300
|
+
OUT[b] = nout;
|
|
301
|
+
for (const s of succ[b])
|
|
302
|
+
if (!work.includes(s))
|
|
303
|
+
work.push(s);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Edges: for each use, the defs live just before it (block-local order first, then IN).
|
|
307
|
+
const edges = [];
|
|
308
|
+
const seen = new Set();
|
|
309
|
+
for (const b of cfg.blocks) {
|
|
310
|
+
const live = new Map();
|
|
311
|
+
for (const [k, v] of IN[b.id])
|
|
312
|
+
live.set(k, new Set(v));
|
|
313
|
+
if (b.id === cfg.entry)
|
|
314
|
+
for (const [name, p] of params)
|
|
315
|
+
live.set(name, new Set([p]));
|
|
316
|
+
for (const s of b.stmts) {
|
|
317
|
+
const names = uses.get(s);
|
|
318
|
+
if (names) {
|
|
319
|
+
for (const name of names) {
|
|
320
|
+
for (const d of live.get(name) ?? []) {
|
|
321
|
+
const key = `${d}>${s}>${name}`;
|
|
322
|
+
if (!seen.has(key)) {
|
|
323
|
+
seen.add(key);
|
|
324
|
+
edges.push({ defStmt: d, useStmt: s, name });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const dnames = defs.get(s);
|
|
330
|
+
if (dnames)
|
|
331
|
+
for (const name of dnames)
|
|
332
|
+
live.set(name, new Set([s]));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { edges, defs, uses, params };
|
|
336
|
+
}
|
|
337
|
+
// @implements A-SPEC-510.4
|
|
338
|
+
/** The DDG verifier — [] means the reaching-definition THEORY holds on this instance. */
|
|
339
|
+
function ddgViolations(ast, cfg, ddg) {
|
|
340
|
+
const out = [];
|
|
341
|
+
const stmtBlock = new Map();
|
|
342
|
+
for (const b of cfg.blocks)
|
|
343
|
+
for (const s of b.stmts)
|
|
344
|
+
stmtBlock.set(s, b.id);
|
|
345
|
+
const paramPoints = new Set(ddg.params.values());
|
|
346
|
+
const succ = cfg.blocks.map(() => []);
|
|
347
|
+
for (const e of cfg.edges)
|
|
348
|
+
succ[e.from].push(e.to);
|
|
349
|
+
const reachesFrom = (a, b) => {
|
|
350
|
+
const seen = new Set([a]);
|
|
351
|
+
const st = [a];
|
|
352
|
+
while (st.length) {
|
|
353
|
+
const x = st.pop();
|
|
354
|
+
if (x === b)
|
|
355
|
+
return true;
|
|
356
|
+
for (const s of succ[x])
|
|
357
|
+
if (!seen.has(s)) {
|
|
358
|
+
seen.add(s);
|
|
359
|
+
st.push(s);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
};
|
|
364
|
+
const dup = new Set();
|
|
365
|
+
for (const e of ddg.edges) {
|
|
366
|
+
const key = `${e.defStmt}>${e.useStmt}>${e.name}`;
|
|
367
|
+
if (dup.has(key))
|
|
368
|
+
out.push(`duplicate edge ${key}`);
|
|
369
|
+
dup.add(key);
|
|
370
|
+
if (!stmtBlock.has(e.useStmt)) {
|
|
371
|
+
out.push(`use ${e.useStmt} outside the CFG`);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const db = paramPoints.has(e.defStmt) ? cfg.entry : stmtBlock.get(e.defStmt);
|
|
375
|
+
if (db === undefined) {
|
|
376
|
+
out.push(`def ${e.defStmt} outside the CFG`);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const ub = stmtBlock.get(e.useStmt);
|
|
380
|
+
// A reaching definition MUST have a CFG path def→use (same block counts as ordered).
|
|
381
|
+
if (db === ub) {
|
|
382
|
+
const stmts = cfg.blocks[db].stmts;
|
|
383
|
+
if (!paramPoints.has(e.defStmt) && stmts.indexOf(e.defStmt) >= stmts.indexOf(e.useStmt)) {
|
|
384
|
+
// def after use in the same block: only legitimate via a loop back to the block head.
|
|
385
|
+
if (!reachesFrom(ub, db))
|
|
386
|
+
out.push(`same-block def ${e.defStmt} after use ${e.useStmt} with no loop`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else if (!reachesFrom(db, ub)) {
|
|
390
|
+
out.push(`no CFG path from def ${e.defStmt} (block ${db}) to use ${e.useStmt} (block ${ub})`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const LANG_FIXTURES: ReadonlyArray<{
|
|
2
|
+
file: string;
|
|
3
|
+
lang: string;
|
|
4
|
+
construct: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const LANG_FIXTURES_DIR: string;
|
|
7
|
+
export interface LangEnvelopeRow {
|
|
8
|
+
file: string;
|
|
9
|
+
lang: string;
|
|
10
|
+
construct: string;
|
|
11
|
+
symbols: number;
|
|
12
|
+
edges: number;
|
|
13
|
+
/** Distinct relation kinds the scanner recovered — 'calls' | 'imports' | 'inherits'. */
|
|
14
|
+
relationKinds: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Scan EXACTLY the language corpus with the CURRENT extractor. Pure with respect to anything
|
|
18
|
+
* outside the declared fixture list, exactly like the TS/Python census.
|
|
19
|
+
*/
|
|
20
|
+
export declare function languageEnvelope(dir?: string): LangEnvelopeRow[];
|
|
21
|
+
/** Constructs the current extractor cannot see at all — the honest half of the envelope. */
|
|
22
|
+
export declare function invisibleLangConstructs(rows?: LangEnvelopeRow[]): string[];
|
|
23
|
+
/** Per-language totals, for the activation-log table. */
|
|
24
|
+
export declare function envelopeByLanguage(rows?: LangEnvelopeRow[]): Record<string, {
|
|
25
|
+
files: number;
|
|
26
|
+
symbols: number;
|
|
27
|
+
edges: number;
|
|
28
|
+
invisible: number;
|
|
29
|
+
}>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.LANG_FIXTURES_DIR = exports.LANG_FIXTURES = void 0;
|
|
37
|
+
exports.languageEnvelope = languageEnvelope;
|
|
38
|
+
exports.invisibleLangConstructs = invisibleLangConstructs;
|
|
39
|
+
exports.envelopeByLanguage = envelopeByLanguage;
|
|
40
|
+
// @implements A-SPEC-510.6
|
|
41
|
+
/**
|
|
42
|
+
* The per-language honesty envelope for L0 extraction — the .1 method (a construct corpus whose
|
|
43
|
+
* failures are frozen AS failures) extended to every language holmes-kit advertises beyond
|
|
44
|
+
* TS/Python. What the extractor cannot see in Go, Rust, Java, C# or C++ becomes a NUMBER here
|
|
45
|
+
* instead of an unexamined assumption.
|
|
46
|
+
*
|
|
47
|
+
* Concepts are matched across languages so the table compares like with like: types+methods,
|
|
48
|
+
* interfaces/traits, generics/templates, concurrency/async, error handling, branching.
|
|
49
|
+
*/
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const cpg_scanner_1 = require("../cpg-scanner");
|
|
53
|
+
exports.LANG_FIXTURES = [
|
|
54
|
+
// Go
|
|
55
|
+
{ file: 'types_methods.go', lang: 'go', construct: 'struct + pointer/value receivers' },
|
|
56
|
+
{ file: 'interfaces.go', lang: 'go', construct: 'interface + implementations + range' },
|
|
57
|
+
{ file: 'generics.go', lang: 'go', construct: 'type-parameter constraints, generic struct' },
|
|
58
|
+
{ file: 'concurrency.go', lang: 'go', construct: 'goroutine + channel + WaitGroup' },
|
|
59
|
+
{ file: 'errors.go', lang: 'go', construct: 'error wrapping + defer/recover' },
|
|
60
|
+
{ file: 'branching.go', lang: 'go', construct: 'type switch with multi-value case' },
|
|
61
|
+
// Rust
|
|
62
|
+
{ file: 'types_methods.rs', lang: 'rust', construct: 'struct + impl block' },
|
|
63
|
+
{ file: 'traits.rs', lang: 'rust', construct: 'trait with default method + impl for' },
|
|
64
|
+
{ file: 'generics.rs', lang: 'rust', construct: 'bounded generics + generic impl' },
|
|
65
|
+
{ file: 'async_fixture.rs', lang: 'rust', construct: 'async fn + await' },
|
|
66
|
+
{ file: 'errors.rs', lang: 'rust', construct: 'Result + ? operator + enum error' },
|
|
67
|
+
{ file: 'branching.rs', lang: 'rust', construct: 'match with guards and or-patterns' },
|
|
68
|
+
// Java
|
|
69
|
+
{ file: 'WidgetFixture.java', lang: 'java', construct: 'class + constructor + fields' },
|
|
70
|
+
{ file: 'RendererFixture.java', lang: 'java', construct: 'interface with default method' },
|
|
71
|
+
{ file: 'GenericsFixture.java', lang: 'java', construct: 'bounded type params + generic method' },
|
|
72
|
+
{ file: 'LambdaFixture.java', lang: 'java', construct: 'lambda + method reference + stream' },
|
|
73
|
+
{ file: 'ErrorsFixture.java', lang: 'java', construct: 'try-with-resources + catch + finally' },
|
|
74
|
+
{ file: 'BranchingFixture.java', lang: 'java', construct: 'switch with fallthrough + default' },
|
|
75
|
+
// C#
|
|
76
|
+
{ file: 'WidgetFixture.cs', lang: 'csharp', construct: 'class + expression-bodied members' },
|
|
77
|
+
{ file: 'RendererFixture.cs', lang: 'csharp', construct: 'interface default impl + implementer' },
|
|
78
|
+
{ file: 'GenericsFixture.cs', lang: 'csharp', construct: 'generic class with where + generic method' },
|
|
79
|
+
{ file: 'AsyncFixture.cs', lang: 'csharp', construct: 'async/await Task methods' },
|
|
80
|
+
{ file: 'ErrorsFixture.cs', lang: 'csharp', construct: 'try/catch/finally' },
|
|
81
|
+
{ file: 'BranchingFixture.cs', lang: 'csharp', construct: 'switch expression with patterns' },
|
|
82
|
+
// C++
|
|
83
|
+
{ file: 'widget.cpp', lang: 'cpp', construct: 'class + member init + const method' },
|
|
84
|
+
{ file: 'renderer.cpp', lang: 'cpp', construct: 'abstract base + virtual override' },
|
|
85
|
+
{ file: 'templates.cpp', lang: 'cpp', construct: 'function + class templates' },
|
|
86
|
+
{ file: 'lambdas.cpp', lang: 'cpp', construct: 'lambda capture + algorithm' },
|
|
87
|
+
{ file: 'errors.cpp', lang: 'cpp', construct: 'throw + multiple catch + catch-all' },
|
|
88
|
+
{ file: 'branching.cpp', lang: 'cpp', construct: 'switch with fallthrough + block case' },
|
|
89
|
+
];
|
|
90
|
+
exports.LANG_FIXTURES_DIR = (() => {
|
|
91
|
+
const here = path.join(__dirname, 'fixtures-lang');
|
|
92
|
+
// Fixtures are DATA (excluded from tsc), so a dist-resolved consumer falls back to src.
|
|
93
|
+
return fs.existsSync(here) ? here : here.replace(`${path.sep}dist${path.sep}`, `${path.sep}src${path.sep}`);
|
|
94
|
+
})();
|
|
95
|
+
/**
|
|
96
|
+
* Scan EXACTLY the language corpus with the CURRENT extractor. Pure with respect to anything
|
|
97
|
+
* outside the declared fixture list, exactly like the TS/Python census.
|
|
98
|
+
*/
|
|
99
|
+
function languageEnvelope(dir = exports.LANG_FIXTURES_DIR) {
|
|
100
|
+
const scanned = new cpg_scanner_1.CpgScanner().scan(dir, dir);
|
|
101
|
+
const byBase = new Map(scanned.map((f) => [path.basename(f.path), f]));
|
|
102
|
+
return exports.LANG_FIXTURES.map(({ file, lang, construct }) => {
|
|
103
|
+
const f = byBase.get(file);
|
|
104
|
+
const edges = f?.edges ?? [];
|
|
105
|
+
return {
|
|
106
|
+
file,
|
|
107
|
+
lang,
|
|
108
|
+
construct,
|
|
109
|
+
symbols: f?.symbols.length ?? 0,
|
|
110
|
+
edges: edges.length,
|
|
111
|
+
relationKinds: [...new Set(edges.map((e) => e.rel))].sort(),
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Constructs the current extractor cannot see at all — the honest half of the envelope. */
|
|
116
|
+
function invisibleLangConstructs(rows = languageEnvelope()) {
|
|
117
|
+
return rows.filter((r) => r.symbols === 0).map((r) => r.file);
|
|
118
|
+
}
|
|
119
|
+
/** Per-language totals, for the activation-log table. */
|
|
120
|
+
function envelopeByLanguage(rows = languageEnvelope()) {
|
|
121
|
+
const out = {};
|
|
122
|
+
for (const r of rows) {
|
|
123
|
+
const acc = out[r.lang] ?? (out[r.lang] = { files: 0, symbols: 0, edges: 0, invisible: 0 });
|
|
124
|
+
acc.files++;
|
|
125
|
+
acc.symbols += r.symbols;
|
|
126
|
+
acc.edges += r.edges;
|
|
127
|
+
if (r.symbols === 0)
|
|
128
|
+
acc.invisible++;
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export declare const LAYERS: readonly ["relations", "ast", "cfg", "ddg", "cdg", "runner", "taint"];
|
|
2
|
+
export type Layer = typeof LAYERS[number];
|
|
3
|
+
/** The language families holmes-kit advertises, each with the probe inputs the derivation needs. */
|
|
4
|
+
export declare const MATRIX_LANGUAGES: readonly [{
|
|
5
|
+
readonly lang: "typescript";
|
|
6
|
+
readonly label: "TypeScript / JavaScript";
|
|
7
|
+
readonly ext: ".ts";
|
|
8
|
+
readonly astLang: "typescript";
|
|
9
|
+
readonly sampleFile: "x.test.ts";
|
|
10
|
+
}, {
|
|
11
|
+
readonly lang: "python";
|
|
12
|
+
readonly label: "Python";
|
|
13
|
+
readonly ext: ".py";
|
|
14
|
+
readonly astLang: "python";
|
|
15
|
+
readonly sampleFile: "test_x.py";
|
|
16
|
+
}, {
|
|
17
|
+
readonly lang: "csharp";
|
|
18
|
+
readonly label: "C#";
|
|
19
|
+
readonly ext: ".cs";
|
|
20
|
+
readonly astLang: "csharp";
|
|
21
|
+
readonly sampleFile: "XTest.cs";
|
|
22
|
+
}, {
|
|
23
|
+
readonly lang: "java";
|
|
24
|
+
readonly label: "Java";
|
|
25
|
+
readonly ext: ".java";
|
|
26
|
+
readonly astLang: "java";
|
|
27
|
+
readonly sampleFile: "XTest.java";
|
|
28
|
+
}, {
|
|
29
|
+
readonly lang: "go";
|
|
30
|
+
readonly label: "Go";
|
|
31
|
+
readonly ext: ".go";
|
|
32
|
+
readonly astLang: "go";
|
|
33
|
+
readonly sampleFile: "x_test.go";
|
|
34
|
+
}, {
|
|
35
|
+
readonly lang: "rust";
|
|
36
|
+
readonly label: "Rust";
|
|
37
|
+
readonly ext: ".rs";
|
|
38
|
+
readonly astLang: "rust";
|
|
39
|
+
readonly sampleFile: "tests/x.rs";
|
|
40
|
+
}, {
|
|
41
|
+
readonly lang: "cpp";
|
|
42
|
+
readonly label: "C++";
|
|
43
|
+
readonly ext: ".cpp";
|
|
44
|
+
readonly astLang: "cpp";
|
|
45
|
+
readonly sampleFile: "x_test.cpp";
|
|
46
|
+
}];
|
|
47
|
+
export type MatrixLanguage = typeof MATRIX_LANGUAGES[number]['lang'];
|
|
48
|
+
export type Support = 'full' | 'partial' | 'none';
|
|
49
|
+
export interface Cell {
|
|
50
|
+
support: Support;
|
|
51
|
+
basis: string;
|
|
52
|
+
}
|
|
53
|
+
export type LanguageMatrix = Record<Layer, Record<string, Cell>>;
|
|
54
|
+
/** Derive the whole matrix. Pure, cheap, and impossible to drift from its sources. */
|
|
55
|
+
export declare function languageMatrix(): LanguageMatrix;
|
|
56
|
+
/** Render docs/language-support.md — the table is a PRODUCT of the code, not a claim beside it. */
|
|
57
|
+
export declare function renderLanguageSupport(): string;
|