@elaraai/tsserver-plugin-east 1.0.13 → 1.0.15
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.cjs +731 -153
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -24,8 +24,6 @@ __export(tsserver_plugin_exports, {
|
|
|
24
24
|
init: () => init
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(tsserver_plugin_exports);
|
|
27
|
-
var import_node_fs = require("node:fs");
|
|
28
|
-
var import_node_path2 = require("node:path");
|
|
29
27
|
|
|
30
28
|
// ../east-diagnostics/dist/src/east-type.js
|
|
31
29
|
function isEastExprType(type) {
|
|
@@ -71,40 +69,64 @@ function matchBlockBuilderCall(node, ctx) {
|
|
|
71
69
|
// ../east-diagnostics/dist/src/rules/no-redundant-east-cast.js
|
|
72
70
|
var NAME = "no-redundant-east-cast";
|
|
73
71
|
var CODE = 990001;
|
|
72
|
+
function isEastValueCall(expr, t) {
|
|
73
|
+
if (!t.isCallExpression(expr))
|
|
74
|
+
return false;
|
|
75
|
+
const callee = expr.expression;
|
|
76
|
+
return t.isPropertyAccessExpression(callee) && t.isIdentifier(callee.expression) && callee.expression.text === "East" && callee.name.text === "value";
|
|
77
|
+
}
|
|
78
|
+
function report(ctx, target, messageText, fixDescription, newText) {
|
|
79
|
+
const sf = ctx.sourceFile;
|
|
80
|
+
const start = target.getStart(sf);
|
|
81
|
+
const length = target.getEnd() - start;
|
|
82
|
+
ctx.report({
|
|
83
|
+
ruleName: NAME,
|
|
84
|
+
code: CODE,
|
|
85
|
+
start,
|
|
86
|
+
length,
|
|
87
|
+
messageText,
|
|
88
|
+
category: "warning",
|
|
89
|
+
fix: { description: fixDescription, changes: [{ start, length, newText }] }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
74
92
|
var noRedundantEastCast = {
|
|
75
93
|
name: NAME,
|
|
76
94
|
code: CODE,
|
|
77
|
-
description: "Disallow
|
|
95
|
+
description: "Disallow TypeScript type info on the value of $.let/$.const that the East type argument already governs (a cast, `new Map<K,V>()` generics, or an `East.value(x,T)` wrapper).",
|
|
78
96
|
check(node, ctx) {
|
|
79
97
|
const match = matchBlockBuilderCall(node, ctx);
|
|
80
|
-
if (match === void 0
|
|
98
|
+
if (match === void 0)
|
|
81
99
|
return;
|
|
82
100
|
const t = ctx.ts;
|
|
83
101
|
const value = match.args[0];
|
|
84
102
|
if (value === void 0)
|
|
85
103
|
return;
|
|
86
|
-
|
|
104
|
+
const sf = ctx.sourceFile;
|
|
105
|
+
if (isEastValueCall(value, t)) {
|
|
106
|
+
const inner = value.arguments[0];
|
|
107
|
+
if (inner === void 0)
|
|
108
|
+
return;
|
|
109
|
+
const typeArg = match.args[1] ?? value.arguments[1];
|
|
110
|
+
const receiverText = match.call.expression.getText(sf);
|
|
111
|
+
const newText = `${receiverText}(${inner.getText(sf)}${typeArg !== void 0 ? `, ${typeArg.getText(sf)}` : ""})`;
|
|
112
|
+
report(ctx, match.call, `Redundant \`East.value(...)\` inside \`$.${match.method}\`: pass the value (and its East type) to \`$.${match.method}\` directly.`, "Lift the value and type out of East.value(...)", newText);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (match.args.length < 2)
|
|
116
|
+
return;
|
|
117
|
+
let cast;
|
|
87
118
|
if (t.isAsExpression(value))
|
|
88
|
-
|
|
119
|
+
cast = value.expression;
|
|
89
120
|
else if (t.isTypeAssertionExpression(value))
|
|
90
|
-
|
|
91
|
-
if (
|
|
121
|
+
cast = value.expression;
|
|
122
|
+
if (cast !== void 0) {
|
|
123
|
+
report(ctx, value, `Redundant cast: \`$.${match.method}\` infers the value type from the East type argument; drop the \`as \u2026\` on the value.`, "Remove redundant cast", cast.getText(sf));
|
|
92
124
|
return;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
code: CODE,
|
|
99
|
-
start,
|
|
100
|
-
length,
|
|
101
|
-
messageText: `Redundant cast: \`$.${match.method}\` infers the value type from the East type argument; drop the \`as \u2026\` on the value.`,
|
|
102
|
-
category: "warning",
|
|
103
|
-
fix: {
|
|
104
|
-
description: "Remove redundant cast",
|
|
105
|
-
changes: [{ start, length, newText: inner.getText(sf) }]
|
|
106
|
-
}
|
|
107
|
-
});
|
|
125
|
+
}
|
|
126
|
+
if (t.isNewExpression(value) && value.typeArguments !== void 0 && value.typeArguments.length > 0) {
|
|
127
|
+
const ctorArgs = (value.arguments ?? []).map((a) => a.getText(sf)).join(", ");
|
|
128
|
+
report(ctx, value, `Redundant type arguments: \`$.${match.method}\` infers the value type from the East type argument; drop the \`<\u2026>\` on \`new ${value.expression.getText(sf)}\`.`, "Remove redundant constructor type arguments", `new ${value.expression.getText(sf)}(${ctorArgs})`);
|
|
129
|
+
}
|
|
108
130
|
}
|
|
109
131
|
};
|
|
110
132
|
|
|
@@ -336,6 +358,59 @@ var preferLetConstOverEastValue = {
|
|
|
336
358
|
}
|
|
337
359
|
};
|
|
338
360
|
|
|
361
|
+
// ../east-diagnostics/dist/src/east-source.js
|
|
362
|
+
var import_node_fs = require("node:fs");
|
|
363
|
+
var import_node_path = require("node:path");
|
|
364
|
+
var importsCache = /* @__PURE__ */ new WeakMap();
|
|
365
|
+
function importsEastPackage(sf, t) {
|
|
366
|
+
const cached = importsCache.get(sf);
|
|
367
|
+
if (cached !== void 0)
|
|
368
|
+
return cached;
|
|
369
|
+
let found = false;
|
|
370
|
+
for (const stmt of sf.statements) {
|
|
371
|
+
if (!t.isImportDeclaration(stmt) && !t.isExportDeclaration(stmt))
|
|
372
|
+
continue;
|
|
373
|
+
const spec = stmt.moduleSpecifier;
|
|
374
|
+
if (spec !== void 0 && t.isStringLiteral(spec) && spec.text.startsWith("@elaraai/")) {
|
|
375
|
+
found = true;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
importsCache.set(sf, found);
|
|
380
|
+
return found;
|
|
381
|
+
}
|
|
382
|
+
var pkgDirCache = /* @__PURE__ */ new Map();
|
|
383
|
+
function packageDirOf(p) {
|
|
384
|
+
const start = (0, import_node_path.dirname)((0, import_node_path.resolve)(p));
|
|
385
|
+
if (pkgDirCache.has(start))
|
|
386
|
+
return pkgDirCache.get(start);
|
|
387
|
+
let dir = start;
|
|
388
|
+
let result;
|
|
389
|
+
for (; ; ) {
|
|
390
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) {
|
|
391
|
+
result = dir;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
395
|
+
if (parent === dir)
|
|
396
|
+
break;
|
|
397
|
+
dir = parent;
|
|
398
|
+
}
|
|
399
|
+
pkgDirCache.set(start, result);
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
function resolvesWithinOwnPackage(sourceFileName, specifierText) {
|
|
403
|
+
try {
|
|
404
|
+
const own = packageDirOf(sourceFileName);
|
|
405
|
+
if (own === void 0)
|
|
406
|
+
return false;
|
|
407
|
+
const targetAbs = (0, import_node_path.resolve)((0, import_node_path.dirname)(sourceFileName), specifierText);
|
|
408
|
+
return packageDirOf(targetAbs) === own;
|
|
409
|
+
} catch {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
339
414
|
// ../east-diagnostics/dist/src/rules/no-relative-src-import.js
|
|
340
415
|
var NAME7 = "no-relative-src-import";
|
|
341
416
|
var CODE7 = 990007;
|
|
@@ -361,6 +436,8 @@ var noRelativeSrcImport = {
|
|
|
361
436
|
const deepPackageSrc = DEEP_PACKAGE_SRC.test(text);
|
|
362
437
|
if (!relativeIntoSrc && !deepPackageSrc)
|
|
363
438
|
return;
|
|
439
|
+
if (relativeIntoSrc && !deepPackageSrc && resolvesWithinOwnPackage(ctx.sourceFile.fileName, text))
|
|
440
|
+
return;
|
|
364
441
|
const sf = ctx.sourceFile;
|
|
365
442
|
const start = specifier.getStart(sf);
|
|
366
443
|
ctx.report({
|
|
@@ -395,8 +472,8 @@ var noLetConstInExpression = {
|
|
|
395
472
|
}
|
|
396
473
|
if (parent === void 0)
|
|
397
474
|
return;
|
|
398
|
-
const
|
|
399
|
-
if (
|
|
475
|
+
const allowed = t.isVariableDeclaration(parent) && parent.initializer === current || t.isExpressionStatement(parent) && parent.expression === current || t.isReturnStatement(parent) && parent.expression === current || t.isArrowFunction(parent) && parent.body === current;
|
|
476
|
+
if (allowed)
|
|
400
477
|
return;
|
|
401
478
|
const sf = ctx.sourceFile;
|
|
402
479
|
const start = call.getStart(sf);
|
|
@@ -411,6 +488,40 @@ var noLetConstInExpression = {
|
|
|
411
488
|
}
|
|
412
489
|
};
|
|
413
490
|
|
|
491
|
+
// ../east-diagnostics/dist/src/east-ir.js
|
|
492
|
+
function chainRootReceiver(node, ctx) {
|
|
493
|
+
const t = ctx.ts;
|
|
494
|
+
let root = node;
|
|
495
|
+
for (; ; ) {
|
|
496
|
+
if (t.isCallExpression(root))
|
|
497
|
+
root = root.expression;
|
|
498
|
+
else if (t.isPropertyAccessExpression(root) || t.isElementAccessExpression(root)) {
|
|
499
|
+
root = root.expression;
|
|
500
|
+
} else {
|
|
501
|
+
return root;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function bodyBuildsEastIr(body, ctx) {
|
|
506
|
+
const t = ctx.ts;
|
|
507
|
+
let found = false;
|
|
508
|
+
const visit = (n) => {
|
|
509
|
+
if (found)
|
|
510
|
+
return;
|
|
511
|
+
if (isBlockBuilderCallback(n, ctx))
|
|
512
|
+
return;
|
|
513
|
+
if (t.isCallExpression(n)) {
|
|
514
|
+
if (matchBlockBuilderCall(n, ctx) !== void 0 || isBlockBuilderType(ctx.checker.getTypeAtLocation(chainRootReceiver(n.expression, ctx)))) {
|
|
515
|
+
found = true;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
t.forEachChild(n, visit);
|
|
520
|
+
};
|
|
521
|
+
visit(body);
|
|
522
|
+
return found;
|
|
523
|
+
}
|
|
524
|
+
|
|
414
525
|
// ../east-diagnostics/dist/src/rules/no-unexecuted-east-expression.js
|
|
415
526
|
var NAME9 = "no-unexecuted-east-expression";
|
|
416
527
|
var CODE9 = 990009;
|
|
@@ -423,16 +534,7 @@ var noUnexecutedEastExpression = {
|
|
|
423
534
|
if (!t.isExpressionStatement(node))
|
|
424
535
|
return;
|
|
425
536
|
const expr = node.expression;
|
|
426
|
-
|
|
427
|
-
for (; ; ) {
|
|
428
|
-
if (t.isCallExpression(root)) {
|
|
429
|
-
root = root.expression;
|
|
430
|
-
} else if (t.isPropertyAccessExpression(root) || t.isElementAccessExpression(root)) {
|
|
431
|
-
root = root.expression;
|
|
432
|
-
} else {
|
|
433
|
-
break;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
537
|
+
const root = chainRootReceiver(expr, ctx);
|
|
436
538
|
if (isBlockBuilderType(ctx.checker.getTypeAtLocation(root)))
|
|
437
539
|
return;
|
|
438
540
|
if (!isEastExprType(ctx.checker.getTypeAtLocation(expr)))
|
|
@@ -509,79 +611,9 @@ var noReinlinedEastBinding = {
|
|
|
509
611
|
}
|
|
510
612
|
};
|
|
511
613
|
|
|
512
|
-
// ../east-diagnostics/dist/src/rules/no-east-data-builder-helper.js
|
|
513
|
-
var NAME11 = "no-east-data-builder-helper";
|
|
514
|
-
var CODE11 = 990011;
|
|
515
|
-
var VALUE_CONSTRUCTORS = /* @__PURE__ */ new Set(["variant", "some"]);
|
|
516
|
-
function isEastValueConstructor(expr, t) {
|
|
517
|
-
if (t.isCallExpression(expr)) {
|
|
518
|
-
const callee = expr.expression;
|
|
519
|
-
if (t.isIdentifier(callee) && VALUE_CONSTRUCTORS.has(callee.text))
|
|
520
|
-
return true;
|
|
521
|
-
return t.isPropertyAccessExpression(callee) && t.isIdentifier(callee.expression) && callee.expression.text === "East" && callee.name.text === "value";
|
|
522
|
-
}
|
|
523
|
-
return t.isIdentifier(expr) && expr.text === "none";
|
|
524
|
-
}
|
|
525
|
-
function returnExpressions(fn, t) {
|
|
526
|
-
if (fn.body === void 0)
|
|
527
|
-
return [];
|
|
528
|
-
if (!t.isBlock(fn.body))
|
|
529
|
-
return [fn.body];
|
|
530
|
-
const out = [];
|
|
531
|
-
const visit = (n) => {
|
|
532
|
-
if (t.isFunctionDeclaration(n) || t.isFunctionExpression(n) || t.isArrowFunction(n))
|
|
533
|
-
return;
|
|
534
|
-
if (t.isReturnStatement(n) && n.expression !== void 0)
|
|
535
|
-
out.push(n.expression);
|
|
536
|
-
t.forEachChild(n, visit);
|
|
537
|
-
};
|
|
538
|
-
t.forEachChild(fn.body, visit);
|
|
539
|
-
return out;
|
|
540
|
-
}
|
|
541
|
-
function isBuilderFunction(fn, ctx) {
|
|
542
|
-
const t = ctx.ts;
|
|
543
|
-
const first = fn.parameters[0];
|
|
544
|
-
if (first !== void 0 && isBlockBuilderType(ctx.checker.getTypeAtLocation(first.name))) {
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
const returns = returnExpressions(fn, t);
|
|
548
|
-
return returns.length > 0 && returns.every((r) => isEastValueConstructor(r, t));
|
|
549
|
-
}
|
|
550
|
-
var noEastDataBuilderHelper = {
|
|
551
|
-
name: NAME11,
|
|
552
|
-
code: CODE11,
|
|
553
|
-
description: "Flag a TS helper whose only job is to return a hand-built East value (variant/some/none/East.value) \u2014 inline it or make it a real East.function.",
|
|
554
|
-
check(node, ctx) {
|
|
555
|
-
const t = ctx.ts;
|
|
556
|
-
let fn;
|
|
557
|
-
let reportNode;
|
|
558
|
-
if (t.isFunctionDeclaration(node) && node.body !== void 0) {
|
|
559
|
-
fn = node;
|
|
560
|
-
reportNode = node.name ?? node;
|
|
561
|
-
} else if (t.isVariableDeclaration(node) && node.initializer !== void 0 && (t.isArrowFunction(node.initializer) || t.isFunctionExpression(node.initializer))) {
|
|
562
|
-
fn = node.initializer;
|
|
563
|
-
reportNode = node.name;
|
|
564
|
-
}
|
|
565
|
-
if (fn === void 0 || reportNode === void 0)
|
|
566
|
-
return;
|
|
567
|
-
if (!isBuilderFunction(fn, ctx))
|
|
568
|
-
return;
|
|
569
|
-
const sf = ctx.sourceFile;
|
|
570
|
-
const start = reportNode.getStart(sf);
|
|
571
|
-
ctx.report({
|
|
572
|
-
ruleName: NAME11,
|
|
573
|
-
code: CODE11,
|
|
574
|
-
start,
|
|
575
|
-
length: reportNode.getEnd() - start,
|
|
576
|
-
messageText: "This helper just returns a hand-built East value (`variant`/`some`/`none`/`East.value`), so it is an authoring-time macro, not a real East function. Inline the constructor at each call site (repetition is welcome), or make it a real `East.function` if you need a reusable East computation.",
|
|
577
|
-
category: "warning"
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
|
-
|
|
582
614
|
// ../east-diagnostics/dist/src/rules/prefer-jsx-over-factory-call.js
|
|
583
|
-
var
|
|
584
|
-
var
|
|
615
|
+
var NAME11 = "prefer-jsx-over-factory-call";
|
|
616
|
+
var CODE11 = 990012;
|
|
585
617
|
var jsxElementCache = /* @__PURE__ */ new WeakMap();
|
|
586
618
|
function jsxElementType(ctx) {
|
|
587
619
|
const cached = jsxElementCache.get(ctx.sourceFile);
|
|
@@ -647,8 +679,8 @@ function sameType(a, b, checker) {
|
|
|
647
679
|
return c.isTypeAssignableTo(a, b) && c.isTypeAssignableTo(b, a);
|
|
648
680
|
}
|
|
649
681
|
var preferJsxOverFactoryCall = {
|
|
650
|
-
name:
|
|
651
|
-
code:
|
|
682
|
+
name: NAME11,
|
|
683
|
+
code: CODE11,
|
|
652
684
|
description: "In a .tsx file, prefer the <Foo> JSX tag over a factory's Foo.Root(...) when the call produces a JSX element.",
|
|
653
685
|
check(node, ctx) {
|
|
654
686
|
const t = ctx.ts;
|
|
@@ -680,8 +712,8 @@ var preferJsxOverFactoryCall = {
|
|
|
680
712
|
const sf = ctx.sourceFile;
|
|
681
713
|
const start = callee.getStart(sf);
|
|
682
714
|
ctx.report({
|
|
683
|
-
ruleName:
|
|
684
|
-
code:
|
|
715
|
+
ruleName: NAME11,
|
|
716
|
+
code: CODE11,
|
|
685
717
|
start,
|
|
686
718
|
length: callee.getEnd() - start,
|
|
687
719
|
messageText: `Author this with the \`<${tagName}>\` JSX tag instead of \`${factoryIdent.text}.Root(...)\` \u2014 in a .tsx file the JSX tag is the authoring surface (the call already produces a JSX element).`,
|
|
@@ -691,8 +723,8 @@ var preferJsxOverFactoryCall = {
|
|
|
691
723
|
};
|
|
692
724
|
|
|
693
725
|
// ../east-diagnostics/dist/src/rules/no-untracked-east-data.js
|
|
694
|
-
var
|
|
695
|
-
var
|
|
726
|
+
var NAME12 = "no-untracked-east-data";
|
|
727
|
+
var CODE12 = 990013;
|
|
696
728
|
function plainLiteralInitializer(decl, t) {
|
|
697
729
|
const init2 = decl.initializer;
|
|
698
730
|
if (init2 === void 0)
|
|
@@ -704,8 +736,8 @@ function plainLiteralInitializer(decl, t) {
|
|
|
704
736
|
return void 0;
|
|
705
737
|
}
|
|
706
738
|
var noUntrackedEastData = {
|
|
707
|
-
name:
|
|
708
|
-
code:
|
|
739
|
+
name: NAME12,
|
|
740
|
+
code: CODE12,
|
|
709
741
|
description: "Inside East blocks, bind data consumed in East-typed positions with $.const/$.let, not a bare JS const.",
|
|
710
742
|
check(node, ctx) {
|
|
711
743
|
const t = ctx.ts;
|
|
@@ -732,8 +764,8 @@ var noUntrackedEastData = {
|
|
|
732
764
|
const sf = ctx.sourceFile;
|
|
733
765
|
const start = node.getStart(sf);
|
|
734
766
|
ctx.report({
|
|
735
|
-
ruleName:
|
|
736
|
-
code:
|
|
767
|
+
ruleName: NAME12,
|
|
768
|
+
code: CODE12,
|
|
737
769
|
start,
|
|
738
770
|
length: node.getEnd() - start,
|
|
739
771
|
messageText: `Bare \`const ${node.text} = \u2026\` isn't tracked by the East block builder. Bind East data with \`$.const([...], Type)\` (or \`$.let\`) so the binding carries its East type and is evaluated once.`,
|
|
@@ -742,8 +774,569 @@ var noUntrackedEastData = {
|
|
|
742
774
|
}
|
|
743
775
|
};
|
|
744
776
|
|
|
777
|
+
// ../east-diagnostics/dist/src/rules/no-compile-time-data-injection.js
|
|
778
|
+
var NAME13 = "no-compile-time-data-injection";
|
|
779
|
+
var CODE13 = 990015;
|
|
780
|
+
var FS_MODULES = /* @__PURE__ */ new Set(["node:fs", "fs", "node:fs/promises", "fs/promises"]);
|
|
781
|
+
function fire(ctx, target, messageText) {
|
|
782
|
+
const sf = ctx.sourceFile;
|
|
783
|
+
const start = target.getStart(sf);
|
|
784
|
+
ctx.report({ ruleName: NAME13, code: CODE13, start, length: target.getEnd() - start, messageText, category: "warning" });
|
|
785
|
+
}
|
|
786
|
+
function importOfSymbol(sym, t) {
|
|
787
|
+
for (const d of sym?.declarations ?? []) {
|
|
788
|
+
let n = d;
|
|
789
|
+
if (t.isImportSpecifier(n))
|
|
790
|
+
n = n.parent.parent.parent;
|
|
791
|
+
else if (t.isNamespaceImport(n))
|
|
792
|
+
n = n.parent.parent;
|
|
793
|
+
else if (t.isImportClause(n))
|
|
794
|
+
n = n.parent;
|
|
795
|
+
else
|
|
796
|
+
continue;
|
|
797
|
+
if (t.isImportDeclaration(n))
|
|
798
|
+
return n;
|
|
799
|
+
}
|
|
800
|
+
return void 0;
|
|
801
|
+
}
|
|
802
|
+
function resolvesToFsImport(id, ctx) {
|
|
803
|
+
const t = ctx.ts;
|
|
804
|
+
const imp = importOfSymbol(ctx.checker.getSymbolAtLocation(id), t);
|
|
805
|
+
return imp !== void 0 && t.isStringLiteral(imp.moduleSpecifier) && FS_MODULES.has(imp.moduleSpecifier.text);
|
|
806
|
+
}
|
|
807
|
+
function isProcessEnv(node, t) {
|
|
808
|
+
return t.isPropertyAccessExpression(node) && t.isIdentifier(node.expression) && node.expression.text === "process" && node.name.text === "env";
|
|
809
|
+
}
|
|
810
|
+
var noCompileTimeDataInjection = {
|
|
811
|
+
name: NAME13,
|
|
812
|
+
code: CODE13,
|
|
813
|
+
description: "Flag build-time data ingestion (a node:fs import or call, JSON.parse, process.env) at module scope \u2014 load data at runtime via e3.input / datasets / platform tasks.",
|
|
814
|
+
check(node, ctx) {
|
|
815
|
+
const t = ctx.ts;
|
|
816
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
817
|
+
return;
|
|
818
|
+
if (t.isImportDeclaration(node)) {
|
|
819
|
+
const spec = node.moduleSpecifier;
|
|
820
|
+
if (t.isStringLiteral(spec) && FS_MODULES.has(spec.text)) {
|
|
821
|
+
fire(ctx, node, `Importing \`${spec.text}\` into East/e3 source bakes build-time file I/O into the deployed program. Read data at runtime via \`e3.input\` / a dataset, or an \`east-node-io\` platform task.`);
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (insideBlockScope(node, ctx))
|
|
826
|
+
return;
|
|
827
|
+
if (isProcessEnv(node, t)) {
|
|
828
|
+
fire(ctx, node, "Reading `process.env` at module scope couples the deployed program to its build environment. Make it an `e3.input` / dataset parameter.");
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (!t.isCallExpression(node))
|
|
832
|
+
return;
|
|
833
|
+
const callee = node.expression;
|
|
834
|
+
const base = t.isIdentifier(callee) ? callee : t.isPropertyAccessExpression(callee) && t.isIdentifier(callee.expression) ? callee.expression : void 0;
|
|
835
|
+
if (base !== void 0 && resolvesToFsImport(base, ctx)) {
|
|
836
|
+
fire(ctx, node, "This `node:fs` call reads/probes the filesystem at build/deploy time and bakes the result into the program. Ingest at runtime via `e3.input` / a dataset / an `east-node-io` task.");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (t.isPropertyAccessExpression(callee) && t.isIdentifier(callee.expression) && callee.expression.text === "JSON" && callee.name.text === "parse") {
|
|
840
|
+
fire(ctx, node, "`JSON.parse(...)` at module scope bakes parsed data into the program. Load it at runtime via `e3.input` / a dataset.");
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// ../east-diagnostics/dist/src/rules/no-compile-time-seed-data.js
|
|
846
|
+
var NAME14 = "no-compile-time-seed-data";
|
|
847
|
+
var CODE14 = 990021;
|
|
848
|
+
function fire2(ctx, target, messageText) {
|
|
849
|
+
const sf = ctx.sourceFile;
|
|
850
|
+
const start = target.getStart(sf);
|
|
851
|
+
ctx.report({ ruleName: NAME14, code: CODE14, start, length: target.getEnd() - start, messageText, category: "warning" });
|
|
852
|
+
}
|
|
853
|
+
function importDeclOfSymbol(sym, t) {
|
|
854
|
+
for (const d of sym?.declarations ?? []) {
|
|
855
|
+
let n = d;
|
|
856
|
+
if (t.isImportSpecifier(n))
|
|
857
|
+
n = n.parent.parent.parent;
|
|
858
|
+
else if (t.isNamespaceImport(n))
|
|
859
|
+
n = n.parent.parent;
|
|
860
|
+
else if (t.isImportClause(n))
|
|
861
|
+
n = n.parent;
|
|
862
|
+
else
|
|
863
|
+
continue;
|
|
864
|
+
if (t.isImportDeclaration(n))
|
|
865
|
+
return n;
|
|
866
|
+
}
|
|
867
|
+
return void 0;
|
|
868
|
+
}
|
|
869
|
+
function resolvesToEastImport(id, ctx) {
|
|
870
|
+
const t = ctx.ts;
|
|
871
|
+
const imp = importDeclOfSymbol(ctx.checker.getSymbolAtLocation(id), t);
|
|
872
|
+
return imp !== void 0 && t.isStringLiteral(imp.moduleSpecifier) && imp.moduleSpecifier.text.startsWith("@elaraai/");
|
|
873
|
+
}
|
|
874
|
+
function isE3InputCall(node, ctx) {
|
|
875
|
+
const t = ctx.ts;
|
|
876
|
+
const callee = node.expression;
|
|
877
|
+
if (!t.isPropertyAccessExpression(callee) || callee.name.text !== "input")
|
|
878
|
+
return false;
|
|
879
|
+
if (!t.isIdentifier(callee.expression))
|
|
880
|
+
return false;
|
|
881
|
+
const imp = importDeclOfSymbol(ctx.checker.getSymbolAtLocation(callee.expression), t);
|
|
882
|
+
return imp !== void 0 && t.isStringLiteral(imp.moduleSpecifier) && imp.moduleSpecifier.text === "@elaraai/e3";
|
|
883
|
+
}
|
|
884
|
+
function rootIdentifier(node, t) {
|
|
885
|
+
let cur = node;
|
|
886
|
+
for (; ; ) {
|
|
887
|
+
if (t.isPropertyAccessExpression(cur) || t.isElementAccessExpression(cur))
|
|
888
|
+
cur = cur.expression;
|
|
889
|
+
else if (t.isCallExpression(cur))
|
|
890
|
+
cur = cur.expression;
|
|
891
|
+
else
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
return t.isIdentifier(cur) ? cur : void 0;
|
|
895
|
+
}
|
|
896
|
+
var VALUE_CTORS = /* @__PURE__ */ new Set([
|
|
897
|
+
"Map",
|
|
898
|
+
"Set",
|
|
899
|
+
"Date",
|
|
900
|
+
"ArrayBuffer",
|
|
901
|
+
"Uint8Array",
|
|
902
|
+
"Int8Array",
|
|
903
|
+
"Uint8ClampedArray",
|
|
904
|
+
"Int16Array",
|
|
905
|
+
"Uint16Array",
|
|
906
|
+
"Int32Array",
|
|
907
|
+
"Uint32Array",
|
|
908
|
+
"Float32Array",
|
|
909
|
+
"Float64Array",
|
|
910
|
+
"BigInt64Array",
|
|
911
|
+
"BigUint64Array"
|
|
912
|
+
]);
|
|
913
|
+
function embedsHostComputation(expr, ctx) {
|
|
914
|
+
const t = ctx.ts;
|
|
915
|
+
let bad = false;
|
|
916
|
+
const visit = (n) => {
|
|
917
|
+
if (bad)
|
|
918
|
+
return;
|
|
919
|
+
if (t.isCallExpression(n)) {
|
|
920
|
+
const root = rootIdentifier(n.expression, t);
|
|
921
|
+
if (root === void 0 || !resolvesToEastImport(root, ctx)) {
|
|
922
|
+
bad = true;
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
} else if (t.isNewExpression(n)) {
|
|
926
|
+
const ctor = n.expression;
|
|
927
|
+
const ok = t.isIdentifier(ctor) && (VALUE_CTORS.has(ctor.text) || resolvesToEastImport(ctor, ctx));
|
|
928
|
+
if (!ok) {
|
|
929
|
+
bad = true;
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
t.forEachChild(n, visit);
|
|
934
|
+
};
|
|
935
|
+
visit(expr);
|
|
936
|
+
return bad;
|
|
937
|
+
}
|
|
938
|
+
var MUTATORS = /* @__PURE__ */ new Set(["set", "add", "push", "unshift", "splice", "delete", "clear", "fill", "sort", "copyWithin", "pop", "shift"]);
|
|
939
|
+
function isAssignmentOp(kind, t) {
|
|
940
|
+
const k = t.SyntaxKind;
|
|
941
|
+
return kind === k.EqualsToken || kind === k.PlusEqualsToken || kind === k.MinusEqualsToken || kind === k.AsteriskEqualsToken || kind === k.SlashEqualsToken || kind === k.PercentEqualsToken || kind === k.AmpersandEqualsToken || kind === k.BarEqualsToken || kind === k.CaretEqualsToken || kind === k.LessThanLessThanEqualsToken || kind === k.GreaterThanGreaterThanEqualsToken || kind === k.GreaterThanGreaterThanGreaterThanEqualsToken || kind === k.AsteriskAsteriskEqualsToken || kind === k.QuestionQuestionEqualsToken || kind === k.BarBarEqualsToken || kind === k.AmpersandAmpersandEqualsToken;
|
|
942
|
+
}
|
|
943
|
+
function insideLoop(node, t) {
|
|
944
|
+
let cur = node.parent;
|
|
945
|
+
while (cur !== void 0) {
|
|
946
|
+
if (t.isForStatement(cur) || t.isForOfStatement(cur) || t.isForInStatement(cur) || t.isWhileStatement(cur) || t.isDoStatement(cur)) {
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
cur = cur.parent;
|
|
950
|
+
}
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
function isHostFilled(sym, ctx) {
|
|
954
|
+
const t = ctx.ts;
|
|
955
|
+
let filled = false;
|
|
956
|
+
const isSym = (n) => t.isIdentifier(n) && ctx.checker.getSymbolAtLocation(n) === sym;
|
|
957
|
+
const visit = (n) => {
|
|
958
|
+
if (filled)
|
|
959
|
+
return;
|
|
960
|
+
if (t.isCallExpression(n) && t.isPropertyAccessExpression(n.expression) && MUTATORS.has(n.expression.name.text) && isSym(n.expression.expression)) {
|
|
961
|
+
if (insideLoop(n, t) || n.arguments.some((a) => embedsHostComputation(a, ctx))) {
|
|
962
|
+
filled = true;
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (t.isBinaryExpression(n) && isAssignmentOp(n.operatorToken.kind, t)) {
|
|
967
|
+
const root = rootIdentifier(n.left, t);
|
|
968
|
+
if (root !== void 0 && isSym(root) && (insideLoop(n, t) || embedsHostComputation(n.right, ctx))) {
|
|
969
|
+
filled = true;
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
t.forEachChild(n, visit);
|
|
974
|
+
};
|
|
975
|
+
visit(ctx.sourceFile);
|
|
976
|
+
return filled;
|
|
977
|
+
}
|
|
978
|
+
var noCompileTimeSeedData = {
|
|
979
|
+
name: NAME14,
|
|
980
|
+
code: CODE14,
|
|
981
|
+
description: "Flag host-computed data passed as the seed (3rd arg) of e3.input \u2014 the default must be a small authored constant; load real data at runtime.",
|
|
982
|
+
check(node, ctx) {
|
|
983
|
+
const t = ctx.ts;
|
|
984
|
+
if (!t.isCallExpression(node) || !isE3InputCall(node, ctx))
|
|
985
|
+
return;
|
|
986
|
+
if (insideBlockScope(node, ctx))
|
|
987
|
+
return;
|
|
988
|
+
const seedArg = node.arguments[2];
|
|
989
|
+
if (seedArg === void 0)
|
|
990
|
+
return;
|
|
991
|
+
let expr = seedArg;
|
|
992
|
+
let sym;
|
|
993
|
+
if (t.isIdentifier(seedArg)) {
|
|
994
|
+
sym = ctx.checker.getSymbolAtLocation(seedArg);
|
|
995
|
+
const decl = sym?.valueDeclaration;
|
|
996
|
+
if (decl === void 0 || !t.isVariableDeclaration(decl) || decl.initializer === void 0)
|
|
997
|
+
return;
|
|
998
|
+
expr = decl.initializer;
|
|
999
|
+
}
|
|
1000
|
+
while (t.isAsExpression(expr) || t.isSatisfiesExpression(expr) || t.isParenthesizedExpression(expr)) {
|
|
1001
|
+
expr = expr.expression;
|
|
1002
|
+
}
|
|
1003
|
+
const hostComputed = embedsHostComputation(expr, ctx);
|
|
1004
|
+
const hostFilled = sym !== void 0 && isHostFilled(sym, ctx);
|
|
1005
|
+
if (!hostComputed && !hostFilled)
|
|
1006
|
+
return;
|
|
1007
|
+
const nameArg = node.arguments[0];
|
|
1008
|
+
const name = nameArg !== void 0 && t.isStringLiteralLike(nameArg) ? nameArg.text : "\u2026";
|
|
1009
|
+
const reason = hostFilled ? "this seed is an authored-empty collection then filled in place by host code (a `for`-loop / `.set(...)`)" : "this seed is assembled by host calls (`num(...)`, `BigInt(...)`, parsed config) at module-evaluation time";
|
|
1010
|
+
fire2(ctx, seedArg, `Host-computed data passed as the \`e3.input("${name}", \u2026)\` seed bakes a build-time snapshot into the deployed program \u2014 ${reason}. The default (3rd arg) must be a small AUTHORED CONSTANT (a literal, an empty/literal Map/Set/array/struct, or an East value \`variant\`/\`some\`/\`none\`/\`East.value\`) or omitted. Load real/bulk data at RUNTIME: put the bytes in a \`BlobType\` input and parse with \`blob.decodeCsv(...)\` inside an \`e3.task\`, read files in a task via a platform \`FileSystem.readFile\`, or use \`e3.record(...)\` + \`e3.mutation\` for set-once root state.`);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
// ../east-diagnostics/dist/src/rules/no-host-in-east-block.js
|
|
1015
|
+
var NAME15 = "no-host-in-east-block";
|
|
1016
|
+
var CODE15 = 990020;
|
|
1017
|
+
function resolvesToEastImport2(id, ctx) {
|
|
1018
|
+
const t = ctx.ts;
|
|
1019
|
+
const sym = ctx.checker.getSymbolAtLocation(id);
|
|
1020
|
+
for (const d of sym?.declarations ?? []) {
|
|
1021
|
+
let n = d;
|
|
1022
|
+
if (t.isImportSpecifier(n))
|
|
1023
|
+
n = n.parent.parent.parent;
|
|
1024
|
+
else if (t.isNamespaceImport(n))
|
|
1025
|
+
n = n.parent.parent;
|
|
1026
|
+
else if (t.isImportClause(n))
|
|
1027
|
+
n = n.parent;
|
|
1028
|
+
else
|
|
1029
|
+
continue;
|
|
1030
|
+
if (t.isImportDeclaration(n) && t.isStringLiteral(n.moduleSpecifier) && n.moduleSpecifier.text.startsWith("@elaraai/")) {
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
function resolvesToInBlockEastBinding(id, ctx) {
|
|
1037
|
+
const t = ctx.ts;
|
|
1038
|
+
const sym = ctx.checker.getSymbolAtLocation(id);
|
|
1039
|
+
for (const d of sym?.declarations ?? []) {
|
|
1040
|
+
if (t.isFunctionDeclaration(d) && d.body !== void 0 && insideBlockScope(d, ctx))
|
|
1041
|
+
return true;
|
|
1042
|
+
if (t.isVariableDeclaration(d) && d.initializer !== void 0 && (t.isArrowFunction(d.initializer) || t.isFunctionExpression(d.initializer)) && insideBlockScope(d, ctx)) {
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
if (t.isParameter(d)) {
|
|
1046
|
+
const fn = d.parent;
|
|
1047
|
+
if ((t.isArrowFunction(fn) || t.isFunctionExpression(fn) || t.isFunctionDeclaration(fn)) && insideBlockScope(fn, ctx)) {
|
|
1048
|
+
const first = fn.parameters[0];
|
|
1049
|
+
if (first === void 0 || !isBlockBuilderType(ctx.checker.getTypeAtLocation(first.name)))
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
function isEastCall(call, ctx) {
|
|
1057
|
+
const t = ctx.ts;
|
|
1058
|
+
const f = call.expression;
|
|
1059
|
+
if (isEastExprType(ctx.checker.getTypeAtLocation(f)))
|
|
1060
|
+
return true;
|
|
1061
|
+
const root = chainRootReceiver(f, ctx);
|
|
1062
|
+
if (isBlockBuilderType(ctx.checker.getTypeAtLocation(root)))
|
|
1063
|
+
return true;
|
|
1064
|
+
if (t.isPropertyAccessExpression(f) && isEastExprType(ctx.checker.getTypeAtLocation(f.expression)))
|
|
1065
|
+
return true;
|
|
1066
|
+
if (t.isIdentifier(root) && resolvesToEastImport2(root, ctx))
|
|
1067
|
+
return true;
|
|
1068
|
+
if (t.isPropertyAccessExpression(f) && t.isIdentifier(root) && resolvesToInBlockEastBinding(root, ctx))
|
|
1069
|
+
return true;
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
function isEast(expr, ctx) {
|
|
1073
|
+
return isEastExprType(ctx.checker.getTypeAtLocation(expr));
|
|
1074
|
+
}
|
|
1075
|
+
function insideJsx(node, t) {
|
|
1076
|
+
let cur = node.parent;
|
|
1077
|
+
while (cur !== void 0) {
|
|
1078
|
+
if (t.isJsxElement(cur) || t.isJsxSelfClosingElement(cur) || t.isJsxFragment(cur) || t.isJsxExpression(cur) || t.isJsxAttribute(cur)) {
|
|
1079
|
+
return true;
|
|
1080
|
+
}
|
|
1081
|
+
cur = cur.parent;
|
|
1082
|
+
}
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
function isJsx(node, t) {
|
|
1086
|
+
return t.isJsxElement(node) || t.isJsxFragment(node) || t.isJsxSelfClosingElement(node) || t.isParenthesizedExpression(node) && isJsx(node.expression, t);
|
|
1087
|
+
}
|
|
1088
|
+
function returnExpressions(fn, t) {
|
|
1089
|
+
if (fn.body === void 0)
|
|
1090
|
+
return [];
|
|
1091
|
+
if (!t.isBlock(fn.body))
|
|
1092
|
+
return [fn.body];
|
|
1093
|
+
const out = [];
|
|
1094
|
+
const visit = (n) => {
|
|
1095
|
+
if (t.isFunctionDeclaration(n) || t.isFunctionExpression(n) || t.isArrowFunction(n))
|
|
1096
|
+
return;
|
|
1097
|
+
if (t.isReturnStatement(n) && n.expression !== void 0)
|
|
1098
|
+
out.push(n.expression);
|
|
1099
|
+
t.forEachChild(n, visit);
|
|
1100
|
+
};
|
|
1101
|
+
t.forEachChild(fn.body, visit);
|
|
1102
|
+
return out;
|
|
1103
|
+
}
|
|
1104
|
+
var REPORT = (ctx, target, messageText, fix) => {
|
|
1105
|
+
const sf = ctx.sourceFile;
|
|
1106
|
+
const start = target.getStart(sf);
|
|
1107
|
+
const length = target.getEnd() - start;
|
|
1108
|
+
ctx.report({
|
|
1109
|
+
ruleName: NAME15,
|
|
1110
|
+
code: CODE15,
|
|
1111
|
+
start,
|
|
1112
|
+
length,
|
|
1113
|
+
messageText,
|
|
1114
|
+
category: "warning",
|
|
1115
|
+
...fix !== void 0 ? { fix: { description: fix.description, changes: [{ start, length, newText: fix.newText }] } } : {}
|
|
1116
|
+
});
|
|
1117
|
+
};
|
|
1118
|
+
var noHostInEastBlock = {
|
|
1119
|
+
name: NAME15,
|
|
1120
|
+
code: CODE15,
|
|
1121
|
+
description: "Flag host-language constructs (host calls, operators on East operands, JS control-flow, host string interpolation) inside an East block \u2014 express them in East.",
|
|
1122
|
+
check(node, ctx) {
|
|
1123
|
+
const t = ctx.ts;
|
|
1124
|
+
if (!insideBlockScope(node, ctx))
|
|
1125
|
+
return;
|
|
1126
|
+
if (insideJsx(node, t))
|
|
1127
|
+
return;
|
|
1128
|
+
{
|
|
1129
|
+
let fn;
|
|
1130
|
+
let reportNode;
|
|
1131
|
+
if (t.isFunctionDeclaration(node) && node.body !== void 0) {
|
|
1132
|
+
fn = node;
|
|
1133
|
+
reportNode = node.name ?? node;
|
|
1134
|
+
} else if (t.isVariableDeclaration(node) && node.initializer !== void 0 && (t.isArrowFunction(node.initializer) || t.isFunctionExpression(node.initializer))) {
|
|
1135
|
+
fn = node.initializer;
|
|
1136
|
+
reportNode = node.name;
|
|
1137
|
+
}
|
|
1138
|
+
if (fn !== void 0 && reportNode !== void 0) {
|
|
1139
|
+
const first = fn.parameters[0];
|
|
1140
|
+
if (first !== void 0 && isBlockBuilderType(ctx.checker.getTypeAtLocation(first.name)))
|
|
1141
|
+
return;
|
|
1142
|
+
if (returnExpressions(fn, t).some((r) => isJsx(r, t)))
|
|
1143
|
+
return;
|
|
1144
|
+
REPORT(ctx, reportNode, "TS closure/function declared inside an East block \u2014 an authoring-time macro (it can't be serialized or recursed and expands inline at each call). Make it a real `East.function` (`$.const(East.function(...))`) or inline it.");
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (t.isForStatement(node) || t.isWhileStatement(node) || t.isForOfStatement(node)) {
|
|
1149
|
+
if (t.isForOfStatement(node) && isEast(node.expression, ctx))
|
|
1150
|
+
return;
|
|
1151
|
+
if (!bodyBuildsEastIr(node.statement, ctx))
|
|
1152
|
+
return;
|
|
1153
|
+
REPORT(ctx, node.getChildAt(0, ctx.sourceFile), "Host loop building East IR \u2014 bind the data with `$.const([...], ArrayType(...))` and use an East collection op (`data.map(($, x) => \u2026)`) or `$.for(data, ($, x) => \u2026)`.");
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
if (t.isIfStatement(node)) {
|
|
1157
|
+
const emits = bodyBuildsEastIr(node.thenStatement, ctx) || node.elseStatement !== void 0 && bodyBuildsEastIr(node.elseStatement, ctx);
|
|
1158
|
+
if (!emits)
|
|
1159
|
+
return;
|
|
1160
|
+
REPORT(ctx, node.getChildAt(0, ctx.sourceFile), "Host `if` building East IR \u2014 use East's `$.if(cond, \u2026)` so the branch is chosen at East runtime.");
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
if (t.isConditionalExpression(node)) {
|
|
1164
|
+
if (isEast(node.condition, ctx) && isEast(node.whenTrue, ctx) && isEast(node.whenFalse, ctx)) {
|
|
1165
|
+
const sf = ctx.sourceFile;
|
|
1166
|
+
REPORT(ctx, node, "Host `?:` selecting between East values \u2014 use `cond.ifElse(() => a, () => b)`.", {
|
|
1167
|
+
description: "Rewrite as cond.ifElse(...)",
|
|
1168
|
+
newText: `(${node.condition.getText(sf)}).ifElse(() => ${node.whenTrue.getText(sf)}, () => ${node.whenFalse.getText(sf)})`
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (t.isBinaryExpression(node)) {
|
|
1174
|
+
const op = node.operatorToken.kind;
|
|
1175
|
+
const k = t.SyntaxKind;
|
|
1176
|
+
const logical = op === k.AmpersandAmpersandToken || op === k.BarBarToken;
|
|
1177
|
+
const arith = op === k.PlusToken || op === k.MinusToken || op === k.AsteriskToken || op === k.SlashToken || op === k.PercentToken || op === k.EqualsEqualsEqualsToken || op === k.ExclamationEqualsEqualsToken || op === k.EqualsEqualsToken || op === k.LessThanToken || op === k.LessThanEqualsToken || op === k.GreaterThanToken || op === k.GreaterThanEqualsToken;
|
|
1178
|
+
if (logical && isEast(node.left, ctx) && isEast(node.right, ctx)) {
|
|
1179
|
+
REPORT(ctx, node, "Host `&&`/`||` on East booleans \u2014 use East's `.and(() => \u2026)` / `.or(() => \u2026)`.");
|
|
1180
|
+
} else if (arith && (isEast(node.left, ctx) || isEast(node.right, ctx))) {
|
|
1181
|
+
REPORT(ctx, node, "Host operator on an East value \u2014 use the East method (`.add`/`.subtract`/`.multiply`/`.divide`) or `East.equal`/`East.less`/`East.greater`.");
|
|
1182
|
+
}
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
if (t.isPrefixUnaryExpression(node) && (node.operator === t.SyntaxKind.MinusToken || node.operator === t.SyntaxKind.ExclamationToken)) {
|
|
1186
|
+
if (isEast(node.operand, ctx)) {
|
|
1187
|
+
REPORT(ctx, node, "Host unary operator on an East value \u2014 use `.negate()` / `East.not`.");
|
|
1188
|
+
}
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (t.isElementAccessExpression(node)) {
|
|
1192
|
+
if (isEast(node.expression, ctx))
|
|
1193
|
+
return;
|
|
1194
|
+
REPORT(ctx, node, "Host index access on a JS value inside an East block \u2014 model the data as an East collection and read it with `.get(...)` / East ops, not `[i]`.");
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (t.isTemplateExpression(node) && !(node.parent !== void 0 && t.isTaggedTemplateExpression(node.parent))) {
|
|
1198
|
+
REPORT(ctx, node, "Host string interpolation inside an East block \u2014 build the string in East with `East.str`\u2026`` (or `str`\u2026``).");
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (t.isCallExpression(node)) {
|
|
1202
|
+
if (isEastCall(node, ctx))
|
|
1203
|
+
return;
|
|
1204
|
+
const callee = node.expression;
|
|
1205
|
+
const KEY_ACCESSORS = /* @__PURE__ */ new Set(["get", "tryGet", "has", "insert", "insertOrUpdate", "update", "remove"]);
|
|
1206
|
+
REPORT(ctx, node, t.isPropertyAccessExpression(callee) && KEY_ACCESSORS.has(callee.name.text) ? "Host call inside an East block \u2014 make it a real `East.function` (`$.const(East.function(...))`) or inline it as East." : "Host call inside an East block \u2014 this is an authoring-time macro over East. Make it a real `East.function` (`$.const(East.function(...))`), inline it as East, or use the East stdlib.");
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// ../east-diagnostics/dist/src/rules/no-module-scope-east-macro.js
|
|
1212
|
+
var NAME16 = "no-module-scope-east-macro";
|
|
1213
|
+
var CODE16 = 990011;
|
|
1214
|
+
var VALUE_CONSTRUCTORS = /* @__PURE__ */ new Set(["variant", "some"]);
|
|
1215
|
+
function unparen(e, t) {
|
|
1216
|
+
let cur = e;
|
|
1217
|
+
while (t.isParenthesizedExpression(cur))
|
|
1218
|
+
cur = cur.expression;
|
|
1219
|
+
return cur;
|
|
1220
|
+
}
|
|
1221
|
+
function isJsx2(node, t) {
|
|
1222
|
+
return t.isJsxElement(node) || t.isJsxFragment(node) || t.isJsxSelfClosingElement(node) || t.isParenthesizedExpression(node) && isJsx2(node.expression, t);
|
|
1223
|
+
}
|
|
1224
|
+
function isHostTemplate(e, t) {
|
|
1225
|
+
return t.isTemplateExpression(unparen(e, t));
|
|
1226
|
+
}
|
|
1227
|
+
function isEastValueConstructor(expr, t) {
|
|
1228
|
+
if (t.isCallExpression(expr)) {
|
|
1229
|
+
const callee = expr.expression;
|
|
1230
|
+
if (t.isIdentifier(callee) && VALUE_CONSTRUCTORS.has(callee.text))
|
|
1231
|
+
return true;
|
|
1232
|
+
return t.isPropertyAccessExpression(callee) && t.isIdentifier(callee.expression) && callee.expression.text === "East" && callee.name.text === "value";
|
|
1233
|
+
}
|
|
1234
|
+
return t.isIdentifier(expr) && expr.text === "none";
|
|
1235
|
+
}
|
|
1236
|
+
function isEastBuilderCall(call, ctx) {
|
|
1237
|
+
const t = ctx.ts;
|
|
1238
|
+
const callee = call.expression;
|
|
1239
|
+
if (t.isIdentifier(callee) && VALUE_CONSTRUCTORS.has(callee.text))
|
|
1240
|
+
return true;
|
|
1241
|
+
const root = chainRootReceiver(callee, ctx);
|
|
1242
|
+
if (t.isIdentifier(root) && root.text === "East")
|
|
1243
|
+
return true;
|
|
1244
|
+
return isBlockBuilderType(ctx.checker.getTypeAtLocation(root));
|
|
1245
|
+
}
|
|
1246
|
+
function containsEastBuilder(expr, ctx) {
|
|
1247
|
+
const t = ctx.ts;
|
|
1248
|
+
let found = false;
|
|
1249
|
+
const visit = (n) => {
|
|
1250
|
+
if (found)
|
|
1251
|
+
return;
|
|
1252
|
+
if (t.isCallExpression(n) && isEastBuilderCall(n, ctx)) {
|
|
1253
|
+
found = true;
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
t.forEachChild(n, visit);
|
|
1257
|
+
};
|
|
1258
|
+
visit(expr);
|
|
1259
|
+
return found;
|
|
1260
|
+
}
|
|
1261
|
+
function returnBuildsEast(r, ctx) {
|
|
1262
|
+
const t = ctx.ts;
|
|
1263
|
+
if (isJsx2(r, t))
|
|
1264
|
+
return false;
|
|
1265
|
+
if (isEastValueConstructor(r, t))
|
|
1266
|
+
return true;
|
|
1267
|
+
if (isEastExprType(ctx.checker.getTypeAtLocation(r)))
|
|
1268
|
+
return true;
|
|
1269
|
+
const root = chainRootReceiver(r, ctx);
|
|
1270
|
+
const rootType = ctx.checker.getTypeAtLocation(root);
|
|
1271
|
+
if (isEastExprType(rootType) || isBlockBuilderType(rootType))
|
|
1272
|
+
return true;
|
|
1273
|
+
return containsEastBuilder(r, ctx);
|
|
1274
|
+
}
|
|
1275
|
+
function returnExpressions2(fn, t) {
|
|
1276
|
+
if (fn.body === void 0)
|
|
1277
|
+
return [];
|
|
1278
|
+
if (!t.isBlock(fn.body))
|
|
1279
|
+
return [fn.body];
|
|
1280
|
+
const out = [];
|
|
1281
|
+
const visit = (n) => {
|
|
1282
|
+
if (t.isFunctionDeclaration(n) || t.isFunctionExpression(n) || t.isArrowFunction(n))
|
|
1283
|
+
return;
|
|
1284
|
+
if (t.isReturnStatement(n) && n.expression !== void 0)
|
|
1285
|
+
out.push(n.expression);
|
|
1286
|
+
t.forEachChild(n, visit);
|
|
1287
|
+
};
|
|
1288
|
+
t.forEachChild(fn.body, visit);
|
|
1289
|
+
return out;
|
|
1290
|
+
}
|
|
1291
|
+
var noModuleScopeEastMacro = {
|
|
1292
|
+
name: NAME16,
|
|
1293
|
+
code: CODE16,
|
|
1294
|
+
description: "Flag a module-scope TS helper that builds East values/IR or a composite string key \u2014 make it a real East.function or model typed/nested East data.",
|
|
1295
|
+
check(node, ctx) {
|
|
1296
|
+
const t = ctx.ts;
|
|
1297
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
1298
|
+
return;
|
|
1299
|
+
let fn;
|
|
1300
|
+
let reportNode;
|
|
1301
|
+
if (t.isFunctionDeclaration(node) && node.body !== void 0) {
|
|
1302
|
+
fn = node;
|
|
1303
|
+
reportNode = node.name ?? node;
|
|
1304
|
+
} else if (t.isVariableDeclaration(node) && node.initializer !== void 0 && (t.isArrowFunction(node.initializer) || t.isFunctionExpression(node.initializer))) {
|
|
1305
|
+
fn = node.initializer;
|
|
1306
|
+
reportNode = node.name;
|
|
1307
|
+
}
|
|
1308
|
+
if (fn === void 0 || reportNode === void 0)
|
|
1309
|
+
return;
|
|
1310
|
+
if (insideBlockScope(fn, ctx))
|
|
1311
|
+
return;
|
|
1312
|
+
const first = fn.parameters[0];
|
|
1313
|
+
if (first !== void 0 && isBlockBuilderType(ctx.checker.getTypeAtLocation(first.name)))
|
|
1314
|
+
return;
|
|
1315
|
+
const rs = returnExpressions2(fn, t);
|
|
1316
|
+
if (rs.some((r) => isJsx2(r, t)))
|
|
1317
|
+
return;
|
|
1318
|
+
if (rs.length === 0)
|
|
1319
|
+
return;
|
|
1320
|
+
const everyBuildsEast = rs.every((r) => returnBuildsEast(r, ctx));
|
|
1321
|
+
const everyHostKey = rs.every((r) => isHostTemplate(r, t));
|
|
1322
|
+
if (!everyBuildsEast && !everyHostKey)
|
|
1323
|
+
return;
|
|
1324
|
+
const sf = ctx.sourceFile;
|
|
1325
|
+
const start = reportNode.getStart(sf);
|
|
1326
|
+
ctx.report({
|
|
1327
|
+
ruleName: NAME16,
|
|
1328
|
+
code: CODE16,
|
|
1329
|
+
start,
|
|
1330
|
+
length: reportNode.getEnd() - start,
|
|
1331
|
+
messageText: everyHostKey ? "This helper builds a composite string key from a host template literal \u2014 the signature of a string-keyed data model. Model the data with typed keys / nested East structures instead." : "This module-scope TS helper builds East values/IR \u2014 an authoring-time macro that expands inline and can't be serialized. Make it a real `East.function`, or inline it.",
|
|
1332
|
+
category: "warning"
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
|
|
745
1337
|
// ../east-diagnostics/dist/src/rules/index.js
|
|
746
1338
|
var allRules = [
|
|
1339
|
+
// East-side idiom hygiene (original set)
|
|
747
1340
|
noRedundantEastCast,
|
|
748
1341
|
preferExplicitEastType,
|
|
749
1342
|
preferSomeNone,
|
|
@@ -754,9 +1347,15 @@ var allRules = [
|
|
|
754
1347
|
noLetConstInExpression,
|
|
755
1348
|
noUnexecutedEastExpression,
|
|
756
1349
|
noReinlinedEastBinding,
|
|
757
|
-
noEastDataBuilderHelper,
|
|
758
1350
|
preferJsxOverFactoryCall,
|
|
759
|
-
noUntrackedEastData
|
|
1351
|
+
noUntrackedEastData,
|
|
1352
|
+
// host-vs-East family: the general block rule + the module-scope macro rule,
|
|
1353
|
+
// plus the separate build-time-data concerns (ingestion primitives, and
|
|
1354
|
+
// host-computed e3.input seed data)
|
|
1355
|
+
noHostInEastBlock,
|
|
1356
|
+
noModuleScopeEastMacro,
|
|
1357
|
+
noCompileTimeDataInjection,
|
|
1358
|
+
noCompileTimeSeedData
|
|
760
1359
|
];
|
|
761
1360
|
|
|
762
1361
|
// ../east-diagnostics/dist/src/run.js
|
|
@@ -783,7 +1382,7 @@ function runEastRules(tsModule, program, sourceFile, checker, options = {}, rule
|
|
|
783
1382
|
|
|
784
1383
|
// ../east-diagnostics/dist/src/east-module.js
|
|
785
1384
|
var import_node_module = require("node:module");
|
|
786
|
-
var
|
|
1385
|
+
var import_node_path2 = require("node:path");
|
|
787
1386
|
var import_node_url = require("node:url");
|
|
788
1387
|
var cache = /* @__PURE__ */ new Map();
|
|
789
1388
|
var pendingImports = /* @__PURE__ */ new Set();
|
|
@@ -804,7 +1403,7 @@ function getEastModule(projectDir) {
|
|
|
804
1403
|
const cached = cache.get(projectDir);
|
|
805
1404
|
if (cached !== void 0)
|
|
806
1405
|
return cached ?? void 0;
|
|
807
|
-
const require_ = (0, import_node_module.createRequire)((0,
|
|
1406
|
+
const require_ = (0, import_node_module.createRequire)((0, import_node_path2.join)(projectDir, "_.js"));
|
|
808
1407
|
let entry;
|
|
809
1408
|
try {
|
|
810
1409
|
entry = require_.resolve("@elaraai/east");
|
|
@@ -1173,25 +1772,6 @@ var CATEGORY = {
|
|
|
1173
1772
|
warning: "Warning",
|
|
1174
1773
|
suggestion: "Suggestion"
|
|
1175
1774
|
};
|
|
1176
|
-
function isElaraaiPackageSrc(filePath) {
|
|
1177
|
-
const file = (0, import_node_path2.resolve)(filePath);
|
|
1178
|
-
let dir = (0, import_node_path2.dirname)(file);
|
|
1179
|
-
for (; ; ) {
|
|
1180
|
-
const candidate = (0, import_node_path2.join)(dir, "package.json");
|
|
1181
|
-
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
1182
|
-
try {
|
|
1183
|
-
const name = JSON.parse((0, import_node_fs.readFileSync)(candidate, "utf-8")).name;
|
|
1184
|
-
return typeof name === "string" && name.startsWith("@elaraai/") && file.startsWith((0, import_node_path2.join)(dir, "src") + "/");
|
|
1185
|
-
} catch {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
const parent = (0, import_node_path2.dirname)(dir);
|
|
1190
|
-
if (parent === dir)
|
|
1191
|
-
return false;
|
|
1192
|
-
dir = parent;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
1775
|
function init(modules) {
|
|
1196
1776
|
const t = modules.typescript;
|
|
1197
1777
|
return {
|
|
@@ -1216,19 +1796,17 @@ function init(modules) {
|
|
|
1216
1796
|
const rewritten = rewriteEastAssignability(t, program, sourceFile, d, east);
|
|
1217
1797
|
return rewritten === void 0 ? d : { ...d, messageText: rewritten };
|
|
1218
1798
|
});
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1799
|
+
const ruleDiagnostics = runEastRules(t, program, sourceFile, program.getTypeChecker(), info.config?.disabled !== void 0 ? { disabled: info.config.disabled } : {});
|
|
1800
|
+
for (const d of ruleDiagnostics) {
|
|
1801
|
+
diagnostics.push({
|
|
1802
|
+
file: sourceFile,
|
|
1803
|
+
start: d.start,
|
|
1804
|
+
length: d.length,
|
|
1805
|
+
messageText: `${d.messageText} (${d.ruleName})`,
|
|
1806
|
+
category: t.DiagnosticCategory[CATEGORY[d.category]],
|
|
1807
|
+
code: d.code,
|
|
1808
|
+
source: "east"
|
|
1809
|
+
});
|
|
1232
1810
|
}
|
|
1233
1811
|
return diagnostics;
|
|
1234
1812
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elaraai/tsserver-plugin-east",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"description": "TypeScript language service plugin for East — East idiom diagnostics and localized East type-diff error messages as native editor squiggles.",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"esbuild": "^0.27.3",
|
|
34
34
|
"typescript": "~5.9.2",
|
|
35
|
-
"@elaraai/east": "1.0.
|
|
36
|
-
"@elaraai/east-diagnostics": "1.0.
|
|
35
|
+
"@elaraai/east": "1.0.15",
|
|
36
|
+
"@elaraai/east-diagnostics": "1.0.15"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build.mjs",
|