@nudojs/cli 0.1.0 → 0.2.1

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 (65) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +34 -0
  4. package/dist/chunk-3HCPXPEW.js +5286 -0
  5. package/dist/evaluator-api.d.ts +96 -2
  6. package/dist/evaluator-api.js +191 -3
  7. package/dist/index.js +595 -120
  8. package/examples/real-world.js +66 -0
  9. package/examples/validate-real-world.ts +160 -0
  10. package/package.json +28 -3
  11. package/src/__tests__/builtin-advanced.test.ts +292 -0
  12. package/src/__tests__/builtin-api.test.ts +308 -0
  13. package/src/__tests__/builtin-functions.test.ts +313 -0
  14. package/src/__tests__/builtin-map.test.ts +129 -0
  15. package/src/__tests__/builtin-promise.test.ts +150 -0
  16. package/src/__tests__/builtin-regexp.test.ts +133 -0
  17. package/src/__tests__/builtin-set.test.ts +163 -0
  18. package/src/__tests__/builtin-url.test.ts +256 -0
  19. package/src/__tests__/builtin-web.test.ts +340 -0
  20. package/src/__tests__/combination-scenarios.test.ts +384 -0
  21. package/src/__tests__/edge-cases.test.ts +749 -0
  22. package/src/__tests__/evaluator-loops.test.ts +283 -0
  23. package/src/__tests__/evaluator-nullish-coalescing.test.ts +86 -0
  24. package/src/__tests__/evaluator-optional-chaining.test.ts +37 -0
  25. package/src/__tests__/evaluator-phase3.test.ts +353 -2
  26. package/src/__tests__/evaluator-refined.test.ts +121 -0
  27. package/src/__tests__/evaluator-string-methods.test.ts +151 -0
  28. package/src/__tests__/evaluator.test.ts +702 -2
  29. package/src/__tests__/mock-arrow-function.test.ts +169 -0
  30. package/src/__tests__/mock-comparison.test.ts +221 -0
  31. package/src/__tests__/mock-comprehensive.test.ts +250 -0
  32. package/src/__tests__/mock-final-integration.test.ts +263 -0
  33. package/src/__tests__/mock-helpers.test.ts +174 -0
  34. package/src/__tests__/mock-realworld.test.ts +191 -0
  35. package/src/__tests__/mock-rest-debug.test.ts +110 -0
  36. package/src/__tests__/mock-rest-edge.test.ts +176 -0
  37. package/src/__tests__/mock-sinon-runtime.test.ts +168 -0
  38. package/src/__tests__/narrowing-discriminated.test.ts +35 -0
  39. package/src/__tests__/narrowing-in-operator.test.ts +56 -0
  40. package/src/__tests__/narrowing-switch.test.ts +56 -0
  41. package/src/__tests__/narrowing-truthiness.test.ts +52 -0
  42. package/src/__tests__/nudo-mock-syntax.test.ts +245 -0
  43. package/src/__tests__/realworld-patterns.test.ts +430 -0
  44. package/src/__tests__/syntax-sugar.test.ts +345 -0
  45. package/src/builtins/builtin-intl.ts +17 -0
  46. package/src/builtins/builtin-map.ts +174 -0
  47. package/src/builtins/builtin-promise.ts +116 -0
  48. package/src/builtins/builtin-reflect.ts +30 -0
  49. package/src/builtins/builtin-regexp.ts +34 -0
  50. package/src/builtins/builtin-set.ts +131 -0
  51. package/src/builtins/builtin-symbol.ts +16 -0
  52. package/src/builtins/builtin-url.ts +58 -0
  53. package/src/builtins/builtin-weak.ts +22 -0
  54. package/src/builtins/builtin-web.ts +111 -0
  55. package/src/builtins/index.ts +61 -0
  56. package/src/config.ts +32 -0
  57. package/src/env-loader.ts +174 -0
  58. package/src/evaluator-api.ts +22 -0
  59. package/src/evaluator.ts +4218 -171
  60. package/src/index.ts +776 -125
  61. package/src/narrowing.ts +286 -15
  62. package/src/resolve-npm.ts +63 -0
  63. package/dist/chunk-JW3RMCNY.js +0 -1467
  64. package/dist/chunk-RBHWWZII.js +0 -1467
  65. package/dist/chunk-VN5HMBGB.js +0 -1467
