@elaraai/east-diagnostics 1.0.4

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 (87) hide show
  1. package/LICENSE.md +31 -0
  2. package/README.md +74 -0
  3. package/dist/src/block-builder.d.ts +16 -0
  4. package/dist/src/block-builder.d.ts.map +1 -0
  5. package/dist/src/block-builder.js +19 -0
  6. package/dist/src/block-builder.js.map +1 -0
  7. package/dist/src/block-scope.d.ts +27 -0
  8. package/dist/src/block-scope.d.ts.map +1 -0
  9. package/dist/src/block-scope.js +53 -0
  10. package/dist/src/block-scope.js.map +1 -0
  11. package/dist/src/east-type.d.ts +8 -0
  12. package/dist/src/east-type.d.ts.map +1 -0
  13. package/dist/src/east-type.js +28 -0
  14. package/dist/src/east-type.js.map +1 -0
  15. package/dist/src/index.d.ts +10 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/index.js +4 -0
  18. package/dist/src/index.js.map +1 -0
  19. package/dist/src/rules/index.d.ts +19 -0
  20. package/dist/src/rules/index.d.ts.map +1 -0
  21. package/dist/src/rules/index.js +39 -0
  22. package/dist/src/rules/index.js.map +1 -0
  23. package/dist/src/rules/no-east-data-builder-helper.d.ts +3 -0
  24. package/dist/src/rules/no-east-data-builder-helper.d.ts.map +1 -0
  25. package/dist/src/rules/no-east-data-builder-helper.js +90 -0
  26. package/dist/src/rules/no-east-data-builder-helper.js.map +1 -0
  27. package/dist/src/rules/no-east-namespaced-type.d.ts +7 -0
  28. package/dist/src/rules/no-east-namespaced-type.d.ts.map +1 -0
  29. package/dist/src/rules/no-east-namespaced-type.js +32 -0
  30. package/dist/src/rules/no-east-namespaced-type.js.map +1 -0
  31. package/dist/src/rules/no-handrolled-variant.d.ts +3 -0
  32. package/dist/src/rules/no-handrolled-variant.d.ts.map +1 -0
  33. package/dist/src/rules/no-handrolled-variant.js +45 -0
  34. package/dist/src/rules/no-handrolled-variant.js.map +1 -0
  35. package/dist/src/rules/no-let-const-in-expression.d.ts +3 -0
  36. package/dist/src/rules/no-let-const-in-expression.d.ts.map +1 -0
  37. package/dist/src/rules/no-let-const-in-expression.js +51 -0
  38. package/dist/src/rules/no-let-const-in-expression.js.map +1 -0
  39. package/dist/src/rules/no-redundant-east-cast.d.ts +3 -0
  40. package/dist/src/rules/no-redundant-east-cast.d.ts.map +1 -0
  41. package/dist/src/rules/no-redundant-east-cast.js +44 -0
  42. package/dist/src/rules/no-redundant-east-cast.js.map +1 -0
  43. package/dist/src/rules/no-reinlined-east-binding.d.ts +3 -0
  44. package/dist/src/rules/no-reinlined-east-binding.d.ts.map +1 -0
  45. package/dist/src/rules/no-reinlined-east-binding.js +79 -0
  46. package/dist/src/rules/no-reinlined-east-binding.js.map +1 -0
  47. package/dist/src/rules/no-relative-src-import.d.ts +3 -0
  48. package/dist/src/rules/no-relative-src-import.d.ts.map +1 -0
  49. package/dist/src/rules/no-relative-src-import.js +40 -0
  50. package/dist/src/rules/no-relative-src-import.js.map +1 -0
  51. package/dist/src/rules/no-ts-helpers-in-east.d.ts +3 -0
  52. package/dist/src/rules/no-ts-helpers-in-east.d.ts.map +1 -0
  53. package/dist/src/rules/no-ts-helpers-in-east.js +50 -0
  54. package/dist/src/rules/no-ts-helpers-in-east.js.map +1 -0
  55. package/dist/src/rules/no-unexecuted-east-expression.d.ts +3 -0
  56. package/dist/src/rules/no-unexecuted-east-expression.d.ts.map +1 -0
  57. package/dist/src/rules/no-unexecuted-east-expression.js +50 -0
  58. package/dist/src/rules/no-unexecuted-east-expression.js.map +1 -0
  59. package/dist/src/rules/prefer-explicit-east-type.d.ts +3 -0
  60. package/dist/src/rules/prefer-explicit-east-type.d.ts.map +1 -0
  61. package/dist/src/rules/prefer-explicit-east-type.js +60 -0
  62. package/dist/src/rules/prefer-explicit-east-type.js.map +1 -0
  63. package/dist/src/rules/prefer-jsx-over-factory-call.d.ts +3 -0
  64. package/dist/src/rules/prefer-jsx-over-factory-call.d.ts.map +1 -0
  65. package/dist/src/rules/prefer-jsx-over-factory-call.js +141 -0
  66. package/dist/src/rules/prefer-jsx-over-factory-call.js.map +1 -0
  67. package/dist/src/rules/prefer-let-const-over-east-value.d.ts +7 -0
  68. package/dist/src/rules/prefer-let-const-over-east-value.d.ts.map +1 -0
  69. package/dist/src/rules/prefer-let-const-over-east-value.js +71 -0
  70. package/dist/src/rules/prefer-let-const-over-east-value.js.map +1 -0
  71. package/dist/src/rules/prefer-some-none.d.ts +7 -0
  72. package/dist/src/rules/prefer-some-none.d.ts.map +1 -0
  73. package/dist/src/rules/prefer-some-none.js +36 -0
  74. package/dist/src/rules/prefer-some-none.js.map +1 -0
  75. package/dist/src/run.d.ts +11 -0
  76. package/dist/src/run.d.ts.map +1 -0
  77. package/dist/src/run.js +25 -0
  78. package/dist/src/run.js.map +1 -0
  79. package/dist/src/service.d.ts +19 -0
  80. package/dist/src/service.d.ts.map +1 -0
  81. package/dist/src/service.js +149 -0
  82. package/dist/src/service.js.map +1 -0
  83. package/dist/src/types.d.ts +56 -0
  84. package/dist/src/types.d.ts.map +1 -0
  85. package/dist/src/types.js +2 -0
  86. package/dist/src/types.js.map +1 -0
  87. package/package.json +49 -0
