@velarscript/web 0.31.0 → 0.33.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 +1 -1
- package/dist/analysis/calls/intrinsics/config.d.ts +10 -0
- package/dist/analysis/calls/intrinsics/config.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics/config.js +19 -0
- package/dist/analysis/calls/intrinsics/config.js.map +1 -0
- package/dist/analysis/calls/intrinsics/forms.d.ts +10 -0
- package/dist/analysis/calls/intrinsics/forms.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics/forms.js +38 -0
- package/dist/analysis/calls/intrinsics/forms.js.map +1 -0
- package/dist/analysis/calls/intrinsics/http.d.ts +10 -0
- package/dist/analysis/calls/intrinsics/http.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics/http.js +37 -0
- package/dist/analysis/calls/intrinsics/http.js.map +1 -0
- package/dist/analysis/calls/intrinsics/reactive.d.ts +10 -0
- package/dist/analysis/calls/intrinsics/reactive.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics/reactive.js +30 -0
- package/dist/analysis/calls/intrinsics/reactive.js.map +1 -0
- package/dist/analysis/calls/intrinsics/storage.d.ts +16 -0
- package/dist/analysis/calls/intrinsics/storage.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics/storage.js +91 -0
- package/dist/analysis/calls/intrinsics/storage.js.map +1 -0
- package/dist/analysis/calls/intrinsics/web.d.ts +10 -0
- package/dist/analysis/calls/intrinsics/web.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics/web.js +100 -0
- package/dist/analysis/calls/intrinsics/web.js.map +1 -0
- package/dist/analysis/calls/intrinsics.d.ts +13 -0
- package/dist/analysis/calls/intrinsics.d.ts.map +1 -0
- package/dist/analysis/calls/intrinsics.js +31 -0
- package/dist/analysis/calls/intrinsics.js.map +1 -0
- package/dist/analysis/components/contracts.d.ts +19 -0
- package/dist/analysis/components/contracts.d.ts.map +1 -0
- package/dist/analysis/components/contracts.js +83 -0
- package/dist/analysis/components/contracts.js.map +1 -0
- package/dist/analysis/components/declaration.d.ts +4 -0
- package/dist/analysis/components/declaration.d.ts.map +1 -0
- package/dist/analysis/components/declaration.js +163 -0
- package/dist/analysis/components/declaration.js.map +1 -0
- package/dist/analysis/components/host.d.ts +61 -0
- package/dist/analysis/components/host.d.ts.map +1 -0
- package/dist/analysis/components/host.js +22 -0
- package/dist/analysis/components/host.js.map +1 -0
- package/dist/analysis/components/lifecycle.d.ts +22 -0
- package/dist/analysis/components/lifecycle.d.ts.map +1 -0
- package/dist/analysis/components/lifecycle.js +33 -0
- package/dist/analysis/components/lifecycle.js.map +1 -0
- package/dist/analysis/components/ownership.d.ts +74 -0
- package/dist/analysis/components/ownership.d.ts.map +1 -0
- package/dist/analysis/components/ownership.js +106 -0
- package/dist/analysis/components/ownership.js.map +1 -0
- package/dist/analysis/declarations.d.ts +99 -0
- package/dist/analysis/declarations.d.ts.map +1 -0
- package/dist/analysis/declarations.js +176 -0
- package/dist/analysis/declarations.js.map +1 -0
- package/dist/analysis/inference.d.ts +75 -0
- package/dist/analysis/inference.d.ts.map +1 -0
- package/dist/analysis/inference.js +257 -0
- package/dist/analysis/inference.js.map +1 -0
- package/dist/analysis/jsx/attributes.d.ts +103 -0
- package/dist/analysis/jsx/attributes.d.ts.map +1 -0
- package/dist/analysis/jsx/attributes.js +601 -0
- package/dist/analysis/jsx/attributes.js.map +1 -0
- package/dist/analysis/jsx/elements.d.ts +17 -0
- package/dist/analysis/jsx/elements.d.ts.map +1 -0
- package/dist/analysis/jsx/elements.js +213 -0
- package/dist/analysis/jsx/elements.js.map +1 -0
- package/dist/analysis/jsx/host.d.ts +61 -0
- package/dist/analysis/jsx/host.d.ts.map +1 -0
- package/dist/analysis/jsx/host.js +15 -0
- package/dist/analysis/jsx/host.js.map +1 -0
- package/dist/analysis/jsx/keys.d.ts +15 -0
- package/dist/analysis/jsx/keys.d.ts.map +1 -0
- package/dist/analysis/jsx/keys.js +67 -0
- package/dist/analysis/jsx/keys.js.map +1 -0
- package/dist/analysis/jsx/security.d.ts +29 -0
- package/dist/analysis/jsx/security.d.ts.map +1 -0
- package/dist/analysis/jsx/security.js +61 -0
- package/dist/analysis/jsx/security.js.map +1 -0
- package/dist/analysis/jsx-detection.d.ts +49 -0
- package/dist/analysis/jsx-detection.d.ts.map +1 -0
- package/dist/analysis/jsx-detection.js +196 -0
- package/dist/analysis/jsx-detection.js.map +1 -0
- package/dist/analysis/keyed-rebuild.d.ts +148 -0
- package/dist/analysis/keyed-rebuild.d.ts.map +1 -0
- package/dist/analysis/keyed-rebuild.js +299 -0
- package/dist/analysis/keyed-rebuild.js.map +1 -0
- package/dist/analysis/keyframes-analysis.d.ts +6 -0
- package/dist/analysis/keyframes-analysis.d.ts.map +1 -0
- package/dist/analysis/keyframes-analysis.js +79 -0
- package/dist/analysis/keyframes-analysis.js.map +1 -0
- package/dist/analysis/look/builders.d.ts +29 -0
- package/dist/analysis/look/builders.d.ts.map +1 -0
- package/dist/analysis/look/builders.js +174 -0
- package/dist/analysis/look/builders.js.map +1 -0
- package/dist/analysis/look/conditions.d.ts +13 -0
- package/dist/analysis/look/conditions.d.ts.map +1 -0
- package/dist/analysis/look/conditions.js +84 -0
- package/dist/analysis/look/conditions.js.map +1 -0
- package/dist/analysis/look/edits.d.ts +36 -0
- package/dist/analysis/look/edits.d.ts.map +1 -0
- package/dist/analysis/look/edits.js +80 -0
- package/dist/analysis/look/edits.js.map +1 -0
- package/dist/analysis/look/entries.d.ts +56 -0
- package/dist/analysis/look/entries.d.ts.map +1 -0
- package/dist/analysis/look/entries.js +223 -0
- package/dist/analysis/look/entries.js.map +1 -0
- package/dist/analysis/look/host.d.ts +47 -0
- package/dist/analysis/look/host.d.ts.map +1 -0
- package/dist/analysis/look/host.js +16 -0
- package/dist/analysis/look/host.js.map +1 -0
- package/dist/analysis/look/tokens.d.ts +75 -0
- package/dist/analysis/look/tokens.d.ts.map +1 -0
- package/dist/analysis/look/tokens.js +156 -0
- package/dist/analysis/look/tokens.js.map +1 -0
- package/dist/analysis/look/values.d.ts +45 -0
- package/dist/analysis/look/values.d.ts.map +1 -0
- package/dist/analysis/look/values.js +148 -0
- package/dist/analysis/look/values.js.map +1 -0
- package/dist/analysis/look-conditions.d.ts +47 -0
- package/dist/analysis/look-conditions.d.ts.map +1 -0
- package/dist/analysis/look-conditions.js +157 -0
- package/dist/analysis/look-conditions.js.map +1 -0
- package/dist/analysis/look-sites.d.ts +62 -0
- package/dist/analysis/look-sites.d.ts.map +1 -0
- package/dist/analysis/look-sites.js +121 -0
- package/dist/analysis/look-sites.js.map +1 -0
- package/dist/analysis/look-values.d.ts +19 -0
- package/dist/analysis/look-values.d.ts.map +1 -1
- package/dist/analysis/look-values.js +38 -0
- package/dist/analysis/look-values.js.map +1 -1
- package/dist/analysis/look-vocabulary-guidance.d.ts +76 -0
- package/dist/analysis/look-vocabulary-guidance.d.ts.map +1 -0
- package/dist/analysis/look-vocabulary-guidance.js +338 -0
- package/dist/analysis/look-vocabulary-guidance.js.map +1 -0
- package/dist/analysis/media-conditions.d.ts +33 -0
- package/dist/analysis/media-conditions.d.ts.map +1 -0
- package/dist/analysis/media-conditions.js +92 -0
- package/dist/analysis/media-conditions.js.map +1 -0
- package/dist/analysis/program-passes.d.ts +106 -0
- package/dist/analysis/program-passes.d.ts.map +1 -0
- package/dist/analysis/program-passes.js +149 -0
- package/dist/analysis/program-passes.js.map +1 -0
- package/dist/analysis/reactivity/bindings.d.ts +37 -0
- package/dist/analysis/reactivity/bindings.d.ts.map +1 -0
- package/dist/analysis/reactivity/bindings.js +88 -0
- package/dist/analysis/reactivity/bindings.js.map +1 -0
- package/dist/analysis/reactivity/derived.d.ts +39 -0
- package/dist/analysis/reactivity/derived.d.ts.map +1 -0
- package/dist/analysis/reactivity/derived.js +80 -0
- package/dist/analysis/reactivity/derived.js.map +1 -0
- package/dist/analysis/reactivity/host.d.ts +38 -0
- package/dist/analysis/reactivity/host.d.ts.map +1 -0
- package/dist/analysis/reactivity/host.js +14 -0
- package/dist/analysis/reactivity/host.js.map +1 -0
- package/dist/analysis/reactivity/retired-accessors.d.ts +82 -0
- package/dist/analysis/reactivity/retired-accessors.d.ts.map +1 -0
- package/dist/analysis/reactivity/retired-accessors.js +118 -0
- package/dist/analysis/reactivity/retired-accessors.js.map +1 -0
- package/dist/analysis/renderable.d.ts +47 -0
- package/dist/analysis/renderable.d.ts.map +1 -0
- package/dist/analysis/renderable.js +105 -0
- package/dist/analysis/renderable.js.map +1 -0
- package/dist/analysis/routes.d.ts +64 -0
- package/dist/analysis/routes.d.ts.map +1 -0
- package/dist/analysis/routes.js +182 -0
- package/dist/analysis/routes.js.map +1 -0
- package/dist/analysis/url-attributes.d.ts +12 -0
- package/dist/analysis/url-attributes.d.ts.map +1 -0
- package/dist/analysis/url-attributes.js +48 -0
- package/dist/analysis/url-attributes.js.map +1 -0
- package/dist/analysis/watch-cycles.d.ts +102 -0
- package/dist/analysis/watch-cycles.d.ts.map +1 -1
- package/dist/analysis/watch-cycles.js +240 -1
- package/dist/analysis/watch-cycles.js.map +1 -1
- package/dist/analysis/watch-subject.d.ts +45 -0
- package/dist/analysis/watch-subject.d.ts.map +1 -0
- package/dist/analysis/watch-subject.js +272 -0
- package/dist/analysis/watch-subject.js.map +1 -0
- package/dist/analysis/web-types.d.ts +27 -0
- package/dist/analysis/web-types.d.ts.map +1 -0
- package/dist/analysis/web-types.js +170 -0
- package/dist/analysis/web-types.js.map +1 -0
- package/dist/analyzer.d.ts +18 -617
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +211 -4853
- package/dist/analyzer.js.map +1 -1
- package/dist/compiler.d.ts +20 -0
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +31 -864
- package/dist/compiler.js.map +1 -1
- package/dist/documentation.d.ts +28 -0
- package/dist/documentation.d.ts.map +1 -0
- package/dist/documentation.js +67 -0
- package/dist/documentation.js.map +1 -0
- package/dist/editor/completions.d.ts +12 -0
- package/dist/editor/completions.d.ts.map +1 -0
- package/dist/editor/completions.js +6 -0
- package/dist/editor/completions.js.map +1 -0
- package/dist/editor/jsx-completion.d.ts +12 -0
- package/dist/editor/jsx-completion.d.ts.map +1 -0
- package/dist/editor/jsx-completion.js +137 -0
- package/dist/editor/jsx-completion.js.map +1 -0
- package/dist/editor/look-completion.d.ts +29 -0
- package/dist/editor/look-completion.d.ts.map +1 -0
- package/dist/editor/look-completion.js +159 -0
- package/dist/editor/look-completion.js.map +1 -0
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +12 -288
- package/dist/editor.js.map +1 -1
- package/dist/emit/components.d.ts +33 -0
- package/dist/emit/components.d.ts.map +1 -0
- package/dist/emit/components.js +135 -0
- package/dist/emit/components.js.map +1 -0
- package/dist/emit/jsx.d.ts +57 -0
- package/dist/emit/jsx.d.ts.map +1 -0
- package/dist/emit/jsx.js +384 -0
- package/dist/emit/jsx.js.map +1 -0
- package/dist/emit/look-css.d.ts +44 -0
- package/dist/emit/look-css.d.ts.map +1 -0
- package/dist/emit/look-css.js +177 -0
- package/dist/emit/look-css.js.map +1 -0
- package/dist/emit/look.d.ts +39 -0
- package/dist/emit/look.d.ts.map +1 -0
- package/dist/emit/look.js +218 -0
- package/dist/emit/look.js.map +1 -0
- package/dist/emit/runtime-imports.d.ts +40 -0
- package/dist/emit/runtime-imports.d.ts.map +1 -0
- package/dist/emit/runtime-imports.js +188 -0
- package/dist/emit/runtime-imports.js.map +1 -0
- package/dist/emitter.d.ts +15 -68
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +79 -1008
- package/dist/emitter.js.map +1 -1
- package/dist/look/animation.d.ts +7 -0
- package/dist/look/animation.d.ts.map +1 -0
- package/dist/look/animation.js +13 -0
- package/dist/look/animation.js.map +1 -0
- package/dist/look/builders.d.ts +35 -0
- package/dist/look/builders.d.ts.map +1 -0
- package/dist/look/builders.js +74 -0
- package/dist/look/builders.js.map +1 -0
- package/dist/look/css-functions.d.ts +28 -0
- package/dist/look/css-functions.d.ts.map +1 -0
- package/dist/look/css-functions.js +54 -0
- package/dist/look/css-functions.js.map +1 -0
- package/dist/look/keyword-sets.d.ts +41 -0
- package/dist/look/keyword-sets.d.ts.map +1 -0
- package/dist/look/keyword-sets.js +117 -0
- package/dist/look/keyword-sets.js.map +1 -0
- package/dist/look/keywords.d.ts +55 -0
- package/dist/look/keywords.d.ts.map +1 -0
- package/dist/look/keywords.js +387 -0
- package/dist/look/keywords.js.map +1 -0
- package/dist/look/media.d.ts +19 -0
- package/dist/look/media.d.ts.map +1 -0
- package/dist/look/media.js +27 -0
- package/dist/look/media.js.map +1 -0
- package/dist/look/naming.d.ts +9 -0
- package/dist/look/naming.d.ts.map +1 -0
- package/dist/look/naming.js +45 -0
- package/dist/look/naming.js.map +1 -0
- package/dist/look/properties.d.ts +16 -0
- package/dist/look/properties.d.ts.map +1 -0
- package/dist/look/properties.js +52 -0
- package/dist/look/properties.js.map +1 -0
- package/dist/look/shorthands.d.ts +25 -0
- package/dist/look/shorthands.d.ts.map +1 -0
- package/dist/look/shorthands.js +130 -0
- package/dist/look/shorthands.js.map +1 -0
- package/dist/look/tokens.d.ts +38 -0
- package/dist/look/tokens.d.ts.map +1 -0
- package/dist/look/tokens.js +59 -0
- package/dist/look/tokens.js.map +1 -0
- package/dist/look/units.d.ts +24 -0
- package/dist/look/units.d.ts.map +1 -0
- package/dist/look/units.js +33 -0
- package/dist/look/units.js.map +1 -0
- package/dist/look.d.ts +32 -227
- package/dist/look.d.ts.map +1 -1
- package/dist/look.js +28 -933
- package/dist/look.js.map +1 -1
- package/dist/module-roster.d.ts +16 -0
- package/dist/module-roster.d.ts.map +1 -0
- package/dist/module-roster.js +28 -0
- package/dist/module-roster.js.map +1 -0
- package/dist/modules/app.d.ts +9 -0
- package/dist/modules/app.d.ts.map +1 -0
- package/dist/modules/app.js +20 -0
- package/dist/modules/app.js.map +1 -0
- package/dist/modules/browser-test.d.ts +9 -0
- package/dist/modules/browser-test.d.ts.map +1 -0
- package/dist/modules/browser-test.js +67 -0
- package/dist/modules/browser-test.js.map +1 -0
- package/dist/modules/browser.d.ts +10 -0
- package/dist/modules/browser.d.ts.map +1 -0
- package/dist/modules/browser.js +89 -0
- package/dist/modules/browser.js.map +1 -0
- package/dist/modules/config.d.ts +9 -0
- package/dist/modules/config.d.ts.map +1 -0
- package/dist/modules/config.js +14 -0
- package/dist/modules/config.js.map +1 -0
- package/dist/modules/files.d.ts +9 -0
- package/dist/modules/files.d.ts.map +1 -0
- package/dist/modules/files.js +16 -0
- package/dist/modules/files.js.map +1 -0
- package/dist/modules/forms.d.ts +9 -0
- package/dist/modules/forms.d.ts.map +1 -0
- package/dist/modules/forms.js +26 -0
- package/dist/modules/forms.js.map +1 -0
- package/dist/modules/http.d.ts +9 -0
- package/dist/modules/http.d.ts.map +1 -0
- package/dist/modules/http.js +127 -0
- package/dist/modules/http.js.map +1 -0
- package/dist/modules/look.d.ts +9 -0
- package/dist/modules/look.d.ts.map +1 -0
- package/dist/modules/look.js +58 -0
- package/dist/modules/look.js.map +1 -0
- package/dist/modules/realtime.d.ts +9 -0
- package/dist/modules/realtime.d.ts.map +1 -0
- package/dist/modules/realtime.js +163 -0
- package/dist/modules/realtime.js.map +1 -0
- package/dist/modules/storage.d.ts +9 -0
- package/dist/modules/storage.d.ts.map +1 -0
- package/dist/modules/storage.js +59 -0
- package/dist/modules/storage.js.map +1 -0
- package/dist/modules/types.d.ts +78 -0
- package/dist/modules/types.d.ts.map +1 -0
- package/dist/modules/types.js +154 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/modules/web.d.ts +9 -0
- package/dist/modules/web.d.ts.map +1 -0
- package/dist/modules/web.js +63 -0
- package/dist/modules/web.js.map +1 -0
- package/dist/modules/websocket.d.ts +11 -0
- package/dist/modules/websocket.d.ts.map +1 -0
- package/dist/modules/websocket.js +75 -0
- package/dist/modules/websocket.js.map +1 -0
- package/dist/parser/expressions/jsx.d.ts +17 -0
- package/dist/parser/expressions/jsx.d.ts.map +1 -0
- package/dist/parser/expressions/jsx.js +43 -0
- package/dist/parser/expressions/jsx.js.map +1 -0
- package/dist/parser/expressions/keyframes.d.ts +15 -0
- package/dist/parser/expressions/keyframes.d.ts.map +1 -0
- package/dist/parser/expressions/keyframes.js +33 -0
- package/dist/parser/expressions/keyframes.js.map +1 -0
- package/dist/parser/expressions/look.d.ts +16 -0
- package/dist/parser/expressions/look.d.ts.map +1 -0
- package/dist/parser/expressions/look.js +37 -0
- package/dist/parser/expressions/look.js.map +1 -0
- package/dist/parser/expressions/visual-block.d.ts +17 -0
- package/dist/parser/expressions/visual-block.d.ts.map +1 -0
- package/dist/parser/expressions/visual-block.js +21 -0
- package/dist/parser/expressions/visual-block.js.map +1 -0
- package/dist/parser/keyframes-source.d.ts +24 -0
- package/dist/parser/keyframes-source.d.ts.map +1 -0
- package/dist/parser/keyframes-source.js +133 -0
- package/dist/parser/keyframes-source.js.map +1 -0
- package/dist/parser/look-source.d.ts +25 -0
- package/dist/parser/look-source.d.ts.map +1 -0
- package/dist/parser/look-source.js +189 -0
- package/dist/parser/look-source.js.map +1 -0
- package/dist/parser/spans.d.ts +24 -0
- package/dist/parser/spans.d.ts.map +1 -0
- package/dist/parser/spans.js +113 -0
- package/dist/parser/spans.js.map +1 -0
- package/dist/parser/statements/components.d.ts +37 -0
- package/dist/parser/statements/components.d.ts.map +1 -0
- package/dist/parser/statements/components.js +185 -0
- package/dist/parser/statements/components.js.map +1 -0
- package/dist/parser/statements/heads.d.ts +38 -0
- package/dist/parser/statements/heads.d.ts.map +1 -0
- package/dist/parser/statements/heads.js +55 -0
- package/dist/parser/statements/heads.js.map +1 -0
- package/dist/parser/statements/imports.d.ts +9 -0
- package/dist/parser/statements/imports.d.ts.map +1 -0
- package/dist/parser/statements/imports.js +31 -0
- package/dist/parser/statements/imports.js.map +1 -0
- package/dist/parser/statements/reactive.d.ts +31 -0
- package/dist/parser/statements/reactive.d.ts.map +1 -0
- package/dist/parser/statements/reactive.js +68 -0
- package/dist/parser/statements/reactive.js.map +1 -0
- package/dist/parser/statements/unsafe-css.d.ts +21 -0
- package/dist/parser/statements/unsafe-css.d.ts.map +1 -0
- package/dist/parser/statements/unsafe-css.js +42 -0
- package/dist/parser/statements/unsafe-css.js.map +1 -0
- package/dist/parser.d.ts +37 -50
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +98 -882
- package/dist/parser.js.map +1 -1
- package/dist/runtime-sources.generated.d.ts +1 -1
- package/dist/runtime-sources.generated.d.ts.map +1 -1
- package/dist/runtime-sources.generated.js +4 -4
- package/dist/runtime-sources.generated.js.map +1 -1
- package/package.json +2 -2
package/dist/parser.js
CHANGED
|
@@ -1,107 +1,97 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The Web parser's composition root. `parser.ts` was one 1,050-line module;
|
|
3
|
+
* D115 P4 R3e split the syntax families into `parser/` and left the class that
|
|
4
|
+
* the Web extension registers here — its one field, the eight `protected` seams,
|
|
5
|
+
* and the host every collaborator reaches back through.
|
|
6
|
+
*
|
|
7
|
+
* Where to look:
|
|
8
|
+
*
|
|
9
|
+
* - `parser/spans.ts` span shifting, the three diagnostic constructors
|
|
10
|
+
* - `parser/look-source.ts` a `look:` block's own indented source
|
|
11
|
+
* - `parser/keyframes-source.ts` a `keyframes:` block's own indented source
|
|
12
|
+
* - `parser/statements/heads.ts` whether a contextual word is a declaration head
|
|
13
|
+
* - `parser/statements/components.ts` props, lifecycle names, the component item loop
|
|
14
|
+
* - `parser/statements/reactive.ts` state, computed, resource, action, watch
|
|
15
|
+
* - `parser/statements/imports.ts` `import css unsafe`
|
|
16
|
+
* - `parser/statements/unsafe-css.ts` the inline `unsafe css` block and its placement
|
|
17
|
+
* - `parser/expressions/jsx.ts` the JSX token and its interpolations
|
|
18
|
+
* - `parser/expressions/look.ts` `look:` as a value
|
|
19
|
+
* - `parser/expressions/keyframes.ts` `keyframes:` as a value
|
|
20
|
+
* - `parser/expressions/visual-block.ts` consuming the block those two are written in
|
|
21
|
+
*
|
|
22
|
+
* A collaborator never names `VelarWebParser`: it declares the interface it
|
|
23
|
+
* needs, and that interface is the record of what it depends on. The host's
|
|
24
|
+
* properties are live reads of the parser, because the token cursor moves under
|
|
25
|
+
* a collaborator while it runs.
|
|
26
|
+
*/
|
|
27
|
+
import {} from "@velarscript/compiler";
|
|
28
|
+
import { Parser, } from "@velarscript/compiler/extension";
|
|
29
|
+
import { WEB_JSX_TOKEN } from "./lexer.js";
|
|
30
|
+
import { parseJsxExpression } from "./parser/expressions/jsx.js";
|
|
31
|
+
import { parseKeyframesExpression } from "./parser/expressions/keyframes.js";
|
|
32
|
+
import { parseLookExpression } from "./parser/expressions/look.js";
|
|
33
|
+
import { parseComponent, parseComponentProps } from "./parser/statements/components.js";
|
|
34
|
+
import { actionHeaderShapes, blockHeaderAhead, componentHeaderShapes, exposeItemAhead, namedDeclarationAhead, reactiveBindingShapes, } from "./parser/statements/heads.js";
|
|
35
|
+
import { parseCssImport } from "./parser/statements/imports.js";
|
|
36
|
+
import { parseActionDeclaration, parseComputedDeclaration, parseResourceDeclaration, parseStateDeclaration, parseWatchDeclaration, } from "./parser/statements/reactive.js";
|
|
37
|
+
import { parseUnsafeCssStatement } from "./parser/statements/unsafe-css.js";
|
|
38
|
+
import { diagnostic, span } from "./parser/spans.js";
|
|
20
39
|
export class VelarWebParser extends Parser {
|
|
21
40
|
insideComponentProps = 0;
|
|
41
|
+
host;
|
|
42
|
+
/**
|
|
43
|
+
* The one object every collaborator is handed. Its properties are live reads
|
|
44
|
+
* of the parser: the cursor moves under a collaborator, so `current()`, the
|
|
45
|
+
* diagnostics array and `insideComponentProps` must be the parser's own and
|
|
46
|
+
* not a snapshot taken when the host was built. `insideComponentProps` is
|
|
47
|
+
* `private` here, so it arrives as an accessor pair — the component body loop
|
|
48
|
+
* raises it, the prop list reads it back.
|
|
49
|
+
*/
|
|
50
|
+
webParserHost() {
|
|
51
|
+
const parser = this;
|
|
52
|
+
return {
|
|
53
|
+
advance: () => parser.advance(),
|
|
54
|
+
check: (kind) => parser.check(kind),
|
|
55
|
+
checkWord: (value) => parser.checkWord(value),
|
|
56
|
+
consumeNewlines: () => parser.consumeNewlines(),
|
|
57
|
+
current: () => parser.current(),
|
|
58
|
+
get diagnostics() { return parser.diagnostics; },
|
|
59
|
+
expect: (kind, message) => parser.expect(kind, message),
|
|
60
|
+
expectStatementBoundary: () => parser.expectStatementBoundary(),
|
|
61
|
+
get insideComponentProps() { return parser.insideComponentProps; },
|
|
62
|
+
set insideComponentProps(value) { parser.insideComponentProps = value; },
|
|
63
|
+
match: (kind) => parser.match(kind),
|
|
64
|
+
matchExtensionKeyword: (value) => parser.matchExtensionKeyword(value),
|
|
65
|
+
matchWord: (value) => parser.matchWord(value),
|
|
66
|
+
parseBlock: () => parser.parseBlock(),
|
|
67
|
+
parseExpression: (minimumPrecedence) => parser.parseExpression(minimumPrecedence),
|
|
68
|
+
parseNestedExpression: (fragment, offset, bracketFragment, sourceOffsets) => parser.parseNestedExpression(fragment, offset, bracketFragment, sourceOffsets),
|
|
69
|
+
parseParameters: () => parser.parseParameters(),
|
|
70
|
+
parseStatement: () => parser.parseStatement(),
|
|
71
|
+
parseTypeParameters: () => parser.parseTypeParameters(),
|
|
72
|
+
parseTypeReference: (allowTrailingOptional) => parser.parseTypeReference(allowTrailingOptional),
|
|
73
|
+
peekKind: (distance) => parser.peekKind(distance),
|
|
74
|
+
peekValue: (distance) => parser.peekValue(distance),
|
|
75
|
+
previous: () => parser.previous(),
|
|
76
|
+
skipMistypedDeclaration: () => parser.skipMistypedDeclaration(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
22
79
|
constructor(tokens, lexicalExtensions) {
|
|
23
80
|
super(tokens, lexicalExtensions);
|
|
81
|
+
this.host = this.webParserHost();
|
|
24
82
|
}
|
|
25
83
|
/**
|
|
26
84
|
* WEB-N4: a component prop list is where a Web author reaches for the HTML
|
|
27
|
-
* names, and
|
|
28
|
-
* identifiers. Recovering the keyword as the prop name turns an eleven-message
|
|
29
|
-
* parser cascade into one directed message, and a declaration-position `?`
|
|
30
|
-
* teaches the default value that actually makes a prop omittable.
|
|
85
|
+
* names, and Core's own list is what every other parameter list is read with.
|
|
31
86
|
*/
|
|
32
87
|
parseParameters() {
|
|
33
88
|
if (this.insideComponentProps === 0)
|
|
34
89
|
return super.parseParameters();
|
|
35
|
-
this.
|
|
36
|
-
const parameters = [];
|
|
37
|
-
if (!this.check("rightParen")) {
|
|
38
|
-
do {
|
|
39
|
-
if (this.match("ellipsis"))
|
|
40
|
-
this.diagnostics.push(diagnostic("VEL2016", "Components use named props and do not support rest parameters", this.previous().span));
|
|
41
|
-
// 'class' and 'look' are the props every component already carries, so
|
|
42
|
-
// the same message answers both the keyword token and the now-ordinary
|
|
43
|
-
// name.
|
|
44
|
-
const universalProp = this.checkWord("look") || this.check("class");
|
|
45
|
-
if (universalProp) {
|
|
46
|
-
const token = this.advance();
|
|
47
|
-
this.diagnostics.push(diagnostic("VEL2016", `Every component already accepts '${token.value}'; remove it from the prop list and pass it at the call site with ${token.value}={...}`, token.span));
|
|
48
|
-
}
|
|
49
|
-
const nameToken = universalProp ? this.previous() : this.check("identifier") ? this.advance() : this.componentPropKeyword();
|
|
50
|
-
if (!nameToken) {
|
|
51
|
-
this.diagnostics.push(diagnostic("VEL2016", "A component prop list holds 'name: Type' props separated by commas", this.current().span));
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
let optionalMarker = false;
|
|
55
|
-
if (this.check("question")) {
|
|
56
|
-
this.advance();
|
|
57
|
-
optionalMarker = true;
|
|
58
|
-
}
|
|
59
|
-
const type = this.match("colon") ? this.parseTypeReference() : null;
|
|
60
|
-
let defaultValue = this.match("assign") ? this.parseExpression() : null;
|
|
61
|
-
if (optionalMarker) {
|
|
62
|
-
this.diagnostics.push(diagnostic("VEL2016", `A component prop becomes omittable through its default value, not through '?': write '${nameToken.value}: Type = default' for a real default, or '${nameToken.value}: Type? = null' when absence is the value`, span(nameToken.span.start, (defaultValue ?? type ?? nameToken).span.end)));
|
|
63
|
-
defaultValue ??= { kind: "LiteralExpression", value: null, raw: "null", span: nameToken.span };
|
|
64
|
-
}
|
|
65
|
-
parameters.push({
|
|
66
|
-
name: nameToken.value,
|
|
67
|
-
type: optionalMarker && type ? { syntax: { kind: "OptionalTypeSyntax", inner: type.syntax, span: type.span }, span: type.span } : type,
|
|
68
|
-
defaultValue,
|
|
69
|
-
rest: false,
|
|
70
|
-
span: span(nameToken.span.start, (defaultValue ?? type ?? nameToken).span.end),
|
|
71
|
-
});
|
|
72
|
-
} while (this.match("comma") && !this.check("rightParen"));
|
|
73
|
-
}
|
|
74
|
-
this.expect("rightParen", "Expected ')' after parameters");
|
|
75
|
-
return parameters;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Consumes a keyword standing where a prop name belongs and reports the one
|
|
79
|
-
* message that names it, or returns null when the token cannot be a name.
|
|
80
|
-
*/
|
|
81
|
-
componentPropKeyword() {
|
|
82
|
-
const token = this.current();
|
|
83
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(token.value))
|
|
84
|
-
return null;
|
|
85
|
-
if (token.kind === "identifier" || token.kind === "eof" || token.kind === "newline")
|
|
86
|
-
return null;
|
|
87
|
-
this.advance();
|
|
88
|
-
this.diagnostics.push(diagnostic("VEL2016", `'${token.value}' is a VelarScript keyword and cannot name a component prop; choose another name`, token.span));
|
|
89
|
-
return token;
|
|
90
|
+
return parseComponentProps(this.host);
|
|
90
91
|
}
|
|
91
92
|
createNestedParser(tokens) {
|
|
92
93
|
return new VelarWebParser(tokens, this.lexicalExtensions);
|
|
93
94
|
}
|
|
94
|
-
/**
|
|
95
|
-
* `@` selects the component's closed compiler namespace. The role names are
|
|
96
|
-
* `mounted` and `cleanup`; neither is found through author-name lookup.
|
|
97
|
-
*/
|
|
98
|
-
matchLifecycleHook(name) {
|
|
99
|
-
if (!this.check("at") || this.peekKind(1) !== "identifier" || this.peekValue(1) !== name)
|
|
100
|
-
return false;
|
|
101
|
-
this.advance();
|
|
102
|
-
this.advance();
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
95
|
validateExtensionTypeArguments(name, arguments_, nameSpan) {
|
|
106
96
|
if (name !== "Component")
|
|
107
97
|
return false;
|
|
@@ -125,41 +115,41 @@ export class VelarWebParser extends Parser {
|
|
|
125
115
|
this.skipMistypedDeclaration();
|
|
126
116
|
return { kind: "PassStatement", span: span(keyword.span.start, name.span.end) };
|
|
127
117
|
}
|
|
128
|
-
if (this.
|
|
118
|
+
if (namedDeclarationAhead(this.host, "component", componentHeaderShapes)) {
|
|
129
119
|
this.advance();
|
|
130
120
|
if (modifiers.abstract)
|
|
131
121
|
this.diagnostics.push(diagnostic("VEL2013", "Only classes can be declared with 'abstract'", this.previous().span));
|
|
132
122
|
if (modifiers.asynchronous)
|
|
133
123
|
this.diagnostics.push(diagnostic("VEL2013", "Components are not declared with 'async'", this.previous().span));
|
|
134
|
-
return this.
|
|
124
|
+
return parseComponent(this.host, start, modifiers.exported);
|
|
135
125
|
}
|
|
136
126
|
if (modifiers.abstract || modifiers.asynchronous)
|
|
137
127
|
return undefined;
|
|
138
|
-
if (this.
|
|
128
|
+
if (namedDeclarationAhead(this.host, "state", reactiveBindingShapes)) {
|
|
139
129
|
this.advance();
|
|
140
|
-
return this.
|
|
130
|
+
return parseStateDeclaration(this.host, start, modifiers.exported);
|
|
141
131
|
}
|
|
142
|
-
if (this.
|
|
132
|
+
if (namedDeclarationAhead(this.host, "computed", reactiveBindingShapes)) {
|
|
143
133
|
this.advance();
|
|
144
|
-
return this.
|
|
134
|
+
return parseComputedDeclaration(this.host, start, modifiers.exported);
|
|
145
135
|
}
|
|
146
|
-
if (this.
|
|
136
|
+
if (namedDeclarationAhead(this.host, "resource", reactiveBindingShapes)) {
|
|
147
137
|
this.advance();
|
|
148
138
|
if (modifiers.exported)
|
|
149
139
|
this.diagnostics.push(diagnostic("VEL2018", "A resource is component-owned and cannot be exported", this.previous().span));
|
|
150
|
-
return this.
|
|
140
|
+
return parseResourceDeclaration(this.host, start, modifiers.exported);
|
|
151
141
|
}
|
|
152
|
-
if (this.
|
|
142
|
+
if (namedDeclarationAhead(this.host, "action", actionHeaderShapes)) {
|
|
153
143
|
this.advance();
|
|
154
|
-
return this.
|
|
144
|
+
return parseActionDeclaration(this.host, start, modifiers.exported);
|
|
155
145
|
}
|
|
156
|
-
if (this.
|
|
146
|
+
if (blockHeaderAhead(this.host, "watch")) {
|
|
157
147
|
this.advance();
|
|
158
148
|
if (modifiers.exported)
|
|
159
149
|
this.diagnostics.push(diagnostic("VEL2001", "A watch block cannot be exported", this.previous().span));
|
|
160
|
-
return this.
|
|
150
|
+
return parseWatchDeclaration(this.host, start);
|
|
161
151
|
}
|
|
162
|
-
if (this.
|
|
152
|
+
if (exposeItemAhead(this.host)) {
|
|
163
153
|
this.advance();
|
|
164
154
|
const value = this.parseExpression();
|
|
165
155
|
this.diagnostics.push(diagnostic("VEL5056", "'expose' is only valid as a top-level component item; declare 'exposes HandleType' on that component", span(start, value.span.end)));
|
|
@@ -168,119 +158,14 @@ export class VelarWebParser extends Parser {
|
|
|
168
158
|
return undefined;
|
|
169
159
|
}
|
|
170
160
|
parseUnsafeExtensionStatement(start) {
|
|
171
|
-
|
|
172
|
-
|| this.peekValue(1) !== WEB_UNSAFE_CSS_TOKEN)
|
|
173
|
-
return undefined;
|
|
174
|
-
this.advance();
|
|
175
|
-
const token = this.advance();
|
|
176
|
-
const payload = token.payload;
|
|
177
|
-
if (!payload || payload.kind !== "WebUnsafeCssBlockSyntax") {
|
|
178
|
-
this.diagnostics.push(diagnostic("VEL5037", "The inline unsafe CSS token is missing its raw source", token.span));
|
|
179
|
-
return { kind: "PassStatement", span: token.span };
|
|
180
|
-
}
|
|
181
|
-
const placement = this.parseUnsafeCssPlacement();
|
|
182
|
-
const declaration = {
|
|
183
|
-
kind: "ExtensionStatement:web:unsafe-css",
|
|
184
|
-
source: { kind: "inline", css: payload.css, span: payload.contentSpan },
|
|
185
|
-
placement,
|
|
186
|
-
span: span(start, this.previous().span.end),
|
|
187
|
-
};
|
|
188
|
-
return declaration;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* D30 item 16: a Web declaration head is claimed only in its own declaration
|
|
192
|
-
* shape — the word, a name, and one of the tokens that can follow it there.
|
|
193
|
-
* `state = 1`, `state(x)`, and `state.field` all keep the identifier reading.
|
|
194
|
-
*/
|
|
195
|
-
namedDeclarationAhead(word, shapes) {
|
|
196
|
-
return this.checkWord(word) && this.peekKind(1) === "identifier" && shapes.has(this.peekKind(2));
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* `watch` opens a block: its header line ends in ':' and an indented body
|
|
200
|
-
* follows. No expression statement can end in ':', so the lookahead is exact
|
|
201
|
-
* and `watch = 1` / `watch(value)` stay ordinary code.
|
|
202
|
-
*/
|
|
203
|
-
blockHeaderAhead(word) {
|
|
204
|
-
if (!this.checkWord(word))
|
|
205
|
-
return false;
|
|
206
|
-
let depth = 0;
|
|
207
|
-
let offset = 1;
|
|
208
|
-
for (;; offset += 1) {
|
|
209
|
-
const kind = this.peekKind(offset);
|
|
210
|
-
if (kind === "leftParen" || kind === "leftBracket" || kind === "leftBrace")
|
|
211
|
-
depth += 1;
|
|
212
|
-
else if (kind === "rightParen" || kind === "rightBracket" || kind === "rightBrace")
|
|
213
|
-
depth -= 1;
|
|
214
|
-
else if (kind === "eof")
|
|
215
|
-
return false;
|
|
216
|
-
else if (depth === 0 && (kind === "newline" || kind === "dedent"))
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
if (offset < 3 || this.peekKind(offset - 1) !== "colon")
|
|
220
|
-
return false;
|
|
221
|
-
while (this.peekKind(offset) === "newline")
|
|
222
|
-
offset += 1;
|
|
223
|
-
return this.peekKind(offset) === "indent";
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* `expose value` is the one Web statement head followed by a bare expression
|
|
227
|
-
* rather than by a name and a shape token. It is claimed only when the next
|
|
228
|
-
* token opens a fresh value, so `expose(handle)` reads as a call and
|
|
229
|
-
* `expose = handle` as an assignment — the identifier reading wins wherever
|
|
230
|
-
* the two could compete.
|
|
231
|
-
*/
|
|
232
|
-
exposeItemAhead() {
|
|
233
|
-
return this.checkWord("expose") && exposeValueStartKinds.has(this.peekKind(1));
|
|
161
|
+
return parseUnsafeCssStatement(this.host, start);
|
|
234
162
|
}
|
|
235
163
|
parseExtensionImport(start) {
|
|
236
|
-
|
|
237
|
-
// the CSS boundary is claimed only when the word is followed by the
|
|
238
|
-
// boundary marker or by the path itself, so a module named `css` still
|
|
239
|
-
// imports by name.
|
|
240
|
-
if (!this.checkWord("css") || !(this.peekKind(1) === "unsafe" || this.peekKind(1) === "string"))
|
|
241
|
-
return undefined;
|
|
242
|
-
this.advance();
|
|
243
|
-
this.expect("unsafe", "Native CSS is an unsafe boundary; write 'import css unsafe'");
|
|
244
|
-
const source = this.expect("string", "Expected a relative .css path after 'import css unsafe'");
|
|
245
|
-
const placement = this.parseUnsafeCssPlacement();
|
|
246
|
-
if ((!source.value.startsWith("./") && !source.value.startsWith("../")) || !source.value.endsWith(".css")) {
|
|
247
|
-
this.diagnostics.push(diagnostic("VEL5037", "Unsafe CSS imports require an explicit relative path ending in '.css'", source.span));
|
|
248
|
-
}
|
|
249
|
-
const declaration = {
|
|
250
|
-
kind: "ExtensionStatement:web:unsafe-css",
|
|
251
|
-
source: { kind: "external", path: source.value, span: source.span },
|
|
252
|
-
placement,
|
|
253
|
-
span: span(start, this.previous().span.end),
|
|
254
|
-
};
|
|
255
|
-
return declaration;
|
|
256
|
-
}
|
|
257
|
-
parseUnsafeCssPlacement() {
|
|
258
|
-
let placement = "before";
|
|
259
|
-
if (this.current().kind === "identifier" && this.current().value === "before") {
|
|
260
|
-
this.expect("identifier", "Expected 'before'");
|
|
261
|
-
placement = "before";
|
|
262
|
-
}
|
|
263
|
-
else if (this.current().kind === "identifier" && this.current().value === "after") {
|
|
264
|
-
this.expect("identifier", "Expected 'after'");
|
|
265
|
-
placement = "after";
|
|
266
|
-
}
|
|
267
|
-
else
|
|
268
|
-
this.diagnostics.push(diagnostic("VEL5037", "Unsafe CSS must explicitly declare 'before look' or 'after look'", this.current().span));
|
|
269
|
-
if (!this.matchExtensionKeyword("look")) {
|
|
270
|
-
this.diagnostics.push(diagnostic("VEL5037", "Unsafe CSS order must end with 'look'", this.current().span));
|
|
271
|
-
}
|
|
272
|
-
return placement;
|
|
164
|
+
return parseCssImport(this.host, start);
|
|
273
165
|
}
|
|
274
166
|
parseExtensionExpression(token) {
|
|
275
|
-
if (token.kind === "extensionToken" && token.value === WEB_JSX_TOKEN)
|
|
276
|
-
|
|
277
|
-
if (!payload || payload.kind !== "WebJsxElementSyntax") {
|
|
278
|
-
this.diagnostics.push(diagnostic("VEL5001", "The Web JSX token is missing its structured syntax", token.span));
|
|
279
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: token.span };
|
|
280
|
-
}
|
|
281
|
-
const syntax = shiftJsxSyntax(payload, token.span.start - payload.span.start);
|
|
282
|
-
return jsxExpression(syntax, (source) => this.parseJsxEmbedded(source), (item) => this.diagnostics.push(item));
|
|
283
|
-
}
|
|
167
|
+
if (token.kind === "extensionToken" && token.value === WEB_JSX_TOKEN)
|
|
168
|
+
return parseJsxExpression(this.host, token);
|
|
284
169
|
if (token.kind !== "identifier")
|
|
285
170
|
return undefined;
|
|
286
171
|
// `look:` and `keyframes:` open a block-valued expression. Without the ':'
|
|
@@ -288,679 +173,10 @@ export class VelarWebParser extends Parser {
|
|
|
288
173
|
if (!this.check("colon"))
|
|
289
174
|
return undefined;
|
|
290
175
|
if (token.value === "keyframes")
|
|
291
|
-
return this.
|
|
176
|
+
return parseKeyframesExpression(this.host, token);
|
|
292
177
|
if (token.value !== "look")
|
|
293
178
|
return undefined;
|
|
294
|
-
|
|
295
|
-
// expectation failed in turn; the whole shape is read up front, once, instead.
|
|
296
|
-
const layout = visualBlockLayout((distance) => this.peekKind(distance));
|
|
297
|
-
if (layout === "none") {
|
|
298
|
-
this.diagnostics.push(diagnostic("VEL5038", "A Look value is written as 'look:' followed by an indented block of 'property = value' entries", token.span));
|
|
299
|
-
this.skipMistypedDeclaration();
|
|
300
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: token.span };
|
|
301
|
-
}
|
|
302
|
-
if (layout === "empty") {
|
|
303
|
-
// The colon stays unconsumed so the surrounding statement still ends at
|
|
304
|
-
// its own newline; only this one message describes the missing block.
|
|
305
|
-
this.diagnostics.push(diagnostic("VEL5038", "A Look block requires at least one indented 'property = value' entry", token.span));
|
|
306
|
-
this.advance();
|
|
307
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: token.span };
|
|
308
|
-
}
|
|
309
|
-
const block = this.consumeVisualBlock(layout, "Expected Look entries", "Expected the end of the Look block");
|
|
310
|
-
const payload = block.value === WEB_LOOK_TOKEN ? block.payload : undefined;
|
|
311
|
-
if (!payload || payload.kind !== "WebLookBlockSyntax") {
|
|
312
|
-
this.diagnostics.push(diagnostic("VEL5038", "The Look block is missing its structured syntax", block.span));
|
|
313
|
-
}
|
|
314
|
-
const syntax = payload?.kind === "WebLookBlockSyntax"
|
|
315
|
-
? shiftLookSyntax(payload, block.span.start - payload.span.start)
|
|
316
|
-
: undefined;
|
|
317
|
-
const entries = new LookSourceParser(syntax ?? { kind: "WebLookBlockSyntax", lines: [], span: block.span }, (text, offset, openingIndent) => openingIndent
|
|
318
|
-
? this.parseNestedExpression(openingIndent + text, offset - openingIndent.length, true)
|
|
319
|
-
: this.parseNestedExpression(text, offset), (item) => this.diagnostics.push(item)).parse();
|
|
320
|
-
const expression = { kind: "ExtensionExpression:web:look", entries, span: span(token.span.start, block.span.end) };
|
|
321
|
-
return expression;
|
|
322
|
-
}
|
|
323
|
-
/** Consumes the ':' , the layout around the block, and the block itself. */
|
|
324
|
-
consumeVisualBlock(layout, entries, end) {
|
|
325
|
-
this.advance();
|
|
326
|
-
if (layout === "bracketed")
|
|
327
|
-
return this.expect("extensionToken", entries);
|
|
328
|
-
this.consumeNewlines();
|
|
329
|
-
this.advance();
|
|
330
|
-
const block = this.expect("extensionToken", entries);
|
|
331
|
-
this.consumeNewlines();
|
|
332
|
-
this.expect("dedent", end);
|
|
333
|
-
return block;
|
|
334
|
-
}
|
|
335
|
-
parseKeyframesExpression(token) {
|
|
336
|
-
const layout = visualBlockLayout((distance) => this.peekKind(distance));
|
|
337
|
-
if (layout === "none") {
|
|
338
|
-
this.diagnostics.push(diagnostic("VEL5060", "A keyframes value is written as 'keyframes:' followed by indented 'from:', 'to:', or 'N%:' stops", token.span));
|
|
339
|
-
this.skipMistypedDeclaration();
|
|
340
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: token.span };
|
|
341
|
-
}
|
|
342
|
-
if (layout === "empty") {
|
|
343
|
-
this.diagnostics.push(diagnostic("VEL5060", "A keyframes block requires at least one indented stop", token.span));
|
|
344
|
-
this.advance();
|
|
345
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: token.span };
|
|
346
|
-
}
|
|
347
|
-
const block = this.consumeVisualBlock(layout, "Expected keyframe stops", "Expected the end of the keyframes block");
|
|
348
|
-
const payload = block.value === WEB_KEYFRAMES_TOKEN ? block.payload : undefined;
|
|
349
|
-
if (!payload || payload.kind !== "WebKeyframesBlockSyntax") {
|
|
350
|
-
this.diagnostics.push(diagnostic("VEL5060", "The keyframes block is missing its structured syntax", block.span));
|
|
351
|
-
}
|
|
352
|
-
const syntax = payload?.kind === "WebKeyframesBlockSyntax"
|
|
353
|
-
? shiftKeyframesSyntax(payload, block.span.start - payload.span.start)
|
|
354
|
-
: undefined;
|
|
355
|
-
const stops = new KeyframesSourceParser(syntax ?? { kind: "WebKeyframesBlockSyntax", lines: [], span: block.span }, (text, offset, openingIndent) => openingIndent
|
|
356
|
-
? this.parseNestedExpression(openingIndent + text, offset - openingIndent.length, true)
|
|
357
|
-
: this.parseNestedExpression(text, offset), (item) => this.diagnostics.push(item)).parse();
|
|
358
|
-
const expression = { kind: "ExtensionExpression:web:keyframes", stops, span: span(token.span.start, block.span.end) };
|
|
359
|
-
return expression;
|
|
360
|
-
}
|
|
361
|
-
// A '{for item in items: ...}' block inside JSX gets targeted guidance to
|
|
362
|
-
// '.map(...)' instead of an expression-parse cascade; there is no magic JSX
|
|
363
|
-
// control flow. The child recovers as an inert null literal so the rest of
|
|
364
|
-
// the module still analyzes and reports its own guidance in the same compile.
|
|
365
|
-
parseJsxEmbedded(source) {
|
|
366
|
-
// WEB-U13: '{/* ... */}' is the JSX comment habit. VelarScript has no block
|
|
367
|
-
// comment at all, so the interpolation gets one message naming '//' instead
|
|
368
|
-
// of two 'Expected an expression' failures.
|
|
369
|
-
if (/^\s*\/[*/]/u.test(source.source)) {
|
|
370
|
-
this.diagnostics.push(recoveredDiagnostic("VEL5002", "JSX has no comment form; write a '//' comment on its own line outside the markup", source.span));
|
|
371
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: source.span };
|
|
372
|
-
}
|
|
373
|
-
if (/^\s*for\b/u.test(source.source)) {
|
|
374
|
-
const detail = /^\s*for\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\s+([^:{\n]+):/u.exec(source.source);
|
|
375
|
-
const binding = detail?.[1] ?? "item";
|
|
376
|
-
const iterable = detail?.[2]?.trim() || "items";
|
|
377
|
-
this.diagnostics.push({
|
|
378
|
-
code: "VEL5049",
|
|
379
|
-
message: `Use '{${iterable}.map((${binding}) => ...)}'; JSX has no 'for' blocks, so lists render with '.map(...)'`,
|
|
380
|
-
span: source.span,
|
|
381
|
-
recovered: true,
|
|
382
|
-
});
|
|
383
|
-
return { kind: "LiteralExpression", value: null, raw: "null", span: source.span };
|
|
384
|
-
}
|
|
385
|
-
// JSX interpolation braces are a bracket context: the expression inside
|
|
386
|
-
// '{...}' continues across physical lines exactly as inside parentheses.
|
|
387
|
-
const layoutAtStart = /^[ \t]*(?:rf|fr|f|r)?["'](?:\r\n|\r|\n)/u.test(source.source);
|
|
388
|
-
return layoutAtStart
|
|
389
|
-
? this.parseNestedExpression(source.openingIndent + source.source, source.span.start - source.openingIndent.length, true)
|
|
390
|
-
: this.parseNestedExpression(source.source, source.span.start, true);
|
|
391
|
-
}
|
|
392
|
-
parseStateDeclaration(start, exported) {
|
|
393
|
-
const name = this.expect("identifier", "Expected a state name");
|
|
394
|
-
const type = this.match("colon") ? this.parseTypeReference() : null;
|
|
395
|
-
this.expect("assign", "Expected '=' after state name");
|
|
396
|
-
const initializer = this.parseExpression();
|
|
397
|
-
return { kind: "ExtensionStatement:web:state", exported, name: name.value, type, initializer, span: span(start, initializer.span.end) };
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* D71 rule 182: `computed` parses exactly where `state` parses, through the
|
|
401
|
-
* same shape lookahead — the two halves of the reactive row differ in what
|
|
402
|
-
* they mean, not in how they are written.
|
|
403
|
-
*/
|
|
404
|
-
parseComputedDeclaration(start, exported) {
|
|
405
|
-
const name = this.expect("identifier", "Expected a computed name");
|
|
406
|
-
const type = this.match("colon") ? this.parseTypeReference() : null;
|
|
407
|
-
this.expect("assign", "Expected '=' after computed name");
|
|
408
|
-
const initializer = this.parseExpression();
|
|
409
|
-
return { kind: "ExtensionStatement:web:computed", exported, name: name.value, type, initializer, span: span(start, initializer.span.end) };
|
|
410
|
-
}
|
|
411
|
-
parseResourceDeclaration(start, exported) {
|
|
412
|
-
const name = this.expect("identifier", "Expected a resource name");
|
|
413
|
-
const type = this.match("colon") ? this.parseTypeReference() : null;
|
|
414
|
-
this.expect("assign", "Expected '=' after resource name");
|
|
415
|
-
const initializer = this.parseExpression();
|
|
416
|
-
return { kind: "ExtensionStatement:web:resource", exported, name: name.value, type, initializer, span: span(start, initializer.span.end) };
|
|
417
|
-
}
|
|
418
|
-
parseActionDeclaration(start, exported) {
|
|
419
|
-
const name = this.expect("identifier", "Expected an action name");
|
|
420
|
-
const parameters = this.parseParameters();
|
|
421
|
-
const parameterListEnd = this.previous().span.end;
|
|
422
|
-
const returnType = this.match("arrow") ? this.parseTypeReference() : null;
|
|
423
|
-
const body = this.parseBlock();
|
|
424
|
-
const end = body.at(-1)?.span.end ?? returnType?.span.end ?? name.span.end;
|
|
425
|
-
return {
|
|
426
|
-
kind: "ExtensionStatement:web:action",
|
|
427
|
-
exported,
|
|
428
|
-
name: name.value,
|
|
429
|
-
parameters,
|
|
430
|
-
returnType,
|
|
431
|
-
...(returnType ? { resultAnnotationSpan: span(parameterListEnd, returnType.span.end) } : {}),
|
|
432
|
-
signatureSpan: span(start, returnType?.span.end ?? parameterListEnd),
|
|
433
|
-
body,
|
|
434
|
-
span: span(start, end),
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
parseWatchDeclaration(start) {
|
|
438
|
-
const expression = this.parseExpression();
|
|
439
|
-
let currentName = null;
|
|
440
|
-
let previousName = null;
|
|
441
|
-
if (this.matchWord("as")) {
|
|
442
|
-
currentName = this.expect("identifier", "Expected the current watch value name").value;
|
|
443
|
-
this.expect("comma", "Expected ',' between watch value names");
|
|
444
|
-
previousName = this.expect("identifier", "Expected the previous watch value name").value;
|
|
445
|
-
}
|
|
446
|
-
const body = this.parseBlock();
|
|
447
|
-
return { kind: "ExtensionStatement:web:watch", expression, currentName, previousName, body, span: span(start, body.at(-1)?.span.end ?? expression.span.end) };
|
|
448
|
-
}
|
|
449
|
-
parseComponent(start, exported) {
|
|
450
|
-
const name = this.expect("identifier", "Expected a component name");
|
|
451
|
-
if (this.check("less")) {
|
|
452
|
-
this.parseTypeParameters();
|
|
453
|
-
this.diagnostics.push(diagnostic("VEL2025", `Component '${name.value}' cannot declare type parameters; ${typeParameterDeclarationFormsPhrase()} take '<T>'`, name.span));
|
|
454
|
-
}
|
|
455
|
-
this.insideComponentProps += 1;
|
|
456
|
-
const parameters = this.check("leftParen") ? this.parseParameters() : [];
|
|
457
|
-
this.insideComponentProps -= 1;
|
|
458
|
-
for (const parameter of parameters) {
|
|
459
|
-
if (parameter.rest) {
|
|
460
|
-
this.diagnostics.push(diagnostic("VEL2016", "Components use named props and do not support rest parameters", parameter.span));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const handleType = this.matchExtensionKeyword("exposes") ? this.parseTypeReference() : null;
|
|
464
|
-
this.expect("colon", "Expected ':' before component body");
|
|
465
|
-
this.expect("newline", "Expected a newline before component body");
|
|
466
|
-
this.consumeNewlines();
|
|
467
|
-
this.expect("indent", "Expected an indented component body");
|
|
468
|
-
const body = [];
|
|
469
|
-
this.consumeNewlines();
|
|
470
|
-
while (!this.check("dedent") && !this.check("eof")) {
|
|
471
|
-
const itemStart = this.current().span.start;
|
|
472
|
-
let item = null;
|
|
473
|
-
if (this.exposeItemAhead()) {
|
|
474
|
-
this.advance();
|
|
475
|
-
const value = this.parseExpression();
|
|
476
|
-
item = { kind: "ExtensionStatement:web:expose", value, span: span(itemStart, value.span.end) };
|
|
477
|
-
}
|
|
478
|
-
else if (this.namedDeclarationAhead("state", reactiveBindingShapes)) {
|
|
479
|
-
this.advance();
|
|
480
|
-
item = this.parseStateDeclaration(itemStart, false);
|
|
481
|
-
}
|
|
482
|
-
else if (this.namedDeclarationAhead("computed", reactiveBindingShapes)) {
|
|
483
|
-
this.advance();
|
|
484
|
-
item = this.parseComputedDeclaration(itemStart, false);
|
|
485
|
-
}
|
|
486
|
-
else if (this.namedDeclarationAhead("resource", reactiveBindingShapes)) {
|
|
487
|
-
this.advance();
|
|
488
|
-
item = this.parseResourceDeclaration(itemStart, false);
|
|
489
|
-
}
|
|
490
|
-
else if (this.namedDeclarationAhead("action", actionHeaderShapes)) {
|
|
491
|
-
this.advance();
|
|
492
|
-
item = this.parseActionDeclaration(itemStart, false);
|
|
493
|
-
}
|
|
494
|
-
else if (this.blockHeaderAhead("watch")) {
|
|
495
|
-
this.advance();
|
|
496
|
-
item = this.parseWatchDeclaration(itemStart);
|
|
497
|
-
}
|
|
498
|
-
else if (this.matchLifecycleHook("mounted")) {
|
|
499
|
-
const body = this.parseBlock();
|
|
500
|
-
item = { kind: "ExtensionStatement:web:mounted", body, span: span(itemStart, body.at(-1)?.span.end ?? itemStart) };
|
|
501
|
-
}
|
|
502
|
-
else if (this.matchLifecycleHook("cleanup")) {
|
|
503
|
-
const body = this.parseBlock();
|
|
504
|
-
item = { kind: "ExtensionStatement:web:cleanup", body, span: span(itemStart, body.at(-1)?.span.end ?? itemStart) };
|
|
505
|
-
}
|
|
506
|
-
else if (this.check("at")) {
|
|
507
|
-
const marker = this.advance();
|
|
508
|
-
const name = this.check("identifier") ? this.advance() : null;
|
|
509
|
-
this.diagnostics.push(diagnostic("VEL5061", name
|
|
510
|
-
? `Unknown compiler-owned name '@${name.value}' in a component; the component namespace contains only '@mounted:' and '@cleanup:'`
|
|
511
|
-
: "Expected a compiler-owned component name after '@'; the component namespace contains only '@mounted:' and '@cleanup:'", span(marker.span.start, (name ?? marker).span.end)));
|
|
512
|
-
this.skipMistypedDeclaration();
|
|
513
|
-
}
|
|
514
|
-
else if (this.check("identifier") && lifecycleHookSpellings.has(this.current().value) && this.peekKind(1) === "colon") {
|
|
515
|
-
// The bare words are ordinary author names. Recover as the one accepted
|
|
516
|
-
// compiler-owned spelling so the body keeps analyzing, while retaining
|
|
517
|
-
// a diagnostic: recovery is not a second alias.
|
|
518
|
-
const keyword = this.advance();
|
|
519
|
-
this.diagnostics.push(recoveredDiagnostic("VEL5061", `Use '@${keyword.value}:'; it is a compiler-owned component name, which leaves '${keyword.value}' free for your own method`, keyword.span));
|
|
520
|
-
const body = this.parseBlock();
|
|
521
|
-
item = keyword.value === "mounted"
|
|
522
|
-
? { kind: "ExtensionStatement:web:mounted", body, span: span(itemStart, body.at(-1)?.span.end ?? itemStart) }
|
|
523
|
-
: { kind: "ExtensionStatement:web:cleanup", body, span: span(itemStart, body.at(-1)?.span.end ?? itemStart) };
|
|
524
|
-
}
|
|
525
|
-
else if (this.check("identifier") && renderBlockSpellings.has(this.current().value) && this.peekKind(1) === "colon") {
|
|
526
|
-
const keyword = this.advance();
|
|
527
|
-
this.diagnostics.push(diagnostic("VEL5048", `Use 'return <...>'; a component returns its JSX directly and has no '${keyword.value}:' block`, keyword.span));
|
|
528
|
-
this.skipMistypedDeclaration();
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
item = this.parseStatement();
|
|
532
|
-
}
|
|
533
|
-
if (item)
|
|
534
|
-
body.push(item);
|
|
535
|
-
if (this.previous().kind !== "dedent")
|
|
536
|
-
this.expectStatementBoundary();
|
|
537
|
-
this.consumeNewlines();
|
|
538
|
-
}
|
|
539
|
-
const close = this.expect("dedent", "Expected the end of component body");
|
|
540
|
-
return { kind: "ExtensionStatement:web:component", exported, name: name.value, parameters, handleType, body, span: span(start, body.at(-1)?.span.end ?? close.span.end) };
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
function shiftSourceSpan(sourceSpan, offset) {
|
|
544
|
-
return offset === 0 ? sourceSpan : span(sourceSpan.start + offset, sourceSpan.end + offset);
|
|
545
|
-
}
|
|
546
|
-
function shiftExpressionSource(source, offset) {
|
|
547
|
-
return offset === 0 ? source : { ...source, span: shiftSourceSpan(source.span, offset) };
|
|
548
|
-
}
|
|
549
|
-
function shiftJsxSyntax(syntax, offset) {
|
|
550
|
-
if (offset === 0)
|
|
551
|
-
return syntax;
|
|
552
|
-
return {
|
|
553
|
-
...syntax,
|
|
554
|
-
span: shiftSourceSpan(syntax.span, offset),
|
|
555
|
-
tagSpan: shiftSourceSpan(syntax.tagSpan, offset),
|
|
556
|
-
attributes: syntax.attributes.map((attribute) => ({
|
|
557
|
-
...attribute,
|
|
558
|
-
span: shiftSourceSpan(attribute.span, offset),
|
|
559
|
-
value: typeof attribute.value === "object" && attribute.value !== null
|
|
560
|
-
? shiftExpressionSource(attribute.value, offset)
|
|
561
|
-
: attribute.value,
|
|
562
|
-
})),
|
|
563
|
-
children: syntax.children.map((child) => {
|
|
564
|
-
if (child.kind === "WebJsxElementSyntax")
|
|
565
|
-
return shiftJsxSyntax(child, offset);
|
|
566
|
-
if (child.kind === "WebJsxExpressionSyntax") {
|
|
567
|
-
return {
|
|
568
|
-
...child,
|
|
569
|
-
span: shiftSourceSpan(child.span, offset),
|
|
570
|
-
expression: shiftExpressionSource(child.expression, offset),
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
return { ...child, span: shiftSourceSpan(child.span, offset) };
|
|
574
|
-
}),
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
function shiftLookSyntax(syntax, offset) {
|
|
578
|
-
if (offset === 0)
|
|
579
|
-
return syntax;
|
|
580
|
-
return {
|
|
581
|
-
...syntax,
|
|
582
|
-
span: shiftSourceSpan(syntax.span, offset),
|
|
583
|
-
lines: syntax.lines.map((line) => ({
|
|
584
|
-
...line,
|
|
585
|
-
start: line.start + offset,
|
|
586
|
-
end: line.end + offset,
|
|
587
|
-
})),
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
function shiftKeyframesSyntax(syntax, offset) {
|
|
591
|
-
if (offset === 0)
|
|
592
|
-
return syntax;
|
|
593
|
-
return {
|
|
594
|
-
...syntax,
|
|
595
|
-
span: shiftSourceSpan(syntax.span, offset),
|
|
596
|
-
lines: syntax.lines.map((line) => ({ ...line, start: line.start + offset, end: line.end + offset })),
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
function jsxExpression(syntax, parseExpression, report) {
|
|
600
|
-
return {
|
|
601
|
-
kind: "ExtensionExpression:web:jsx",
|
|
602
|
-
tag: syntax.tag,
|
|
603
|
-
tagSpan: syntax.tagSpan,
|
|
604
|
-
attributes: syntax.attributes.map((attribute) => ({
|
|
605
|
-
name: attribute.name,
|
|
606
|
-
value: typeof attribute.value === "object" && attribute.value !== null
|
|
607
|
-
? parseExpression(attribute.value)
|
|
608
|
-
: attribute.value,
|
|
609
|
-
span: attribute.span,
|
|
610
|
-
})),
|
|
611
|
-
children: syntax.children.map((child) => {
|
|
612
|
-
if (child.kind === "WebJsxElementSyntax")
|
|
613
|
-
return jsxExpression(child, parseExpression, report);
|
|
614
|
-
if (child.kind === "WebJsxExpressionSyntax") {
|
|
615
|
-
return { kind: "JSXExpressionChild", expression: parseExpression(child.expression), span: child.span };
|
|
616
|
-
}
|
|
617
|
-
// A bare (unbraced) 'for name in expr:' line written directly as JSX
|
|
618
|
-
// content receives the same .map() guidance as its braced spelling;
|
|
619
|
-
// there is no magic JSX control flow.
|
|
620
|
-
const bareFor = /(?:^|\n)[ \t]*for\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\s+([^:{<\n]+):/u.exec(child.value);
|
|
621
|
-
if (bareFor) {
|
|
622
|
-
const offset = child.span.start + (bareFor.index + bareFor[0].indexOf("for"));
|
|
623
|
-
report(recoveredDiagnostic("VEL5049", `Use '{${bareFor[2].trim()}.map((${bareFor[1]}) => ...)}'; JSX has no 'for' blocks, so lists render with '.map(...)'`, span(offset, offset + (bareFor[0].length - bareFor[0].indexOf("for")))));
|
|
624
|
-
}
|
|
625
|
-
return { kind: "JSXText", value: child.value, span: child.span };
|
|
626
|
-
}),
|
|
627
|
-
span: syntax.span,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
class LookSourceParser {
|
|
631
|
-
lines;
|
|
632
|
-
blockSpan;
|
|
633
|
-
parseExpression;
|
|
634
|
-
report;
|
|
635
|
-
index = 0;
|
|
636
|
-
constructor(block, parseExpression, report) {
|
|
637
|
-
this.blockSpan = block.span;
|
|
638
|
-
this.parseExpression = parseExpression;
|
|
639
|
-
this.report = report;
|
|
640
|
-
this.lines = block.lines;
|
|
641
|
-
}
|
|
642
|
-
parse() {
|
|
643
|
-
if (this.lines.length === 0) {
|
|
644
|
-
this.report(diagnostic("VEL5038", "A Look block requires at least one entry", this.blockSpan));
|
|
645
|
-
return [];
|
|
646
|
-
}
|
|
647
|
-
return this.parseEntries(this.lines[0].indent);
|
|
179
|
+
return parseLookExpression(this.host, token);
|
|
648
180
|
}
|
|
649
|
-
parseEntries(indent) {
|
|
650
|
-
const entries = [];
|
|
651
|
-
while (this.index < this.lines.length) {
|
|
652
|
-
const line = this.lines[this.index];
|
|
653
|
-
if (line.indent < indent)
|
|
654
|
-
break;
|
|
655
|
-
if (line.indent > indent) {
|
|
656
|
-
this.report(diagnostic("VEL5038", "Unexpected Look indentation", this.lineSpan(line)));
|
|
657
|
-
this.index += 1;
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
this.index += 1;
|
|
661
|
-
if (line.text.startsWith("if ") && line.text.endsWith(":")) {
|
|
662
|
-
entries.push(this.parseIf(line, indent, "if "));
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
if (line.text === "else:" || line.text.startsWith("else if ")) {
|
|
666
|
-
this.report(diagnostic("VEL5038", "Look 'else' must immediately follow an 'if' at the same indentation", this.lineSpan(line)));
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
// A kebab-case property receives camelCase guidance and recovers as the
|
|
670
|
-
// camelCase entry, so semantic analysis still checks its value and every
|
|
671
|
-
// other Look and JSX diagnostic co-reports in the same compile.
|
|
672
|
-
const kebab = /^([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)+)\s*=(.*)$/u.exec(line.text);
|
|
673
|
-
const property = kebab
|
|
674
|
-
? null
|
|
675
|
-
: /^([A-Za-z][A-Za-z0-9]*)\s*=\s*([\s\S]+)$/u.exec(line.text);
|
|
676
|
-
if (kebab && kebab[2].trim().length === 0) {
|
|
677
|
-
const camel = kebab[1].replace(/-+([A-Za-z])/gu, (_, letter) => letter.toUpperCase());
|
|
678
|
-
this.report(diagnostic("VEL5038", `Use '${camel}'; Look properties use the DOM camelCase spelling`, this.lineSpan(line)));
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (kebab || property) {
|
|
682
|
-
const propertyName = kebab
|
|
683
|
-
? kebab[1].replace(/-+([A-Za-z])/gu, (_, letter) => letter.toUpperCase())
|
|
684
|
-
: property[1];
|
|
685
|
-
if (kebab) {
|
|
686
|
-
this.report(recoveredDiagnostic("VEL5038", `Use '${propertyName}'; Look properties use the DOM camelCase spelling`, this.lineSpan(line)));
|
|
687
|
-
}
|
|
688
|
-
const assignment = line.text.indexOf("=");
|
|
689
|
-
const afterAssignment = line.text.slice(assignment + 1);
|
|
690
|
-
const valueText = afterAssignment.trim();
|
|
691
|
-
const valueStart = line.start + assignment + 1 + (afterAssignment.length - afterAssignment.trimStart().length);
|
|
692
|
-
if (/^(?:margin|padding|inset)/u.test(propertyName) && /^[+-]?\d[\w.%]*(?:\s+[+-]?\d[\w.%]*)+$/u.test(valueText)) {
|
|
693
|
-
const builderArguments = valueText
|
|
694
|
-
.split(/\s+/u)
|
|
695
|
-
.map((token) => (/^[+-]?\d+(?:\.\d+)?$/u.test(token) ? `${token}px` : token))
|
|
696
|
-
.join(", ");
|
|
697
|
-
this.report(recoveredDiagnostic("VEL5038", `Use 'spacing(${builderArguments})'; Look multi-value shorthand is written with the spacing builder`, this.lineSpan(line)));
|
|
698
|
-
entries.push({
|
|
699
|
-
kind: "LookProperty",
|
|
700
|
-
name: propertyName,
|
|
701
|
-
value: { kind: "LiteralExpression", value: null, raw: "null", span: span(valueStart, valueStart + valueText.length) },
|
|
702
|
-
span: this.lineSpan(line),
|
|
703
|
-
});
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
entries.push({
|
|
707
|
-
kind: "LookProperty",
|
|
708
|
-
name: propertyName,
|
|
709
|
-
value: this.parseExpression(valueText, valueStart, /[\r\n]/u.test(valueText) ? line.openingIndent : undefined),
|
|
710
|
-
span: this.lineSpan(line),
|
|
711
|
-
});
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
if (line.text.startsWith("...")) {
|
|
715
|
-
const afterSpread = line.text.slice(3);
|
|
716
|
-
const valueText = afterSpread.trim();
|
|
717
|
-
if (!valueText) {
|
|
718
|
-
this.report(diagnostic("VEL5038", "Look composition requires a value after '...'", this.lineSpan(line)));
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
entries.push({
|
|
722
|
-
kind: "LookSpread",
|
|
723
|
-
value: this.parseExpression(valueText, line.start + 3 + (afterSpread.length - afterSpread.trimStart().length)),
|
|
724
|
-
span: this.lineSpan(line),
|
|
725
|
-
});
|
|
726
|
-
continue;
|
|
727
|
-
}
|
|
728
|
-
const target = /^@([A-Za-z][A-Za-z0-9]*):$/u.exec(line.text)?.[1];
|
|
729
|
-
if (!target) {
|
|
730
|
-
this.report(diagnostic("VEL5038", "Look entries use 'property = value', 'if condition:', '@target:', or composition with '...'", this.lineSpan(line)));
|
|
731
|
-
continue;
|
|
732
|
-
}
|
|
733
|
-
const next = this.lines[this.index];
|
|
734
|
-
if (!next || next.indent <= line.indent) {
|
|
735
|
-
this.report(diagnostic("VEL5038", `Look target '@${target}' requires an indented body`, this.lineSpan(line)));
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
const children = this.parseEntries(next.indent);
|
|
739
|
-
entries.push({ kind: "LookTarget", name: target, entries: children, span: span(line.start, children.at(-1)?.span.end ?? line.end) });
|
|
740
|
-
}
|
|
741
|
-
return entries;
|
|
742
|
-
}
|
|
743
|
-
parseIf(line, indent, prefix) {
|
|
744
|
-
const conditionSource = line.text.slice(prefix.length, -1);
|
|
745
|
-
const conditionText = conditionSource.trim();
|
|
746
|
-
const conditionOffset = line.start + prefix.length + (conditionSource.length - conditionSource.trimStart().length);
|
|
747
|
-
const condition = this.parseLookCondition(conditionText, conditionOffset);
|
|
748
|
-
const next = this.lines[this.index];
|
|
749
|
-
let thenEntries = [];
|
|
750
|
-
if (!next || next.indent <= line.indent) {
|
|
751
|
-
this.report(diagnostic("VEL5038", "A Look if branch requires an indented body", this.lineSpan(line)));
|
|
752
|
-
}
|
|
753
|
-
else {
|
|
754
|
-
thenEntries = this.parseEntries(next.indent);
|
|
755
|
-
}
|
|
756
|
-
let elseEntries = [];
|
|
757
|
-
const alternate = this.lines[this.index];
|
|
758
|
-
if (alternate?.indent === indent && alternate.text.startsWith("else if ") && alternate.text.endsWith(":")) {
|
|
759
|
-
this.index += 1;
|
|
760
|
-
elseEntries = [this.parseIf(alternate, indent, "else if ")];
|
|
761
|
-
}
|
|
762
|
-
else if (alternate?.indent === indent && alternate.text === "else:") {
|
|
763
|
-
this.index += 1;
|
|
764
|
-
const elseBody = this.lines[this.index];
|
|
765
|
-
if (!elseBody || elseBody.indent <= alternate.indent) {
|
|
766
|
-
this.report(diagnostic("VEL5038", "A Look else branch requires an indented body", this.lineSpan(alternate)));
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
elseEntries = this.parseEntries(elseBody.indent);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
return {
|
|
773
|
-
kind: "LookIf",
|
|
774
|
-
condition,
|
|
775
|
-
thenEntries,
|
|
776
|
-
elseEntries,
|
|
777
|
-
span: span(line.start, elseEntries.at(-1)?.span.end ?? thenEntries.at(-1)?.span.end ?? line.end),
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
parseLookCondition(text, absoluteOffset) {
|
|
781
|
-
const hooks = new Map();
|
|
782
|
-
let rewritten = "";
|
|
783
|
-
// Every rewrite here is length-preserving, because `hooks` is keyed by an
|
|
784
|
-
// offset into `text` and `replaceLookHooks` looks those keys up against
|
|
785
|
-
// spans parsed out of `rewritten`. `@name` becomes `_name` and a string
|
|
786
|
-
// literal is copied verbatim, so the two offsets stay equal.
|
|
787
|
-
for (let index = 0; index < text.length;) {
|
|
788
|
-
// A string literal is Core's to define — prefixes, escapes, raw content
|
|
789
|
-
// and every delimiter — so the `@` rewrite steps over whatever Core
|
|
790
|
-
// scans rather than keeping a second spelling of string lexing here.
|
|
791
|
-
const literal = scanStringLiteral(text, index);
|
|
792
|
-
if (literal) {
|
|
793
|
-
const end = Math.max(literal.end, index + 1);
|
|
794
|
-
rewritten += text.slice(index, end);
|
|
795
|
-
index = end;
|
|
796
|
-
continue;
|
|
797
|
-
}
|
|
798
|
-
const match = /^@([A-Za-z][A-Za-z0-9]*)/u.exec(text.slice(index));
|
|
799
|
-
if (match) {
|
|
800
|
-
hooks.set(absoluteOffset + index, match[1]);
|
|
801
|
-
rewritten += `_${match[1]}`;
|
|
802
|
-
index += match[0].length;
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
rewritten += text[index];
|
|
806
|
-
index += 1;
|
|
807
|
-
}
|
|
808
|
-
const parsed = this.parseExpression(rewritten, absoluteOffset);
|
|
809
|
-
return replaceLookHooks(parsed, hooks);
|
|
810
|
-
}
|
|
811
|
-
lineSpan(line) {
|
|
812
|
-
return span(line.start, line.end);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
class KeyframesSourceParser {
|
|
816
|
-
lines;
|
|
817
|
-
blockSpan;
|
|
818
|
-
parseExpression;
|
|
819
|
-
report;
|
|
820
|
-
seenOffsets = new Set();
|
|
821
|
-
index = 0;
|
|
822
|
-
previousGroupStart = -1;
|
|
823
|
-
constructor(block, parseExpression, report) {
|
|
824
|
-
this.lines = block.lines;
|
|
825
|
-
this.blockSpan = block.span;
|
|
826
|
-
this.parseExpression = parseExpression;
|
|
827
|
-
this.report = report;
|
|
828
|
-
}
|
|
829
|
-
parse() {
|
|
830
|
-
if (this.lines.length === 0) {
|
|
831
|
-
this.report(diagnostic("VEL5060", "A keyframes block requires at least one stop", this.blockSpan));
|
|
832
|
-
return [];
|
|
833
|
-
}
|
|
834
|
-
const indent = this.lines[0].indent;
|
|
835
|
-
const stops = [];
|
|
836
|
-
while (this.index < this.lines.length) {
|
|
837
|
-
const line = this.lines[this.index];
|
|
838
|
-
if (line.indent !== indent) {
|
|
839
|
-
this.report(diagnostic("VEL5060", "Unexpected keyframes indentation; stops share one indentation level", this.lineSpan(line)));
|
|
840
|
-
this.index += 1;
|
|
841
|
-
continue;
|
|
842
|
-
}
|
|
843
|
-
this.index += 1;
|
|
844
|
-
const label = /^(.+):$/u.exec(line.text)?.[1]?.trim();
|
|
845
|
-
if (!label) {
|
|
846
|
-
this.report(diagnostic("VEL5060", "A keyframe stop uses 'from:', 'to:', or a percentage such as '50%:'", this.lineSpan(line)));
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
const offsets = this.parseOffsets(label, line);
|
|
850
|
-
const next = this.lines[this.index];
|
|
851
|
-
if (!next || next.indent <= line.indent) {
|
|
852
|
-
this.report(diagnostic("VEL5060", `Keyframe stop '${label}' requires an indented property body`, this.lineSpan(line)));
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
const entries = this.parseEntries(next.indent, line.indent);
|
|
856
|
-
if (offsets.length > 0)
|
|
857
|
-
stops.push({
|
|
858
|
-
offsets,
|
|
859
|
-
entries,
|
|
860
|
-
span: span(line.start, entries.at(-1)?.span.end ?? line.end),
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
// WB-I6: no summary. Every path above pushes a stop or says why that line is not one, on the line rather than the block.
|
|
864
|
-
return stops;
|
|
865
|
-
}
|
|
866
|
-
parseOffsets(label, line) {
|
|
867
|
-
const parts = label.split(",").map((part) => part.trim());
|
|
868
|
-
const offsets = [];
|
|
869
|
-
for (const part of parts) {
|
|
870
|
-
let offset = part === "from" ? 0 : part === "to" ? 100 : null;
|
|
871
|
-
const percentage = /^(\d+(?:\.\d+)?)%$/u.exec(part);
|
|
872
|
-
if (percentage) {
|
|
873
|
-
offset = Number(percentage[1]);
|
|
874
|
-
if (offset === 0 || offset === 100) {
|
|
875
|
-
this.report(diagnostic("VEL5060", `Use '${offset === 0 ? "from" : "to"}:'; ${offset}% has one canonical keyframe spelling`, this.lineSpan(line)));
|
|
876
|
-
continue;
|
|
877
|
-
}
|
|
878
|
-
if (!(offset > 0 && offset < 100)) {
|
|
879
|
-
this.report(diagnostic("VEL5060", `Keyframe percentage '${part}' must be greater than 0% and less than 100%`, this.lineSpan(line)));
|
|
880
|
-
continue;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
if (offset === null) {
|
|
884
|
-
this.report(diagnostic("VEL5060", `Unknown keyframe stop '${part}'; use from, to, or a percentage between them`, this.lineSpan(line)));
|
|
885
|
-
continue;
|
|
886
|
-
}
|
|
887
|
-
if (this.seenOffsets.has(offset)) {
|
|
888
|
-
this.report(diagnostic("VEL5060", `Keyframe stop '${part}' duplicates ${offset === 0 ? "from" : offset === 100 ? "to" : `${offset}%`}`, this.lineSpan(line)));
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
this.seenOffsets.add(offset);
|
|
892
|
-
offsets.push(offset);
|
|
893
|
-
}
|
|
894
|
-
const groupStart = offsets.length > 0 ? Math.min(...offsets) : this.previousGroupStart;
|
|
895
|
-
if (groupStart < this.previousGroupStart) {
|
|
896
|
-
this.report(diagnostic("VEL5060", "Keyframe stops must be declared in ascending order", this.lineSpan(line)));
|
|
897
|
-
}
|
|
898
|
-
else
|
|
899
|
-
this.previousGroupStart = groupStart;
|
|
900
|
-
return offsets;
|
|
901
|
-
}
|
|
902
|
-
parseEntries(indent, stopIndent) {
|
|
903
|
-
const entries = [];
|
|
904
|
-
while (this.index < this.lines.length) {
|
|
905
|
-
const line = this.lines[this.index];
|
|
906
|
-
if (line.indent <= stopIndent)
|
|
907
|
-
break;
|
|
908
|
-
this.index += 1;
|
|
909
|
-
if (line.indent !== indent) {
|
|
910
|
-
this.report(diagnostic("VEL5060", "Keyframe stop bodies cannot contain nested targets, conditions, or blocks", this.lineSpan(line)));
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
if (line.text.startsWith("if ") || line.text.startsWith("@") || line.text.startsWith("...") || line.text === "look:") {
|
|
914
|
-
this.report(diagnostic("VEL5060", "Keyframe stops contain only direct Look properties; conditions, targets, composition, and spreads are not allowed", this.lineSpan(line)));
|
|
915
|
-
continue;
|
|
916
|
-
}
|
|
917
|
-
const kebab = /^([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)+)\s*=\s*([\s\S]+)$/u.exec(line.text);
|
|
918
|
-
const property = kebab ? null : /^([A-Za-z][A-Za-z0-9]*)\s*=\s*([\s\S]+)$/u.exec(line.text);
|
|
919
|
-
if (!kebab && !property) {
|
|
920
|
-
this.report(diagnostic("VEL5060", "A keyframe property is written as 'property = value'", this.lineSpan(line)));
|
|
921
|
-
continue;
|
|
922
|
-
}
|
|
923
|
-
const name = kebab
|
|
924
|
-
? kebab[1].replace(/-+([A-Za-z])/gu, (_, letter) => letter.toUpperCase())
|
|
925
|
-
: property[1];
|
|
926
|
-
if (kebab)
|
|
927
|
-
this.report(recoveredDiagnostic("VEL5038", `Use '${name}'; Look properties use the DOM camelCase spelling`, this.lineSpan(line)));
|
|
928
|
-
const assignment = line.text.indexOf("=");
|
|
929
|
-
const afterAssignment = line.text.slice(assignment + 1);
|
|
930
|
-
const valueText = afterAssignment.trim();
|
|
931
|
-
const valueStart = line.start + assignment + 1 + (afterAssignment.length - afterAssignment.trimStart().length);
|
|
932
|
-
entries.push({
|
|
933
|
-
kind: "LookProperty",
|
|
934
|
-
name,
|
|
935
|
-
value: this.parseExpression(valueText, valueStart, /[\r\n]/u.test(valueText) ? line.openingIndent : undefined),
|
|
936
|
-
span: this.lineSpan(line),
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
return entries;
|
|
940
|
-
}
|
|
941
|
-
lineSpan(line) {
|
|
942
|
-
return span(line.start, line.end);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
function replaceLookHooks(expression, hooks) {
|
|
946
|
-
if (expression.kind === "IdentifierExpression" && hooks.has(expression.span.start)) {
|
|
947
|
-
const hook = { kind: "ExtensionExpression:web:look-hook", name: hooks.get(expression.span.start), span: expression.span };
|
|
948
|
-
return hook;
|
|
949
|
-
}
|
|
950
|
-
const visit = (value) => {
|
|
951
|
-
if (Array.isArray(value))
|
|
952
|
-
return value.map(visit);
|
|
953
|
-
if (!value || typeof value !== "object")
|
|
954
|
-
return value;
|
|
955
|
-
const record = value;
|
|
956
|
-
if (record.kind === "IdentifierExpression" && typeof record.span === "object" && record.span) {
|
|
957
|
-
const sourceSpan = record.span;
|
|
958
|
-
const name = hooks.get(sourceSpan.start);
|
|
959
|
-
if (name)
|
|
960
|
-
return { kind: "ExtensionExpression:web:look-hook", name, span: sourceSpan };
|
|
961
|
-
}
|
|
962
|
-
return Object.fromEntries(Object.entries(record).map(([key, child]) => [key, key === "span" ? child : visit(child)]));
|
|
963
|
-
};
|
|
964
|
-
return visit(expression);
|
|
965
181
|
}
|
|
966
182
|
//# sourceMappingURL=parser.js.map
|