@0xcraft/powershot 1.1.3 → 1.1.5
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/README.md +2 -2
- package/dist/git.js +5 -3
- package/dist/ground.js +61 -13
- package/dist/lang/packs.js +170 -8
- package/dist/langtest.js +1144 -2
- package/dist/reinvention.js +334 -0
- package/dist/report/sarif.js +2 -2
- package/dist/review.js +5 -1
- package/dist/selftest.js +575 -8
- package/dist/verifiers/dropped-guard.js +188 -84
- package/dist/verifiers/foreign-dropped-guard.js +174 -50
- package/dist/verifiers/foreign-reinvented.js +87 -26
- package/dist/verifiers/foreign-tokens.js +232 -20
- package/dist/verifiers/guard-diff.js +138 -0
- package/dist/verifiers/reinvented.js +31 -11
- package/docs/architecture.md +26 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,8 +123,8 @@ oracle is never counted as a pass in either profile.
|
|
|
123
123
|
| `phantom-dep` | Imports absent from project manifests | Dependency manifests |
|
|
124
124
|
| `phantom-config` | Configuration keys with no declared source | Repository config index |
|
|
125
125
|
| `contract-drift` | Signature changes with callers left behind | Types and references |
|
|
126
|
-
| `reinvented` | New
|
|
127
|
-
| `dropped-guard` |
|
|
126
|
+
| `reinvented` | New cross-file declarations with a token-identical implementation, package, visibility, wrapper, and binding context | Base declaration + scoped token fingerprint |
|
|
127
|
+
| `dropped-guard` | Early-exit guards deleted while every other token in the file and changed source set stays unchanged | Pre/post control-flow AST |
|
|
128
128
|
| `swallowed-error` | Empty or ineffective error handling | AST shape |
|
|
129
129
|
| `vacuous-test` | Tests that do not assert behavior | Test AST |
|
|
130
130
|
| `assertion-drift` | Expectations changed under stable behavior | Pre/post test AST |
|
package/dist/git.js
CHANGED
|
@@ -89,7 +89,7 @@ function changedPaths(raw) {
|
|
|
89
89
|
}
|
|
90
90
|
const path = fields[i++];
|
|
91
91
|
if (path)
|
|
92
|
-
out.push({ path, beforePath: status === 'A' ? undefined : path });
|
|
92
|
+
out.push({ path, beforePath: status === 'A' ? undefined : path, deleted: status === 'D' });
|
|
93
93
|
}
|
|
94
94
|
return out;
|
|
95
95
|
}
|
|
@@ -195,9 +195,9 @@ export function collectChanges(root, range) {
|
|
|
195
195
|
}
|
|
196
196
|
const files = [];
|
|
197
197
|
const tracked = changedPaths(git(root, [
|
|
198
|
-
'diff', '--name-status', '-z', '--diff-filter=
|
|
198
|
+
'diff', '--name-status', '-z', '--diff-filter=ACDMRT', '--find-renames', ...diffArgs,
|
|
199
199
|
]));
|
|
200
|
-
for (const { path, beforePath } of tracked) {
|
|
200
|
+
for (const { path, beforePath, deleted } of tracked) {
|
|
201
201
|
const pathspecs = beforePath && beforePath !== path
|
|
202
202
|
? [':(literal)' + beforePath, ':(literal)' + path]
|
|
203
203
|
: [':(literal)' + path];
|
|
@@ -207,6 +207,8 @@ export function collectChanges(root, range) {
|
|
|
207
207
|
]);
|
|
208
208
|
files.push({
|
|
209
209
|
path,
|
|
210
|
+
beforePath: beforePath && beforePath !== path ? beforePath : undefined,
|
|
211
|
+
deleted,
|
|
210
212
|
added: addedLinesInPatch(patch),
|
|
211
213
|
before: beforePath === undefined ? undefined : fileAtRef(root, baseRef, beforePath),
|
|
212
214
|
});
|
package/dist/ground.js
CHANGED
|
@@ -4,6 +4,7 @@ import { decode } from './text.js';
|
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { PACKS, packFor, parseIsolated } from './lang/packs.js';
|
|
6
6
|
import { insideRepo, isSymlink, repoPath } from './fspolicy.js';
|
|
7
|
+
import { createReinventionScopeResolver, exportedDeclarations, typescriptImplementationFingerprint } from './reinvention.js';
|
|
7
8
|
const CODE_EXT = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
8
9
|
const TS_CONFIG = /^tsconfig(?:\..+)?\.json$/i;
|
|
9
10
|
const MISSING_TYPE_PREFIXES = [
|
|
@@ -215,7 +216,7 @@ function makeDepsFor(root) {
|
|
|
215
216
|
* packages, a syntax-only project for unconfigured changes and the base-ref trees,
|
|
216
217
|
* and one deduplicated symbol index over the relevant project closures.
|
|
217
218
|
*/
|
|
218
|
-
export async function buildGround(root, changed, signal) {
|
|
219
|
+
export async function buildGround(root, changed, signal, inventory = changed) {
|
|
219
220
|
root = resolve(root);
|
|
220
221
|
const directoryCache = new Map();
|
|
221
222
|
const projectCache = new Map();
|
|
@@ -252,6 +253,7 @@ export async function buildGround(root, changed, signal) {
|
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
const beforeProject = new Project({ useInMemoryFileSystem: true });
|
|
256
|
+
const beforeSources = new Map();
|
|
255
257
|
const files = [];
|
|
256
258
|
for (const c of changed) {
|
|
257
259
|
if (!CODE_EXT.test(c.path))
|
|
@@ -263,7 +265,12 @@ export async function buildGround(root, changed, signal) {
|
|
|
263
265
|
const sf = configured?.project.getSourceFile(abs) ?? syntaxProject.getSourceFile(abs);
|
|
264
266
|
if (!sf)
|
|
265
267
|
continue;
|
|
266
|
-
const
|
|
268
|
+
const beforeKey = c.beforePath ?? c.path;
|
|
269
|
+
let before = c.before === undefined ? undefined : beforeSources.get(beforeKey);
|
|
270
|
+
if (c.before !== undefined && !before) {
|
|
271
|
+
before = beforeProject.createSourceFile(`/before/${beforeKey}`, c.before);
|
|
272
|
+
beforeSources.set(beforeKey, before);
|
|
273
|
+
}
|
|
267
274
|
files.push({
|
|
268
275
|
sf,
|
|
269
276
|
changed: c,
|
|
@@ -288,8 +295,9 @@ export async function buildGround(root, changed, signal) {
|
|
|
288
295
|
configFiles,
|
|
289
296
|
beforeProject,
|
|
290
297
|
changed,
|
|
298
|
+
inventory,
|
|
291
299
|
files,
|
|
292
|
-
symbolIndex: buildSymbolIndex(sourceFiles, root),
|
|
300
|
+
symbolIndex: buildSymbolIndex(sourceFiles, root, changed, beforeProject),
|
|
293
301
|
deps: depsFor(join(root, 'x.ts')),
|
|
294
302
|
depsFor,
|
|
295
303
|
typed,
|
|
@@ -437,35 +445,75 @@ async function parseForeign(root, changed, signal) {
|
|
|
437
445
|
return result ? [result] : [];
|
|
438
446
|
});
|
|
439
447
|
}
|
|
440
|
-
function buildSymbolIndex(sourceFiles, root) {
|
|
448
|
+
function buildSymbolIndex(sourceFiles, root, changed, beforeProject) {
|
|
441
449
|
const index = new Map();
|
|
450
|
+
const changes = new Map(changed.map((file) => [file.path, file]));
|
|
451
|
+
const scopeFor = createReinventionScopeResolver(root);
|
|
452
|
+
const relevantNames = new Set();
|
|
453
|
+
for (const sf of sourceFiles) {
|
|
454
|
+
const rel = repoPath(root, String(sf.getFilePath()));
|
|
455
|
+
if (!changes.has(rel))
|
|
456
|
+
continue;
|
|
457
|
+
for (const declaration of sf.getFunctions()) {
|
|
458
|
+
const name = declaration.getName();
|
|
459
|
+
if (name)
|
|
460
|
+
relevantNames.add(normalizeName(name));
|
|
461
|
+
}
|
|
462
|
+
for (const declaration of sf.getVariableDeclarations()) {
|
|
463
|
+
const initializer = declaration.getInitializer();
|
|
464
|
+
if (initializer?.isKind(SyntaxKind.ArrowFunction) || initializer?.isKind(SyntaxKind.FunctionExpression)) {
|
|
465
|
+
relevantNames.add(normalizeName(declaration.getName()));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
442
469
|
for (const sf of sourceFiles) {
|
|
443
470
|
const path = String(sf.getFilePath());
|
|
444
471
|
// the project glob follows symlinked directories, so what it loaded is not
|
|
445
472
|
// proof of where the file is
|
|
446
473
|
if (path.includes('/node_modules/') || !insideRepo(root, path))
|
|
447
474
|
continue;
|
|
448
|
-
for (const
|
|
449
|
-
const
|
|
450
|
-
|
|
475
|
+
for (const { name, node: decl } of exportedDeclarations(sf)) {
|
|
476
|
+
const key = normalizeName(name);
|
|
477
|
+
// Fingerprint only names the change could have introduced. This keeps index
|
|
478
|
+
// construction proportional to the diff even when the project closure is a
|
|
479
|
+
// very large monorepo.
|
|
480
|
+
if (!relevantNames.has(key))
|
|
451
481
|
continue;
|
|
452
482
|
// only index things that could plausibly be reimplemented
|
|
453
483
|
const kind = decl.getKind();
|
|
454
484
|
if (kind !== SyntaxKind.FunctionDeclaration &&
|
|
455
|
-
kind !== SyntaxKind.VariableDeclaration
|
|
456
|
-
|
|
485
|
+
kind !== SyntaxKind.VariableDeclaration)
|
|
486
|
+
continue;
|
|
487
|
+
// A barrel alias can be new in this change even when its underlying callable
|
|
488
|
+
// predates it. Index the declaration from its own module, where both its name
|
|
489
|
+
// and base existence can be proved, rather than manufacturing history for the
|
|
490
|
+
// new alias or recording every `export *` as another copy.
|
|
491
|
+
if (decl.getSourceFile() !== sf)
|
|
457
492
|
continue;
|
|
458
|
-
// a barrel re-exports another module's symbol, so record where it is actually
|
|
459
|
-
// declared — otherwise `export * from './x'` makes every symbol look duplicated
|
|
460
493
|
const declPath = String(decl.getSourceFile().getFilePath());
|
|
461
494
|
if (declPath.includes('/node_modules/') || !insideRepo(root, declPath))
|
|
462
495
|
continue;
|
|
463
496
|
const rel = repoPath(root, declPath);
|
|
464
|
-
const
|
|
497
|
+
const fingerprint = typescriptImplementationFingerprint(decl, rel);
|
|
498
|
+
if (!fingerprint)
|
|
499
|
+
continue;
|
|
500
|
+
const change = changes.get(rel);
|
|
501
|
+
const beforePath = change?.beforePath ?? rel;
|
|
502
|
+
const before = change?.before === undefined ? undefined : beforeProject.getSourceFile('/before/' + beforePath);
|
|
503
|
+
const sameScope = scopeFor(beforePath) === scopeFor(rel);
|
|
504
|
+
const existedInBase = change === undefined || (sameScope && before ? exportedDeclarations(before).some((baseDeclaration) => baseDeclaration.name === name &&
|
|
505
|
+
typescriptImplementationFingerprint(baseDeclaration.node, beforePath) === fingerprint) : false);
|
|
465
506
|
const list = index.get(key) ?? [];
|
|
466
507
|
if (list.some((e) => e.file === rel && e.line === decl.getStartLineNumber()))
|
|
467
508
|
continue;
|
|
468
|
-
list.push({
|
|
509
|
+
list.push({
|
|
510
|
+
file: rel,
|
|
511
|
+
name,
|
|
512
|
+
line: decl.getStartLineNumber(),
|
|
513
|
+
fingerprint,
|
|
514
|
+
existedInBase,
|
|
515
|
+
scope: scopeFor(rel),
|
|
516
|
+
});
|
|
469
517
|
index.set(key, list);
|
|
470
518
|
}
|
|
471
519
|
}
|
package/dist/lang/packs.js
CHANGED
|
@@ -5,11 +5,29 @@ import { Worker } from 'node:worker_threads';
|
|
|
5
5
|
const COMMON_NODES = {
|
|
6
6
|
identifier: ['identifier'],
|
|
7
7
|
comment: ['comment', 'line_comment', 'block_comment'],
|
|
8
|
-
ifCondition: 'condition',
|
|
9
|
-
ifBody: 'consequence',
|
|
8
|
+
ifCondition: ['condition'],
|
|
9
|
+
ifBody: ['consequence', 'body'],
|
|
10
|
+
ifAlternative: ['alternative'],
|
|
10
11
|
declarationName: 'name',
|
|
11
12
|
block: ['block'],
|
|
13
|
+
callableBody: ['block'],
|
|
14
|
+
callableOwner: [],
|
|
15
|
+
callableOwnerBody: [],
|
|
16
|
+
fileScope: [],
|
|
17
|
+
reusableContainer: [],
|
|
18
|
+
reusableScope: [],
|
|
19
|
+
reusableWrapper: [],
|
|
20
|
+
reusablePrefix: [],
|
|
21
|
+
reusableBlocker: [],
|
|
22
|
+
bindingContext: [],
|
|
23
|
+
bindingDeclaration: [],
|
|
24
|
+
bindingIdentifier: ['identifier'],
|
|
12
25
|
};
|
|
26
|
+
function declarationHeader(node) {
|
|
27
|
+
const boundaries = [node.text.indexOf('{')];
|
|
28
|
+
const end = boundaries.filter((index) => index >= 0).sort((left, right) => left - right)[0];
|
|
29
|
+
return end === undefined ? node.text : node.text.slice(0, end);
|
|
30
|
+
}
|
|
13
31
|
/** Pull the key out of `getenv("HOME")` / `environ["HOME"]` style reads. */
|
|
14
32
|
function envKeysFrom(root, callPattern, callTypes) {
|
|
15
33
|
const out = [];
|
|
@@ -106,9 +124,16 @@ const PYTHON = {
|
|
|
106
124
|
nodes: {
|
|
107
125
|
...COMMON_NODES,
|
|
108
126
|
ifStatement: ['if_statement'],
|
|
109
|
-
ifBody: 'consequence',
|
|
110
127
|
bail: ['return_statement', 'raise_statement', 'continue_statement', 'break_statement'],
|
|
111
128
|
declaration: ['function_definition', 'class_definition'],
|
|
129
|
+
callable: ['function_definition'],
|
|
130
|
+
callableOwner: ['class_definition', 'decorated_definition'],
|
|
131
|
+
callableOwnerBody: ['block', 'function_definition', 'class_definition'],
|
|
132
|
+
reusableDeclaration: ['function_definition', 'class_definition'],
|
|
133
|
+
reusableContainer: ['decorated_definition'],
|
|
134
|
+
reusableWrapper: ['decorated_definition'],
|
|
135
|
+
bindingContext: ['import_statement', 'import_from_statement'],
|
|
136
|
+
bindingDeclaration: ['expression_statement'],
|
|
112
137
|
},
|
|
113
138
|
envReads(root) {
|
|
114
139
|
return envKeysFrom(root, /os\.environ|getenv/, ['call', 'subscript']);
|
|
@@ -251,6 +276,12 @@ const PYTHON = {
|
|
|
251
276
|
}
|
|
252
277
|
return out;
|
|
253
278
|
},
|
|
279
|
+
fileConstraints(root) {
|
|
280
|
+
return root.namedChildren
|
|
281
|
+
.filter((node) => COMMON_NODES.comment.includes(node.type) && node.startPosition.row <= 1)
|
|
282
|
+
.map((node) => node.text.trim())
|
|
283
|
+
.filter((text) => /coding\s*[:=]/.test(text));
|
|
284
|
+
},
|
|
254
285
|
};
|
|
255
286
|
const GO = {
|
|
256
287
|
name: 'go',
|
|
@@ -261,6 +292,12 @@ const GO = {
|
|
|
261
292
|
ifStatement: ['if_statement'],
|
|
262
293
|
bail: ['return_statement', 'continue_statement', 'break_statement', 'goto_statement'],
|
|
263
294
|
declaration: ['function_declaration', 'method_declaration', 'type_declaration'],
|
|
295
|
+
callable: ['function_declaration', 'method_declaration'],
|
|
296
|
+
fileScope: ['package_clause'],
|
|
297
|
+
reusableDeclaration: ['function_declaration', 'type_declaration'],
|
|
298
|
+
bindingContext: ['import_declaration'],
|
|
299
|
+
bindingDeclaration: ['const_declaration', 'var_declaration', 'type_declaration'],
|
|
300
|
+
bindingIdentifier: ['identifier', 'type_identifier'],
|
|
264
301
|
},
|
|
265
302
|
envReads(root) {
|
|
266
303
|
return envKeysFrom(root, /os\.Getenv|os\.LookupEnv/, ['call_expression']);
|
|
@@ -281,6 +318,12 @@ const GO = {
|
|
|
281
318
|
// the blank identifier is someone saying "I know". An empty `if err != nil` isn't.
|
|
282
319
|
return out;
|
|
283
320
|
},
|
|
321
|
+
fileConstraints(root) {
|
|
322
|
+
return root.namedChildren
|
|
323
|
+
.filter((node) => COMMON_NODES.comment.includes(node.type))
|
|
324
|
+
.map((node) => node.text.trim())
|
|
325
|
+
.filter((text) => /^\/\/go:build\b|^\/\/\s*\+build\b/.test(text));
|
|
326
|
+
},
|
|
284
327
|
};
|
|
285
328
|
const JAVA = {
|
|
286
329
|
name: 'java',
|
|
@@ -291,6 +334,17 @@ const JAVA = {
|
|
|
291
334
|
ifStatement: ['if_statement'],
|
|
292
335
|
bail: ['return_statement', 'throw_statement', 'continue_statement', 'break_statement'],
|
|
293
336
|
declaration: ['method_declaration', 'class_declaration', 'interface_declaration', 'record_declaration'],
|
|
337
|
+
callable: ['method_declaration', 'constructor_declaration'],
|
|
338
|
+
fileScope: ['package_declaration'],
|
|
339
|
+
callableBody: ['block', 'constructor_body'],
|
|
340
|
+
callableOwner: [
|
|
341
|
+
'class_declaration', 'interface_declaration', 'record_declaration',
|
|
342
|
+
'enum_declaration', 'annotation_type_declaration',
|
|
343
|
+
],
|
|
344
|
+
callableOwnerBody: ['class_body', 'interface_body', 'record_body', 'enum_body', 'annotation_type_body'],
|
|
345
|
+
reusableDeclaration: ['class_declaration', 'interface_declaration', 'record_declaration'],
|
|
346
|
+
bindingContext: ['import_declaration'],
|
|
347
|
+
block: ['block', 'constructor_body'],
|
|
294
348
|
},
|
|
295
349
|
envReads(root) {
|
|
296
350
|
return envKeysFrom(root, /System\.getenv/, ['method_invocation']);
|
|
@@ -304,10 +358,16 @@ const RUST = {
|
|
|
304
358
|
nodes: {
|
|
305
359
|
...COMMON_NODES,
|
|
306
360
|
ifStatement: ['if_expression'],
|
|
307
|
-
ifCondition: 'condition',
|
|
308
|
-
ifBody: 'consequence',
|
|
309
361
|
bail: ['return_expression', 'break_expression', 'continue_expression'],
|
|
310
362
|
declaration: ['function_item', 'struct_item', 'enum_item', 'trait_item'],
|
|
363
|
+
callable: ['function_item'],
|
|
364
|
+
callableOwner: ['impl_item', 'trait_item', 'mod_item'],
|
|
365
|
+
callableOwnerBody: ['declaration_list'],
|
|
366
|
+
reusableDeclaration: ['function_item', 'struct_item', 'enum_item', 'trait_item'],
|
|
367
|
+
reusablePrefix: ['attribute_item'],
|
|
368
|
+
bindingContext: ['use_declaration', 'extern_crate_declaration'],
|
|
369
|
+
bindingDeclaration: ['const_item', 'static_item', 'type_item', 'mod_item'],
|
|
370
|
+
bindingIdentifier: ['identifier', 'type_identifier'],
|
|
311
371
|
},
|
|
312
372
|
envReads(root) {
|
|
313
373
|
return envKeysFrom(root, /env::var|env::var_os/, ['call_expression']);
|
|
@@ -328,6 +388,14 @@ const RUST = {
|
|
|
328
388
|
}
|
|
329
389
|
return out;
|
|
330
390
|
},
|
|
391
|
+
reusableAcrossFiles(node) {
|
|
392
|
+
// `pub(self)`, `pub(super)`, and `pub(in ...)` need a module graph to prove
|
|
393
|
+
// accessibility from another file. Plain `pub` and crate-wide visibility do not.
|
|
394
|
+
return /\bpub(?:\s*\(\s*crate\s*\))?\s+/.test(declarationHeader(node));
|
|
395
|
+
},
|
|
396
|
+
fileConstraints(root) {
|
|
397
|
+
return nodesOfType(root, ['inner_attribute_item']).map((node) => node.text.trim());
|
|
398
|
+
},
|
|
331
399
|
};
|
|
332
400
|
const CLIKE_LOG = /^(std::(cout|cerr)|printf|fprintf|Console\.|Log|log|logger|error_log|print_r|var_dump)/;
|
|
333
401
|
const CPP = {
|
|
@@ -339,11 +407,30 @@ const CPP = {
|
|
|
339
407
|
ifStatement: ['if_statement'],
|
|
340
408
|
bail: ['return_statement', 'throw_statement', 'break_statement', 'continue_statement', 'goto_statement'],
|
|
341
409
|
declaration: ['function_definition'],
|
|
410
|
+
callable: ['function_definition'],
|
|
411
|
+
callableBody: ['compound_statement'],
|
|
412
|
+
callableOwner: [
|
|
413
|
+
'namespace_definition', 'class_specifier', 'struct_specifier', 'union_specifier', 'template_declaration',
|
|
414
|
+
],
|
|
415
|
+
callableOwnerBody: ['declaration_list', 'field_declaration_list', 'function_definition'],
|
|
416
|
+
reusableDeclaration: ['function_definition'],
|
|
417
|
+
reusableContainer: ['namespace_definition', 'declaration_list', 'template_declaration'],
|
|
418
|
+
reusableScope: ['namespace_definition'],
|
|
419
|
+
reusableWrapper: ['template_declaration'],
|
|
420
|
+
bindingContext: [
|
|
421
|
+
'preproc_include', 'preproc_def', 'preproc_function_def',
|
|
422
|
+
'using_declaration', 'alias_declaration', 'namespace_alias_definition',
|
|
423
|
+
],
|
|
424
|
+
bindingDeclaration: ['declaration', 'type_definition'],
|
|
425
|
+
bindingIdentifier: ['identifier', 'type_identifier', 'namespace_identifier'],
|
|
342
426
|
declarationName: 'declarator',
|
|
343
427
|
block: ['compound_statement'],
|
|
344
428
|
},
|
|
345
429
|
envReads: (root) => envKeysFrom(root, /getenv|GetEnvironmentVariable/, ['call_expression']),
|
|
346
430
|
swallowedError: catchBased(['catch_clause'], 'body', CLIKE_LOG),
|
|
431
|
+
reusableAcrossFiles(node) {
|
|
432
|
+
return !/\bstatic\b/.test(declarationHeader(node));
|
|
433
|
+
},
|
|
347
434
|
};
|
|
348
435
|
const C = {
|
|
349
436
|
name: 'c',
|
|
@@ -354,10 +441,19 @@ const C = {
|
|
|
354
441
|
ifStatement: ['if_statement'],
|
|
355
442
|
bail: ['return_statement', 'break_statement', 'continue_statement', 'goto_statement'],
|
|
356
443
|
declaration: ['function_definition'],
|
|
444
|
+
callable: ['function_definition'],
|
|
445
|
+
callableBody: ['compound_statement'],
|
|
446
|
+
reusableDeclaration: ['function_definition'],
|
|
447
|
+
bindingContext: ['preproc_include', 'preproc_def', 'preproc_function_def'],
|
|
448
|
+
bindingDeclaration: ['declaration', 'type_definition'],
|
|
449
|
+
bindingIdentifier: ['identifier', 'type_identifier'],
|
|
357
450
|
declarationName: 'declarator',
|
|
358
451
|
block: ['compound_statement'],
|
|
359
452
|
},
|
|
360
453
|
envReads: (root) => envKeysFrom(root, /getenv/, ['call_expression']),
|
|
454
|
+
reusableAcrossFiles(node) {
|
|
455
|
+
return !/\bstatic\b/.test(declarationHeader(node));
|
|
456
|
+
},
|
|
361
457
|
// C has no exceptions; its error handling is return codes, which cannot be told
|
|
362
458
|
// from ordinary control flow without types. The other checks still apply.
|
|
363
459
|
};
|
|
@@ -370,6 +466,17 @@ const CSHARP = {
|
|
|
370
466
|
ifStatement: ['if_statement'],
|
|
371
467
|
bail: ['return_statement', 'throw_statement', 'break_statement', 'continue_statement'],
|
|
372
468
|
declaration: ['method_declaration', 'class_declaration', 'interface_declaration', 'record_declaration'],
|
|
469
|
+
callable: ['method_declaration', 'constructor_declaration', 'local_function_statement'],
|
|
470
|
+
callableOwner: [
|
|
471
|
+
'namespace_declaration', 'file_scoped_namespace_declaration',
|
|
472
|
+
'class_declaration', 'struct_declaration', 'interface_declaration', 'record_declaration',
|
|
473
|
+
],
|
|
474
|
+
callableOwnerBody: ['declaration_list'],
|
|
475
|
+
reusableDeclaration: ['class_declaration', 'interface_declaration', 'record_declaration'],
|
|
476
|
+
reusableContainer: ['namespace_declaration', 'file_scoped_namespace_declaration', 'declaration_list'],
|
|
477
|
+
reusableScope: ['namespace_declaration', 'file_scoped_namespace_declaration'],
|
|
478
|
+
reusableBlocker: ['ERROR'],
|
|
479
|
+
bindingContext: ['using_directive', 'extern_alias_directive'],
|
|
373
480
|
block: ['block', 'declaration_list'],
|
|
374
481
|
},
|
|
375
482
|
envReads: (root) => envKeysFrom(root, /GetEnvironmentVariable/, ['invocation_expression']),
|
|
@@ -384,6 +491,18 @@ const PHP = {
|
|
|
384
491
|
ifStatement: ['if_statement'],
|
|
385
492
|
bail: ['return_statement', 'throw_expression', 'break_statement', 'continue_statement'],
|
|
386
493
|
declaration: ['function_definition', 'method_declaration', 'class_declaration'],
|
|
494
|
+
callable: ['function_definition', 'method_declaration'],
|
|
495
|
+
callableBody: ['compound_statement'],
|
|
496
|
+
callableOwner: [
|
|
497
|
+
'namespace_definition', 'class_declaration', 'interface_declaration', 'trait_declaration', 'enum_declaration',
|
|
498
|
+
],
|
|
499
|
+
callableOwnerBody: ['compound_statement', 'declaration_list'],
|
|
500
|
+
reusableDeclaration: ['function_definition', 'class_declaration'],
|
|
501
|
+
reusableContainer: ['namespace_definition', 'compound_statement'],
|
|
502
|
+
reusableScope: ['namespace_definition'],
|
|
503
|
+
bindingContext: ['namespace_use_declaration', 'expression_statement'],
|
|
504
|
+
bindingDeclaration: ['const_declaration'],
|
|
505
|
+
bindingIdentifier: ['name', 'variable_name'],
|
|
387
506
|
block: ['compound_statement'],
|
|
388
507
|
},
|
|
389
508
|
envReads: (root) => envKeysFrom(root, /getenv|\$_ENV/, ['function_call_expression', 'subscript_expression']),
|
|
@@ -399,10 +518,23 @@ const KOTLIN = {
|
|
|
399
518
|
ifStatement: ['if_expression'],
|
|
400
519
|
bail: ['jump_expression'],
|
|
401
520
|
declaration: ['function_declaration', 'class_declaration', 'object_declaration'],
|
|
521
|
+
callable: ['function_declaration'],
|
|
522
|
+
fileScope: ['package_header'],
|
|
523
|
+
callableBody: ['function_body', 'block', 'statements'],
|
|
524
|
+
callableOwner: ['class_declaration', 'object_declaration'],
|
|
525
|
+
callableOwnerBody: ['class_body'],
|
|
526
|
+
reusableDeclaration: ['function_declaration', 'class_declaration', 'object_declaration'],
|
|
527
|
+
reusableContainer: ['import_list'],
|
|
528
|
+
bindingContext: ['import_header'],
|
|
529
|
+
bindingDeclaration: ['property_declaration', 'type_alias'],
|
|
530
|
+
bindingIdentifier: ['simple_identifier', 'type_identifier'],
|
|
402
531
|
block: ['statements', 'block'],
|
|
403
532
|
},
|
|
404
533
|
envReads: (root) => envKeysFrom(root, /System\.getenv|getenv/, ['call_expression']),
|
|
405
534
|
swallowedError: catchBased(['catch_block'], undefined, /^(println|print|log|logger|Log)\b/),
|
|
535
|
+
reusableAcrossFiles(node) {
|
|
536
|
+
return !/\bprivate\b/.test(declarationHeader(node));
|
|
537
|
+
},
|
|
406
538
|
};
|
|
407
539
|
/*
|
|
408
540
|
* Swift is deliberately absent.
|
|
@@ -427,6 +559,14 @@ const SWIFT_DISABLED = {
|
|
|
427
559
|
ifStatement: ['if_statement'],
|
|
428
560
|
bail: ['control_transfer_statement'],
|
|
429
561
|
declaration: ['function_declaration', 'class_declaration', 'protocol_declaration'],
|
|
562
|
+
callable: ['function_declaration'],
|
|
563
|
+
callableBody: ['function_body', 'statements'],
|
|
564
|
+
callableOwner: [
|
|
565
|
+
'class_declaration', 'struct_declaration', 'protocol_declaration',
|
|
566
|
+
'extension_declaration', 'enum_declaration',
|
|
567
|
+
],
|
|
568
|
+
callableOwnerBody: ['class_body', 'protocol_body', 'enum_class_body'],
|
|
569
|
+
reusableDeclaration: ['function_declaration', 'class_declaration', 'protocol_declaration'],
|
|
430
570
|
block: ['statements', 'function_body'],
|
|
431
571
|
},
|
|
432
572
|
envReads: (root) => envKeysFrom(root, /ProcessInfo|environment/, ['call_expression', 'subscript_expression']),
|
|
@@ -438,11 +578,17 @@ const RUBY = {
|
|
|
438
578
|
grammar: 'tree-sitter-ruby',
|
|
439
579
|
nodes: {
|
|
440
580
|
...COMMON_NODES,
|
|
441
|
-
ifStatement: ['if', 'if_modifier'],
|
|
442
|
-
ifCondition: 'condition',
|
|
443
|
-
ifBody: 'consequence',
|
|
581
|
+
ifStatement: ['if', 'if_modifier', 'unless', 'unless_modifier'],
|
|
444
582
|
bail: ['return', 'break', 'next'],
|
|
445
583
|
declaration: ['method', 'singleton_method', 'class', 'module'],
|
|
584
|
+
callable: ['method', 'singleton_method'],
|
|
585
|
+
callableBody: ['body_statement'],
|
|
586
|
+
callableOwner: ['class', 'module'],
|
|
587
|
+
callableOwnerBody: ['body_statement'],
|
|
588
|
+
reusableDeclaration: ['method', 'singleton_method', 'class', 'module'],
|
|
589
|
+
bindingContext: ['call'],
|
|
590
|
+
bindingDeclaration: ['assignment'],
|
|
591
|
+
bindingIdentifier: ['identifier', 'constant'],
|
|
446
592
|
block: ['body_statement', 'do_block', 'block'],
|
|
447
593
|
},
|
|
448
594
|
envReads: (root) => envKeysFrom(root, /ENV/, ['element_reference', 'call']),
|
|
@@ -477,6 +623,12 @@ const RUBY = {
|
|
|
477
623
|
}
|
|
478
624
|
return out;
|
|
479
625
|
},
|
|
626
|
+
fileConstraints(root) {
|
|
627
|
+
return root.namedChildren
|
|
628
|
+
.filter((node) => COMMON_NODES.comment.includes(node.type) && node.startPosition.row <= 1)
|
|
629
|
+
.map((node) => node.text.trim())
|
|
630
|
+
.filter((text) => /^#\s*(?:frozen_string_literal|encoding|coding)\s*[:=]/.test(text));
|
|
631
|
+
},
|
|
480
632
|
};
|
|
481
633
|
void SWIFT_DISABLED;
|
|
482
634
|
const SOLIDITY = {
|
|
@@ -489,6 +641,16 @@ const SOLIDITY = {
|
|
|
489
641
|
// revert and require are how a contract refuses, alongside plain return
|
|
490
642
|
bail: ['return_statement', 'revert_statement', 'break_statement', 'continue_statement'],
|
|
491
643
|
declaration: ['function_definition', 'contract_declaration', 'modifier_definition'],
|
|
644
|
+
callable: ['function_definition', 'modifier_definition'],
|
|
645
|
+
callableBody: ['function_body', 'block_statement'],
|
|
646
|
+
callableOwner: ['contract_declaration'],
|
|
647
|
+
callableOwnerBody: ['contract_body'],
|
|
648
|
+
reusableDeclaration: ['function_definition', 'contract_declaration'],
|
|
649
|
+
bindingContext: ['import_directive', 'pragma_directive'],
|
|
650
|
+
bindingDeclaration: [
|
|
651
|
+
'constant_variable_declaration', 'struct_declaration', 'enum_declaration',
|
|
652
|
+
'user_defined_value_type_definition',
|
|
653
|
+
],
|
|
492
654
|
block: ['block_statement', 'function_body', 'contract_body'],
|
|
493
655
|
},
|
|
494
656
|
swallowedError: catchBased(['catch_clause'], 'body', /^(emit|console\.log)/),
|