@@ -1,6 +1,10 @@
1
1
  import { Node } from '@babel/types';
2
- import { Environment, TypeValue } from '@nudojs/core';
2
+ import { TypeValue, Environment } from '@nudojs/core';
3
3
 
4
+ /** 值上命名成员是否可能存在(union 部分成员持有的 no-method 降 warning
5
+ * 用):primitive/literal 走 wrapper 原型近似表('x'.charCodeAt),
6
+ * instance 走近似方法表。诊断侧据此区分"部分成员可能有"与"确定没有"。 */
7
+ declare function memberMayExistOn(tv: TypeValue, propName: string): boolean;
4
8
  type SourceRange = {
5
9
  start: {
6
10
  line: number;
@@ -11,6 +15,7 @@ type SourceRange = {
11
15
  column: number;
12
16
  };
13
17
  };
18
+ declare function setCurrentSource(source: string): void;
14
19
  declare const RETURN_SIGNAL: unique symbol;
15
20
  declare const BRANCH_SIGNAL: unique symbol;
16
21
  declare const THROW_SIGNAL: unique symbol;
@@ -33,8 +38,79 @@ declare function resetMemo(): void;
33
38
  declare function setModuleResolver(resolver: ((source: string, fromDir: string) => {
34
39
  ast: Node;
35
40
  filePath: string;
41
+ json?: unknown;
36
42
  } | null) | null): void;
43
+ declare function setEnvModules(modules: Record<string, Record<string, TypeValue>>): void;
44
+ declare function resetEnvModules(): void;
45
+ declare function setMockModules(mocks: Map<string, {
46
+ fromPath: string;
47
+ names?: string[];
48
+ }>): void;
49
+ declare function resetMockModules(): void;
50
+ declare function setUnknownBuiltinHandler(handler: ((name: string, loc?: {
51
+ start: {
52
+ line: number;
53
+ column: number;
54
+ };
55
+ end: {
56
+ line: number;
57
+ column: number;
58
+ };
59
+ }) => void) | null): void;
60
+ declare function setSampleCount(count: number): void;
61
+ declare function setMaxConcreteIter(count: number): void;
37
62
  declare function setNodeTypeCollector(collector: ((node: Node, tv: TypeValue) => void) | null): void;
63
+ type CallRecord = {
64
+ fnName: string;
65
+ argTypes: TypeValue[];
66
+ resultType: TypeValue;
67
+ throws: TypeValue;
68
+ callLoc?: {
69
+ line: number;
70
+ column: number;
71
+ };
72
+ targetModule?: string;
73
+ targetExport?: string;
74
+ /** export names the same function value was re-exported under after its
75
+ * defining module (barrel `index.js`, CJS forwarding shims); usage-site
76
+ * records stay name-matchable against them */
77
+ targetAliases?: string[];
78
+ /** module whose evaluation created the function value (definition site).
79
+ * Usage-site collection transitively evaluates the library under require(),
80
+ * so records of the library's INTERNAL calls (no export tag) can still be
81
+ * attributed to the defining file — while test-local functions and builtin
82
+ * statics stay unattributed and cannot pollute same-name candidates. */
83
+ fnModule?: string;
84
+ };
85
+ /** usage-site 执行标记:外部调用记录的实参里携带的闭包(测试回调等)在
86
+ * case 合成重求值时被执行,其内部错误属于使用现场文件,与本文件分析无关。
87
+ * 分析侧在注入前对全部外部实参调用本函数;执行来源栈读到该标记时
88
+ * recordUnknown.originModule 记为标记值,分析侧据此丢弃。不按模块路径
89
+ * 比较——跨文件依赖(本文件 require 链上加载的模块)的执行记录是合法
90
+ * 的可达分析,不能误伤。 */
91
+ declare const USAGE_SITE_MODULE = "__usage_site__";
92
+ declare function setUsageSiteTag(tv: TypeValue, _seen?: Set<object>): void;
93
+ declare function setCallCollector(collector: ((record: CallRecord) => void) | null): void;
94
+ type UnknownRecord = {
95
+ kind: "method" | "property" | "global";
96
+ name: string;
97
+ receiverType?: TypeValue;
98
+ loc?: {
99
+ line: number;
100
+ column: number;
101
+ };
102
+ reason?: string;
103
+ origin?: {
104
+ line: number;
105
+ column: number;
106
+ };
107
+ /** 定义当前正在执行的函数的模块(执行来源栈顶)。case 合成重求值会执行
108
+ * 注入实参携带的使用现场闭包体,其内部错误记录属于使用现场文件——
109
+ * 分析侧据此丢弃(行数守卫会被同行号假阳性骗过)。 */
110
+ originModule?: string;
111
+ };
112
+ declare function setUnknownCollector(collector: ((record: UnknownRecord) => void) | null): void;
113
+ declare function setProvenanceTracking(enabled: boolean): void;
38
114
  declare function evaluate(node: Node, env: Environment): EvalResult;
39
115
  type CallResult = {
40
116
  value: TypeValue;
@@ -54,4 +130,22 @@ declare function setCurrentFileDir(dir: string): void;
54
130
  */
55
131
  declare function narrow(test: Node, env: Environment): [trueEnv: Environment, falseEnv: Environment];
56
132
 
57
- export { evaluate, evaluateFunction, evaluateFunctionFull, evaluateProgram, getUnreachableRanges, narrow, resetMemo, resetUnreachableRanges, setCurrentFileDir, setModuleResolver, setNodeTypeCollector };
133
+ type LoadedEnv = {
134
+ modules: Record<string, Record<string, TypeValue>>;
135
+ };
136
+ declare function preloadPathEnvs(envNames: string[], baseDir: string): Promise<void>;
137
+ declare function loadEnvsAsync(envNames: string[], globalEnv: Environment, baseDir?: string): Promise<LoadedEnv>;
138
+ declare function loadEnvs(envNames: string[], globalEnv: Environment): LoadedEnv;
139
+
140
+ type NudoConfig = {
141
+ env?: string[];
142
+ mocks?: Record<string, string>;
143
+ };
144
+ declare function findProjectConfig(startDir: string): {
145
+ config: NudoConfig;
146
+ projectDir: string;
147
+ } | null;
148
+
149
+ declare function resolveNpmNudo(source: string, fromDir: string): string | null;
150
+
151
+ export { type CallRecord, type LoadedEnv, type NudoConfig, USAGE_SITE_MODULE, type UnknownRecord, evaluate, evaluateFunction, evaluateFunctionFull, evaluateProgram, findProjectConfig, getUnreachableRanges, loadEnvs, loadEnvsAsync, memberMayExistOn, narrow, preloadPathEnvs, resetEnvModules, resetMemo, resetMockModules, resetUnreachableRanges, resolveNpmNudo, setCallCollector, setCurrentFileDir, setCurrentSource, setEnvModules, setMaxConcreteIter, setMockModules, setModuleResolver, setNodeTypeCollector, setProvenanceTracking, setSampleCount, setUnknownBuiltinHandler, setUnknownCollector, setUsageSiteTag };
@@ -1,26 +1,214 @@
1
1
  import {
2
+ USAGE_SITE_MODULE,
2
3
  evaluate,
3
4
  evaluateFunction,
4
5
  evaluateFunctionFull,
5
6
  evaluateProgram,
6
7
  getUnreachableRanges,
8
+ memberMayExistOn,
7
9
  narrow,
10
+ resetEnvModules,
8
11
  resetMemo,
12
+ resetMockModules,
9
13
  resetUnreachableRanges,
14
+ resolveNpmNudo,
15
+ setCallCollector,
10
16
  setCurrentFileDir,
17
+ setCurrentSource,
18
+ setEnvModules,
19
+ setMaxConcreteIter,
20
+ setMockModules,
11
21
  setModuleResolver,
12
- setNodeTypeCollector
13
- } from "./chunk-JW3RMCNY.js";
22
+ setNodeTypeCollector,
23
+ setProvenanceTracking,
24
+ setSampleCount,
25
+ setUnknownBuiltinHandler,
26
+ setUnknownCollector,
27
+ setUsageSiteTag
28
+ } from "./chunk-3HCPXPEW.js";
29
+
30
+ // src/env-loader.ts
31
+ import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync } from "fs";
32
+ import { resolve as resolvePath, join as joinPath } from "path";
33
+ import { pathToFileURL } from "url";
34
+ import { createRequire } from "module";
35
+ import { createHash } from "crypto";
36
+ import { tmpdir } from "os";
37
+ import { defineEnv as defineEsEnv } from "@nudojs/env/es";
38
+ import { defineEnv as defineWebEnv } from "@nudojs/env/web";
39
+ import { defineEnv as defineNodeEnv } from "@nudojs/env/node";
40
+ var envFactories = {
41
+ es: defineEsEnv,
42
+ web: defineWebEnv,
43
+ node: defineNodeEnv
44
+ };
45
+ var impliedDeps = {
46
+ web: ["es"],
47
+ node: ["es"]
48
+ };
49
+ function resolveEnvNames(names) {
50
+ const resolved = /* @__PURE__ */ new Set();
51
+ const visit = (name) => {
52
+ if (resolved.has(name)) return;
53
+ const deps = impliedDeps[name];
54
+ if (deps) deps.forEach(visit);
55
+ resolved.add(name);
56
+ };
57
+ names.forEach(visit);
58
+ return [...resolved];
59
+ }
60
+ var pathEnvCache = /* @__PURE__ */ new Map();
61
+ var pathEnvByPath = /* @__PURE__ */ new Map();
62
+ var pathEnvBaseDirs = /* @__PURE__ */ new Set();
63
+ function isPathEnvName(name, baseDir) {
64
+ if (name in envFactories) return false;
65
+ if (name.includes("/") || name.startsWith("./") || name.startsWith("../")) return true;
66
+ const resolved = resolvePath(baseDir, name);
67
+ return resolved.endsWith(".ts") && existsSync(resolved);
68
+ }
69
+ function rewriteBareImports(text) {
70
+ const require2 = createRequire(import.meta.url);
71
+ let rewrote = false;
72
+ const out = text.replace(/(["'])(@nudojs\/[a-z0-9-]+)\1/g, (_m, quote, spec) => {
73
+ try {
74
+ const entry = require2.resolve(spec);
75
+ const url = pathToFileURL(entry).href;
76
+ rewrote = true;
77
+ return `${quote}${url}${quote}`;
78
+ } catch {
79
+ return `${quote}${spec}${quote}`;
80
+ }
81
+ });
82
+ return rewrote ? out : null;
83
+ }
84
+ async function importPathEnv(resolvedPath, mtimeMs) {
85
+ const cacheKey = `${resolvedPath}:${mtimeMs}`;
86
+ if (pathEnvCache.has(cacheKey)) return;
87
+ let mod = null;
88
+ try {
89
+ const url = pathToFileURL(resolvedPath).href + `?mtime=${mtimeMs}`;
90
+ mod = await import(url);
91
+ } catch {
92
+ mod = null;
93
+ }
94
+ if (!mod) {
95
+ try {
96
+ const text = readFileSync(resolvedPath, "utf-8");
97
+ const rewritten = rewriteBareImports(text);
98
+ if (rewritten === null) return;
99
+ const cacheDir = joinPath(tmpdir(), "nudo-env");
100
+ mkdirSync(cacheDir, { recursive: true });
101
+ const hash = createHash("md5").update(`${resolvedPath}:${mtimeMs}`).digest("hex").slice(0, 16);
102
+ const copyPath = joinPath(cacheDir, `${hash}.ts`);
103
+ writeFileSync(copyPath, rewritten, "utf-8");
104
+ mod = await import(pathToFileURL(copyPath).href);
105
+ } catch {
106
+ return;
107
+ }
108
+ }
109
+ if (mod && typeof mod.defineEnv === "function") {
110
+ pathEnvCache.set(cacheKey, mod.defineEnv);
111
+ pathEnvByPath.set(resolvedPath, mod.defineEnv);
112
+ }
113
+ }
114
+ function lookupPathEnv(name) {
115
+ if (pathEnvByPath.size === 0) return void 0;
116
+ const candidates = name.startsWith("/") ? [name] : [name, ...[...pathEnvBaseDirs].map((d) => resolvePath(d, name))];
117
+ for (const candidate of candidates) {
118
+ const hit = pathEnvByPath.get(candidate);
119
+ if (hit) return hit;
120
+ }
121
+ return void 0;
122
+ }
123
+ async function preloadPathEnvs(envNames, baseDir) {
124
+ pathEnvBaseDirs.add(baseDir);
125
+ for (const name of envNames) {
126
+ if (!isPathEnvName(name, baseDir)) continue;
127
+ const resolved = resolvePath(baseDir, name);
128
+ if (!existsSync(resolved)) continue;
129
+ try {
130
+ const { mtimeMs } = statSync(resolved);
131
+ await importPathEnv(resolved, mtimeMs);
132
+ } catch {
133
+ }
134
+ }
135
+ }
136
+ async function loadEnvsAsync(envNames, globalEnv, baseDir = process.cwd()) {
137
+ await preloadPathEnvs(envNames, baseDir);
138
+ return loadEnvs(envNames, globalEnv);
139
+ }
140
+ function loadEnvs(envNames, globalEnv) {
141
+ const allModules = {};
142
+ const resolved = resolveEnvNames(envNames);
143
+ for (const name of resolved) {
144
+ const factory = envFactories[name] ?? lookupPathEnv(name);
145
+ if (!factory) continue;
146
+ const def = factory();
147
+ for (const [key, value] of Object.entries(def.globals)) {
148
+ globalEnv.bind(key, value);
149
+ }
150
+ if (def.modules) {
151
+ for (const [modName, exports] of Object.entries(def.modules)) {
152
+ allModules[modName] = { ...allModules[modName], ...exports };
153
+ }
154
+ }
155
+ }
156
+ return { modules: allModules };
157
+ }
158
+
159
+ // src/config.ts
160
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
161
+ import { resolve, dirname } from "path";
162
+ function findProjectConfig(startDir) {
163
+ let dir = resolve(startDir);
164
+ const root = resolve("/");
165
+ while (dir !== root) {
166
+ const pkgPath = resolve(dir, "package.json");
167
+ if (existsSync2(pkgPath)) {
168
+ try {
169
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
170
+ if (pkg.nudo) {
171
+ return { config: pkg.nudo, projectDir: dir };
172
+ }
173
+ } catch {
174
+ }
175
+ return null;
176
+ }
177
+ const parent = dirname(dir);
178
+ if (parent === dir) break;
179
+ dir = parent;
180
+ }
181
+ return null;
182
+ }
14
183
  export {
184
+ USAGE_SITE_MODULE,
15
185
  evaluate,
16
186
  evaluateFunction,
17
187
  evaluateFunctionFull,
18
188
  evaluateProgram,
189
+ findProjectConfig,
19
190
  getUnreachableRanges,
191
+ loadEnvs,
192
+ loadEnvsAsync,
193
+ memberMayExistOn,
20
194
  narrow,
195
+ preloadPathEnvs,
196
+ resetEnvModules,
21
197
  resetMemo,
198
+ resetMockModules,
22
199
  resetUnreachableRanges,
200
+ resolveNpmNudo,
201
+ setCallCollector,
23
202
  setCurrentFileDir,
203
+ setCurrentSource,
204
+ setEnvModules,
205
+ setMaxConcreteIter,
206
+ setMockModules,
24
207
  setModuleResolver,
25
- setNodeTypeCollector
208
+ setNodeTypeCollector,
209
+ setProvenanceTracking,
210
+ setSampleCount,
211
+ setUnknownBuiltinHandler,
212
+ setUnknownCollector,
213
+ setUsageSiteTag
26
214
  };