@nudojs/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/chunk-N7E2A7IK.js +7216 -0
  2. package/dist/exec.d.ts +2 -0
  3. package/dist/exec.js +174 -0
  4. package/dist/index-DgyudAkw.d.ts +911 -0
  5. package/dist/index.d.ts +64 -710
  6. package/dist/index.js +3269 -8426
  7. package/package.json +18 -5
  8. package/CHANGELOG.md +0 -34
  9. package/src/__tests__/environment.test.ts +0 -48
  10. package/src/__tests__/fn-sig-impl.test.ts +0 -57
  11. package/src/__tests__/ops-refined.test.ts +0 -196
  12. package/src/__tests__/ops.test.ts +0 -84
  13. package/src/__tests__/type-value-phase2.test.ts +0 -117
  14. package/src/__tests__/type-value-phase3.test.ts +0 -94
  15. package/src/__tests__/type-value-refined.test.ts +0 -210
  16. package/src/__tests__/type-value.test.ts +0 -319
  17. package/src/algebra/__tests__/abs-native.test.ts +0 -176
  18. package/src/algebra/__tests__/arithmetic.test.ts +0 -170
  19. package/src/algebra/__tests__/ast-eval.test.ts +0 -154
  20. package/src/algebra/__tests__/bridge.test.ts +0 -160
  21. package/src/algebra/__tests__/call-budget.test.ts +0 -117
  22. package/src/algebra/__tests__/check-case-consistency.test.ts +0 -69
  23. package/src/algebra/__tests__/check-gold.test.ts +0 -290
  24. package/src/algebra/__tests__/check-json.test.ts +0 -57
  25. package/src/algebra/__tests__/check-mini-repo.test.ts +0 -49
  26. package/src/algebra/__tests__/check-real-commander.test.ts +0 -43
  27. package/src/algebra/__tests__/check-real-packages.test.ts +0 -165
  28. package/src/algebra/__tests__/check-recall-gold.test.ts +0 -1024
  29. package/src/algebra/__tests__/check-require-dep-memo.test.ts +0 -45
  30. package/src/algebra/__tests__/check-return-contract.test.ts +0 -90
  31. package/src/algebra/__tests__/check-shape-gold.test.ts +0 -303
  32. package/src/algebra/__tests__/check-source-memo.test.ts +0 -204
  33. package/src/algebra/__tests__/check.test.ts +0 -69
  34. package/src/algebra/__tests__/denote.test.ts +0 -77
  35. package/src/algebra/__tests__/diagnostics.test.ts +0 -64
  36. package/src/algebra/__tests__/differential.test.ts +0 -124
  37. package/src/algebra/__tests__/div-mod.test.ts +0 -38
  38. package/src/algebra/__tests__/example0.test.ts +0 -103
  39. package/src/algebra/__tests__/exec-analyze-throws.test.ts +0 -60
  40. package/src/algebra/__tests__/exec-async.test.ts +0 -46
  41. package/src/algebra/__tests__/exec-bpath.test.ts +0 -193
  42. package/src/algebra/__tests__/exec-class.test.ts +0 -56
  43. package/src/algebra/__tests__/exec-e2e.test.ts +0 -97
  44. package/src/algebra/__tests__/exec-generator.test.ts +0 -53
  45. package/src/algebra/__tests__/exec-inherit-default.test.ts +0 -111
  46. package/src/algebra/__tests__/exec-optional-nested.test.ts +0 -87
  47. package/src/algebra/__tests__/exec-rest.test.ts +0 -55
  48. package/src/algebra/__tests__/exec-spread-forof.test.ts +0 -85
  49. package/src/algebra/__tests__/exec-static-computed.test.ts +0 -61
  50. package/src/algebra/__tests__/exec-switch.test.ts +0 -74
  51. package/src/algebra/__tests__/exec-trycatch.test.ts +0 -71
  52. package/src/algebra/__tests__/fn-fp-memo.test.ts +0 -166
  53. package/src/algebra/__tests__/fn-fp-multidecl.test.ts +0 -49
  54. package/src/algebra/__tests__/format.test.ts +0 -34
  55. package/src/algebra/__tests__/generalize-memo.test.ts +0 -285
  56. package/src/algebra/__tests__/generalize.test.ts +0 -113
  57. package/src/algebra/__tests__/hash-source.test.ts +0 -19
  58. package/src/algebra/__tests__/hof.test.ts +0 -80
  59. package/src/algebra/__tests__/inlay.test.ts +0 -30
  60. package/src/algebra/__tests__/language.test.ts +0 -77
  61. package/src/algebra/__tests__/leq.test.ts +0 -109
  62. package/src/algebra/__tests__/load-deps-fp-memo.test.ts +0 -132
  63. package/src/algebra/__tests__/methods.test.ts +0 -37
  64. package/src/algebra/__tests__/node-types.test.ts +0 -26
  65. package/src/algebra/__tests__/nudo-constraints.ts +0 -46
  66. package/src/algebra/__tests__/objects.test.ts +0 -117
  67. package/src/algebra/__tests__/parse-source-cache.test.ts +0 -42
  68. package/src/algebra/__tests__/refine-import.test.ts +0 -109
  69. package/src/algebra/__tests__/stable-source-key-block.test.ts +0 -23
  70. package/src/algebra/__tests__/stable-source-key.test.ts +0 -31
  71. package/src/algebra/__tests__/surface.test.ts +0 -41
  72. package/src/algebra/__tests__/this-inherit.test.ts +0 -130
  73. package/src/algebra/abs-fn.ts +0 -49
  74. package/src/algebra/abs-modules.ts +0 -139
  75. package/src/algebra/abs.ts +0 -206
  76. package/src/algebra/arithmetic.ts +0 -691
  77. package/src/algebra/ast-eval.ts +0 -1763
  78. package/src/algebra/bridge.ts +0 -350
  79. package/src/algebra/builtins.ts +0 -373
  80. package/src/algebra/check-report.ts +0 -150
  81. package/src/algebra/check.ts +0 -1867
  82. package/src/algebra/constraint.ts +0 -304
  83. package/src/algebra/denote.ts +0 -166
  84. package/src/algebra/diagnostics.ts +0 -210
  85. package/src/algebra/exec/call.ts +0 -29
  86. package/src/algebra/exec/calls.ts +0 -96
  87. package/src/algebra/exec/class-registry.ts +0 -29
  88. package/src/algebra/exec/class.ts +0 -329
  89. package/src/algebra/exec/index.ts +0 -6
  90. package/src/algebra/exec/member-diag.ts +0 -141
  91. package/src/algebra/exec/run.ts +0 -341
  92. package/src/algebra/exec/runtime.ts +0 -583
  93. package/src/algebra/exec/transpile.ts +0 -993
  94. package/src/algebra/fn-fp.ts +0 -294
  95. package/src/algebra/format.ts +0 -91
  96. package/src/algebra/generalize.ts +0 -776
  97. package/src/algebra/hash-source.ts +0 -27
  98. package/src/algebra/index.ts +0 -34
  99. package/src/algebra/inlay.ts +0 -198
  100. package/src/algebra/language.ts +0 -289
  101. package/src/algebra/leak.ts +0 -68
  102. package/src/algebra/leq.ts +0 -301
  103. package/src/algebra/load-deps-fp.ts +0 -97
  104. package/src/algebra/methods.ts +0 -128
  105. package/src/algebra/objects.ts +0 -251
  106. package/src/algebra/parse-source.ts +0 -64
  107. package/src/algebra/phi.ts +0 -42
  108. package/src/algebra/pred.ts +0 -395
  109. package/src/algebra/refine.ts +0 -227
  110. package/src/algebra/stable-source-key.ts +0 -81
  111. package/src/algebra/surface.ts +0 -206
  112. package/src/algebra/template.ts +0 -143
  113. package/src/algebra/term.ts +0 -119
  114. package/src/environment.ts +0 -74
  115. package/src/index.ts +0 -73
  116. package/src/mock-helpers.ts +0 -166
  117. package/src/ops.ts +0 -191
  118. package/src/refinements/range.ts +0 -86
  119. package/src/refinements/template-predicates.ts +0 -11
  120. package/src/refinements/template.ts +0 -176
  121. package/src/strip-types.ts +0 -236
  122. package/src/type-value.ts +0 -513
  123. package/tsconfig.json +0 -8
