@velarscript/web 0.27.4 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,15 @@ export declare class VelarWebAnalyzer extends Analyzer {
45
45
  * name resolves to even where a narrowed copy answers the lookup.
46
46
  */
47
47
  private readonly computedBindingSpans;
48
+ /**
49
+ * D114 W: the declaration spans of every `resource` in scope, kept the way
50
+ * `computedBindingSpans` keeps derived values — the question is asked of the
51
+ * binding a name resolves to, so a local shadow of a resource's name is not
52
+ * one.
53
+ */
54
+ private readonly resourceBindingSpans;
55
+ /** D114 W A2(b): this module's `action` and `async def` bodies by name. */
56
+ private reactiveWriters;
48
57
  /** Local names bound to an imported `export computed`, from the Web interface. */
49
58
  private readonly importedComputedNames;
50
59
  /** The resolved spans of those imports, so a local shadow of the name is not one. */
@@ -84,6 +93,11 @@ export declare class VelarWebAnalyzer extends Analyzer {
84
93
  * to `WEB_OWNED_TYPE_NAMES` extends this protection with it. The last time
85
94
  * this family was repaired by listing names instead of deriving them, the
86
95
  * list drifted; D57 rule 135 is the same repair on the Core roster.
96
+ *
97
+ * Core now says the same sentence about its own built-in type names —
98
+ * `builtinTypeNameDeclarationMessage` in packages/compiler/src/analyzer.ts,
99
+ * reported as VEL3007. The rosters differ; the wording is meant to read
100
+ * alike, so a change to either sentence belongs in both.
87
101
  */
88
102
  private rejectWebOwnedTypeNames;
89
103
  /**
@@ -117,6 +131,12 @@ export declare class VelarWebAnalyzer extends Analyzer {
117
131
  * shadow is writable state.
118
132
  */
119
133
  private isComputedBinding;
134
+ /**
135
+ * True when `name` resolves to a `resource` declaration. Asked of the binding
136
+ * rather than of the spelling, for the reason `isComputedBinding` is: a local
137
+ * `state` may shadow a resource's name, and the shadow is not a resource.
138
+ */
139
+ private isResourceBinding;
120
140
  private isImportedComputedBinding;
121
141
  /**
122
142
  * D71 rule 184: a cross-module `computed` travels through `reactiveExports`
@@ -203,6 +223,32 @@ export declare class VelarWebAnalyzer extends Analyzer {
203
223
  * source behind it at all, and a computed subject has one but hides which.
204
224
  */
205
225
  private rejectFrozenWatchSubject;
226
+ /**
227
+ * D114 W: the three watch shapes a compile can prove re-trigger the watch
228
+ * itself. D90 R21 removed the analysis of *who writes what* across calls, and
229
+ * nothing here brings it back: two watches writing one state are still an
230
+ * ordinary program, a write reached through an ordinary helper is still
231
+ * silent, and a write under `if`, `match`, a loop, `try`, a nested `def` or an
232
+ * arrow is still the author's converging correction to make.
233
+ *
234
+ * What is refused is only what is decided at the top of the body, with no
235
+ * condition to end it:
236
+ *
237
+ * - **B** the body writes the watched place itself (`watch count: count =
238
+ * count + 1`), assignment, compound assignment, or a mutating collection
239
+ * call on the watched collection;
240
+ * - **A2(a)** the subject is a `resource` field and the body reloads that
241
+ * same resource — a reload writes exactly those fields, so every completed
242
+ * load re-triggers the watch;
243
+ * - **A2(b)** the body starts an `action` or an `async def` of this module
244
+ * whose own top level writes the watched place. One hop, one module, no
245
+ * condition on either end; anything further is the runtime budget's.
246
+ *
247
+ * One diagnostic per watch, at the first statement that earns it. A watch with
248
+ * two of these has two mistakes, and the second is read after the first is
249
+ * fixed, exactly as two errors on one line are.
250
+ */
251
+ private rejectWatchCycle;
206
252
  /**
207
253
  * D89 A4: records `list = list.map(item => {…})`, React's immutable update,
208
254
  * where the callback builds a new record rather than changing a field.
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,KAAK,UAAU,EAA2C,KAAK,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClK,OAAO,EACL,QAAQ,EAgBR,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,UAAU,EAEf,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,iCAAiC,CAAC;AA6FzC,eAAO,MAAM,oBAAoB,iDAAiD,CAAC;AAmHnF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gCAAgC,GAAG,SAAS,GAAG,SAAS,CAsLlG;AAi4CD,qBAAa,gBAAiB,SAAQ,QAAQ;IAC5C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAAK;IACzB,iHAAiH;IACjH,OAAO,CAAC,cAAc,CAAK;IAC3B,4FAA4F;IAC5F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,wFAAwF;IACxF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0B;IAC5D,sGAAsG;IACtG,OAAO,CAAC,eAAe,CAAuE;IAC9F,OAAO,CAAC,wBAAwB,CAAK;IACrC,OAAO,CAAC,QAAQ,CAAK;IACrB,iFAAiF;IACjF,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAgC;IACnE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuC;IAChF,OAAO,CAAC,gBAAgB,CAAmD;IAC3E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkC;IAClE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAC1D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsF;IACpH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,gBAAgB,CAA0C;IAClE;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,gBAAgB,CAA4D;IACpF,OAAO,CAAC,gBAAgB,CAAK;IAC7B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAC1D,kFAAkF;IAClF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAsB;IAC5D,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAqB;IAC3D,gHAAgH;IAChH,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAiD;IAC7F,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAClE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqE;IAC/G,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;IAC7D,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2B;IAC1D,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,8FAA8F;IAC9F,OAAO,CAAC,4BAA4B,CAA0C;IAE9E,YAAY,OAAO,GAAE,eAAoB,EAAE,UAAU,GAAE,SAAS,yBAAyB,EAAO,EAY/F;IAEQ,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,UAAU,EAAE,CAgBxD;IAED;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IAShC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,uBAAuB;IA6C/B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAW3B,UAAmB,4BAA4B,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAM7E;IAED,UAAmB,yBAAyB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CA2G1E;IAED,UAAmB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAc9D;IAED,OAAO,CAAC,0BAA0B;IAelC,OAAO,CAAC,eAAe;IAWvB,UAAmB,gCAAgC,CAAC,SAAS,EAAE,SAAS,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAM/H;IAED;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;IAiBlC;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,yBAAyB;IAKjC;;;;;;;OAOG;IACH,UAAmB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,OAAO,GAAG,MAAgB,GAAG,IAAI,CAWnG;IAED,UAAmB,wBAAwB,CAAC,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CA2GrH;IAED,UAAmB,eAAe,CAAC,UAAU,EAAE,UAAU,EAAE,cAAc,GAAE,SAAuB,GAAG,SAAS,CA4B7G;IAID,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,6BAA6B;IAoBrC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,6BAA6B;IAerC;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,wBAAwB;IA6ChC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,6BAA6B;IAWrC;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;IAajC;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAqB/B,0EAA0E;IAC1E,OAAO,CAAC,kBAAkB;IAK1B;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IA0B1B,8FAA8F;IAC9F,OAAO,CAAC,eAAe;IAQvB;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;IAehC;;;;;;OAMG;IACH,OAAO,CAAC,gCAAgC;IAwBxC,wFAAwF;IACxF,OAAO,CAAC,yBAAyB;IAkBjC;;;;;;;OAOG;IACH,OAAO,CAAC,6BAA6B;IAmCrC,UAAmB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAI,CAExF;IAED,UAAmB,kBAAkB,CACnC,MAAM,EAAE,OAAO,iCAAiC,EAAE,kBAAkB,EACpE,UAAU,EAAE,SAAS,UAAU,EAAE,EACjC,aAAa,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,EACrD,QAAQ,EAAE,IAAI,GACb,SAAS,GAAG,SAAS,CASvB;IAED,UAAmB,2BAA2B,CAC5C,MAAM,EAAE,OAAO,iCAAiC,EAAE,UAAU,EAC5D,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,iCAAiC,EAAE,UAAU,KAAK,OAAO,EACnF,OAAO,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,SAAS,GAC/C,OAAO,GAAG,SAAS,CA8CrB;IAED;;;;;OAKG;IACH,UAAmB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAM1D;IAED,UAAmB,4BAA4B,IAAI,OAAO,CAGzD;IAED,UAAmB,4BAA4B,IAAI,MAAM,GAAG,IAAI,CAI/D;IAED,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,0BAA0B;IAyBlC,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,gBAAgB;IA6HxB,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,2BAA2B;IAQnC,UAAmB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAE/E;IAED,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,kBAAkB;IA8D1B;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAgF5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IAgD1B;;;;;;;;OAQG;IACH,OAAO,CAAC,2BAA2B;IAyBnC,iGAAiG;IACjG,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,uBAAuB;IAkC/B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAgCxB,OAAO,CAAC,2BAA2B;IAqBnC;;;;;;;OAOG;IACH,OAAO,CAAC,0BAA0B;IASlC;;;;;;;OAOG;IACH,OAAO,CAAC,0BAA0B;IAclC;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAoDxB,+FAA+F;IAC/F,OAAO,CAAC,eAAe;IAKvB;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;IAiBhC,uFAAuF;IACvF,OAAO,CAAC,uBAAuB;IAY/B,8FAA8F;IAC9F,OAAO,CAAC,kBAAkB;IAe1B,mFAAmF;IACnF,OAAO,CAAC,qBAAqB;IAgB7B,uFAAuF;IACvF,OAAO,CAAC,iBAAiB;IAKzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,uBAAuB;IAe/B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAgFpC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;;;OAIG;IACH,OAAO,CAAC,2BAA2B;IAWnC,OAAO,CAAC,kBAAkB;IAiE1B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,QAAQ;IAiEhB;;;;;;;;OAQG;IACH,OAAO,CAAC,yBAAyB;IAwBjC;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,uBAAuB;IAoB/B,OAAO,CAAC,wBAAwB;IAuBhC,OAAO,CAAC,uBAAuB;IAqF/B;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAuB/B;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA6BvC,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,yBAAyB;IAyJjC;;;;;;;;;OASG;IACH,OAAO,CAAC,iCAAiC;IA4BzC,uFAAuF;IACvF,OAAO,CAAC,wBAAwB;IAQhC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,6BAA6B;IAwFrC;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAc/B;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAwBpB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IA0BxB,OAAO,CAAC,yBAAyB;IAcjC,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;IAWvB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,gBAAgB;IAQxB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,oBAAoB;IA6B5B,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,4BAA4B;CAmBrC"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
package/dist/analyzer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { mechanicalEdits, mechanicalFix, semanticTypeIdentity } from "@velarscript/compiler";
2
- import { Analyzer, anyType, boolType, describeType, expressionContainsDirectAwait, invalidType, isInvalidType, isAssignable, isReadonlyView, nullType, nonOptional, numberType, optionalOf, spanIdentity, stringType, unknownType, } from "@velarscript/compiler/extension";
2
+ import { Analyzer, anyType, boolType, describeType, expressionContainsDirectAwait, invalidType, isInvalidType, isAssignable, isReadonlyView, mutatingCollectionMethods, nullType, nonOptional, numberType, optionalOf, spanIdentity, stringType, unknownType, } from "@velarscript/compiler/extension";
3
3
  import { BROWSER_TEST_MODULE, BROWSER_TEST_SOURCE_SUFFIX, browserTestImportGuidance } from "./browser-test.js";
4
4
  import { cssTokens } from "./css-tokens.js";
5
5
  import { LOOK_ABSENT_MEDIA_SUBJECTS, LOOK_ARITHMETIC_HINT, LOOK_ANIMATION_DIRECTIONS, LOOK_ANIMATION_EASINGS, LOOK_ANIMATION_FILLS, LOOK_BORDER_STYLE_NAMES, LOOK_BUILDER_NUMERIC_RANGES, LOOK_BUILDER_SIGNATURES, LOOK_BUILDERS, LOOK_EXCLUDED_PROPERTIES, LOOK_HOOKS, LOOK_CSS_WIDE_KEYWORDS, LOOK_COLOR_KEYWORDS, LOOK_LARGE_KEYWORD_SETS, LOOK_LENGTH_BUILDERS, LOOK_MEDIA_LENGTH_UNITS, LOOK_MEDIA_SUBJECTS, LOOK_NUMERIC_TYPE_NAMES, LOOK_NON_ANIMATABLE_PROPERTIES, LOOK_PARTIAL_KEYWORD_PROPERTIES, LOOK_PROPERTIES, LOOK_PROPERTY_KEYWORDS, LOOK_PROPERTY_VALUE_KINDS, LOOK_SHARED_METRIC_KEYWORDS, LOOK_TARGETS, LOOK_TOKEN_NAME_RULE, LOOK_TOKEN_NO_FALLBACK_GUIDANCE, LOOK_UNIT_TYPES, LOOK_UNITLESS_PROPERTIES, isLookTokenName, isLookVarReference, lookOwnKeywords, lookShorthandOverlap, lookShorthandParts, lookTokenReference, lookVarReferenceName, nearestLookName, } from "./look.js";
@@ -1181,6 +1181,169 @@ function watchSubjectPath(expression) {
1181
1181
  return false;
1182
1182
  }
1183
1183
  }
1184
+ /**
1185
+ * D114 W: a reactive place written as one comparable key, so "the write and the
1186
+ * subject name the same place" is one string equality.
1187
+ *
1188
+ * It is deliberately narrower than `renderWatchSubject`, which reconstructs any
1189
+ * expression for a message. A key has to *decide*, so only the parts that name
1190
+ * the same place on two evaluations are allowed into one: names, fields, and an
1191
+ * index that is either a literal or another such path. `items[next()]` renders
1192
+ * perfectly well and answers a different element every call, so it has no key
1193
+ * and the shapes below stay silent on it — which is the right answer for a
1194
+ * refusal that has to be right every time.
1195
+ */
1196
+ function reactiveWritePath(expression) {
1197
+ switch (expression.kind) {
1198
+ case "IdentifierExpression":
1199
+ return expression.name;
1200
+ case "MemberExpression": {
1201
+ if (expression.optional)
1202
+ return null;
1203
+ const object = reactiveWritePath(expression.object);
1204
+ return object === null ? null : `${object}.${expression.property}`;
1205
+ }
1206
+ case "IndexExpression": {
1207
+ if (expression.optional)
1208
+ return null;
1209
+ const object = reactiveWritePath(expression.object);
1210
+ if (object === null)
1211
+ return null;
1212
+ const index = expression.index.kind === "LiteralExpression"
1213
+ ? (typeof expression.index.value === "string" ? JSON.stringify(expression.index.value) : expression.index.raw)
1214
+ : reactiveWritePath(expression.index);
1215
+ return index === null ? null : `${object}[${index}]`;
1216
+ }
1217
+ default:
1218
+ return null;
1219
+ }
1220
+ }
1221
+ /** The root name a reactive path starts from, which is the binding it resolves through. */
1222
+ function reactivePathRoot(expression) {
1223
+ switch (expression.kind) {
1224
+ case "IdentifierExpression":
1225
+ return expression.name;
1226
+ case "MemberExpression":
1227
+ case "IndexExpression":
1228
+ return reactivePathRoot(expression.object);
1229
+ default:
1230
+ return null;
1231
+ }
1232
+ }
1233
+ function bindingPatternBinds(pattern, name) {
1234
+ switch (pattern.kind) {
1235
+ case "NameBindingPattern":
1236
+ return pattern.name === name;
1237
+ case "ObjectBindingPattern":
1238
+ return pattern.rest?.name === name || pattern.entries.some((entry) => bindingPatternBinds(entry.pattern, name));
1239
+ case "ListBindingPattern":
1240
+ return pattern.rest?.name === name
1241
+ || pattern.elements.some((element) => element !== null && bindingPatternBinds(element, name));
1242
+ default:
1243
+ return false;
1244
+ }
1245
+ }
1246
+ /**
1247
+ * D114 W: whether a body statement introduces its own binding of `name`. From
1248
+ * that statement on, the spelling names something else, and a write through it
1249
+ * is not a write of the watched place. The scan stops there rather than
1250
+ * guessing which of the two a later line meant.
1251
+ */
1252
+ function statementBindsName(statement, name) {
1253
+ switch (statement.kind) {
1254
+ case "VariableDeclaration":
1255
+ return bindingPatternBinds(statement.pattern, name);
1256
+ case "UsingDeclaration":
1257
+ case "FunctionDeclaration":
1258
+ case "ClassDeclaration":
1259
+ return statement.name === name;
1260
+ default:
1261
+ return false;
1262
+ }
1263
+ }
1264
+ /**
1265
+ * D114 W: the call a body statement makes when the statement is nothing but
1266
+ * that call. `detach` is included because it is how a synchronous watch body
1267
+ * starts asynchronous work — the tour and four charter fences spell the reload
1268
+ * that way — so a refusal that only saw the bare call would miss the shape it
1269
+ * exists for. Everything else (a call inside an `if`, an argument, an assigned
1270
+ * result) is not a plain top-level call and is not offered here.
1271
+ */
1272
+ function topLevelCall(statement) {
1273
+ const expression = statement.kind === "ExpressionStatement" ? statement.expression
1274
+ : statement.kind === "DetachStatement" ? statement.expression
1275
+ : null;
1276
+ return expression !== null && expression.kind === "CallExpression" ? expression : null;
1277
+ }
1278
+ /**
1279
+ * D114 W: whether one plain body statement writes the reactive place `path`.
1280
+ * An assignment or a compound assignment to it is one; so is a call of a
1281
+ * mutating collection method on it, because a watch on a collection fires on
1282
+ * its deep mutation and `mutating` is the compiler's own roster of the calls
1283
+ * that mutate.
1284
+ */
1285
+ function reactiveWriteOf(statement, path, mutating) {
1286
+ if (statement.kind === "AssignmentStatement")
1287
+ return reactiveWritePath(statement.target) === path;
1288
+ if (mutating === null)
1289
+ return false;
1290
+ const call = statement.kind === "ExpressionStatement" && statement.expression.kind === "CallExpression"
1291
+ ? statement.expression
1292
+ : null;
1293
+ if (call === null || call.callee.kind !== "MemberExpression" || call.callee.optional)
1294
+ return false;
1295
+ return mutating.has(call.callee.property) && reactiveWritePath(call.callee.object) === path;
1296
+ }
1297
+ /**
1298
+ * D114 W A2(b): whether an `action` or `async def` writes `path` at its own top
1299
+ * level, unconditionally. One hop: what the callee itself calls is not
1300
+ * followed. A parameter of the callee's own that is spelled like the path's
1301
+ * root, or a binding it declares before the write, means the write is not of
1302
+ * the watched place and the answer is no.
1303
+ */
1304
+ function writerWritesPath(writer, path, root, mutating) {
1305
+ if (writer.parameters.includes(root))
1306
+ return false;
1307
+ for (const statement of writer.body) {
1308
+ if (statementBindsName(statement, root))
1309
+ return false;
1310
+ if (reactiveWriteOf(statement, path, mutating))
1311
+ return true;
1312
+ }
1313
+ return false;
1314
+ }
1315
+ function collectReactiveWriters(program) {
1316
+ const writers = new Map();
1317
+ const claim = (name, declaration) => {
1318
+ writers.set(name, writers.has(name) ? null : declaration);
1319
+ };
1320
+ const record = (statements) => {
1321
+ for (const statement of statements) {
1322
+ if (statement.kind === "FunctionDeclaration") {
1323
+ claim(statement.name, statement.asynchronous
1324
+ ? { spelling: "async def", parameters: statement.parameters.map((parameter) => parameter.name), body: statement.body }
1325
+ : null);
1326
+ record(statement.body);
1327
+ continue;
1328
+ }
1329
+ if (!isWebStatement(statement))
1330
+ continue;
1331
+ if (statement.kind === "ExtensionStatement:web:action") {
1332
+ claim(statement.name, {
1333
+ spelling: "action",
1334
+ parameters: statement.parameters.map((parameter) => parameter.name),
1335
+ body: statement.body,
1336
+ });
1337
+ record(statement.body);
1338
+ continue;
1339
+ }
1340
+ if (statement.kind === "ExtensionStatement:web:component")
1341
+ record(statement.body);
1342
+ }
1343
+ };
1344
+ record(program.body);
1345
+ return writers;
1346
+ }
1184
1347
  /** The escapes a text literal carries back into source (`scanStringEscape`). */
1185
1348
  const WATCH_SUBJECT_TEXT_ESCAPES = {
1186
1349
  "\\": "\\\\",
@@ -1782,6 +1945,15 @@ export class VelarWebAnalyzer extends Analyzer {
1782
1945
  * name resolves to even where a narrowed copy answers the lookup.
1783
1946
  */
1784
1947
  computedBindingSpans = new Set();
1948
+ /**
1949
+ * D114 W: the declaration spans of every `resource` in scope, kept the way
1950
+ * `computedBindingSpans` keeps derived values — the question is asked of the
1951
+ * binding a name resolves to, so a local shadow of a resource's name is not
1952
+ * one.
1953
+ */
1954
+ resourceBindingSpans = new Set();
1955
+ /** D114 W A2(b): this module's `action` and `async def` bodies by name. */
1956
+ reactiveWriters = new Map();
1785
1957
  /** Local names bound to an imported `export computed`, from the Web interface. */
1786
1958
  importedComputedNames;
1787
1959
  /** The resolved spans of those imports, so a local shadow of the name is not one. */
@@ -1820,6 +1992,7 @@ export class VelarWebAnalyzer extends Analyzer {
1820
1992
  this.keyedListSources.clear();
1821
1993
  this.keyedListRebuilds.length = 0;
1822
1994
  this.moduleFunctions = collectModuleFunctions(program);
1995
+ this.reactiveWriters = collectReactiveWriters(program);
1823
1996
  super.analyze(program);
1824
1997
  this.reportStaticJsxKeys();
1825
1998
  this.reportRetiredComputedFunction();
@@ -1856,6 +2029,11 @@ export class VelarWebAnalyzer extends Analyzer {
1856
2029
  * to `WEB_OWNED_TYPE_NAMES` extends this protection with it. The last time
1857
2030
  * this family was repaired by listing names instead of deriving them, the
1858
2031
  * list drifted; D57 rule 135 is the same repair on the Core roster.
2032
+ *
2033
+ * Core now says the same sentence about its own built-in type names —
2034
+ * `builtinTypeNameDeclarationMessage` in packages/compiler/src/analyzer.ts,
2035
+ * reported as VEL3007. The rosters differ; the wording is meant to read
2036
+ * alike, so a change to either sentence belongs in both.
1859
2037
  */
1860
2038
  rejectWebOwnedTypeNames(program) {
1861
2039
  const reject = (name, errorSpan, noun) => {
@@ -1992,7 +2170,9 @@ export class VelarWebAnalyzer extends Analyzer {
1992
2170
  // the body only runs on a later change, so its reads are deferred
1993
2171
  // for the module-initialization-cycle classification.
1994
2172
  const watched = this.inferExpression(statement.expression);
1995
- this.rejectFrozenWatchSubject(statement.expression, watched, statement.currentName, statement.previousName);
2173
+ if (this.rejectFrozenWatchSubject(statement.expression, watched, statement.currentName, statement.previousName)) {
2174
+ this.rejectWatchCycle(statement.expression, watched, statement.body);
2175
+ }
1996
2176
  this.enterScope();
1997
2177
  if (statement.currentName)
1998
2178
  this.declareBinding(statement.currentName, false, watched, statement.span);
@@ -2127,6 +2307,15 @@ export class VelarWebAnalyzer extends Analyzer {
2127
2307
  const binding = this.lookup(name);
2128
2308
  return binding !== null && this.computedBindingSpans.has(spanIdentity(binding.span));
2129
2309
  }
2310
+ /**
2311
+ * True when `name` resolves to a `resource` declaration. Asked of the binding
2312
+ * rather than of the spelling, for the reason `isComputedBinding` is: a local
2313
+ * `state` may shadow a resource's name, and the shadow is not a resource.
2314
+ */
2315
+ isResourceBinding(name) {
2316
+ const binding = this.lookup(name);
2317
+ return binding !== null && this.resourceBindingSpans.has(spanIdentity(binding.span));
2318
+ }
2130
2319
  isImportedComputedBinding(name) {
2131
2320
  const binding = this.lookup(name);
2132
2321
  return binding !== null && this.importedComputedSpans.has(spanIdentity(binding.span));
@@ -2415,14 +2604,14 @@ export class VelarWebAnalyzer extends Analyzer {
2415
2604
  const name = expression.kind === "IdentifierExpression" ? expression.name : null;
2416
2605
  if (name !== null && this.reactiveBindingKind(name) === null && this.zeroArgumentReader(watched)) {
2417
2606
  this.diagnostics.push(diagnostic("VEL5064", `'${name}' is the reader itself, so watching it watches a value that never changes; declare the derived value — 'computed name = ${name}()' — then 'watch name:'`, expression.span));
2418
- return;
2607
+ return false;
2419
2608
  }
2420
2609
  if (this.frozenWatchSubject(expression)) {
2421
2610
  this.diagnostics.push(diagnostic("VEL5064", `This watch subject never changes, so its body can never run${name === null ? "" : ` — '${name}' is not a reactive source`}; watch a 'state', a 'computed', a prop, or a resource field, or move these statements to where they should run`, expression.span));
2422
- return;
2611
+ return false;
2423
2612
  }
2424
2613
  if (watchSubjectPath(expression))
2425
- return;
2614
+ return true;
2426
2615
  // D69's own shape, `watch total()`, is a called `computed`, and VEL5063 has
2427
2616
  // already named it with the one-character edit that makes this subject
2428
2617
  // legal. Stacking the shape rule on top would report one mistake twice and
@@ -2430,7 +2619,7 @@ export class VelarWebAnalyzer extends Analyzer {
2430
2619
  // VEL5063 in its turn. The same reason the frozen rule is asked first.
2431
2620
  if (this.diagnostics.some((item) => item.code === "VEL5063"
2432
2621
  && item.span.start === expression.span.start && item.span.end === expression.span.end))
2433
- return;
2622
+ return false;
2434
2623
  const derived = currentName ?? "value";
2435
2624
  const watchLine = currentName === null
2436
2625
  ? `watch ${derived}:`
@@ -2439,6 +2628,82 @@ export class VelarWebAnalyzer extends Analyzer {
2439
2628
  this.diagnostics.push(diagnostic("VEL5071", rendered === null
2440
2629
  ? `A watch subject names what to watch, not what to compute. Declare the value — 'computed ${derived} = ...' — then '${watchLine}'`
2441
2630
  : `A watch subject names what to watch, not what to compute: '${rendered}' computes a value. Declare it — 'computed ${derived} = ${rendered}' — then '${watchLine}'`, expression.span));
2631
+ return false;
2632
+ }
2633
+ /**
2634
+ * D114 W: the three watch shapes a compile can prove re-trigger the watch
2635
+ * itself. D90 R21 removed the analysis of *who writes what* across calls, and
2636
+ * nothing here brings it back: two watches writing one state are still an
2637
+ * ordinary program, a write reached through an ordinary helper is still
2638
+ * silent, and a write under `if`, `match`, a loop, `try`, a nested `def` or an
2639
+ * arrow is still the author's converging correction to make.
2640
+ *
2641
+ * What is refused is only what is decided at the top of the body, with no
2642
+ * condition to end it:
2643
+ *
2644
+ * - **B** the body writes the watched place itself (`watch count: count =
2645
+ * count + 1`), assignment, compound assignment, or a mutating collection
2646
+ * call on the watched collection;
2647
+ * - **A2(a)** the subject is a `resource` field and the body reloads that
2648
+ * same resource — a reload writes exactly those fields, so every completed
2649
+ * load re-triggers the watch;
2650
+ * - **A2(b)** the body starts an `action` or an `async def` of this module
2651
+ * whose own top level writes the watched place. One hop, one module, no
2652
+ * condition on either end; anything further is the runtime budget's.
2653
+ *
2654
+ * One diagnostic per watch, at the first statement that earns it. A watch with
2655
+ * two of these has two mistakes, and the second is read after the first is
2656
+ * fixed, exactly as two errors on one line are.
2657
+ */
2658
+ rejectWatchCycle(subject, watched, body) {
2659
+ const root = reactivePathRoot(subject);
2660
+ if (root === null)
2661
+ return;
2662
+ const path = reactiveWritePath(subject);
2663
+ const collection = nonOptional(this.expandAliases(watched));
2664
+ const mutating = collection.kind === "list" || collection.kind === "map"
2665
+ || collection.kind === "set" || collection.kind === "record"
2666
+ ? mutatingCollectionMethods(collection.kind)
2667
+ : null;
2668
+ // A resource publishes `value`, `loading`, `ready` and `error`, and
2669
+ // `reload` is the one member of the five that is not one of them. Asking it
2670
+ // that way keeps `analyzeResourceDeclaration`'s field map the only roster:
2671
+ // a field added there is a field this recognises, with nothing to update.
2672
+ const resource = subject.kind === "MemberExpression" && !subject.optional
2673
+ && subject.object.kind === "IdentifierExpression" && subject.property !== "reload"
2674
+ && this.isResourceBinding(subject.object.name)
2675
+ ? subject.object.name
2676
+ : null;
2677
+ for (const statement of body) {
2678
+ if (statementBindsName(statement, root))
2679
+ return;
2680
+ if (path !== null && reactiveWriteOf(statement, path, mutating)) {
2681
+ // A derived value is offered only where it could be declared. A field
2682
+ // or an element has no `computed` spelling of its own, so naming one
2683
+ // would hand the author a line that does not compile.
2684
+ const derived = subject.kind === "IdentifierExpression"
2685
+ ? `declare 'computed ${path} = ...' instead`
2686
+ : "write this value where it is produced instead";
2687
+ this.diagnostics.push(diagnostic("VEL5077", `This watch writes its own subject '${path}' at the top of its body, so every run re-triggers it and the runtime stops the loop after 100 rounds; write the condition that ends it, or watch the input this value follows and ${derived}`, statement.span));
2688
+ return;
2689
+ }
2690
+ const call = topLevelCall(statement);
2691
+ if (call === null)
2692
+ continue;
2693
+ if (resource !== null && call.callee.kind === "MemberExpression" && !call.callee.optional
2694
+ && call.callee.property === "reload" && call.callee.object.kind === "IdentifierExpression"
2695
+ && call.callee.object.name === resource) {
2696
+ this.diagnostics.push(diagnostic("VEL5078", `This watch reloads '${resource}' — the resource it watches — so every completed load re-triggers it; watch the input the load reads instead, as 'watch userId:' with 'detach ${resource}.reload()' in its body`, call.span));
2697
+ return;
2698
+ }
2699
+ if (path === null || call.callee.kind !== "IdentifierExpression")
2700
+ continue;
2701
+ const writer = this.reactiveWriters.get(call.callee.name) ?? null;
2702
+ if (writer === null || !writerWritesPath(writer, path, root, mutating))
2703
+ continue;
2704
+ this.diagnostics.push(diagnostic("VEL5079", `This watch starts '${call.callee.name}', which writes '${path}' — the reactive value this watch is on — so each completed run re-triggers the watch; make the write conditional, or watch the input '${call.callee.name}' reads`, call.span));
2705
+ return;
2706
+ }
2442
2707
  }
2443
2708
  /**
2444
2709
  * D89 A4: records `list = list.map(item => {…})`, React's immutable update,
@@ -2837,6 +3102,7 @@ export class VelarWebAnalyzer extends Analyzer {
2837
3102
  ["reload", { kind: "function", parameters: [], requiredParameters: 0, result: { kind: "promise", value: nullType } }],
2838
3103
  ]);
2839
3104
  this.declareBinding(statement.name, false, { kind: "object", fields }, statement.span);
3105
+ this.resourceBindingSpans.add(spanIdentity(statement.span));
2840
3106
  }
2841
3107
  actionType(statement) {
2842
3108
  const declaredResult = this.resolvedAsyncResult(this.inferredFunctionResult(statement));
@@ -2925,7 +3191,9 @@ export class VelarWebAnalyzer extends Analyzer {
2925
3191
  this.flowFrameDepth += 1;
2926
3192
  this.synchronousReactiveDepth += 1;
2927
3193
  const watched = this.inferExpression(item.expression);
2928
- this.rejectFrozenWatchSubject(item.expression, watched, item.currentName, item.previousName);
3194
+ if (this.rejectFrozenWatchSubject(item.expression, watched, item.currentName, item.previousName)) {
3195
+ this.rejectWatchCycle(item.expression, watched, item.body);
3196
+ }
2929
3197
  this.enterScope();
2930
3198
  if (item.currentName)
2931
3199
  this.declareBinding(item.currentName, false, watched, item.span);