@elaraai/tsserver-plugin-east 1.0.90 → 1.0.92

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 (2) hide show
  1. package/dist/index.cjs +122 -13
  2. package/package.json +3 -3
package/dist/index.cjs CHANGED
@@ -793,10 +793,112 @@ var noUnexecutedEastExpression = {
793
793
  // ../east-diagnostics/dist/src/rules/no-reinlined-east-binding.js
794
794
  var NAME10 = "no-reinlined-east-binding";
795
795
  var CODE10 = 990010;
796
+ var REPEATING_METHODS = /* @__PURE__ */ new Set([
797
+ "map",
798
+ "filter",
799
+ "filterMap",
800
+ "firstMap",
801
+ "flatMap",
802
+ "flattenToArray",
803
+ "flattenToSet",
804
+ "flattenToDict",
805
+ "forEach",
806
+ "reduce",
807
+ "scan",
808
+ "mapReduce",
809
+ "groupReduce",
810
+ "groupBy",
811
+ "groupSize",
812
+ "groupSum",
813
+ "groupMean",
814
+ "groupEvery",
815
+ "groupSome",
816
+ "groupMaximum",
817
+ "groupMinimum",
818
+ "groupToArrays",
819
+ "groupToSets",
820
+ "groupToDicts",
821
+ "groupFindAll",
822
+ "groupFindFirst",
823
+ "groupFindMaximum",
824
+ "groupFindMinimum",
825
+ "toDict",
826
+ "toSet",
827
+ "toArray",
828
+ "toTree",
829
+ "sort",
830
+ "sortInPlace",
831
+ "isSorted",
832
+ "findFirst",
833
+ "findAll",
834
+ "findMaximum",
835
+ "findMinimum",
836
+ "findSortedFirst",
837
+ "findSortedLast",
838
+ "findSortedRange",
839
+ "mergeAll",
840
+ "unionInPlace",
841
+ "mapRows",
842
+ "some",
843
+ "every",
844
+ "sum",
845
+ "mean",
846
+ "maximum",
847
+ "minimum",
848
+ "getKeys",
849
+ "generate"
850
+ ]);
851
+ var LOOP_METHODS = /* @__PURE__ */ new Set(["for", "while"]);
852
+ function isFunctionLike(node, t) {
853
+ return t.isArrowFunction(node) || t.isFunctionExpression(node) || t.isFunctionDeclaration(node) || t.isMethodDeclaration(node);
854
+ }
855
+ function enclosingFunction(node, t) {
856
+ let current = node.parent;
857
+ while (current !== void 0 && !isFunctionLike(current, t))
858
+ current = current.parent;
859
+ return current;
860
+ }
861
+ function hostKind(fn, ctx) {
862
+ const t = ctx.ts;
863
+ const call = fn.parent;
864
+ if (call === void 0 || !t.isCallExpression(call) || !call.arguments.some((arg) => arg === fn))
865
+ return void 0;
866
+ const callee = call.expression;
867
+ if (isEastFunctionCall(call, t))
868
+ return "nested function";
869
+ if (!t.isPropertyAccessExpression(callee))
870
+ return void 0;
871
+ const method = callee.name.text;
872
+ const receiver = callee.expression;
873
+ if (LOOP_METHODS.has(method)) {
874
+ return isBlockBuilderType(ctx.checker.getTypeAtLocation(receiver)) ? "loop body" : void 0;
875
+ }
876
+ if (!REPEATING_METHODS.has(method))
877
+ return void 0;
878
+ if (isEastExprType(ctx.checker.getTypeAtLocation(receiver)))
879
+ return "callback";
880
+ if (t.isPropertyAccessExpression(receiver) && t.isIdentifier(receiver.expression) && receiver.expression.text === "East") {
881
+ return "callback";
882
+ }
883
+ return void 0;
884
+ }
885
+ function repeatingHostBetween(use, declFunction, ctx) {
886
+ let found;
887
+ let current = use.parent;
888
+ while (current !== void 0 && current !== declFunction) {
889
+ if (isFunctionLike(current, ctx.ts)) {
890
+ const kind = hostKind(current, ctx);
891
+ if (kind !== void 0)
892
+ found = kind;
893
+ }
894
+ current = current.parent;
895
+ }
896
+ return found;
897
+ }
796
898
  var noReinlinedEastBinding = {
797
899
  name: NAME10,
798
900
  code: CODE10,
799
- description: "An East Expr bound to a JS const/let and reused inside an East block is re-inlined per use \u2014 bind it once with $.let/$.const.",
901
+ description: "An East Expr bound to a JS const/let and read twice inside one East body, or read inside a per-element callback, a loop body or a nested East.function, is re-inlined \u2014 and re-evaluated \u2014 at each read; bind it once with $.let/$.const.",
800
902
  check(node, ctx) {
801
903
  const t = ctx.ts;
802
904
  if (!t.isVariableDeclaration(node))
@@ -818,32 +920,39 @@ var noReinlinedEastBinding = {
818
920
  if (declSymbol === void 0)
819
921
  return;
820
922
  const name = node.name.text;
821
- const perBody = /* @__PURE__ */ new Map();
923
+ const declFunction = enclosingFunction(node, t);
924
+ const perFunction = /* @__PURE__ */ new Map();
925
+ let repeating;
822
926
  const visit = (n) => {
823
- if (t.isIdentifier(n) && n !== node.name && n.text === name) {
824
- if (ctx.checker.getSymbolAtLocation(n) === declSymbol) {
825
- const body = enclosingBlockScope(n, ctx);
826
- if (body !== void 0)
827
- perBody.set(body, (perBody.get(body) ?? 0) + 1);
927
+ if (t.isIdentifier(n) && n !== node.name && n.text === name && ctx.checker.getSymbolAtLocation(n) === declSymbol) {
928
+ if (enclosingBlockScope(n, ctx) !== void 0) {
929
+ const multiplied = repeatingHostBetween(n, declFunction, ctx);
930
+ if (multiplied !== void 0) {
931
+ repeating ??= multiplied;
932
+ } else {
933
+ const bucket = enclosingFunction(n, t) ?? ctx.sourceFile;
934
+ perFunction.set(bucket, (perFunction.get(bucket) ?? 0) + 1);
935
+ }
828
936
  }
829
937
  }
830
938
  t.forEachChild(n, visit);
831
939
  };
832
940
  visit(ctx.sourceFile);
833
- let maxInBody = 0;
834
- for (const count of perBody.values())
835
- if (count > maxInBody)
836
- maxInBody = count;
837
- if (maxInBody < 2)
941
+ let worst = 0;
942
+ for (const count of perFunction.values())
943
+ if (count > worst)
944
+ worst = count;
945
+ if (worst < 2 && repeating === void 0)
838
946
  return;
839
947
  const sf = ctx.sourceFile;
840
948
  const start = node.getStart(sf);
949
+ const messageText = worst >= 2 ? `This East expression is bound to a JS \`const\`/\`let\` and read ${worst} times inside one East body, so it is re-inlined \u2014 and re-evaluated, with a fresh identity for mutable values \u2014 at each read. Bind it once with \`$.const\`/\`$.let\`.` : `This East expression is bound to a JS \`const\`/\`let\` outside a ${repeating} and read inside it, so it is re-inlined there \u2014 and re-evaluated every time the ${repeating} runs. Bind it once with \`$.const\`/\`$.let\` before the call.`;
841
950
  ctx.report({
842
951
  ruleName: NAME10,
843
952
  code: CODE10,
844
953
  start,
845
954
  length: node.getEnd() - start,
846
- 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 \u2014 and re-evaluated, with a fresh identity for mutable values \u2014 at each use. Bind it once with `$.const`/`$.let`.",
955
+ messageText,
847
956
  category: "error"
848
957
  });
849
958
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elaraai/tsserver-plugin-east",
3
- "version": "1.0.90",
3
+ "version": "1.0.92",
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-diagnostics": "1.0.90",
36
- "@elaraai/east": "1.0.90"
35
+ "@elaraai/east": "1.0.92",
36
+ "@elaraai/east-diagnostics": "1.0.92"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "node scripts/build.mjs",