@nudojs/core 0.2.0 → 0.3.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 (115) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +26 -0
  3. package/dist/index.d.ts +1601 -12
  4. package/dist/index.js +9657 -110
  5. package/package.json +18 -2
  6. package/src/__tests__/fn-sig-impl.test.ts +57 -0
  7. package/src/__tests__/ops-refined.test.ts +3 -1
  8. package/src/__tests__/ops.test.ts +8 -33
  9. package/src/__tests__/type-value.test.ts +106 -0
  10. package/src/algebra/__tests__/abs-native.test.ts +176 -0
  11. package/src/algebra/__tests__/arithmetic.test.ts +170 -0
  12. package/src/algebra/__tests__/ast-eval.test.ts +154 -0
  13. package/src/algebra/__tests__/bridge.test.ts +160 -0
  14. package/src/algebra/__tests__/call-budget.test.ts +117 -0
  15. package/src/algebra/__tests__/check-case-consistency.test.ts +69 -0
  16. package/src/algebra/__tests__/check-gold.test.ts +290 -0
  17. package/src/algebra/__tests__/check-json.test.ts +57 -0
  18. package/src/algebra/__tests__/check-mini-repo.test.ts +49 -0
  19. package/src/algebra/__tests__/check-real-commander.test.ts +43 -0
  20. package/src/algebra/__tests__/check-real-packages.test.ts +165 -0
  21. package/src/algebra/__tests__/check-recall-gold.test.ts +1024 -0
  22. package/src/algebra/__tests__/check-require-dep-memo.test.ts +45 -0
  23. package/src/algebra/__tests__/check-return-contract.test.ts +90 -0
  24. package/src/algebra/__tests__/check-shape-gold.test.ts +303 -0
  25. package/src/algebra/__tests__/check-source-memo.test.ts +204 -0
  26. package/src/algebra/__tests__/check.test.ts +69 -0
  27. package/src/algebra/__tests__/denote.test.ts +77 -0
  28. package/src/algebra/__tests__/diagnostics.test.ts +64 -0
  29. package/src/algebra/__tests__/differential.test.ts +124 -0
  30. package/src/algebra/__tests__/div-mod.test.ts +38 -0
  31. package/src/algebra/__tests__/example0.test.ts +103 -0
  32. package/src/algebra/__tests__/exec-analyze-throws.test.ts +60 -0
  33. package/src/algebra/__tests__/exec-async.test.ts +46 -0
  34. package/src/algebra/__tests__/exec-bpath.test.ts +193 -0
  35. package/src/algebra/__tests__/exec-class.test.ts +56 -0
  36. package/src/algebra/__tests__/exec-e2e.test.ts +97 -0
  37. package/src/algebra/__tests__/exec-generator.test.ts +53 -0
  38. package/src/algebra/__tests__/exec-inherit-default.test.ts +111 -0
  39. package/src/algebra/__tests__/exec-optional-nested.test.ts +87 -0
  40. package/src/algebra/__tests__/exec-rest.test.ts +55 -0
  41. package/src/algebra/__tests__/exec-spread-forof.test.ts +85 -0
  42. package/src/algebra/__tests__/exec-static-computed.test.ts +61 -0
  43. package/src/algebra/__tests__/exec-switch.test.ts +74 -0
  44. package/src/algebra/__tests__/exec-trycatch.test.ts +71 -0
  45. package/src/algebra/__tests__/fn-fp-memo.test.ts +166 -0
  46. package/src/algebra/__tests__/fn-fp-multidecl.test.ts +49 -0
  47. package/src/algebra/__tests__/format.test.ts +34 -0
  48. package/src/algebra/__tests__/generalize-memo.test.ts +285 -0
  49. package/src/algebra/__tests__/generalize.test.ts +113 -0
  50. package/src/algebra/__tests__/hash-source.test.ts +19 -0
  51. package/src/algebra/__tests__/hof.test.ts +80 -0
  52. package/src/algebra/__tests__/inlay.test.ts +30 -0
  53. package/src/algebra/__tests__/language.test.ts +77 -0
  54. package/src/algebra/__tests__/leq.test.ts +109 -0
  55. package/src/algebra/__tests__/load-deps-fp-memo.test.ts +132 -0
  56. package/src/algebra/__tests__/methods.test.ts +37 -0
  57. package/src/algebra/__tests__/node-types.test.ts +26 -0
  58. package/src/algebra/__tests__/nudo-constraints.ts +46 -0
  59. package/src/algebra/__tests__/objects.test.ts +117 -0
  60. package/src/algebra/__tests__/parse-source-cache.test.ts +42 -0
  61. package/src/algebra/__tests__/refine-import.test.ts +109 -0
  62. package/src/algebra/__tests__/stable-source-key-block.test.ts +23 -0
  63. package/src/algebra/__tests__/stable-source-key.test.ts +31 -0
  64. package/src/algebra/__tests__/surface.test.ts +41 -0
  65. package/src/algebra/__tests__/this-inherit.test.ts +130 -0
  66. package/src/algebra/abs-fn.ts +49 -0
  67. package/src/algebra/abs-modules.ts +139 -0
  68. package/src/algebra/abs.ts +206 -0
  69. package/src/algebra/arithmetic.ts +691 -0
  70. package/src/algebra/ast-eval.ts +1763 -0
  71. package/src/algebra/bridge.ts +350 -0
  72. package/src/algebra/builtins.ts +373 -0
  73. package/src/algebra/check-report.ts +150 -0
  74. package/src/algebra/check.ts +1867 -0
  75. package/src/algebra/constraint.ts +304 -0
  76. package/src/algebra/denote.ts +166 -0
  77. package/src/algebra/diagnostics.ts +210 -0
  78. package/src/algebra/exec/call.ts +29 -0
  79. package/src/algebra/exec/calls.ts +96 -0
  80. package/src/algebra/exec/class-registry.ts +29 -0
  81. package/src/algebra/exec/class.ts +329 -0
  82. package/src/algebra/exec/index.ts +6 -0
  83. package/src/algebra/exec/member-diag.ts +141 -0
  84. package/src/algebra/exec/run.ts +341 -0
  85. package/src/algebra/exec/runtime.ts +583 -0
  86. package/src/algebra/exec/transpile.ts +993 -0
  87. package/src/algebra/fn-fp.ts +294 -0
  88. package/src/algebra/format.ts +91 -0
  89. package/src/algebra/generalize.ts +776 -0
  90. package/src/algebra/hash-source.ts +27 -0
  91. package/src/algebra/index.ts +34 -0
  92. package/src/algebra/inlay.ts +198 -0
  93. package/src/algebra/language.ts +289 -0
  94. package/src/algebra/leak.ts +68 -0
  95. package/src/algebra/leq.ts +301 -0
  96. package/src/algebra/load-deps-fp.ts +97 -0
  97. package/src/algebra/methods.ts +128 -0
  98. package/src/algebra/objects.ts +251 -0
  99. package/src/algebra/parse-source.ts +64 -0
  100. package/src/algebra/phi.ts +42 -0
  101. package/src/algebra/pred.ts +395 -0
  102. package/src/algebra/refine.ts +227 -0
  103. package/src/algebra/stable-source-key.ts +81 -0
  104. package/src/algebra/surface.ts +206 -0
  105. package/src/algebra/template.ts +143 -0
  106. package/src/algebra/term.ts +119 -0
  107. package/src/environment.ts +5 -0
  108. package/src/index.ts +33 -0
  109. package/src/mock-helpers.ts +166 -0
  110. package/src/ops.ts +44 -63
  111. package/src/refinements/template-predicates.ts +11 -0
  112. package/src/refinements/template.ts +1 -3
  113. package/src/strip-types.ts +236 -0
  114. package/src/type-value.ts +153 -12
  115. package/src/refinements/index.ts +0 -2