@@ -0,0 +1,51 @@
1
+ import { matchBlockBuilderCall } from "../block-builder.js";
2
+ const NAME = "no-let-const-in-expression";
3
+ const CODE = 990008;
4
+ // `$.let` / `$.const` declare a variable in the block (a statement-like effect)
5
+ // and return the expression handle. The only correct position is the initializer
6
+ // of a JS `const`/`let`. Used anywhere else — as an argument (`$.if($.let(...))`),
7
+ // a chain target (`$.let(...).add(1n)`), or an operand — it buries a declaration
8
+ // inside an expression. Type-checks fine, so the checker won't catch it.
9
+ export const noLetConstInExpression = {
10
+ name: NAME,
11
+ code: CODE,
12
+ description: "Require $.let/$.const to be bound to a const; disallow using the result inline in an expression.",
13
+ check(node, ctx) {
14
+ const match = matchBlockBuilderCall(node, ctx);
15
+ if (match === undefined)
16
+ return;
17
+ const t = ctx.ts;
18
+ const call = match.call;
19
+ // Walk out of wrapping parentheses.
20
+ let current = call;
21
+ let parent = current.parent;
22
+ while (parent !== undefined && t.isParenthesizedExpression(parent)) {
23
+ current = parent;
24
+ parent = parent.parent;
25
+ }
26
+ if (parent === undefined)
27
+ return;
28
+ // Flag only when the result is consumed inline: a chain target
29
+ // (`$.let(...).add(1n)`), an index target, an operator operand, or an
30
+ // argument to another call (`$.if($.let(...))`). A const/let initializer,
31
+ // a bare statement, `return $.const(...)`, and a concise arrow body are all
32
+ // valid and stay silent.
33
+ const usedInExpression = (t.isPropertyAccessExpression(parent) && parent.expression === current) ||
34
+ (t.isElementAccessExpression(parent) && parent.expression === current) ||
35
+ (t.isBinaryExpression(parent) && (parent.left === current || parent.right === current)) ||
36
+ (t.isCallExpression(parent) && parent.arguments.some((arg) => arg === current));
37
+ if (!usedInExpression)
38
+ return;
39
+ const sf = ctx.sourceFile;
40
+ const start = call.getStart(sf);
41
+ ctx.report({
42
+ ruleName: NAME,
43
+ code: CODE,
44
+ start,
45
+ length: call.getEnd() - start,
46
+ messageText: `\`$.${match.method}\` declares a variable — bind it to a \`const\` first (\`const x = $.${match.method}(value, Type)\`), don't use the result inline (e.g. \`$.if($.let(...))\` or \`$.let(...).add(...)\`).`,
47
+ category: "warning",
48
+ });
49
+ },
50
+ };
51
+ //# sourceMappingURL=no-let-const-in-expression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-let-const-in-expression.js","sourceRoot":"","sources":["../../../src/rules/no-let-const-in-expression.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,IAAI,GAAG,4BAA4B,CAAC;AAC1C,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,gFAAgF;AAChF,iFAAiF;AACjF,mFAAmF;AACnF,iFAAiF;AACjF,yEAAyE;AACzE,MAAM,CAAC,MAAM,sBAAsB,GAAa;IAC9C,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,WAAW,EACT,kGAAkG;IACpG,KAAK,CAAC,IAAI,EAAE,GAAG;QACb,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAEhC,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QAExB,oCAAoC;QACpC,IAAI,OAAO,GAAY,IAAI,CAAC;QAC5B,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC5B,OAAO,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,GAAG,MAAM,CAAC;YACjB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACzB,CAAC;QACD,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QAEjC,+DAA+D;QAC/D,sEAAsE;QACtE,0EAA0E;QAC1E,4EAA4E;QAC5E,yBAAyB;QACzB,MAAM,gBAAgB,GACpB,CAAC,CAAC,CAAC,0BAA0B,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC;YACvE,CAAC,CAAC,CAAC,yBAAyB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC;YACtE,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;YACvF,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC;QAClF,IAAI,CAAC,gBAAgB;YAAE,OAAO;QAE9B,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAChC,GAAG,CAAC,MAAM,CAAC;YACT,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,KAAK;YACL,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK;YAC7B,WAAW,EAAE,OAAO,KAAK,CAAC,MAAM,wEAAwE,KAAK,CAAC,MAAM,uGAAuG;YAC3N,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const noRedundantEastCast: EastRule;
3
+ //# sourceMappingURL=no-redundant-east-cast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-redundant-east-cast.d.ts","sourceRoot":"","sources":["../../../src/rules/no-redundant-east-cast.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAU5C,eAAO,MAAM,mBAAmB,EAAE,QAkCjC,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { matchBlockBuilderCall } from "../block-builder.js";
2
+ const NAME = "no-redundant-east-cast";
3
+ const CODE = 990001;
4
+ // `$.let` / `$.const` two-arg overload is `(<T>(expr: SubtypeExprOrValue<NoInfer<T>>, type: T) => ...)`:
5
+ // `NoInfer<T>` makes the East type argument drive inference, so a TS cast on the
6
+ // value is dead weight. (The one-arg overload infers from the value, where a
7
+ // cast *would* matter — hence we only fire when the type argument is present.)
8
+ export const noRedundantEastCast = {
9
+ name: NAME,
10
+ code: CODE,
11
+ description: "Disallow a TypeScript cast on the value argument of $.let/$.const when the East type argument is present (the type argument already drives inference).",
12
+ check(node, ctx) {
13
+ const match = matchBlockBuilderCall(node, ctx);
14
+ if (match === undefined || match.args.length < 2)
15
+ return;
16
+ const t = ctx.ts;
17
+ const value = match.args[0];
18
+ if (value === undefined)
19
+ return;
20
+ let inner;
21
+ if (t.isAsExpression(value))
22
+ inner = value.expression;
23
+ else if (t.isTypeAssertionExpression(value))
24
+ inner = value.expression;
25
+ if (inner === undefined)
26
+ return;
27
+ const sf = ctx.sourceFile;
28
+ const start = value.getStart(sf);
29
+ const length = value.getEnd() - start;
30
+ ctx.report({
31
+ ruleName: NAME,
32
+ code: CODE,
33
+ start,
34
+ length,
35
+ messageText: `Redundant cast: \`$.${match.method}\` infers the value type from the East type argument; drop the \`as …\` on the value.`,
36
+ category: "warning",
37
+ fix: {
38
+ description: "Remove redundant cast",
39
+ changes: [{ start, length, newText: inner.getText(sf) }],
40
+ },
41
+ });
42
+ },
43
+ };
44
+ //# sourceMappingURL=no-redundant-east-cast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-redundant-east-cast.js","sourceRoot":"","sources":["../../../src/rules/no-redundant-east-cast.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,IAAI,GAAG,wBAAwB,CAAC;AACtC,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,yGAAyG;AACzG,iFAAiF;AACjF,6EAA6E;AAC7E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAa;IAC3C,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,WAAW,EACT,wJAAwJ;IAC1J,KAAK,CAAC,IAAI,EAAE,GAAG;QACb,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QAEzD,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAEhC,IAAI,KAAgC,CAAC;QACrC,IAAI,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC;YAAE,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;aACjD,IAAI,CAAC,CAAC,yBAAyB,CAAC,KAAK,CAAC;YAAE,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;QACtE,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO;QAEhC,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC;QACtC,GAAG,CAAC,MAAM,CAAC;YACT,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,KAAK;YACL,MAAM;YACN,WAAW,EAAE,uBAAuB,KAAK,CAAC,MAAM,uFAAuF;YACvI,QAAQ,EAAE,SAAS;YACnB,GAAG,EAAE;gBACH,WAAW,EAAE,uBAAuB;gBACpC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;aACzD;SACF,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const noReinlinedEastBinding: EastRule;
3
+ //# sourceMappingURL=no-reinlined-east-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-reinlined-east-binding.d.ts","sourceRoot":"","sources":["../../../src/rules/no-reinlined-east-binding.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAsB5C,eAAO,MAAM,sBAAsB,EAAE,QAuDpC,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { isEastExprType } from "../east-type.js";
2
+ import { matchBlockBuilderCall } from "../block-builder.js";
3
+ import { enclosingBlockScope } from "../block-scope.js";
4
+ const NAME = "no-reinlined-east-binding";
5
+ const CODE = 990010;
6
+ // An East `Expr` is a value tree, not a slot. Bound to a JS `const`/`let` and
7
+ // referenced more than once inside the same East block, it is *re-inlined* at
8
+ // each use — the tree is duplicated, so it is re-evaluated per use and (for
9
+ // mutable values) gets a fresh identity each time. `$.let`/`$.const` introduce
10
+ // a single block binding evaluated once and referenced by name, which is the
11
+ // only way to get single-evaluation + shared identity. So multi-use JS bindings
12
+ // of an `Expr` are an error, not a style nit.
13
+ //
14
+ // Scoped to the hazard: a single use is a harmless alias / single-pass argument,
15
+ // and inline composition (`Stack.VStack([A(), B(), …])`) never binds, so neither
16
+ // trips. Re-inlining only duplicates within one East block tree, so the count is
17
+ // bucketed per enclosing East block scope (an `East.function` body or, for JSX
18
+ // authoring, a `<Reactive>{$ => …}}` / `ui(…)` BlockBuilder callback).
19
+ export const noReinlinedEastBinding = {
20
+ name: NAME,
21
+ code: CODE,
22
+ description: "An East Expr bound to a JS const/let and reused inside an East block is re-inlined per use — bind it once with $.let/$.const.",
23
+ check(node, ctx) {
24
+ const t = ctx.ts;
25
+ if (!t.isVariableDeclaration(node))
26
+ return;
27
+ if (!t.isIdentifier(node.name))
28
+ return;
29
+ if (node.initializer === undefined)
30
+ return;
31
+ let init = node.initializer;
32
+ while (t.isParenthesizedExpression(init))
33
+ init = init.expression;
34
+ // `$.let(...)` / `$.const(...)` is the correct form — never flag it.
35
+ if (matchBlockBuilderCall(init, ctx) !== undefined)
36
+ return;
37
+ // Aliasing an existing binding (`const a = b`) re-inlines nothing; the root
38
+ // binding, if unbound, is flagged at its own declaration.
39
+ if (t.isIdentifier(init))
40
+ return;
41
+ // Only Expr-typed bindings re-inline. Types (`StructType(...)`), plain JS
42
+ // objects, callbacks held for a single handoff, etc. are fine.
43
+ if (!isEastExprType(ctx.checker.getTypeAtLocation(init)))
44
+ return;
45
+ const declSymbol = ctx.checker.getSymbolAtLocation(node.name);
46
+ if (declSymbol === undefined)
47
+ return;
48
+ const name = node.name.text;
49
+ const perBody = new Map();
50
+ const visit = (n) => {
51
+ if (t.isIdentifier(n) && n !== node.name && n.text === name) {
52
+ if (ctx.checker.getSymbolAtLocation(n) === declSymbol) {
53
+ const body = enclosingBlockScope(n, ctx);
54
+ if (body !== undefined)
55
+ perBody.set(body, (perBody.get(body) ?? 0) + 1);
56
+ }
57
+ }
58
+ t.forEachChild(n, visit);
59
+ };
60
+ visit(ctx.sourceFile);
61
+ let maxInBody = 0;
62
+ for (const count of perBody.values())
63
+ if (count > maxInBody)
64
+ maxInBody = count;
65
+ if (maxInBody < 2)
66
+ return;
67
+ const sf = ctx.sourceFile;
68
+ const start = node.getStart(sf);
69
+ ctx.report({
70
+ ruleName: NAME,
71
+ code: CODE,
72
+ start,
73
+ length: node.getEnd() - start,
74
+ messageText: "This East expression is bound to a JS `const`/`let` and used more than once inside an East block, so it is re-inlined — and re-evaluated, with a fresh identity for mutable values — at each use. Bind it once with `$.const`/`$.let`.",
75
+ category: "error",
76
+ });
77
+ },
78
+ };
79
+ //# sourceMappingURL=no-reinlined-east-binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-reinlined-east-binding.js","sourceRoot":"","sources":["../../../src/rules/no-reinlined-east-binding.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,IAAI,GAAG,2BAA2B,CAAC;AACzC,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,+EAA+E;AAC/E,6EAA6E;AAC7E,gFAAgF;AAChF,8CAA8C;AAC9C,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,iFAAiF;AACjF,+EAA+E;AAC/E,uEAAuE;AAEvE,MAAM,CAAC,MAAM,sBAAsB,GAAa;IAC9C,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,WAAW,EACT,+HAA+H;IACjI,KAAK,CAAC,IAAI,EAAE,GAAG;QACb,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACjB,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC;YAAE,OAAO;QAC3C,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO;QACvC,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,OAAO;QAE3C,IAAI,IAAI,GAAkB,IAAI,CAAC,WAAW,CAAC;QAC3C,OAAO,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAEjE,qEAAqE;QACrE,IAAI,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,SAAS;YAAE,OAAO;QAC3D,4EAA4E;QAC5E,0DAA0D;QAC1D,IAAI,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;YAAE,OAAO;QACjC,0EAA0E;QAC1E,+DAA+D;QAC/D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO;QAEjE,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO;QAErC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;QAC3C,MAAM,KAAK,GAAG,CAAC,CAAU,EAAQ,EAAE;YACjC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC5D,IAAI,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;oBACtD,MAAM,IAAI,GAAG,mBAAmB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;oBACzC,IAAI,IAAI,KAAK,SAAS;wBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC1E,CAAC;YACH,CAAC;YACD,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC,CAAC;QACF,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAEtB,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE;YAAE,IAAI,KAAK,GAAG,SAAS;gBAAE,SAAS,GAAG,KAAK,CAAC;QAC/E,IAAI,SAAS,GAAG,CAAC;YAAE,OAAO;QAE1B,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAChC,GAAG,CAAC,MAAM,CAAC;YACT,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,KAAK;YACL,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK;YAC7B,WAAW,EACT,wOAAwO;YAC1O,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const noRelativeSrcImport: EastRule;
3
+ //# sourceMappingURL=no-relative-src-import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-relative-src-import.d.ts","sourceRoot":"","sources":["../../../src/rules/no-relative-src-import.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAW5C,eAAO,MAAM,mBAAmB,EAAE,QA6BjC,CAAC"}
@@ -0,0 +1,40 @@
1
+ const NAME = "no-relative-src-import";
2
+ const CODE = 990007;
3
+ const DEEP_PACKAGE_SRC = /^@elaraai\/[^/]+\/src(\/|$)/;
4
+ const RELATIVE_SRC = /\/src(\/|$)/;
5
+ // Tests and examples must import East packages by their published name
6
+ // (`@elaraai/east`), not reach into a package's `src/` via a relative path or a
7
+ // deep `/src` specifier — those bypass the package's public API surface.
8
+ export const noRelativeSrcImport = {
9
+ name: NAME,
10
+ code: CODE,
11
+ description: "Import East packages by published name, not via ../src or a deep /src path.",
12
+ check(node, ctx) {
13
+ const t = ctx.ts;
14
+ let specifier;
15
+ if (t.isImportDeclaration(node))
16
+ specifier = node.moduleSpecifier;
17
+ else if (t.isExportDeclaration(node))
18
+ specifier = node.moduleSpecifier;
19
+ else
20
+ return;
21
+ if (specifier === undefined || !t.isStringLiteral(specifier))
22
+ return;
23
+ const text = specifier.text;
24
+ const relativeIntoSrc = text.startsWith(".") && RELATIVE_SRC.test(text);
25
+ const deepPackageSrc = DEEP_PACKAGE_SRC.test(text);
26
+ if (!relativeIntoSrc && !deepPackageSrc)
27
+ return;
28
+ const sf = ctx.sourceFile;
29
+ const start = specifier.getStart(sf);
30
+ ctx.report({
31
+ ruleName: NAME,
32
+ code: CODE,
33
+ start,
34
+ length: specifier.getEnd() - start,
35
+ messageText: "Import East packages by their published name (e.g. `@elaraai/east`), not a relative `../src/...` path or a deep `/src` import.",
36
+ category: "warning",
37
+ });
38
+ },
39
+ };
40
+ //# sourceMappingURL=no-relative-src-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-relative-src-import.js","sourceRoot":"","sources":["../../../src/rules/no-relative-src-import.ts"],"names":[],"mappings":"AAOA,MAAM,IAAI,GAAG,wBAAwB,CAAC;AACtC,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,MAAM,gBAAgB,GAAG,6BAA6B,CAAC;AACvD,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC,uEAAuE;AACvE,gFAAgF;AAChF,yEAAyE;AACzE,MAAM,CAAC,MAAM,mBAAmB,GAAa;IAC3C,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,WAAW,EAAE,6EAA6E;IAC1F,KAAK,CAAC,IAAI,EAAE,GAAG;QACb,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACjB,IAAI,SAAoC,CAAC;QACzC,IAAI,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC;aAC7D,IAAI,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC;;YAClE,OAAO;QAEZ,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC;YAAE,OAAO;QACrE,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC;QAC5B,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,MAAM,cAAc,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,IAAI,CAAC,cAAc;YAAE,OAAO;QAEhD,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrC,GAAG,CAAC,MAAM,CAAC;YACT,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,KAAK;YACL,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,KAAK;YAClC,WAAW,EACT,gIAAgI;YAClI,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const noTsHelpersInEast: EastRule;
3
+ //# sourceMappingURL=no-ts-helpers-in-east.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-ts-helpers-in-east.d.ts","sourceRoot":"","sources":["../../../src/rules/no-ts-helpers-in-east.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAiB5C,eAAO,MAAM,iBAAiB,EAAE,QAsC/B,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { isEastExprType } from "../east-type.js";
2
+ const NAME = "no-ts-helpers-in-east";
3
+ const CODE = 990010;
4
+ // A TypeScript helper that constructs East values — a `function`/`const` whose
5
+ // return type is an East `Expr` — should be inlined at each call site;
6
+ // repetition is preferred over a `node()`/`link()`-style builder.
7
+ //
8
+ // Keyed on *declarations* (function declarations and `const fn = (…) => …`),
9
+ // which excludes every inline callback (East.function bodies, `.map`, event
10
+ // handlers are bare arrow *arguments*, never declarations). Note: a component
11
+ // factory (`createButton(): ExprType<UIComponentType>`) is structurally
12
+ // identical, so this rule is APPLICATION-SCOPED — the daemon must not run it on
13
+ // East library-factory packages. Hence it is exported separately, not in the
14
+ // default `allRules`.
15
+ export const noTsHelpersInEast = {
16
+ name: NAME,
17
+ code: CODE,
18
+ description: "Disallow TypeScript helpers that build East values (functions returning an East expression); inline at the call site.",
19
+ check(node, ctx) {
20
+ const t = ctx.ts;
21
+ let fnLike;
22
+ let reportNode;
23
+ if (t.isFunctionDeclaration(node) && node.body !== undefined && node.name !== undefined) {
24
+ fnLike = node;
25
+ reportNode = node.name;
26
+ }
27
+ else if (t.isVariableDeclaration(node) &&
28
+ node.initializer !== undefined &&
29
+ (t.isArrowFunction(node.initializer) || t.isFunctionExpression(node.initializer))) {
30
+ fnLike = node.initializer;
31
+ reportNode = node.name;
32
+ }
33
+ if (fnLike === undefined || reportNode === undefined)
34
+ return;
35
+ const signature = ctx.checker.getSignatureFromDeclaration(fnLike);
36
+ if (signature === undefined || !isEastExprType(signature.getReturnType()))
37
+ return;
38
+ const sf = ctx.sourceFile;
39
+ const start = reportNode.getStart(sf);
40
+ ctx.report({
41
+ ruleName: NAME,
42
+ code: CODE,
43
+ start,
44
+ length: reportNode.getEnd() - start,
45
+ messageText: "This TypeScript helper builds an East value (it returns an East expression). Inline the construction at each call site instead of factoring it into a helper — repetition is preferred.",
46
+ category: "warning",
47
+ });
48
+ },
49
+ };
50
+ //# sourceMappingURL=no-ts-helpers-in-east.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-ts-helpers-in-east.js","sourceRoot":"","sources":["../../../src/rules/no-ts-helpers-in-east.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,IAAI,GAAG,uBAAuB,CAAC;AACrC,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,+EAA+E;AAC/E,uEAAuE;AACvE,kEAAkE;AAClE,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,wEAAwE;AACxE,gFAAgF;AAChF,6EAA6E;AAC7E,sBAAsB;AACtB,MAAM,CAAC,MAAM,iBAAiB,GAAa;IACzC,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,WAAW,EACT,uHAAuH;IACzH,KAAK,CAAC,IAAI,EAAE,GAAG;QACb,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QAEjB,IAAI,MAA2C,CAAC;QAChD,IAAI,UAA+B,CAAC;QACpC,IAAI,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACxF,MAAM,GAAG,IAAI,CAAC;YACd,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QACzB,CAAC;aAAM,IACL,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC;YAC7B,IAAI,CAAC,WAAW,KAAK,SAAS;YAC9B,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EACjF,CAAC;YACD,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;YAC1B,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QACzB,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO;QAE7D,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC;QAClE,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC;YAAE,OAAO;QAElF,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC,MAAM,CAAC;YACT,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,KAAK;YACL,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,GAAG,KAAK;YACnC,WAAW,EACT,yLAAyL;YAC3L,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const noUnexecutedEastExpression: EastRule;
3
+ //# sourceMappingURL=no-unexecuted-east-expression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unexecuted-east-expression.d.ts","sourceRoot":"","sources":["../../../src/rules/no-unexecuted-east-expression.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAY5C,eAAO,MAAM,0BAA0B,EAAE,QAuCxC,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { isEastExprType, isBlockBuilderType } from "../east-type.js";
2
+ const NAME = "no-unexecuted-east-expression";
3
+ const CODE = 990009;
4
+ // A bare expression statement whose value is an East `Expr` is dead: East
5
+ // expressions are pure values, so unless executed with `$( … )` or bound with
6
+ // `$.let`/`$.const`, the statement has no effect. Catches both calling an
7
+ // effectful function without `$()` (`log(msg);`) and `East.value(x);` standing
8
+ // alone. The `$( … )` / `$.x( … )` execution forms return `void`, and are
9
+ // excluded explicitly so renamed builders never false-positive.
10
+ export const noUnexecutedEastExpression = {
11
+ name: NAME,
12
+ code: CODE,
13
+ description: "Flag a bare East expression statement that is never executed with $() or bound — it has no effect.",
14
+ check(node, ctx) {
15
+ const t = ctx.ts;
16
+ if (!t.isExpressionStatement(node))
17
+ return;
18
+ const expr = node.expression;
19
+ // Exclude any statement rooted in the block builder `$`: `$(...)`, `$.if(...)`,
20
+ // chained `$.if(...).else(...)`, `$.try(...).catch(...)`, etc. Walk down the
21
+ // call/member chain to its base receiver and check that.
22
+ let root = expr;
23
+ for (;;) {
24
+ if (t.isCallExpression(root)) {
25
+ root = root.expression;
26
+ }
27
+ else if (t.isPropertyAccessExpression(root) || t.isElementAccessExpression(root)) {
28
+ root = root.expression;
29
+ }
30
+ else {
31
+ break;
32
+ }
33
+ }
34
+ if (isBlockBuilderType(ctx.checker.getTypeAtLocation(root)))
35
+ return;
36
+ if (!isEastExprType(ctx.checker.getTypeAtLocation(expr)))
37
+ return;
38
+ const sf = ctx.sourceFile;
39
+ const start = expr.getStart(sf);
40
+ ctx.report({
41
+ ruleName: NAME,
42
+ code: CODE,
43
+ start,
44
+ length: expr.getEnd() - start,
45
+ messageText: "This East expression is never executed or bound, so it has no effect. Wrap it in `$( … )` to run it for its effect, or bind it with `$.let` / `$.const`.",
46
+ category: "warning",
47
+ });
48
+ },
49
+ };
50
+ //# sourceMappingURL=no-unexecuted-east-expression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unexecuted-east-expression.js","sourceRoot":"","sources":["../../../src/rules/no-unexecuted-east-expression.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErE,MAAM,IAAI,GAAG,+BAA+B,CAAC;AAC7C,MAAM,IAAI,GAAG,MAAM,CAAC;AAEpB,0EAA0E;AAC1E,8EAA8E;AAC9E,0EAA0E;AAC1E,+EAA+E;AAC/E,0EAA0E;AAC1E,gEAAgE;AAChE,MAAM,CAAC,MAAM,0BAA0B,GAAa;IAClD,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,WAAW,EACT,oGAAoG;IACtG,KAAK,CAAC,IAAI,EAAE,GAAG;QACb,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACjB,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC;YAAE,OAAO;QAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;QAE7B,gFAAgF;QAChF,6EAA6E;QAC7E,yDAAyD;QACzD,IAAI,IAAI,GAAY,IAAI,CAAC;QACzB,SAAS,CAAC;YACR,IAAI,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;YACzB,CAAC;iBAAM,IAAI,CAAC,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnF,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC;YACzB,CAAC;iBAAM,CAAC;gBACN,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO;QAEpE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO;QAEjE,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAChC,GAAG,CAAC,MAAM,CAAC;YACT,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,KAAK;YACL,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK;YAC7B,WAAW,EACT,0JAA0J;YAC5J,QAAQ,EAAE,SAAS;SACpB,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const preferExplicitEastType: EastRule;
3
+ //# sourceMappingURL=prefer-explicit-east-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-explicit-east-type.d.ts","sourceRoot":"","sources":["../../../src/rules/prefer-explicit-east-type.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,aAAa,CAAC;AAiCtD,eAAO,MAAM,sBAAsB,EAAE,QAiCpC,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { matchBlockBuilderCall } from "../block-builder.js";
2
+ const NAME = "prefer-explicit-east-type";
3
+ const CODE = 990002;
4
+ function isEmptyNewMapOrSet(node, t) {
5
+ if (!t.isNewExpression(node) || !t.isIdentifier(node.expression))
6
+ return false;
7
+ const ctor = node.expression.text;
8
+ if (ctor !== "Map" && ctor !== "Set")
9
+ return false;
10
+ return node.arguments === undefined || node.arguments.length === 0;
11
+ }
12
+ function isRawValueLiteral(node, t) {
13
+ return (t.isNumericLiteral(node) ||
14
+ t.isBigIntLiteral(node) ||
15
+ t.isStringLiteralLike(node) ||
16
+ node.kind === t.SyntaxKind.TrueKeyword ||
17
+ node.kind === t.SyntaxKind.FalseKeyword ||
18
+ node.kind === t.SyntaxKind.NullKeyword ||
19
+ t.isArrayLiteralExpression(node) ||
20
+ t.isObjectLiteralExpression(node) ||
21
+ t.isNewExpression(node));
22
+ }
23
+ // The one-arg `$.let(x)` / `$.const(x)` form infers the East type from the JS
24
+ // value. That is fine when `x` is already an East expression (type fully
25
+ // determined), but for an under-determined literal — `[]`, `{}`, `new Map()` —
26
+ // the inferred type is `never`/empty and almost always wrong. Nudge toward the
27
+ // explicit two-arg form. Stays silent on East expressions so `$.let(arr.sum())`
28
+ // is not flagged.
29
+ export const preferExplicitEastType = {
30
+ name: NAME,
31
+ code: CODE,
32
+ description: "Encourage passing the East type as the second argument to $.let/$.const for raw JS values whose East type is under-determined.",
33
+ check(node, ctx) {
34
+ const match = matchBlockBuilderCall(node, ctx);
35
+ if (match === undefined || match.args.length !== 1)
36
+ return;
37
+ const t = ctx.ts;
38
+ const value = match.args[0];
39
+ if (value === undefined)
40
+ return;
41
+ const underDetermined = (t.isArrayLiteralExpression(value) && value.elements.length === 0) ||
42
+ (t.isObjectLiteralExpression(value) && value.properties.length === 0) ||
43
+ isEmptyNewMapOrSet(value, t);
44
+ const mode = ctx.options.preferExplicitEastType?.mode ?? "under-determined";
45
+ const flag = underDetermined || (mode === "all-raw-values" && isRawValueLiteral(value, t));
46
+ if (!flag)
47
+ return;
48
+ const sf = ctx.sourceFile;
49
+ const start = value.getStart(sf);
50
+ ctx.report({
51
+ ruleName: NAME,
52
+ code: CODE,
53
+ start,
54
+ length: value.getEnd() - start,
55
+ messageText: `Provide the East type as the second argument to \`$.${match.method}\` — e.g. \`$.${match.method}([], ArrayType(FloatType))\` — to pin the value type; it is under-determined from the value alone.`,
56
+ category: "suggestion",
57
+ });
58
+ },
59
+ };
60
+ //# sourceMappingURL=prefer-explicit-east-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-explicit-east-type.js","sourceRoot":"","sources":["../../../src/rules/prefer-explicit-east-type.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { EastRule } from "../types.js";
2
+ export declare const preferJsxOverFactoryCall: EastRule;
3
+ //# sourceMappingURL=prefer-jsx-over-factory-call.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-jsx-over-factory-call.d.ts","sourceRoot":"","sources":["../../../src/rules/prefer-jsx-over-factory-call.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAyB,MAAM,aAAa,CAAC;AAoHnE,eAAO,MAAM,wBAAwB,EAAE,QA0CtC,CAAC"}
@@ -0,0 +1,141 @@
1
+ const NAME = "prefer-jsx-over-factory-call";
2
+ const CODE = 990012;
3
+ // The signal that a `Foo.Root(...)` factory call has a `<Foo>` JSX tag form is
4
+ // not the import path — it's the RESULT TYPE. Every UI component factory's
5
+ // `.Root(...)` returns the same type a JSX element evaluates to (`JSX.Element`
6
+ // for this file's JSX runtime). So a `.Root(...)` whose result is that element
7
+ // type is, by construction, authorable as a tag — independent of which package
8
+ // it came from. This keys on the type, so it generalises to any JSX-producing
9
+ // factory and needs no per-package list.
10
+ // Resolving `JSX.Element` walks the filesystem, so memoize it per file (the
11
+ // SourceFile is rebuilt on every program, so the key is naturally invalidated).
12
+ // `null` records "no JSX element type here" so we don't re-resolve on each call.
13
+ const jsxElementCache = new WeakMap();
14
+ /** The `JSX.Element` type for `sourceFile`'s active JSX runtime, or undefined. */
15
+ function jsxElementType(ctx) {
16
+ const cached = jsxElementCache.get(ctx.sourceFile);
17
+ if (cached !== undefined)
18
+ return cached ?? undefined;
19
+ const computed = computeJsxElementType(ctx);
20
+ jsxElementCache.set(ctx.sourceFile, computed ?? null);
21
+ return computed;
22
+ }
23
+ function computeJsxElementType(ctx) {
24
+ const t = ctx.ts;
25
+ const elementOf = (ns) => {
26
+ const el = ctx.checker.getExportsOfModule(ns).find((s) => s.name === "Element");
27
+ return el ? ctx.checker.getDeclaredTypeOfSymbol(el) : undefined;
28
+ };
29
+ // Classic / global runtime: a visible `JSX` namespace (global or imported).
30
+ const resolveName = ctx.checker.resolveName;
31
+ const globalNs = resolveName?.("JSX", ctx.sourceFile, t.SymbolFlags.Namespace, false);
32
+ if (globalNs !== undefined) {
33
+ const fromGlobal = elementOf(globalNs);
34
+ if (fromGlobal !== undefined)
35
+ return fromGlobal;
36
+ }
37
+ // Automatic runtime (`jsx: react-jsx` + `jsxImportSource`): the `JSX` namespace
38
+ // lives in `<source>/jsx-runtime`, not globally. Resolve that module and read
39
+ // its `JSX.Element`. Needs the program for compiler options + resolution.
40
+ const program = ctx.program;
41
+ if (program === undefined)
42
+ return undefined;
43
+ const options = program.getCompilerOptions();
44
+ const base = jsxImportSourceFor(options, ctx.sourceFile, t);
45
+ if (base === undefined)
46
+ return undefined;
47
+ // Resolve exactly as TypeScript does. The host checks the program's loaded
48
+ // source files first (so in-memory programs resolve), then falls through to
49
+ // `ts.sys` for everything else on disk (package.json `exports`, node_modules).
50
+ const resolutionHost = {
51
+ fileExists: (f) => program.getSourceFile(f) !== undefined || t.sys.fileExists(f),
52
+ readFile: (f) => program.getSourceFile(f)?.text ?? t.sys.readFile(f),
53
+ directoryExists: (d) => t.sys.directoryExists(d),
54
+ getCurrentDirectory: () => t.sys.getCurrentDirectory(),
55
+ getDirectories: (d) => t.sys.getDirectories(d),
56
+ };
57
+ if (t.sys.realpath !== undefined)
58
+ resolutionHost.realpath = (p) => t.sys.realpath(p);
59
+ const resolved = t.resolveModuleName(`${base}/jsx-runtime`, ctx.sourceFile.fileName, options, resolutionHost).resolvedModule?.resolvedFileName;
60
+ if (resolved === undefined)
61
+ return undefined;
62
+ const runtimeSf = program.getSourceFile(resolved);
63
+ const moduleSym = runtimeSf ? ctx.checker.getSymbolAtLocation(runtimeSf) : undefined;
64
+ if (moduleSym === undefined)
65
+ return undefined;
66
+ const jsxNs = ctx.checker.getExportsOfModule(moduleSym).find((s) => s.name === "JSX");
67
+ return jsxNs ? elementOf(jsxNs) : undefined;
68
+ }
69
+ /** The JSX import source in effect for `sourceFile` (per-file pragma overrides). */
70
+ function jsxImportSourceFor(options, sourceFile, t) {
71
+ const pragmas = sourceFile.pragmas;
72
+ const pragma = pragmas?.get("jsximportsource");
73
+ const fromPragma = Array.isArray(pragma)
74
+ ? pragma[pragma.length - 1]?.arguments?.factory
75
+ : pragma?.arguments?.factory;
76
+ if (fromPragma !== undefined)
77
+ return fromPragma;
78
+ // Only the automatic runtime carries an implicit import source.
79
+ if (options.jsx !== t.JsxEmit.ReactJSX && options.jsx !== t.JsxEmit.ReactJSXDev)
80
+ return undefined;
81
+ return options.jsxImportSource ?? "react";
82
+ }
83
+ /** True when `a` and `b` are the same type (mutually assignable). */
84
+ function sameType(a, b, checker) {
85
+ const c = checker;
86
+ if (c.isTypeAssignableTo === undefined)
87
+ return a === b; // older TS: identity only
88
+ return c.isTypeAssignableTo(a, b) && c.isTypeAssignableTo(b, a);
89
+ }
90
+ // In a `.tsx` file, `Foo.Root(...)` on a plain identifier whose result is the
91
+ // file's JSX element type is the un-migrated form — author it as `<Foo>`. Gated:
92
+ // JSX file only; `Ident.Root(...)` on a bare identifier (so `Slice.Frame.Root(...)`
93
+ // / `Slice.config(...)` never match); and the call's result type is JSX.Element
94
+ // (so a `.Root` returning anything else — a config object, a number — is silent).
95
+ export const preferJsxOverFactoryCall = {
96
+ name: NAME,
97
+ code: CODE,
98
+ description: "In a .tsx file, prefer the <Foo> JSX tag over a factory's Foo.Root(...) when the call produces a JSX element.",
99
+ check(node, ctx) {
100
+ const t = ctx.ts;
101
+ if (ctx.sourceFile.languageVariant !== t.LanguageVariant.JSX)
102
+ return;
103
+ if (!t.isCallExpression(node))
104
+ return;
105
+ const callee = node.expression;
106
+ if (!t.isPropertyAccessExpression(callee))
107
+ return;
108
+ if (callee.name.text !== "Root")
109
+ return;
110
+ if (!t.isIdentifier(callee.expression))
111
+ return;
112
+ const factoryIdent = callee.expression;
113
+ const element = jsxElementType(ctx);
114
+ if (element === undefined)
115
+ return;
116
+ const result = ctx.checker.getTypeAtLocation(node);
117
+ if (!sameType(result, element, ctx.checker))
118
+ return;
119
+ // The tag is named after the factory's *exported* name, not the local
120
+ // binding — so an aliased import (`import { Box as BoxFactory }`) still
121
+ // suggests `<Box>`, not `<BoxFactory>`.
122
+ let tagName = factoryIdent.text;
123
+ const sym = ctx.checker.getSymbolAtLocation(factoryIdent);
124
+ if (sym !== undefined && (sym.flags & t.SymbolFlags.Alias) !== 0) {
125
+ const target = ctx.checker.getAliasedSymbol(sym);
126
+ if (target.name.length > 0)
127
+ tagName = target.name;
128
+ }
129
+ const sf = ctx.sourceFile;
130
+ const start = callee.getStart(sf);
131
+ ctx.report({
132
+ ruleName: NAME,
133
+ code: CODE,
134
+ start,
135
+ length: callee.getEnd() - start,
136
+ messageText: `Author this with the \`<${tagName}>\` JSX tag instead of \`${factoryIdent.text}.Root(...)\` — in a .tsx file the JSX tag is the authoring surface (the call already produces a JSX element).`,
137
+ category: "suggestion",
138
+ });
139
+ },
140
+ };
141
+ //# sourceMappingURL=prefer-jsx-over-factory-call.js.map