@nudojs/parser 1.3.1 → 2.0.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/index.d.ts CHANGED
@@ -174,36 +174,49 @@ type DirectiveDiag = {
174
174
  code: string;
175
175
  message: string;
176
176
  };
177
- declare function setDirectiveDiagCollector(fn: ((d: DirectiveDiag) => void) | null): void;
178
- /** 当前诊断累计序号(since 锚:消费方只排干自身 extract 产生的增量) */
179
- declare function directiveDiagCount(): number;
180
- /** 取走已收集的诊断(全量排干 + 清空 seen)——CLI/测试整批消费 */
181
- declare function takeDirectiveDiags(): DirectiveDiag[];
182
177
  /**
183
- * 只取走 seq > since 的诊断(清空仅限增量)——对齐 takeInterfaceDiagsSince。
184
- * LSP 长驻进程里 analyze/check/lens 用它排干**自身 extract** 产生的诊断,
185
- * 不窃取在途其他消费方待收的指令文法诊断(全量 take 曾在 await 窗口偷走跨文件诊断)。
178
+ * 在指定诊断数组上执行 fn:fn 内所有指令文法诊断(含不传 diags 的 extract
179
+ * 与再解析路径,如 @nudo:mock 表达式的 parseCaseArgExpr 复解析)落袋并共用
180
+ * 同一去重域——用于「extract + 复解析」必须共享 seen 的窗口(nudojs check
181
+ * 的 D1 段)。fn 内显式传 diags 的 extract 自成新去重域。
186
182
  */
187
- declare function takeDirectiveDiagsSince(since: number): DirectiveDiag[];
183
+ declare function runWithDirectiveDiags<T>(diags: DirectiveDiag[], fn: () => T): T;
188
184
  /**
189
185
  * case 实参 / 指令类型表达式唯一文法:约束构建器优先,其余为具体字面量、
190
186
  * 结构字面量与箭头函数。`T.*` 文法已物理删除。
191
187
  * depth 按包含关系逐层 +1 线程化传递(兄弟共享父深度,不用共享可变计数器)。
192
188
  */
193
189
  declare function parseCaseArgExpr(expr: string, depth?: number): Abs;
190
+ /**
191
+ * extractDirectives 显式诊断通道 opts。
192
+ *
193
+ * `diags`:调用方自备的诊断累积数组——本次 extract 产生的指令文法诊断
194
+ * 全部同步落袋,单次调用内同文案去重。不传时为纯查询形态:诊断丢弃
195
+ * (等价 extractDirectivesQuiet);若外层有 runWithDirectiveDiags 活动域,
196
+ * 则落入该域并与其共享去重。
197
+ */
198
+ type ExtractDirectivesOpts = {
199
+ diags?: DirectiveDiag[];
200
+ };
194
201
  /**
195
202
  * D6=G2:指令绑定 AST 最近 Function(含 nested function、class method)。
196
203
  * 作用域清单与 refine 的 @nudo:contract 同源(core directive-scan),
197
204
  * case 与 contract 对同一函数集合同时可见。
198
205
  */
199
- declare function extractDirectives(ast: Node): FunctionWithDirectives[];
206
+ declare function extractDirectives(ast: Node, opts?: ExtractDirectivesOpts): FunctionWithDirectives[];
200
207
  /**
201
- * 纯查询 extract:排干自身产生的指令文法诊断增量并丢弃——
202
- * hover/completion/collectSkipReturns 等探测路径不得把诊断留在全局 buffer
203
- * 供在途 validate/check 误窃,也不得自己背走别人的在途诊断。
208
+ * 纯查询 extract:诊断丢弃——hover/completion/collectSkipReturns 等探测
209
+ * 路径不产诊断(不污染消费方待收的诊断,也不背走别人的在途诊断)。
210
+ * 需要诊断的调用方用 extractDirectives(ast, { diags })。
204
211
  */
205
212
  declare function extractDirectivesQuiet(ast: Node): FunctionWithDirectives[];
206
- declare function extractInlineDirectives(node: Node): InlineDirective[];
213
+ /**
214
+ * 行内指令(@nudo:as / @nudo:replace)抽取,源为节点的 leadingComments。
215
+ * 诊断通道与 extractDirectives 同形:传 opts.diags 落调用方数组(单次调用
216
+ * 内同文案去重,语句级调用各自独立去重);不传则落外层
217
+ * runWithDirectiveDiags 域(无则丢弃,纯查询形态)。
218
+ */
219
+ declare function extractInlineDirectives(node: Node, opts?: ExtractDirectivesOpts): InlineDirective[];
207
220
  declare function extractFileDirectives(ast: Node): FileDirective[];
