@velarscript/web 0.23.4 → 0.25.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/README.md CHANGED
@@ -92,7 +92,10 @@ The package also owns Look, the checked visual language integrated with VelarScr
92
92
  values and JSX. `look:` values, ordinary functions, imports/exports, named
93
93
  `velar/look` builders, unit-aware properties and arithmetic, bounded `if` conditions, `@state` hooks, and
94
94
  `@target` blocks lower to extracted standard CSS with stable readable markers.
95
- Native and component JSX accept universal `class` and `look` props; inline
96
- `style` directives are rejected. Raw CSS is available only through an explicit
97
- `import css unsafe` declaration whose mandatory `before look` or `after look`
98
- placement defines source order without inventing priority semantics.
95
+ Native and component JSX accept universal `class` and `look` props, checked
96
+ `look:property` and `style:property` directives, and a raw `style` attribute is
97
+ rejected. A design system's CSS custom property is read by `token("--name")`,
98
+ which is legal in every Look property, so a token-driven visual layer stays
99
+ inside Look. Raw CSS is available through an explicit `import css unsafe`
100
+ declaration whose mandatory `before look` or `after look` placement defines
101
+ source order without inventing priority semantics.
@@ -10,10 +10,14 @@ export declare class VelarWebAnalyzer extends Analyzer {
10
10
  private watchBodyDepth;
11
11
  /** D89 A4: the binding identity of every list a keyed `.map(...)` interpolation renders. */
12
12
  private readonly keyedListSources;
13
- /** D89 A4: every `list = list.map(item => {…})` rewrite of this module, in source order. */
13
+ /** D89 A4: every row rebuild of this module assigned or derived in source order. */
14
14
  private readonly keyedListRebuilds;
15
+ /** D89 A4: this module's `def` bodies by name, so a `computed` that calls one can be read through. */
16
+ private moduleFunctions;
15
17
  private synchronousReactiveDepth;
16
18
  private jsxDepth;
19
+ /** VEL5075: how many component bodies enclose the declaration being analyzed. */
20
+ private componentBodyDepth;
17
21
  private readonly resources;
18
22
  private readonly unsafeCssImports;
19
23
  private readonly probedOperandTypes;
@@ -124,6 +128,32 @@ export declare class VelarWebAnalyzer extends Analyzer {
124
128
  protected inferExtensionExpression(expression: Expression, _contextualType: ValueType): ValueType | undefined;
125
129
  protected inferExpression(expression: Expression, contextualType?: ValueType): ValueType;
126
130
  private writableStateName;
131
+ /**
132
+ * P2b-5: a `def` that answers markup and answers it with a component element.
133
+ *
134
+ * Everything about the shape is legal one step out. A component element is a
135
+ * legal module-level expression — `const root = <App />` is the instantiation
136
+ * site D90 R4-b rules on, and `mount` takes exactly the instance it evaluates
137
+ * to. A `def` answering markup is a legal markup helper — dispatch over a
138
+ * closed vocabulary is what the P2b wave was writing. The defect is only
139
+ * where the two meet, and it is a representation split the type does not
140
+ * carry: a component element in a *child* position lowers to `__velarChild`,
141
+ * which owns a scope and answers a DOM node, while the same element standing
142
+ * alone lowers to `__velarInstantiate`, which answers an instance. Both are
143
+ * typed `WebNode`; only one is one. Returned from a helper and handed to a
144
+ * render, the instance reaches `__velarAppend` and takes the whole subtree
145
+ * down with "JSX can render only text, finite numbers, bool, enums, WebNode
146
+ * values, and Lists of those values" — the check-green, runtime-dead shape.
147
+ *
148
+ * The walk stops at every JSX element, which is exactly where the emitter
149
+ * stops: inside one, every position is a child position and every component
150
+ * element there is already correct — `return <div><Badge /></div>` and an
151
+ * interpolated `{cond ? <Badge /> : ...}` both work today and must keep
152
+ * working. Only a component element the returned markup *starts* with is the
153
+ * defect, including one reached through a `.map(...)` answering a row per
154
+ * item, which fails the same way for the same reason.
155
+ */
156
+ private rejectUnownedComponentElement;
127
157
  /**
128
158
  * The audit's seventh root cause: a `def` that declares reactive state and
129
159
  * answers `WebNode` is a component wearing a function's clothes, and calling
@@ -184,12 +214,33 @@ export declare class VelarWebAnalyzer extends Analyzer {
184
214
  */
185
215
  private recordKeyedListRebuild;
186
216
  /**
187
- * D89 A4: raises the advisory for the rewrites whose list a keyed position
217
+ * D89 A4, the wider proven shape: the same churn written as a derived value.
218
+ * `computed rows = source.map(item => {…})` and `computed rows = build(...)`
219
+ * over a `for`/`append` builder both hand a keyed position a fresh record for
220
+ * every row on every recompute, which is exactly what the assignment shape
221
+ * does — the reconciliation wave compiled both and found only one of them
222
+ * named. It is one advisory with a wider proof, not a second code: the defect,
223
+ * the consequence, and the suppression are the same.
224
+ *
225
+ * A `const` in a component body is deliberately not here. It is constructed
226
+ * once, so its records never move, and advising it would be a guess rather
227
+ * than a proof.
228
+ */
229
+ private recordDerivedKeyedListRebuild;
230
+ /**
231
+ * Whether a derived initializer constructs one fresh record per source
232
+ * element. Two spellings are proven and everything else is silent: a `map`
233
+ * whose callback answers a record literal — the same recognizer the
234
+ * assignment shape uses — and a call to a `def` this module declares whose
235
+ * whole answer is a list it filled with record literals.
236
+ */
237
+ private rebuildsRecordsPerElement;
238
+ /**
239
+ * D89 A4: raises the advisory for the rebuilds whose list a keyed position
188
240
  * really renders. The advisory channel cannot reach `this.diagnostics`, so
189
241
  * nothing here fails a build, changes an emitted byte, or moves a semantic
190
- * rule; `// velar-allow A4: <reason>` suppresses it where `map` plus a record
191
- * literal is the only spelling, which a `readonly` list or one API response
192
- * makes it.
242
+ * rule; `// velar-allow A4: <reason>` suppresses it where building the rows is
243
+ * the only spelling, which a `readonly` list or one API response makes it.
193
244
  */
194
245
  private adviseKeyedListRebuilds;
195
246
  /** A value that is read by calling it and takes no arguments to do so. */
@@ -306,6 +357,24 @@ export declare class VelarWebAnalyzer extends Analyzer {
306
357
  private checkAnimationKeyword;
307
358
  private analyzeKeyframes;
308
359
  private reportKeyframeSnapshotReads;
360
+ /**
361
+ * D104 rule 2 — the wider half of the promise VEL5039 already made. A
362
+ * repeated property name is refused because the second entry overwrites the
363
+ * first and Look has no source order to settle which is meant; a shorthand
364
+ * beside a longhand it writes is the same defect with two spellings, and it
365
+ * is the one that hid thirty-six dead declarations in a consumer's shell.
366
+ * Reports once against the entry that completes the pair.
367
+ */
368
+ private reportLookShorthandOverlap;
369
+ /**
370
+ * The same rule where the properties are written as element directives. A
371
+ * `look:` directive lowers to exactly the rule a block entry does, so an
372
+ * element carrying `look:padding` and `look:paddingTop` has the pair in the
373
+ * same scope as surely as a block does. The `style:` directives are not
374
+ * checked here: they compose one inline style object, where the browser's own
375
+ * source order settles the pair.
376
+ */
377
+ private reportLookDirectiveOverlap;
309
378
  /**
310
379
  * Records one entry in its lowered scope (condition signature plus target) so
311
380
  * two sibling blocks with the same condition report the property they both
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"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"}
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;AAyFzC,eAAO,MAAM,oBAAoB,iDAAiD,CAAC;AAkHnF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gCAAgC,GAAG,SAAS,GAAG,SAAS,CAsLlG;AA6vCD,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,8FAA8F;IAC9F,OAAO,CAAC,4BAA4B,CAA0C;IAE9E,YAAY,OAAO,GAAE,eAAoB,EAAE,UAAU,GAAE,SAAS,yBAAyB,EAAO,EAW/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;IAwE5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IAgD1B;;;;;;;;OAQG;IACH,OAAO,CAAC,2BAA2B;IAyBnC,iGAAiG;IACjG,OAAO,CAAC,mBAAmB;IAW3B,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;IAmDxB,+FAA+F;IAC/F,OAAO,CAAC,eAAe;IAKvB,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;IAwJjC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;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"}
package/dist/analyzer.js CHANGED
@@ -2,7 +2,7 @@ import { mechanicalEdits, mechanicalFix, semanticTypeIdentity } from "@velarscri
2
2
  import { Analyzer, anyType, boolType, describeType, expressionContainsDirectAwait, invalidType, isInvalidType, isAssignable, isReadonlyView, 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
- 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_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_TARGETS, LOOK_TOKEN_NAME_RULE, LOOK_TOKEN_NO_FALLBACK_GUIDANCE, LOOK_UNIT_TYPES, LOOK_UNITLESS_PROPERTIES, isLookTokenName, isLookVarReference, lookOwnKeywords, lookTokenReference, lookVarReferenceName, nearestLookName, } from "./look.js";
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_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_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";
6
6
  import { collectLookStaticValues, evaluateLookStaticExpression, isLookStaticValue, lookStaticCss } from "./look-static.js";
7
7
  import { keyframeCssValue } from "./keyframes.js";
8
8
  import { byCodeUnit } from "./stable-order.js";
@@ -428,6 +428,21 @@ function lookShorthandStringGuidance(name, value) {
428
428
  }
429
429
  return null;
430
430
  }
431
+ /**
432
+ * D104 rule 2 — what a refusal says when two entries in one scope write the
433
+ * same CSS declaration. Written once because two positions raise it: a Look
434
+ * block's entries and an element's `look:` directives lower to the same
435
+ * one-rule-per-property stylesheet, so they have the same defect and deserve
436
+ * the same sentence (D57 rule 134).
437
+ *
438
+ * The message leads with the mechanism, because the pair is legal CSS and the
439
+ * author has every reason to expect source order to settle it. It does not
440
+ * here: see LOOK_SHORTHAND_LONGHANDS for why the winner is decided module-wide.
441
+ */
442
+ function lookShorthandOverlapMessage(shorthand, longhand, subject) {
443
+ const parts = lookShorthandParts(shorthand);
444
+ return `${subject} sets '${shorthand}' and '${longhand}', and the shorthand writes the longhand. Look lowers every entry to its own CSS rule and orders those rules by where each property first appears in the module rather than by this block, so which of the two wins is not this code's to choose — an unrelated look elsewhere in the module decides it. Write ${parts.join(", ")} in place of '${shorthand}', or drop '${longhand}'`;
445
+ }
431
446
  const lookCssWideKeywords = new Set(LOOK_CSS_WIDE_KEYWORDS);
432
447
  const lookSharedMetricKeywords = ["auto", "none", "normal", "min-content", "max-content", "fit-content", "stretch"];
433
448
  const lookMetricKeywords = new Set([...lookCssWideKeywords, ...lookSharedMetricKeywords]);
@@ -1275,6 +1290,134 @@ function keyedRebuiltRecord(body, row) {
1275
1290
  }
1276
1291
  return null;
1277
1292
  }
1293
+ /**
1294
+ * D89 A4, the wider proven shape: every `def` of the module by name, so a
1295
+ * `computed` whose initializer calls one can be read through to what that call
1296
+ * builds. A name declared twice maps to null — two bodies under one name make
1297
+ * the question unanswerable, and an advisory that cannot prove which body runs
1298
+ * stays silent rather than guessing.
1299
+ */
1300
+ function collectModuleFunctions(program) {
1301
+ const functions = new Map();
1302
+ const record = (statements) => {
1303
+ for (const statement of statements) {
1304
+ if (statement.kind === "FunctionDeclaration") {
1305
+ functions.set(statement.name, functions.has(statement.name) ? null : statement);
1306
+ record(statement.body);
1307
+ continue;
1308
+ }
1309
+ if (isWebStatement(statement) && statement.kind === "ExtensionStatement:web:component") {
1310
+ record(statement.body);
1311
+ }
1312
+ }
1313
+ };
1314
+ record(program.body);
1315
+ return functions;
1316
+ }
1317
+ /** Every name a binding pattern introduces, added to `names`. */
1318
+ function collectPatternNames(pattern, names) {
1319
+ if (pattern.kind === "NameBindingPattern") {
1320
+ names.add(pattern.name);
1321
+ return;
1322
+ }
1323
+ if (pattern.kind === "ListBindingPattern") {
1324
+ for (const element of pattern.elements)
1325
+ if (element)
1326
+ collectPatternNames(element, names);
1327
+ }
1328
+ else {
1329
+ for (const entry of pattern.entries)
1330
+ collectPatternNames(entry.pattern, names);
1331
+ }
1332
+ if (pattern.rest)
1333
+ names.add(pattern.rest.name);
1334
+ }
1335
+ /** True when the expression reads one of `names` anywhere inside it. */
1336
+ function readsAnyName(value, names) {
1337
+ if (!value || typeof value !== "object")
1338
+ return false;
1339
+ if (Array.isArray(value))
1340
+ return value.some((item) => readsAnyName(item, names));
1341
+ const record = value;
1342
+ if (record.kind === "IdentifierExpression" && typeof record.name === "string" && names.has(record.name))
1343
+ return true;
1344
+ return Object.values(record).some((child) => readsAnyName(child, names));
1345
+ }
1346
+ /**
1347
+ * D89 A4, the wider proven shape: whether a `def` answers a list it filled with
1348
+ * freshly constructed records — the `for`/`append` builder that spells the same
1349
+ * churn `list = list.map(item => {…})` spells with `map`, and the spelling the
1350
+ * P2b consumer actually wrote.
1351
+ *
1352
+ * Four things are proven, and any one of them missing leaves the advisory
1353
+ * silent, because the whole product is naming a rebuild that really happens:
1354
+ *
1355
+ * 1. the body's last statement returns a name it declared here, initialized to
1356
+ * an empty list literal — so the list is the `def`'s own, not one handed in;
1357
+ * 2. every value appended to that list is a record literal, so every element is
1358
+ * a new value on every call. One `append(row)` of a source record is the
1359
+ * identity-preserving spelling this advisory teaches, and one member call
1360
+ * the proof cannot read leaves the whole answer unproven;
1361
+ * 3. at least one such record reads a parameter of the `def` or the binding of
1362
+ * the `for` that appends it. A builder whose records are constant answers
1363
+ * the same content every call, so nothing it is derived from can move and
1364
+ * the `computed` over it never recomputes;
1365
+ * 4. nothing else touches the list — it is built and answered, nothing more.
1366
+ */
1367
+ function buildsFreshRecords(declaration) {
1368
+ const returned = declaration.body.at(-1);
1369
+ if (!returned || returned.kind !== "ReturnStatement" || returned.value?.kind !== "IdentifierExpression")
1370
+ return false;
1371
+ const built = returned.value.name;
1372
+ const local = declaration.body.find((statement) => statement.kind === "VariableDeclaration"
1373
+ && statement.pattern.kind === "NameBindingPattern" && statement.pattern.name === built);
1374
+ if (!local || local.kind !== "VariableDeclaration")
1375
+ return false;
1376
+ if (local.initializer.kind !== "ListExpression" || local.initializer.elements.length > 0)
1377
+ return false;
1378
+ const parameters = new Set(declaration.parameters.map((parameter) => parameter.name));
1379
+ let records = 0;
1380
+ let varying = false;
1381
+ let unproven = false;
1382
+ const walk = (statements, visible) => {
1383
+ for (const statement of statements) {
1384
+ if (statement.kind === "ForStatement") {
1385
+ const inner = new Set(visible);
1386
+ collectPatternNames(statement.pattern, inner);
1387
+ if (statement.secondPattern)
1388
+ collectPatternNames(statement.secondPattern, inner);
1389
+ walk(statement.body, inner);
1390
+ continue;
1391
+ }
1392
+ if (statement.kind === "IfStatement") {
1393
+ walk(statement.thenBody, visible);
1394
+ walk(statement.elseBody ?? [], visible);
1395
+ continue;
1396
+ }
1397
+ if (statement.kind === "WhileStatement") {
1398
+ walk(statement.body, visible);
1399
+ continue;
1400
+ }
1401
+ if (statement.kind !== "ExpressionStatement")
1402
+ continue;
1403
+ const call = statement.expression;
1404
+ if (call.kind !== "CallExpression" || call.callee.kind !== "MemberExpression")
1405
+ continue;
1406
+ if (call.callee.object.kind !== "IdentifierExpression" || call.callee.object.name !== built)
1407
+ continue;
1408
+ const [appended] = call.arguments;
1409
+ if (call.callee.property !== "append" || !appended || appended.kind !== "ObjectExpression") {
1410
+ unproven = true;
1411
+ continue;
1412
+ }
1413
+ records += 1;
1414
+ if (readsAnyName(appended, visible))
1415
+ varying = true;
1416
+ }
1417
+ };
1418
+ walk(declaration.body, parameters);
1419
+ return !unproven && records > 0 && varying;
1420
+ }
1278
1421
  /**
1279
1422
  * The reactive declarations a `def` body may not hold when the `def` answers
1280
1423
  * `WebNode`. `watch`, `resource` and `action` are already refused outside a
@@ -1396,6 +1539,59 @@ function subtreeHoldsJsx(value) {
1396
1539
  }
1397
1540
  return false;
1398
1541
  }
1542
+ /**
1543
+ * VEL5075: the JSX elements a body's own `return`s *start* with — the roots of
1544
+ * the returned markup, which is where the emitter decides between an instance
1545
+ * and a child. The walk descends through ordinary expression nodes, `.map(...)`
1546
+ * callbacks included, and stops at the first JSX element on every path: inside
1547
+ * one, every JSX position is a child position and needs no verdict. A nested
1548
+ * `def` or `component` owns its own returns, exactly as it does elsewhere here.
1549
+ */
1550
+ function returnedMarkupRoots(body) {
1551
+ const roots = [];
1552
+ const collect = (value) => {
1553
+ const pending = [value];
1554
+ while (pending.length > 0) {
1555
+ const entry = pending.pop();
1556
+ if (Array.isArray(entry)) {
1557
+ for (const item of entry)
1558
+ pending.push(item);
1559
+ continue;
1560
+ }
1561
+ if (entry === null || typeof entry !== "object")
1562
+ continue;
1563
+ const node = entry;
1564
+ if (node.kind === "ExtensionExpression:web:jsx") {
1565
+ roots.push(node);
1566
+ continue;
1567
+ }
1568
+ for (const key of Object.keys(node))
1569
+ pending.push(node[key]);
1570
+ }
1571
+ };
1572
+ const pending = [...body];
1573
+ while (pending.length > 0) {
1574
+ const value = pending.pop();
1575
+ if (Array.isArray(value)) {
1576
+ for (const entry of value)
1577
+ pending.push(entry);
1578
+ continue;
1579
+ }
1580
+ if (value === null || typeof value !== "object")
1581
+ continue;
1582
+ const node = value;
1583
+ const kind = typeof node.kind === "string" ? node.kind : null;
1584
+ if (kind === "FunctionDeclaration" || kind === "ExtensionStatement:web:component")
1585
+ continue;
1586
+ if (kind === "ReturnStatement") {
1587
+ collect(node.value);
1588
+ continue;
1589
+ }
1590
+ for (const key of Object.keys(node))
1591
+ pending.push(node[key]);
1592
+ }
1593
+ return roots;
1594
+ }
1399
1595
  /** The component spelling of a helper's name: components render as a PascalCase tag. */
1400
1596
  function componentSpelling(name) {
1401
1597
  return name.length === 0 ? name : `${name[0].toUpperCase()}${name.slice(1)}`;
@@ -1413,10 +1609,14 @@ export class VelarWebAnalyzer extends Analyzer {
1413
1609
  watchBodyDepth = 0;
1414
1610
  /** D89 A4: the binding identity of every list a keyed `.map(...)` interpolation renders. */
1415
1611
  keyedListSources = new Set();
1416
- /** D89 A4: every `list = list.map(item => {…})` rewrite of this module, in source order. */
1612
+ /** D89 A4: every row rebuild of this module assigned or derived in source order. */
1417
1613
  keyedListRebuilds = [];
1614
+ /** D89 A4: this module's `def` bodies by name, so a `computed` that calls one can be read through. */
1615
+ moduleFunctions = new Map();
1418
1616
  synchronousReactiveDepth = 0;
1419
1617
  jsxDepth = 0;
1618
+ /** VEL5075: how many component bodies enclose the declaration being analyzed. */
1619
+ componentBodyDepth = 0;
1420
1620
  resources;
1421
1621
  unsafeCssImports = new Set();
1422
1622
  probedOperandTypes = new Map();
@@ -1478,6 +1678,7 @@ export class VelarWebAnalyzer extends Analyzer {
1478
1678
  this.rejectWebOwnedTypeNames(program);
1479
1679
  this.keyedListSources.clear();
1480
1680
  this.keyedListRebuilds.length = 0;
1681
+ this.moduleFunctions = collectModuleFunctions(program);
1481
1682
  super.analyze(program);
1482
1683
  this.reportStaticJsxKeys();
1483
1684
  this.reportRetiredComputedFunction();
@@ -1591,8 +1792,10 @@ export class VelarWebAnalyzer extends Analyzer {
1591
1792
  if (statement.kind === "VariableDeclaration") {
1592
1793
  this.recordRetiredAccessorDeclaration(statement);
1593
1794
  }
1594
- if (statement.kind === "FunctionDeclaration")
1795
+ if (statement.kind === "FunctionDeclaration") {
1595
1796
  this.rejectStatefulWebNodeFunction(statement);
1797
+ this.rejectUnownedComponentElement(statement);
1798
+ }
1596
1799
  if (!isWebStatement(statement))
1597
1800
  return false;
1598
1801
  switch (statement.kind) {
@@ -1771,6 +1974,7 @@ export class VelarWebAnalyzer extends Analyzer {
1771
1974
  this.declareBinding(statement.name, false, declared, statement.span);
1772
1975
  this.markDeclaredBindingReactive(statement.name, "prop");
1773
1976
  this.computedBindingSpans.add(spanIdentity(statement.span));
1977
+ this.recordDerivedKeyedListRebuild(statement);
1774
1978
  }
1775
1979
  /**
1776
1980
  * True when `name` resolves to a `computed` declaration or to an imported one.
@@ -1944,6 +2148,49 @@ export class VelarWebAnalyzer extends Analyzer {
1944
2148
  writableStateName(name) {
1945
2149
  return this.reactiveBindingKind(name) === "state";
1946
2150
  }
2151
+ /**
2152
+ * P2b-5: a `def` that answers markup and answers it with a component element.
2153
+ *
2154
+ * Everything about the shape is legal one step out. A component element is a
2155
+ * legal module-level expression — `const root = <App />` is the instantiation
2156
+ * site D90 R4-b rules on, and `mount` takes exactly the instance it evaluates
2157
+ * to. A `def` answering markup is a legal markup helper — dispatch over a
2158
+ * closed vocabulary is what the P2b wave was writing. The defect is only
2159
+ * where the two meet, and it is a representation split the type does not
2160
+ * carry: a component element in a *child* position lowers to `__velarChild`,
2161
+ * which owns a scope and answers a DOM node, while the same element standing
2162
+ * alone lowers to `__velarInstantiate`, which answers an instance. Both are
2163
+ * typed `WebNode`; only one is one. Returned from a helper and handed to a
2164
+ * render, the instance reaches `__velarAppend` and takes the whole subtree
2165
+ * down with "JSX can render only text, finite numbers, bool, enums, WebNode
2166
+ * values, and Lists of those values" — the check-green, runtime-dead shape.
2167
+ *
2168
+ * The walk stops at every JSX element, which is exactly where the emitter
2169
+ * stops: inside one, every position is a child position and every component
2170
+ * element there is already correct — `return <div><Badge /></div>` and an
2171
+ * interpolated `{cond ? <Badge /> : ...}` both work today and must keep
2172
+ * working. Only a component element the returned markup *starts* with is the
2173
+ * defect, including one reached through a `.map(...)` answering a row per
2174
+ * item, which fails the same way for the same reason.
2175
+ */
2176
+ rejectUnownedComponentElement(statement) {
2177
+ // A helper nested in a component body is emitted with that component's
2178
+ // scope in hand, so its component elements are `__velarChild` and answer
2179
+ // nodes. Only a helper standing outside every component body has nowhere
2180
+ // for the emitter to put them.
2181
+ if (this.componentBodyDepth > 0)
2182
+ return;
2183
+ const answersMarkup = statement.returnType
2184
+ ? carriesWebNode(this.resolveAnnotation(statement.returnType))
2185
+ : bodyReturnsJsx(statement.body);
2186
+ if (!answersMarkup)
2187
+ return;
2188
+ for (const element of returnedMarkupRoots(statement.body)) {
2189
+ if (!/^[A-Z]/u.test(element.tag))
2190
+ continue;
2191
+ this.diagnostics.push(diagnostic("VEL5075", `'${statement.name}' answers markup with the component element '<${element.tag} />', and a component element standing on its own is an instance rather than a node: it is built by the position that shows it, and a 'def' is not one, so what this returns fails the moment JSX tries to render it. Write '<${element.tag} />' where it is rendered — a component's own body, or a child position inside markup this returns such as '<div><${element.tag} ... /></div>' — and keep the helper for the native elements it builds.`, element.span));
2192
+ }
2193
+ }
1947
2194
  /**
1948
2195
  * The audit's seventh root cause: a `def` that declares reactive state and
1949
2196
  * answers `WebNode` is a component wearing a function's clothes, and calling
@@ -2088,6 +2335,7 @@ export class VelarWebAnalyzer extends Analyzer {
2088
2335
  if (!binding)
2089
2336
  return;
2090
2337
  this.keyedListRebuilds.push({
2338
+ kind: "assignment",
2091
2339
  source: spanIdentity(binding.span),
2092
2340
  name: statement.target.name,
2093
2341
  field: rebuilt.field,
@@ -2095,12 +2343,57 @@ export class VelarWebAnalyzer extends Analyzer {
2095
2343
  });
2096
2344
  }
2097
2345
  /**
2098
- * D89 A4: raises the advisory for the rewrites whose list a keyed position
2346
+ * D89 A4, the wider proven shape: the same churn written as a derived value.
2347
+ * `computed rows = source.map(item => {…})` and `computed rows = build(...)`
2348
+ * over a `for`/`append` builder both hand a keyed position a fresh record for
2349
+ * every row on every recompute, which is exactly what the assignment shape
2350
+ * does — the reconciliation wave compiled both and found only one of them
2351
+ * named. It is one advisory with a wider proof, not a second code: the defect,
2352
+ * the consequence, and the suppression are the same.
2353
+ *
2354
+ * A `const` in a component body is deliberately not here. It is constructed
2355
+ * once, so its records never move, and advising it would be a guess rather
2356
+ * than a proof.
2357
+ */
2358
+ recordDerivedKeyedListRebuild(statement) {
2359
+ if (!this.rebuildsRecordsPerElement(statement.initializer))
2360
+ return;
2361
+ this.keyedListRebuilds.push({
2362
+ kind: "derived",
2363
+ source: spanIdentity(statement.span),
2364
+ name: statement.name,
2365
+ field: null,
2366
+ span: statement.span,
2367
+ });
2368
+ }
2369
+ /**
2370
+ * Whether a derived initializer constructs one fresh record per source
2371
+ * element. Two spellings are proven and everything else is silent: a `map`
2372
+ * whose callback answers a record literal — the same recognizer the
2373
+ * assignment shape uses — and a call to a `def` this module declares whose
2374
+ * whole answer is a list it filled with record literals.
2375
+ */
2376
+ rebuildsRecordsPerElement(value) {
2377
+ if (value.kind !== "CallExpression")
2378
+ return false;
2379
+ if (value.callee.kind === "MemberExpression" && value.callee.property === "map") {
2380
+ const callback = value.arguments[0];
2381
+ if (!callback || callback.kind !== "ArrowFunctionExpression" || callback.parameters.length !== 1)
2382
+ return false;
2383
+ const [row] = callback.parameters;
2384
+ return row !== undefined && keyedRebuiltRecord(callback.body, row.name) !== null;
2385
+ }
2386
+ if (value.callee.kind !== "IdentifierExpression")
2387
+ return false;
2388
+ const declaration = this.moduleFunctions.get(value.callee.name);
2389
+ return declaration !== undefined && declaration !== null && buildsFreshRecords(declaration);
2390
+ }
2391
+ /**
2392
+ * D89 A4: raises the advisory for the rebuilds whose list a keyed position
2099
2393
  * really renders. The advisory channel cannot reach `this.diagnostics`, so
2100
2394
  * nothing here fails a build, changes an emitted byte, or moves a semantic
2101
- * rule; `// velar-allow A4: <reason>` suppresses it where `map` plus a record
2102
- * literal is the only spelling, which a `readonly` list or one API response
2103
- * makes it.
2395
+ * rule; `// velar-allow A4: <reason>` suppresses it where building the rows is
2396
+ * the only spelling, which a `readonly` list or one API response makes it.
2104
2397
  */
2105
2398
  adviseKeyedListRebuilds() {
2106
2399
  for (const rebuild of this.keyedListRebuilds) {
@@ -2110,8 +2403,13 @@ export class VelarWebAnalyzer extends Analyzer {
2110
2403
  // the same key and then drops it because the row it holds is no longer
2111
2404
  // the same value, so a message blaming the key sends an author to check
2112
2405
  // `id`, find it unchanged, and conclude the advisory is wrong.
2113
- const field = rebuild.field ?? "<field>";
2114
- this.advise("A4", `This rebuilds every row of '${rebuild.name}', so every row is a new value and the keyed list that renders '${rebuild.name}' no longer recognises any of them: it destroys and rebuilds all of its children — an input being typed into loses focus. Change the field in place instead: '${rebuild.name}[index].${field} = ...'`, rebuild.span);
2406
+ // A derived value has no row to write: whatever it hands the keyed
2407
+ // position it built itself. The two ways out are to render the rows that
2408
+ // do have an identity, or to carry those same records through.
2409
+ const remedy = rebuild.kind === "derived"
2410
+ ? "A derived value cannot keep rows it builds: render the source rows and change the field on them in place, or carry the source records through instead of constructing new ones."
2411
+ : `Change the field in place instead: '${rebuild.name}[index].${rebuild.field ?? "<field>"} = ...'`;
2412
+ this.advise("A4", `This rebuilds every row of '${rebuild.name}'${rebuild.kind === "derived" ? " on every recompute" : ""}, so every row is a new value and the keyed list that renders '${rebuild.name}' no longer recognises any of them: it destroys and rebuilds all of its children — an input being typed into loses focus. ${remedy}`, rebuild.span);
2115
2413
  }
2116
2414
  }
2117
2415
  /** A value that is read by calling it and takes no arguments to do so. */
@@ -2419,6 +2717,9 @@ export class VelarWebAnalyzer extends Analyzer {
2419
2717
  const outerConstructorDepth = this.constructorDepth;
2420
2718
  if (!this.isPredeclared(statement))
2421
2719
  this.declareBinding(statement.name, false, this.componentType(statement), statement.span);
2720
+ // VEL5075: a `def` written from here to the matching decrement lowers
2721
+ // through this component's scope, so its component elements are children.
2722
+ this.componentBodyDepth += 1;
2422
2723
  this.enterScope();
2423
2724
  this.flowFrameDepth += 1;
2424
2725
  const previousStates = this.componentStates;
@@ -2559,6 +2860,7 @@ export class VelarWebAnalyzer extends Analyzer {
2559
2860
  this.explicitReadonlyPropBindings = previousExplicitReadonlyProps;
2560
2861
  this.flowFrameDepth -= 1;
2561
2862
  this.exitScope();
2863
+ this.componentBodyDepth -= 1;
2562
2864
  this.constructorDepth = outerConstructorDepth;
2563
2865
  }
2564
2866
  containsReadonlyView(type) {
@@ -2673,6 +2975,7 @@ export class VelarWebAnalyzer extends Analyzer {
2673
2975
  this.analyzeLookEntries(entry.entries, true, true, inheritedTerms, repeated ? `${scopeKey}@${entry.name}#${entry.span.start}` : `${scopeKey}@${entry.name}`);
2674
2976
  continue;
2675
2977
  }
2978
+ this.reportLookShorthandOverlap(scopeKey, entry.name, entry.span);
2676
2979
  if (!this.recordLookEntry(scopeKey, entry.name)) {
2677
2980
  this.diagnostics.push(diagnostic("VEL5039", `Look property '${entry.name}' is defined more than once in the same scope`, entry.span));
2678
2981
  }
@@ -2998,6 +3301,46 @@ export class VelarWebAnalyzer extends Analyzer {
2998
3301
  };
2999
3302
  visit(expression);
3000
3303
  }
3304
+ /**
3305
+ * D104 rule 2 — the wider half of the promise VEL5039 already made. A
3306
+ * repeated property name is refused because the second entry overwrites the
3307
+ * first and Look has no source order to settle which is meant; a shorthand
3308
+ * beside a longhand it writes is the same defect with two spellings, and it
3309
+ * is the one that hid thirty-six dead declarations in a consumer's shell.
3310
+ * Reports once against the entry that completes the pair.
3311
+ */
3312
+ reportLookShorthandOverlap(scopeKey, name, span) {
3313
+ for (const other of this.lookEntryScopes.get(scopeKey) ?? []) {
3314
+ const overlap = lookShorthandOverlap(other, name);
3315
+ if (!overlap)
3316
+ continue;
3317
+ this.diagnostics.push(diagnostic("VEL5039", lookShorthandOverlapMessage(overlap.shorthand, overlap.longhand, "This Look scope"), span));
3318
+ return;
3319
+ }
3320
+ }
3321
+ /**
3322
+ * The same rule where the properties are written as element directives. A
3323
+ * `look:` directive lowers to exactly the rule a block entry does, so an
3324
+ * element carrying `look:padding` and `look:paddingTop` has the pair in the
3325
+ * same scope as surely as a block does. The `style:` directives are not
3326
+ * checked here: they compose one inline style object, where the browser's own
3327
+ * source order settles the pair.
3328
+ */
3329
+ reportLookDirectiveOverlap(expression) {
3330
+ const written = [];
3331
+ for (const attribute of expression.attributes) {
3332
+ if (!attribute.name.startsWith("look:"))
3333
+ continue;
3334
+ const property = attribute.name.slice("look:".length);
3335
+ if (!LOOK_PROPERTIES.has(property))
3336
+ continue;
3337
+ const overlapping = written.map((other) => lookShorthandOverlap(other, property)).find(Boolean);
3338
+ if (overlapping) {
3339
+ this.diagnostics.push(diagnostic("VEL5039", lookShorthandOverlapMessage(overlapping.shorthand, overlapping.longhand, `Element '<${expression.tag}>'`), attribute.span));
3340
+ }
3341
+ written.push(property);
3342
+ }
3343
+ }
3001
3344
  /**
3002
3345
  * Records one entry in its lowered scope (condition signature plus target) so
3003
3346
  * two sibling blocks with the same condition report the property they both
@@ -3309,6 +3652,7 @@ export class VelarWebAnalyzer extends Analyzer {
3309
3652
  }
3310
3653
  if (attributes.size !== expression.attributes.length)
3311
3654
  this.diagnostics.push(diagnostic("VEL5014", `JSX element '${expression.tag}' has duplicate attributes`, expression.span));
3655
+ this.reportLookDirectiveOverlap(expression);
3312
3656
  for (const attribute of expression.attributes) {
3313
3657
  if (removedJsxControlAttributes.has(attribute.name)) {
3314
3658
  this.diagnostics.push(diagnostic("VEL5029", `JSX '${attribute.name}' was removed; branch with an ordinary expression such as {condition ? <A /> : <B />}`, attribute.span));