@nudojs/parser 1.2.1 → 1.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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +214 -54
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -188,8 +188,9 @@ declare function takeDirectiveDiagsSince(since: number): DirectiveDiag[];
|
|
|
188
188
|
/**
|
|
189
189
|
* case 实参 / 指令类型表达式唯一文法:约束构建器优先,其余为具体字面量、
|
|
190
190
|
* 结构字面量与箭头函数。`T.*` 文法已物理删除。
|
|
191
|
+
* depth 按包含关系逐层 +1 线程化传递(兄弟共享父深度,不用共享可变计数器)。
|
|
191
192
|
*/
|
|
192
|
-
declare function parseCaseArgExpr(expr: string): Abs;
|
|
193
|
+
declare function parseCaseArgExpr(expr: string, depth?: number): Abs;
|
|
193
194
|
/**
|
|
194
195
|
* D6=G2:指令绑定 AST 最近 Function(含 nested function、class method)。
|
|
195
196
|
* 作用域清单与 refine 的 @nudo:contract 同源(core directive-scan),
|
package/dist/index.js
CHANGED
|
@@ -170,6 +170,7 @@ function paramName(param) {
|
|
|
170
170
|
return void 0;
|
|
171
171
|
}
|
|
172
172
|
function unwrapDefaultExport(mod) {
|
|
173
|
+
if (mod == null) return void 0;
|
|
173
174
|
if (typeof mod === "function") return mod;
|
|
174
175
|
return mod.default;
|
|
175
176
|
}
|
|
@@ -189,6 +190,7 @@ import {
|
|
|
189
190
|
execNudoModule,
|
|
190
191
|
isNudoConstraint,
|
|
191
192
|
constraintToEntryAbs,
|
|
193
|
+
CONSTRAINT_BUILDER_NAMES,
|
|
192
194
|
CONSTRAINT_EXPR_RE,
|
|
193
195
|
SELF,
|
|
194
196
|
listFnDirectiveScopes,
|
|
@@ -258,18 +260,94 @@ function emitDirectiveDiag(d) {
|
|
|
258
260
|
function resetDirectiveDiagSeen() {
|
|
259
261
|
directiveDiagSeen.clear();
|
|
260
262
|
}
|
|
261
|
-
var CASE_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:case\
|
|
263
|
+
var CASE_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:case\b[ \t]*([^\n]*)/g;
|
|
262
264
|
var JS_IDENT_RE = /^[\p{L}$_][\p{L}\p{N}$_]*$/u;
|
|
263
|
-
var MOCK_INLINE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:mock\
|
|
264
|
-
var MOCK_FROM_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:mock\
|
|
265
|
-
var MOCK_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:mock\
|
|
266
|
-
var PURE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:pure\b/g;
|
|
267
|
-
var SKIP_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:skip\b(?:[ \t]+(\S[^\n]*))?/g;
|
|
268
|
-
var SAMPLE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*)?@nudo:sample\b(?:[ \t]+(\S+))?/g;
|
|
265
|
+
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;
|
|
267
|
+
var MOCK_TAG_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:mock\b[ \t]*([^\n]*)/g;
|
|
268
|
+
var PURE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:pure\b/g;
|
|
269
|
+
var SKIP_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:skip\b(?:[ \t]+(\S[^\n]*))?/g;
|
|
270
|
+
var SAMPLE_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/[ \t]*)?@nudo:sample\b(?:[ \t]+(\S+))?/g;
|
|
269
271
|
var SAMPLE_NUM_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
272
|
+
var TYPE_EXPR_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
273
|
+
"gt",
|
|
274
|
+
"ge",
|
|
275
|
+
"lt",
|
|
276
|
+
"le",
|
|
277
|
+
"int",
|
|
278
|
+
"min",
|
|
279
|
+
"max",
|
|
280
|
+
"length",
|
|
281
|
+
"shift",
|
|
282
|
+
"optional"
|
|
283
|
+
]);
|
|
284
|
+
function isSafeTypeExprNode(node) {
|
|
285
|
+
switch (node.type) {
|
|
286
|
+
case "NumericLiteral":
|
|
287
|
+
case "StringLiteral":
|
|
288
|
+
case "BooleanLiteral":
|
|
289
|
+
case "NullLiteral":
|
|
290
|
+
return true;
|
|
291
|
+
case "Identifier":
|
|
292
|
+
return node.name === "undefined";
|
|
293
|
+
case "UnaryExpression":
|
|
294
|
+
return node.operator === "-" && node.argument.type === "NumericLiteral";
|
|
295
|
+
case "ArrayExpression":
|
|
296
|
+
return node.elements.every(
|
|
297
|
+
(el) => el !== null && el.type !== "SpreadElement" && isSafeTypeExprNode(el)
|
|
298
|
+
);
|
|
299
|
+
case "ObjectExpression":
|
|
300
|
+
return node.properties.every((prop) => {
|
|
301
|
+
if (prop.type !== "ObjectProperty") return false;
|
|
302
|
+
if (prop.computed) return false;
|
|
303
|
+
const key = prop.key;
|
|
304
|
+
if (key.type !== "Identifier" && key.type !== "StringLiteral") return false;
|
|
305
|
+
return isSafeTypeExprNode(prop.value);
|
|
306
|
+
});
|
|
307
|
+
case "CallExpression": {
|
|
308
|
+
const argsOk = node.arguments.every(
|
|
309
|
+
(arg) => arg.type !== "SpreadElement" && arg.type !== "ArgumentPlaceholder" && isSafeTypeExprNode(arg)
|
|
310
|
+
);
|
|
311
|
+
if (!argsOk) return false;
|
|
312
|
+
return isSafeTypeExprCallee(node.callee);
|
|
313
|
+
}
|
|
314
|
+
default:
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function isSafeTypeExprCallee(node) {
|
|
319
|
+
if (node.type === "Identifier") {
|
|
320
|
+
return CONSTRAINT_BUILDER_NAMES.includes(node.name);
|
|
321
|
+
}
|
|
322
|
+
if (node.type === "MemberExpression") {
|
|
323
|
+
if (node.computed) return false;
|
|
324
|
+
if (node.property.type !== "Identifier") return false;
|
|
325
|
+
if (!TYPE_EXPR_CHAIN_METHODS.has(node.property.name)) return false;
|
|
326
|
+
return isSafeTypeExprNode(node.object);
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
function isSafeTypeExprSource(s) {
|
|
331
|
+
try {
|
|
332
|
+
const ast = parse(`(${s});`);
|
|
333
|
+
if (ast.program.body.length !== 1) return false;
|
|
334
|
+
const stmt = ast.program.body[0];
|
|
335
|
+
if (stmt.type !== "ExpressionStatement") return false;
|
|
336
|
+
return isSafeTypeExprNode(stmt.expression);
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
270
341
|
function tryParseConstraint(expr) {
|
|
271
342
|
const s = expr.trim();
|
|
272
343
|
if (!CONSTRAINT_EXPR_RE.test(s)) return void 0;
|
|
344
|
+
if (!isSafeTypeExprSource(s)) {
|
|
345
|
+
emitDirectiveDiag({
|
|
346
|
+
code: "nudo:directive-syntax",
|
|
347
|
+
message: `Unsafe constraint expression rejected: ${s.slice(0, 80)} (only builder calls, chain methods, and literals are allowed)`
|
|
348
|
+
});
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
273
351
|
try {
|
|
274
352
|
const src = `export const __nudo_case_arg = (${s});`;
|
|
275
353
|
const exports = execNudoModule(src);
|
|
@@ -305,9 +383,10 @@ function constraintSelfEqLit(c, selfIds) {
|
|
|
305
383
|
if (isSelf(b) && a.op === "lit") return { found: true, value: a.value };
|
|
306
384
|
return { found: false };
|
|
307
385
|
}
|
|
308
|
-
function constraintToCaseArgAbs(c) {
|
|
386
|
+
function constraintToCaseArgAbs(c, depth) {
|
|
387
|
+
if (depth > MAX_CASE_ARG_DEPTH) return depthCapDiag(constraintPreview(c));
|
|
309
388
|
if (c.members && c.members.length > 0) {
|
|
310
|
-
const parts = c.members.map((m) => constraintToCaseArgAbs(m));
|
|
389
|
+
const parts = c.members.map((m) => constraintToCaseArgAbs(m, depth + 1));
|
|
311
390
|
if (parts.length === 1) return parts[0];
|
|
312
391
|
return { shape: { k: "sum", members: parts }, conf: "path" };
|
|
313
392
|
}
|
|
@@ -322,12 +401,12 @@ function constraintToCaseArgAbs(c) {
|
|
|
322
401
|
return absUndefLit();
|
|
323
402
|
}
|
|
324
403
|
if (c.element) {
|
|
325
|
-
return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element) });
|
|
404
|
+
return absExact({ k: "arr", element: constraintToCaseArgAbs(c.element, depth + 1) });
|
|
326
405
|
}
|
|
327
406
|
if (c.fields) {
|
|
328
407
|
const slots = {};
|
|
329
408
|
for (const [key, field] of Object.entries(c.fields)) {
|
|
330
|
-
slots[key] = { value: constraintToCaseArgAbs(field.constraint) };
|
|
409
|
+
slots[key] = { value: constraintToCaseArgAbs(field.constraint, depth + 1) };
|
|
331
410
|
}
|
|
332
411
|
return absExact({ k: "obj", slots });
|
|
333
412
|
}
|
|
@@ -340,18 +419,34 @@ function constraintToCaseArgAbs(c) {
|
|
|
340
419
|
}
|
|
341
420
|
return constraintToEntryAbs(c, "__arg");
|
|
342
421
|
}
|
|
343
|
-
|
|
422
|
+
var MAX_CASE_ARG_DEPTH = 32;
|
|
423
|
+
function depthCapDiag(expr) {
|
|
424
|
+
emitDirectiveDiag({
|
|
425
|
+
code: "nudo:directive-syntax",
|
|
426
|
+
message: `Type expression nesting exceeds depth ${MAX_CASE_ARG_DEPTH}: ${expr.trim().slice(0, 40)} \u2026 (deeper structure collapses to unknown)`
|
|
427
|
+
});
|
|
428
|
+
return absUnknown();
|
|
429
|
+
}
|
|
430
|
+
function constraintPreview(c) {
|
|
431
|
+
if (c.element) return "array(\u2026)";
|
|
432
|
+
if (c.members) return "union(\u2026)";
|
|
433
|
+
if (c.fields) return "shape({ \u2026 })";
|
|
434
|
+
if (c.fn) return "fn(\u2026)";
|
|
435
|
+
return c.prim ? `${c.prim}()` : "constraint";
|
|
436
|
+
}
|
|
437
|
+
function parseCaseArgExpr(expr, depth = 0) {
|
|
438
|
+
if (depth > MAX_CASE_ARG_DEPTH) return depthCapDiag(expr);
|
|
344
439
|
const constraint = tryParseConstraint(expr);
|
|
345
440
|
if (constraint) {
|
|
346
441
|
try {
|
|
347
|
-
return constraintToCaseArgAbs(constraint);
|
|
442
|
+
return constraintToCaseArgAbs(constraint, depth);
|
|
348
443
|
} catch {
|
|
349
444
|
return absUnknown();
|
|
350
445
|
}
|
|
351
446
|
}
|
|
352
|
-
return parseLiteralOrStructure(expr);
|
|
447
|
+
return parseLiteralOrStructure(expr, depth);
|
|
353
448
|
}
|
|
354
|
-
function parseLiteralOrStructure(expr) {
|
|
449
|
+
function parseLiteralOrStructure(expr, depth) {
|
|
355
450
|
const s = expr.trim();
|
|
356
451
|
if (s === "true") return absLit(true);
|
|
357
452
|
if (s === "false") return absLit(false);
|
|
@@ -370,17 +465,20 @@ function parseLiteralOrStructure(expr) {
|
|
|
370
465
|
return absLit(s.slice(1, -1));
|
|
371
466
|
}
|
|
372
467
|
if (s.startsWith("{") && s.endsWith("}")) {
|
|
373
|
-
return parseObjectLiteral(s.slice(1, -1).trim());
|
|
468
|
+
return parseObjectLiteral(s.slice(1, -1).trim(), depth);
|
|
374
469
|
}
|
|
375
470
|
if (s.startsWith("[") && s.endsWith("]")) {
|
|
376
471
|
const content = s.slice(1, -1).trim();
|
|
377
472
|
if (!content) return absExact({ k: "tuple", elements: [] });
|
|
378
473
|
const elements = splitTopLevelArgs(content);
|
|
379
|
-
return absExact({
|
|
474
|
+
return absExact({
|
|
475
|
+
k: "tuple",
|
|
476
|
+
elements: elements.map((e) => parseCaseArgExpr(e, depth + 1))
|
|
477
|
+
});
|
|
380
478
|
}
|
|
381
479
|
return absUnknown();
|
|
382
480
|
}
|
|
383
|
-
function parseObjectLiteral(content) {
|
|
481
|
+
function parseObjectLiteral(content, depth) {
|
|
384
482
|
if (!content) return absExact({ k: "obj", slots: {} });
|
|
385
483
|
const entries = splitTopLevelArgs(content);
|
|
386
484
|
const slots = {};
|
|
@@ -389,7 +487,7 @@ function parseObjectLiteral(content) {
|
|
|
389
487
|
if (colonIdx === -1) continue;
|
|
390
488
|
const key = entry.slice(0, colonIdx).trim().replace(/^["']|["']$/g, "");
|
|
391
489
|
const val = entry.slice(colonIdx + 1).trim();
|
|
392
|
-
slots[key] = { value: parseCaseArgExpr(val) };
|
|
490
|
+
slots[key] = { value: parseCaseArgExpr(val, depth + 1) };
|
|
393
491
|
}
|
|
394
492
|
return absExact({ k: "obj", slots });
|
|
395
493
|
}
|
|
@@ -555,6 +653,13 @@ function parseSkipReturnsExpr(rest) {
|
|
|
555
653
|
if (inner !== null && s.slice(inner.length + 2).trim() === "") {
|
|
556
654
|
return parseAbsExprDiag(inner, "@nudo:skip (...)");
|
|
557
655
|
}
|
|
656
|
+
if (inner === null) {
|
|
657
|
+
emitDirectiveDiag({
|
|
658
|
+
code: "nudo:directive-syntax",
|
|
659
|
+
message: `@nudo:skip type expression has unclosed parenthesis (expected @nudo:skip (<typeExpr>)) \u2014 got: ${s.slice(0, 60)}`
|
|
660
|
+
});
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
558
663
|
}
|
|
559
664
|
return void 0;
|
|
560
665
|
}
|
|
@@ -648,7 +753,8 @@ function parseSinonExpr(expr) {
|
|
|
648
753
|
return null;
|
|
649
754
|
}
|
|
650
755
|
function parseNudoMockExpr(expr) {
|
|
651
|
-
|
|
756
|
+
let s = expr.trim();
|
|
757
|
+
while (s.startsWith("sinon.")) s = s.slice("sinon.".length);
|
|
652
758
|
const stubReturnsOnFirstMatch = s.match(/^stub\(\)\.returns\((.+)\)\.onFirstCall\(\)$/);
|
|
653
759
|
if (stubReturnsOnFirstMatch) {
|
|
654
760
|
const helper = stub.returns(parseAbsExpr(stubReturnsOnFirstMatch[1].trim()));
|
|
@@ -719,9 +825,6 @@ function parseNudoMockExpr(expr) {
|
|
|
719
825
|
if (s === "mock()") {
|
|
720
826
|
return mock();
|
|
721
827
|
}
|
|
722
|
-
if (s.startsWith("sinon.")) {
|
|
723
|
-
return parseNudoMockExpr(s.slice("sinon.".length));
|
|
724
|
-
}
|
|
725
828
|
return null;
|
|
726
829
|
}
|
|
727
830
|
function parseDirectivesFromCommentTexts(comments) {
|
|
@@ -831,8 +934,17 @@ function scanMalformedCaseNames(text, caseArgSpans) {
|
|
|
831
934
|
while ((m = CASE_TAG_REGEX.exec(text)) !== null) {
|
|
832
935
|
if (inCaseArgs(m.index)) continue;
|
|
833
936
|
const rest = m[1].trim();
|
|
834
|
-
|
|
835
|
-
if (
|
|
937
|
+
const legal = rest.match(/^("[^"]+")[ \t]*\(/);
|
|
938
|
+
if (legal) {
|
|
939
|
+
const parenIdx = m.index + m[0].length - m[1].length + legal[0].length - 1;
|
|
940
|
+
if (extractBalancedParens(text, parenIdx) === null) {
|
|
941
|
+
emitDirectiveDiag({
|
|
942
|
+
code: "nudo:directive-syntax",
|
|
943
|
+
message: `Malformed @nudo:case: unclosed argument list (missing closing ")") \u2014 got: @nudo:case ${rest.slice(0, 60)}`
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
836
948
|
let reason;
|
|
837
949
|
if (rest.startsWith("'")) {
|
|
838
950
|
reason = `single-quoted name (use double quotes: @nudo:case "name" (\u2026))`;
|
|
@@ -872,6 +984,14 @@ function scanMalformedMockNames(text, caseArgSpans) {
|
|
|
872
984
|
code: "nudo:directive-syntax",
|
|
873
985
|
message: `Malformed @nudo:mock name '${name}': not a valid identifier (use letters, digits, _, $; Unicode letters allowed)`
|
|
874
986
|
});
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const afterName = rest.slice(name.length);
|
|
990
|
+
if (/^[ \t]+from[ \t]+"[^"\n]*$/.test(afterName)) {
|
|
991
|
+
emitDirectiveDiag({
|
|
992
|
+
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)}`
|
|
994
|
+
});
|
|
875
995
|
}
|
|
876
996
|
}
|
|
877
997
|
}
|
|
@@ -898,9 +1018,32 @@ function extractDirectivesQuiet(ast) {
|
|
|
898
1018
|
takeDirectiveDiagsSince(since);
|
|
899
1019
|
return out;
|
|
900
1020
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1021
|
+
function stripInlineCommentPrefix(line, kind) {
|
|
1022
|
+
const s = kind === "block" ? line.replace(/^\s*\*\s?/, "") : line.replace(/^\//, "");
|
|
1023
|
+
return s.trim();
|
|
1024
|
+
}
|
|
1025
|
+
var AS_LINE_REGEX = /^@nudo:as[ \t]+(.+)$/;
|
|
1026
|
+
var REPLACE_LINE_REGEX = /^@nudo:replace[ \t]+(.+)$/;
|
|
1027
|
+
var AS_BARE_RE = /^@nudo:as[ \t]*$/;
|
|
1028
|
+
var REPLACE_BARE_RE = /^@nudo:replace[ \t]*$/;
|
|
1029
|
+
function findTrailingCommentStart(expr) {
|
|
1030
|
+
let inString = null;
|
|
1031
|
+
for (let i = 0; i < expr.length; i++) {
|
|
1032
|
+
const ch = expr[i];
|
|
1033
|
+
if (inString) {
|
|
1034
|
+
if (ch === inString) inString = null;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (ch === '"' || ch === "'") {
|
|
1038
|
+
inString = ch;
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if ((ch === " " || ch === " ") && /^[ \t]+(?:\/\/|\/\*)/.test(expr.slice(i))) {
|
|
1042
|
+
return i;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return -1;
|
|
1046
|
+
}
|
|
904
1047
|
function extractInlineDirectives(node) {
|
|
905
1048
|
resetDirectiveDiagSeen();
|
|
906
1049
|
const comments = node.leadingComments;
|
|
@@ -908,39 +1051,56 @@ function extractInlineDirectives(node) {
|
|
|
908
1051
|
const results = [];
|
|
909
1052
|
for (const comment of comments) {
|
|
910
1053
|
if (comment.type !== "CommentLine" && comment.type !== "CommentBlock") continue;
|
|
911
|
-
const
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const
|
|
1054
|
+
const kind = comment.type === "CommentLine" ? "line" : "block";
|
|
1055
|
+
for (const rawLine of comment.value.split("\n")) {
|
|
1056
|
+
const line = stripInlineCommentPrefix(rawLine, kind);
|
|
1057
|
+
const asMatch = line.match(AS_LINE_REGEX);
|
|
1058
|
+
if (asMatch) {
|
|
1059
|
+
const rawExpr = asMatch[1].trim();
|
|
1060
|
+
const trailingIdx = findTrailingCommentStart(rawExpr);
|
|
1061
|
+
if (trailingIdx !== -1) {
|
|
1062
|
+
const typePart = rawExpr.slice(0, trailingIdx).trim();
|
|
1063
|
+
emitDirectiveDiag({
|
|
1064
|
+
code: "nudo:directive-syntax",
|
|
1065
|
+
message: `Trailing comment in @nudo:as type expression: '${rawExpr}' (remove the comment; the type must be a clean expression)`
|
|
1066
|
+
});
|
|
1067
|
+
results.push({ kind: "as", typeAbs: typePart ? parseAbsExprDiag(typePart, "@nudo:as") : absUnknown() });
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
results.push({ kind: "as", typeAbs: parseAbsExprDiag(rawExpr, "@nudo:as") });
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (AS_BARE_RE.test(line)) {
|
|
918
1074
|
emitDirectiveDiag({
|
|
919
1075
|
code: "nudo:directive-syntax",
|
|
920
|
-
message:
|
|
1076
|
+
message: `@nudo:as requires a type expression (e.g. @nudo:as number())`
|
|
921
1077
|
});
|
|
922
|
-
results.push({ kind: "as", typeAbs: typePart ? parseAbsExprDiag(typePart, "@nudo:as") : absUnknown() });
|
|
923
1078
|
continue;
|
|
924
1079
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1080
|
+
const replaceMatch = line.match(REPLACE_LINE_REGEX);
|
|
1081
|
+
if (replaceMatch) {
|
|
1082
|
+
const raw = replaceMatch[1].trim();
|
|
1083
|
+
const sepIdx = findReplaceSeparator(raw);
|
|
1084
|
+
if (sepIdx !== -1) {
|
|
1085
|
+
const targetSource = raw.slice(0, sepIdx).trim();
|
|
1086
|
+
const typeExprStr = raw.slice(sepIdx + 1).trim();
|
|
1087
|
+
results.push({
|
|
1088
|
+
kind: "replace",
|
|
1089
|
+
targetSource,
|
|
1090
|
+
typeAbs: parseAbsExprDiag(typeExprStr, "@nudo:replace")
|
|
1091
|
+
});
|
|
1092
|
+
} else {
|
|
1093
|
+
emitDirectiveDiag({
|
|
1094
|
+
code: "nudo:directive-syntax",
|
|
1095
|
+
message: `Malformed @nudo:replace: cannot separate target from type (expected @nudo:replace <target> <typeExpr>) \u2014 got: ${raw.slice(0, 60)}`
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (REPLACE_BARE_RE.test(line)) {
|
|
941
1101
|
emitDirectiveDiag({
|
|
942
1102
|
code: "nudo:directive-syntax",
|
|
943
|
-
message:
|
|
1103
|
+
message: `@nudo:replace requires a target and type expression (expected @nudo:replace <target> <typeExpr>)`
|
|
944
1104
|
});
|
|
945
1105
|
}
|
|
946
1106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/parser",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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.
|
|
42
|
+
"@nudojs/core": "1.6.0",
|
|
43
43
|
"@babel/types": "^8.0.6"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|