208
221
 
209
- export { type AsDirective, type CaseDirective, type Directive, type DirectiveDiag, type EnvDirective, type FileDirective, type FunctionWithDirectives, type InlineDirective, type InstanceMethod, type MockDirective, type MockModuleDirective, type PureDirective, type ReplaceDirective, type SampleDirective, type SinonExpression, type SkipDirective, asAssignmentExpression, asExpression, asIdentifier, asMemberExpression, asProgram, classIdName, classInstanceMethods, classMemberKey, classMemberKeyName, directiveDiagCount, exportSpecifierExportedName, exportSpecifierLocalName, extractDirectives, extractDirectivesQuiet, extractFileDirectives, extractInlineDirectives, fnOrClassIdLoc, fnOrClassIdName, getClassMembers, getDeclarations, getDeclaratorId, getDeclaratorInit, getExportDeclaration, getExportSpecifiers, getExpressionStatementExpression, getFirstDeclaratorId, identifierName, isModuleExportsMember, memberObjectName, memberPropertyKey, memberPropertyName, methodFunctionNode, nameOrStringValue, paramDisplayName, paramName, parse, parseCaseArgExpr, programBody, setDirectiveDiagCollector, takeDirectiveDiags, takeDirectiveDiagsSince, unwrapDefaultExport, unwrapExport };
222
+ export { type AsDirective, type CaseDirective, type Directive, type DirectiveDiag, type EnvDirective, type ExtractDirectivesOpts, type FileDirective, type FunctionWithDirectives, type InlineDirective, type InstanceMethod, type MockDirective, type MockModuleDirective, type PureDirective, type ReplaceDirective, type SampleDirective, type SinonExpression, type SkipDirective, asAssignmentExpression, asExpression, asIdentifier, asMemberExpression, asProgram, classIdName, classInstanceMethods, classMemberKey, classMemberKeyName, exportSpecifierExportedName, exportSpecifierLocalName, extractDirectives, extractDirectivesQuiet, extractFileDirectives, extractInlineDirectives, fnOrClassIdLoc, fnOrClassIdName, getClassMembers, getDeclarations, getDeclaratorId, getDeclaratorInit, getExportDeclaration, getExportSpecifiers, getExpressionStatementExpression, getFirstDeclaratorId, identifierName, isModuleExportsMember, memberObjectName, memberPropertyKey, memberPropertyName, methodFunctionNode, nameOrStringValue, paramDisplayName, paramName, parse, parseCaseArgExpr, programBody, runWithDirectiveDiags, unwrapDefaultExport, unwrapExport };
package/dist/index.js CHANGED
@@ -181,7 +181,6 @@ import {
181
181
  spy,
182
182
  mock,
183
183
  abs as makeAbs,
184
- lit as termLit,
185
184
  numLit,
186
185
  strLit,
187
186
  boolLit,
@@ -200,70 +199,34 @@ import {
200
199
  scanCaseTags,
201
200
  extractBalancedParens
202
201
  } from "@nudojs/core";
202
+ import { absLit } from "@nudojs/core/internal";
203
203
  function absExact(shape) {
204
204
  return makeAbs(shape, void 0, void 0, "exact");
205
205
  }
206
206
  function absUnknown() {
207
207
  return makeAbs({ k: "unknown" }, void 0, void 0, "partial");
208
208
  }
209
- function absNullLit() {
210
- return makeAbs({ k: "unknown" }, termLit(null), void 0, "exact");
211
- }
212
- function absUndefLit() {
213
- return makeAbs({ k: "unknown" }, termLit(void 0), void 0, "exact");
214
- }
215
- function absLit(v) {
216
- if (typeof v === "number") return numLit(v);
217
- if (typeof v === "string") return strLit(v);
218
- if (typeof v === "boolean") return boolLit(v);
219
- if (v === null) return absNullLit();
220
- return absUndefLit();
221
- }
222
- var directiveDiagCollector = null;
223
- var directiveDiagSeq = 0;
224
- var directiveDiags = [];
225
- var MAX_DIRECTIVE_DIAGS = 1024;
226
- var directiveDiagSeen = /* @__PURE__ */ new Set();
227
- function setDirectiveDiagCollector(fn) {
228
- directiveDiagCollector = fn;
229
- }
230
- function directiveDiagCount() {
231
- return directiveDiagSeq;
232
- }
233
- function takeDirectiveDiags() {
234
- const out = directiveDiags.map((e) => e.d);
235
- directiveDiags.length = 0;
236
- directiveDiagSeen.clear();
237
- return out;
238
- }
239
- function takeDirectiveDiagsSince(since) {
240
- const out = [];
241
- let kept = 0;
242
- for (const e of directiveDiags) {
243
- if (e.seq > since) out.push(e.d);
244
- else directiveDiags[kept++] = e;
209
+ var activeDiagSink = null;
210
+ function runWithDirectiveDiags(diags, fn) {
211
+ const prev = activeDiagSink;
212
+ activeDiagSink = { list: diags, seen: /* @__PURE__ */ new Set() };
213
+ try {
214
+ return fn();
215
+ } finally {
216
+ activeDiagSink = prev;
245
217
  }
246
- directiveDiags.length = kept;
247
- return out;
248
218
  }
249
219
  function emitDirectiveDiag(d) {
220
+ if (!activeDiagSink) return;
250
221
  const key = `${d.code}\0${d.message}`;
251
- if (directiveDiagSeen.has(key)) return;
252
- directiveDiagSeen.add(key);
253
- if (directiveDiags.length >= MAX_DIRECTIVE_DIAGS) {
254
- directiveDiags.shift();
255
- directiveDiagSeen.clear();
256
- }
257
- directiveDiags.push({ seq: ++directiveDiagSeq, d });
258
- directiveDiagCollector?.(d);
259
- }
260
- function resetDirectiveDiagSeen() {
261
- directiveDiagSeen.clear();
222
+ if (activeDiagSink.seen.has(key)) return;
223
+ activeDiagSink.seen.add(key);
224
+ activeDiagSink.list.push(d);
262
225
  }
263
226
  var CASE_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:case\b[ \t]*([^\n]*)/g;
264
227
  var JS_IDENT_RE = /^[\p{L}$_][\p{L}\p{N}$_]*$/u;
265
228
  var MOCK_INLINE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]+([^\s=]+)[ \t]*=[ \t]*(.+)/g;
266
- var MOCK_FROM_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]+([^\s]+)[ \t]+from[ \t]+"([^"\n]+)"/g;
229
+ var MOCK_FROM_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]+([^\s]+)[ \t]+from[ \t]+(?:"([^"\n]+)"|'([^'\n]+)')/g;
267
230
  var MOCK_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]*([^\n]*)/g;
