@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.
- package/dist/analyzer.d.ts +46 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +275 -7
- package/dist/analyzer.js.map +1 -1
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +10 -0
- package/dist/emitter.js.map +1 -1
- package/dist/runtime-foundation.d.ts.map +1 -1
- package/dist/runtime-foundation.js +161 -20
- package/dist/runtime-foundation.js.map +1 -1
- package/package.json +2 -2
package/dist/analyzer.d.ts
CHANGED
|
@@ -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.
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -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,
|
|
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);
|