@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 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 helpers duplicating existing symbols | Symbol index |
127
- | `dropped-guard` | Removed guards, early returns, protective branches | Pre/post AST |
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=ACMRT', '--find-renames', ...diffArgs,
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 before = c.before === undefined ? undefined : beforeProject.createSourceFile(`/before/${c.path}`, c.before, { overwrite: true });
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 [name, decls] of sf.getExportedDeclarations()) {
449
- const decl = decls[0];
450
- if (!decl)
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
- kind !== SyntaxKind.ClassDeclaration)
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 key = normalizeName(name);
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({ file: rel, name, line: decl.getStartLineNumber() });
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
  }
@@ -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)/),