268
231
  var PURE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:pure\b/g;
269
232
  var SKIP_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:skip\b(?:[ \t]+(\S[^\n]*))?/g;
@@ -397,8 +360,8 @@ function constraintToCaseArgAbs(c, depth) {
397
360
  if (typeof lv === "number" && !Number.isNaN(lv)) return numLit(lv);
398
361
  if (typeof lv === "string") return strLit(lv);
399
362
  if (typeof lv === "boolean") return boolLit(lv);
400
- if (lv === null) return absNullLit();
401
- return absUndefLit();
363
+ if (lv === null) return absLit(null);
364
+ return absLit(void 0);
402
365
  }
403
366
  if (c.element) {
404
367
  return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element, depth + 1) });
@@ -845,7 +808,7 @@ function parseDirectivesFromCommentTexts(comments) {
845
808
  directives.push({
846
809
  kind: "mock",
847
810
  name,
848
- fromPath: mockFromMatch[2]
811
+ fromPath: mockFromMatch[2] ?? mockFromMatch[3]
849
812
  });
850
813
  mockFromRanges.push([mockFromMatch.index, mockFromMatch.index + mockFromMatch[0].length]);
851
814
  }
@@ -974,7 +937,7 @@ function scanMalformedMockNames(text, caseArgSpans) {
974
937
  if (!nameMatch) {
975
938
  emitDirectiveDiag({
976
939
  code: "nudo:directive-syntax",
977
- message: `Malformed @nudo:mock: cannot parse name (expected @nudo:mock <ident> = <expr> or @nudo:mock <ident> from "path") \u2014 got: ${rest.slice(0, 60)}`
940
+ message: `Malformed @nudo:mock: cannot parse name (expected @nudo:mock <ident> = <expr> or @nudo:mock <ident> from "path" / 'path') \u2014 got: ${rest.slice(0, 60)}`
978
941
  });
979
942
  continue;
980
943
  }
@@ -987,16 +950,25 @@ function scanMalformedMockNames(text, caseArgSpans) {
987
950
  continue;
988
951
  }
989
952
  const afterName = rest.slice(name.length);
990
- if (/^[ \t]+from[ \t]+"[^"\n]*$/.test(afterName)) {
953
+ if (/^[ \t]+from[ \t]+(?:"[^"\n]*|'[^'\n]*)$/.test(afterName)) {
991
954
  emitDirectiveDiag({
992
955
  code: "nudo:directive-syntax",
993
- message: `Malformed @nudo:mock from: unclosed quote in path (expected @nudo:mock ${name} from "path") \u2014 got: @nudo:mock ${rest.slice(0, 60)}`
956
+ message: `Malformed @nudo:mock from: unclosed quote in path (expected @nudo:mock ${name} from "path" / 'path') \u2014 got: @nudo:mock ${rest.slice(0, 60)}`
994
957
  });
995
958
  }
