@0xcraft/powershot 1.1.4 → 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 +19 -12
- package/dist/lang/packs.js +170 -8
- package/dist/langtest.js +1056 -6
- package/dist/reinvention.js +227 -2
- package/dist/report/sarif.js +2 -2
- package/dist/review.js +5 -1
- package/dist/selftest.js +417 -9
- package/dist/verifiers/dropped-guard.js +188 -84
- package/dist/verifiers/foreign-dropped-guard.js +174 -50
- package/dist/verifiers/foreign-reinvented.js +22 -7
- package/dist/verifiers/foreign-tokens.js +228 -19
- package/dist/verifiers/guard-diff.js +138 -0
- package/dist/verifiers/reinvented.js +7 -5
- package/docs/architecture.md +25 -0
- 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,7 +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, typescriptImplementationFingerprint } from './reinvention.js';
|
|
7
|
+
import { createReinventionScopeResolver, exportedDeclarations, typescriptImplementationFingerprint } from './reinvention.js';
|
|
8
8
|
const CODE_EXT = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
9
9
|
const TS_CONFIG = /^tsconfig(?:\..+)?\.json$/i;
|
|
10
10
|
const MISSING_TYPE_PREFIXES = [
|
|
@@ -216,7 +216,7 @@ function makeDepsFor(root) {
|
|
|
216
216
|
* packages, a syntax-only project for unconfigured changes and the base-ref trees,
|
|
217
217
|
* and one deduplicated symbol index over the relevant project closures.
|
|
218
218
|
*/
|
|
219
|
-
export async function buildGround(root, changed, signal) {
|
|
219
|
+
export async function buildGround(root, changed, signal, inventory = changed) {
|
|
220
220
|
root = resolve(root);
|
|
221
221
|
const directoryCache = new Map();
|
|
222
222
|
const projectCache = new Map();
|
|
@@ -253,6 +253,7 @@ export async function buildGround(root, changed, signal) {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
const beforeProject = new Project({ useInMemoryFileSystem: true });
|
|
256
|
+
const beforeSources = new Map();
|
|
256
257
|
const files = [];
|
|
257
258
|
for (const c of changed) {
|
|
258
259
|
if (!CODE_EXT.test(c.path))
|
|
@@ -264,7 +265,12 @@ export async function buildGround(root, changed, signal) {
|
|
|
264
265
|
const sf = configured?.project.getSourceFile(abs) ?? syntaxProject.getSourceFile(abs);
|
|
265
266
|
if (!sf)
|
|
266
267
|
continue;
|
|
267
|
-
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
|
+
}
|
|
268
274
|
files.push({
|
|
269
275
|
sf,
|
|
270
276
|
changed: c,
|
|
@@ -289,6 +295,7 @@ export async function buildGround(root, changed, signal) {
|
|
|
289
295
|
configFiles,
|
|
290
296
|
beforeProject,
|
|
291
297
|
changed,
|
|
298
|
+
inventory,
|
|
292
299
|
files,
|
|
293
300
|
symbolIndex: buildSymbolIndex(sourceFiles, root, changed, beforeProject),
|
|
294
301
|
deps: depsFor(join(root, 'x.ts')),
|
|
@@ -465,24 +472,18 @@ function buildSymbolIndex(sourceFiles, root, changed, beforeProject) {
|
|
|
465
472
|
// proof of where the file is
|
|
466
473
|
if (path.includes('/node_modules/') || !insideRepo(root, path))
|
|
467
474
|
continue;
|
|
468
|
-
for (const
|
|
475
|
+
for (const { name, node: decl } of exportedDeclarations(sf)) {
|
|
469
476
|
const key = normalizeName(name);
|
|
470
477
|
// Fingerprint only names the change could have introduced. This keeps index
|
|
471
478
|
// construction proportional to the diff even when the project closure is a
|
|
472
479
|
// very large monorepo.
|
|
473
480
|
if (!relevantNames.has(key))
|
|
474
481
|
continue;
|
|
475
|
-
const decl = decls[0];
|
|
476
|
-
if (!decl)
|
|
477
|
-
continue;
|
|
478
482
|
// only index things that could plausibly be reimplemented
|
|
479
483
|
const kind = decl.getKind();
|
|
480
484
|
if (kind !== SyntaxKind.FunctionDeclaration &&
|
|
481
485
|
kind !== SyntaxKind.VariableDeclaration)
|
|
482
486
|
continue;
|
|
483
|
-
const fingerprint = typescriptImplementationFingerprint(decl);
|
|
484
|
-
if (!fingerprint)
|
|
485
|
-
continue;
|
|
486
487
|
// A barrel alias can be new in this change even when its underlying callable
|
|
487
488
|
// predates it. Index the declaration from its own module, where both its name
|
|
488
489
|
// and base existence can be proved, rather than manufacturing history for the
|
|
@@ -493,9 +494,15 @@ function buildSymbolIndex(sourceFiles, root, changed, beforeProject) {
|
|
|
493
494
|
if (declPath.includes('/node_modules/') || !insideRepo(root, declPath))
|
|
494
495
|
continue;
|
|
495
496
|
const rel = repoPath(root, declPath);
|
|
497
|
+
const fingerprint = typescriptImplementationFingerprint(decl, rel);
|
|
498
|
+
if (!fingerprint)
|
|
499
|
+
continue;
|
|
496
500
|
const change = changes.get(rel);
|
|
497
|
-
const
|
|
498
|
-
const
|
|
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);
|
|
499
506
|
const list = index.get(key) ?? [];
|
|
500
507
|
if (list.some((e) => e.file === rel && e.line === decl.getStartLineNumber()))
|
|
501
508
|
continue;
|
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)/),
|