@@ -0,0 +1,290 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { checkSource, pTrue } from "../index.ts";
3
+ import { withStdImport, stdOpts } from "./nudo-constraints.ts";
4
+
5
+ /**
6
+ * nudo check 金标:多形态 JS 片段上的门禁行为。
7
+ * 每条:source + 期望 ok / 期望出现的 issue code。
8
+ */
9
+
10
+ type Gold = {
11
+ name: string;
12
+ source: string;
13
+ expectOk: boolean;
14
+ expectCode?: string;
15
+ };
16
+
17
+ const golds: Gold[] = [
18
+ {
19
+ name: "valid positive call",
20
+ source: `
21
+ /**
22
+ * @nudo:refine x positive
23
+ */
24
+ function needsPositive(x) {
25
+ if (x > 0) return x;
26
+ return 0;
27
+ }
28
+ const r = needsPositive(5);
29
+ `,
30
+ expectOk: true,
31
+ },
32
+ {
33
+ name: "negative call violates x>0",
34
+ source: `
35
+ /**
36
+ * @nudo:refine x positive
37
+ */
38
+ function needsPositive(x) {
39
+ if (x > 0) return x;
40
+ return 0;
41
+ }
42
+ const r = needsPositive(-1);
43
+ `,
44
+ expectOk: false,
45
+ expectCode: "nudo:constraint-violated",
46
+ },
47
+ {
48
+ name: "range guard lo is clamp not violation",
49
+ source: `
50
+ function clampId(id) {
51
+ if (id < 1) return 1;
52
+ if (id > 9999) return 9999;
53
+ return id;
54
+ }
55
+ clampId(0);
56
+ `,
57
+ // `id < 1` 是回退守卫,不作为调用前置
58
+ expectOk: true,
59
+ },
60
+ {
61
+ name: "range guard valid mid",
62
+ source: `
63
+ function clampId(id) {
64
+ if (id < 1) return 1;
65
+ if (id > 9999) return 9999;
66
+ return id;
67
+ }
68
+ clampId(50);
69
+ `,
70
+ expectOk: true,
71
+ },
72
+ {
73
+ name: "pure arithmetic no constraints",
74
+ source: `
75
+ function add(a, b) { return a + b; }
76
+ const r = add(1, 2);
77
+ `,
78
+ expectOk: true,
79
+ },
80
+ {
81
+ name: "ESM export function still listed",
82
+ source: `
83
+ export function scale(x) { return x + 1; }
84
+ `,
85
+ expectOk: true,
86
+ },
87
+ {
88
+ name: "async function listed",
89
+ source: `
90
+ export async function load(id) { return id + 1; }
91
+ `,
92
+ expectOk: true,
93
+ },
94
+ {
95
+ name: "class constructor listed",
96
+ source: `
97
+ class Counter {
98
+ constructor(n) { this.n = n; }
99
+ get() { return this.n; }
100
+ }
101
+ export function main() { return new Counter(1).get(); }
102
+ `,
103
+ expectOk: true,
104
+ },
105
+ // --- 真阳性:必须报 ---
106
+ {
107
+ name: "zero violates x>0",
108
+ source: `
109
+ /**
110
+ * @nudo:refine x positive
111
+ */
112
+ function needsPositive(x) {
113
+ if (x > 0) return x;
114
+ return 0;
115
+ }
116
+ needsPositive(0);
117
+ `,
118
+ expectOk: false,
119
+ expectCode: "nudo:constraint-violated",
120
+ },
121
+ {
122
+ name: "unary negative call",
123
+ source: `
124
+ /**
125
+ * @nudo:refine x positive
126
+ */
127
+ function needsPositive(x) {
128
+ if (x > 0) return x;
129
+ return 0;
130
+ }
131
+ needsPositive(-3);
132
+ `,
133
+ expectOk: false,
134
+ expectCode: "nudo:constraint-violated",
135
+ },
136
+ {
137
+ name: "x>=1 rejects 0",
138
+ source: `
139
+ /**
140
+ * @nudo:refine i atLeast1
141
+ */
142
+ function idx(i) {
143
+ if (i >= 1) return i;
144
+ return 1;
145
+ }
146
+ idx(0);
147
+ `,
148
+ expectOk: false,
149
+ expectCode: "nudo:constraint-violated",
150
+ },
151
+ {
152
+ name: "upper bound x<10 rejects 10",
153
+ source: `
154
+ /**
155
+ * @nudo:refine n small
156
+ */
157
+ function small(n) {
158
+ if (n < 10) return n;
159
+ return 9;
160
+ }
161
+ small(10);
162
+ `,
163
+ expectOk: false,
164
+ expectCode: "nudo:constraint-violated",
165
+ },
166
+ // --- 真阴性:不得误报(clamp / 无前置) ---
167
+ {
168
+ name: "clamp negative input is not violation",
169
+ source: `
170
+ function clamp(n, lo, hi) {
171
+ if (n < lo) return lo;
172
+ if (n > hi) return hi;
173
+ return n;
174
+ }
175
+ clamp(-5, 0, 100);
176
+ `,
177
+ expectOk: true,
178
+ },
179
+ {
180
+ name: "no guard no constraint",
181
+ source: `
182
+ function double(n) { return n * 2; }
183
+ double(-1);
184
+ `,
185
+ expectOk: true,
186
+ },
187
+ {
188
+ name: "equality guard is not numeric precondition",
189
+ source: `
190
+ function onlyZero(x) {
191
+ if (x === 0) return 0;
192
+ return 1;
193
+ }
194
+ onlyZero(5);
195
+ `,
196
+ expectOk: true,
197
+ },
198
+ {
199
+ name: "min boundary valid",
200
+ source: `
201
+ function needsPositive(x) {
202
+ if (x > 0) return x;
203
+ return 0;
204
+ }
205
+ needsPositive(1);
206
+ `,
207
+ expectOk: true,
208
+ },
209
+ // --- 结构 ---
210
+ {
211
+ name: "arrow function constraint",
212
+ source: `
213
+ /**
214
+ * @nudo:refine x positive
215
+ */
216
+ const needsPositive = (x) => {
217
+ if (x > 0) return x;
218
+ return 0;
219
+ };
220
+ needsPositive(-2);
221
+ `,
222
+ expectOk: false,
223
+ expectCode: "nudo:constraint-violated",
224
+ },
225
+ {
226
+ name: "export default function",
227
+ source: `
228
+ /**
229
+ * @nudo:refine x positive
230
+ */
231
+ export default function needsPositive(x) {
232
+ if (x > 0) return x;
233
+ return 0;
234
+ }
235
+ needsPositive(-1);
236
+ `,
237
+ expectOk: false,
238
+ expectCode: "nudo:constraint-violated",
239
+ },
240
+ {
241
+ name: "both bounds mid valid",
242
+ source: `
243
+ /**
244
+ * @nudo:refine n percent
245
+ */
246
+ function pct(n) {
247
+ if (n >= 0 && n <= 100) return n;
248
+ return 0;
249
+ }
250
+ pct(50);
251
+ `,
252
+ expectOk: true,
253
+ },
254
+ {
255
+ name: "both bounds high invalid",
256
+ source: `
257
+ /**
258
+ * @nudo:refine n percent
259
+ */
260
+ function pct(n) {
261
+ if (n >= 0 && n <= 100) return n;
262
+ return 0;
263
+ }
264
+ pct(150);
265
+ `,
266
+ expectOk: false,
267
+ expectCode: "nudo:constraint-violated",
268
+ },
269
+ ];
270
+
271
+ describe("nudo check gold standards", () => {
272
+ for (const g of golds) {
273
+ it(g.name, () => {
274
+ const report = checkSource("gold.js", withStdImport(g.source), pTrue, stdOpts);
275
+ expect(report.ok, formatIssues(report)).toBe(g.expectOk);
276
+ if (g.expectCode) {
277
+ expect(
278
+ report.issues.some((i) => i.code === g.expectCode),
279
+ formatIssues(report),
280
+ ).toBe(true);
281
+ }
282
+ // 每个 case 至少能列出函数或明确无函数
283
+ expect(Array.isArray(report.signatures)).toBe(true);
284
+ });
285
+ }
286
+ });
287
+
288
+ function formatIssues(r: { issues: Array<{ severity: string; code: string; message: string }> }): string {
289
+ return r.issues.map((i) => `${i.severity} ${i.code}: ${i.message}`).join("; ") || "(none)";
290
+ }
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { checkSource, serializeCheckJson, pTrue } from "../index.ts";
3
+ import { withStdImport, stdOpts } from "./nudo-constraints.ts";
4
+
5
+ describe("CheckJson contract v1", () => {
6
+ const src = `
7
+ /**
8
+ * @nudo:refine x positive
9
+ */
10
+ function needsPositive(x) {
11
+ if (x > 0) return x;
12
+ return 0;
13
+ }
14
+ needsPositive(-1);
15
+ `;
16
+
17
+ it("has stable top-level fields", () => {
18
+ const j = serializeCheckJson(checkSource("c.js", withStdImport(src), pTrue, stdOpts));
19
+ expect(j.version).toBe(1);
20
+ expect(j.file).toBe("c.js");
21
+ expect(j.ok).toBe(false);
22
+ expect(j.summary).toMatchObject({
23
+ errors: expect.any(Number),
24
+ warnings: expect.any(Number),
25
+ infos: expect.any(Number),
26
+ functions: expect.any(Number),
27
+ });
28
+ expect(Array.isArray(j.signatures)).toBe(true);
29
+ expect(Array.isArray(j.issues)).toBe(true);
30
+ });
31
+
32
+ it("signatures carry abs display + detail", () => {
33
+ const j = serializeCheckJson(checkSource("c.js", withStdImport(src), pTrue, stdOpts));
34
+ const sig = j.signatures.find((s) => s.name === "needsPositive");
35
+ expect(sig).toBeDefined();
36
+ expect(sig!.params).toEqual(["x"]);
37
+ expect(sig!.abs).toContain("#");
38
+ expect(typeof sig!.conf).toBe("string");
39
+ });
40
+
41
+ it("issues carry actual/expected for violations", () => {
42
+ const j = serializeCheckJson(checkSource("c.js", withStdImport(src), pTrue, stdOpts));
43
+ const err = j.issues.find((i) => i.code === "nudo:constraint-violated");
44
+ expect(err).toBeDefined();
45
+ expect(err!.actual).toBeDefined();
46
+ expect(err!.expected).toBeDefined();
47
+ expect(err!.line).toBeGreaterThan(0);
48
+ });
49
+
50
+ it("ok file has ok:true and empty error issues", () => {
51
+ const j = serializeCheckJson(
52
+ checkSource("ok.js", `function f(x){ return x+1; }\nf(1);\n`, pTrue, stdOpts),
53
+ );
54
+ expect(j.ok).toBe(true);
55
+ expect(j.issues.filter((i) => i.severity === "error")).toEqual([]);
56
+ });
57
+ });
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { checkSource, pTrue } from "../index.ts";
6
+ import { withStdImport, stdOpts } from "./nudo-constraints.ts";
7
+
8
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../../");
9
+ const mini = (f: string) => readFileSync(resolve(root, "docs/examples/mini-repo", f), "utf8");
10
+
11
+ /**
12
+ * mini-repo 人工金标:文件级 check 期望。
13
+ * 这是「替换 tsc」的 recall 基线——新增违例模式时先补这里。
14
+ */
15
+ describe("mini-repo check gold", () => {
16
+ it("validators.js: no false positives", () => {
17
+ const r = checkSource("validators.js", mini("validators.js"));
18
+ expect(r.ok, r.issues.map((i) => i.message).join("; ")).toBe(true);
19
+ expect(r.signatures.map((f) => f.name)).toContain("isPositive");
20
+ expect(r.signatures.map((f) => f.name)).toContain("clamp");
21
+ });
22
+
23
+ it("store.js: class methods listed", () => {
24
+ const r = checkSource("store.js", mini("store.js"));
25
+ expect(r.ok).toBe(true);
26
+ });
27
+
28
+ it("user-service.js: ok (no literal violating calls)", () => {
29
+ const r = checkSource("user-service.js", mini("user-service.js"));
30
+ expect(r.ok, r.issues.map((i) => `${i.severity} ${i.message}`).join("; ")).toBe(true);
31
+ });
32
+
33
+ it("inline violating snippet still caught (TP recall)", () => {
34
+ // isPositive 是谓词(无 if-return-param 前置);真正门禁用 refine
35
+ const src = `
36
+ /**
37
+ * @nudo:refine x positive
38
+ */
39
+ function needsPositive(x) {
40
+ if (x > 0) return x;
41
+ return 0;
42
+ }
43
+ needsPositive(-1);
44
+ `;
45
+ const r = checkSource("tp.js", withStdImport(src), pTrue, stdOpts);
46
+ expect(r.ok).toBe(false);
47
+ expect(r.issues.some((i) => i.code === "nudo:constraint-violated")).toBe(true);
48
+ });
49
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync, existsSync, readdirSync } from "node:fs";
3
+ import { join, dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { checkSource } from "../index.ts";
6
+
7
+ /**
8
+ * 真实包精度门禁:commander(devDep)上不得出现任何 error 级误报。
9
+ * 包不存在时跳过(非 monorepo 环境)。
10
+ */
11
+
12
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../..");
13
+ const commanderLib = join(root, "node_modules/commander/lib");
14
+
15
+ const ERROR_CODES = [
16
+ "nudo:constraint-violated",
17
+ "nudo:assign-mismatch",
18
+ "nudo:arg-structure",
19
+ ] as const;
20
+
21
+ describe("real package precision (commander)", () => {
22
+ const hasPkg = existsSync(commanderLib);
23
+
24
+ it.runIf(hasPkg)("no false-positive errors (constraint / assign / arg-structure)", () => {
25
+ const files = readdirSync(commanderLib).filter((f) => f.endsWith(".js"));
26
+ expect(files.length).toBeGreaterThan(0);
27
+
28
+ const violations: string[] = [];
29
+ let scanned = 0;
30
+ for (const f of files) {
31
+ const source = readFileSync(join(commanderLib, f), "utf8");
32
+ const r = checkSource(`commander/${f}`, source);
33
+ scanned++;
34
+ for (const i of r.issues) {
35
+ if (i.severity === "error" && (ERROR_CODES as readonly string[]).includes(i.code)) {
36
+ violations.push(`commander/${f}:${i.line ?? "?"} [${i.code}] ${i.message}`);
37
+ }
38
+ }
39
+ }
40
+ expect(scanned).toBeGreaterThan(3);
41
+ expect(violations, violations.join("\n")).toEqual([]);
42
+ });
43
+ });
@@ -0,0 +1,165 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync, existsSync, readdirSync } from "node:fs";
3
+ import { join, dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createRequire } from "node:module";
6
+ import { checkSource } from "../index.ts";
7
+
8
+ /**
9
+ * 真实包精度门禁:error 级误报必须为 0。
10
+ * 包不存在时跳过(非 monorepo / 未装依赖)。
11
+ */
12
+
13
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../..");
14
+ const require = createRequire(import.meta.url);
15
+
16
+ const ERROR_CODES = [
17
+ "nudo:constraint-violated",
18
+ "nudo:assign-mismatch",
19
+ "nudo:arg-structure",
20
+ "nudo:case-inconsistency",
21
+ ] as const;
22
+
23
+ function pkgJsFiles(pkgName: string): { label: string; source: string }[] {
24
+ let pkgRoot: string | undefined;
25
+ // 先试 package.json;exports 未暴露时从主入口反推
26
+ try {
27
+ pkgRoot = dirname(require.resolve(`${pkgName}/package.json`));
28
+ } catch {
29
+ try {
30
+ let p = require.resolve(pkgName);
31
+ // 主入口可能在 lib/ 或 src/;向上找到含 package.json 的目录
32
+ for (let i = 0; i < 4; i++) {
33
+ const parent = dirname(p);
34
+ if (existsSync(join(parent, "package.json"))) {
35
+ pkgRoot = parent;
36
+ break;
37
+ }
38
+ p = parent;
39
+ }
40
+ } catch {
41
+ return [];
42
+ }
43
+ }
44
+ if (!pkgRoot) return [];
45
+ const out: { label: string; source: string }[] = [];
46
+ const walk = (dir: string): void => {
47
+ let entries;
48
+ try {
49
+ entries = readdirSync(dir, { withFileTypes: true });
50
+ } catch {
51
+ return;
52
+ }
53
+ for (const e of entries) {
54
+ if (e.name === "node_modules" || e.name.startsWith(".") || e.name === "tests" || e.name === "typings") continue;
55
+ const p = join(dir, e.name);
56
+ if (e.isDirectory()) {
57
+ walk(p);
58
+ } else if (/\.(mjs|cjs)$/.test(e.name) || e.name.endsWith(".js")) {
59
+ try {
60
+ out.push({
61
+ label: `${pkgName}/${p.slice(pkgRoot!.length + 1)}`,
62
+ source: readFileSync(p, "utf8"),
63
+ });
64
+ } catch {
65
+ // skip
66
+ }
67
+ }
68
+ }
69
+ };
70
+ walk(pkgRoot);
71
+ return out;
72
+ }
73
+
74
+ function scanPackage(pkgName: string): { scanned: number; violations: string[] } {
75
+ const files = pkgJsFiles(pkgName);
76
+ const violations: string[] = [];
77
+ let scanned = 0;
78
+ for (const { label, source } of files) {
79
+ let r;
80
+ try {
81
+ r = checkSource(label, source);
82
+ } catch {
83
+ continue;
84
+ }
85
+ scanned++;
86
+ for (const i of r.issues) {
87
+ if (i.severity === "error" && (ERROR_CODES as readonly string[]).includes(i.code)) {
88
+ violations.push(`${label}:${i.line ?? "?"} [${i.code}] ${i.message}`);
89
+ }
90
+ }
91
+ }
92
+ return { scanned, violations };
93
+ }
94
+
95
+ describe("real package precision", () => {
96
+ it.runIf(existsSync(join(root, "node_modules/commander/lib")))(
97
+ "commander: no false-positive errors",
98
+ () => {
99
+ const { scanned, violations } = scanPackage("commander");
100
+ expect(scanned).toBeGreaterThan(3);
101
+ expect(violations, violations.join("\n")).toEqual([]);
102
+ },
103
+ );
104
+
105
+ it("escape-string-regexp: no false-positive errors", () => {
106
+ const { scanned, violations } = scanPackage("escape-string-regexp");
107
+ expect(scanned).toBeGreaterThan(0);
108
+ expect(violations, violations.join("\n")).toEqual([]);
109
+ });
110
+
111
+ it("is-plain-obj: no false-positive errors", () => {
112
+ const { scanned, violations } = scanPackage("is-plain-obj");
113
+ expect(scanned).toBeGreaterThan(0);
114
+ expect(violations, violations.join("\n")).toEqual([]);
115
+ });
116
+
117
+ it("debug: no false-positive errors", () => {
118
+ const { scanned, violations } = scanPackage("debug");
119
+ expect(scanned).toBeGreaterThan(0);
120
+ expect(violations, violations.join("\n")).toEqual([]);
121
+ });
122
+
123
+ it("yocto-queue (class): no false-positive errors", () => {
124
+ const { scanned, violations } = scanPackage("yocto-queue");
125
+ expect(scanned).toBeGreaterThan(0);
126
+ expect(violations, violations.join("\n")).toEqual([]);
127
+ });
128
+
129
+ it("p-limit: no false-positive errors", () => {
130
+ const { scanned, violations } = scanPackage("p-limit");
131
+ expect(scanned).toBeGreaterThan(0);
132
+ expect(violations, violations.join("\n")).toEqual([]);
133
+ });
134
+
135
+ it("kleur: no false-positive errors", () => {
136
+ const { scanned, violations } = scanPackage("kleur");
137
+ expect(scanned).toBeGreaterThan(0);
138
+ expect(violations, violations.join("\n")).toEqual([]);
139
+ });
140
+
141
+ it("eventemitter3: no false-positive errors", () => {
142
+ const { scanned, violations } = scanPackage("eventemitter3");
143
+ expect(scanned).toBeGreaterThan(0);
144
+ expect(violations, violations.join("\n")).toEqual([]);
145
+ });
146
+
147
+ it.runIf(existsSync(join(root, "node_modules/ms")))(
148
+ "ms: no false-positive errors",
149
+ () => {
150
+ const { scanned, violations } = scanPackage("ms");
151
+ expect(scanned).toBeGreaterThan(0);
152
+ expect(violations, violations.join("\n")).toEqual([]);
153
+ },
154
+ );
155
+
156
+ it.runIf(existsSync(join(root, "node_modules/lodash")))(
157
+ "lodash: no false-positive errors",
158
+ () => {
159
+ const { scanned, violations } = scanPackage("lodash");
160
+ // lodash 体积大:至少扫到一批入口文件
161
+ expect(scanned).toBeGreaterThan(5);
162
+ expect(violations, violations.join("\n").slice(0, 2000)).toEqual([]);
163
+ },
164
+ );
165
+ });