@virune/compiler 1.1.0-rc.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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/dist/src/analysis/control-flow.d.ts +9 -0
- package/dist/src/analysis/control-flow.d.ts.map +1 -0
- package/dist/src/analysis/control-flow.js +47 -0
- package/dist/src/analysis/control-flow.js.map +1 -0
- package/dist/src/ast/nodes.d.ts +495 -0
- package/dist/src/ast/nodes.d.ts.map +1 -0
- package/dist/src/ast/nodes.js +2 -0
- package/dist/src/ast/nodes.js.map +1 -0
- package/dist/src/binder/symbols.d.ts +36 -0
- package/dist/src/binder/symbols.d.ts.map +1 -0
- package/dist/src/binder/symbols.js +30 -0
- package/dist/src/binder/symbols.js.map +1 -0
- package/dist/src/checker/builtin-members.d.ts +4 -0
- package/dist/src/checker/builtin-members.d.ts.map +1 -0
- package/dist/src/checker/builtin-members.js +175 -0
- package/dist/src/checker/builtin-members.js.map +1 -0
- package/dist/src/checker/checker.d.ts +148 -0
- package/dist/src/checker/checker.d.ts.map +1 -0
- package/dist/src/checker/checker.js +2985 -0
- package/dist/src/checker/checker.js.map +1 -0
- package/dist/src/checker/effect-registry.d.ts +14 -0
- package/dist/src/checker/effect-registry.d.ts.map +1 -0
- package/dist/src/checker/effect-registry.js +11 -0
- package/dist/src/checker/effect-registry.js.map +1 -0
- package/dist/src/checker/type-operations.d.ts +27 -0
- package/dist/src/checker/type-operations.d.ts.map +1 -0
- package/dist/src/checker/type-operations.js +268 -0
- package/dist/src/checker/type-operations.js.map +1 -0
- package/dist/src/codegen/emitter.d.ts +83 -0
- package/dist/src/codegen/emitter.d.ts.map +1 -0
- package/dist/src/codegen/emitter.js +1029 -0
- package/dist/src/codegen/emitter.js.map +1 -0
- package/dist/src/codegen/helpers.d.ts +7 -0
- package/dist/src/codegen/helpers.d.ts.map +1 -0
- package/dist/src/codegen/helpers.js +48 -0
- package/dist/src/codegen/helpers.js.map +1 -0
- package/dist/src/codegen/runtime-imports.d.ts +6 -0
- package/dist/src/codegen/runtime-imports.d.ts.map +1 -0
- package/dist/src/codegen/runtime-imports.js +45 -0
- package/dist/src/codegen/runtime-imports.js.map +1 -0
- package/dist/src/codegen/writer.d.ts +14 -0
- package/dist/src/codegen/writer.d.ts.map +1 -0
- package/dist/src/codegen/writer.js +56 -0
- package/dist/src/codegen/writer.js.map +1 -0
- package/dist/src/compiler.d.ts +24 -0
- package/dist/src/compiler.d.ts.map +1 -0
- package/dist/src/compiler.js +97 -0
- package/dist/src/compiler.js.map +1 -0
- package/dist/src/diagnostics/codes.d.ts +18 -0
- package/dist/src/diagnostics/codes.d.ts.map +1 -0
- package/dist/src/diagnostics/codes.js +43 -0
- package/dist/src/diagnostics/codes.js.map +1 -0
- package/dist/src/diagnostics/diagnostic.d.ts +41 -0
- package/dist/src/diagnostics/diagnostic.d.ts.map +1 -0
- package/dist/src/diagnostics/diagnostic.js +50 -0
- package/dist/src/diagnostics/diagnostic.js.map +1 -0
- package/dist/src/diagnostics/render.d.ts +44 -0
- package/dist/src/diagnostics/render.d.ts.map +1 -0
- package/dist/src/diagnostics/render.js +44 -0
- package/dist/src/diagnostics/render.js.map +1 -0
- package/dist/src/experimental-api.d.ts +19 -0
- package/dist/src/experimental-api.d.ts.map +1 -0
- package/dist/src/experimental-api.js +17 -0
- package/dist/src/experimental-api.js.map +1 -0
- package/dist/src/hir/lower.d.ts +6 -0
- package/dist/src/hir/lower.d.ts.map +1 -0
- package/dist/src/hir/lower.js +124 -0
- package/dist/src/hir/lower.js.map +1 -0
- package/dist/src/hir/nodes.d.ts +12 -0
- package/dist/src/hir/nodes.d.ts.map +1 -0
- package/dist/src/hir/nodes.js +2 -0
- package/dist/src/hir/nodes.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interop/checked-api.d.ts +21 -0
- package/dist/src/interop/checked-api.d.ts.map +1 -0
- package/dist/src/interop/checked-api.js +46 -0
- package/dist/src/interop/checked-api.js.map +1 -0
- package/dist/src/interop/decision.d.ts +24 -0
- package/dist/src/interop/decision.d.ts.map +1 -0
- package/dist/src/interop/decision.js +96 -0
- package/dist/src/interop/decision.js.map +1 -0
- package/dist/src/interop/frontend-component-emission.d.ts +17 -0
- package/dist/src/interop/frontend-component-emission.d.ts.map +1 -0
- package/dist/src/interop/frontend-component-emission.js +271 -0
- package/dist/src/interop/frontend-component-emission.js.map +1 -0
- package/dist/src/interop/jsx-view-validation.d.ts +20 -0
- package/dist/src/interop/jsx-view-validation.d.ts.map +1 -0
- package/dist/src/interop/jsx-view-validation.js +847 -0
- package/dist/src/interop/jsx-view-validation.js.map +1 -0
- package/dist/src/interop/operation-api.d.ts +32 -0
- package/dist/src/interop/operation-api.d.ts.map +1 -0
- package/dist/src/interop/operation-api.js +61 -0
- package/dist/src/interop/operation-api.js.map +1 -0
- package/dist/src/interop/operation.d.ts +136 -0
- package/dist/src/interop/operation.d.ts.map +1 -0
- package/dist/src/interop/operation.js +575 -0
- package/dist/src/interop/operation.js.map +1 -0
- package/dist/src/interop/repetition-host-locator.d.ts +25 -0
- package/dist/src/interop/repetition-host-locator.d.ts.map +1 -0
- package/dist/src/interop/repetition-host-locator.js +61 -0
- package/dist/src/interop/repetition-host-locator.js.map +1 -0
- package/dist/src/interop/repetition-host-usage-validation.d.ts +11 -0
- package/dist/src/interop/repetition-host-usage-validation.d.ts.map +1 -0
- package/dist/src/interop/repetition-host-usage-validation.js +89 -0
- package/dist/src/interop/repetition-host-usage-validation.js.map +1 -0
- package/dist/src/interop/types.d.ts +365 -0
- package/dist/src/interop/types.d.ts.map +1 -0
- package/dist/src/interop/types.js +2 -0
- package/dist/src/interop/types.js.map +1 -0
- package/dist/src/mir/lower.d.ts +4 -0
- package/dist/src/mir/lower.d.ts.map +1 -0
- package/dist/src/mir/lower.js +131 -0
- package/dist/src/mir/lower.js.map +1 -0
- package/dist/src/mir/nodes.d.ts +36 -0
- package/dist/src/mir/nodes.d.ts.map +1 -0
- package/dist/src/mir/nodes.js +2 -0
- package/dist/src/mir/nodes.js.map +1 -0
- package/dist/src/project/incremental.d.ts +9 -0
- package/dist/src/project/incremental.d.ts.map +1 -0
- package/dist/src/project/incremental.js +15 -0
- package/dist/src/project/incremental.js.map +1 -0
- package/dist/src/project/project.d.ts +98 -0
- package/dist/src/project/project.d.ts.map +1 -0
- package/dist/src/project/project.js +884 -0
- package/dist/src/project/project.js.map +1 -0
- package/dist/src/public-api.d.ts +45 -0
- package/dist/src/public-api.d.ts.map +1 -0
- package/dist/src/public-api.js +37 -0
- package/dist/src/public-api.js.map +1 -0
- package/dist/src/reference/evaluator.d.ts +13 -0
- package/dist/src/reference/evaluator.d.ts.map +1 -0
- package/dist/src/reference/evaluator.js +304 -0
- package/dist/src/reference/evaluator.js.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts +65 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.js +226 -0
- package/dist/src/selfhost/bootstrap-artifact-normalizer.js.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts +23 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.js +122 -0
- package/dist/src/selfhost/bootstrap-artifact-snapshot.js.map +1 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.d.ts +36 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.js +52 -0
- package/dist/src/selfhost/bootstrap-compiler-selection.js.map +1 -0
- package/dist/src/selfhost/bootstrap-execution-probe.d.ts +42 -0
- package/dist/src/selfhost/bootstrap-execution-probe.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-execution-probe.js +115 -0
- package/dist/src/selfhost/bootstrap-execution-probe.js.map +1 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.d.ts +40 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.js +144 -0
- package/dist/src/selfhost/bootstrap-rollback-decision.js.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-history.d.ts +49 -0
- package/dist/src/selfhost/bootstrap-shadow-history.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-history.js +348 -0
- package/dist/src/selfhost/bootstrap-shadow-history.js.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-report.d.ts +43 -0
- package/dist/src/selfhost/bootstrap-shadow-report.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-shadow-report.js +94 -0
- package/dist/src/selfhost/bootstrap-shadow-report.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-executor.d.ts +53 -0
- package/dist/src/selfhost/bootstrap-stage-executor.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-executor.js +159 -0
- package/dist/src/selfhost/bootstrap-stage-executor.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-loader.d.ts +14 -0
- package/dist/src/selfhost/bootstrap-stage-loader.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-loader.js +112 -0
- package/dist/src/selfhost/bootstrap-stage-loader.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts +8 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.js +85 -0
- package/dist/src/selfhost/bootstrap-stage-pipeline.js.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-runner.d.ts +47 -0
- package/dist/src/selfhost/bootstrap-stage-runner.d.ts.map +1 -0
- package/dist/src/selfhost/bootstrap-stage-runner.js +106 -0
- package/dist/src/selfhost/bootstrap-stage-runner.js.map +1 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.d.ts +83 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.d.ts.map +1 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.js +233 -0
- package/dist/src/selfhost/clean-bootstrap-evidence.js.map +1 -0
- package/dist/src/selfhost/compiler-facade.d.ts +33 -0
- package/dist/src/selfhost/compiler-facade.d.ts.map +1 -0
- package/dist/src/selfhost/compiler-facade.js +65 -0
- package/dist/src/selfhost/compiler-facade.js.map +1 -0
- package/dist/src/selfhost/contract.d.ts +151 -0
- package/dist/src/selfhost/contract.d.ts.map +1 -0
- package/dist/src/selfhost/contract.js +335 -0
- package/dist/src/selfhost/contract.js.map +1 -0
- package/dist/src/selfhost/differential-artifacts.d.ts +8 -0
- package/dist/src/selfhost/differential-artifacts.d.ts.map +1 -0
- package/dist/src/selfhost/differential-artifacts.js +50 -0
- package/dist/src/selfhost/differential-artifacts.js.map +1 -0
- package/dist/src/selfhost/differential-harness.d.ts +91 -0
- package/dist/src/selfhost/differential-harness.d.ts.map +1 -0
- package/dist/src/selfhost/differential-harness.js +246 -0
- package/dist/src/selfhost/differential-harness.js.map +1 -0
- package/dist/src/selfhost/full-language-inventory-runner.d.ts +49 -0
- package/dist/src/selfhost/full-language-inventory-runner.d.ts.map +1 -0
- package/dist/src/selfhost/full-language-inventory-runner.js +225 -0
- package/dist/src/selfhost/full-language-inventory-runner.js.map +1 -0
- package/dist/src/selfhost/full-language-inventory.d.ts +42 -0
- package/dist/src/selfhost/full-language-inventory.d.ts.map +1 -0
- package/dist/src/selfhost/full-language-inventory.js +163 -0
- package/dist/src/selfhost/full-language-inventory.js.map +1 -0
- package/dist/src/selfhost/interop-resolution-witness.d.ts +35 -0
- package/dist/src/selfhost/interop-resolution-witness.d.ts.map +1 -0
- package/dist/src/selfhost/interop-resolution-witness.js +186 -0
- package/dist/src/selfhost/interop-resolution-witness.js.map +1 -0
- package/dist/src/selfhost/kernel-model-host-adapter.d.ts +28 -0
- package/dist/src/selfhost/kernel-model-host-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/kernel-model-host-adapter.js +36 -0
- package/dist/src/selfhost/kernel-model-host-adapter.js.map +1 -0
- package/dist/src/selfhost/legacy-adapter.d.ts +8 -0
- package/dist/src/selfhost/legacy-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/legacy-adapter.js +186 -0
- package/dist/src/selfhost/legacy-adapter.js.map +1 -0
- package/dist/src/selfhost/module-graph.d.ts +57 -0
- package/dist/src/selfhost/module-graph.d.ts.map +1 -0
- package/dist/src/selfhost/module-graph.js +267 -0
- package/dist/src/selfhost/module-graph.js.map +1 -0
- package/dist/src/selfhost/mvp-adapter.d.ts +19 -0
- package/dist/src/selfhost/mvp-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/mvp-adapter.js +186 -0
- package/dist/src/selfhost/mvp-adapter.js.map +1 -0
- package/dist/src/selfhost/node-executor.d.ts +6 -0
- package/dist/src/selfhost/node-executor.d.ts.map +1 -0
- package/dist/src/selfhost/node-executor.js +138 -0
- package/dist/src/selfhost/node-executor.js.map +1 -0
- package/dist/src/selfhost/project-compiler-adapter.d.ts +77 -0
- package/dist/src/selfhost/project-compiler-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/project-compiler-adapter.js +358 -0
- package/dist/src/selfhost/project-compiler-adapter.js.map +1 -0
- package/dist/src/selfhost/project-differential-adapter.d.ts +15 -0
- package/dist/src/selfhost/project-differential-adapter.d.ts.map +1 -0
- package/dist/src/selfhost/project-differential-adapter.js +65 -0
- package/dist/src/selfhost/project-differential-adapter.js.map +1 -0
- package/dist/src/selfhost/promotion-evidence.d.ts +62 -0
- package/dist/src/selfhost/promotion-evidence.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-evidence.js +332 -0
- package/dist/src/selfhost/promotion-evidence.js.map +1 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.d.ts +54 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.js +343 -0
- package/dist/src/selfhost/promotion-policy-replay-v2.js.map +1 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.d.ts +55 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.js +227 -0
- package/dist/src/selfhost/promotion-shadow-history-v2.js.map +1 -0
- package/dist/src/selfhost/promotion-subject.d.ts +34 -0
- package/dist/src/selfhost/promotion-subject.d.ts.map +1 -0
- package/dist/src/selfhost/promotion-subject.js +141 -0
- package/dist/src/selfhost/promotion-subject.js.map +1 -0
- package/dist/src/selfhost/source-manifest.d.ts +31 -0
- package/dist/src/selfhost/source-manifest.d.ts.map +1 -0
- package/dist/src/selfhost/source-manifest.js +194 -0
- package/dist/src/selfhost/source-manifest.js.map +1 -0
- package/dist/src/selfhost/stage-compiler-facade.d.ts +25 -0
- package/dist/src/selfhost/stage-compiler-facade.d.ts.map +1 -0
- package/dist/src/selfhost/stage-compiler-facade.js +100 -0
- package/dist/src/selfhost/stage-compiler-facade.js.map +1 -0
- package/dist/src/semantic-evidence/snapshot.d.ts +114 -0
- package/dist/src/semantic-evidence/snapshot.d.ts.map +1 -0
- package/dist/src/semantic-evidence/snapshot.js +336 -0
- package/dist/src/semantic-evidence/snapshot.js.map +1 -0
- package/dist/src/source.d.ts +25 -0
- package/dist/src/source.d.ts.map +1 -0
- package/dist/src/source.js +10 -0
- package/dist/src/source.js.map +1 -0
- package/dist/src/syntax/cst-to-ast.d.ts +108 -0
- package/dist/src/syntax/cst-to-ast.d.ts.map +1 -0
- package/dist/src/syntax/cst-to-ast.js +383 -0
- package/dist/src/syntax/cst-to-ast.js.map +1 -0
- package/dist/src/syntax/documentation.d.ts +11 -0
- package/dist/src/syntax/documentation.d.ts.map +1 -0
- package/dist/src/syntax/documentation.js +261 -0
- package/dist/src/syntax/documentation.js.map +1 -0
- package/dist/src/syntax/parser.d.ts +114 -0
- package/dist/src/syntax/parser.d.ts.map +1 -0
- package/dist/src/syntax/parser.js +970 -0
- package/dist/src/syntax/parser.js.map +1 -0
- package/dist/src/syntax/syntax-metadata.d.ts +8 -0
- package/dist/src/syntax/syntax-metadata.d.ts.map +1 -0
- package/dist/src/syntax/syntax-metadata.js +14 -0
- package/dist/src/syntax/syntax-metadata.js.map +1 -0
- package/dist/src/syntax/tokens.d.ts +96 -0
- package/dist/src/syntax/tokens.d.ts.map +1 -0
- package/dist/src/syntax/tokens.js +260 -0
- package/dist/src/syntax/tokens.js.map +1 -0
- package/dist/src/types/types.d.ts +97 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/dist/src/types/types.js +105 -0
- package/dist/src/types/types.js.map +1 -0
- package/package.json +47 -0
- package/schema/diagnostics-v1.schema.json +75 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
import { TypeOperations } from '../checker/type-operations.js';
|
|
2
|
+
import { frontendHostPrimitiveName, frontendScalarRecordFields } from './frontend-component-emission.js';
|
|
3
|
+
const jsxAttributeName = /^[A-Za-z_$][A-Za-z0-9_$-]*(?::[A-Za-z_$][A-Za-z0-9_$-]*)?$/u;
|
|
4
|
+
const stringInterpolationPlaceholder = /(?<!\{)\{[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\}(?!\})/u;
|
|
5
|
+
const nativeChildrenProperty = '$viruneChildren';
|
|
6
|
+
/**
|
|
7
|
+
* Validate checked View structure against the project's real TypeScript JSX
|
|
8
|
+
* environment. This pass produces proof-only TSX and does not define emission
|
|
9
|
+
* or native component transport semantics.
|
|
10
|
+
*/
|
|
11
|
+
export function validateFrontendJsxUsage(module, semantic, options) {
|
|
12
|
+
const components = module.declarations.filter((declaration) => declaration.kind === 'ComponentDeclaration');
|
|
13
|
+
if (components.length === 0 || semantic.diagnostics.hasErrors)
|
|
14
|
+
return;
|
|
15
|
+
if (options.jsInteropProvider === undefined) {
|
|
16
|
+
for (const component of components)
|
|
17
|
+
semantic.diagnostics.error('L4308', `Cannot validate JSX usage for component ${component.name}: the JavaScript interop provider is unavailable`, component.span);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const provider = options.jsInteropProvider;
|
|
21
|
+
const resolver = provider.resolveJsxUsage;
|
|
22
|
+
if (resolver === undefined) {
|
|
23
|
+
for (const component of components)
|
|
24
|
+
semantic.diagnostics.error('L4308', `Cannot validate JSX usage for component ${component.name}: the JavaScript interop provider does not support JSX whole-usage validation`, component.span);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const imports = module.imports.filter(item => item.sourceKind === 'javascript').map(renderJavaScriptImport);
|
|
28
|
+
const repetitionHostImport = options.repetitionHost === undefined
|
|
29
|
+
? undefined
|
|
30
|
+
: `import * as $viruneRepetitionHostProof from ${JSON.stringify(options.repetitionHost.moduleSpecifier)};`;
|
|
31
|
+
const repetitionHostExpression = options.repetitionHost === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: `$viruneRepetitionHostProof[${JSON.stringify(options.repetitionHost.exportName)}]`;
|
|
34
|
+
const externalTagRoots = javaScriptValueImportNames(module);
|
|
35
|
+
const types = new TypeOperations({ arena: semantic.arena, diagnostics: semantic.diagnostics });
|
|
36
|
+
for (const component of components) {
|
|
37
|
+
const views = [];
|
|
38
|
+
collectViewReturns(component.body, views);
|
|
39
|
+
const context = {
|
|
40
|
+
semantic,
|
|
41
|
+
types,
|
|
42
|
+
externalTagRoots,
|
|
43
|
+
usedNativeProofs: new Set(),
|
|
44
|
+
repetitionValues: new Map(),
|
|
45
|
+
callbackValues: new Map(),
|
|
46
|
+
...(repetitionHostExpression === undefined ? {} : { repetitionHostExpression }),
|
|
47
|
+
};
|
|
48
|
+
const renderedViews = [];
|
|
49
|
+
for (const view of views) {
|
|
50
|
+
const onlyChild = view.body.children.length === 1 ? view.body.children[0] : undefined;
|
|
51
|
+
const rendered = onlyChild?.kind === 'ViewElement' ? renderViewElement(onlyChild, context) : renderViewBlock(view.body, context);
|
|
52
|
+
if (rendered === undefined)
|
|
53
|
+
break;
|
|
54
|
+
renderedViews.push(rendered);
|
|
55
|
+
}
|
|
56
|
+
if (context.failure !== undefined) {
|
|
57
|
+
semantic.diagnostics.error('L4308', `Cannot validate JSX usage for component ${component.name}: ${context.failure.message}`, context.failure.span);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const sourceText = [
|
|
61
|
+
...imports,
|
|
62
|
+
...(repetitionHostImport === undefined ? [] : [repetitionHostImport]),
|
|
63
|
+
...context.usedNativeProofs,
|
|
64
|
+
...[...externalTagRoots].map(name => `void ${name};`),
|
|
65
|
+
...renderedViews.map(view => `${view};`),
|
|
66
|
+
].join('\n');
|
|
67
|
+
let resolution;
|
|
68
|
+
try {
|
|
69
|
+
resolution = resolver.call(provider, {
|
|
70
|
+
containingFile: options.containingFile,
|
|
71
|
+
platform: options.platform,
|
|
72
|
+
sourceText,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
resolution = undefined;
|
|
77
|
+
}
|
|
78
|
+
if (resolution?.accepted !== true)
|
|
79
|
+
semantic.diagnostics.error('L4308', `TypeScript JSX whole-usage validation rejected component ${component.name}`, component.span);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function renderFrontendHostPrimitiveName(name) {
|
|
83
|
+
switch (name) {
|
|
84
|
+
case 'Bool': return 'boolean';
|
|
85
|
+
case 'Int':
|
|
86
|
+
case 'Float': return 'number';
|
|
87
|
+
case 'BigInt': return 'bigint';
|
|
88
|
+
case 'String': return 'string';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function renderFrontendHostPrimitiveValue(name) {
|
|
92
|
+
switch (name) {
|
|
93
|
+
case 'Bool': return '(false as boolean)';
|
|
94
|
+
case 'Int':
|
|
95
|
+
case 'Float': return '(0 as number)';
|
|
96
|
+
case 'BigInt': return '(0n as bigint)';
|
|
97
|
+
case 'String': return '("" as string)';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function renderFrontendHostType(typeId, semantic, directTypeName) {
|
|
101
|
+
const primitive = frontendHostPrimitiveName(typeId, semantic);
|
|
102
|
+
if (primitive !== undefined)
|
|
103
|
+
return renderFrontendHostPrimitiveName(primitive);
|
|
104
|
+
const fields = frontendScalarRecordFields(typeId, semantic, directTypeName);
|
|
105
|
+
if (fields === undefined)
|
|
106
|
+
return undefined;
|
|
107
|
+
return `{ ${fields.map(field => `${JSON.stringify(field.name)}: ${renderFrontendHostPrimitiveName(field.primitive)};`).join(' ')} }`;
|
|
108
|
+
}
|
|
109
|
+
function renderFrontendHostValue(typeId, semantic) {
|
|
110
|
+
const primitive = frontendHostPrimitiveName(typeId, semantic);
|
|
111
|
+
if (primitive !== undefined)
|
|
112
|
+
return renderFrontendHostPrimitiveValue(primitive);
|
|
113
|
+
const fields = frontendScalarRecordFields(typeId, semantic);
|
|
114
|
+
if (fields === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
return `({ ${fields.map(field => `${JSON.stringify(field.name)}: ${renderFrontendHostPrimitiveValue(field.primitive)}`).join(', ')} })`;
|
|
117
|
+
}
|
|
118
|
+
function renderNativeComponentProof(component, semantic) {
|
|
119
|
+
if (/^[a-z]/u.test(component.name) || component.symbolId === undefined)
|
|
120
|
+
return undefined;
|
|
121
|
+
const componentSymbol = semantic.symbols.get(component.symbolId);
|
|
122
|
+
if (componentSymbol?.kind !== 'component')
|
|
123
|
+
return undefined;
|
|
124
|
+
const componentType = semantic.arena.get(componentSymbol.typeId);
|
|
125
|
+
if (componentType.kind !== 'function' || componentType.parameters.length !== component.parameters.length)
|
|
126
|
+
return undefined;
|
|
127
|
+
const properties = [];
|
|
128
|
+
for (let index = 0; index < component.parameters.length; index += 1) {
|
|
129
|
+
const parameter = component.parameters[index];
|
|
130
|
+
const typeId = componentType.parameters[index];
|
|
131
|
+
if (typeId === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
const rendered = renderFrontendHostType(typeId, semantic, parameter.type.name);
|
|
134
|
+
if (rendered === undefined)
|
|
135
|
+
return undefined;
|
|
136
|
+
properties.push(`${JSON.stringify(parameter.name)}: ${rendered};`);
|
|
137
|
+
}
|
|
138
|
+
properties.push(`${JSON.stringify(nativeChildrenProperty)}?: () => unknown;`);
|
|
139
|
+
return `declare function ${component.name}(props: { ${properties.join(' ')} }): never;`;
|
|
140
|
+
}
|
|
141
|
+
function renderJavaScriptImport(declaration) {
|
|
142
|
+
const source = JSON.stringify(declaration.source);
|
|
143
|
+
if (declaration.typeOnly)
|
|
144
|
+
return `import ${source};`;
|
|
145
|
+
if (declaration.defaultImport !== undefined)
|
|
146
|
+
return `import ${declaration.defaultImport} from ${source};`;
|
|
147
|
+
if (declaration.namespaceImport !== undefined)
|
|
148
|
+
return `import * as ${declaration.namespaceImport} from ${source};`;
|
|
149
|
+
if (declaration.items.length > 0)
|
|
150
|
+
return `import { ${renderImportItems(declaration.items)} } from ${source};`;
|
|
151
|
+
return `import ${source};`;
|
|
152
|
+
}
|
|
153
|
+
function renderImportItems(items) {
|
|
154
|
+
return items.map(item => item.imported === item.local ? item.imported : `${item.imported} as ${item.local}`).join(', ');
|
|
155
|
+
}
|
|
156
|
+
function javaScriptValueImportNames(module) {
|
|
157
|
+
const names = new Set();
|
|
158
|
+
for (const declaration of module.imports) {
|
|
159
|
+
if (declaration.sourceKind !== 'javascript' || declaration.typeOnly)
|
|
160
|
+
continue;
|
|
161
|
+
if (declaration.defaultImport !== undefined)
|
|
162
|
+
names.add(declaration.defaultImport);
|
|
163
|
+
if (declaration.namespaceImport !== undefined)
|
|
164
|
+
names.add(declaration.namespaceImport);
|
|
165
|
+
for (const item of declaration.items)
|
|
166
|
+
names.add(item.local);
|
|
167
|
+
}
|
|
168
|
+
return names;
|
|
169
|
+
}
|
|
170
|
+
function collectViewReturns(block, views) {
|
|
171
|
+
for (const statement of block.statements) {
|
|
172
|
+
switch (statement.kind) {
|
|
173
|
+
case 'ReturnStatement':
|
|
174
|
+
if (statement.value?.kind === 'ViewExpression')
|
|
175
|
+
views.push(statement.value);
|
|
176
|
+
break;
|
|
177
|
+
case 'IfStatement':
|
|
178
|
+
collectViewReturns(statement.thenBlock, views);
|
|
179
|
+
if (statement.elseBranch?.kind === 'BlockStatement')
|
|
180
|
+
collectViewReturns(statement.elseBranch, views);
|
|
181
|
+
else if (statement.elseBranch !== undefined)
|
|
182
|
+
collectIfViews(statement.elseBranch, views);
|
|
183
|
+
break;
|
|
184
|
+
case 'ForStatement':
|
|
185
|
+
case 'WhileStatement':
|
|
186
|
+
collectViewReturns(statement.body, views);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function collectIfViews(statement, views) {
|
|
192
|
+
collectViewReturns(statement.thenBlock, views);
|
|
193
|
+
if (statement.elseBranch?.kind === 'BlockStatement')
|
|
194
|
+
collectViewReturns(statement.elseBranch, views);
|
|
195
|
+
else if (statement.elseBranch !== undefined)
|
|
196
|
+
collectIfViews(statement.elseBranch, views);
|
|
197
|
+
}
|
|
198
|
+
function renderViewBlock(block, context) {
|
|
199
|
+
const children = [];
|
|
200
|
+
for (const child of block.children) {
|
|
201
|
+
const rendered = renderViewChild(child, context);
|
|
202
|
+
if (rendered === undefined)
|
|
203
|
+
return undefined;
|
|
204
|
+
children.push(rendered);
|
|
205
|
+
}
|
|
206
|
+
return `<>${children.join('')}</>`;
|
|
207
|
+
}
|
|
208
|
+
function renderViewBlockExpression(block, context) {
|
|
209
|
+
if (block.children.length !== 1)
|
|
210
|
+
return renderViewBlock(block, context);
|
|
211
|
+
const child = block.children[0];
|
|
212
|
+
switch (child.kind) {
|
|
213
|
+
case 'ViewTextChild': return renderStringValue(child.value);
|
|
214
|
+
case 'ViewExpressionChild': return renderViewValue(child.expression, context);
|
|
215
|
+
case 'ViewChildrenSlot': return '<></>';
|
|
216
|
+
case 'ViewElement': return renderViewElement(child, context);
|
|
217
|
+
case 'ViewConditional': return renderConditionalExpression(child, context);
|
|
218
|
+
case 'ViewRepetition': return renderViewRepetition(child, context);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function renderViewChild(child, context) {
|
|
222
|
+
switch (child.kind) {
|
|
223
|
+
case 'ViewTextChild': return `{${renderStringValue(child.value)}}`;
|
|
224
|
+
case 'ViewExpressionChild': {
|
|
225
|
+
const value = renderViewValue(child.expression, context);
|
|
226
|
+
return value === undefined ? undefined : `{${value}}`;
|
|
227
|
+
}
|
|
228
|
+
case 'ViewChildrenSlot': return '{<></>}';
|
|
229
|
+
case 'ViewElement': return renderViewElement(child, context);
|
|
230
|
+
case 'ViewConditional': return renderViewConditional(child, context);
|
|
231
|
+
case 'ViewRepetition': {
|
|
232
|
+
const value = renderViewRepetition(child, context);
|
|
233
|
+
return value === undefined ? undefined : `{${value}}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function renderViewRepetition(repetition, context) {
|
|
238
|
+
if (context.repetitionHostExpression !== undefined)
|
|
239
|
+
return renderHostViewRepetition(repetition, context);
|
|
240
|
+
const evidence = repetition.checkedEvidence;
|
|
241
|
+
if (evidence === undefined)
|
|
242
|
+
return fail(context, repetition.span, 'View repetition reached JSX validation without checked repetition evidence');
|
|
243
|
+
if ((repetition.indexName === undefined) !== (evidence.indexSymbolId === undefined))
|
|
244
|
+
return fail(context, repetition.span, 'View repetition checked evidence does not match its source index binding');
|
|
245
|
+
const source = evidence.sourceKind === 'external-array'
|
|
246
|
+
? renderExternalRepetitionSource(repetition.source, context)
|
|
247
|
+
: renderNativeRepetitionSource(repetition, evidence, context);
|
|
248
|
+
if (source === undefined)
|
|
249
|
+
return undefined;
|
|
250
|
+
const sourceName = `$viruneViewSource${repetition.id}`;
|
|
251
|
+
const lengthName = `$viruneViewLength${repetition.id}`;
|
|
252
|
+
const indexName = `$viruneViewIndex${repetition.id}`;
|
|
253
|
+
const itemName = `$viruneViewItem${repetition.id}`;
|
|
254
|
+
const childrenName = `$viruneViewChildren${repetition.id}`;
|
|
255
|
+
const previousItem = context.repetitionValues.get(evidence.itemSymbolId);
|
|
256
|
+
const hadItem = context.repetitionValues.has(evidence.itemSymbolId);
|
|
257
|
+
context.repetitionValues.set(evidence.itemSymbolId, itemName);
|
|
258
|
+
let previousIndex;
|
|
259
|
+
let hadIndex = false;
|
|
260
|
+
if (evidence.indexSymbolId !== undefined) {
|
|
261
|
+
previousIndex = context.repetitionValues.get(evidence.indexSymbolId);
|
|
262
|
+
hadIndex = context.repetitionValues.has(evidence.indexSymbolId);
|
|
263
|
+
context.repetitionValues.set(evidence.indexSymbolId, indexName);
|
|
264
|
+
}
|
|
265
|
+
const body = renderRepetitionBlock(repetition.body, childrenName, context, 2);
|
|
266
|
+
if (hadItem)
|
|
267
|
+
context.repetitionValues.set(evidence.itemSymbolId, previousItem);
|
|
268
|
+
else
|
|
269
|
+
context.repetitionValues.delete(evidence.itemSymbolId);
|
|
270
|
+
if (evidence.indexSymbolId !== undefined) {
|
|
271
|
+
if (hadIndex)
|
|
272
|
+
context.repetitionValues.set(evidence.indexSymbolId, previousIndex);
|
|
273
|
+
else
|
|
274
|
+
context.repetitionValues.delete(evidence.indexSymbolId);
|
|
275
|
+
}
|
|
276
|
+
if (body === undefined)
|
|
277
|
+
return undefined;
|
|
278
|
+
const holeGuard = evidence.sourceKind === 'external-array' ? `\n\t\tif (!Object.prototype.hasOwnProperty.call(${sourceName}, ${indexName})) continue;` : '';
|
|
279
|
+
const bodyText = body.length === 0 ? '' : `\n${body}`;
|
|
280
|
+
return `(() => {\n\tconst ${sourceName} = ${source};\n\tconst ${lengthName} = ${sourceName}.length;\n\tconst ${childrenName} = [];\n\tfor (let ${indexName} = 0; ${indexName} < ${lengthName}; ${indexName}++) {${holeGuard}\n\t\tconst ${itemName} = ${sourceName}[${indexName}] as (typeof ${sourceName})[number];${bodyText}\n\t}\n\treturn ${childrenName};\n})()`;
|
|
281
|
+
}
|
|
282
|
+
function renderHostViewRepetition(repetition, context) {
|
|
283
|
+
const evidence = repetition.checkedEvidence;
|
|
284
|
+
const host = context.repetitionHostExpression;
|
|
285
|
+
if (evidence === undefined || host === undefined)
|
|
286
|
+
return fail(context, repetition.span, 'Host View repetition reached JSX validation without checked Host evidence');
|
|
287
|
+
if ((repetition.indexName === undefined) !== (evidence.indexSymbolId === undefined))
|
|
288
|
+
return fail(context, repetition.span, 'View repetition checked evidence does not match its source index binding');
|
|
289
|
+
const source = evidence.sourceKind === 'external-array'
|
|
290
|
+
? renderExternalRepetitionSource(repetition.source, context)
|
|
291
|
+
: renderNativeRepetitionSource(repetition, evidence, context);
|
|
292
|
+
if (source === undefined)
|
|
293
|
+
return undefined;
|
|
294
|
+
const sourceName = `$viruneHostSource${repetition.id}`;
|
|
295
|
+
const lengthName = `$viruneHostLength${repetition.id}`;
|
|
296
|
+
const indexName = `$viruneHostIndex${repetition.id}`;
|
|
297
|
+
const itemName = `$viruneHostItem${repetition.id}`;
|
|
298
|
+
const entriesName = `$viruneHostEntries${repetition.id}`;
|
|
299
|
+
const readValueName = `$viruneHostReadValue${repetition.id}`;
|
|
300
|
+
const readIndexName = `$viruneHostReadIndex${repetition.id}`;
|
|
301
|
+
const groupIdName = `$viruneHostGroupId${repetition.id}`;
|
|
302
|
+
const previousItem = context.repetitionValues.get(evidence.itemSymbolId);
|
|
303
|
+
const hadItem = context.repetitionValues.has(evidence.itemSymbolId);
|
|
304
|
+
context.repetitionValues.set(evidence.itemSymbolId, `${readValueName}()`);
|
|
305
|
+
let previousIndex;
|
|
306
|
+
let hadIndex = false;
|
|
307
|
+
if (evidence.indexSymbolId !== undefined) {
|
|
308
|
+
previousIndex = context.repetitionValues.get(evidence.indexSymbolId);
|
|
309
|
+
hadIndex = context.repetitionValues.has(evidence.indexSymbolId);
|
|
310
|
+
context.repetitionValues.set(evidence.indexSymbolId, `${readIndexName}()`);
|
|
311
|
+
}
|
|
312
|
+
const body = renderViewBlockExpression(repetition.body, context);
|
|
313
|
+
if (hadItem)
|
|
314
|
+
context.repetitionValues.set(evidence.itemSymbolId, previousItem);
|
|
315
|
+
else
|
|
316
|
+
context.repetitionValues.delete(evidence.itemSymbolId);
|
|
317
|
+
if (evidence.indexSymbolId !== undefined) {
|
|
318
|
+
if (hadIndex)
|
|
319
|
+
context.repetitionValues.set(evidence.indexSymbolId, previousIndex);
|
|
320
|
+
else
|
|
321
|
+
context.repetitionValues.delete(evidence.indexSymbolId);
|
|
322
|
+
}
|
|
323
|
+
if (body === undefined)
|
|
324
|
+
return undefined;
|
|
325
|
+
const holeGuard = evidence.sourceKind === 'external-array'
|
|
326
|
+
? `\n\t\t\tif (!Object.prototype.hasOwnProperty.call(${sourceName}, ${indexName})) continue;`
|
|
327
|
+
: '';
|
|
328
|
+
return `${host}(
|
|
329
|
+
() => {
|
|
330
|
+
const ${sourceName} = ${source};
|
|
331
|
+
const ${lengthName} = ${sourceName}.length;
|
|
332
|
+
const ${entriesName} = [];
|
|
333
|
+
for (let ${indexName} = 0; ${indexName} < ${lengthName}; ${indexName}++) {${holeGuard}
|
|
334
|
+
const ${itemName} = ${sourceName}[${indexName}] as (typeof ${sourceName})[number];
|
|
335
|
+
${entriesName}.push({ id: ("" as string), index: ${indexName}, value: ${itemName} });
|
|
336
|
+
}
|
|
337
|
+
return ${entriesName};
|
|
338
|
+
},
|
|
339
|
+
(${readValueName}, ${readIndexName}, ${groupIdName}) => {
|
|
340
|
+
void ${groupIdName};
|
|
341
|
+
return ${body};
|
|
342
|
+
},
|
|
343
|
+
)`;
|
|
344
|
+
}
|
|
345
|
+
function renderExternalRepetitionSource(expression, context) {
|
|
346
|
+
if (expression.kind === 'IdentifierExpression' && expression.symbolId !== undefined) {
|
|
347
|
+
const symbol = context.semantic.symbols.get(expression.symbolId);
|
|
348
|
+
if (symbol?.kind === 'import' && !symbol.typeOnly)
|
|
349
|
+
return expression.name;
|
|
350
|
+
if (symbol?.kind === 'variable' && !symbol.mutable) {
|
|
351
|
+
const declaration = symbol.declaration;
|
|
352
|
+
if (declaration?.kind === 'LetStatement') {
|
|
353
|
+
const letStatement = declaration;
|
|
354
|
+
if (letStatement.annotation === undefined)
|
|
355
|
+
return renderExternalRepetitionSource(letStatement.value, context);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (expression.kind === 'FieldExpression') {
|
|
360
|
+
const target = renderExternalRepetitionSource(expression.target, context);
|
|
361
|
+
return target === undefined ? undefined : `${target}.${expression.field}`;
|
|
362
|
+
}
|
|
363
|
+
if (expression.kind === 'CallExpression' && expression.foreignCall === true && expression.typeArguments.length === 0 && expression.arguments.length === 0) {
|
|
364
|
+
const callee = renderExternalRepetitionSource(expression.callee, context);
|
|
365
|
+
return callee === undefined ? undefined : `${callee}()`;
|
|
366
|
+
}
|
|
367
|
+
return fail(context, expression.span, 'External Array repetition source cannot be represented in the current JSX validation slice without guessing its TypeScript type');
|
|
368
|
+
}
|
|
369
|
+
function renderNativeRepetitionSource(repetition, evidence, context) {
|
|
370
|
+
if (repetition.source.kind === 'ListExpression' && repetition.source.items.length > 0)
|
|
371
|
+
return renderNativeRepetitionList(repetition.source, context);
|
|
372
|
+
const symbol = context.semantic.symbols.get(evidence.itemSymbolId);
|
|
373
|
+
if (symbol === undefined)
|
|
374
|
+
return fail(context, repetition.span, 'native View repetition item binding is unavailable to JSX validation');
|
|
375
|
+
const probe = renderTypeProbe(symbol.typeId, context, repetition.span);
|
|
376
|
+
return probe === undefined ? undefined : `[${probe}]`;
|
|
377
|
+
}
|
|
378
|
+
function renderNativeRepetitionList(expression, context) {
|
|
379
|
+
const items = [];
|
|
380
|
+
for (const item of expression.items) {
|
|
381
|
+
const rendered = item.kind === 'ListExpression' ? renderNativeRepetitionList(item, context) : renderViewValue(item, context);
|
|
382
|
+
if (rendered === undefined)
|
|
383
|
+
return undefined;
|
|
384
|
+
items.push(rendered);
|
|
385
|
+
}
|
|
386
|
+
return `[${items.join(', ')}]`;
|
|
387
|
+
}
|
|
388
|
+
function renderTypeProbe(typeId, context, span) {
|
|
389
|
+
const type = context.semantic.arena.get(typeId);
|
|
390
|
+
if (type.kind === 'primitive') {
|
|
391
|
+
switch (type.name) {
|
|
392
|
+
case 'Bool': return '(false as boolean)';
|
|
393
|
+
case 'Int':
|
|
394
|
+
case 'Float': return '(0 as number)';
|
|
395
|
+
case 'BigInt': return '(0n as bigint)';
|
|
396
|
+
case 'String': return '("" as string)';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return fail(context, span, `native View repetition item type ${context.semantic.arena.display(typeId)} is not safely projectable in this validation slice`);
|
|
400
|
+
}
|
|
401
|
+
function renderRepetitionBlock(block, target, context, indent) {
|
|
402
|
+
const lines = [];
|
|
403
|
+
for (const child of block.children) {
|
|
404
|
+
const rendered = renderRepetitionChild(child, target, context, indent);
|
|
405
|
+
if (rendered === undefined)
|
|
406
|
+
return undefined;
|
|
407
|
+
if (rendered.length > 0)
|
|
408
|
+
lines.push(rendered);
|
|
409
|
+
}
|
|
410
|
+
return lines.join('\n');
|
|
411
|
+
}
|
|
412
|
+
function renderRepetitionChild(child, target, context, indent) {
|
|
413
|
+
const prefix = '\t'.repeat(indent);
|
|
414
|
+
switch (child.kind) {
|
|
415
|
+
case 'ViewTextChild': return `${prefix}${target}.push(${renderStringValue(child.value)});`;
|
|
416
|
+
case 'ViewExpressionChild': {
|
|
417
|
+
const value = renderViewValue(child.expression, context);
|
|
418
|
+
return value === undefined ? undefined : `${prefix}${target}.push(${value});`;
|
|
419
|
+
}
|
|
420
|
+
case 'ViewChildrenSlot': return fail(context, child.span, 'compiler-managed children slots cannot appear inside View repetition');
|
|
421
|
+
case 'ViewElement': {
|
|
422
|
+
const value = renderViewElement(child, context);
|
|
423
|
+
return value === undefined ? undefined : `${prefix}${target}.push(${value});`;
|
|
424
|
+
}
|
|
425
|
+
case 'ViewConditional': return renderRepetitionConditional(child, target, context, indent);
|
|
426
|
+
case 'ViewRepetition': {
|
|
427
|
+
const value = renderViewRepetition(child, context);
|
|
428
|
+
return value === undefined ? undefined : `${prefix}${target}.push(...${value});`;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function renderRepetitionConditional(conditional, target, context, indent) {
|
|
433
|
+
const condition = renderViewValue(conditional.condition, context);
|
|
434
|
+
if (condition === undefined)
|
|
435
|
+
return undefined;
|
|
436
|
+
const prefix = '\t'.repeat(indent);
|
|
437
|
+
const thenBranch = renderRepetitionBlock(conditional.thenBlock, target, context, indent + 1);
|
|
438
|
+
if (thenBranch === undefined)
|
|
439
|
+
return undefined;
|
|
440
|
+
const thenText = thenBranch.length === 0 ? '' : `\n${thenBranch}`;
|
|
441
|
+
let text = `${prefix}if (${condition}) {${thenText}\n${prefix}}`;
|
|
442
|
+
if (conditional.elseBranch === undefined)
|
|
443
|
+
return text;
|
|
444
|
+
if (conditional.elseBranch.kind === 'ViewBlock') {
|
|
445
|
+
const elseBranch = renderRepetitionBlock(conditional.elseBranch, target, context, indent + 1);
|
|
446
|
+
if (elseBranch === undefined)
|
|
447
|
+
return undefined;
|
|
448
|
+
const elseText = elseBranch.length === 0 ? '' : `\n${elseBranch}`;
|
|
449
|
+
return `${text} else {${elseText}\n${prefix}}`;
|
|
450
|
+
}
|
|
451
|
+
const nested = renderRepetitionConditional(conditional.elseBranch, target, context, indent);
|
|
452
|
+
return nested === undefined ? undefined : `${text} else ${nested.slice(prefix.length)}`;
|
|
453
|
+
}
|
|
454
|
+
function renderViewElement(element, context) {
|
|
455
|
+
const root = element.tag[0];
|
|
456
|
+
const symbol = context.semantic.globalScope.lookup(root);
|
|
457
|
+
let nativeComponent;
|
|
458
|
+
if (element.tag.length === 1 && symbol?.kind === 'component' && symbol.declaration?.kind === 'ComponentDeclaration')
|
|
459
|
+
nativeComponent = symbol.declaration;
|
|
460
|
+
const nativeProof = nativeComponent === undefined ? undefined : renderNativeComponentProof(nativeComponent, context.semantic);
|
|
461
|
+
const native = nativeProof !== undefined;
|
|
462
|
+
if (symbol?.kind === 'component' && !native) {
|
|
463
|
+
if (element.tag.length === 1 && /^[a-z]/u.test(root))
|
|
464
|
+
return fail(context, element.span, `lowercase Virune-native component tag ${root} would be interpreted as a JSX intrinsic tag`);
|
|
465
|
+
return fail(context, element.span, `Virune-native component tag ${element.tag.join('.')} requires a native component boundary that is not implemented in this validation slice`);
|
|
466
|
+
}
|
|
467
|
+
const intrinsic = element.tag.length === 1 && /^[a-z]/u.test(root);
|
|
468
|
+
const external = context.externalTagRoots.has(root);
|
|
469
|
+
if (intrinsic && external)
|
|
470
|
+
return fail(context, element.span, `lowercase JavaScript-imported View tag ${root} is ambiguous with JSX intrinsic syntax`);
|
|
471
|
+
if (!native && !intrinsic && !external) {
|
|
472
|
+
return fail(context, element.span, `View tag ${element.tag.join('.')} is neither intrinsic nor rooted in a JavaScript-imported External binding`);
|
|
473
|
+
}
|
|
474
|
+
if (nativeProof !== undefined && nativeComponent !== undefined) {
|
|
475
|
+
if (!validateNativeComponentProperties(element, nativeComponent, context))
|
|
476
|
+
return undefined;
|
|
477
|
+
context.usedNativeProofs.add(nativeProof);
|
|
478
|
+
}
|
|
479
|
+
const properties = [];
|
|
480
|
+
for (let propertyIndex = 0; propertyIndex < element.properties.length; propertyIndex += 1) {
|
|
481
|
+
const property = element.properties[propertyIndex];
|
|
482
|
+
if (!jsxAttributeName.test(property.name))
|
|
483
|
+
return fail(context, property.span, `property ${JSON.stringify(property.name)} cannot be represented as a preserved JSX attribute without speculative lowering`);
|
|
484
|
+
let value;
|
|
485
|
+
if (native)
|
|
486
|
+
value = renderNativeComponentPropertyValue(property.value, context);
|
|
487
|
+
else {
|
|
488
|
+
const projection = context.semantic.frontendCallableProjections.find(item => item.viewElementNodeId === element.id && item.propertyIndex === propertyIndex && item.property === property.name);
|
|
489
|
+
if (projection?.viewCallback !== undefined) {
|
|
490
|
+
if (property.value.kind !== 'LambdaExpression')
|
|
491
|
+
return fail(context, property.span, `View callback property ${property.name} is missing its checked lambda`);
|
|
492
|
+
value = renderFrontendViewCallbackValue(property.value, projection.viewCallback, context);
|
|
493
|
+
if (value === undefined)
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
else if (projection?.descriptor !== undefined) {
|
|
497
|
+
value = renderFrontendCallableValue(projection.descriptor);
|
|
498
|
+
if (value === undefined)
|
|
499
|
+
return fail(context, property.span, `native callable property ${property.name} has unsupported frontend projection evidence`);
|
|
500
|
+
}
|
|
501
|
+
else
|
|
502
|
+
value = external ? renderExternalViewPropertyValue(property.value, context) : renderViewValue(property.value, context);
|
|
503
|
+
}
|
|
504
|
+
if (value === undefined)
|
|
505
|
+
return undefined;
|
|
506
|
+
properties.push(`${property.name}={${value}}`);
|
|
507
|
+
}
|
|
508
|
+
const tag = element.tag.join('.');
|
|
509
|
+
if (native && element.children !== undefined) {
|
|
510
|
+
const children = renderViewBlockExpression(element.children, context);
|
|
511
|
+
if (children === undefined)
|
|
512
|
+
return undefined;
|
|
513
|
+
properties.push(`${nativeChildrenProperty}={() => ${children}}`);
|
|
514
|
+
}
|
|
515
|
+
const attributes = properties.length === 0 ? '' : ` ${properties.join(' ')}`;
|
|
516
|
+
if (element.children === undefined || native)
|
|
517
|
+
return `<${tag}${attributes} />`;
|
|
518
|
+
if (external && containsDirectChildrenSlot(element.children)) {
|
|
519
|
+
return fail(context, element.span, `compiler-managed children slot beneath JavaScript-imported External component ${tag} cannot preserve zero-or-more child contribution without changing the downstream children shape`);
|
|
520
|
+
}
|
|
521
|
+
if (external && containsDirectConditionalAbsence(element.children)) {
|
|
522
|
+
return fail(context, element.span, `View if without else beneath JavaScript-imported External component ${tag} cannot preserve zero-child absence without making the empty fragment observable as a child`);
|
|
523
|
+
}
|
|
524
|
+
const children = renderViewBlockContents(element.children, context);
|
|
525
|
+
return children === undefined ? undefined : `<${tag}${attributes}>${children}</${tag}>`;
|
|
526
|
+
}
|
|
527
|
+
function renderExternalViewPropertyValue(expression, context) {
|
|
528
|
+
const typeId = expression.inferredTypeId;
|
|
529
|
+
if (typeId === undefined)
|
|
530
|
+
return fail(context, expression.span, 'an External JSX property value has no checked type');
|
|
531
|
+
const type = context.semantic.arena.get(typeId);
|
|
532
|
+
if (type.kind !== 'foreign' || (type.snapshot.category === 'primitive' && type.snapshot.primitive === 'string'))
|
|
533
|
+
return renderViewValue(expression, context);
|
|
534
|
+
if (type.snapshot.category === 'unknown' || type.snapshot.category === 'any') {
|
|
535
|
+
return fail(context, expression.span, `View value type ${context.semantic.arena.display(typeId)} is not safely projectable in this validation slice`);
|
|
536
|
+
}
|
|
537
|
+
return renderCheckedExternalPropertyExpression(expression, context, `External JSX property value type ${context.semantic.arena.display(typeId)} requires a checked expression shape not representable in this validation slice`);
|
|
538
|
+
}
|
|
539
|
+
function renderCheckedExternalPropertyExpression(expression, context, failureMessage, depth = 0) {
|
|
540
|
+
if (depth > 12)
|
|
541
|
+
return fail(context, expression.span, failureMessage);
|
|
542
|
+
if (expression.kind === 'IdentifierExpression' && expression.symbolId !== undefined) {
|
|
543
|
+
const symbol = context.semantic.symbols.get(expression.symbolId);
|
|
544
|
+
if (symbol?.kind === 'import' && !symbol.typeOnly)
|
|
545
|
+
return expression.name;
|
|
546
|
+
if (symbol?.kind === 'variable' && !symbol.mutable) {
|
|
547
|
+
const declaration = symbol.declaration;
|
|
548
|
+
if (declaration?.kind === 'LetStatement') {
|
|
549
|
+
const letStatement = declaration;
|
|
550
|
+
if (letStatement.annotation === undefined)
|
|
551
|
+
return renderCheckedExternalPropertyExpression(letStatement.value, context, failureMessage, depth + 1);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (expression.kind === 'FieldExpression') {
|
|
556
|
+
const target = renderCheckedExternalPropertyExpression(expression.target, context, failureMessage, depth + 1);
|
|
557
|
+
return target === undefined ? undefined : `${target}.${expression.field}`;
|
|
558
|
+
}
|
|
559
|
+
if (expression.kind === 'IndexExpression' && expression.foreignIndex === true) {
|
|
560
|
+
const target = renderCheckedExternalPropertyExpression(expression.target, context, failureMessage, depth + 1);
|
|
561
|
+
if (target === undefined)
|
|
562
|
+
return undefined;
|
|
563
|
+
const index = renderCheckedExternalPropertyOperationArgument(expression.index, context, failureMessage, depth + 1);
|
|
564
|
+
return index === undefined ? undefined : `${target}[${index}]`;
|
|
565
|
+
}
|
|
566
|
+
if (expression.kind === 'CallExpression' && (expression.foreignCall === true || expression.foreignConstruct === true) && expression.typeArguments.length === 0) {
|
|
567
|
+
const callee = renderCheckedExternalPropertyExpression(expression.callee, context, failureMessage, depth + 1);
|
|
568
|
+
if (callee === undefined)
|
|
569
|
+
return undefined;
|
|
570
|
+
const argumentsList = [];
|
|
571
|
+
for (const argument of expression.arguments) {
|
|
572
|
+
const rendered = renderCheckedExternalPropertyOperationArgument(argument, context, failureMessage, depth + 1);
|
|
573
|
+
if (rendered === undefined)
|
|
574
|
+
return undefined;
|
|
575
|
+
argumentsList.push(rendered);
|
|
576
|
+
}
|
|
577
|
+
return expression.foreignConstruct === true
|
|
578
|
+
? `new (${callee})(${argumentsList.join(', ')})`
|
|
579
|
+
: `${callee}(${argumentsList.join(', ')})`;
|
|
580
|
+
}
|
|
581
|
+
return fail(context, expression.span, failureMessage);
|
|
582
|
+
}
|
|
583
|
+
function renderCheckedExternalPropertyOperationArgument(expression, context, failureMessage, depth) {
|
|
584
|
+
const typeId = expression.inferredTypeId;
|
|
585
|
+
if (typeId === undefined)
|
|
586
|
+
return fail(context, expression.span, failureMessage);
|
|
587
|
+
const type = context.semantic.arena.get(typeId);
|
|
588
|
+
if (type.kind === 'foreign') {
|
|
589
|
+
if (type.snapshot.category === 'unknown' || type.snapshot.category === 'any')
|
|
590
|
+
return fail(context, expression.span, failureMessage);
|
|
591
|
+
return renderCheckedExternalPropertyExpression(expression, context, failureMessage, depth);
|
|
592
|
+
}
|
|
593
|
+
if (expression.kind === 'LiteralExpression')
|
|
594
|
+
return renderLiteral(expression, context);
|
|
595
|
+
if (expression.kind === 'UnaryExpression' && expression.operator === '-' && expression.operand.kind === 'LiteralExpression' && ['Int', 'Float', 'BigInt'].includes(expression.operand.literalKind)) {
|
|
596
|
+
const literal = renderLiteral(expression.operand, context);
|
|
597
|
+
return literal === undefined ? undefined : `-${literal}`;
|
|
598
|
+
}
|
|
599
|
+
return fail(context, expression.span, failureMessage);
|
|
600
|
+
}
|
|
601
|
+
function renderFrontendCallablePrimitiveType(primitive, parameter) {
|
|
602
|
+
switch (primitive) {
|
|
603
|
+
case 'Bool': return 'boolean';
|
|
604
|
+
case 'Int': return parameter ? undefined : 'number';
|
|
605
|
+
case 'Float': return 'number';
|
|
606
|
+
case 'BigInt': return 'bigint';
|
|
607
|
+
case 'String': return 'string';
|
|
608
|
+
case 'Unit': return 'undefined';
|
|
609
|
+
default: return undefined;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function renderFrontendCallablePrimitiveValue(primitive) {
|
|
613
|
+
switch (primitive) {
|
|
614
|
+
case 'Bool': return 'false';
|
|
615
|
+
case 'Int':
|
|
616
|
+
case 'Float': return '0';
|
|
617
|
+
case 'BigInt': return '0n';
|
|
618
|
+
case 'String': return '""';
|
|
619
|
+
case 'Unit': return 'undefined';
|
|
620
|
+
default: return undefined;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function renderFrontendViewCallbackValue(lambda, evidence, context) {
|
|
624
|
+
if (lambda.async || !lambda.expressionBody || lambda.body.kind !== 'ViewExpression' || lambda.parameters.length !== evidence.parameterCount) {
|
|
625
|
+
return fail(context, lambda.span, 'checked View callback evidence does not match its lambda');
|
|
626
|
+
}
|
|
627
|
+
const parameters = [];
|
|
628
|
+
const previous = new Map();
|
|
629
|
+
for (let index = 0; index < lambda.parameters.length; index += 1) {
|
|
630
|
+
const parameter = lambda.parameters[index];
|
|
631
|
+
if (parameter.symbolId === undefined)
|
|
632
|
+
return fail(context, parameter.span, `View callback parameter ${parameter.name} lacks checked symbol evidence`);
|
|
633
|
+
const name = `$viruneCallbackParam${index}`;
|
|
634
|
+
parameters.push(name);
|
|
635
|
+
previous.set(parameter.symbolId, context.callbackValues.get(parameter.symbolId));
|
|
636
|
+
context.callbackValues.set(parameter.symbolId, name);
|
|
637
|
+
}
|
|
638
|
+
const body = renderViewBlockExpression(lambda.body.body, context);
|
|
639
|
+
for (const parameter of lambda.parameters) {
|
|
640
|
+
if (parameter.symbolId === undefined)
|
|
641
|
+
continue;
|
|
642
|
+
const value = previous.get(parameter.symbolId);
|
|
643
|
+
if (value === undefined)
|
|
644
|
+
context.callbackValues.delete(parameter.symbolId);
|
|
645
|
+
else
|
|
646
|
+
context.callbackValues.set(parameter.symbolId, value);
|
|
647
|
+
}
|
|
648
|
+
return body === undefined ? undefined : `((${parameters.join(', ')}) => ${body})`;
|
|
649
|
+
}
|
|
650
|
+
function renderFrontendCallableValue(descriptor) {
|
|
651
|
+
const parameters = [];
|
|
652
|
+
for (let index = 0; index < descriptor.parameters.length; index += 1) {
|
|
653
|
+
const type = renderFrontendCallablePrimitiveType(descriptor.parameters[index], true);
|
|
654
|
+
if (type === undefined)
|
|
655
|
+
return undefined;
|
|
656
|
+
parameters.push(`$p${index}: ${type}`);
|
|
657
|
+
}
|
|
658
|
+
const resultType = renderFrontendCallablePrimitiveType(descriptor.result, false);
|
|
659
|
+
const resultValue = renderFrontendCallablePrimitiveValue(descriptor.result);
|
|
660
|
+
if (resultType === undefined || resultValue === undefined)
|
|
661
|
+
return undefined;
|
|
662
|
+
return descriptor.async
|
|
663
|
+
? `(async (${parameters.join(', ')}): Promise<${resultType}> => ${resultValue})`
|
|
664
|
+
: `((${parameters.join(', ')}): ${resultType} => ${resultValue})`;
|
|
665
|
+
}
|
|
666
|
+
function renderNativeComponentPropertyValue(expression, context) {
|
|
667
|
+
const typeId = expression.inferredTypeId;
|
|
668
|
+
if (typeId !== undefined) {
|
|
669
|
+
const type = context.semantic.arena.get(typeId);
|
|
670
|
+
if (type.kind === 'named' && (type.declarationKind === 'newtype' || type.declarationKind === 'record')) {
|
|
671
|
+
const rendered = renderFrontendHostValue(typeId, context.semantic);
|
|
672
|
+
if (rendered !== undefined)
|
|
673
|
+
return rendered;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return renderViewValue(expression, context);
|
|
677
|
+
}
|
|
678
|
+
function validateNativeComponentProperties(element, component, context) {
|
|
679
|
+
if (component.symbolId === undefined) {
|
|
680
|
+
fail(context, element.span, `Virune-native component ${component.name} lacks checked signature evidence`);
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
const componentSymbol = context.semantic.symbols.get(component.symbolId);
|
|
684
|
+
const componentType = componentSymbol?.kind === 'component' ? context.semantic.arena.get(componentSymbol.typeId) : undefined;
|
|
685
|
+
if (componentType?.kind !== 'function' || componentType.parameters.length !== component.parameters.length) {
|
|
686
|
+
fail(context, element.span, `Virune-native component ${component.name} lacks checked signature evidence`);
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
const parameters = new Map(component.parameters.map((parameter, index) => [parameter.name, componentType.parameters[index]]));
|
|
690
|
+
const seen = new Set();
|
|
691
|
+
for (const property of element.properties) {
|
|
692
|
+
if (seen.has(property.name)) {
|
|
693
|
+
fail(context, property.span, `Virune-native component ${component.name} property ${property.name} is duplicated`);
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
seen.add(property.name);
|
|
697
|
+
const expected = parameters.get(property.name);
|
|
698
|
+
if (expected === undefined) {
|
|
699
|
+
fail(context, property.span, `Virune-native component ${component.name} has no property ${property.name}`);
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
const actual = property.value.inferredTypeId;
|
|
703
|
+
if (actual === undefined) {
|
|
704
|
+
fail(context, property.span, `Virune-native component ${component.name} property ${property.name} lacks checked type evidence`);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
if (!context.types.isAssignable(actual, expected)) {
|
|
708
|
+
fail(context, property.span, `Virune-native component ${component.name} property ${property.name} has type ${context.semantic.arena.display(actual)}; expected ${context.semantic.arena.display(expected)}`);
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
for (const parameter of component.parameters) {
|
|
713
|
+
if (seen.has(parameter.name))
|
|
714
|
+
continue;
|
|
715
|
+
fail(context, element.span, `Virune-native component ${component.name} requires property ${parameter.name}`);
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
function renderViewBlockContents(block, context) {
|
|
721
|
+
const children = [];
|
|
722
|
+
for (const child of block.children) {
|
|
723
|
+
const rendered = renderViewChild(child, context);
|
|
724
|
+
if (rendered === undefined)
|
|
725
|
+
return undefined;
|
|
726
|
+
children.push(rendered);
|
|
727
|
+
}
|
|
728
|
+
return children.join('');
|
|
729
|
+
}
|
|
730
|
+
function containsDirectChildrenSlot(block) {
|
|
731
|
+
return block.children.some(child => child.kind === 'ViewChildrenSlot' || (child.kind === 'ViewConditional' && conditionalContainsDirectChildrenSlot(child)));
|
|
732
|
+
}
|
|
733
|
+
function conditionalContainsDirectChildrenSlot(conditional) {
|
|
734
|
+
if (containsDirectChildrenSlot(conditional.thenBlock))
|
|
735
|
+
return true;
|
|
736
|
+
if (conditional.elseBranch === undefined)
|
|
737
|
+
return false;
|
|
738
|
+
return conditional.elseBranch.kind === 'ViewBlock'
|
|
739
|
+
? containsDirectChildrenSlot(conditional.elseBranch)
|
|
740
|
+
: conditionalContainsDirectChildrenSlot(conditional.elseBranch);
|
|
741
|
+
}
|
|
742
|
+
function containsDirectConditionalAbsence(block) {
|
|
743
|
+
return block.children.some(child => child.kind === 'ViewConditional' && conditionalContainsAbsence(child));
|
|
744
|
+
}
|
|
745
|
+
function conditionalContainsAbsence(conditional) {
|
|
746
|
+
if (conditional.elseBranch === undefined)
|
|
747
|
+
return true;
|
|
748
|
+
if (containsDirectConditionalAbsence(conditional.thenBlock))
|
|
749
|
+
return true;
|
|
750
|
+
return conditional.elseBranch.kind === 'ViewBlock'
|
|
751
|
+
? containsDirectConditionalAbsence(conditional.elseBranch)
|
|
752
|
+
: conditionalContainsAbsence(conditional.elseBranch);
|
|
753
|
+
}
|
|
754
|
+
function renderViewConditional(conditional, context) {
|
|
755
|
+
const condition = renderViewValue(conditional.condition, context);
|
|
756
|
+
if (condition === undefined)
|
|
757
|
+
return undefined;
|
|
758
|
+
const thenBranch = renderViewBlockExpression(conditional.thenBlock, context);
|
|
759
|
+
if (thenBranch === undefined)
|
|
760
|
+
return undefined;
|
|
761
|
+
const elseBranch = conditional.elseBranch === undefined
|
|
762
|
+
? '<></>'
|
|
763
|
+
: conditional.elseBranch.kind === 'ViewBlock'
|
|
764
|
+
? renderViewBlockExpression(conditional.elseBranch, context)
|
|
765
|
+
: renderConditionalExpression(conditional.elseBranch, context);
|
|
766
|
+
return elseBranch === undefined ? undefined : `{${condition} ? ${thenBranch} : ${elseBranch}}`;
|
|
767
|
+
}
|
|
768
|
+
function renderConditionalExpression(conditional, context) {
|
|
769
|
+
const condition = renderViewValue(conditional.condition, context);
|
|
770
|
+
if (condition === undefined)
|
|
771
|
+
return undefined;
|
|
772
|
+
const thenBranch = renderViewBlockExpression(conditional.thenBlock, context);
|
|
773
|
+
if (thenBranch === undefined)
|
|
774
|
+
return undefined;
|
|
775
|
+
const elseBranch = conditional.elseBranch === undefined
|
|
776
|
+
? '<></>'
|
|
777
|
+
: conditional.elseBranch.kind === 'ViewBlock'
|
|
778
|
+
? renderViewBlockExpression(conditional.elseBranch, context)
|
|
779
|
+
: renderConditionalExpression(conditional.elseBranch, context);
|
|
780
|
+
return elseBranch === undefined ? undefined : `(${condition} ? ${thenBranch} : ${elseBranch})`;
|
|
781
|
+
}
|
|
782
|
+
function renderViewValue(expression, context) {
|
|
783
|
+
if (expression.kind === 'IdentifierExpression' && expression.symbolId !== undefined) {
|
|
784
|
+
const callbackValue = context.callbackValues.get(expression.symbolId);
|
|
785
|
+
if (callbackValue !== undefined)
|
|
786
|
+
return callbackValue;
|
|
787
|
+
const repetitionValue = context.repetitionValues.get(expression.symbolId);
|
|
788
|
+
if (repetitionValue !== undefined)
|
|
789
|
+
return repetitionValue;
|
|
790
|
+
}
|
|
791
|
+
if (expression.kind === 'LiteralExpression')
|
|
792
|
+
return renderLiteral(expression, context);
|
|
793
|
+
if (expression.kind === 'UnaryExpression' && expression.operator === '-' && expression.operand.kind === 'LiteralExpression' && ['Int', 'Float', 'BigInt'].includes(expression.operand.literalKind)) {
|
|
794
|
+
const literal = renderLiteral(expression.operand, context);
|
|
795
|
+
return literal === undefined ? undefined : `-${literal}`;
|
|
796
|
+
}
|
|
797
|
+
const typeId = expression.inferredTypeId;
|
|
798
|
+
if (typeId === undefined)
|
|
799
|
+
return fail(context, expression.span, 'a View expression value has no checked type');
|
|
800
|
+
const type = context.semantic.arena.get(typeId);
|
|
801
|
+
if (type.kind === 'primitive') {
|
|
802
|
+
switch (type.name) {
|
|
803
|
+
case 'Bool': return '(false as boolean)';
|
|
804
|
+
case 'Int':
|
|
805
|
+
case 'Float': return '(0 as number)';
|
|
806
|
+
case 'BigInt': return '(0n as bigint)';
|
|
807
|
+
case 'String': return '("" as string)';
|
|
808
|
+
default: return fail(context, expression.span, `View value type ${context.semantic.arena.display(typeId)} is not safely projectable in this validation slice`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (type.kind === 'foreign' && type.snapshot.category === 'primitive' && type.snapshot.primitive === 'string')
|
|
812
|
+
return '("" as string)';
|
|
813
|
+
if (type.kind === 'foreign' && expression.kind === 'IdentifierExpression' && expression.symbolId !== undefined) {
|
|
814
|
+
const symbol = context.semantic.symbols.get(expression.symbolId);
|
|
815
|
+
if (symbol?.kind === 'import' && !symbol.typeOnly) {
|
|
816
|
+
if (type.snapshot.category === 'unknown' || type.snapshot.category === 'any')
|
|
817
|
+
return fail(context, expression.span, `View value type ${context.semantic.arena.display(typeId)} is not safely projectable in this validation slice`);
|
|
818
|
+
return expression.name;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return fail(context, expression.span, `View value type ${context.semantic.arena.display(typeId)} requires a boundary not implemented by this validation slice`);
|
|
822
|
+
}
|
|
823
|
+
function renderLiteral(expression, context) {
|
|
824
|
+
switch (expression.literalKind) {
|
|
825
|
+
case 'String': return renderStringValue(String(expression.value));
|
|
826
|
+
case 'Bool': return expression.value === true ? 'true' : 'false';
|
|
827
|
+
case 'Int':
|
|
828
|
+
case 'Float':
|
|
829
|
+
return typeof expression.value === 'number' && Number.isFinite(expression.value)
|
|
830
|
+
? String(expression.value)
|
|
831
|
+
: fail(context, expression.span, 'non-finite numeric literals cannot be represented in JSX validation');
|
|
832
|
+
case 'BigInt':
|
|
833
|
+
return typeof expression.value === 'bigint'
|
|
834
|
+
? `${expression.value}n`
|
|
835
|
+
: fail(context, expression.span, 'invalid bigint literal cannot be represented in JSX validation');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function renderStringValue(value) {
|
|
839
|
+
if (stringInterpolationPlaceholder.test(value))
|
|
840
|
+
return '("" as string)';
|
|
841
|
+
return JSON.stringify(value.replaceAll('{{', '{').replaceAll('}}', '}'));
|
|
842
|
+
}
|
|
843
|
+
function fail(context, span, message) {
|
|
844
|
+
context.failure ??= { span, message };
|
|
845
|
+
return undefined;
|
|
846
|
+
}
|
|
847
|
+
//# sourceMappingURL=jsx-view-validation.js.map
|