@0xcraft/powershot 1.1.4 → 1.2.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/dist/langtest.js CHANGED
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
10
10
  import { execFileSync } from 'node:child_process';
11
11
  import { Project } from 'ts-morph';
12
12
  import { PACKS, parse } from './lang/packs.js';
13
- import { foreignReinvented, tokensFor } from './verifiers/foreign.js';
13
+ import { foreignDroppedGuard, foreignReinvented, tokensFor } from './verifiers/foreign.js';
14
14
  /** each fixture: a handler that discards, one that genuinely handles, one explained */
15
15
  const FIXTURES = {
16
16
  python: 'def f():\n try:\n a()\n except Exception:\n pass\n try:\n b()\n except Exception as e:\n raise RuntimeError("x") from e\n try:\n c()\n except Exception:\n pass # deliberate\n',
@@ -37,7 +37,7 @@ const REINVENTED_NAME = {
37
37
  python: ['def f():', 'def normalize_payload():'],
38
38
  go: ['func F()', 'func NormalizePayload()'],
39
39
  java: ['class A', 'class NormalizePayload'],
40
- rust: ['fn f()', 'fn normalize_payload()'],
40
+ rust: ['fn f()', 'pub fn normalize_payload()'],
41
41
  cpp: ['void f()', 'void normalizePayload()'],
42
42
  c: ['int f(', 'int normalizePayload('],
43
43
  'c#': ['class A', 'class NormalizePayload'],
@@ -59,13 +59,622 @@ const REINVENTED_MUTATION = {
59
59
  ruby: [' a\n', ' z\n'],
60
60
  solidity: ['a()', 'z()'],
61
61
  };
62
+ const NESTED_REINVENTION = {
63
+ python: [
64
+ 'class Primary:\n def normalize_payload(self):\n return True\n',
65
+ 'class Recording:\n def normalize_payload(self):\n return True\n',
66
+ ],
67
+ go: [
68
+ 'package sample\ntype Primary struct{}\nfunc (Primary) NormalizePayload() bool { return true }\n',
69
+ 'package sample\ntype Recording struct{}\nfunc (Recording) NormalizePayload() bool { return true }\n',
70
+ ],
71
+ java: [
72
+ 'class Primary { boolean normalizePayload() { return true; } }\n',
73
+ 'class Recording { boolean normalizePayload() { return true; } }\n',
74
+ ],
75
+ cpp: [
76
+ 'struct Primary { bool normalizePayload() { return true; } };\n',
77
+ 'struct Recording { bool normalizePayload() { return true; } };\n',
78
+ ],
79
+ 'c#': [
80
+ 'class Primary { bool NormalizePayload() { return true; } }\n',
81
+ 'class Recording { bool NormalizePayload() { return true; } }\n',
82
+ ],
83
+ php: [
84
+ '<?php class Primary { function normalizePayload() { return true; } }\n',
85
+ '<?php class Recording { function normalizePayload() { return true; } }\n',
86
+ ],
87
+ kotlin: [
88
+ 'class Primary { fun normalizePayload(): Boolean = true }\n',
89
+ 'class Recording { fun normalizePayload(): Boolean = true }\n',
90
+ ],
91
+ ruby: [
92
+ 'class Primary\n def normalize_payload\n true\n end\nend\n',
93
+ 'class Recording\n def normalize_payload\n true\n end\nend\n',
94
+ ],
95
+ solidity: [
96
+ 'contract Primary { function normalizePayload() public pure returns (bool) { return true; } }\n',
97
+ 'contract Recording { function normalizePayload() public pure returns (bool) { return true; } }\n',
98
+ ],
99
+ };
100
+ const WRAPPED_REINVENTION = {
101
+ python: {
102
+ same: '@portable\ndef normalize_payload():\n return True\n',
103
+ differentWrapper: '@separate\ndef normalize_payload():\n return True\n',
104
+ },
105
+ cpp: {
106
+ same: [
107
+ 'namespace portable {',
108
+ 'template <typename T>',
109
+ 'T normalizePayload(T value) { return value; }',
110
+ '}',
111
+ '',
112
+ ].join('\n'),
113
+ differentScope: [
114
+ 'namespace separate {',
115
+ 'template <typename T>',
116
+ 'T normalizePayload(T value) { return value; }',
117
+ '}',
118
+ '',
119
+ ].join('\n'),
120
+ differentWrapper: [
121
+ 'namespace portable {',
122
+ 'template <typename T, typename U>',
123
+ 'T normalizePayload(T value) { return value; }',
124
+ '}',
125
+ '',
126
+ ].join('\n'),
127
+ },
128
+ 'c#': {
129
+ same: 'namespace Portable.Sample { class NormalizePayload { bool Run() { return true; } } }\n',
130
+ differentScope: 'namespace portable.Sample { class NormalizePayload { bool Run() { return true; } } }\n',
131
+ },
132
+ php: {
133
+ same: '<?php namespace Portable\\Sample { function normalizePayload() { return true; } }\n',
134
+ differentScope: '<?php namespace Separate\\Sample { function normalizePayload() { return true; } }\n',
135
+ },
136
+ };
137
+ const FILE_PRIVATE_REINVENTION = {
138
+ rust: '#[inline]\nfn normalize_payload() -> bool { true }\n',
139
+ cpp: '[[maybe_unused]] static int normalizePayload(int value) { return value; }\n',
140
+ c: 'static int normalizePayload(int value) { return value; }\n',
141
+ kotlin: '@Deprecated("fixture")\nprivate fun normalizePayload(): Boolean = true\n',
142
+ };
143
+ const BINDING_CONTEXT_REINVENTION = {
144
+ python: {
145
+ same: 'from alpha import Tools\ndef normalize_payload(value):\n return value\n',
146
+ different: 'from beta import Tools\ndef normalize_payload(value):\n return value\n',
147
+ },
148
+ go: {
149
+ same: 'package sample\nimport _ "alpha"\nfunc NormalizePayload(value int) int { return value }\n',
150
+ different: 'package sample\nimport _ "beta"\nfunc NormalizePayload(value int) int { return value }\n',
151
+ },
152
+ java: {
153
+ same: 'import alpha.Tools;\nclass NormalizePayload { boolean run() { return true; } }\n',
154
+ different: 'import beta.Tools;\nclass NormalizePayload { boolean run() { return true; } }\n',
155
+ },
156
+ rust: {
157
+ same: 'use alpha::Tools;\npub fn normalize_payload() -> bool { true }\n',
158
+ different: 'use beta::Tools;\npub fn normalize_payload() -> bool { true }\n',
159
+ },
160
+ cpp: {
161
+ same: '#include "alpha.h"\nint normalizePayload(int value) { return value; }\n',
162
+ different: '#include "beta.h"\nint normalizePayload(int value) { return value; }\n',
163
+ },
164
+ c: {
165
+ same: '#include "alpha.h"\nint normalizePayload(int value) { return value; }\n',
166
+ different: '#include "beta.h"\nint normalizePayload(int value) { return value; }\n',
167
+ },
168
+ 'c#': {
169
+ same: 'using Alpha;\nclass NormalizePayload { bool Run() { return true; } }\n',
170
+ different: 'using Beta;\nclass NormalizePayload { bool Run() { return true; } }\n',
171
+ },
172
+ php: {
173
+ same: '<?php use Alpha\\Tools;\nfunction normalizePayload($value) { return $value; }\n',
174
+ different: '<?php use Beta\\Tools;\nfunction normalizePayload($value) { return $value; }\n',
175
+ },
176
+ kotlin: {
177
+ same: 'import alpha.Tools\nfun normalizePayload(value: Int): Int = value\n',
178
+ different: 'import beta.Tools\nfun normalizePayload(value: Int): Int = value\n',
179
+ },
180
+ ruby: {
181
+ same: 'require "alpha"\ndef normalize_payload(value)\n value\nend\n',
182
+ different: 'require "beta"\ndef normalize_payload(value)\n value\nend\n',
183
+ },
184
+ solidity: {
185
+ same: 'import "./alpha.sol";\nfunction normalizePayload(uint value) pure returns (uint) { return value; }\n',
186
+ different: 'import "./beta.sol";\nfunction normalizePayload(uint value) pure returns (uint) { return value; }\n',
187
+ },
188
+ };
189
+ const MODULE_BINDING_REINVENTION = {
190
+ python: {
191
+ same: 'SCALE = 2\ndef normalize_payload(value):\n return value * SCALE\n',
192
+ different: 'SCALE = 3\ndef normalize_payload(value):\n return value * SCALE\n',
193
+ },
194
+ go: {
195
+ same: 'package sample\nconst SCALE = 2\nfunc NormalizePayload(value int) int { return value * SCALE }\n',
196
+ different: 'package sample\nconst SCALE = 3\nfunc NormalizePayload(value int) int { return value * SCALE }\n',
197
+ },
198
+ rust: {
199
+ same: 'const SCALE: i32 = 2;\npub fn normalize_payload(value: i32) -> i32 { value * SCALE }\n',
200
+ different: 'const SCALE: i32 = 3;\npub fn normalize_payload(value: i32) -> i32 { value * SCALE }\n',
201
+ },
202
+ cpp: {
203
+ same: 'const int SCALE = 2;\nint normalizePayload(int value) { return value * SCALE; }\n',
204
+ different: 'const int SCALE = 3;\nint normalizePayload(int value) { return value * SCALE; }\n',
205
+ },
206
+ c: {
207
+ same: 'const int SCALE = 2;\nint normalizePayload(int value) { return value * SCALE; }\n',
208
+ different: 'const int SCALE = 3;\nint normalizePayload(int value) { return value * SCALE; }\n',
209
+ },
210
+ php: {
211
+ same: '<?php const SCALE = 2;\nfunction normalizePayload($value) { return $value * SCALE; }\n',
212
+ different: '<?php const SCALE = 3;\nfunction normalizePayload($value) { return $value * SCALE; }\n',
213
+ },
214
+ kotlin: {
215
+ same: 'const val SCALE = 2\nfun normalizePayload(value: Int): Int = value * SCALE\n',
216
+ different: 'const val SCALE = 3\nfun normalizePayload(value: Int): Int = value * SCALE\n',
217
+ },
218
+ ruby: {
219
+ same: 'SCALE = 2\ndef normalize_payload(value)\n value * SCALE\nend\n',
220
+ different: 'SCALE = 3\ndef normalize_payload(value)\n value * SCALE\nend\n',
221
+ },
222
+ solidity: {
223
+ same: 'uint constant SCALE = 2;\nfunction normalizePayload(uint value) pure returns (uint) { return value * SCALE; }\n',
224
+ different: 'uint constant SCALE = 3;\nfunction normalizePayload(uint value) pure returns (uint) { return value * SCALE; }\n',
225
+ },
226
+ };
227
+ const TYPE_BINDING_REINVENTION = {
228
+ go: {
229
+ same: 'package sample\ntype Payload = int\nfunc NormalizePayload(value Payload) Payload { return value }\n',
230
+ different: 'package sample\ntype Payload = string\nfunc NormalizePayload(value Payload) Payload { return value }\n',
231
+ },
232
+ rust: {
233
+ same: 'type Payload = i32;\npub fn normalize_payload(value: Payload) -> Payload { value }\n',
234
+ different: 'type Payload = i64;\npub fn normalize_payload(value: Payload) -> Payload { value }\n',
235
+ },
236
+ cpp: {
237
+ same: 'using Payload = int;\nPayload normalizePayload(Payload value) { return value; }\n',
238
+ different: 'using Payload = long;\nPayload normalizePayload(Payload value) { return value; }\n',
239
+ },
240
+ c: {
241
+ same: 'typedef int Payload;\nPayload normalizePayload(Payload value) { return value; }\n',
242
+ different: 'typedef long Payload;\nPayload normalizePayload(Payload value) { return value; }\n',
243
+ },
244
+ kotlin: {
245
+ same: 'typealias Payload = Int\nfun normalizePayload(value: Payload): Payload = value\n',
246
+ different: 'typealias Payload = Long\nfun normalizePayload(value: Payload): Payload = value\n',
247
+ },
248
+ };
249
+ const RELATIVE_BINDING_REINVENTION = {
250
+ python: 'from .tools import transform\ndef normalize_payload(value):\n return transform(value)\n',
251
+ rust: 'use super::tools::transform;\npub fn normalize_payload(value: i32) -> i32 { transform(value) }\n',
252
+ cpp: '#include "tools.h"\nint normalizePayload(int value) { return transform(value); }\n',
253
+ c: '#include "tools.h"\nint normalizePayload(int value) { return transform(value); }\n',
254
+ ruby: 'require_relative "tools"\ndef normalize_payload(value)\n transform(value)\nend\n',
255
+ solidity: 'import "./tools.sol";\nfunction normalizePayload(uint value) pure returns (uint) { return transform(value); }\n',
256
+ };
257
+ const GUARD_CONTRACT_MUTATION = {
258
+ python: ['def release(slot):', 'def release(slot: object):'],
259
+ go: ['func Release(slot Slot) bool', 'func Release(slot *Slot) bool'],
260
+ java: ['static boolean release(Slot slot)', 'static boolean release(Object slot)'],
261
+ rust: ['fn sweep(slot: &Slot) -> bool', 'fn sweep(slot: Slot) -> bool'],
262
+ cpp: ['bool release(Slot& slot)', 'bool release(const Slot& slot)'],
263
+ c: ['int release(Slot *slot)', 'int release(const Slot *slot)'],
264
+ 'c#': ['static bool Release(Slot slot)', 'static bool Release(object slot)'],
265
+ php: ['function release($slot)', 'function release(object $slot)'],
266
+ kotlin: ['fun release(slot: Slot): Boolean', 'fun release(slot: Slot?): Boolean'],
267
+ ruby: ['def release(slot)', 'def release(slot, force = false)'],
268
+ solidity: [
269
+ 'function release(Slot slot) public returns (bool)',
270
+ 'function release(Slot slot, bool force) public returns (bool)',
271
+ ],
272
+ };
273
+ const OWNER_GUARDS = {
274
+ python: {
275
+ before: 'class Primary:\n def release(self, slot):\n if slot is None: return False\n return True\n',
276
+ guard: ' if slot is None: return False\n',
277
+ },
278
+ go: {
279
+ before: 'package sample\ntype Primary struct{}\nfunc (Primary) Release(slot *int) bool {\n\tif slot == nil { return false }\n\treturn true\n}\n',
280
+ guard: '\tif slot == nil { return false }\n',
281
+ },
282
+ java: {
283
+ before: 'class Primary { boolean release(Object slot) {\n if (slot == null) return false;\n return true;\n} }\n',
284
+ guard: ' if (slot == null) return false;\n',
285
+ },
286
+ rust: {
287
+ before: 'struct Primary;\nimpl Primary { fn release(&self, slot: Option<u8>) -> bool {\n if slot.is_none() { return false; }\n true\n} }\n',
288
+ guard: ' if slot.is_none() { return false; }\n',
289
+ },
290
+ cpp: {
291
+ before: 'struct Primary { bool release(void* slot) {\n if (slot == nullptr) return false;\n return true;\n} };\n',
292
+ guard: ' if (slot == nullptr) return false;\n',
293
+ },
294
+ 'c#': {
295
+ before: 'class Primary { bool Release(object slot) {\n if (slot == null) return false;\n return true;\n} }\n',
296
+ guard: ' if (slot == null) return false;\n',
297
+ },
298
+ php: {
299
+ before: '<?php class Primary { function release($slot) {\n if ($slot === null) return false;\n return true;\n} }\n',
300
+ guard: ' if ($slot === null) return false;\n',
301
+ },
302
+ kotlin: {
303
+ before: 'class Primary { fun release(slot: Any?): Boolean {\n if (slot == null) return false\n return true\n} }\n',
304
+ guard: ' if (slot == null) return false\n',
305
+ },
306
+ ruby: {
307
+ before: 'class Primary\n def release(slot)\n return false if slot.nil?\n true\n end\nend\n',
308
+ guard: ' return false if slot.nil?\n',
309
+ },
310
+ solidity: {
311
+ before: 'contract Primary { function release(address slot) public returns (bool) {\n if (slot == address(0)) return false;\n return true;\n} }\n',
312
+ guard: ' if (slot == address(0)) return false;\n',
313
+ },
314
+ };
315
+ const GUARD_CASES = {
316
+ python: {
317
+ before: [
318
+ 'def ready(slot): return True',
319
+ 'def close_slot(slot): return True',
320
+ 'def release(slot):',
321
+ ' if not ready(slot):',
322
+ ' return False',
323
+ ' return close_slot(slot)',
324
+ '',
325
+ ].join('\n'),
326
+ guard: ' if not ready(slot):\n return False\n',
327
+ extracted: [
328
+ 'def ready(slot): return True',
329
+ 'def close_slot(slot): return True',
330
+ 'def close_if_ready(slot):',
331
+ ' if not ready(slot):',
332
+ ' return False',
333
+ ' return close_slot(slot)',
334
+ 'def release(slot):',
335
+ ' return close_if_ready(slot)',
336
+ '',
337
+ ].join('\n'),
338
+ },
339
+ go: {
340
+ before: [
341
+ 'package sample',
342
+ 'type Slot struct{}',
343
+ 'func (Slot) Ready() bool { return true }',
344
+ 'func (Slot) Close() bool { return true }',
345
+ 'func Release(slot Slot) bool {',
346
+ '\tif !slot.Ready() { return false }',
347
+ '\treturn slot.Close()',
348
+ '}',
349
+ '',
350
+ ].join('\n'),
351
+ guard: '\tif !slot.Ready() { return false }\n',
352
+ extracted: [
353
+ 'package sample',
354
+ 'type Slot struct{}',
355
+ 'func (Slot) Ready() bool { return true }',
356
+ 'func (Slot) Close() bool { return true }',
357
+ 'func closeIfReady(slot Slot) bool {',
358
+ '\tif !slot.Ready() { return false }',
359
+ '\treturn slot.Close()',
360
+ '}',
361
+ 'func Release(slot Slot) bool { return closeIfReady(slot) }',
362
+ '',
363
+ ].join('\n'),
364
+ },
365
+ java: {
366
+ before: [
367
+ 'class Slot {',
368
+ ' boolean ready() { return true; }',
369
+ ' boolean close() { return true; }',
370
+ '}',
371
+ 'class Runner {',
372
+ ' static boolean release(Slot slot) {',
373
+ ' if (!slot.ready()) { return false; }',
374
+ ' return slot.close();',
375
+ ' }',
376
+ '}',
377
+ '',
378
+ ].join('\n'),
379
+ guard: ' if (!slot.ready()) { return false; }\n',
380
+ extracted: [
381
+ 'class Slot {',
382
+ ' boolean ready() { return true; }',
383
+ ' boolean close() { return true; }',
384
+ '}',
385
+ 'class Runner {',
386
+ ' static boolean closeIfReady(Slot slot) {',
387
+ ' if (!slot.ready()) { return false; }',
388
+ ' return slot.close();',
389
+ ' }',
390
+ ' static boolean release(Slot slot) { return closeIfReady(slot); }',
391
+ '}',
392
+ '',
393
+ ].join('\n'),
394
+ },
395
+ rust: {
396
+ before: [
397
+ 'struct Slot;',
398
+ 'impl Slot {',
399
+ ' fn is_ready(&self) -> bool { true }',
400
+ ' fn close(&self) -> bool { true }',
401
+ '}',
402
+ 'fn sweep(slot: &Slot) -> bool {',
403
+ ' if !slot.is_ready() { return false; }',
404
+ ' slot.close()',
405
+ '}',
406
+ '',
407
+ ].join('\n'),
408
+ guard: ' if !slot.is_ready() { return false; }\n',
409
+ extracted: [
410
+ 'struct Slot;',
411
+ 'impl Slot {',
412
+ ' fn is_ready(&self) -> bool { true }',
413
+ ' fn close(&self) -> bool { true }',
414
+ ' fn close_if_ready(&self) -> bool {',
415
+ ' if !self.is_ready() { return false; }',
416
+ ' self.close()',
417
+ ' }',
418
+ '}',
419
+ 'fn sweep(slot: &Slot) -> bool {',
420
+ ' slot.close_if_ready()',
421
+ '}',
422
+ '',
423
+ ].join('\n'),
424
+ },
425
+ cpp: {
426
+ before: [
427
+ 'struct Slot { bool ready(); bool close(); };',
428
+ 'bool release(Slot& slot) {',
429
+ ' if (!slot.ready()) { return false; }',
430
+ ' return slot.close();',
431
+ '}',
432
+ '',
433
+ ].join('\n'),
434
+ guard: ' if (!slot.ready()) { return false; }\n',
435
+ extracted: [
436
+ 'struct Slot { bool ready(); bool close(); };',
437
+ 'bool closeIfReady(Slot& slot) {',
438
+ ' if (!slot.ready()) { return false; }',
439
+ ' return slot.close();',
440
+ '}',
441
+ 'bool release(Slot& slot) { return closeIfReady(slot); }',
442
+ '',
443
+ ].join('\n'),
444
+ },
445
+ c: {
446
+ before: [
447
+ 'typedef struct Slot Slot;',
448
+ 'int slot_ready(Slot *slot) { return 1; }',
449
+ 'int slot_close(Slot *slot) { return 1; }',
450
+ 'int release(Slot *slot) {',
451
+ ' if (!slot_ready(slot)) { return 0; }',
452
+ ' return slot_close(slot);',
453
+ '}',
454
+ '',
455
+ ].join('\n'),
456
+ guard: ' if (!slot_ready(slot)) { return 0; }\n',
457
+ extracted: [
458
+ 'typedef struct Slot Slot;',
459
+ 'int slot_ready(Slot *slot) { return 1; }',
460
+ 'int slot_close(Slot *slot) { return 1; }',
461
+ 'int close_if_ready(Slot *slot) {',
462
+ ' if (!slot_ready(slot)) { return 0; }',
463
+ ' return slot_close(slot);',
464
+ '}',
465
+ 'int release(Slot *slot) { return close_if_ready(slot); }',
466
+ '',
467
+ ].join('\n'),
468
+ },
469
+ 'c#': {
470
+ before: [
471
+ 'class Slot {',
472
+ ' public bool Ready() { return true; }',
473
+ ' public bool Close() { return true; }',
474
+ '}',
475
+ 'class Runner {',
476
+ ' static bool Release(Slot slot) {',
477
+ ' if (!slot.Ready()) { return false; }',
478
+ ' return slot.Close();',
479
+ ' }',
480
+ '}',
481
+ '',
482
+ ].join('\n'),
483
+ guard: ' if (!slot.Ready()) { return false; }\n',
484
+ extracted: [
485
+ 'class Slot {',
486
+ ' public bool Ready() { return true; }',
487
+ ' public bool Close() { return true; }',
488
+ '}',
489
+ 'class Runner {',
490
+ ' static bool CloseIfReady(Slot slot) {',
491
+ ' if (!slot.Ready()) { return false; }',
492
+ ' return slot.Close();',
493
+ ' }',
494
+ ' static bool Release(Slot slot) { return CloseIfReady(slot); }',
495
+ '}',
496
+ '',
497
+ ].join('\n'),
498
+ },
499
+ php: {
500
+ before: [
501
+ '<?php',
502
+ 'function ready($slot) { return true; }',
503
+ 'function close_slot($slot) { return true; }',
504
+ 'function release($slot) {',
505
+ ' if (!ready($slot)) { return false; }',
506
+ ' return close_slot($slot);',
507
+ '}',
508
+ '',
509
+ ].join('\n'),
510
+ guard: ' if (!ready($slot)) { return false; }\n',
511
+ extracted: [
512
+ '<?php',
513
+ 'function ready($slot) { return true; }',
514
+ 'function close_slot($slot) { return true; }',
515
+ 'function close_if_ready($slot) {',
516
+ ' if (!ready($slot)) { return false; }',
517
+ ' return close_slot($slot);',
518
+ '}',
519
+ 'function release($slot) { return close_if_ready($slot); }',
520
+ '',
521
+ ].join('\n'),
522
+ },
523
+ kotlin: {
524
+ before: [
525
+ 'class Slot {',
526
+ ' fun ready(): Boolean = true',
527
+ ' fun close(): Boolean = true',
528
+ '}',
529
+ 'fun release(slot: Slot): Boolean {',
530
+ ' if (!slot.ready()) return false',
531
+ ' return slot.close()',
532
+ '}',
533
+ '',
534
+ ].join('\n'),
535
+ guard: ' if (!slot.ready()) return false\n',
536
+ extracted: [
537
+ 'class Slot {',
538
+ ' fun ready(): Boolean = true',
539
+ ' fun close(): Boolean = true',
540
+ '}',
541
+ 'fun closeIfReady(slot: Slot): Boolean {',
542
+ ' if (!slot.ready()) return false',
543
+ ' return slot.close()',
544
+ '}',
545
+ 'fun release(slot: Slot): Boolean = closeIfReady(slot)',
546
+ '',
547
+ ].join('\n'),
548
+ },
549
+ ruby: {
550
+ before: [
551
+ 'def ready(slot)',
552
+ ' true',
553
+ 'end',
554
+ 'def close_slot(slot)',
555
+ ' true',
556
+ 'end',
557
+ 'def release(slot)',
558
+ ' if !ready(slot)',
559
+ ' return false',
560
+ ' end',
561
+ ' close_slot(slot)',
562
+ 'end',
563
+ '',
564
+ ].join('\n'),
565
+ guard: ' if !ready(slot)\n return false\n end\n',
566
+ extracted: [
567
+ 'def ready(slot)',
568
+ ' true',
569
+ 'end',
570
+ 'def close_slot(slot)',
571
+ ' true',
572
+ 'end',
573
+ 'def close_if_ready(slot)',
574
+ ' if !ready(slot)',
575
+ ' return false',
576
+ ' end',
577
+ ' close_slot(slot)',
578
+ 'end',
579
+ 'def release(slot)',
580
+ ' close_if_ready(slot)',
581
+ 'end',
582
+ '',
583
+ ].join('\n'),
584
+ },
585
+ solidity: {
586
+ before: [
587
+ 'contract Slot {',
588
+ ' function ready() public pure returns (bool) { return true; }',
589
+ ' function close() public pure returns (bool) { return true; }',
590
+ '}',
591
+ 'contract Runner {',
592
+ ' function release(Slot slot) public returns (bool) {',
593
+ ' if (!slot.ready()) { return false; }',
594
+ ' return slot.close();',
595
+ ' }',
596
+ '}',
597
+ '',
598
+ ].join('\n'),
599
+ guard: ' if (!slot.ready()) { return false; }\n',
600
+ extracted: [
601
+ 'contract Slot {',
602
+ ' function ready() public pure returns (bool) { return true; }',
603
+ ' function close() public pure returns (bool) { return true; }',
604
+ '}',
605
+ 'contract Runner {',
606
+ ' function closeIfReady(Slot slot) internal returns (bool) {',
607
+ ' if (!slot.ready()) { return false; }',
608
+ ' return slot.close();',
609
+ ' }',
610
+ ' function release(Slot slot) public returns (bool) { return closeIfReady(slot); }',
611
+ '}',
612
+ '',
613
+ ].join('\n'),
614
+ },
615
+ };
616
+ const GUARD_ABSORPTION = {
617
+ python: [
618
+ 'def close_slot(slot): return True',
619
+ 'def close_slot(slot):\n if not ready(slot):\n return False\n return True',
620
+ ],
621
+ go: [
622
+ 'func (Slot) Close() bool { return true }',
623
+ 'func (slot Slot) Close() bool { if !slot.Ready() { return false }; return true }',
624
+ ],
625
+ java: [
626
+ ' boolean close() { return true; }',
627
+ ' boolean close() { if (!ready()) { return false; } return true; }',
628
+ ],
629
+ rust: [
630
+ ' fn close(&self) -> bool { true }',
631
+ ' fn close(&self) -> bool { if !self.is_ready() { return false; } true }',
632
+ ],
633
+ cpp: [
634
+ 'struct Slot { bool ready(); bool close(); };',
635
+ 'struct Slot { bool ready(); bool close() { if (!ready()) { return false; } return true; } };',
636
+ ],
637
+ c: [
638
+ 'int slot_close(Slot *slot) { return 1; }',
639
+ 'int slot_close(Slot *slot) { if (!slot_ready(slot)) { return 0; } return 1; }',
640
+ ],
641
+ 'c#': [
642
+ ' public bool Close() { return true; }',
643
+ ' public bool Close() { if (!Ready()) { return false; } return true; }',
644
+ ],
645
+ php: [
646
+ 'function close_slot($slot) { return true; }',
647
+ 'function close_slot($slot) { if (!ready($slot)) { return false; } return true; }',
648
+ ],
649
+ kotlin: [
650
+ ' fun close(): Boolean = true',
651
+ ' fun close(): Boolean { if (!ready()) return false; return true }',
652
+ ],
653
+ ruby: [
654
+ 'def close_slot(slot)\n true\nend',
655
+ 'def close_slot(slot)\n return false if !ready(slot)\n true\nend',
656
+ ],
657
+ solidity: [
658
+ ' function close() public pure returns (bool) { return true; }',
659
+ ' function close() public pure returns (bool) { if (!ready()) { return false; } return true; }',
660
+ ],
661
+ };
62
662
  function allLines(source) {
63
663
  return new Set(Array.from({ length: source.split('\n').length }, (_, index) => index + 1));
64
664
  }
65
- async function foreignReinventionGround(pack, existingSource, addedSource, existingBeforeSource = existingSource, addedBeforeSource) {
665
+ function withFileScope(language, source, scope) {
666
+ if (language === 'go')
667
+ return source.replace(/^package\s+\w+/m, 'package ' + scope);
668
+ if (language === 'java')
669
+ return 'package ' + scope + ';\n' + source;
670
+ if (language === 'kotlin')
671
+ return 'package ' + scope + '\n' + source;
672
+ return source;
673
+ }
674
+ async function foreignReinventionGround(pack, existingSource, addedSource, existingBeforeSource = existingSource, addedBeforeSource, paths) {
66
675
  const extension = pack.extensions[0];
67
- const existingPath = 'z-existing' + extension;
68
- const addedPath = 'a-new' + extension;
676
+ const existingPath = paths?.existing ?? 'z-existing' + extension;
677
+ const addedPath = paths?.added ?? 'a-new' + extension;
69
678
  const existingTree = await parse(pack, existingSource);
70
679
  const addedTree = await parse(pack, addedSource);
71
680
  const beforeTree = existingBeforeSource === null ? undefined : await parse(pack, existingBeforeSource);
@@ -97,6 +706,77 @@ async function foreignReinventionGround(pack, existingSource, addedSource, exist
97
706
  foreign,
98
707
  };
99
708
  }
709
+ async function foreignReinventionPair(pack, source, variant) {
710
+ return {
711
+ control: await foreignReinventionGround(pack, source, source),
712
+ variant: await foreignReinventionGround(pack, source, variant),
713
+ };
714
+ }
715
+ async function foreignGuardGround(pack, before, after, other) {
716
+ const path = 'src/release' + pack.extensions[0];
717
+ const tree = await parse(pack, after);
718
+ const beforeTree = await parse(pack, before);
719
+ if (!tree || !beforeTree)
720
+ throw new Error('guard fixture did not parse');
721
+ const changed = { path, added: allLines(after), before };
722
+ const changes = [changed];
723
+ const foreign = [{ path, pack, tree, beforeTree, changed }];
724
+ if (other) {
725
+ const otherTree = await parse(pack, other.after);
726
+ const otherBeforeTree = await parse(pack, other.before);
727
+ if (!otherTree || !otherBeforeTree)
728
+ throw new Error('secondary guard fixture did not parse');
729
+ const otherChange = {
730
+ path: other.path,
731
+ added: allLines(other.after),
732
+ before: other.before,
733
+ };
734
+ changes.push(otherChange);
735
+ foreign.push({
736
+ path: other.path,
737
+ pack,
738
+ tree: otherTree,
739
+ beforeTree: otherBeforeTree,
740
+ changed: otherChange,
741
+ });
742
+ }
743
+ return {
744
+ root: '/virtual/repo',
745
+ sourceFiles: [],
746
+ configFiles: [],
747
+ beforeProject: new Project({ useInMemoryFileSystem: true }),
748
+ changed: changes,
749
+ files: [],
750
+ symbolIndex: new Map(),
751
+ deps: new Set(),
752
+ depsFor: () => new Set(),
753
+ typed: false,
754
+ internalPrefixes: [],
755
+ foreign,
756
+ };
757
+ }
758
+ async function rustTraitMethodGround(pack) {
759
+ const existing = [
760
+ 'trait Source { fn access_mode(&self) -> bool; }',
761
+ 'struct Primary;',
762
+ 'impl Source for Primary {',
763
+ ' fn access_mode(&self) -> bool { true }',
764
+ '}',
765
+ '',
766
+ ].join('\n');
767
+ const added = [
768
+ '#[cfg(test)]',
769
+ 'mod support {',
770
+ ' use super::Source;',
771
+ ' struct Recording;',
772
+ ' impl Source for Recording {',
773
+ ' fn access_mode(&self) -> bool { true }',
774
+ ' }',
775
+ '}',
776
+ '',
777
+ ].join('\n');
778
+ return foreignReinventionGround(pack, existing, added);
779
+ }
100
780
  async function one(name) {
101
781
  const pack = PACKS.find((p) => p.name === name);
102
782
  if (!pack) {
@@ -123,9 +803,15 @@ async function one(name) {
123
803
  assert.ok(tree, 'failed to parse — grammar missing or ABI mismatch');
124
804
  });
125
805
  check('declares the node names the generic checks need', () => {
126
- for (const key of ['identifier', 'comment', 'ifStatement', 'bail', 'declaration', 'block']) {
806
+ for (const key of [
807
+ 'identifier', 'comment', 'ifStatement', 'bail', 'declaration',
808
+ 'callable', 'callableBody', 'reusableDeclaration', 'block',
809
+ ]) {
127
810
  assert.ok(pack.nodes[key].length > 0, 'no ' + key);
128
811
  }
812
+ if (pack.nodes.callableOwner.length > 0) {
813
+ assert.ok(pack.nodes.callableOwnerBody.length > 0, 'callable owners have no body vocabulary');
814
+ }
129
815
  });
130
816
  const hits = tree ? pack.swallowedError?.(tree.rootNode) ?? [] : [];
131
817
  if (name === 'c') {
@@ -170,6 +856,370 @@ async function one(name) {
170
856
  check('reinvented ignores an implementation already present in the changed file', () => {
171
857
  assert.equal(foreignReinvented.run(targetPreexistingGround).length, 0);
172
858
  });
859
+ const bindingContext = BINDING_CONTEXT_REINVENTION[name];
860
+ const bindingContextGrounds = await foreignReinventionPair(pack, bindingContext.same, bindingContext.different);
861
+ check('reinvented preserves an exact binding context control', () => {
862
+ assert.ok(foreignReinvented.run(bindingContextGrounds.control).length >= 1);
863
+ });
864
+ check('reinvented keeps different import and directive bindings separate', () => {
865
+ assert.equal(foreignReinvented.run(bindingContextGrounds.variant).length, 0);
866
+ });
867
+ const moduleBinding = MODULE_BINDING_REINVENTION[name];
868
+ if (moduleBinding) {
869
+ const sameModuleBindingGround = await foreignReinventionGround(pack, moduleBinding.same, moduleBinding.same);
870
+ const differentModuleBindingGround = await foreignReinventionGround(pack, moduleBinding.same, moduleBinding.different);
871
+ check('reinvented preserves an exact referenced module binding control', () => {
872
+ assert.ok(foreignReinvented.run(sameModuleBindingGround).length >= 1);
873
+ });
874
+ check('reinvented keeps different referenced module bindings separate', () => {
875
+ assert.equal(foreignReinvented.run(differentModuleBindingGround).length, 0);
876
+ });
877
+ }
878
+ const typeBinding = TYPE_BINDING_REINVENTION[name];
879
+ if (typeBinding) {
880
+ const sameTypeBindingGround = await foreignReinventionGround(pack, typeBinding.same, typeBinding.same);
881
+ const differentTypeBindingGround = await foreignReinventionGround(pack, typeBinding.same, typeBinding.different);
882
+ check('reinvented preserves an exact referenced type binding control', () => {
883
+ assert.ok(foreignReinvented.run(sameTypeBindingGround).length >= 1);
884
+ });
885
+ check('reinvented keeps different referenced type bindings separate', () => {
886
+ assert.equal(foreignReinvented.run(differentTypeBindingGround).length, 0);
887
+ });
888
+ }
889
+ const relativeBinding = RELATIVE_BINDING_REINVENTION[name];
890
+ if (relativeBinding) {
891
+ const extension = pack.extensions[0];
892
+ const sameDirectoryGround = await foreignReinventionGround(pack, relativeBinding, relativeBinding, relativeBinding, undefined, { existing: 'alpha/existing' + extension, added: 'alpha/added' + extension });
893
+ const differentDirectoryGround = await foreignReinventionGround(pack, relativeBinding, relativeBinding, relativeBinding, undefined, { existing: 'alpha/existing' + extension, added: 'beta/added' + extension });
894
+ check('reinvented preserves a same-directory relative binding control', () => {
895
+ assert.ok(foreignReinvented.run(sameDirectoryGround).length >= 1);
896
+ });
897
+ check('reinvented qualifies relative bindings by source directory', () => {
898
+ assert.equal(foreignReinvented.run(differentDirectoryGround).length, 0);
899
+ });
900
+ }
901
+ if (name === 'go' || name === 'java' || name === 'kotlin') {
902
+ const scopedGround = async (existingScope, addedScope) => foreignReinventionGround(pack, withFileScope(name, existing, existingScope), withFileScope(name, existing, addedScope));
903
+ const sameScopeGround = await scopedGround('alpha', 'alpha');
904
+ const otherScopeGround = await scopedGround('alpha', 'beta');
905
+ check('reinvented detects an exact declaration in the same language package', () => {
906
+ assert.ok(foreignReinvented.run(sameScopeGround).length >= 1);
907
+ });
908
+ check('reinvented keeps language packages separate', () => {
909
+ assert.equal(foreignReinvented.run(otherScopeGround).length, 0);
910
+ });
911
+ }
912
+ if (name === 'go') {
913
+ const scoped = withFileScope(name, existing, 'sample');
914
+ const directoryGround = await foreignReinventionGround(pack, scoped, scoped, scoped, undefined, { existing: 'first/existing.go', added: 'second/added.go' });
915
+ check('reinvented keeps distinct Go import paths separate', () => {
916
+ assert.equal(foreignReinvented.run(directoryGround).length, 0);
917
+ });
918
+ }
919
+ const filePrivate = FILE_PRIVATE_REINVENTION[name];
920
+ if (filePrivate) {
921
+ const privateGround = await foreignReinventionGround(pack, filePrivate, filePrivate);
922
+ check('reinvented ignores declarations unavailable across files', () => {
923
+ assert.equal(foreignReinvented.run(privateGround).length, 0);
924
+ });
925
+ }
926
+ if (name === 'cpp') {
927
+ const anonymous = 'namespace { bool normalizePayload() { return true; } }\n';
928
+ const anonymousGround = await foreignReinventionGround(pack, anonymous, anonymous);
929
+ check('reinvented ignores declarations in anonymous namespaces', () => {
930
+ assert.equal(foreignReinvented.run(anonymousGround).length, 0);
931
+ });
932
+ }
933
+ const nested = NESTED_REINVENTION[name];
934
+ if (nested) {
935
+ const nestedGround = await foreignReinventionGround(pack, nested[0], nested[1]);
936
+ check('reinvented ignores matching methods owned by separate types', () => {
937
+ assert.equal(foreignReinvented.run(nestedGround).length, 0);
938
+ });
939
+ }
940
+ const wrapped = WRAPPED_REINVENTION[name];
941
+ if (wrapped) {
942
+ const wrappedGround = await foreignReinventionGround(pack, wrapped.same, wrapped.same);
943
+ check('reinvented sees reusable declarations through module-level wrappers', () => {
944
+ assert.ok(foreignReinvented.run(wrappedGround).length >= 1);
945
+ });
946
+ if (wrapped.differentScope) {
947
+ const otherScopeGround = await foreignReinventionGround(pack, wrapped.same, wrapped.differentScope);
948
+ check('reinvented keeps language namespaces separate', () => {
949
+ assert.equal(foreignReinvented.run(otherScopeGround).length, 0);
950
+ });
951
+ }
952
+ if (wrapped.differentWrapper) {
953
+ const otherWrapperGround = await foreignReinventionGround(pack, wrapped.same, wrapped.differentWrapper);
954
+ check('reinvented keeps decorator and template semantics in the fingerprint', () => {
955
+ assert.equal(foreignReinvented.run(otherWrapperGround).length, 0);
956
+ });
957
+ }
958
+ }
959
+ if (name === 'cpp') {
960
+ const first = [
961
+ 'namespace A { const int SCALE = 2; int normalizePayload(int value) { return value * SCALE; } }',
962
+ 'namespace B { const int SCALE = 3; }',
963
+ '',
964
+ ].join('\n');
965
+ const shadowed = [
966
+ 'namespace B { const int SCALE = 2; }',
967
+ 'namespace A { const int SCALE = 3; int normalizePayload(int value) { return value * SCALE; } }',
968
+ '',
969
+ ].join('\n');
970
+ const shadowedGround = await foreignReinventionGround(pack, first, shadowed);
971
+ check('reinvented resolves C++ module bindings in the declaration namespace', () => {
972
+ assert.equal(foreignReinvented.run(shadowedGround).length, 0);
973
+ });
974
+ }
975
+ if (name === 'php') {
976
+ const first = [
977
+ '<?php namespace A { const SCALE = 2; function normalizePayload($value) { return $value * SCALE; } }',
978
+ 'namespace B { const SCALE = 3; }',
979
+ '',
980
+ ].join('\n');
981
+ const shadowed = [
982
+ '<?php namespace B { const SCALE = 2; }',
983
+ 'namespace A { const SCALE = 3; function normalizePayload($value) { return $value * SCALE; } }',
984
+ '',
985
+ ].join('\n');
986
+ const shadowedGround = await foreignReinventionGround(pack, first, shadowed);
987
+ check('reinvented resolves PHP module bindings in the declaration namespace', () => {
988
+ assert.equal(foreignReinvented.run(shadowedGround).length, 0);
989
+ });
990
+ }
991
+ if (name === 'go') {
992
+ const linux = '//go:build linux\npackage sample\nfunc NormalizePayload() bool { return true }\n';
993
+ const windows = linux.replace('linux', 'windows');
994
+ const buildConstraintGrounds = await foreignReinventionPair(pack, linux, windows);
995
+ check('reinvented preserves an exact Go build constraint control', () => {
996
+ assert.ok(foreignReinvented.run(buildConstraintGrounds.control).length >= 1);
997
+ });
998
+ check('reinvented keeps different Go build constraints separate', () => {
999
+ assert.equal(foreignReinvented.run(buildConstraintGrounds.variant).length, 0);
1000
+ });
1001
+ }
1002
+ if (name === 'c#') {
1003
+ const fileLocal = 'file class NormalizePayload { bool Run() { return true; } }\n';
1004
+ const fileLocalGround = await foreignReinventionGround(pack, fileLocal, fileLocal);
1005
+ check('reinvented ignores C# file-local declarations', () => {
1006
+ assert.equal(foreignReinvented.run(fileLocalGround).length, 0);
1007
+ });
1008
+ }
1009
+ if (name === 'rust') {
1010
+ const unix = '#[cfg(unix)]\npub fn normalize_payload() -> bool { true }\n';
1011
+ const windows = unix.replace('unix', 'windows');
1012
+ const cfgGrounds = await foreignReinventionPair(pack, unix, windows);
1013
+ check('reinvented preserves an exact Rust cfg control', () => {
1014
+ assert.ok(foreignReinvented.run(cfgGrounds.control).length >= 1);
1015
+ });
1016
+ check('reinvented keeps different Rust cfg declarations separate', () => {
1017
+ assert.equal(foreignReinvented.run(cfgGrounds.variant).length, 0);
1018
+ });
1019
+ const selfVisible = 'pub(self) fn normalize_payload() -> bool { true }\n';
1020
+ const selfVisibleGround = await foreignReinventionGround(pack, selfVisible, selfVisible);
1021
+ check('reinvented ignores Rust visibility that needs a module graph', () => {
1022
+ assert.equal(foreignReinvented.run(selfVisibleGround).length, 0);
1023
+ });
1024
+ const traitMethodGround = await rustTraitMethodGround(pack);
1025
+ check('reinvented ignores a trait method repeated by a test double', () => {
1026
+ assert.equal(foreignReinvented.run(traitMethodGround).length, 0);
1027
+ });
1028
+ const continuation = 'fn release(slot: &Slot, force: bool) -> bool {\n slot.close()\n}\n';
1029
+ const conditionalReturn = [
1030
+ 'fn release(slot: &Slot, force: bool) -> bool {',
1031
+ ' if !slot.is_ready() {',
1032
+ ' if force { return false; }',
1033
+ ' slot.record_miss();',
1034
+ ' }',
1035
+ ' slot.close()',
1036
+ '}',
1037
+ '',
1038
+ ].join('\n');
1039
+ const withAlternative = [
1040
+ 'fn release(slot: &Slot, force: bool) -> bool {',
1041
+ ' if !slot.is_ready() { return false; } else { slot.record_ready(); }',
1042
+ ' slot.close()',
1043
+ '}',
1044
+ '',
1045
+ ].join('\n');
1046
+ const conditionalGround = await foreignGuardGround(pack, conditionalReturn, continuation);
1047
+ const alternativeGround = await foreignGuardGround(pack, withAlternative, continuation);
1048
+ check('dropped-guard ignores a branch that only conditionally returns', () => {
1049
+ assert.equal(foreignDroppedGuard.run(conditionalGround).length, 0);
1050
+ });
1051
+ check('dropped-guard ignores a removed conditional with an alternative', () => {
1052
+ assert.equal(foreignDroppedGuard.run(alternativeGround).length, 0);
1053
+ });
1054
+ }
1055
+ }
1056
+ const guardCase = GUARD_CASES[name];
1057
+ if (guardCase) {
1058
+ assert.ok(guardCase.before.includes(guardCase.guard), 'guard fixture cannot remove its guard');
1059
+ const deletionGround = await foreignGuardGround(pack, guardCase.before, guardCase.before.replace(guardCase.guard, ''));
1060
+ const refactorGround = await foreignGuardGround(pack, guardCase.before, guardCase.extracted);
1061
+ check('dropped-guard detects a guard-only deletion', () => {
1062
+ assert.ok(foreignDroppedGuard.run(deletionGround).length >= 1);
1063
+ });
1064
+ check('dropped-guard ignores a guarded operation extracted behind a helper or method', () => {
1065
+ assert.equal(foreignDroppedGuard.run(refactorGround).length, 0);
1066
+ });
1067
+ const absorption = GUARD_ABSORPTION[name];
1068
+ const absorbed = guardCase.before
1069
+ .replace(absorption[0], absorption[1])
1070
+ .replace(guardCase.guard, '');
1071
+ assert.notEqual(absorbed, guardCase.before, 'guard-absorption fixture did not mutate');
1072
+ const absorptionGround = await foreignGuardGround(pack, guardCase.before, absorbed);
1073
+ check('dropped-guard ignores a guard absorbed by an already-called helper or method', () => {
1074
+ assert.equal(foreignDroppedGuard.run(absorptionGround).length, 0);
1075
+ });
1076
+ const contractMutation = GUARD_CONTRACT_MUTATION[name];
1077
+ assert.ok(contractMutation, 'no callable-contract mutation fixture written');
1078
+ const withoutGuard = guardCase.before.replace(guardCase.guard, '');
1079
+ const changedContract = withoutGuard.replace(contractMutation[0], contractMutation[1]);
1080
+ assert.notEqual(changedContract, withoutGuard, 'callable-contract fixture did not mutate');
1081
+ const contractGround = await foreignGuardGround(pack, guardCase.before, changedContract);
1082
+ check('dropped-guard abstains when the callable contract changes', () => {
1083
+ assert.equal(foreignDroppedGuard.run(contractGround).length, 0);
1084
+ });
1085
+ if (name === 'go' || name === 'java' || name === 'kotlin') {
1086
+ const beforeScoped = withFileScope(name, guardCase.before, 'alpha');
1087
+ const afterScoped = withFileScope(name, withoutGuard, 'beta');
1088
+ const packageGround = await foreignGuardGround(pack, beforeScoped, afterScoped);
1089
+ check('dropped-guard abstains when the language package changes', () => {
1090
+ assert.equal(foreignDroppedGuard.run(packageGround).length, 0);
1091
+ });
1092
+ }
1093
+ if (name === 'go') {
1094
+ const tagChangeGround = await foreignGuardGround(pack, guardCase.before, withoutGuard, {
1095
+ path: 'src/platform.go',
1096
+ before: '//go:build linux\npackage sample\n',
1097
+ after: '//go:build windows\npackage sample\n',
1098
+ });
1099
+ check('dropped-guard treats a Go build-tag edit as another executable change', () => {
1100
+ assert.equal(foreignDroppedGuard.run(tagChangeGround).length, 0);
1101
+ });
1102
+ }
1103
+ const ownerGuard = OWNER_GUARDS[name];
1104
+ if (ownerGuard) {
1105
+ assert.ok(ownerGuard.before.includes(ownerGuard.guard), 'owner guard fixture cannot remove its guard');
1106
+ const sameOwner = ownerGuard.before.replace(ownerGuard.guard, '');
1107
+ const movedOwner = sameOwner.replaceAll('Primary', 'Recording');
1108
+ const sameOwnerGround = await foreignGuardGround(pack, ownerGuard.before, sameOwner);
1109
+ const movedOwnerGround = await foreignGuardGround(pack, ownerGuard.before, movedOwner);
1110
+ check('dropped-guard detects a guard-only deletion inside an owner', () => {
1111
+ assert.ok(foreignDroppedGuard.run(sameOwnerGround).length >= 1);
1112
+ });
1113
+ check('dropped-guard abstains when the callable moves to another owner', () => {
1114
+ assert.equal(foreignDroppedGuard.run(movedOwnerGround).length, 0);
1115
+ });
1116
+ }
1117
+ if (name === 'java') {
1118
+ const nestedBefore = [
1119
+ 'class Runner {',
1120
+ ' static boolean release(Slot slot, boolean enabled) {',
1121
+ ' if (enabled) {',
1122
+ ' if (slot == null) return false;',
1123
+ ' return slot.close();',
1124
+ ' }',
1125
+ ' return true;',
1126
+ ' }',
1127
+ '}',
1128
+ '',
1129
+ ].join('\n');
1130
+ const nestedAfter = nestedBefore.replace(' if (slot == null) return false;\n', '');
1131
+ const strengthenedAfter = nestedAfter.replace('if (enabled)', 'if (enabled && slot != null)');
1132
+ const siblingBefore = nestedBefore.replace(' if (enabled) {', ' observe(slot);\n if (enabled) {');
1133
+ const siblingAfter = nestedAfter.replace(' if (enabled) {', ' ensureSlot(slot);\n if (enabled) {');
1134
+ const nestedGround = await foreignGuardGround(pack, nestedBefore, nestedAfter);
1135
+ const strengthenedGround = await foreignGuardGround(pack, nestedBefore, strengthenedAfter);
1136
+ const siblingGround = await foreignGuardGround(pack, siblingBefore, siblingAfter);
1137
+ check('dropped-guard detects a guard-only deletion under stable control flow', () => {
1138
+ assert.ok(foreignDroppedGuard.run(nestedGround).length >= 1);
1139
+ });
1140
+ check('dropped-guard abstains when an ancestor condition replaces the guard', () => {
1141
+ assert.equal(foreignDroppedGuard.run(strengthenedGround).length, 0);
1142
+ });
1143
+ check('dropped-guard abstains when a sibling statement changes too', () => {
1144
+ assert.equal(foreignDroppedGuard.run(siblingGround).length, 0);
1145
+ });
1146
+ const importBefore = [
1147
+ 'import alpha.Slot;',
1148
+ 'class Runner {',
1149
+ ' static boolean release(Slot slot) {',
1150
+ ' if (slot == null) return false;',
1151
+ ' return slot.close();',
1152
+ ' }',
1153
+ '}',
1154
+ '',
1155
+ ].join('\n');
1156
+ const importAfter = importBefore
1157
+ .replace('import alpha.Slot;', 'import beta.Slot;')
1158
+ .replace(' if (slot == null) return false;\n', '');
1159
+ const importGround = await foreignGuardGround(pack, importBefore, importAfter);
1160
+ check('dropped-guard abstains when an import binding changes', () => {
1161
+ assert.equal(foreignDroppedGuard.run(importGround).length, 0);
1162
+ });
1163
+ const repeatedBefore = [
1164
+ 'class Runner {',
1165
+ ' static boolean release(Slot slot, boolean enabled) {',
1166
+ ' if (slot == null) return false;',
1167
+ ' if (enabled) {',
1168
+ ' if (slot /* repeated */ == null) return false;',
1169
+ ' return slot.close();',
1170
+ ' }',
1171
+ ' return true;',
1172
+ ' }',
1173
+ '}',
1174
+ '',
1175
+ ].join('\n');
1176
+ const repeatedAfter = repeatedBefore.replace(' if (slot /* repeated */ == null) return false;\n', '');
1177
+ const repeatedGround = await foreignGuardGround(pack, repeatedBefore, repeatedAfter);
1178
+ check('dropped-guard matches remaining guards without comments or layout', () => {
1179
+ assert.equal(foreignDroppedGuard.run(repeatedGround).length, 0);
1180
+ });
1181
+ }
1182
+ if (name === 'c#') {
1183
+ const fileScopedBefore = [
1184
+ 'namespace Sample;',
1185
+ 'class First {}',
1186
+ 'class Second {',
1187
+ ' bool Release(object slot) {',
1188
+ ' if (slot == null) return false;',
1189
+ ' return true;',
1190
+ ' }',
1191
+ '}',
1192
+ '',
1193
+ ].join('\n');
1194
+ const fileScopedAfter = fileScopedBefore.replace(' if (slot == null) return false;\n', '');
1195
+ const fileScopedGround = await foreignGuardGround(pack, fileScopedBefore, fileScopedAfter);
1196
+ check('dropped-guard reaches later types in a file-scoped namespace', () => {
1197
+ assert.ok(foreignDroppedGuard.run(fileScopedGround).length >= 1);
1198
+ });
1199
+ }
1200
+ if (name === 'ruby') {
1201
+ const modifierBefore = [
1202
+ 'def ready(slot)',
1203
+ ' true',
1204
+ 'end',
1205
+ 'def release(slot)',
1206
+ ' return false if !ready(slot)',
1207
+ ' true',
1208
+ 'end',
1209
+ '',
1210
+ ].join('\n');
1211
+ const modifierAfter = modifierBefore.replace(' return false if !ready(slot)\n', '');
1212
+ const modifierGround = await foreignGuardGround(pack, modifierBefore, modifierAfter);
1213
+ check('dropped-guard detects a postfix early-return guard', () => {
1214
+ assert.ok(foreignDroppedGuard.run(modifierGround).length >= 1);
1215
+ });
1216
+ const unlessBefore = modifierBefore.replace('return false if !ready(slot)', 'return false unless ready(slot)');
1217
+ const unlessAfter = unlessBefore.replace(' return false unless ready(slot)\n', '');
1218
+ const unlessGround = await foreignGuardGround(pack, unlessBefore, unlessAfter);
1219
+ check('dropped-guard detects a postfix unless guard', () => {
1220
+ assert.ok(foreignDroppedGuard.run(unlessGround).length >= 1);
1221
+ });
1222
+ }
173
1223
  }
174
1224
  return failed;
175
1225
  }