996
959
  }
997
960
  }
998
- function extractDirectives(ast) {
999
- resetDirectiveDiagSeen();
961
+ function extractDirectives(ast, opts) {
962
+ if (!opts?.diags) return collectFnDirectives(ast);
963
+ const prev = activeDiagSink;
964
+ activeDiagSink = { list: opts.diags, seen: /* @__PURE__ */ new Set() };
965
+ try {
966
+ return collectFnDirectives(ast);
967
+ } finally {
968
+ activeDiagSink = prev;
969
+ }
970
+ }
971
+ function collectFnDirectives(ast) {
1000
972
  const results = [];
1001
973
  if (ast.type !== "File") return results;
1002
974
  const scopes = listFnDirectiveScopes(ast);
@@ -1013,10 +985,7 @@ function extractDirectives(ast) {
1013
985
  return results;
1014
986
  }
1015
987
  function extractDirectivesQuiet(ast) {
1016
- const since = directiveDiagCount();
1017
- const out = extractDirectives(ast);
1018
- takeDirectiveDiagsSince(since);
1019
- return out;
988
+ return extractDirectives(ast, { diags: [] });
1020
989
  }
1021
990
  function stripInlineCommentPrefix(line, kind) {
1022
991
  const s = kind === "block" ? line.replace(/^\s*\*\s?/, "") : line.replace(/^\//, "");
@@ -1044,10 +1013,19 @@ function findTrailingCommentStart(expr) {
1044
1013
  }
1045
1014
  return -1;
1046
1015
  }
1047
- function extractInlineDirectives(node) {
1048
- resetDirectiveDiagSeen();
1016
+ function extractInlineDirectives(node, opts) {
1049
1017
  const comments = node.leadingComments;
1050
1018
  if (!comments) return [];
1019
+ if (!opts?.diags) return collectInlineDirectivesFromComments(comments);
1020
+ const prev = activeDiagSink;
1021
+ activeDiagSink = { list: opts.diags, seen: /* @__PURE__ */ new Set() };
1022
+ try {
1023
+ return collectInlineDirectivesFromComments(comments);
1024
+ } finally {
1025
+ activeDiagSink = prev;
1026
+ }
1027
+ }
1028
+ function collectInlineDirectivesFromComments(comments) {
1051
1029
  const results = [];
1052
1030
  for (const comment of comments) {
1053
1031
  if (comment.type !== "CommentLine" && comment.type !== "CommentBlock") continue;
@@ -1194,7 +1172,6 @@ export {
1194
1172
  classInstanceMethods,
1195
1173
  classMemberKey,
1196
1174
  classMemberKeyName,
1197
- directiveDiagCount,
1198
1175
  exportSpecifierExportedName,
1199
1176
  exportSpecifierLocalName,
1200
1177
  extractDirectives,
@@ -1232,14 +1209,12 @@ export {
1232
1209
  parseMockModulePayload2 as parseMockModulePayload,
1233
1210
  parseNudoImportPayload,
1234
1211
  programBody,
1212
+ runWithDirectiveDiags,
1235
1213
  scanBudgetDecl,
1236
1214
  scanCaseTags2 as scanCaseTags,
1237
1215
  scanContractSegments,
1238
1216
  scanThrowsDecl,
1239
- setDirectiveDiagCollector,
1240
1217
  stripTypes,
1241
- takeDirectiveDiags,
1242
- takeDirectiveDiagsSince,
1243
1218
  unwrapDefaultExport,
1244
1219
  unwrapExport
1245
1220
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/parser",
3
- "version": "1.3.1",
3
+ "version": "2.0.0",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -39,7 +39,7 @@
39
39
  "registry": "https://registry.npmjs.org"
40
40
  },
41
41
  "dependencies": {
42
- "@nudojs/core": "1.7.0",
42
+ "@nudojs/core": "1.7.2",
43
43
  "@babel/types": "^8.0.6"
44
44
  },
45
45
  "scripts": {