@@ -1,1024 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { checkSource, pTrue, type CheckReport } from "../index.ts";
3
- import { withStdImport, stdOpts, STD_NUDO_SRC } from "./nudo-constraints.ts";
4
-
5
- /**
6
- * 金标 recall:真实 JS 库常见模式的人工标注。
7
- *
8
- * 标注约定(人工):
9
- * - violation:调用点必须报 nudo:constraint-violated
10
- * - ok:不得报 error(真阴性,防误报)
11
- *
12
- * 指标:
13
- * - recall = TP / (TP + FN) 必须 = 1(漏报 = 门禁失效)
14
- * - precision = TP / (TP + FP) 必须 = 1(误报 = 门禁噪音)
15
- */
16
-
17
- type Expect = "ok" | "violation";
18
-
19
- type Gold = {
20
- id: string;
21
- /** 模式来源(真实库/惯用法) */
22
- origin: string;
23
- source: string;
24
- expect: Expect;
25
- note?: string;
26
- };
27
-
28
- /** 真实库惯用法金标(人工标注) */
29
- const GOLD: Gold[] = [
30
- // --- 延时 / 间隔(ms / setTimeout 风格) ---
31
- {
32
- id: "delay-positive",
33
- origin: "ms / setTimeout delay",
34
- source: `
35
- /**
36
- * @nudo:refine ms positive
37
- */
38
- function setDelay(ms) {
39
- if (ms > 0) return ms;
40
- return 0;
41
- }
42
- setDelay(100);
43
- `,
44
- expect: "ok",
45
- note: "合法正延时",
46
- },
47
- {
48
- id: "delay-zero-violates",
49
- origin: "ms / setTimeout delay",
50
- source: `
51
- /**
52
- * @nudo:refine ms positive
53
- */
54
- function setDelay(ms) {
55
- if (ms > 0) return ms;
56
- return 0;
57
- }
58
- setDelay(0);
59
- `,
60
- expect: "violation",
61
- note: "0 不满足 >0",
62
- },
63
- {
64
- id: "delay-negative-violates",
65
- origin: "ms / setTimeout delay",
66
- source: `
67
- /**
68
- * @nudo:refine ms positive
69
- */
70
- function setDelay(ms) {
71
- if (ms > 0) return ms;
72
- return 0;
73
- }
74
- setDelay(-50);
75
- `,
76
- expect: "violation",
77
- },
78
- // --- 百分比 / 进度 ---
79
- {
80
- id: "pct-mid-ok",
81
- origin: "progress / opacity",
82
- source: `
83
- /**
84
- * @nudo:refine n percent
85
- */
86
- function pct(n) {
87
- if (n >= 0 && n <= 100) return n;
88
- return 0;
89
- }
90
- pct(50);
91
- `,
92
- expect: "ok",
93
- },
94
- {
95
- id: "pct-high-violates",
96
- origin: "progress / opacity",
97
- source: `
98
- /**
99
- * @nudo:refine n percent
100
- */
101
- function pct(n) {
102
- if (n >= 0 && n <= 100) return n;
103
- return 0;
104
- }
105
- pct(150);
106
- `,
107
- expect: "violation",
108
- note: "上界 100",
109
- },
110
- {
111
- id: "pct-low-violates",
112
- origin: "progress / opacity",
113
- source: `
114
- /**
115
- * @nudo:refine n percent
116
- */
117
- function pct(n) {
118
- if (n >= 0 && n <= 100) return n;
119
- return 0;
120
- }
121
- pct(-1);
122
- `,
123
- expect: "violation",
124
- note: "下界 0",
125
- },
126
- // --- 端口 / 索引 ---
127
- {
128
- id: "port-range-ok",
129
- origin: "net / listen port",
130
- source: `
131
- /**
132
- * @nudo:refine port atLeast1 && port <= 65535
133
- */
134
- function listen(port) {
135
- if (port >= 1 && port <= 65535) return port;
136
- return 80;
137
- }
138
- listen(8080);
139
- `,
140
- expect: "ok",
141
- },
142
- {
143
- id: "port-zero-violates",
144
- origin: "net / listen port",
145
- source: `
146
- /**
147
- * @nudo:refine port atLeast1 && port <= 65535
148
- */
149
- function listen(port) {
150
- if (port >= 1 && port <= 65535) return port;
151
- return 80;
152
- }
153
- listen(0);
154
- `,
155
- expect: "violation",
156
- },
157
- {
158
- id: "index-nonneg-ok",
159
- origin: "array index",
160
- source: `
161
- /**
162
- * @nudo:refine i nonNeg
163
- */
164
- function at(i) {
165
- if (i >= 0) return i;
166
- return 0;
167
- }
168
- at(3);
169
- `,
170
- expect: "ok",
171
- },
172
- {
173
- id: "index-neg-violates",
174
- origin: "array index",
175
- source: `
176
- /**
177
- * @nudo:refine i nonNeg
178
- */
179
- function at(i) {
180
- if (i >= 0) return i;
181
- return 0;
182
- }
183
- at(-1);
184
- `,
185
- expect: "violation",
186
- },
187
- // --- clamp:回退守卫不是调用前置 ---
188
- {
189
- id: "clamp-low-input-ok",
190
- origin: "lodash.clamp 风格",
191
- source: `
192
- function clamp(n, lo, hi) {
193
- if (n < lo) return lo;
194
- if (n > hi) return hi;
195
- return n;
196
- }
197
- clamp(-5, 0, 10);
198
- `,
199
- expect: "ok",
200
- note: "clamp 合法接受越界输入",
201
- },
202
- {
203
- id: "clamp-high-input-ok",
204
- origin: "lodash.clamp 风格",
205
- source: `
206
- function clamp(n, lo, hi) {
207
- if (n < lo) return lo;
208
- if (n > hi) return hi;
209
- return n;
210
- }
211
- clamp(99, 0, 10);
212
- `,
213
- expect: "ok",
214
- },
215
- // --- 上界-only ---
216
- {
217
- id: "max-size-ok",
218
- origin: "buffer / pageSize",
219
- source: `
220
- /**
221
- * @nudo:refine n max100
222
- */
223
- function pageSize(n) {
224
- if (n <= 100) return n;
225
- return 100;
226
- }
227
- pageSize(20);
228
- `,
229
- expect: "ok",
230
- },
231
- {
232
- id: "max-size-violates",
233
- origin: "buffer / pageSize",
234
- source: `
235
- /**
236
- * @nudo:refine n max100
237
- */
238
- function pageSize(n) {
239
- if (n <= 100) return n;
240
- return 100;
241
- }
242
- pageSize(1000);
243
- `,
244
- expect: "violation",
245
- },
246
- // --- 箭头 / export default ---
247
- {
248
- id: "arrow-violates",
249
- origin: "模块导出箭头",
250
- source: `
251
- /**
252
- * @nudo:refine x positive
253
- */
254
- const needsPositive = (x) => {
255
- if (x > 0) return x;
256
- return 0;
257
- };
258
- needsPositive(-2);
259
- `,
260
- expect: "violation",
261
- },
262
- {
263
- id: "export-default-violates",
264
- origin: "export default",
265
- source: `
266
- export default /**
267
- * @nudo:refine x positive
268
- */
269
- function needsPositive(x) {
270
- if (x > 0) return x;
271
- return 0;
272
- }
273
- needsPositive(-1);
274
- `,
275
- expect: "violation",
276
- },
277
- // --- 无前置:不得误报 ---
278
- {
279
- id: "no-guard-ok",
280
- origin: "普通工具函数",
281
- source: `
282
- function double(n) { return n * 2; }
283
- double(-1);
284
- `,
285
- expect: "ok",
286
- },
287
- {
288
- id: "string-op-ok",
289
- origin: "debug / format",
290
- source: `
291
- function fmt(s) { return String(s); }
292
- fmt(-1);
293
- `,
294
- expect: "ok",
295
- },
296
- {
297
- id: "equality-guard-not-precondition",
298
- origin: "分支处理",
299
- source: `
300
- function onlyZero(x) {
301
- if (x === 0) return 0;
302
- return 1;
303
- }
304
- onlyZero(5);
305
- `,
306
- expect: "ok",
307
- },
308
- // --- 调用链:wrapper 无条件转发 ---
309
- {
310
- id: "chained-valid-ok",
311
- origin: "内部转发",
312
- source: `
313
- /**
314
- * @nudo:refine x positive
315
- */
316
- function needsPositive(x) {
317
- if (x > 0) return x;
318
- return 0;
319
- }
320
- function wrapper(n) {
321
- return needsPositive(n);
322
- }
323
- wrapper(3);
324
- `,
325
- expect: "ok",
326
- note: "合法值经转发仍合法",
327
- },
328
- {
329
- id: "wrapper-forward-violates",
330
- origin: "内部转发",
331
- source: `
332
- /**
333
- * @nudo:refine x positive
334
- */
335
- function needsPositive(x) {
336
- if (x > 0) return x;
337
- return 0;
338
- }
339
- function wrapper(n) {
340
- return needsPositive(n);
341
- }
342
- wrapper(-1);
343
- `,
344
- expect: "violation",
345
- note: "无条件转发:wrapper 的实参须满足 target 前置",
346
- },
347
- {
348
- id: "arrow-wrapper-forward-violates",
349
- origin: "箭头转发",
350
- source: `
351
- /**
352
- * @nudo:refine x positive
353
- */
354
- function needsPositive(x) {
355
- if (x > 0) return x;
356
- return 0;
357
- }
358
- const wrap = (n) => needsPositive(n);
359
- wrap(0);
360
- `,
361
- expect: "violation",
362
- },
363
- {
364
- id: "conditional-wrapper-not-forward",
365
- origin: "带守卫的转发",
366
- source: `
367
- /**
368
- * @nudo:refine x positive
369
- */
370
- function needsPositive(x) {
371
- if (x > 0) return x;
372
- return 0;
373
- }
374
- function safeWrap(n) {
375
- if (n > 0) return needsPositive(n);
376
- return 0;
377
- }
378
- safeWrap(-1);
379
- `,
380
- expect: "ok",
381
- note: "有守卫,不是无条件转发——clamp 语义",
382
- },
383
- {
384
- id: "direct-invalid-in-chain",
385
- origin: "内部转发",
386
- source: `
387
- /**
388
- * @nudo:refine x positive
389
- */
390
- function needsPositive(x) {
391
- if (x > 0) return x;
392
- return 0;
393
- }
394
- needsPositive(-3);
395
- `,
396
- expect: "violation",
397
- },
398
- // --- 扫描边界已扩展:别名与对象属性调用 ---
399
- {
400
- id: "member-call-violates",
401
- origin: "obj.method(-1)",
402
- source: `
403
- /**
404
- * @nudo:refine x positive
405
- */
406
- function needsPositive(x) {
407
- if (x > 0) return x;
408
- return 0;
409
- }
410
- const api = { needsPositive };
411
- api.needsPositive(-1);
412
- `,
413
- expect: "violation",
414
- note: "对象属性简写调用",
415
- },
416
- {
417
- id: "member-renamed-key-violates",
418
- origin: "{ key: fn }",
419
- source: `
420
- /**
421
- * @nudo:refine x positive
422
- */
423
- function needsPositive(x) {
424
- if (x > 0) return x;
425
- return 0;
426
- }
427
- const api = { check: needsPositive };
428
- api.check(-1);
429
- `,
430
- expect: "violation",
431
- },
432
- {
433
- id: "aliased-fn-violates",
434
- origin: "const f = fn; f(-1)",
435
- source: `
436
- /**
437
- * @nudo:refine x positive
438
- */
439
- function needsPositive(x) {
440
- if (x > 0) return x;
441
- return 0;
442
- }
443
- const f = needsPositive;
444
- f(-1);
445
- `,
446
- expect: "violation",
447
- },
448
- {
449
- id: "aliased-fn-valid-ok",
450
- origin: "const f = fn; f(5)",
451
- source: `
452
- /**
453
- * @nudo:refine x positive
454
- */
455
- function needsPositive(x) {
456
- if (x > 0) return x;
457
- return 0;
458
- }
459
- const f = needsPositive;
460
- f(5);
461
- `,
462
- expect: "ok",
463
- },
464
- {
465
- id: "member-valid-ok",
466
- origin: "obj.method(5)",
467
- source: `
468
- /**
469
- * @nudo:refine x positive
470
- */
471
- function needsPositive(x) {
472
- if (x > 0) return x;
473
- return 0;
474
- }
475
- const api = { needsPositive };
476
- api.needsPositive(5);
477
- `,
478
- expect: "ok",
479
- },
480
- // --- 结构可赋值 ---
481
- {
482
- id: "assign-compatible-ok",
483
- origin: "结构赋值",
484
- source: `
485
- let a = { x: 1 };
486
- a = { x: 2 };
487
- `,
488
- expect: "ok",
489
- },
490
- {
491
- id: "assign-missing-slot-violates",
492
- origin: "结构赋值",
493
- source: `
494
- let a = { x: 1 };
495
- a = { y: 2 };
496
- `,
497
- expect: "violation",
498
- note: "缺 x;多 y 允许(宽度)",
499
- },
500
- {
501
- id: "assign-wider-ok",
502
- origin: "结构赋值",
503
- source: `
504
- let a = { x: 1 };
505
- a = { x: 2, z: "s" };
506
- `,
507
- expect: "ok",
508
- },
509
- {
510
- id: "assign-prim-mismatch-violates",
511
- origin: "结构赋值",
512
- source: `
513
- let n = 1;
514
- n = "str";
515
- `,
516
- expect: "violation",
517
- },
518
- // --- 传参结构 ---
519
- {
520
- id: "arg-structure-ok",
521
- origin: "传参结构",
522
- source: `
523
- function readX(p) {
524
- return p.x;
525
- }
526
- readX({ x: 1 });
527
- `,
528
- expect: "ok",
529
- },
530
- {
531
- id: "arg-missing-slot-violates",
532
- origin: "传参结构",
533
- source: `
534
- function readXY(p) {
535
- return p.x + p.y;
536
- }
537
- readXY({ x: 1 });
538
- `,
539
- expect: "violation",
540
- note: "缺 y",
541
- },
542
- {
543
- id: "arg-extra-slot-ok",
544
- origin: "传参结构",
545
- source: `
546
- function readX(p) {
547
- return p.x;
548
- }
549
- readX({ x: 1, z: 2 });
550
- `,
551
- expect: "ok",
552
- },
553
- {
554
- id: "arg-ident-missing-slot-violates",
555
- origin: "传参结构·标识符",
556
- source: `
557
- function readXY(p) {
558
- return p.x + p.y;
559
- }
560
- const o = { x: 1 };
561
- readXY(o);
562
- `,
563
- expect: "violation",
564
- },
565
- {
566
- id: "arg-ident-ok",
567
- origin: "传参结构·标识符",
568
- source: `
569
- function readX(p) {
570
- return p.x;
571
- }
572
- const o = { x: 1 };
573
- readX(o);
574
- `,
575
- expect: "ok",
576
- },
577
- // --- 递归:门禁必须可完成,截断只 warning ---
578
- {
579
- id: "recursion-fac-ok",
580
- origin: "递归·阶乘",
581
- source: `
582
- function fac(n) {
583
- if (n <= 1) return 1;
584
- return n * fac(n - 1);
585
- }
586
- const x = fac(5);
587
- `,
588
- expect: "ok",
589
- note: "递归截断记 warning,不得 error;check 不得栈溢出",
590
- },
591
- {
592
- id: "recursion-mutual-ok",
593
- origin: "递归·互递归",
594
- source: `
595
- function isEven(n) {
596
- if (n === 0) return true;
597
- return isOdd(n - 1);
598
- }
599
- function isOdd(n) {
600
- if (n === 0) return false;
601
- return isEven(n - 1);
602
- }
603
- const e = isEven(4);
604
- `,
605
- expect: "ok",
606
- },
607
- {
608
- id: "recursion-with-refine-ok",
609
- origin: "递归·有 return 契约",
610
- source: `
611
- /**
612
- * @nudo:refine return positive
613
- */
614
- function sumTo(n) {
615
- if (n <= 1) return 1;
616
- return n + sumTo(n - 1);
617
- }
618
- const s = sumTo(10);
619
- `,
620
- expect: "ok",
621
- note: "截断后 conf 降级,不得把 opaque 误报成 constraint-violated",
622
- },
623
- // --- any:任意值 ≠ 分析失败 ---
624
- {
625
- id: "any-param-call-ok",
626
- origin: "any·无契约参数",
627
- source: `
628
- function id(x) {
629
- return x;
630
- }
631
- id(1);
632
- id("a");
633
- `,
634
- expect: "ok",
635
- },
636
- {
637
- id: "any-assign-to-number-ok",
638
- origin: "any·源侧放行",
639
- source: `
640
- /**
641
- * @nudo:refine n positive
642
- */
643
- function needsPos(n) {
644
- if (n > 0) return n;
645
- return 0;
646
- }
647
- function wrap(v) {
648
- return needsPos(v);
649
- }
650
- `,
651
- expect: "ok",
652
- note: "any ≤ 任意目标:wrap 的 v 为 any,不构成 error(文档化语义)",
653
- },
654
- {
655
- id: "literal-still-violates-through-wrapper",
656
- origin: "any·不吞字面量违例",
657
- source: `
658
- /**
659
- * @nudo:refine n positive
660
- */
661
- function needsPos(n) {
662
- if (n > 0) return n;
663
- return 0;
664
- }
665
- function wrap(v) {
666
- return needsPos(v);
667
- }
668
- wrap(0);
669
- `,
670
- expect: "violation",
671
- note: "经 wrapper 的字面量 0 仍须报 constraint-violated",
672
- },
673
- ];
674
-
675
- /** require 金标:用 loadModule 喂外部源码 */
676
- type RequireGold = {
677
- id: string;
678
- /** 入口文件源码 */
679
- source: string;
680
- /** spec → 模块源码 */
681
- modules: Record<string, string>;
682
- expect: Expect;
683
- /** 人工备注:case 语义或预期依据 */
684
- note?: string;
685
- };
686
-
687
- const REQUIRE_GOLD: RequireGold[] = [
688
- {
689
- id: "require-destructure-violates",
690
- source: `
691
- const { needsPositive } = require("./v.js");
692
- needsPositive(-1);
693
- `,
694
- modules: {
695
- "./v.js": `
696
- /// @nudo:import { positive } from "./std.nudo.js"
697
- /**
698
- * @nudo:refine x positive
699
- */
700
- function needsPositive(x) {
701
- if (x > 0) return x;
702
- return 0;
703
- }
704
- module.exports = { needsPositive };
705
- `,
706
- },
707
- expect: "violation",
708
- },
709
- {
710
- id: "require-destructure-ok",
711
- source: `
712
- const { needsPositive } = require("./v.js");
713
- needsPositive(5);
714
- `,
715
- modules: {
716
- "./v.js": `
717
- /// @nudo:import { positive } from "./std.nudo.js"
718
- /**
719
- * @nudo:refine x positive
720
- */
721
- function needsPositive(x) {
722
- if (x > 0) return x;
723
- return 0;
724
- }
725
- module.exports = { needsPositive };
726
- `,
727
- },
728
- expect: "ok",
729
- },
730
- {
731
- id: "require-member-violates",
732
- source: `
733
- const v = require("./v.js");
734
- v.needsPositive(0);
735
- `,
736
- modules: {
737
- "./v.js": `
738
- /// @nudo:import { positive } from "./std.nudo.js"
739
- /**
740
- * @nudo:refine x positive
741
- */
742
- function needsPositive(x) {
743
- if (x > 0) return x;
744
- return 0;
745
- }
746
- module.exports = { needsPositive };
747
- `,
748
- },
749
- expect: "violation",
750
- },
751
- {
752
- id: "require-named-prop-violates",
753
- source: `
754
- const needsPositive = require("./v.js").needsPositive;
755
- needsPositive(-3);
756
- `,
757
- modules: {
758
- "./v.js": `
759
- /// @nudo:import { positive } from "./std.nudo.js"
760
- /**
761
- * @nudo:refine x positive
762
- */
763
- function needsPositive(x) {
764
- if (x > 0) return x;
765
- return 0;
766
- }
767
- module.exports.needsPositive = needsPositive;
768
- `,
769
- },
770
- expect: "violation",
771
- },
772
- {
773
- id: "esm-import-named-violates",
774
- source: `
775
- import { needsPositive } from "./v.js";
776
- needsPositive(-1);
777
- `,
778
- modules: {
779
- "./v.js": `
780
- export /// @nudo:import { positive } from "./std.nudo.js"
781
- /**
782
- * @nudo:refine x positive
783
- */
784
- function needsPositive(x) {
785
- if (x > 0) return x;
786
- return 0;
787
- }
788
- `,
789
- },
790
- expect: "violation",
791
- },
792
- {
793
- id: "esm-import-alias-violates",
794
- source: `
795
- import { needsPositive as np } from "./v.js";
796
- np(0);
797
- `,
798
- modules: {
799
- "./v.js": `
800
- export /// @nudo:import { positive } from "./std.nudo.js"
801
- /**
802
- * @nudo:refine x positive
803
- */
804
- function needsPositive(x) {
805
- if (x > 0) return x;
806
- return 0;
807
- }
808
- `,
809
- },
810
- expect: "violation",
811
- },
812
- {
813
- id: "esm-namespace-member-violates",
814
- source: `
815
- import * as v from "./v.js";
816
- v.needsPositive(-2);
817
- `,
818
- modules: {
819
- "./v.js": `
820
- export /// @nudo:import { positive } from "./std.nudo.js"
821
- /**
822
- * @nudo:refine x positive
823
- */
824
- function needsPositive(x) {
825
- if (x > 0) return x;
826
- return 0;
827
- }
828
- `,
829
- },
830
- expect: "violation",
831
- },
832
- {
833
- id: "esm-import-ok",
834
- source: `
835
- import { needsPositive } from "./v.js";
836
- needsPositive(10);
837
- `,
838
- modules: {
839
- "./v.js": `
840
- export /// @nudo:import { positive } from "./std.nudo.js"
841
- /**
842
- * @nudo:refine x positive
843
- */
844
- function needsPositive(x) {
845
- if (x > 0) return x;
846
- return 0;
847
- }
848
- `,
849
- },
850
- expect: "ok",
851
- },
852
- {
853
- id: "dynamic-import-destructure-violates",
854
- source: `
855
- async function main() {
856
- const { needsPositive } = await import("./v.js");
857
- needsPositive(-1);
858
- }
859
- main();
860
- `,
861
- modules: {
862
- "./v.js": `
863
- export /// @nudo:import { positive } from "./std.nudo.js"
864
- /**
865
- * @nudo:refine x positive
866
- */
867
- function needsPositive(x) {
868
- if (x > 0) return x;
869
- return 0;
870
- }
871
- `,
872
- },
873
- expect: "violation",
874
- },
875
- {
876
- id: "dynamic-import-ns-member-violates",
877
- source: `
878
- async function main() {
879
- const v = await import("./v.js");
880
- v.needsPositive(0);
881
- }
882
- main();
883
- `,
884
- modules: {
885
- "./v.js": `
886
- export /// @nudo:import { positive } from "./std.nudo.js"
887
- /**
888
- * @nudo:refine x positive
889
- */
890
- function needsPositive(x) {
891
- if (x > 0) return x;
892
- return 0;
893
- }
894
- `,
895
- },
896
- expect: "violation",
897
- },
898
- {
899
- id: "reexport-hop-violates",
900
- source: `
901
- import { needsPositive } from "./barrel.js";
902
- needsPositive(-1);
903
- `,
904
- modules: {
905
- "./barrel.js": `export { needsPositive } from "./v.js";\n`,
906
- "./v.js": `
907
- export /// @nudo:import { positive } from "./std.nudo.js"
908
- /**
909
- * @nudo:refine x positive
910
- */
911
- function needsPositive(x) {
912
- if (x > 0) return x;
913
- return 0;
914
- }
915
- `,
916
- },
917
- expect: "violation",
918
- note: "barrel 只 re-export,约束在 v.js",
919
- },
920
- ];
921
-
922
- function run(g: Gold): CheckReport {
923
- return checkSource(`gold-${g.id}.js`, withStdImport(g.source), pTrue, stdOpts);
924
- }
925
-
926
- function bucket(g: Gold, r: CheckReport): "TP" | "FN" | "FP" | "TN" {
927
- const hasErr = r.issues.some((i) => i.severity === "error");
928
- if (g.expect === "violation") return hasErr ? "TP" : "FN";
929
- return hasErr ? "FP" : "TN";
930
- }
931
-
932
- describe("check gold recall (human-labeled)", () => {
933
- const counts = { TP: 0, FN: 0, FP: 0, TN: 0 };
934
- const failures: string[] = [];
935
-
936
- for (const g of GOLD) {
937
- it(`${g.id} [${g.origin}] → ${g.expect}`, () => {
938
- const r = run(g);
939
- const b = bucket(g, r);
940
- counts[b]++;
941
- if (b === "FN" || b === "FP") {
942
- failures.push(
943
- `${g.id} (${b}): ${g.note ?? ""} issues=${r.issues
944
- .map((i) => i.code)
945
- .join(",")}`,
946
- );
947
- }
948
- if (g.expect === "violation") {
949
- expect(
950
- r.issues.some(
951
- (i) =>
952
- i.severity === "error" &&
953
- (i.code === "nudo:constraint-violated" ||
954
- i.code === "nudo:assign-mismatch" ||
955
- i.code === "nudo:arg-structure"),
956
- ),
957
- `expected violation, got: ${r.issues.map((i) => `${i.code} ${i.message}`).join("; ") || "ok"}`,
958
- ).toBe(true);
959
- expect(r.ok).toBe(false);
960
- } else {
961
- expect(r.ok, `false positive: ${r.issues.map((i) => i.message).join("; ")}`).toBe(true);
962
- }
963
- });
964
- }
965
-
966
- it("recall = 1.0 and precision = 1.0 on this gold set", () => {
967
- // 本 it 只汇总;逐条 it 已失败则这里也会红
968
- const recall = counts.TP + counts.FN === 0 ? 1 : counts.TP / (counts.TP + counts.FN);
969
- const precision = counts.TP + counts.FP === 0 ? 1 : counts.TP / (counts.TP + counts.FP);
970
- // vitest 并行下 counts 可能未累完——用同步重算
971
- let TP = 0, FN = 0, FP = 0, TN = 0;
972
- for (const g of GOLD) {
973
- const b = bucket(g, run(g));
974
- if (b === "TP") TP++;
975
- else if (b === "FN") FN++;
976
- else if (b === "FP") FP++;
977
- else TN++;
978
- }
979
- const rec = TP + FN === 0 ? 1 : TP / (TP + FN);
980
- const prec = TP + FP === 0 ? 1 : TP / (TP + FP);
981
- const detail = `TP=${TP} FN=${FN} FP=${FP} TN=${TN} recall=${rec.toFixed(2)} precision=${prec.toFixed(2)}`;
982
- expect(rec, `recall < 1: ${detail}`).toBe(1);
983
- expect(prec, `precision < 1: ${detail}`).toBe(1);
984
- expect(TP).toBeGreaterThan(5);
985
- });
986
- });
987
-
988
- describe("check require cross-file gold", () => {
989
- for (const g of REQUIRE_GOLD) {
990
- it(`${g.id} → ${g.expect}`, () => {
991
- const r = checkSource(`req-${g.id}.js`, g.source, pTrue, {
992
- loadModule: (spec) => (spec.includes("std.nudo") ? STD_NUDO_SRC : g.modules[spec]),
993
- fromFile: `req-${g.id}.js`,
994
- });
995
- if (g.expect === "violation") {
996
- expect(
997
- r.issues.some((i) => i.code === "nudo:constraint-violated"),
998
- r.issues.map((i) => i.message).join("; ") || "ok",
999
- ).toBe(true);
1000
- } else {
1001
- expect(r.ok, r.issues.map((i) => i.message).join("; ")).toBe(true);
1002
- }
1003
- });
1004
- }
1005
- });
1006
-
1007
- describe("check ESM import gold", () => {
1008
- for (const g of REQUIRE_GOLD.filter((x) => x.id.startsWith("esm-"))) {
1009
- it(`${g.id} → ${g.expect}`, () => {
1010
- const r = checkSource(`esm-${g.id}.js`, g.source, pTrue, {
1011
- loadModule: (spec) => (spec.includes("std.nudo") ? STD_NUDO_SRC : g.modules[spec]),
1012
- fromFile: `esm-${g.id}.js`,
1013
- });
1014
- if (g.expect === "violation") {
1015
- expect(
1016
- r.issues.some((i) => i.code === "nudo:constraint-violated"),
1017
- r.issues.map((i) => i.message).join("; ") || "ok",
1018
- ).toBe(true);
1019
- } else {
1020
- expect(r.ok, r.issues.map((i) => i.message).join("; ")).toBe(true);
1021
- }
1022
- });
1023
- }
1024
- });