1pub mod action_authority;
8pub mod action_field_lowering;
9pub mod application_publication;
10pub mod application_semantic_model;
11pub mod asm_validation;
12pub mod authored_semantics;
13pub mod binding_table;
14pub mod capability_projection;
15pub mod capture_escape;
16pub mod codec_protocol;
17pub mod compilation_unit;
18pub mod compiler_pass;
19pub mod component_composition;
20pub mod component_diagnostics;
21pub mod component_graph;
22pub mod component_inheritance_lowering;
23pub mod component_initialization;
24pub mod component_instance;
25pub mod component_instance_scope;
26pub mod component_invocation;
27pub mod component_ir;
28pub mod component_ir_optimization;
29pub mod component_scope;
30pub mod composition_typing;
31pub mod computed_getter_lowering;
32pub mod computed_instance_slots;
33pub mod computed_projection;
34pub mod computed_value;
35pub mod consumer;
36pub mod context;
37pub mod context_declaration_candidate;
38pub mod context_dependency;
39mod context_designator;
40pub mod context_diagnostics;
41pub mod context_evaluation;
42pub mod context_inspection;
43pub mod context_lifetime;
44pub mod context_ownership;
45pub mod context_projection;
46pub mod context_resolution;
47pub mod context_resume;
48pub mod context_resume_projection;
49pub mod context_typing;
50pub mod context_update;
51pub mod control_flow;
52pub mod document_template;
53pub mod effect;
54pub mod effect_capability;
55pub mod effect_diagnostics;
56pub mod effect_field_lowering;
57pub mod effect_inspection;
58pub mod effect_projection;
59pub mod effect_resume;
60pub mod environment_input;
61pub mod environment_ownership;
62pub mod environment_publication;
63pub mod environment_read_lowering;
64pub mod environment_read_ownership;
65pub mod explain;
66pub mod expression_graph;
67pub mod file_route_publication;
68pub mod form;
69pub mod form_binding;
70pub mod form_definition_lowering;
71pub mod form_diagnostics;
72pub mod form_field;
73pub mod form_field_definition_lowering;
74pub mod form_inspection;
75pub mod form_ir;
76pub mod form_ir_optimization;
77pub mod form_ownership;
78pub mod form_projection;
79pub mod form_reset;
80pub mod form_serialization;
81pub mod form_submission;
82pub mod form_submission_host;
83pub mod form_submission_projection;
84pub mod form_tracking;
85pub mod form_validation;
86pub mod form_validation_definition_lowering;
87pub mod form_validation_plan;
88pub mod function_summary;
89pub mod html_codegen;
90pub mod incremental_projection;
91pub mod instance_context;
92pub mod intermediate_representation;
93#[cfg(any(test, all(feature = "wasm", target_arch = "wasm32")))]
94mod language_service;
95pub mod layout_composition;
96pub mod layout_graph;
97pub mod lazy_action_chunks;
98pub mod legacy_decorator_lowering;
99pub mod metaframework_handoff;
100pub mod model;
101pub mod module_graph;
102pub mod opaque;
103pub mod ordinary_html_codegen;
104pub mod ordinary_template_instance;
105pub mod ordinary_template_integrity;
106pub mod page_codegen;
107pub mod persistent_cache;
108pub mod platform;
109pub mod production_audit;
110pub mod production_bootstrap;
111pub mod production_chunk_graph;
112pub mod production_cleanup;
113pub mod production_constant_pool;
114pub mod production_deduplication;
115pub mod production_diagnostics;
116pub mod production_elimination;
117pub mod production_module_emitter;
118pub mod production_optimization;
119pub mod production_reachability;
120pub mod production_reports;
121pub mod production_runtime_artifact;
122pub mod production_scheduler;
123pub mod production_validation;
124pub mod project_discovery;
125pub mod provider;
126pub mod purity_effect;
127pub mod resource;
128pub mod resource_projection;
129pub mod resume_activation;
130pub mod resume_anchor;
131pub mod resume_boot;
132pub mod resume_boundary;
133pub mod resume_capture;
134pub mod resume_chunk;
135pub mod resume_diagnostics;
136pub mod resume_explain;
137pub mod resume_identity;
138pub mod resume_instance;
139pub mod resume_liveness;
140pub mod resume_manifest;
141pub mod resume_plan;
142pub mod resume_restore;
143pub mod resume_schema;
144pub mod route_graph;
145pub mod route_loader;
146pub mod route_loader_projection;
147pub mod route_metadata;
148pub mod route_server_action;
149pub mod runtime_codegen;
150pub mod runtime_component;
151pub mod runtime_component_artifact;
152pub mod runtime_computed;
153pub mod runtime_computed_artifact;
154pub mod runtime_context;
155pub mod runtime_context_artifact;
156pub mod runtime_effect;
157pub mod runtime_effect_artifact;
158pub mod runtime_effect_instance;
159pub mod runtime_form_artifact;
160pub mod runtime_form_registry;
161pub mod runtime_opaque_artifact;
162pub mod runtime_package_invocation_artifact;
163pub mod runtime_resource_artifact;
164pub mod semantic_capability;
165pub mod semantic_graph;
166pub mod semantic_id;
167pub mod semantic_package;
168pub mod semantic_package_runtime;
169pub mod semantic_provenance;
170pub mod semantic_reference;
171pub mod semantic_type;
172pub mod server_action_projection;
173pub mod service;
174pub mod shared_chunk_candidate;
175pub mod slot;
176pub mod slot_binding;
177pub mod slot_content;
178pub mod slot_field_lowering;
179pub mod slot_projection;
180pub mod state_initializer_lowering;
181pub mod state_instance_storage;
182pub mod state_projection;
183pub mod structural_component;
184pub mod structural_occurrence_identity;
185pub mod summarize;
186pub mod symbol_table;
187pub mod template_graph;
188pub mod template_manifest;
189pub mod template_semantics;
190pub mod tooling_products;
191pub mod tooling_reader;
192pub mod tooling_schema;
193pub mod tsx_binding_projection;
194pub mod v2_authoring_lowering;
195pub mod v2_authority_request;
196pub mod v2_authority_response;
197pub mod validation_projection;
198pub use tooling_products::{
199 build_tooling_artifact_graph_v1, build_tooling_build_trace_v1,
200 build_tooling_compile_cost_report_v1, decode_tooling_artifact_graph_v1,
201 decode_tooling_build_trace_v1, decode_tooling_compile_cost_report_v1,
202 decode_tooling_query_snapshot_v1, tooling_artifact_graph_json_v1, tooling_build_trace_json_v1,
203 tooling_compile_cost_report_json_v1, tooling_query_snapshot_json_v1, ToolingArtifactGraphV1,
204 ToolingBuildTraceStageV1, ToolingBuildTraceV1, ToolingCompileCostReportV1,
205 ToolingProductValidationErrorV1, ToolingQueryDiagnosticSecondaryV1,
206 ToolingQueryDiagnosticSeverityV1, ToolingQueryDiagnosticV1, ToolingQueryRangeV1,
207 ToolingQueryReferenceKindV1, ToolingQueryReferenceV1, ToolingQuerySemanticKindV1,
208 ToolingQuerySemanticRecordV1, ToolingQuerySnapshotSourceUnitV1, ToolingQuerySnapshotV1,
209 ToolingTraceIdentityV1, ToolingTraceOutcomeV1, ToolingTraceStageKindV1,
210 ARTIFACT_GRAPH_TOOLING_SCHEMA_V1, BUILD_TRACE_TOOLING_SCHEMA_V1,
211 COMPILE_COST_TOOLING_SCHEMA_V1, QUERY_SNAPSHOT_TOOLING_SCHEMA_V1,
212};
213pub mod watch;
214pub mod workspace;
215
216#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
217mod wasm_binding;
218
219pub use action_authority::{
220 build_action_authority_v1, ActionAdmissionV1, ActionAuthorityV1, ActionCaptureCoverageV1,
221 ActionEnvironmentV1, ActionFactV1, ActionRecordV1, ACTION_AUTHORITY_SCHEMA_VERSION,
222};
223pub use action_field_lowering::{
224 action_field_sites_v1, lower_action_fields_v1, ActionFieldLoweringErrorV1,
225 ActionFieldLoweringV1, ActionFieldSiteV1, ResolvedActionFieldV1,
226 ResolvedTerminalPackageInvocationV1,
227};
228pub use application_publication::{
229 application_publication_manifest_json_v1, build_application_publication_product_from_asm_v1,
230 build_application_publication_product_v1, validate_application_publication_request_v1,
231 ApplicationPublicationArtifactV1, ApplicationPublicationErrorV1,
232 ApplicationPublicationManifestV1, ApplicationPublicationProductV1,
233 ApplicationPublicationProfileV1, ApplicationPublicationRequestErrorV1,
234 ApplicationPublicationRequestV1, ApplicationPublicationSourceV1,
235 ValidatedApplicationPublicationRequestV1, APPLICATION_PUBLICATION_COMPILER_CONTRACT_V1,
236 APPLICATION_PUBLICATION_MANIFEST_SCHEMA_VERSION,
237};
238pub use application_semantic_model::{
239 build_application_semantic_model, build_application_semantic_model_for_unit,
240 build_application_semantic_model_for_unit_with_packages,
241 build_application_semantic_model_from_component_graph,
242 build_file_route_application_semantic_model_for_route_with_packages,
243 build_file_route_application_semantic_model_for_route_with_packages_and_v2_authoring,
244 build_file_route_application_semantic_model_for_unit_with_packages,
245 build_file_route_application_semantic_model_for_unit_with_packages_and_v2_authoring,
246 ApplicationSemanticModel, FileRouteApplicationModelErrorV1, SemanticEntity, SemanticEntityKind,
247};
248pub use asm_validation::{validate_application_semantic_model, AsmValidationDiagnostic};
249pub use authored_semantics::{
250 compose_authored_semantics_v1, normalize_authored_semantics_v1,
251 AuthoredSemanticCandidateKindV1, AuthoredSemanticCompositionErrorV1,
252 AuthoredSemanticNormalizationErrorV1, AuthoredSourceRangeV1,
253 CanonicalAuthoredDeclarationKindV1, CanonicalAuthoredDeclarationV1,
254 CanonicalAuthoredSemanticModelV1, CanonicalIntrinsicKindV1, DerivedAuthoredEvidenceV2,
255 ResolvedAuthoredSemanticCandidateV1, ResolvedIntrinsicIdentityV1,
256 CANONICAL_AUTHORED_SEMANTICS_SCHEMA_VERSION,
257};
258pub use binding_table::{
259 build_binding_table, build_binding_table_with_packages, BindingDiagnostic, BindingTable,
260 ExportBinding, ImportBinding, ImportBindingTarget, ModuleBindingTable,
261};
262pub use capture_escape::{
263 build_capture_escape_graph_v1, CaptureEscapeCoverageV1, CaptureEscapeErrorV1,
264 CaptureEscapeFactV1, CaptureEscapeFactsV1, CaptureEscapeGraphV1, CaptureEscapeKindV1,
265 FunctionCaptureEscapeV1, ResumeCaptureAdmissionV1, CAPTURE_ESCAPE_SCHEMA_VERSION,
266};
267pub use codec_protocol::{
268 build_codec_protocol_v1, CodecBehaviorV1, CodecClassificationV1, CodecClassificationsV1,
269 CodecDeclarationV1, CodecEnvironmentV1, CodecFailureBehaviorV1,
270 CodecProtocolDiagnosticReasonV1, CodecProtocolDiagnosticV1, CodecProtocolRecordV1,
271 CodecProtocolV1, CodecRepresentationV1, CODEC_PROTOCOL_SCHEMA_VERSION,
272};
273pub use compilation_unit::CompilationUnit;
274pub use compiler_pass::{
275 fold_component_graph, AnalysisPass, ConstantEvaluation, ConstantEvaluationPass,
276 ConstantFoldingPass, DependencyAnalysis, DependencyAnalysisPass, ImmutableAsmPass,
277};
278pub use component_composition::{
279 analyze_component_composition, ComponentCompositionAnalysis, ComponentCompositionCycle,
280};
281pub use component_diagnostics::{
282 collect_component_diagnostics, ComponentDiagnosticContract, COMPONENT_DIAGNOSTIC_CONTRACTS,
283};
284pub use component_graph::{
285 build_component_graph, build_component_graph_for_module, build_v2_component_graph_for_module,
286 is_valid_opaque_action_fact, ActionEndpoint, ArithmeticEvaluationError, ArithmeticExpression,
287 ArithmeticExpressionKind, ArithmeticOperator, AuthoredComponentHeritage,
288 AuthoredContextDeclarationCandidate, AuthoredDeclarationKind, AuthoredOpaqueActionFact,
289 AuthoredRouteLoaderDeclarationFact, AuthoredServerActionFact, AuthoredSlotDeclarationCandidate,
290 AuthoredStandardSchemaValidationFact, AuthoredSubmissionDeclarationFact,
291 AuthoredTerminalPackageInvocationFact, AuthoredValidationRuleArgument,
292 AuthoredValidationRuleArgumentKind, AuthoredValidationRuleDeclarationFact,
293 AuthoredValidationRuleExpression, AuthoredValidationRuleExpressionKind, ComparisonOperator,
294 ComponentAction, ComponentDiagnostic, ComponentDiagnosticSeverity, ComponentEffectField,
295 ComponentGraph, ComponentMethod, ComponentNode, ComputedExpression, ComputedExpressionKind,
296 ConstantEvaluationError, ConstantExpression, ConstantExpressionKind, ConsumerDeclaration,
297 ContextDeclaration, ContextDeclarationCandidateKind, ContextDeclarationViolation,
298 ContextDesignator, DeclaredStateType, DeclaredStateTypeKind, DiagnosticSecondaryLabel,
299 EffectBodySyntax, EffectCleanupSyntax, EffectExpression, EffectExpressionKind,
300 EffectStatementSyntax, EffectStatementSyntaxKind, FormDeclarationCandidate,
301 FormDeclarationStatus, FormDeclarationViolation, FormDesignatorFact,
302 FormFieldDeclarationCandidate, FormFieldDeclarationViolation, LogicalOperator, MethodCall,
303 MethodLocalVariable, MethodParameter, RenderAttribute, RenderAttributeValue, RenderChild,
304 RenderEventHandler, RenderFragment, RenderList, RenderModel, SerializableValue,
305 SlotDeclaration, SlotDeclarationViolation, SlotKind, StateField, StateOperation,
306 UnsupportedEffectStatementKind, UnsupportedFormDesignatorFact,
307};
308pub use component_inheritance_lowering::{
309 component_inheritance_sites_v1, lower_component_inheritance_v1,
310 ComponentInheritanceLoweringErrorV1, ComponentInheritanceLoweringV1,
311 ComponentInheritanceSiteV1, ResolvedComponentInheritanceV1,
312};
313pub use component_initialization::{
314 plan_component_initialization, ComponentInitializationPlan, ComponentInstanceBatch,
315 SlotBindingBatch,
316};
317pub use component_instance::{
318 plan_component_instances, plan_component_instances_with_virtual_invocations,
319 structural_template_entity_for_region, BlockedComponentInstancePlan,
320 BlockedComponentInstanceReason, ComponentBuildRoot, ComponentBuildRootKind, ComponentInstance,
321 ComponentInstancePlan, ComponentInstanceStatus,
322};
323pub use component_instance_scope::{
324 build_component_instance_scope_graph, validate_component_instance_scope_graph,
325 ComponentInstanceScopeDiagnostic, ComponentInstanceScopeGraph, ComponentInstanceScopeNode,
326 ComponentInstanceScopeViolation,
327};
328pub use component_invocation::{
329 collect_component_invocations, ComponentInvocationEntity, ComponentInvocationResolutionStatus,
330};
331pub use component_ir::{
332 lower_component_ir, validate_component_ir, ComponentIrInstruction, ComponentIrOperation,
333 ComponentIrReport,
334};
335pub use component_ir_optimization::{
336 optimize_component_ir, validate_optimized_component_ir, OptimizedComponentIrReport,
337};
338pub use component_scope::{ComponentScopeDiagnostic, ComponentScopeGraph};
339pub use composition_typing::{
340 collect_composition_type_products, ComponentInvocationTypeRecord, CompositionCompatibility,
341 CompositionTypeProducts, InstanceContextBindingTypeRecord, SlotBindingTypeRecord,
342};
343pub use computed_getter_lowering::{
344 computed_getter_sites_v1, lower_computed_getters_v1, ComputedGetterLoweringErrorV1,
345 ComputedGetterLoweringV1, ComputedGetterSiteV1,
346};
347pub use computed_instance_slots::{
348 build_computed_instance_slot_registry, validate_computed_instance_slot_registry,
349 ComputedInstanceSlotRecord, ComputedInstanceSlotRegistry,
350 COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION,
351};
352pub use computed_projection::{
353 build_computed_projection_v1, ComputedProjectionRecordV1, ComputedProjectionV1,
354 COMPUTED_PROJECTION_SCHEMA_VERSION,
355};
356pub use computed_value::{
357 collect_computed_values, ComputedCachePolicy, ComputedDiagnosticCode, ComputedPurity,
358 ComputedPurityViolation, ComputedPurityViolationKind, ComputedValue,
359};
360pub use consumer::{collect_consumer_entities, ConsumerEntity, ContextResolutionState};
361pub use context::{collect_context_entities, ContextEntity};
362pub use context_declaration_candidate::{
363 collect_context_declaration_candidates, ContextDeclarationCandidate,
364 ContextDeclarationCandidateRegistry, ContextDeclarationStatus, ContextSemanticEntityId,
365};
366pub use context_dependency::{
367 collect_context_dependency_graph, ContextDependencyCompatibility, ContextDependencyEdge,
368 ContextDependencyEdgeKind, ContextDependencyGraph, ContextDependencyNode,
369 ContextDependencyNodeId, ContextDependencyNodeKind,
370};
371pub use context_diagnostics::collect_context_diagnostics;
372pub use context_evaluation::{
373 collect_context_evaluation_plan, ContextConsumerAvailabilityEntry,
374 ContextConsumerAvailabilityStatus, ContextEvaluationBatch, ContextEvaluationBatchId,
375 ContextEvaluationPlan, ContextEvaluationPlanId, ContextSourceBlockReason,
376 ContextSourcePlanEntry, ContextSourcePlanStatus, ContextValueSourceId,
377};
378pub use context_inspection::{
379 build_context_inspection_registry, ContextInspection, ContextInspectionRegistry,
380};
381pub use context_lifetime::{
382 collect_context_lifetime_analysis, ContextBindingLifetimeRecord, ContextBindingLifetimeSource,
383 ContextBindingLifetimeStatus, ContextDefaultLifetimeRecord, ContextDependencyLifetimeRecord,
384 ContextEntityLifetimeRecord, ContextLifetimeAnalysis, ContextLifetimeEntityId,
385 ContextLifetimeId, LifetimeCompatibilityStatus, ProviderLifetimeRecord,
386};
387pub use context_ownership::{
388 collect_context_ownership_graph, ContextOwnedEntities, ContextOwnershipEdge,
389 ContextOwnershipEdgeKind, ContextOwnershipGraph, ContextOwnershipNode, ContextOwnershipNodeId,
390 ContextOwnershipNodeKind, ContextOwnershipOwnerId, ContextOwnershipTargetId,
391};
392pub use context_projection::{
393 build_context_projection_v1, ContextProjectionV1, ContextProviderRecordV1,
394 ContextTokenRecordV1, CONTEXT_PROJECTION_SCHEMA_VERSION,
395};
396pub use context_resolution::{
397 collect_context_resolutions, ContextResolution, ContextResolutionResult,
398};
399pub use context_resume::{
400 build_context_resume_plan, ContextResumePlan, ContextResumeRecord, ContextResumeSlotId,
401 ContextSlotResumeStatus,
402};
403pub use context_resume_projection::{
404 build_context_resume_projection_v1, ContextConsumerResolutionV1,
405 ContextResumeProjectionRecordV1, ContextResumeProjectionV1,
406 CONTEXT_RESUME_PROJECTION_SCHEMA_VERSION,
407};
408pub use context_typing::{
409 collect_context_type_products, CompatibilityStatus, ConsumerTypeRecord,
410 ContextBindingCompatibility, ContextBindingTypeRecord, ContextSerializationCompatibility,
411 ContextTypeProducts, ContextTypeRecord, ProviderTypeRecord,
412};
413pub use context_update::{build_context_update_plan, ContextActionUpdatePlan, ContextUpdatePlan};
414pub use control_flow::{
415 build_control_flow_graph_v1, ControlFlowAccessKindV1, ControlFlowAccessV1, ControlFlowBlockV1,
416 ControlFlowBranchArmV1, ControlFlowBranchEdgeV1, ControlFlowCoverageStatusV1,
417 ControlFlowCoverageV1, ControlFlowFunctionV1, ControlFlowGraphV1, ControlFlowLoopV1,
418 ControlFlowProvenanceV1, CONTROL_FLOW_SCHEMA_VERSION,
419};
420pub use document_template::{apply_document_template_v1, DocumentTemplateErrorV1};
421pub use effect::{
422 analyze_effect_reactivity, collect_effects, derive_effect_trigger_plan, lower_effect_bodies,
423 plan_effect_execution, validate_effects, ActionBatch, ActionBatchEffectTrigger,
424 ActionEffectExecutionPlan, Effect, EffectBody, EffectComputedPrerequisiteBatch,
425 EffectDeclaration, EffectExecutionBatch, EffectExecutionPlan, EffectExecutionPolicy,
426 EffectReactiveAnalysis, EffectRenderBoundary, EffectSemanticViolation,
427 EffectSemanticViolationKind, EffectStatement, EffectStatementKind, EffectTriggerPlan,
428 EffectValidation, InitialEffectExecutionPlan, UnplannedEffect, UnplannedEffectReason,
429};
430pub use effect_capability::{
431 ArgumentSerializationPolicy, BuiltinCapabilityProvenance, CapabilityDefinition, CapabilityId,
432 CapabilityOperation, CapabilityOperationId, CapabilityOperationKind, CapabilityParameters,
433 CapabilityResultPolicy, CapabilitySignature, CapabilityValueContract, EffectCapabilityRegistry,
434 RuntimeCapabilityLowering, StaticCapabilityPath, EFFECT_CAPABILITY_REGISTRY,
435 EFFECT_CAPABILITY_REGISTRY_VERSION,
436};
437pub use effect_diagnostics::{collect_effect_diagnostics, EffectDiagnosticCode};
438pub use effect_field_lowering::{
439 effect_field_sites_v1, lower_effect_fields_v1, EffectFieldLoweringErrorV1,
440 EffectFieldLoweringV1, EffectFieldSiteV1, ResolvedEffectFieldV1,
441};
442pub use effect_inspection::{
443 build_effect_inspection_registry, validate_effect_inspection_registry, EffectInspection,
444 EffectInspectionActionTrigger, EffectInspectionCapability, EffectInspectionDependencies,
445 EffectInspectionInitialTrigger, EffectInspectionIr, EffectInspectionPrerequisiteBatch,
446 EffectInspectionProvenance, EffectInspectionRegistry, EffectInspectionResumability,
447 EffectInspectionRuntime, EffectInspectionSchedule, EffectInspectionScheduledAction,
448 EffectInspectionUnplanned, EffectInspectionValidation, EffectInspectionValidationDiagnostic,
449 EffectInspectionViolation,
450};
451pub use effect_projection::{
452 build_effect_projection_v1, EffectProjectionRecordV1, EffectProjectionV1,
453 EFFECT_PROJECTION_SCHEMA_VERSION,
454};
455pub use effect_resume::{
456 build_effect_resume_plan, validate_effect_resume_plan, EffectActivationSlotId,
457 EffectActivationStatus, EffectInitialResumeMembership, EffectResumePlan, EffectResumeRecord,
458 EffectResumeValidationDiagnostic,
459};
460pub use environment_input::{
461 build_environment_input_manifest_v1, environment_input_manifest_from_json_v1,
462 environment_input_manifest_json_v1, validate_environment_input_manifest_v1,
463 EnvironmentInputErrorV1, EnvironmentInputManifestV1, ENVIRONMENT_INPUT_SCHEMA_VERSION,
464};
465pub use environment_ownership::{
466 build_environment_ownership_graph_v1, EnvironmentClassV1, EnvironmentOwnershipDiagnosticV1,
467 EnvironmentOwnershipEdgeKindV1, EnvironmentOwnershipEdgeV1, EnvironmentOwnershipErrorV1,
468 EnvironmentOwnershipFactsV1, EnvironmentOwnershipGraphV1, EnvironmentOwnershipNodeV1,
469 EnvironmentOwnershipViolationV1, LifetimeClassV1, ENVIRONMENT_OWNERSHIP_SCHEMA_VERSION,
470};
471pub use environment_publication::{
472 build_environment_publication_artifact_v1, environment_publication_artifact_json_v1,
473 validate_environment_publication_artifact_v1, EnvironmentPublicationArtifactV1,
474 EnvironmentPublicationErrorV1, ENVIRONMENT_PUBLICATION_SCHEMA_VERSION,
475};
476pub use environment_read_lowering::{
477 lower_environment_reads_v1, EnvironmentReadDiagnosticCodeV1, EnvironmentReadDiagnosticV1,
478 EnvironmentReadLoweringV1, EnvironmentReadRecordV1, ENVIRONMENT_READ_LOWERING_SCHEMA_VERSION,
479};
480pub use environment_read_ownership::{
481 build_environment_read_ownership_v1, EnvironmentReadOwnershipErrorV1,
482};
483pub use explain::{explain_json, explain_text};
484pub use expression_graph::{ExpressionGraph, ExpressionNode, ExpressionNodeKind};
485pub use file_route_publication::{
486 build_file_route_publication_v1, file_route_artifact_root_v1,
487 file_route_publication_manifest_json_v1, resolve_file_route_request_match_v1,
488 resolve_file_route_request_v1, FileRoutePublicationErrorV1, FileRoutePublicationManifestV1,
489 FileRoutePublicationProductV1, FileRoutePublicationRequestV1, FileRoutePublicationRouteV1,
490 FileRouteRequestMatchV1, FileRouteRequestTargetV1, FILE_ROUTE_ENVIRONMENT_ARTIFACT_PATH_V1,
491 FILE_ROUTE_PUBLICATION_COMPILER_CONTRACT_V1, FILE_ROUTE_PUBLICATION_MANIFEST_SCHEMA_VERSION,
492};
493pub use form::{collect_form_entities, FormEntity};
494pub use form_binding::{
495 collect_form_field_binding_products, FormControlChannel, FormControlCompatibility,
496 FormControlNormalization, FormFieldBinding, FormFieldBindingCandidate,
497 FormFieldBindingEvidence, FormFieldBindingEvidenceKind, FormFieldBindingExpressionFact,
498 FormFieldBindingProducts, FormFieldBindingViolation, FormInputKind,
499};
500pub use form_definition_lowering::{
501 form_definition_sites_v1, lower_form_definitions_v1, FormDefinitionLoweringErrorV1,
502 FormDefinitionLoweringV1, FormDefinitionSiteV1, ResolvedFormDefinitionV1,
503};
504pub use form_diagnostics::{
505 collect_form_diagnostics, FormDiagnosticReservation, FORM_DIAGNOSTIC_RESERVATIONS,
506};
507pub use form_field::{collect_form_field_products, FormFieldEntity, FormFieldProducts};
508pub use form_field_definition_lowering::{
509 form_field_definition_sites_v1, lower_form_field_definitions_v1,
510 FormFieldDefinitionLoweringErrorV1, FormFieldDefinitionLoweringV1, FormFieldDefinitionSiteV1,
511 ResolvedFormFieldDefinitionV1, ResolvedFormFieldValueClassificationV1,
512};
513pub use form_inspection::{build_form_inspection_registry, FormInspection, FormInspectionRegistry};
514pub use form_ir::{
515 lower_form_ir, FormInstanceIr, FormIrOperation, FormIrReport, FormRuntimeStorage,
516};
517pub use form_ir_optimization::{
518 optimize_form_ir, FormIrOptimizationMetrics, OptimizedFormIrReport,
519};
520pub use form_ownership::{
521 collect_form_ownership_graph, validate_form_ownership_graph, FormOwnershipEdge,
522 FormOwnershipEdgeKind, FormOwnershipGraph, FormOwnershipIntegrityDiagnostic,
523 FormOwnershipIntegrityKind, FormOwnershipNode, FormOwnershipNodeKey, FormOwnershipValidation,
524 FormReferenceEdge, FormReferenceKind,
525};
526pub use form_reset::{
527 collect_reset_products, FieldResetOperation, FieldResetStep, FormResetPlan, ResetProducts,
528};
529pub use form_serialization::{
530 collect_serialization_products, FormFieldSerializationConversion, FormSerializationFormat,
531 FormSerializationPlan, SerializationDeclarationFact, SerializationPlanStatus,
532 SerializationProducts, SerializedFieldPlan,
533};
534pub use form_submission::{
535 collect_submission_products, FormSubmissionCapability, FormSubmissionPlan,
536 SubmissionDeclarationCandidate, SubmissionDeclarationViolation, SubmissionProducts,
537 SubmitResetPolicy,
538};
539pub use form_submission_host::{
540 collect_submission_host_products, SubmissionHost, SubmissionHostCandidate,
541 SubmissionHostProducts, SubmissionHostViolation,
542};
543pub use form_tracking::{
544 collect_form_tracking_products, structurally_equal_serializable_values,
545 validate_dirty_tracking_graph, validate_touched_tracking_graph, DirtyTrackingGraph,
546 DirtyTrackingPlan, DirtyTransitionCause, DirtyTransitionPlan, FieldDirtyTracking,
547 FieldTouchedTracking, FormTrackingIntegrityDiagnostic, FormTrackingIntegrityKind,
548 FormTrackingProducts, FormTrackingValidation, TouchedTrackingGraph, TouchedTrackingPlan,
549 TouchedTransitionCause, TouchedTransitionPlan,
550};
551pub use form_validation::{
552 collect_validation_graph, collect_validation_products, validate_validation_graph,
553 ValidationCompatibility, ValidationDependencyCycle, ValidationDependencyDesignator,
554 ValidationGraph, ValidationGraphEdge, ValidationGraphEdgeKind,
555 ValidationGraphIntegrityDiagnostic, ValidationGraphIntegrityKind, ValidationGraphNode,
556 ValidationGraphNodeKey, ValidationGraphValidation, ValidationProducts, ValidationRule,
557 ValidationRuleArgument, ValidationRuleCandidate, ValidationRuleKind, ValidationRuleViolation,
558};
559pub use form_validation_definition_lowering::{
560 form_validation_definition_sites_v1, lower_form_validation_definitions_v1,
561 FormValidationDefinitionLoweringErrorV1, FormValidationDefinitionSiteV1,
562 ResolvedFormValidationDefinitionKindV1, ResolvedFormValidationDefinitionV1,
563};
564pub use form_validation_plan::{
565 collect_validation_dependency_plans, validate_validation_dependency_plans,
566 BlockedFieldValidationDependency, FieldChangeSet, FieldChangeValidationSchedule,
567 FieldDependencyBlockReason, FieldValidationChangePlan, FieldValidationDependency,
568 FieldValidationSourceEntry, FieldValidationTargetEntry, FormValidationDependencyPlan,
569 ValidationDependencyPlanIntegrityDiagnostic, ValidationDependencyPlanIntegrityKind,
570 ValidationDependencyPlanValidation, ValidationDependencyPlans, ValidationPlanningStatus,
571};
572pub use function_summary::{
573 build_function_summary_graph_v1, FunctionCallCoverageV1, FunctionCallFactV1,
574 FunctionCallFactsV1, FunctionSummaryErrorV1, FunctionSummaryGraphV1, FunctionSummaryV1,
575 FUNCTION_SUMMARY_SCHEMA_VERSION,
576};
577pub use html_codegen::generate_static_html;
578pub use instance_context::{
579 collect_instance_context_registry, ConsumerInstanceId, ConsumerInstanceRecord,
580 ContextDefaultSourceInstanceId, ContextSourceInstanceId, ContextSourceInstanceOwner,
581 InstanceContextRegistry, InstanceContextResolution, InstanceContextResolutionStatus,
582 InstanceContextValueSlotId, ProviderInstanceId, ProviderInstanceRecord,
583};
584pub use intermediate_representation::{
585 analyze_constant_propagation, analyze_dead_assignments, analyze_definition_uses,
586 analyze_liveness, analyze_reachability, analyze_reactive_cycles,
587 analyze_reactive_transitive_graph, analyze_use_definitions, build_reactive_graph,
588 compute_dominators, compute_post_dominators, computed_optimization_pipeline, inspect_dom_nodes,
589 lower_components_to_ir, optimize_computed_ir, optimize_context_ir, optimize_effect_ir,
590 plan_computed_evaluation, validate_context_ir, validate_effect_ir,
591 validate_intermediate_representation, validate_optimized_context_ir, ContextConsumerLoadId,
592 ContextIrReport, ContextSourceFunctionId, ContextValueSlotId, IntermediateRepresentation,
593 IrBinaryOperation, IrBlock, IrBlockId, IrBranchArm, IrBranchEdge, IrCfgCleanupPass,
594 IrCommonSubexpressionEliminationPass, IrComputedEvaluation, IrComputedEvaluationPlan,
595 IrConstant, IrConstantFoldingPass, IrConstantPropagationAnalysis, IrContextConsumerBinding,
596 IrContextLoad, IrContextSourceEvaluation, IrCopyPropagationPass, IrDeadAssignmentAnalysis,
597 IrDeadCodeEliminationPass, IrDefinitionUseAnalysis, IrDomAttribute, IrDomAttributeValue,
598 IrDomBinding, IrDomConditional, IrDomEvent, IrDomInspection, IrDomList, IrDomNode, IrDomNodeId,
599 IrDomNodeKind, IrDomText, IrDominatorTree, IrEffectCompletion, IrEffectExecution, IrFunction,
600 IrInstruction, IrInstructionId, IrInstructionKind, IrInstructionSimplificationPass,
601 IrLivenessAnalysis, IrLoop, IrLoopId, IrModule, IrOperand, IrOptimizationMetrics,
602 IrOptimizationPass, IrOptimizationPassReport, IrOptimizationPipeline, IrOptimizationReport,
603 IrPassManager, IrPostDominatorTree, IrReachabilityAnalysis, IrReactiveCycle,
604 IrReactiveCycleAnalysis, IrReactiveEdge, IrReactiveEdgeKind, IrReactiveGraph, IrReactiveNode,
605 IrReactiveNodeKind, IrReactiveTransitiveAnalysis, IrSchedulerInspection, IrStorage,
606 IrStorageId, IrTemplateEntrypoint, IrUnaryOperation, IrUpdateScheduler, IrUse, IrUseDefinition,
607 IrValidationDiagnostic, IrValue, IrValueDefinition, IrValueId, OptimizedContextIrReport,
608 OptimizedIrContextSourceEvaluation,
609};
610pub use layout_composition::{
611 build_layout_composition_plan_from_components_v1, build_layout_composition_plan_v1,
612 layout_composition_virtual_invocations_from_provenance_v1,
613 layout_composition_virtual_invocations_v1, LayoutCompositionEdgeV1, LayoutCompositionErrorV1,
614 LayoutCompositionPlanV1, LayoutCompositionRouteV1,
615};
616pub use legacy_decorator_lowering::{
617 legacy_decorator_sites_v1, lower_legacy_decorators_v1, LegacyDecoratorLoweringErrorV1,
618 LegacyDecoratorLoweringV1, LegacyDecoratorResolutionV1, LegacyDecoratorSiteV1,
619};
620pub use metaframework_handoff::{
621 build_deployable_release_manifest_v1, build_static_request_handoff_v1,
622 deployable_release_manifest_json_v1, static_request_handoff_json_v1, DeployableArtifactV1,
623 DeployableReleaseManifestV1, StaticRequestHandoffV1, StaticRequestRouteV1,
624};
625pub use model::{
626 ClassSummary, DecoratorSummary, Diagnostic, RenderMethodSummary, Severity, SourceSummary, Span,
627};
628pub use module_graph::{
629 build_module_graph, ModuleEdge, ModuleEdgeKind, ModuleGraph, ModuleNode, ModuleTarget,
630};
631pub use opaque::{OpaqueActionResolution, OpaqueActionResolutionOutcome, OpaqueTerminalBinding};
632pub use ordinary_html_codegen::{
633 generate_ordinary_instance_html, generate_ordinary_instance_html_for_component,
634 generate_structural_conditional_host_fragments, generate_structural_keyed_host_fragments,
635 generate_structural_template_instance_html,
636};
637pub use ordinary_template_instance::{
638 build_ordinary_template_instance_registry, validate_ordinary_template_instance_registry,
639 OrdinaryTemplateBindingKind, OrdinaryTemplateInstanceBindingRecord,
640 OrdinaryTemplateInstanceEventRecord, OrdinaryTemplateInstanceRegistry,
641 OrdinaryTemplateInstanceTargetRecord, OrdinaryTemplateTargetKind,
642 ORDINARY_TEMPLATE_INSTANCE_REGISTRY_VERSION,
643};
644pub use ordinary_template_integrity::{
645 ComputedInstanceSlotIntegrityCode, OrdinaryTemplateIntegrityCode,
646 StateInstanceStorageIntegrityCode,
647};
648pub use page_codegen::{
649 embed_opaque_runtime_artifact, embed_package_invocation_runtime_artifact,
650 generate_standalone_page, generate_standalone_page_with_component_runtime,
651 generate_standalone_page_with_component_runtime_and_forms,
652 generate_standalone_page_with_computed_runtime, generate_standalone_page_with_context_runtime,
653 generate_standalone_page_with_effect_runtime, generate_standalone_page_with_resume_runtime,
654 generate_standalone_page_with_resume_runtime_and_resources,
655};
656pub use production_audit::{
657 build_production_audit_report_v1, production_audit_report_json_v1, ProductionAuditErrorV1,
658 ProductionAuditReportV1, ProductionRuntimeAuthority, PRODUCTION_AUDIT_REPORT_SCHEMA_VERSION,
659 PRODUCTION_RUNTIME_AUTHORITIES, PRODUCTION_RUNTIME_REFINEMENT_INVARIANTS,
660};
661pub use production_bootstrap::{
662 build_production_bootstrap_plan, ProductionBootstrapBlock, ProductionBootstrapPlan,
663 ProductionRuntimeIndexes,
664};
665pub use production_chunk_graph::{
666 extract_production_chunk_graph, validate_production_chunk_graph, ProductionActivationChunkPlan,
667 ProductionChunkDependency, ProductionChunkExtractionError, ProductionChunkExtractionReport,
668 ProductionChunkGraph, ProductionChunkGraphValidationError, ProductionChunkKind,
669 ProductionChunkRecord, ProductionRootChunkInput, ProductionSharedChunkFailurePolicy,
670};
671pub use production_cleanup::{
672 build_production_destroy_plan, validate_production_cleanup_closure,
673 ProductionCleanupClosureViolation, ProductionCleanupKind, ProductionDestroyPlan,
674 ProductionOwnedRuntimeRecord,
675};
676pub use production_constant_pool::{
677 pool_production_constants, ConstantPoolConsumer, ConstantPoolingDecision,
678 ConstantPoolingReport, ProductionConstantCandidate, ProductionConstantPool,
679 ProductionConstantPoolEntry,
680};
681pub use production_deduplication::{
682 deduplicate_generated_programs, DeduplicatedProgramRegistry, ExecutableProgramCandidate,
683 ExecutableProgramCanonicalStream, ExecutableProgramFingerprintRegistry, ProgramAliasRecord,
684 ProgramDeduplicationReport,
685};
686pub use production_diagnostics::{
687 project_production_diagnostics, ProductionDiagnosticContract, ProductionDiagnosticFact,
688 ProductionDiagnosticKind, ProductionDiagnosticProvenance, ProductionProjectedDiagnostic,
689 PRODUCTION_DIAGNOSTIC_CATALOG,
690};
691pub use production_elimination::{
692 eliminate_unreachable_production_records, DeadProductEliminationReason,
693 DeadProductEliminationReport, OptimizationDecision, ProductionArtifactProjection,
694 ProductionExecutionRecord,
695};
696pub use production_module_emitter::{
697 emit_production_modules, validate_production_module, ProductionModuleLayout,
698 ProductionModuleRecord, ProductionModuleValidationError,
699};
700pub use production_optimization::{
701 BenchmarkFixtureId, ConstantPoolEntryId, ExecutableProgramFingerprint, OptimizationDecisionId,
702 OptimizationPolicyId, OptimizationReportId, PerformanceBudgetId, ProductionArtifactId,
703 ProductionChunkId, ProductionOptimizationDiagnosticReservation,
704 ProductionOptimizationIdentityParseError, ProductionOptimizationPolicy,
705 ProductionOptimizationPolicyV1, RuntimeTableId, SharedChunkCandidateId,
706 PRODUCTION_OPTIMIZATION_DIAGNOSTIC_RESERVATIONS,
707 PRODUCTION_OPTIMIZATION_INTEGRITY_RESERVATION_END,
708 PRODUCTION_OPTIMIZATION_INTEGRITY_RESERVATION_START,
709};
710pub use production_reachability::{
711 build_production_reachability_graph, ProductionExecutableRoot, ProductionProgramEdge,
712 ProductionReachabilityBlock, ProductionReachabilityGraph, ProductionReachabilityReason,
713 ProductionUnreachableRecord,
714};
715pub use production_reports::{
716 build_production_reports, optimization_report_json, runtime_cost_report_json,
717 OptimizationInspectionQuery, OptimizationReportV1, ProductionReportInputs,
718 RuntimeCostInspectionQuery, RuntimeCostReportV1, OPTIMIZATION_REPORT_SCHEMA_VERSION,
719 RUNTIME_COST_REPORT_SCHEMA_VERSION,
720};
721pub use production_runtime_artifact::{
722 build_production_runtime_artifact, build_production_runtime_table,
723 parse_production_runtime_artifact_v1, production_runtime_artifact_json,
724 validate_production_runtime_artifact, ProductionArtifactActivationEntry,
725 ProductionArtifactChunkRecord, ProductionArtifactEntry, ProductionArtifactIntegrity,
726 ProductionArtifactIntegrityViolation, ProductionOrdinalMapping, ProductionOrdinalWidth,
727 ProductionRuntimeArtifactV1, ProductionRuntimeTable, ProductionRuntimeTableRegistry,
728 PRODUCTION_RUNTIME_ARTIFACT_SCHEMA_VERSION, PRODUCTION_RUNTIME_TABLE_SCHEMA_VERSION,
729};
730pub use production_scheduler::{
731 build_production_patch_schedule, coalesce_production_binding_writes,
732 BindingWriteCoalescingDecision, BindingWriteCoalescingReport, ProductionPatchBatchKind,
733 ProductionPatchOperation, ProductionPatchSchedule,
734};
735pub use production_validation::{
736 validate_production_runtime_pipeline, ProductionRuntimeFailure, ProductionValidationEvidence,
737 ProductionValidationPhase, ProductionValidationResult,
738};
739pub use project_discovery::{
740 discover_project_v1, discover_semantic_packages_v1, DiscoveredProjectSourceV1,
741 DiscoveredProjectV1, ProjectDiscoveryErrorV1,
742};
743pub use provider::{collect_provider_entities, DuplicateProviderDeclaration, ProviderEntity};
744pub use purity_effect::{
745 build_purity_effect_graph_v1, FunctionEffectFactV1, FunctionEffectKindV1,
746 FunctionPurityEffectV1, FunctionPurityV1, PurityEffectErrorV1, PurityEffectGraphV1,
747 PURITY_EFFECT_SCHEMA_VERSION,
748};
749pub use resource::{
750 ResourceActivation, ResourceDeclaration, ResourceDeclarationError, ResourceEndpointBinding,
751 ResourceEndpointResolution, ResourceEndpointResolutionOutcome, ResourceInvalidationPolicy,
752 ResourceLifecycleError, ResourceLifecycleEvent, ResourceLifecycleState, ResourceRetryPolicy,
753};
754pub use resume_activation::{
755 build_resume_activation_plan, validate_resume_activation_plan, ResumeActivationBlock,
756 ResumeActivationBlockReason, ResumeActivationIntegrityCode,
757 ResumeActivationIntegrityDiagnostic, ResumeActivationPlan, ResumeActivationPolicy,
758 ResumeActivationPolicyDecision, ResumeActivationPrerequisite, RESUME_ACTIVATION_PLAN_VERSION,
759};
760pub use resume_anchor::{
761 build_resume_anchor_plan, validate_resume_anchor_plan, validate_resume_marker_html,
762 ResumeAnchorIntegrityCode, ResumeAnchorIntegrityDiagnostic, ResumeAnchorKind,
763 ResumeAnchorPlacement, ResumeAnchorPlan, ResumeAnchorPlanRecord, ResumeEventMarkerRecord,
764 RESUME_ANCHOR_PLAN_VERSION,
765};
766pub use resume_boundary::{
767 build_resume_boundary_graph, validate_resume_boundary_graph, ResumeBoundary,
768 ResumeBoundaryActivationIdentity, ResumeBoundaryActivationProgram,
769 ResumeBoundaryActivationReference, ResumeBoundaryBlock, ResumeBoundaryBlockSource,
770 ResumeBoundaryGraph, ResumeBoundaryIntegrityCode, ResumeBoundaryIntegrityDiagnostic,
771 ResumeBoundaryOwner, ResumeBoundaryOwnershipEdge, RESUME_BOUNDARY_GRAPH_VERSION,
772};
773pub use resume_capture::{
774 build_resume_capture_plan, capture_resume_snapshot, encode_resume_value,
775 resume_snapshot_artifact_json, resume_snapshot_json, validate_resume_capture_plan,
776 ResumeCaptureBlock, ResumeCaptureBlockReason, ResumeCaptureError, ResumeCaptureErrorKind,
777 ResumeCaptureInstruction, ResumeCaptureIntegrityCode, ResumeCaptureIntegrityDiagnostic,
778 ResumeCapturePlan, ResumeCaptureProgram, ResumeEncodedValue, ResumeEnvelopeWriterPlan,
779 ResumeSnapshotBoundaryV1, ResumeSnapshotStructuralOccurrenceV2,
780 ResumeSnapshotStructuralStateV2, ResumeSnapshotV1, ResumeSnapshotV2,
781 ResumeSnapshotValueRecordV1, RuntimeQuiescenceState, RESUME_CAPTURE_MANIFEST_VERSION,
782 RESUME_CAPTURE_PLAN_VERSION, RESUME_SNAPSHOT_SCHEMA_VERSION,
783};
784pub use resume_chunk::{
785 build_resume_chunk_graph, validate_resume_chunk_graph, ResumeChunk, ResumeChunkBlock,
786 ResumeChunkBlockReason, ResumeChunkGraph, ResumeChunkIntegrityCode,
787 ResumeChunkIntegrityDiagnostic, ResumeChunkModulePlan, ResumeChunkProgram,
788 ResumeChunkProgramInclusion, ResumeChunkRootKind, RESUME_CHUNK_GRAPH_VERSION,
789};
790pub use resume_diagnostics::{
791 build_resume_diagnostic_products, project_resume_diagnostics,
792 project_resume_diagnostics_from_products, ResumeDiagnosticProducts,
793 ResumeDiagnosticReservation, ResumeProjectedDiagnostic, RESUME_DIAGNOSTIC_RESERVATIONS,
794 RESUME_INTEGRITY_RESERVATION_END, RESUME_INTEGRITY_RESERVATION_START,
795};
796pub use resume_identity::{
797 ComputedInstanceCacheSlotId, ComputedInstanceDirtySlotId, ResumeActivationId,
798 ResumeActivationRootKind, ResumeAnchorId, ResumeBoundaryId, ResumeBoundaryKind, ResumeBuildId,
799 ResumeCaptureProgramId, ResumeChunkGroupId, ResumeChunkId, ResumeEventId,
800 ResumeIdentityParseError, ResumeRestoreProgramId, ResumeSchemaId, ResumeSlotId,
801 ResumeSnapshotId, ResumeValueRecordId, StateInstanceSlotId, TemplateInstanceBindingId,
802 TemplateInstanceTargetId,
803};
804pub use resume_liveness::{
805 build_resume_liveness_plan, validate_resume_liveness_plan, ResumeExcludedSlot,
806 ResumeExistingSlot, ResumeLivenessBlock, ResumeLivenessBlockReason,
807 ResumeLivenessClassificationKind, ResumeLivenessClassificationRef, ResumeLivenessIntegrityCode,
808 ResumeLivenessIntegrityDiagnostic, ResumeLivenessOwner, ResumeLivenessPlan, ResumeLivenessSlot,
809 ResumeRecomputableSlot, ResumeRecomputationProof, ResumeRetainedSlot, ResumeRetentionReason,
810 RESUME_LIVENESS_PLAN_VERSION,
811};
812pub use resume_manifest::{
813 build_resume_manifest, compute_resume_build_id, parse_resume_manifest_v6,
814 parse_resume_manifest_v7, resume_manifest_json, validate_resume_manifest, ResumeManifest,
815 ResumeManifestActivationPolicy, ResumeManifestActivationRecord, ResumeManifestAnchorRecord,
816 ResumeManifestBoundaryKind, ResumeManifestBoundaryRecord, ResumeManifestCaptureInstruction,
817 ResumeManifestCaptureProgram, ResumeManifestChunkRecord, ResumeManifestContextSlotRecord,
818 ResumeManifestEffectRecord, ResumeManifestEventRecord,
819 ResumeManifestPhaseIComponentResumeRecord, ResumeManifestRestoreInstruction,
820 ResumeManifestRestoreInstructionRecord, ResumeManifestRestoreProgram,
821 ResumeManifestSlotSchemaRecord, ResumeManifestValidationDiagnostic,
822 RESUME_MANIFEST_SCHEMA_VERSION, RESUME_RUNTIME_PROTOCOL_VERSION,
823};
824pub use resume_plan::{
825 build_resume_plan, ComponentInstanceResumePlan, FormFieldResumePlan, FormInstanceResumePlan,
826 ResumeComponentPlan, ResumeComputedPlan, ResumePlan, SlotBindingResumePlan,
827 StructuralRegionResumePlan,
828};
829pub use resume_restore::{
830 build_resume_restore_plan, validate_resume_restore_plan, ResumeRestoreApplicationSchedule,
831 ResumeRestoreBlock, ResumeRestoreBlockReason, ResumeRestoreInstruction,
832 ResumeRestoreInstructionRecord, ResumeRestoreIntegrityCode, ResumeRestoreIntegrityDiagnostic,
833 ResumeRestorePhase, ResumeRestorePlan, ResumeRestoreProgram, ResumeRestoreSchedulePhase,
834 ResumeRestoreSlotAssignment, RESUME_RESTORE_PLAN_VERSION,
835};
836pub use resume_schema::{
837 build_resume_schema_registry, resume_value_codec, validate_resume_schema_registry,
838 ResumeBoundarySchema, ResumeObjectPropertyCodec, ResumeSchemaBlock, ResumeSchemaBlockReason,
839 ResumeSchemaIntegrityCode, ResumeSchemaIntegrityDiagnostic, ResumeSchemaRegistry,
840 ResumeSlotSchema, ResumeValueCodec, RESUME_SCHEMA_REGISTRY_VERSION,
841};
842pub use route_graph::{
843 build_file_route_graph_from_components_v1, build_file_route_graph_v1, build_route_graph,
844 build_static_route_publication_v1, build_validated_file_route_graph_from_components_v1,
845 build_validated_file_route_graph_v1, build_validated_route_graph_v1, route_manifest_json_v1,
846 route_manifest_v1, FileRouteGraphV1, FileRouteNodeV1, RouteGraph, RouteGraphError,
847 RouteManifestEntryV1, RouteManifestV1, RouteNode,
848};
849pub use route_loader::{
850 build_route_loader_plan_v1, route_loader_plan_json_v1, RouteLoaderBindingV1,
851 RouteLoaderPlanErrorV1, RouteLoaderPlanV1, RouteLoaderRouteV1,
852 ROUTE_LOADER_PLAN_SCHEMA_VERSION,
853};
854pub use route_metadata::{
855 build_route_metadata_manifest_v1, route_metadata_manifest_json_v1, RouteMetadataErrorV1,
856 RouteMetadataInputV1, RouteMetadataManifestV1, RouteMetadataRecordV1,
857 ROUTE_METADATA_SCHEMA_VERSION,
858};
859pub use route_server_action::{
860 build_route_server_action_plan_v1, route_server_action_plan_json_v1,
861 RouteServerActionBindingV1, RouteServerActionPlanErrorV1, RouteServerActionPlanV1,
862 RouteServerActionRouteV1, ROUTE_SERVER_ACTION_PLAN_SCHEMA_VERSION,
863};
864pub use runtime_codegen::generate_runtime_stub;
865pub use runtime_component::{
866 build_runtime_component_registry, RuntimeComponentContextBindingRecord,
867 RuntimeComponentDefinitionRecord, RuntimeComponentInitializationBatch,
868 RuntimeComponentInstanceRecord, RuntimeComponentRegistry, RuntimeComponentSlotBindingRecord,
869 RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION,
870};
871pub use runtime_component_artifact::{
872 build_runtime_component_artifact, runtime_component_artifact_json,
873 validate_runtime_component_artifact, RuntimeComponentArtifact,
874 RUNTIME_COMPONENT_ARTIFACT_SCHEMA_VERSION,
875};
876pub use runtime_computed::{
877 build_runtime_computed_registry, ComputedCacheSlotId, ComputedDirtyFlagId,
878 RuntimeComputedCacheSlot, RuntimeComputedDirtyFlag, RuntimeComputedRecord,
879 RuntimeComputedRegistry,
880};
881pub use runtime_computed_artifact::{
882 build_runtime_computed_artifact, runtime_computed_artifact_json, RuntimeComputedArtifact,
883 RuntimeComputedArtifactDirtyFlag, RuntimeComputedArtifactEvaluation,
884 RuntimeComputedArtifactInstruction, RuntimeComputedArtifactInvalidation,
885 RuntimeComputedArtifactResourceInvalidation, RuntimeComputedArtifactSerialization,
886 RUNTIME_COMPUTED_ARTIFACT_SCHEMA_VERSION,
887};
888pub use runtime_context::{
889 build_runtime_context_registry, validate_runtime_context_registry,
890 RuntimeContextConsumerRecord, RuntimeContextEvaluationBatch, RuntimeContextRegistry,
891 RuntimeContextRegistryValidationDiagnostic, RuntimeContextSourceKind,
892 RuntimeContextSourceRecord, RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION,
893};
894pub use runtime_context_artifact::{
895 build_runtime_context_artifact, runtime_context_artifact_json, RuntimeContextArtifact,
896 SerializedContextActionUpdatePlan, SerializedContextBatchId, SerializedContextConsumerBinding,
897 SerializedContextEvaluationBatch, SerializedContextExecutionBoundary,
898 SerializedContextInstruction, SerializedContextInstructionKind, SerializedContextProgram,
899 SerializedContextSource, SerializedContextSourceKind, RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION,
900};
901pub use runtime_effect::{
902 build_runtime_effect_registry, RuntimeActionBatchEffectTrigger, RuntimeEffectRecord,
903 RuntimeEffectRegistry, RuntimeInitialEffectTrigger,
904};
905pub use runtime_effect_artifact::{
906 build_runtime_effect_artifact, runtime_effect_artifact_json, RuntimeEffectArtifact,
907 RuntimeEffectArtifactActionTrigger, RuntimeEffectArtifactCapabilityInstructionKind,
908 RuntimeEffectArtifactCapabilityOperation, RuntimeEffectArtifactEffect,
909 RuntimeEffectArtifactExecutionBoundary, RuntimeEffectArtifactExecutionPolicy,
910 RuntimeEffectArtifactInitialTrigger, RuntimeEffectArtifactInstance,
911 RuntimeEffectArtifactInstruction, RuntimeEffectArtifactPrerequisiteBatch,
912 RuntimeEffectArtifactProgram, RuntimeEffectArtifactRenderBoundary,
913 RuntimeEffectArtifactStructuralTemplate, RUNTIME_EFFECT_ARTIFACT_SCHEMA_VERSION,
914};
915pub use runtime_effect_instance::{
916 build_runtime_effect_instance_registry, build_runtime_effect_structural_template_registry,
917 RuntimeEffectInstanceRecord, RuntimeEffectInstanceRegistry,
918 RuntimeEffectStructuralTemplateRecord, RUNTIME_EFFECT_INSTANCE_REGISTRY_VERSION,
919};
920pub use runtime_form_artifact::{
921 build_runtime_forms_artifact, runtime_forms_artifact_json, validate_runtime_forms_artifact,
922 RuntimeFormsArtifact, RuntimeFormsArtifactBinding, RuntimeFormsArtifactDependency,
923 RuntimeFormsArtifactField, RuntimeFormsArtifactFieldProgram, RuntimeFormsArtifactFieldSlots,
924 RuntimeFormsArtifactForm, RuntimeFormsArtifactInstance, RuntimeFormsArtifactPrograms,
925 RuntimeFormsArtifactReset, RuntimeFormsArtifactRule, RuntimeFormsArtifactRuleArgument,
926 RuntimeFormsArtifactSerialization, RuntimeFormsArtifactStandardSchemaModule,
927 RuntimeFormsArtifactSubmission, RuntimeFormsArtifactValidation,
928 RUNTIME_FORM_ARTIFACT_SCHEMA_VERSION,
929};
930pub use runtime_form_registry::{
931 build_runtime_form_registry, RuntimeFormInstanceRecord, RuntimeFormRecord, RuntimeFormRegistry,
932 RUNTIME_FORM_REGISTRY_VERSION,
933};
934pub use runtime_opaque_artifact::{
935 build_runtime_opaque_artifact, build_runtime_opaque_artifact_with_modules,
936 runtime_opaque_artifact_json, validate_runtime_opaque_artifact, RuntimeOpaqueArtifact,
937 RuntimeOpaqueArtifactActivation, RuntimeOpaqueArtifactBuildError,
938 RuntimeOpaqueArtifactValidationError, RUNTIME_OPAQUE_ARTIFACT_SCHEMA_VERSION,
939};
940pub use runtime_package_invocation_artifact::{
941 build_runtime_package_invocation_artifact, runtime_package_invocation_artifact_json,
942 validate_runtime_package_invocation_artifact, RuntimePackageInvocation,
943 RuntimePackageInvocationArtifact, RuntimePackageInvocationArtifactValidationError,
944 PACKAGE_INVOCATION_REGISTRY_PATH, RUNTIME_PACKAGE_INVOCATION_ARTIFACT_SCHEMA_VERSION,
945};
946pub use runtime_resource_artifact::{
947 build_runtime_resource_artifact, build_runtime_resource_artifact_with_modules,
948 runtime_resource_artifact_json, validate_runtime_resource_artifact, RuntimeResourceArtifact,
949 RuntimeResourceArtifactActivation, RuntimeResourceArtifactBuildError,
950 RuntimeResourceArtifactDeclaration, RuntimeResourceArtifactEndpoint,
951 RuntimeResourceArtifactValidationError, RUNTIME_RESOURCE_ARTIFACT_SCHEMA_VERSION,
952};
953pub use semantic_capability::{
954 build_semantic_capability_registry, semantic_capability_matrix_text,
955 semantic_capability_migration_text, semantic_capability_registry_json, SemanticCapability,
956 SemanticCapabilityClass, SemanticCapabilityRegistry, SemanticCapabilityStatus,
957 SEMANTIC_CAPABILITY_REGISTRY_SCHEMA_VERSION,
958};
959pub use semantic_graph::{
960 build_semantic_graph, semantic_graph_json, SemanticGraph, SemanticGraphConsumer,
961 SemanticGraphContext, SemanticGraphEdge, SemanticGraphEdgeKind, SemanticGraphNode,
962 SemanticGraphNodeKind, SemanticGraphProvenance, SemanticGraphProvider,
963 SEMANTIC_GRAPH_SCHEMA_VERSION,
964};
965pub use semantic_id::{
966 ComponentInstanceId, ComponentInvocationId, ComponentRootId, ComponentStructuralRegionId,
967 ConsumerId, ContextDeclarationCandidateId, ContextId, DirtyTrackingPlanId, EffectId,
968 EffectInstanceId, EffectStatementId, FieldBindingId, FieldDependencyId, FieldId,
969 FieldResetOperationId, FieldTrackingId, FormDeclarationCandidateId,
970 FormFieldBindingCandidateId, FormFieldDeclarationCandidateId, FormFieldDirtySlotId,
971 FormFieldTouchedSlotId, FormFieldValidationSlotId, FormFieldValueSlotId, FormId,
972 FormInstanceId, FormOwnershipGraphId, FormSubmissionStateSlotId, FormValidationAggregateSlotId,
973 ProviderId, ResetPlanId, ResourceActivationId, ResourceId, SemanticId, SemanticOwner,
974 SerializationPlanId, SlotBindingId, SlotContentFragmentId, SlotDeclarationCandidateId, SlotId,
975 SlotOutletId, SubmissionDeclarationCandidateId, SubmissionHostCandidateId, SubmissionHostId,
976 SubmissionPlanId, TemplatePositionId, TouchedTrackingPlanId, ValidationDependencyCycleId,
977 ValidationGraphId, ValidationPlanId, ValidationRuleCandidateId, ValidationRuleId,
978};
979pub use semantic_package::{
980 parse_semantic_package_contract, SemanticPackageContract, SemanticPackageContractError,
981 SemanticPackageExport, SemanticPackageFormSubmission,
982 SemanticPackageFormSubmissionCancellation, SemanticPackageFormSubmissionExecutionBoundary,
983 SemanticPackageFormSubmissionInput, SemanticPackageFormSubmissionResult, SemanticPackageKind,
984 SemanticPackageOpaqueExecutionBoundary, SemanticPackageOpaqueResumePolicy,
985 SemanticPackageOpaqueTerminal, SemanticPackagePureOperation, SemanticPackageResolutionTable,
986 SemanticPackageResourceCancellation, SemanticPackageResourceEndpoint,
987 SemanticPackageResourceExecutionBoundary, SemanticPackageResourceResumePolicy,
988 SemanticPackageRouteLoader, SemanticPackageRouteLoaderFailure, SemanticPackageRouteLoaderInput,
989 SemanticPackageServerAction, SemanticPackageServerActionInput,
990 SemanticPackageServerActionResponse, SemanticPackageServerCachePolicy,
991 SemanticPackageServerCacheScope, SEMANTIC_PACKAGE_CONTRACT_SCHEMA_VERSION,
992};
993pub use semantic_package_runtime::{
994 SemanticPackageRuntimeModuleError, SemanticPackageRuntimeModuleKey,
995 SemanticPackageRuntimeModuleTable,
996};
997pub use semantic_provenance::SourceProvenance;
998pub use semantic_reference::{SemanticReference, SemanticReferenceKind};
999pub use semantic_type::{
1000 boundary_compatibility, dom_binding_contract, infer_serializable_value_type, is_assignable,
1001 is_state_initializer_assignable, operator_result_type, semantic_type_text,
1002 serialization_compatibility, state_initializer_value_type, BoundaryCompatibility,
1003 BuiltinTypeAuthority, ComputedValueType, DomBindingContract, DomBindingKind,
1004 EffectCompatibility, EffectOperationClassification, EffectStatementTypeRecord,
1005 ExecutionBoundary, ObjectType, ResolvedDeclaredSemanticType, ResourceExecutionBoundary,
1006 ResourceType, SemanticOperator, SemanticType, SemanticTypeAlias, SemanticTypeAssignment,
1007 SemanticTypeId, SemanticTypeModel, SemanticTypeStatus, SerializationCompatibility,
1008 TypeDiagnosticCode, TypeDiagnosticFamily,
1009};
1010pub use shared_chunk_candidate::{
1011 plan_shared_lazy_chunk_candidates, SharedChunkCandidate, SharedChunkCandidatePlan,
1012 SharedChunkConsumerRoot, SharedChunkProgramOccurrence, SharedChunkRejectionReason,
1013 SharedChunkSavingsCalculation,
1014};
1015pub use slot::{collect_slot_entities, SlotEntity};
1016pub use slot_binding::{
1017 collect_slot_bindings, collect_slot_bindings_with_virtual_invocations, SlotBinding,
1018 SlotBindingRegistry, SlotBindingStatus,
1019};
1020pub use slot_content::{
1021 collect_slot_composition, SlotCompositionRegistry, SlotContentFragment,
1022 SlotContentFragmentStatus, SlotContentFragmentViolation, SlotOutlet, SlotOutletStatus,
1023 SlotOutletViolation,
1024};
1025pub use slot_field_lowering::{
1026 lower_slot_fields_v1, slot_field_sites_v1, ResolvedSlotFieldV1, SlotFieldLoweringErrorV1,
1027 SlotFieldLoweringV1, SlotFieldSiteV1,
1028};
1029pub use slot_projection::{
1030 build_slot_projection_graph_v1, SlotProjectionGraphV1, SlotProjectionRecordV1,
1031 SlotProjectionStatusV1, SlotResumabilityCoverageV1, SLOT_PROJECTION_SCHEMA_VERSION,
1032};
1033pub use state_initializer_lowering::{
1034 lower_state_initializers_v1, state_initializer_sites_v1, ResolvedStateInitializerV1,
1035 StateInitializerLoweringErrorV1, StateInitializerLoweringV1, StateInitializerSiteV1,
1036};
1037pub use state_instance_storage::{
1038 build_state_instance_storage_registry, validate_state_instance_storage_registry,
1039 StateInstanceStorageRecord, StateInstanceStorageRegistry,
1040 STATE_INSTANCE_STORAGE_REGISTRY_VERSION,
1041};
1042pub use state_projection::{
1043 build_state_projection_v1, StateProjectionRecordV1, StateProjectionV1, StateResumeAdmissionV1,
1044 StateUpdateCoverageV1, STATE_PROJECTION_SCHEMA_VERSION,
1045};
1046pub use structural_component::{
1047 build_structural_component_graph_v1, ComponentInheritanceStatusV1, ComponentPropsResolutionV1,
1048 StructuralComponentDiagnosticReasonV1, StructuralComponentDiagnosticV1,
1049 StructuralComponentFactV1, StructuralComponentGraphV1, StructuralComponentPropV1,
1050 StructuralComponentRecordV1, STRUCTURAL_COMPONENT_SCHEMA_VERSION,
1051};
1052pub use structural_occurrence_identity::{
1053 decode_structural_occurrence_identity, encode_structural_occurrence_identity,
1054 StructuralOccurrenceIdentity,
1055};
1056pub use summarize::summarize_source;
1057pub use symbol_table::{
1058 build_symbol_table, ModuleSymbol, ModuleSymbolTable, SymbolDiagnostic, SymbolKind, SymbolTable,
1059};
1060pub use template_graph::{
1061 build_template_graph, AttributeValue, ConditionalNode, ElementNode, FragmentNode, ListNode,
1062 TemplateAttribute, TemplateChild, TemplateGraph, TemplateNode, TemplateNodeId,
1063};
1064pub use template_manifest::{
1065 build_template_manifest, build_template_manifest_from_asm, template_manifest_json,
1066 validate_template_manifest, ManifestAction, ManifestBindingTarget, ManifestComponent,
1067 ManifestEvent, ManifestEventKind, ManifestFormBinding, ManifestFormHost, ManifestNode,
1068 ManifestOperation, ManifestOrdinaryBinding, ManifestOrdinaryEvent, ManifestOrdinaryTarget,
1069 ManifestTemplate, TemplateManifest, TEMPLATE_MANIFEST_SCHEMA_VERSION,
1070};
1071pub use template_semantics::{
1072 build_template_semantic_entities, TemplateSemanticEntity, TemplateSemanticKind,
1073 TemplateSemanticScope,
1074};
1075pub use v2_authoring_lowering::{
1076 lower_v2_authoring_v1, V2AuthoringLoweringErrorV1, V2AuthoringLoweringV1,
1077 V2AuthoringResolutionsV1,
1078};
1079pub use v2_authority_request::{
1080 build_v2_authority_component_request_v1, build_v2_authority_request_v1,
1081 build_v2_environment_authority_request_v1, V2AuthorityCanonicalV1, V2AuthorityFormFieldSiteV1,
1082 V2AuthorityMemberSiteV1, V2AuthorityPositionV1, V2AuthorityRequestErrorV1,
1083 V2AuthorityRequestV1, V2AuthoritySiteV1, V2_AUTHORITY_REQUEST_SCHEMA_VERSION,
1084};
1085pub use v2_authority_response::{
1086 v2_authoring_resolutions_from_response_v1, v2_environment_public_resolutions_from_response_v1,
1087 validate_v2_authority_response_v1, ResolvedEnvironmentPublicReadV1,
1088 V2AuthorityFormFieldResolutionV1, V2AuthorityFormFieldValueClassificationV1,
1089 V2AuthorityIdentityV1, V2AuthorityResolutionV1, V2AuthorityResponseErrorV1,
1090 V2AuthorityResponseV1, V2_AUTHORITY_RESPONSE_SCHEMA_VERSION,
1091};
1092
1093#[cfg(test)]
1094mod tests {
1095 use super::*;
1096 use std::{collections::BTreeMap, path::Path};
1097
1098 #[test]
1099 fn summarizes_component_decorator_class_and_render_method() {
1100 let source = r#"
1101@component("x-counter")
1102class Counter extends Component {
1103 render() {
1104 return <button>Count</button>;
1105 }
1106}
1107"#;
1108
1109 let summary = summarize_source("Counter.tsx", source);
1110
1111 assert_eq!(summary.component_decorators.len(), 1);
1112 assert_eq!(summary.component_classes.len(), 1);
1113 assert_eq!(
1114 summary.component_decorators[0].argument.as_deref(),
1115 Some("x-counter")
1116 );
1117 assert_eq!(summary.class_declarations.len(), 1);
1118 assert_eq!(summary.class_declarations[0].name, "Counter");
1119 assert_eq!(summary.render_methods.len(), 1);
1120 assert!(summary.has_tsx_like_syntax);
1121 }
1122
1123 #[test]
1124 fn recognizes_decorator_free_component_classes_without_ps0100() {
1125 let source = r#"
1126import { Component } from "presolve";
1127
1128export default class Home extends Component {
1129 render() {
1130 return <main>Home</main>;
1131 }
1132}
1133"#;
1134
1135 let summary = summarize_source("app/routes/index.tsx", source);
1136
1137 assert_eq!(summary.component_classes.len(), 1);
1138 assert_eq!(summary.component_classes[0].name, "Home");
1139 assert!(summary.component_decorators.is_empty());
1140 assert!(!summary
1141 .diagnostics
1142 .iter()
1143 .any(|diagnostic| diagnostic.code == "PS0100"));
1144 }
1145
1146 #[test]
1147 fn emits_diagnostics_for_empty_source() {
1148 let summary = summarize_source("Empty.tsx", "");
1149 assert!(summary
1150 .diagnostics
1151 .iter()
1152 .any(|diagnostic| diagnostic.code == "PS0001"));
1153 }
1154
1155 #[test]
1156 fn fixture_0001_source_summary_explain_text_matches_expected() {
1157 let fixture_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1158 .join("../..")
1159 .join("fixtures/0001-source-summary");
1160
1161 let input_path = fixture_root.join("input/Counter.tsx");
1162 let expected_path = fixture_root.join("expected/explain.txt");
1163
1164 let source = std::fs::read_to_string(&input_path).expect("failed to read fixture input");
1165 let expected = std::fs::read_to_string(&expected_path)
1166 .expect("failed to read expected explain output");
1167
1168 let summary = summarize_source("fixtures/0001-source-summary/input/Counter.tsx", &source);
1169
1170 let actual = explain_text(&summary);
1171
1172 assert_eq!(actual, expected);
1173 }
1174
1175 #[test]
1176 fn fixture_0001_source_summary_explain_json_matches_expected() {
1177 let fixture_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1178 .join("../..")
1179 .join("fixtures/0001-source-summary");
1180
1181 let input_path = fixture_root.join("input/Counter.tsx");
1182 let expected_path = fixture_root.join("expected/explain.json");
1183
1184 let source = std::fs::read_to_string(&input_path).expect("failed to read fixture input");
1185 let expected = std::fs::read_to_string(&expected_path)
1186 .expect("failed to read expected JSON explain output");
1187
1188 let summary = summarize_source("fixtures/0001-source-summary/input/Counter.tsx", &source);
1189
1190 let actual = explain_json(&summary);
1191
1192 let actual_json: serde_json::Value =
1193 serde_json::from_str(&actual).expect("actual explain JSON is invalid");
1194 let expected_json: serde_json::Value =
1195 serde_json::from_str(&expected).expect("expected explain JSON fixture is invalid");
1196
1197 assert_eq!(actual_json, expected_json);
1198 }
1199
1200 #[test]
1201 #[allow(clippy::too_many_lines)]
1202 fn builds_component_graph_from_parsed_counter() {
1203 let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
1204
1205 let parsed =
1206 presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
1207
1208 let graph = build_component_graph(&parsed);
1209
1210 assert!(graph.diagnostics.is_empty());
1211
1212 let component = graph.components.first().expect("expected component");
1213
1214 assert_eq!(component.class_name, "Counter");
1215 assert_eq!(component.id.as_str(), "component:x-counter");
1216 assert_eq!(component.owner, SemanticOwner::Application);
1217 assert_eq!(component.element_name.as_deref(), Some("x-counter"));
1218 assert_eq!(component.route_path.as_deref(), Some("/counter"));
1219
1220 assert_eq!(component.state_fields.len(), 1);
1221 assert_eq!(component.state_fields[0].name, "count");
1222 assert_eq!(
1223 component.state_fields[0].id.as_str(),
1224 "component:x-counter/state:count"
1225 );
1226 assert_eq!(
1227 component.state_fields[0].owner,
1228 SemanticOwner::entity(component.id.clone())
1229 );
1230 assert_eq!(
1231 component.state_fields[0].initial_value,
1232 Some(SerializableValue::Number("0".to_string()))
1233 );
1234
1235 let method_names = component
1236 .methods
1237 .iter()
1238 .map(|method| method.name.as_str())
1239 .collect::<Vec<_>>();
1240
1241 assert_eq!(method_names, vec!["increment", "render"]);
1242 assert_eq!(
1243 component.methods[0].id.as_str(),
1244 "component:x-counter/method:increment"
1245 );
1246 assert_eq!(
1247 component.methods[0].owner,
1248 SemanticOwner::entity(component.id.clone())
1249 );
1250 assert_eq!(
1251 component.actions,
1252 vec![ComponentAction {
1253 id: SemanticId::component(Some("x-counter"), "Counter").action("increment", 0),
1254 owner: SemanticOwner::entity(
1255 SemanticId::component(Some("x-counter"), "Counter").method("increment"),
1256 ),
1257 method: "increment".to_string(),
1258 operation: StateOperation::AddAssign(SerializableValue::Number("1".to_string())),
1259 field: "count".to_string(),
1260 }]
1261 );
1262
1263 let render = component.render.as_ref().expect("expected render model");
1264
1265 assert_eq!(render.root_element.as_deref(), Some("button"));
1266 assert_eq!(render.attributes.len(), 1);
1267 assert_eq!(render.attributes[0].name, "onClick");
1268 assert!(matches!(
1269 render.attributes[0].value,
1270 RenderAttributeValue::Expression(_)
1271 ));
1272 assert_eq!(render.bindings, vec!["this.count"]);
1273 assert_eq!(render.root_span.expect("expected root span").line, 12);
1274 assert_eq!(render.root_span.expect("expected root span").column, 7);
1275 assert_eq!(render.event_handlers.len(), 1);
1276 assert_eq!(
1277 render.event_handlers[0].id.as_str(),
1278 "component:x-counter/event:click:0"
1279 );
1280 assert_eq!(
1281 render.event_handlers[0].owner,
1282 SemanticOwner::entity(component.id.template())
1283 );
1284 assert_eq!(render.event_handlers[0].event, "click");
1285 assert_eq!(render.event_handlers[0].handler, "this.increment");
1286 assert_eq!(render.event_handlers[0].span.line, 12);
1287 assert_eq!(render.event_handlers[0].span.column, 15);
1288 assert_eq!(render.children.len(), 2);
1289
1290 let RenderChild::Text { value, span } = &render.children[0] else {
1291 panic!("expected text child");
1292 };
1293 assert_eq!(value, "Count:");
1294 assert_eq!(span.line, 13);
1295 assert_eq!(span.column, 9);
1296
1297 let RenderChild::Binding { expression, span } = &render.children[1] else {
1298 panic!("expected binding child");
1299 };
1300 assert_eq!(expression, "this.count");
1301 assert_eq!(span.line, 13);
1302 assert_eq!(span.column, 16);
1303
1304 assert_eq!(graph.references.len(), 2);
1305 assert_eq!(graph.references[0].kind, SemanticReferenceKind::ActionState);
1306 assert_eq!(
1307 graph.references[0].source,
1308 SemanticId::component(Some("x-counter"), "Counter").action("increment", 0)
1309 );
1310 assert_eq!(
1311 graph.references[0].target,
1312 SemanticId::component(Some("x-counter"), "Counter").state_field("count")
1313 );
1314 assert_eq!(
1315 graph.references[0].provenance.path,
1316 Path::new("fixtures/0001-source-summary/input/Counter.tsx")
1317 );
1318 assert_eq!(graph.references[0].provenance.span.line, 7);
1319
1320 assert_eq!(graph.references[1].kind, SemanticReferenceKind::EventMethod);
1321 assert_eq!(
1322 graph.references[1].source,
1323 SemanticId::component(Some("x-counter"), "Counter").event_handler("click", 0)
1324 );
1325 assert_eq!(
1326 graph.references[1].target,
1327 SemanticId::component(Some("x-counter"), "Counter").method("increment")
1328 );
1329 assert_eq!(
1330 graph.references[1].provenance.path,
1331 Path::new("fixtures/0001-source-summary/input/Counter.tsx")
1332 );
1333 assert_eq!(graph.references[1].provenance.span.line, 12);
1334
1335 assert_eq!(graph.provenance[&component.id].span.line, 1);
1336 assert_eq!(graph.provenance[&component.state_fields[0].id].span.line, 4);
1337 assert_eq!(graph.provenance[&component.methods[0].id].span.line, 6);
1338 assert_eq!(graph.provenance[&component.actions[0].id].span.line, 7);
1339 assert_eq!(graph.provenance[&component.id.template()].span.line, 10);
1340 assert_eq!(graph.provenance[&render.event_handlers[0].id].span.line, 12);
1341 }
1342
1343 #[test]
1344 fn lowers_method_parameters_into_canonical_method_metadata() {
1345 let parsed = presolve_parser::parse_file(
1346 "src/Parameters.tsx",
1347 r#"
1348@component("x-parameters")
1349class Parameters extends Component {
1350 save(title: string, retries?: number) {}
1351}
1352"#,
1353 );
1354
1355 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1356 let method = &graph.components[0].methods[0];
1357
1358 assert_eq!(method.name, "save");
1359 assert_eq!(
1360 method
1361 .parameters
1362 .iter()
1363 .map(|parameter| parameter.name.as_str())
1364 .collect::<Vec<_>>(),
1365 vec!["title", "retries"]
1366 );
1367 assert_eq!(method.parameters[0].span.line, 4);
1368 assert_eq!(method.parameters[1].span.line, 4);
1369 }
1370
1371 #[test]
1372 fn assembles_application_semantic_model_from_existing_graphs() {
1373 let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
1374 let parsed =
1375 presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
1376
1377 let asm = build_application_semantic_model(&parsed);
1378 let component = &asm.components[0];
1379
1380 assert!(asm.diagnostics.is_empty());
1381 assert_eq!(asm.templates.len(), 1);
1382 assert!(asm.template_entities.len() >= 3);
1383 assert_eq!(asm.references.len(), 4);
1384 assert_eq!(asm.ownership.len(), asm.provenance.len());
1385 assert_eq!(asm.ownership[&component.id], SemanticOwner::Application);
1386 assert_eq!(
1387 asm.ownership[&component.state_fields[0].id],
1388 SemanticOwner::entity(component.id.clone())
1389 );
1390 assert_eq!(
1391 asm.ownership[&component.actions[0].id],
1392 SemanticOwner::entity(component.methods[0].id.clone())
1393 );
1394 assert_eq!(
1395 asm.ownership[&asm.templates[0].id],
1396 SemanticOwner::entity(component.id.clone())
1397 );
1398 assert_eq!(asm.provenance[&asm.templates[0].id].span.line, 10);
1399
1400 assert!(matches!(
1401 asm.entity(&component.id),
1402 Some(SemanticEntity::Component(_))
1403 ));
1404 assert!(matches!(
1405 asm.entity(&component.state_fields[0].id),
1406 Some(SemanticEntity::StateField(_))
1407 ));
1408 assert_eq!(asm.component(&component.id), Some(component));
1409 assert_eq!(asm.template(&asm.templates[0].id), Some(&asm.templates[0]));
1410 assert_eq!(
1411 asm.owner(&component.actions[0].id),
1412 Some(&component.actions[0].owner)
1413 );
1414 assert_eq!(asm.provenance(&asm.templates[0].id).unwrap().span.line, 10);
1415 let template_binding = asm
1416 .template_entities
1417 .iter()
1418 .find(|entity| entity.kind == TemplateSemanticKind::Binding)
1419 .expect("template binding entity");
1420 assert!(matches!(
1421 asm.entity(&template_binding.id),
1422 Some(SemanticEntity::TemplateEntity(_))
1423 ));
1424 assert_eq!(
1425 asm.template_entities_for(&asm.templates[0].id).len(),
1426 asm.template_entities.len()
1427 );
1428 assert_eq!(asm.references_from(&component.actions[0].id).len(), 1);
1429 assert_eq!(asm.references_to(&component.state_fields[0].id).len(), 2);
1430 assert_eq!(asm.references_to(&component.methods[0].id).len(), 2);
1431 assert!(validate_application_semantic_model(&asm).is_empty());
1432
1433 let mut invalid = asm.clone();
1434 invalid.provenance.remove(&component.actions[0].id);
1435 let diagnostics = validate_application_semantic_model(&invalid);
1436 let codes = diagnostics
1437 .iter()
1438 .map(|diagnostic| diagnostic.code.as_str())
1439 .collect::<Vec<_>>();
1440 assert!(codes.contains(&"PSASM1002"));
1441 assert!(codes.contains(&"PSASM1006"));
1442
1443 let mut invalid_type = asm.clone();
1444 let state_id = component.state_fields[0].id.clone();
1445 invalid_type
1446 .semantic_types
1447 .assignments
1448 .get_mut(&state_id)
1449 .expect("state type")
1450 .id = SemanticTypeId::for_subject(&component.id);
1451 let diagnostics = validate_application_semantic_model(&invalid_type);
1452 assert!(diagnostics
1453 .iter()
1454 .any(|diagnostic| diagnostic.code == "PSASM1102"));
1455
1456 let dependencies = DependencyAnalysisPass.analyze(&asm);
1457 assert_eq!(dependencies.dependencies[&component.actions[0].id].len(), 1);
1458 assert_eq!(
1459 dependencies.dependents[&component.state_fields[0].id].len(),
1460 2
1461 );
1462 }
1463
1464 #[test]
1465 fn carries_declared_state_types_into_component_and_asm_data() {
1466 let parsed = presolve_parser::parse_file(
1467 "src/Panel.tsx",
1468 r#"
1469@component("x-panel")
1470class Panel extends Component {
1471 count: number = state(0);
1472}
1473"#,
1474 );
1475
1476 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1477 let graph_type = graph.components[0].state_fields[0]
1478 .declared_type
1479 .as_ref()
1480 .expect("declared state type");
1481
1482 assert_eq!(graph_type.text, "number");
1483 assert_eq!(graph_type.provenance.path, Path::new("src/Panel.tsx"));
1484 assert_eq!(graph_type.provenance.span.line, 4);
1485 assert_eq!(graph_type.provenance.span.column, 8);
1486 assert_eq!(graph_type.kind, Some(DeclaredStateTypeKind::Number));
1487
1488 let asm = build_application_semantic_model(&parsed);
1489 assert_eq!(
1490 asm.components[0].state_fields[0].declared_type,
1491 Some(graph_type.clone())
1492 );
1493 }
1494
1495 #[test]
1496 fn lowers_and_evaluates_constant_arithmetic_state_initializers() {
1497 let parsed = presolve_parser::parse_file(
1498 "src/ArithmeticState.tsx",
1499 r#"
1500@component("x-arithmetic-state")
1501class ArithmeticState extends Component {
1502 total: number = state((1 + 2) * 3);
1503 difference: number = state(10 - 3);
1504 quotient: number = state(10 / 2);
1505 remainder: number = state(10 % 3);
1506
1507 render() {
1508 return <output>{this.total}</output>;
1509 }
1510}
1511"#,
1512 );
1513 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1514 let field = &graph.components[0].state_fields[0];
1515
1516 assert_eq!(
1517 field
1518 .initial_expression
1519 .as_ref()
1520 .map(ToString::to_string)
1521 .as_deref(),
1522 Some("((1 + 2) * 3)")
1523 );
1524 assert_eq!(
1525 field.initial_value,
1526 Some(SerializableValue::Number("9".to_string()))
1527 );
1528 assert_eq!(
1529 graph.components[0].state_fields[1].initial_value,
1530 Some(SerializableValue::Number("7".to_string()))
1531 );
1532 assert_eq!(
1533 graph.components[0].state_fields[2].initial_value,
1534 Some(SerializableValue::Number("5".to_string()))
1535 );
1536 assert_eq!(
1537 graph.components[0].state_fields[3].initial_value,
1538 Some(SerializableValue::Number("1".to_string()))
1539 );
1540 assert!(graph.diagnostics.is_empty());
1541 }
1542
1543 #[test]
1544 fn reports_invalid_constant_arithmetic_state_initializers() {
1545 let parsed = presolve_parser::parse_file(
1546 "src/ArithmeticState.tsx",
1547 r#"
1548@component("x-arithmetic-state")
1549class ArithmeticState extends Component {
1550 total: number = state(10 / 0);
1551}
1552"#,
1553 );
1554 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1555 let diagnostic = graph
1556 .diagnostics
1557 .iter()
1558 .find(|diagnostic| diagnostic.code == "PSC1022")
1559 .expect("arithmetic diagnostic");
1560
1561 assert!(diagnostic.message.contains("division or remainder by zero"));
1562 assert_eq!(
1563 diagnostic
1564 .provenance
1565 .as_ref()
1566 .map(|provenance| provenance.span.line),
1567 Some(4)
1568 );
1569 }
1570
1571 #[test]
1572 fn lowers_and_evaluates_constant_comparison_state_initializers() {
1573 let parsed = presolve_parser::parse_file(
1574 "src/ComparisonState.tsx",
1575 r#"
1576@component("x-comparison-state")
1577class ComparisonState extends Component {
1578 equal: boolean = state(3 === 3);
1579 notEqual: boolean = state(3 !== 4);
1580 lessThan: boolean = state(2 < 3);
1581 lessThanOrEqual: boolean = state(3 <= 3);
1582 greaterThan: boolean = state(4 > 3);
1583 ready: boolean = state(((1 + 2) * 3) >= 9);
1584
1585 render() {
1586 return <output>{this.ready}</output>;
1587 }
1588}
1589"#,
1590 );
1591 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1592 let fields = &graph.components[0].state_fields;
1593
1594 assert_eq!(
1595 fields[0]
1596 .initial_expression
1597 .as_ref()
1598 .map(ToString::to_string)
1599 .as_deref(),
1600 Some("(3 === 3)")
1601 );
1602 for field in fields {
1603 assert_eq!(field.initial_value, Some(SerializableValue::Boolean(true)));
1604 }
1605 assert_eq!(
1606 fields[5]
1607 .initial_expression
1608 .as_ref()
1609 .map(ToString::to_string)
1610 .as_deref(),
1611 Some("(((1 + 2) * 3) >= 9)")
1612 );
1613 assert!(graph.diagnostics.is_empty());
1614 }
1615
1616 #[test]
1617 fn reports_invalid_constant_comparison_state_initializers() {
1618 let parsed = presolve_parser::parse_file(
1619 "src/ComparisonState.tsx",
1620 r#"
1621@component("x-comparison-state")
1622class ComparisonState extends Component {
1623 ready: boolean = state((10 / 0) >= 1);
1624}
1625"#,
1626 );
1627 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1628 let diagnostic = graph
1629 .diagnostics
1630 .iter()
1631 .find(|diagnostic| diagnostic.code == "PSC1023")
1632 .expect("comparison diagnostic");
1633
1634 assert!(diagnostic.message.contains("division or remainder by zero"));
1635 assert_eq!(
1636 diagnostic
1637 .provenance
1638 .as_ref()
1639 .map(|provenance| provenance.span.line),
1640 Some(4)
1641 );
1642 }
1643
1644 #[test]
1645 fn lowers_and_evaluates_constant_logical_state_initializers() {
1646 let parsed = presolve_parser::parse_file(
1647 "src/LogicalState.tsx",
1648 r#"
1649@component("x-logical-state")
1650class LogicalState extends Component {
1651 both: boolean = state((1 < 2) && (3 >= 3));
1652 either: boolean = state(false || (10 !== 4));
1653 shortAnd: boolean = state(false && ((10 / 0) > 1));
1654 shortOr: boolean = state(true || ((10 / 0) > 1));
1655
1656 render() {
1657 return <output>{this.both}</output>;
1658 }
1659}
1660"#,
1661 );
1662 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1663 let fields = &graph.components[0].state_fields;
1664
1665 assert_eq!(
1666 fields[0]
1667 .initial_expression
1668 .as_ref()
1669 .map(ToString::to_string)
1670 .as_deref(),
1671 Some("((1 < 2) && (3 >= 3))")
1672 );
1673 assert_eq!(
1674 fields
1675 .iter()
1676 .map(|field| field.initial_value.clone())
1677 .collect::<Vec<_>>(),
1678 vec![
1679 Some(SerializableValue::Boolean(true)),
1680 Some(SerializableValue::Boolean(true)),
1681 Some(SerializableValue::Boolean(false)),
1682 Some(SerializableValue::Boolean(true)),
1683 ]
1684 );
1685 assert!(graph.diagnostics.is_empty());
1686 }
1687
1688 #[test]
1689 fn reports_evaluated_invalid_constant_logical_state_initializers() {
1690 let parsed = presolve_parser::parse_file(
1691 "src/LogicalState.tsx",
1692 r#"
1693@component("x-logical-state")
1694class LogicalState extends Component {
1695 ready: boolean = state(true && ((10 / 0) > 1));
1696}
1697"#,
1698 );
1699 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1700 let diagnostic = graph
1701 .diagnostics
1702 .iter()
1703 .find(|diagnostic| diagnostic.code == "PSC1024")
1704 .expect("logical diagnostic");
1705
1706 assert!(diagnostic.message.contains("division or remainder by zero"));
1707 assert_eq!(
1708 diagnostic
1709 .provenance
1710 .as_ref()
1711 .map(|provenance| provenance.span.line),
1712 Some(4)
1713 );
1714 }
1715
1716 #[test]
1717 fn lowers_and_evaluates_constant_nullish_state_initializers() {
1718 let parsed = presolve_parser::parse_file(
1719 "src/NullishState.tsx",
1720 r#"
1721@component("x-nullish-state")
1722class NullishState extends Component {
1723 label: string = state(null ?? "fallback");
1724 total: number = state(5 ?? (10 / 0));
1725
1726 render() {
1727 return <output>{this.label}</output>;
1728 }
1729}
1730"#,
1731 );
1732 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1733 let fields = &graph.components[0].state_fields;
1734
1735 assert_eq!(
1736 fields[0]
1737 .initial_expression
1738 .as_ref()
1739 .map(ToString::to_string)
1740 .as_deref(),
1741 Some("(null ?? \"fallback\")")
1742 );
1743 assert_eq!(
1744 fields[0].initial_value,
1745 Some(SerializableValue::String("fallback".to_string()))
1746 );
1747 assert_eq!(
1748 fields[1].initial_value,
1749 Some(SerializableValue::Number("5".to_string()))
1750 );
1751 assert!(graph.diagnostics.is_empty());
1752 }
1753
1754 #[test]
1755 fn lowers_and_evaluates_constant_unary_state_initializers() {
1756 let parsed = presolve_parser::parse_file(
1757 "src/UnaryState.tsx",
1758 r#"
1759@component("x-unary-state")
1760class UnaryState extends Component {
1761 negated: boolean = state(!(1 < 2));
1762 signed: number = state(-(1 + 2));
1763 render() { return <output>{this.signed}</output>; }
1764}
1765"#,
1766 );
1767 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1768 assert_eq!(
1769 graph.components[0].state_fields[0].initial_value,
1770 Some(SerializableValue::Boolean(false))
1771 );
1772 assert_eq!(
1773 graph.components[0].state_fields[1].initial_value,
1774 Some(SerializableValue::Number("-3".to_string()))
1775 );
1776 }
1777
1778 #[test]
1779 fn reports_reached_invalid_constant_nullish_state_initializers() {
1780 let parsed = presolve_parser::parse_file(
1781 "src/NullishState.tsx",
1782 r#"
1783@component("x-nullish-state")
1784class NullishState extends Component {
1785 total: number = state(null ?? (10 / 0));
1786}
1787"#,
1788 );
1789 let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1790 let diagnostic = graph
1791 .diagnostics
1792 .iter()
1793 .find(|diagnostic| diagnostic.code == "PSC1025")
1794 .expect("nullish diagnostic");
1795 assert!(diagnostic.message.contains("division or remainder by zero"));
1796 }
1797
1798 #[test]
1799 fn classifies_exact_primitive_declared_state_types() {
1800 let source =
1801 include_str!("../../../fixtures/0025-typed-state-annotations/input/TypedState.tsx");
1802 let parsed = presolve_parser::parse_file(
1803 "fixtures/0025-typed-state-annotations/input/TypedState.tsx",
1804 source,
1805 );
1806 let graph = build_component_graph_for_module(&parsed);
1807 let kinds = graph.components[0]
1808 .state_fields
1809 .iter()
1810 .map(|field| {
1811 (
1812 field.name.as_str(),
1813 field
1814 .declared_type
1815 .as_ref()
1816 .and_then(|declared_type| declared_type.kind),
1817 )
1818 })
1819 .collect::<Vec<_>>();
1820
1821 assert_eq!(
1822 kinds,
1823 vec![
1824 ("count", Some(DeclaredStateTypeKind::Number)),
1825 ("status", None),
1826 ("title", Some(DeclaredStateTypeKind::String)),
1827 ("enabled", Some(DeclaredStateTypeKind::Boolean)),
1828 ("empty", Some(DeclaredStateTypeKind::Null)),
1829 ]
1830 );
1831 }
1832
1833 #[test]
1834 fn reports_primitive_declared_state_initializer_mismatches() {
1835 let source = include_str!(
1836 "../../../fixtures/0027-declared-state-type-diagnostics/input/InvalidTypedState.tsx"
1837 );
1838 let parsed = presolve_parser::parse_file(
1839 "fixtures/0027-declared-state-type-diagnostics/input/InvalidTypedState.tsx",
1840 source,
1841 );
1842 let graph = build_component_graph_for_module(&parsed);
1843 let folded = ConstantFoldingPass.transform(
1844 &build_application_semantic_model_from_component_graph(&graph),
1845 );
1846 let diagnostics = folded
1847 .diagnostics
1848 .iter()
1849 .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1850 .collect::<Vec<_>>();
1851
1852 assert_eq!(diagnostics.len(), 6);
1853 assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1016"));
1854
1855 let provenance = folded.diagnostics[0]
1856 .provenance
1857 .as_ref()
1858 .expect("mismatch diagnostic provenance");
1859 assert_eq!(
1860 provenance.path,
1861 Path::new("fixtures/0027-declared-state-type-diagnostics/input/InvalidTypedState.tsx")
1862 );
1863 assert_eq!(provenance.span.line, 3);
1864 assert_eq!(provenance.span.column, 8);
1865 }
1866
1867 #[test]
1868 fn reports_primitive_declared_state_action_assignment_mismatches() {
1869 let source = include_str!(
1870 "../../../fixtures/0028-primitive-action-type-diagnostics/input/InvalidTypedActions.tsx"
1871 );
1872 let parsed = presolve_parser::parse_file(
1873 "fixtures/0028-primitive-action-type-diagnostics/input/InvalidTypedActions.tsx",
1874 source,
1875 );
1876 let graph = build_component_graph_for_module(&parsed);
1877 let folded = ConstantFoldingPass.transform(
1878 &build_application_semantic_model_from_component_graph(&graph),
1879 );
1880 let diagnostics = folded
1881 .diagnostics
1882 .iter()
1883 .filter(|diagnostic| diagnostic.code == "PSC1017")
1884 .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1885 .collect::<Vec<_>>();
1886
1887 assert_eq!(diagnostics.len(), 6);
1888 assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1017"));
1889 assert!(diagnostics.iter().any(|(_, message)| {
1890 message.contains("state field `status`") && message.contains("assigns `number`")
1891 }));
1892 assert!(diagnostics.iter().any(|(_, message)| {
1893 message.contains("state field `collection`") && message.contains("assigns `tuple`")
1894 }));
1895
1896 let provenance = folded
1897 .diagnostics
1898 .iter()
1899 .find(|diagnostic| diagnostic.code == "PSC1017")
1900 .expect("action mismatch diagnostic")
1901 .provenance
1902 .as_ref()
1903 .expect("action mismatch diagnostic provenance");
1904 assert_eq!(
1905 provenance.path,
1906 Path::new(
1907 "fixtures/0028-primitive-action-type-diagnostics/input/InvalidTypedActions.tsx"
1908 )
1909 );
1910 assert_eq!(provenance.span.line, 11);
1911 assert_eq!(provenance.span.column, 5);
1912 }
1913
1914 #[test]
1915 fn reports_non_boolean_primitive_toggle_actions() {
1916 let source = include_str!(
1917 "../../../fixtures/0029-primitive-toggle-type-diagnostics/input/InvalidTypedToggles.tsx"
1918 );
1919 let parsed = presolve_parser::parse_file(
1920 "fixtures/0029-primitive-toggle-type-diagnostics/input/InvalidTypedToggles.tsx",
1921 source,
1922 );
1923 let graph = build_component_graph_for_module(&parsed);
1924 let folded = ConstantFoldingPass.transform(
1925 &build_application_semantic_model_from_component_graph(&graph),
1926 );
1927 let diagnostics = folded
1928 .diagnostics
1929 .iter()
1930 .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1931 .collect::<Vec<_>>();
1932
1933 assert_eq!(diagnostics.len(), 4);
1934 assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1018"));
1935
1936 let provenance = folded.diagnostics[0]
1937 .provenance
1938 .as_ref()
1939 .expect("toggle diagnostic provenance");
1940 assert_eq!(
1941 provenance.path,
1942 Path::new(
1943 "fixtures/0029-primitive-toggle-type-diagnostics/input/InvalidTypedToggles.tsx"
1944 )
1945 );
1946 assert_eq!(provenance.span.line, 10);
1947 assert_eq!(provenance.span.column, 5);
1948 }
1949
1950 #[test]
1951 fn reports_non_numeric_primitive_increment_and_decrement_actions() {
1952 let source = include_str!(
1953 "../../../fixtures/0030-primitive-numeric-action-type-diagnostics/input/InvalidTypedNumericActions.tsx"
1954 );
1955 let parsed = presolve_parser::parse_file(
1956 "fixtures/0030-primitive-numeric-action-type-diagnostics/input/InvalidTypedNumericActions.tsx",
1957 source,
1958 );
1959 let graph = build_component_graph_for_module(&parsed);
1960 let folded = ConstantFoldingPass.transform(
1961 &build_application_semantic_model_from_component_graph(&graph),
1962 );
1963 let diagnostics = folded
1964 .diagnostics
1965 .iter()
1966 .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1967 .collect::<Vec<_>>();
1968
1969 assert_eq!(diagnostics.len(), 4);
1970 assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1019"));
1971
1972 let provenance = folded.diagnostics[0]
1973 .provenance
1974 .as_ref()
1975 .expect("numeric action diagnostic provenance");
1976 assert_eq!(
1977 provenance.path,
1978 Path::new(
1979 "fixtures/0030-primitive-numeric-action-type-diagnostics/input/InvalidTypedNumericActions.tsx"
1980 )
1981 );
1982 assert_eq!(provenance.span.line, 10);
1983 assert_eq!(provenance.span.column, 5);
1984 }
1985
1986 #[test]
1987 fn reports_compound_numeric_action_target_and_operand_mismatches() {
1988 let source = include_str!(
1989 "../../../fixtures/0031-primitive-compound-action-type-diagnostics/input/InvalidTypedCompoundActions.tsx"
1990 );
1991 let parsed = presolve_parser::parse_file(
1992 "fixtures/0031-primitive-compound-action-type-diagnostics/input/InvalidTypedCompoundActions.tsx",
1993 source,
1994 );
1995 let graph = build_component_graph_for_module(&parsed);
1996 let folded = ConstantFoldingPass.transform(
1997 &build_application_semantic_model_from_component_graph(&graph),
1998 );
1999 let diagnostics = folded
2000 .diagnostics
2001 .iter()
2002 .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
2003 .collect::<Vec<_>>();
2004
2005 assert_eq!(diagnostics.len(), 7);
2006 assert_eq!(
2007 diagnostics
2008 .iter()
2009 .filter(|(code, _)| *code == "PSC1020")
2010 .count(),
2011 3
2012 );
2013 assert_eq!(
2014 diagnostics
2015 .iter()
2016 .filter(|(code, _)| *code == "PSC1021")
2017 .count(),
2018 4
2019 );
2020
2021 let provenance = folded.diagnostics[0]
2022 .provenance
2023 .as_ref()
2024 .expect("compound action diagnostic provenance");
2025 assert_eq!(
2026 provenance.path,
2027 Path::new(
2028 "fixtures/0031-primitive-compound-action-type-diagnostics/input/InvalidTypedCompoundActions.tsx"
2029 )
2030 );
2031 assert_eq!(provenance.span.line, 9);
2032 assert_eq!(provenance.span.column, 5);
2033 }
2034
2035 #[test]
2036 fn assembles_application_semantic_model_from_multiple_files() {
2037 let unit = CompilationUnit::parse_sources([
2038 (
2039 "src/Zeta.tsx",
2040 r#"
2041@component("x-zeta")
2042class Zeta extends Component {
2043 render() {
2044 return <div>Zeta</div>;
2045 }
2046}
2047"#,
2048 ),
2049 (
2050 "src/Alpha.tsx",
2051 r#"
2052@component("x-alpha")
2053class Alpha extends Component {
2054 render() {
2055 return <div>Alpha</div>;
2056 }
2057}
2058"#,
2059 ),
2060 ]);
2061
2062 let asm = build_application_semantic_model_for_unit(&unit);
2063
2064 assert_eq!(
2065 unit.files()
2066 .iter()
2067 .map(|file| file.path.to_string_lossy().into_owned())
2068 .collect::<Vec<_>>(),
2069 vec!["src/Alpha.tsx", "src/Zeta.tsx"]
2070 );
2071 assert_eq!(
2072 asm.components
2073 .iter()
2074 .map(|component| component.id.as_str())
2075 .collect::<Vec<_>>(),
2076 vec![
2077 "module:src/Alpha.tsx/component:x-alpha",
2078 "module:src/Zeta.tsx/component:x-zeta"
2079 ]
2080 );
2081 assert!(asm.diagnostics.is_empty());
2082 assert!(validate_application_semantic_model(&asm).is_empty());
2083 }
2084
2085 #[test]
2086 fn component_graph_reports_semantic_errors() {
2087 let source =
2088 include_str!("../../../fixtures/0003-semantic-errors/input/BrokenSemantics.tsx");
2089
2090 let parsed = presolve_parser::parse_file(
2091 "fixtures/0003-semantic-errors/input/BrokenSemantics.tsx",
2092 source,
2093 );
2094
2095 let graph = build_component_graph(&parsed);
2096
2097 let codes = graph
2098 .diagnostics
2099 .iter()
2100 .map(|diagnostic| diagnostic.code.as_str())
2101 .collect::<Vec<_>>();
2102
2103 assert!(codes.contains(&"PSC1001"));
2104 assert!(codes.contains(&"PSC1003"));
2105 assert!(codes.contains(&"PSC1004"));
2106 assert!(graph.references.is_empty());
2107 }
2108
2109 #[test]
2110 fn component_graph_reports_unsupported_event_errors() {
2111 let source = r#"
2112@component("x-counter")
2113class Counter extends Component {
2114 count = state(0);
2115
2116 increment() {
2117 this.count++;
2118 }
2119
2120 render() {
2121 return <button onMouseover={() => this.increment()}>Count: {this.count}</button>;
2122 }
2123}
2124"#;
2125
2126 let parsed = presolve_parser::parse_file("UnsupportedEvent.tsx", source);
2127
2128 let graph = build_component_graph(&parsed);
2129
2130 assert!(graph
2131 .diagnostics
2132 .iter()
2133 .any(|diagnostic| diagnostic.code == "PSC1005"));
2134 }
2135
2136 #[test]
2137 fn component_graph_validates_static_action_parameter_bindings() {
2138 let source = r#"
2139@component("x-parameterized")
2140class Parameterized extends Component {
2141 label = state("Ready");
2142
2143 @action() setLabel(value: string) {
2144 this.label = value;
2145 }
2146
2147 render() {
2148 return <button onClick={() => this.setLabel(1)}>{this.label}</button>;
2149 }
2150}
2151"#;
2152
2153 let parsed = presolve_parser::parse_file("Parameterized.tsx", source);
2154 let graph = build_component_graph(&parsed);
2155 let codes = graph
2156 .diagnostics
2157 .iter()
2158 .map(|diagnostic| diagnostic.code.as_str())
2159 .collect::<Vec<_>>();
2160
2161 assert!(codes.contains(&"PSC1043"));
2162 assert!(!codes.contains(&"PSC1041"));
2163 assert!(!codes.contains(&"PSC1042"));
2164 }
2165
2166 #[test]
2167 fn component_graph_rejects_unbound_action_parameters() {
2168 let source = r#"
2169@component("x-parameterized")
2170class Parameterized extends Component {
2171 label = state("Ready");
2172
2173 @action() setLabel(value: string) {
2174 this.label = value;
2175 }
2176
2177 render() {
2178 return <button onClick={this.setLabel}>{this.label}</button>;
2179 }
2180}
2181"#;
2182
2183 let parsed = presolve_parser::parse_file("UnboundParameterized.tsx", source);
2184 let graph = build_component_graph(&parsed);
2185
2186 assert!(graph
2187 .diagnostics
2188 .iter()
2189 .any(|diagnostic| diagnostic.code == "PSC1042"));
2190 }
2191
2192 #[test]
2193 fn component_graph_requires_action_decorator_for_parameter_state_assignment() {
2194 let source = r#"
2195@component("x-parameterized")
2196class Parameterized extends Component {
2197 label = state("Ready");
2198
2199 setLabel(value: string) {
2200 this.label = value;
2201 }
2202
2203 render() {
2204 return <button onClick={() => this.setLabel("Locked")}>{this.label}</button>;
2205 }
2206}
2207"#;
2208
2209 let parsed = presolve_parser::parse_file("UndecoratedParameterized.tsx", source);
2210 let graph = build_component_graph(&parsed);
2211
2212 assert!(graph.diagnostics.iter().any(|diagnostic| {
2213 diagnostic.code == "PSC1041" && diagnostic.message.contains("requires @action()")
2214 }));
2215 }
2216
2217 #[test]
2218 fn component_graph_rejects_action_parameter_state_type_mismatch() {
2219 let source = r#"
2220@component("x-parameterized")
2221class Parameterized extends Component {
2222 count = state(0);
2223
2224 @action() setCount(value: string) {
2225 this.count = value;
2226 }
2227
2228 render() {
2229 return <button onClick={() => this.setCount("Locked")}>{this.count}</button>;
2230 }
2231}
2232"#;
2233
2234 let parsed = presolve_parser::parse_file("MismatchedParameterized.tsx", source);
2235 let graph = build_component_graph(&parsed);
2236
2237 assert!(graph
2238 .diagnostics
2239 .iter()
2240 .any(|diagnostic| diagnostic.code == "PSC1044"));
2241 }
2242
2243 #[test]
2244 fn component_graph_lowers_serializable_action_local_to_state_assignment() {
2245 let source = r#"
2246@component("x-action-local")
2247class ActionLocal extends Component {
2248 label = state("Ready");
2249
2250 @action() lock() {
2251 const next = "Locked";
2252 this.label = next;
2253 }
2254
2255 render() {
2256 return <button onClick={this.lock}>{this.label}</button>;
2257 }
2258}
2259"#;
2260
2261 let parsed = presolve_parser::parse_file("ActionLocal.tsx", source);
2262 let graph = build_component_graph(&parsed);
2263
2264 assert!(graph.diagnostics.is_empty(), "{:?}", graph.diagnostics);
2265 assert!(matches!(
2266 graph.components[0].actions[0].operation,
2267 StateOperation::Assign(SerializableValue::String(ref value)) if value == "Locked"
2268 ));
2269 }
2270
2271 #[test]
2272 fn component_graph_lowers_structured_serializable_action_local_to_state_assignment() {
2273 let source = r#"
2274@component("x-action-local-record")
2275class ActionLocalRecord extends Component {
2276 profile = state({ name: "Ready", roles: ["reader"] });
2277
2278 @action() promote() {
2279 const next = { name: "Locked", roles: ["writer", "admin"] };
2280 this.profile = next;
2281 }
2282
2283 render() {
2284 return <button onClick={this.promote}>Promote</button>;
2285 }
2286}
2287"#;
2288
2289 let parsed = presolve_parser::parse_file("ActionLocalRecord.tsx", source);
2290 let graph = build_component_graph(&parsed);
2291
2292 assert!(graph.diagnostics.is_empty(), "{:?}", graph.diagnostics);
2293 assert!(matches!(
2294 graph.components[0].actions[0].operation,
2295 StateOperation::Assign(SerializableValue::Object(_))
2296 ));
2297 }
2298
2299 #[test]
2300 fn component_graph_retains_resource_declaration_facts_before_package_resolution() {
2301 let parsed = presolve_parser::parse_file(
2302 "ResourceFact.tsx",
2303 r#"
2304@component("x-resource-fact")
2305class ResourceFact extends Component {
2306 @resource("profile") profile!: string;
2307 render() { return <div>Profile</div>; }
2308}
2309"#,
2310 );
2311 let graph = build_component_graph(&parsed);
2312 let facts = &graph.components[0].resource_declaration_candidates;
2313 assert_eq!(facts.len(), 1);
2314 assert_eq!(facts[0].field, "profile");
2315 assert!(facts[0].decorator_invoked);
2316 assert_eq!(facts[0].decorator_argument_count, 1);
2317 assert_eq!(facts[0].endpoint_designator.as_deref(), Some("profile"));
2318 assert!(graph.diagnostics.is_empty(), "{:?}", graph.diagnostics);
2319 }
2320
2321 #[test]
2322 fn component_graph_retains_and_validates_opaque_action_declarations() {
2323 let parsed = presolve_parser::parse_file(
2324 "OpaqueActions.tsx",
2325 r#"
2326@component("x-opaque-actions")
2327class OpaqueActions extends Component {
2328 @action() @opaque("@acme/analytics", "trackPurchase")
2329 track(): void {}
2330
2331 @action() @opaque("@acme/analytics")
2332 missingExport(): void {}
2333
2334 @opaque("@acme/analytics", "trackPurchase")
2335 missingAction(): void {}
2336
2337 @action() @opaque("@acme/analytics", "trackPurchase")
2338 writesState(): void { this.count++; }
2339
2340 count = state(0);
2341
2342 render() { return <button onClick={this.track}>Buy</button>; }
2343}
2344"#,
2345 );
2346
2347 let graph = build_component_graph(&parsed);
2348 let component = &graph.components[0];
2349 assert_eq!(component.opaque_action_facts.len(), 4);
2350
2351 let track = component
2352 .opaque_action_facts
2353 .iter()
2354 .find(|fact| fact.method_name == "track")
2355 .expect("retained track declaration");
2356 assert_eq!(
2357 track.id.as_str(),
2358 "component:x-opaque-actions/opaque-activation:track"
2359 );
2360 assert_eq!(track.package.as_deref(), Some("@acme/analytics"));
2361 assert_eq!(track.export.as_deref(), Some("trackPurchase"));
2362 assert!(track.is_action);
2363 assert!(track.action_invoked);
2364 assert!(!track.has_body_effects);
2365
2366 assert_eq!(
2367 graph
2368 .diagnostics
2369 .iter()
2370 .filter(|diagnostic| diagnostic.code == "PSC1130")
2371 .count(),
2372 3,
2373 "{:?}",
2374 graph.diagnostics
2375 );
2376 }
2377
2378 #[test]
2379 fn resolves_opaque_action_through_an_integrity_checked_terminal_package_contract() {
2380 let unit = CompilationUnit::parse_sources([(
2381 "src/Checkout.tsx",
2382 r#"
2383import { trackPurchase } from "@acme/analytics";
2384
2385@component("x-checkout")
2386class Checkout extends Component {
2387 @action() @opaque("@acme/analytics", "trackPurchase")
2388 track(): void {}
2389
2390 render() { return <button onClick={this.track}>Buy</button>; }
2391}
2392"#,
2393 )]);
2394 let contract = parse_semantic_package_contract(
2395 r#"{"schema_version":1,"package":"@acme/analytics","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"trackPurchase":{"kind":"opaque","type_signature":"() -> void","runtime_module":"dist/track.js","resume_policy":"cold_fallback","opaque_terminal":{"execution_boundary":"client","resume":"cold_fallback"}}}}"#,
2396 )
2397 .expect("opaque terminal contract");
2398 let mut packages = SemanticPackageResolutionTable::default();
2399 packages
2400 .insert("@acme/analytics".into(), contract)
2401 .expect("unique contract");
2402
2403 let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2404 assert_eq!(model.opaque_action_resolutions.len(), 1);
2405 let resolution = &model.opaque_action_resolutions[0];
2406 assert_eq!(
2407 resolution.activation.as_str(),
2408 "module:src/Checkout.tsx/component:x-checkout/opaque-activation:track"
2409 );
2410 let OpaqueActionResolutionOutcome::Resolved(binding) = &resolution.outcome else {
2411 panic!(
2412 "expected resolved opaque terminal: {:?}",
2413 resolution.outcome
2414 );
2415 };
2416 assert_eq!(binding.package, "@acme/analytics");
2417 assert_eq!(binding.version, "1.2.3");
2418 assert_eq!(binding.export, "trackPurchase");
2419 assert_eq!(binding.runtime_module, "dist/track.js");
2420 assert!(model
2421 .diagnostics
2422 .iter()
2423 .all(|diagnostic| diagnostic.code != "PSC1130" && diagnostic.code != "PSC1131"));
2424 }
2425
2426 #[test]
2427 fn opaque_action_rejects_a_nonopaque_package_export() {
2428 let unit = CompilationUnit::parse_sources([(
2429 "src/Checkout.tsx",
2430 r#"
2431import { format } from "date-kit";
2432
2433@component("x-checkout")
2434class Checkout extends Component {
2435 @action() @opaque("date-kit", "format")
2436 track(): void {}
2437
2438 render() { return <button onClick={this.track}>Buy</button>; }
2439}
2440"#,
2441 )]);
2442 let contract = parse_semantic_package_contract(
2443 r#"{"schema_version":1,"package":"date-kit","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"format":{"kind":"pure","type_signature":"(Date) -> string","runtime_module":"dist/format.js","resume_policy":"input_only"}}}"#,
2444 )
2445 .expect("pure contract");
2446 let mut packages = SemanticPackageResolutionTable::default();
2447 packages.insert("date-kit".into(), contract).unwrap();
2448
2449 let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2450 assert!(matches!(
2451 model.opaque_action_resolutions[0].outcome,
2452 OpaqueActionResolutionOutcome::NonOpaqueBinding {
2453 kind: SemanticPackageKind::Pure,
2454 ..
2455 }
2456 ));
2457 assert!(model
2458 .diagnostics
2459 .iter()
2460 .any(|diagnostic| diagnostic.code == "PSC1131"));
2461 }
2462
2463 #[test]
2464 fn resolves_resource_source_designator_through_integrity_checked_package_contract() {
2465 let unit = CompilationUnit::parse_sources([(
2466 "src/Profile.tsx",
2467 r#"
2468import { loadProfile } from "profile-service";
2469
2470@component("x-profile")
2471class Profile extends Component {
2472 @resource("loadProfile") profile!: Resource<string, string>;
2473 render() { return <div>Profile</div>; }
2474}
2475"#,
2476 )]);
2477 let contract = parse_semantic_package_contract(
2478 r#"{"schema_version":1,"package":"profile-service","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"loadProfile":{"kind":"resource","type_signature":"(ProfileKey) -> Resource<Profile, ProfileError>","runtime_module":"dist/load-profile.js","resume_policy":"snapshot","resource_endpoint":{"execution_boundary":"shared","cancellation":"abort","resume":"snapshot"}}}}"#,
2479 )
2480 .expect("resource contract");
2481 let mut packages = SemanticPackageResolutionTable::default();
2482 packages
2483 .insert("profile-service".into(), contract)
2484 .expect("unique package contract");
2485
2486 let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2487 assert_eq!(model.resource_endpoint_resolutions.len(), 1);
2488 let ResourceEndpointResolutionOutcome::Resolved(endpoint) =
2489 &model.resource_endpoint_resolutions[0].outcome
2490 else {
2491 panic!("expected resolved resource endpoint");
2492 };
2493 assert_eq!(endpoint.local_name, "loadProfile");
2494 assert_eq!(endpoint.package, "profile-service");
2495 assert_eq!(endpoint.version, "1.2.3");
2496 assert_eq!(endpoint.export, "loadProfile");
2497 assert_eq!(
2498 endpoint.endpoint.execution_boundary,
2499 SemanticPackageResourceExecutionBoundary::Shared
2500 );
2501 assert_eq!(model.resource_declarations.len(), 1);
2502 let declaration = model
2503 .resource_declarations
2504 .values()
2505 .next()
2506 .expect("resource declaration");
2507 assert_eq!(declaration.name, "profile");
2508 assert_eq!(declaration.data_type, SemanticType::String);
2509 assert_eq!(declaration.error_type, SemanticType::String);
2510 assert_eq!(
2511 declaration.execution_boundary,
2512 ResourceExecutionBoundary::Shared
2513 );
2514 assert_eq!(model.resource_activations.len(), 1);
2515 assert!(model.resource_activations.values().all(|activation| {
2516 activation.declaration == declaration.id
2517 && activation.state == ResourceLifecycleState::Idle
2518 }));
2519 assert!(
2520 model
2521 .diagnostics
2522 .iter()
2523 .all(|diagnostic| { diagnostic.code != "PSC1046" && diagnostic.code != "PSC1128" }),
2524 "{:?}",
2525 model.diagnostics
2526 );
2527 assert!(matches!(
2528 model
2529 .semantic_types
2530 .assignments
2531 .get(declaration.id.as_semantic_id())
2532 .map(|assignment| &assignment.semantic_type),
2533 Some(SemanticType::Resource(resource))
2534 if resource.data.as_ref() == &SemanticType::String
2535 && resource.error.as_ref() == &SemanticType::String
2536 ));
2537 }
2538
2539 #[test]
2540 fn lowers_direct_computed_resource_data_projection_with_exact_declaration_identity() {
2541 let unit = CompilationUnit::parse_sources([(
2542 "src/Profile.tsx",
2543 r#"
2544import { loadProfile } from "profile-service";
2545
2546@component("x-profile")
2547class Profile extends Component {
2548 @resource("loadProfile") profile!: Resource<string, string>;
2549 @computed() get profileName(): string | null { return this.profile.data; }
2550 render() { return <div>{this.profileName}</div>; }
2551}
2552"#,
2553 )]);
2554 let contract = parse_semantic_package_contract(
2555 r#"{"schema_version":1,"package":"profile-service","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"loadProfile":{"kind":"resource","type_signature":"(ProfileKey) -> Resource<Profile, ProfileError>","runtime_module":"dist/load-profile.js","resume_policy":"snapshot","resource_endpoint":{"execution_boundary":"shared","cancellation":"abort","resume":"snapshot"}}}}"#,
2556 )
2557 .expect("resource contract");
2558 let mut packages = SemanticPackageResolutionTable::default();
2559 packages
2560 .insert("profile-service".into(), contract)
2561 .expect("unique package contract");
2562
2563 let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2564 assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
2565 let declaration = model
2566 .resource_declarations
2567 .values()
2568 .next()
2569 .expect("resource declaration");
2570 let computed = model.components[0].id.computed("profileName");
2571 assert!(model.references.iter().any(|reference| {
2572 reference.kind == SemanticReferenceKind::ComputedResource
2573 && reference.source == computed
2574 && reference.target == *declaration.id.as_semantic_id()
2575 }));
2576 assert_eq!(
2577 model.semantic_types.computed_values[&computed].semantic_type,
2578 SemanticType::Union(vec![SemanticType::Null, SemanticType::String])
2579 );
2580
2581 let ir = lower_components_to_ir(&model);
2582 assert!(ir
2583 .modules
2584 .iter()
2585 .flat_map(|module| &module.functions)
2586 .any(|function| {
2587 function
2588 .blocks
2589 .iter()
2590 .flat_map(|block| &block.instructions)
2591 .any(|instruction| {
2592 matches!(
2593 &instruction.kind,
2594 IrInstructionKind::LoadResource { declaration: loaded }
2595 if loaded == declaration.id.as_semantic_id()
2596 )
2597 })
2598 }));
2599 let artifact = build_runtime_computed_artifact(&model, &ir);
2600 assert!(artifact.resource_invalidations.iter().any(|invalidation| {
2601 invalidation.declaration == declaration.id.as_str()
2602 && invalidation.dependents == vec![computed.to_string()]
2603 }));
2604 assert!(artifact.evaluations.iter().any(|evaluation| {
2605 evaluation.computed == computed.as_str()
2606 && evaluation.program.instructions.iter().any(|instruction| {
2607 matches!(
2608 instruction,
2609 RuntimeComputedArtifactInstruction::LoadResource { declaration: loaded, .. }
2610 if loaded == declaration.id.as_str()
2611 )
2612 })
2613 }));
2614
2615 let chained_unit = CompilationUnit::parse_sources([(
2616 "src/ChainedProfile.tsx",
2617 r#"
2618import { loadProfile } from "profile-service";
2619
2620@component("x-chained-profile")
2621class ChainedProfile extends Component {
2622 @resource("loadProfile") profile!: Resource<string, string>;
2623 @computed() get invalid() { return this.profile.data.length; }
2624 render() { return <div>Profile</div>; }
2625}
2626"#,
2627 )]);
2628 let chained =
2629 build_application_semantic_model_for_unit_with_packages(&chained_unit, &packages);
2630 assert!(chained.diagnostics.iter().any(|diagnostic| {
2631 diagnostic.code == ComputedDiagnosticCode::UnsupportedBody.as_str()
2632 && diagnostic.message.contains("may only directly project")
2633 }));
2634 }
2635
2636 #[test]
2637 fn component_graph_reports_duplicate_event_errors() {
2638 let parsed = presolve_parser::ParsedFile {
2639 path: "DuplicateEvent.tsx".into(),
2640 syntax: presolve_parser::ParsedSourceAst {
2641 source: String::new(),
2642 estree_json: "{}".to_owned(),
2643 span: test_span(),
2644 },
2645 diagnostics: Vec::new(),
2646 imports: Vec::new(),
2647 exports: Vec::new(),
2648 call_expressions: Vec::new(),
2649 type_aliases: Vec::new(),
2650 local_type_bindings: Vec::new(),
2651 local_value_bindings: Vec::new(),
2652 classes: vec![presolve_parser::ParsedClass {
2653 name: "DuplicateEvent".to_string(),
2654 span: test_span(),
2655 heritage: None,
2656 decorators: vec![presolve_parser::ParsedDecorator {
2657 name: "component".to_string(),
2658 is_invoked: true,
2659 arguments: vec![Some("x-duplicate-event".to_string())],
2660 argument: Some("x-duplicate-event".to_string()),
2661 argument_count: 1,
2662 argument_spans: vec![test_span()],
2663 static_member_argument: None,
2664 this_member_argument: None,
2665 validation_rule_expression: None,
2666 span: test_span(),
2667 }],
2668 properties: Vec::new(),
2669 methods: vec![presolve_parser::ParsedMethod {
2670 name: "render".to_string(),
2671 span: test_span(),
2672 decorators: Vec::new(),
2673 is_getter: false,
2674 is_setter: false,
2675 is_async: false,
2676 is_static: false,
2677 jsx_roots: vec![presolve_parser::ParsedJsxNode::Element(
2678 presolve_parser::ParsedJsxElement {
2679 name: "button".to_string(),
2680 name_span: test_span(),
2681 span: test_span(),
2682 attributes: Vec::new(),
2683 event_handlers: vec![
2684 presolve_parser::ParsedEventHandler {
2685 event: "click".to_string(),
2686 handler: "this.render".to_string(),
2687 arguments: Vec::new(),
2688 span: test_span(),
2689 },
2690 presolve_parser::ParsedEventHandler {
2691 event: "click".to_string(),
2692 handler: "this.render".to_string(),
2693 arguments: Vec::new(),
2694 span: test_span(),
2695 },
2696 ],
2697 children: Vec::new(),
2698 },
2699 )],
2700 bindings: Vec::new(),
2701 state_updates: Vec::new(),
2702 local_variables: Vec::new(),
2703 parameters: Vec::new(),
2704 return_type_annotation: None,
2705 return_values: Vec::new(),
2706 computed_expression: None,
2707 effect_body: None,
2708 calls: Vec::new(),
2709 }],
2710 }],
2711 };
2712
2713 let graph = build_component_graph(&parsed);
2714
2715 assert!(graph
2716 .diagnostics
2717 .iter()
2718 .any(|diagnostic| diagnostic.code == "PSC1006"));
2719 }
2720
2721 fn test_span() -> presolve_parser::SourceSpan {
2722 presolve_parser::SourceSpan {
2723 start: 0,
2724 end: 0,
2725 line: 1,
2726 column: 1,
2727 }
2728 }
2729
2730 #[test]
2731 fn builds_increment_action_from_parsed_method_update() {
2732 let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
2733
2734 let parsed =
2735 presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
2736
2737 let graph = build_component_graph(&parsed);
2738 let component = graph.components.first().expect("expected component");
2739
2740 assert_eq!(
2741 component.actions,
2742 vec![ComponentAction {
2743 id: SemanticId::component(Some("x-nested-counter"), "NestedCounter")
2744 .action("increment", 0),
2745 owner: SemanticOwner::entity(
2746 SemanticId::component(Some("x-nested-counter"), "NestedCounter")
2747 .method("increment"),
2748 ),
2749 method: "increment".to_string(),
2750 operation: StateOperation::Increment,
2751 field: "count".to_string(),
2752 }]
2753 );
2754 }
2755
2756 #[test]
2757 fn builds_decrement_action_from_parsed_method_update() {
2758 let source =
2759 include_str!("../../../fixtures/0009-decrement-counter/input/DecrementCounter.tsx");
2760
2761 let parsed = presolve_parser::parse_file(
2762 "fixtures/0009-decrement-counter/input/DecrementCounter.tsx",
2763 source,
2764 );
2765
2766 let graph = build_component_graph(&parsed);
2767 let component = graph.components.first().expect("expected component");
2768
2769 assert_eq!(
2770 component.actions,
2771 vec![ComponentAction {
2772 id: SemanticId::component(Some("x-decrement-counter"), "DecrementCounter")
2773 .action("decrement", 0),
2774 owner: SemanticOwner::entity(
2775 SemanticId::component(Some("x-decrement-counter"), "DecrementCounter")
2776 .method("decrement"),
2777 ),
2778 method: "decrement".to_string(),
2779 operation: StateOperation::Decrement,
2780 field: "count".to_string(),
2781 }]
2782 );
2783 }
2784
2785 #[test]
2786 fn builds_add_and_subtract_assign_actions_from_parsed_method_updates() {
2787 let source =
2788 include_str!("../../../fixtures/0010-add-subtract-assign/input/StepCounter.tsx");
2789
2790 let parsed = presolve_parser::parse_file(
2791 "fixtures/0010-add-subtract-assign/input/StepCounter.tsx",
2792 source,
2793 );
2794
2795 let graph = build_component_graph(&parsed);
2796 let component = graph.components.first().expect("expected component");
2797
2798 assert_eq!(
2799 component.actions,
2800 vec![
2801 ComponentAction {
2802 id: SemanticId::component(Some("x-step-counter"), "StepCounter")
2803 .action("addTwo", 0),
2804 owner: SemanticOwner::entity(
2805 SemanticId::component(Some("x-step-counter"), "StepCounter")
2806 .method("addTwo"),
2807 ),
2808 method: "addTwo".to_string(),
2809 operation: StateOperation::AddAssign(SerializableValue::Number(
2810 "2".to_string()
2811 )),
2812 field: "count".to_string(),
2813 },
2814 ComponentAction {
2815 id: SemanticId::component(Some("x-step-counter"), "StepCounter")
2816 .action("subtractThree", 0),
2817 owner: SemanticOwner::entity(
2818 SemanticId::component(Some("x-step-counter"), "StepCounter")
2819 .method("subtractThree"),
2820 ),
2821 method: "subtractThree".to_string(),
2822 operation: StateOperation::SubtractAssign(SerializableValue::Number(
2823 "3".to_string()
2824 )),
2825 field: "count".to_string(),
2826 }
2827 ]
2828 );
2829 }
2830
2831 #[test]
2832 fn builds_direct_assignment_action_from_parsed_method_update() {
2833 let source =
2834 include_str!("../../../fixtures/0011-direct-assignment/input/ResetCounter.tsx");
2835
2836 let parsed = presolve_parser::parse_file(
2837 "fixtures/0011-direct-assignment/input/ResetCounter.tsx",
2838 source,
2839 );
2840
2841 let graph = build_component_graph(&parsed);
2842 let component = graph.components.first().expect("expected component");
2843
2844 assert_eq!(
2845 component.actions,
2846 vec![ComponentAction {
2847 id: SemanticId::component(Some("x-reset-counter"), "ResetCounter")
2848 .action("reset", 0),
2849 owner: SemanticOwner::entity(
2850 SemanticId::component(Some("x-reset-counter"), "ResetCounter").method("reset"),
2851 ),
2852 method: "reset".to_string(),
2853 operation: StateOperation::Assign(SerializableValue::Number("0".to_string())),
2854 field: "count".to_string(),
2855 }]
2856 );
2857 }
2858
2859 #[test]
2860 fn builds_boolean_toggle_action_from_parsed_method_update() {
2861 let source = include_str!("../../../fixtures/0012-boolean-toggle/input/ToggleFlag.tsx");
2862
2863 let parsed = presolve_parser::parse_file(
2864 "fixtures/0012-boolean-toggle/input/ToggleFlag.tsx",
2865 source,
2866 );
2867
2868 let graph = build_component_graph(&parsed);
2869 let component = graph.components.first().expect("expected component");
2870
2871 assert_eq!(
2872 component.actions,
2873 vec![ComponentAction {
2874 id: SemanticId::component(Some("x-toggle-flag"), "ToggleFlag").action("toggle", 0),
2875 owner: SemanticOwner::entity(
2876 SemanticId::component(Some("x-toggle-flag"), "ToggleFlag").method("toggle"),
2877 ),
2878 method: "toggle".to_string(),
2879 operation: StateOperation::Toggle,
2880 field: "enabled".to_string(),
2881 }]
2882 );
2883 }
2884
2885 #[test]
2886 fn builds_multi_step_actions_from_parsed_method_updates_in_source_order() {
2887 let source =
2888 include_str!("../../../fixtures/0013-multi-step-action/input/BatchActionCounter.tsx");
2889
2890 let parsed = presolve_parser::parse_file(
2891 "fixtures/0013-multi-step-action/input/BatchActionCounter.tsx",
2892 source,
2893 );
2894
2895 let graph = build_component_graph(&parsed);
2896 let component = graph.components.first().expect("expected component");
2897
2898 assert_eq!(
2899 component.actions,
2900 vec![
2901 ComponentAction {
2902 id:
2903 SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2904 .action("apply", 0),
2905 owner:
2906 SemanticOwner::entity(
2907 SemanticId::component(
2908 Some("x-batch-action-counter"),
2909 "BatchActionCounter",
2910 )
2911 .method("apply"),
2912 ),
2913 method: "apply".to_string(),
2914 operation: StateOperation::AddAssign(SerializableValue::Number(
2915 "2".to_string()
2916 )),
2917 field: "count".to_string(),
2918 },
2919 ComponentAction {
2920 id:
2921 SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2922 .action("apply", 1),
2923 owner:
2924 SemanticOwner::entity(
2925 SemanticId::component(
2926 Some("x-batch-action-counter"),
2927 "BatchActionCounter",
2928 )
2929 .method("apply"),
2930 ),
2931 method: "apply".to_string(),
2932 operation: StateOperation::Decrement,
2933 field: "count".to_string(),
2934 },
2935 ComponentAction {
2936 id:
2937 SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2938 .action("apply", 2),
2939 owner:
2940 SemanticOwner::entity(
2941 SemanticId::component(
2942 Some("x-batch-action-counter"),
2943 "BatchActionCounter",
2944 )
2945 .method("apply"),
2946 ),
2947 method: "apply".to_string(),
2948 operation: StateOperation::Assign(SerializableValue::Number("8".to_string())),
2949 field: "count".to_string(),
2950 },
2951 ComponentAction {
2952 id:
2953 SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2954 .action("apply", 3),
2955 owner:
2956 SemanticOwner::entity(
2957 SemanticId::component(
2958 Some("x-batch-action-counter"),
2959 "BatchActionCounter",
2960 )
2961 .method("apply"),
2962 ),
2963 method: "apply".to_string(),
2964 operation: StateOperation::Increment,
2965 field: "count".to_string(),
2966 },
2967 ComponentAction {
2968 id:
2969 SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2970 .action("apply", 4),
2971 owner:
2972 SemanticOwner::entity(
2973 SemanticId::component(
2974 Some("x-batch-action-counter"),
2975 "BatchActionCounter",
2976 )
2977 .method("apply"),
2978 ),
2979 method: "apply".to_string(),
2980 operation: StateOperation::Toggle,
2981 field: "enabled".to_string(),
2982 }
2983 ]
2984 );
2985 }
2986
2987 #[test]
2988 fn generates_static_html_from_template_graph() {
2989 let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
2990
2991 let parsed =
2992 presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
2993
2994 let component_graph = build_component_graph(&parsed);
2995 let template_graph = build_template_graph(&component_graph);
2996 let html = generate_static_html(&template_graph);
2997
2998 assert_eq!(
2999 html,
3000 "<button data-presolve-node=\"n0\" data-presolve-on-click=\"this.increment\" data-presolve-bindings=\"this.count\">Count:<!-- presolve-binding:n1:this.count -->0</button>\n"
3001 );
3002 }
3003
3004 #[test]
3005 fn lowers_jsx_html_attribute_aliases_before_html_and_manifest_generation() {
3006 let source = r#"
3007@component("x-attribute-alias")
3008class AttributeAlias extends Component {
3009 target = state("profile-name");
3010 render() {
3011 return <label className={this.target} htmlFor={this.target}>Profile</label>;
3012 }
3013}
3014"#;
3015 let parsed = presolve_parser::parse_file("AttributeAlias.tsx", source);
3016 let component_graph = build_component_graph(&parsed);
3017 let template_graph = build_template_graph(&component_graph);
3018 let html = generate_static_html(&template_graph);
3019 let manifest = build_template_manifest(&component_graph, &template_graph);
3020 let model = build_application_semantic_model_from_component_graph(&component_graph);
3021 let runtime = build_runtime_component_artifact(&model, &model.component_ir_optimization);
3022
3023 assert!(html.contains("class=\"profile-name\""));
3024 assert!(html.contains("for=\"profile-name\""));
3025 assert!(!html.contains("className="));
3026 assert!(!html.contains("htmlFor="));
3027 assert_eq!(
3028 manifest.components[0]
3029 .template
3030 .nodes
3031 .iter()
3032 .filter_map(|node| match node {
3033 ManifestNode::Binding { attribute, .. } => attribute.as_deref(),
3034 _ => None,
3035 })
3036 .collect::<Vec<_>>(),
3037 vec!["class", "for"]
3038 );
3039 assert_eq!(
3040 runtime
3041 .ordinary_template_bindings
3042 .iter()
3043 .filter_map(|binding| binding.attribute_name.as_deref())
3044 .collect::<Vec<_>>(),
3045 vec!["class", "for"]
3046 );
3047 }
3048
3049 #[test]
3050 fn preserves_string_state_literals_in_template_outputs() {
3051 let source = include_str!("../../../fixtures/0006-string-state/input/StringGreeting.tsx");
3052
3053 let parsed = presolve_parser::parse_file(
3054 "fixtures/0006-string-state/input/StringGreeting.tsx",
3055 source,
3056 );
3057
3058 let component_graph = build_component_graph(&parsed);
3059 let component = component_graph
3060 .components
3061 .first()
3062 .expect("expected component");
3063
3064 assert_eq!(
3065 component.state_fields[0].initial_value,
3066 Some(SerializableValue::String("Austin & <Zero>".to_string()))
3067 );
3068
3069 let template_graph = build_template_graph(&component_graph);
3070 let html = generate_static_html(&template_graph);
3071
3072 assert_eq!(
3073 html,
3074 "<p data-presolve-node=\"n0\" data-presolve-bindings=\"this.name\">Name:<!-- presolve-binding:n1:this.name -->Austin & <Zero></p>\n"
3075 );
3076
3077 let manifest = build_template_manifest(&component_graph, &template_graph);
3078
3079 assert_eq!(
3080 manifest.components[0].template.nodes,
3081 vec![
3082 ManifestNode::Element {
3083 id: "n0".to_string(),
3084 tag: "p".to_string(),
3085 },
3086 ManifestNode::Binding {
3087 id: "n1".to_string(),
3088 expression: "this.name".to_string(),
3089 initial_value: Some(SerializableValue::String("Austin & <Zero>".to_string())),
3090 target: None,
3091 element: None,
3092 attribute: None,
3093 }
3094 ]
3095 );
3096
3097 let manifest_json = template_manifest_json(&manifest);
3098 let manifest_value: serde_json::Value =
3099 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
3100
3101 assert_eq!(
3102 manifest_value["components"][0]["template"]["nodes"][1]["initial_value"],
3103 serde_json::json!("Austin & <Zero>")
3104 );
3105 }
3106
3107 #[test]
3108 fn decodes_jsx_text_character_references_before_static_html_escaping() {
3109 let parsed = presolve_parser::parse_file(
3110 "CodeExample.tsx",
3111 r#"
3112@component("x-code-example")
3113class CodeExample extends Component {
3114 render() {
3115 return <pre><code><button>A & B{ok}</button></code></pre>;
3116 }
3117}
3118"#,
3119 );
3120
3121 let component_graph = build_component_graph(&parsed);
3122 assert!(component_graph.diagnostics.is_empty());
3123
3124 let html = generate_static_html(&build_template_graph(&component_graph));
3125
3126 assert!(html.contains("<button>A & B{ok}</button>"));
3127 assert!(!html.contains("&lt;button&gt;"));
3128 assert!(!html.contains("&#123;"));
3129 }
3130
3131 #[test]
3132 fn preserves_inline_jsx_text_boundaries_in_static_html() {
3133 let parsed = presolve_parser::parse_file(
3134 "CodeExample.tsx",
3135 r#"
3136@component("x-code-example")
3137class CodeExample extends Component {
3138 render() {
3139 return <pre><code><span>import { </span><span className="syntax-type">Component</span><span> } from </span><span>"presolve"</span>;</code></pre>;
3140 }
3141}
3142"#,
3143 );
3144
3145 let component_graph = build_component_graph(&parsed);
3146 assert!(component_graph.diagnostics.is_empty());
3147
3148 let html = generate_static_html(&build_template_graph(&component_graph));
3149
3150 assert!(html.contains("import { </span><span data-presolve-node"));
3151 assert!(html.contains("class=\"syntax-type\">Component</span><span"));
3152 assert!(html.contains("> } from </span><span"));
3153 assert!(html.contains(">\"presolve\"</span>;"));
3154 assert!(!html.contains("data-presolve-bindings"));
3155 }
3156
3157 #[test]
3158 fn preserves_static_jsx_attributes_in_template_outputs() {
3159 let source =
3160 include_str!("../../../fixtures/0014-static-attributes/input/StaticAttributePanel.tsx");
3161
3162 let parsed = presolve_parser::parse_file(
3163 "fixtures/0014-static-attributes/input/StaticAttributePanel.tsx",
3164 source,
3165 );
3166
3167 let component_graph = build_component_graph(&parsed);
3168 assert!(component_graph.diagnostics.is_empty());
3169
3170 let template_graph = build_template_graph(&component_graph);
3171 let root = template_graph.templates[0]
3172 .root
3173 .as_ref()
3174 .expect("expected root");
3175
3176 assert_eq!(root.attributes.len(), 3);
3177 assert_eq!(root.attributes[0].name, "id");
3178 assert_eq!(
3179 root.attributes[0].value,
3180 AttributeValue::Static("panel-root".to_string())
3181 );
3182 assert_eq!(root.attributes[1].name, "aria-label");
3183 assert_eq!(
3184 root.attributes[1].value,
3185 AttributeValue::Static("Status \"Panel\"".to_string())
3186 );
3187 assert_eq!(root.attributes[2].name, "hidden");
3188 assert_eq!(root.attributes[2].value, AttributeValue::Boolean);
3189
3190 let TemplateChild::Element(button) = &root.children[0] else {
3191 panic!("expected button child");
3192 };
3193
3194 assert_eq!(button.attributes.len(), 4);
3195 assert_eq!(button.attributes[0].name, "type");
3196 assert_eq!(
3197 button.attributes[0].value,
3198 AttributeValue::Static("button".to_string())
3199 );
3200 assert_eq!(button.attributes[1].name, "data-mode");
3201 assert_eq!(
3202 button.attributes[1].value,
3203 AttributeValue::Static("safe & sound".to_string())
3204 );
3205 assert_eq!(button.attributes[2].name, "title");
3206 assert_eq!(
3207 button.attributes[2].value,
3208 AttributeValue::Static("Use <carefully>".to_string())
3209 );
3210 assert_eq!(button.attributes[3].name, "data-presolve-bindings");
3211
3212 let html = generate_static_html(&template_graph);
3213
3214 assert_eq!(
3215 html,
3216 "<section data-presolve-node=\"n0\" id=\"panel-root\" aria-label=\"Status "Panel"\" hidden><button data-presolve-node=\"n1\" type=\"button\" data-mode=\"safe & sound\" title=\"Use <carefully>\" data-presolve-bindings=\"this.label\">Label:<!-- presolve-binding:n2:this.label -->Ready</button></section>\n"
3217 );
3218 }
3219
3220 #[test]
3221 fn reports_static_attribute_semantic_errors() {
3222 let source = r#"
3223@component("x-bad-attrs")
3224class BadAttrs extends Component {
3225 disabled = state(false);
3226
3227 render() {
3228 return <button type="button" type="submit" title={label} {...props}>Go</button>;
3229 }
3230}
3231"#;
3232
3233 let parsed = presolve_parser::parse_file("BadAttrs.tsx", source);
3234 let graph = build_component_graph(&parsed);
3235 let codes = graph
3236 .diagnostics
3237 .iter()
3238 .map(|diagnostic| diagnostic.code.as_str())
3239 .collect::<Vec<_>>();
3240
3241 assert!(codes.contains(&"PSC1007"));
3242 assert!(codes.contains(&"PSC1008"));
3243 assert!(codes.contains(&"PSC1009"));
3244 }
3245
3246 #[test]
3247 fn builds_dynamic_attribute_bindings_in_template_outputs() {
3248 let source = include_str!(
3249 "../../../fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx"
3250 );
3251
3252 let parsed = presolve_parser::parse_file(
3253 "fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx",
3254 source,
3255 );
3256
3257 let component_graph = build_component_graph(&parsed);
3258 assert!(component_graph.diagnostics.is_empty());
3259
3260 let template_graph = build_template_graph(&component_graph);
3261 let root = template_graph.templates[0]
3262 .root
3263 .as_ref()
3264 .expect("expected root");
3265
3266 assert_eq!(root.attributes[0].name, "disabled");
3267 assert_eq!(
3268 root.attributes[0].value,
3269 AttributeValue::Binding {
3270 id: TemplateNodeId("n1".to_string()),
3271 expression: "this.disabled".to_string(),
3272 initial_value: Some(SerializableValue::Boolean(false)),
3273 }
3274 );
3275 assert_eq!(root.attributes[1].name, "title");
3276 assert_eq!(
3277 root.attributes[1].value,
3278 AttributeValue::Binding {
3279 id: TemplateNodeId("n2".to_string()),
3280 expression: "this.label".to_string(),
3281 initial_value: Some(SerializableValue::String("Ready".to_string())),
3282 }
3283 );
3284
3285 let html = generate_static_html(&template_graph);
3286
3287 assert_eq!(
3288 html,
3289 "<button data-presolve-node=\"n0\" title=\"Ready\" data-presolve-on-click=\"this.lock\" data-presolve-bindings=\"this.label\">Status:<!-- presolve-binding:n3:this.label -->Ready</button>\n"
3290 );
3291 }
3292
3293 #[test]
3294 fn preserves_boolean_state_literals_in_template_outputs() {
3295 let source = include_str!("../../../fixtures/0007-boolean-state/input/BooleanFlags.tsx");
3296
3297 let parsed = presolve_parser::parse_file(
3298 "fixtures/0007-boolean-state/input/BooleanFlags.tsx",
3299 source,
3300 );
3301
3302 let component_graph = build_component_graph(&parsed);
3303 let component = component_graph
3304 .components
3305 .first()
3306 .expect("expected component");
3307
3308 assert_eq!(
3309 component.state_fields[0].initial_value,
3310 Some(SerializableValue::Boolean(true))
3311 );
3312 assert_eq!(
3313 component.state_fields[1].initial_value,
3314 Some(SerializableValue::Boolean(false))
3315 );
3316
3317 let template_graph = build_template_graph(&component_graph);
3318 let html = generate_static_html(&template_graph);
3319
3320 assert_eq!(
3321 html,
3322 "<section data-presolve-node=\"n0\"><p data-presolve-node=\"n1\" data-presolve-bindings=\"this.enabled\">Enabled:<!-- presolve-binding:n2:this.enabled -->true</p><p data-presolve-node=\"n3\" data-presolve-bindings=\"this.disabled\">Disabled:<!-- presolve-binding:n4:this.disabled -->false</p></section>\n"
3323 );
3324
3325 let manifest = build_template_manifest(&component_graph, &template_graph);
3326
3327 assert_eq!(
3328 manifest.components[0].template.nodes,
3329 vec![
3330 ManifestNode::Element {
3331 id: "n0".to_string(),
3332 tag: "section".to_string(),
3333 },
3334 ManifestNode::Element {
3335 id: "n1".to_string(),
3336 tag: "p".to_string(),
3337 },
3338 ManifestNode::Binding {
3339 id: "n2".to_string(),
3340 expression: "this.enabled".to_string(),
3341 initial_value: Some(SerializableValue::Boolean(true)),
3342 target: None,
3343 element: None,
3344 attribute: None,
3345 },
3346 ManifestNode::Element {
3347 id: "n3".to_string(),
3348 tag: "p".to_string(),
3349 },
3350 ManifestNode::Binding {
3351 id: "n4".to_string(),
3352 expression: "this.disabled".to_string(),
3353 initial_value: Some(SerializableValue::Boolean(false)),
3354 target: None,
3355 element: None,
3356 attribute: None,
3357 }
3358 ]
3359 );
3360
3361 let manifest_json = template_manifest_json(&manifest);
3362 let manifest_value: serde_json::Value =
3363 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
3364
3365 assert_eq!(
3366 manifest_value["components"][0]["template"]["nodes"][2]["initial_value"],
3367 serde_json::json!(true)
3368 );
3369 assert_eq!(
3370 manifest_value["components"][0]["template"]["nodes"][4]["initial_value"],
3371 serde_json::json!(false)
3372 );
3373 }
3374
3375 #[test]
3376 fn preserves_null_state_literals_in_template_outputs() {
3377 let source = include_str!("../../../fixtures/0008-null-state/input/NullSelection.tsx");
3378
3379 let parsed =
3380 presolve_parser::parse_file("fixtures/0008-null-state/input/NullSelection.tsx", source);
3381
3382 let component_graph = build_component_graph(&parsed);
3383 let component = component_graph
3384 .components
3385 .first()
3386 .expect("expected component");
3387
3388 assert_eq!(
3389 component.state_fields[0].initial_value,
3390 Some(SerializableValue::Null)
3391 );
3392
3393 let template_graph = build_template_graph(&component_graph);
3394 let html = generate_static_html(&template_graph);
3395
3396 assert_eq!(
3397 html,
3398 "<p data-presolve-node=\"n0\" data-presolve-bindings=\"this.selection\">Selection:<!-- presolve-binding:n1:this.selection --></p>\n"
3399 );
3400
3401 let manifest = build_template_manifest(&component_graph, &template_graph);
3402
3403 assert_eq!(
3404 manifest.components[0].template.nodes,
3405 vec![
3406 ManifestNode::Element {
3407 id: "n0".to_string(),
3408 tag: "p".to_string(),
3409 },
3410 ManifestNode::Binding {
3411 id: "n1".to_string(),
3412 expression: "this.selection".to_string(),
3413 initial_value: Some(SerializableValue::Null),
3414 target: None,
3415 element: None,
3416 attribute: None,
3417 }
3418 ]
3419 );
3420
3421 let manifest_json = template_manifest_json(&manifest);
3422 let manifest_value: serde_json::Value =
3423 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
3424
3425 assert_eq!(
3426 manifest_value["components"][0]["template"]["nodes"][1]["initial_value"],
3427 serde_json::Value::Null
3428 );
3429 }
3430
3431 #[test]
3432 fn builds_template_graph_from_component_graph() {
3433 let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
3434
3435 let parsed =
3436 presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
3437
3438 let component_graph = build_component_graph(&parsed);
3439 let template_graph = build_template_graph(&component_graph);
3440
3441 assert_eq!(template_graph.templates.len(), 1);
3442
3443 let template = &template_graph.templates[0];
3444 assert_eq!(template.component_name, "Counter");
3445 assert_eq!(template.id.as_str(), "component:x-counter/template:render");
3446 assert_eq!(
3447 template.provenance.path,
3448 Path::new("fixtures/0001-source-summary/input/Counter.tsx")
3449 );
3450 assert_eq!(template.provenance.span.line, 10);
3451 assert_eq!(
3452 template.owner,
3453 SemanticOwner::entity(SemanticId::component(Some("x-counter"), "Counter"))
3454 );
3455
3456 let root = template.root.as_ref().expect("expected template root");
3457
3458 assert_eq!(root.id.0, "n0");
3459 assert_eq!(root.tag_name, "button");
3460 assert_eq!(root.span.line, 12);
3461 assert_eq!(root.span.column, 7);
3462
3463 assert_eq!(root.attributes.len(), 2);
3464 assert_eq!(root.attributes[0].name, "data-presolve-on-click");
3465 assert_eq!(
3466 root.attributes[0].span.expect("expected event span").line,
3467 12
3468 );
3469 assert_eq!(
3470 root.attributes[0].span.expect("expected event span").column,
3471 15
3472 );
3473 assert_eq!(
3474 root.attributes[0].value,
3475 AttributeValue::EventHandler {
3476 event: "click".to_string(),
3477 handler: "this.increment".to_string(),
3478 arguments: Vec::new(),
3479 }
3480 );
3481
3482 assert_eq!(root.attributes[1].name, "data-presolve-bindings");
3483 assert_eq!(root.attributes[1].span, None);
3484 assert_eq!(
3485 root.attributes[1].value,
3486 AttributeValue::BindingList(vec!["this.count".to_string()])
3487 );
3488
3489 assert_eq!(root.children.len(), 2);
3490 let TemplateChild::Text { value, span } = &root.children[0] else {
3491 panic!("expected text child");
3492 };
3493 assert_eq!(value, "Count:");
3494 assert_eq!(span.line, 13);
3495 assert_eq!(span.column, 9);
3496
3497 let TemplateChild::Binding {
3498 id,
3499 expression,
3500 initial_value,
3501 span,
3502 } = &root.children[1]
3503 else {
3504 panic!("expected binding child");
3505 };
3506 assert_eq!(id.0, "n1");
3507 assert_eq!(expression, "this.count");
3508 assert_eq!(
3509 initial_value,
3510 &Some(SerializableValue::Number("0".to_string()))
3511 );
3512 assert_eq!(span.line, 13);
3513 assert_eq!(span.column, 16);
3514 }
3515
3516 #[test]
3517 fn semantic_ids_are_stable_when_component_declaration_order_changes() {
3518 let alpha = r#"
3519@component("x-alpha")
3520class Alpha extends Component {
3521 count = state(0);
3522
3523 increment() {
3524 this.count++;
3525 }
3526
3527 render() {
3528 return <p>{this.count}</p>;
3529 }
3530}
3531"#;
3532 let beta = r#"
3533@component("x-beta")
3534class Beta extends Component {
3535 enabled = state(false);
3536
3537 toggle() {
3538 this.enabled = !this.enabled;
3539 }
3540
3541 render() {
3542 return <p>{this.enabled}</p>;
3543 }
3544}
3545"#;
3546
3547 let ids_for = |source: &str| {
3548 let parsed = presolve_parser::parse_file("App.tsx", source);
3549 let component_graph = build_component_graph(&parsed);
3550 let template_graph = build_template_graph(&component_graph);
3551 let template_ids = template_graph
3552 .templates
3553 .iter()
3554 .map(|template| (template.component_name.as_str(), template.id.to_string()))
3555 .collect::<BTreeMap<_, _>>();
3556
3557 component_graph
3558 .components
3559 .iter()
3560 .map(|component| {
3561 let ids = component
3562 .state_fields
3563 .iter()
3564 .map(|field| field.id.to_string())
3565 .chain(component.methods.iter().map(|method| method.id.to_string()))
3566 .chain(component.actions.iter().map(|action| action.id.to_string()))
3567 .chain(std::iter::once(
3568 template_ids
3569 .get(component.class_name.as_str())
3570 .expect("expected component template")
3571 .clone(),
3572 ))
3573 .collect::<Vec<_>>();
3574
3575 (
3576 component.class_name.clone(),
3577 (component.id.to_string(), ids),
3578 )
3579 })
3580 .collect::<BTreeMap<_, _>>()
3581 };
3582
3583 assert_eq!(
3584 ids_for(&format!("{alpha}\n{beta}")),
3585 ids_for(&format!("{beta}\n{alpha}"))
3586 );
3587 }
3588
3589 #[test]
3590 fn carries_source_spans_into_nested_template_nodes() {
3591 let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
3592
3593 let parsed =
3594 presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
3595
3596 let component_graph = build_component_graph(&parsed);
3597 let template_graph = build_template_graph(&component_graph);
3598 let root = template_graph.templates[0]
3599 .root
3600 .as_ref()
3601 .expect("expected root");
3602
3603 assert_eq!(root.tag_name, "section");
3604 assert_eq!(root.span.line, 12);
3605 assert_eq!(root.span.column, 7);
3606
3607 let TemplateChild::Element(button) = &root.children[0] else {
3608 panic!("expected nested button");
3609 };
3610
3611 assert_eq!(button.tag_name, "button");
3612 assert_eq!(button.span.line, 13);
3613 assert_eq!(button.span.column, 9);
3614 assert_eq!(button.attributes[0].name, "data-presolve-on-click");
3615 assert_eq!(
3616 button.attributes[0].span.expect("expected event span").line,
3617 13
3618 );
3619 assert_eq!(
3620 button.attributes[0]
3621 .span
3622 .expect("expected event span")
3623 .column,
3624 17
3625 );
3626 assert_eq!(button.attributes[1].name, "data-presolve-bindings");
3627 assert_eq!(button.attributes[1].span, None);
3628
3629 let TemplateChild::Text { value, span } = &button.children[0] else {
3630 panic!("expected nested text");
3631 };
3632 assert_eq!(value, "Count: ");
3633 assert_eq!(span.line, 13);
3634 assert_eq!(span.column, 50);
3635
3636 let TemplateChild::Binding {
3637 expression, span, ..
3638 } = &button.children[1]
3639 else {
3640 panic!("expected nested binding");
3641 };
3642 assert_eq!(expression, "this.count");
3643 assert_eq!(span.line, 13);
3644 assert_eq!(span.column, 57);
3645 }
3646
3647 #[test]
3648 fn preserves_fragment_siblings_without_wrapper_elements() {
3649 let source = include_str!("../../../fixtures/0016-fragments/input/FragmentPanel.tsx");
3650
3651 let parsed =
3652 presolve_parser::parse_file("fixtures/0016-fragments/input/FragmentPanel.tsx", source);
3653
3654 let component_graph = build_component_graph(&parsed);
3655 assert!(component_graph.diagnostics.is_empty());
3656
3657 let template_graph = build_template_graph(&component_graph);
3658 let template = &template_graph.templates[0];
3659 let fragment = template
3660 .root_fragment
3661 .as_ref()
3662 .expect("expected fragment root");
3663
3664 assert!(template.root.is_none());
3665 assert_eq!(fragment.id.0, "n0");
3666 assert_eq!(fragment.children.len(), 2);
3667
3668 let TemplateChild::Element(heading) = &fragment.children[0] else {
3669 panic!("expected heading child");
3670 };
3671 assert_eq!(heading.id.0, "n1");
3672 assert_eq!(heading.tag_name, "h1");
3673
3674 let TemplateChild::Fragment(nested) = &fragment.children[1] else {
3675 panic!("expected nested fragment child");
3676 };
3677 assert_eq!(nested.id.0, "n2");
3678
3679 let TemplateChild::Element(paragraph) = &nested.children[0] else {
3680 panic!("expected paragraph child");
3681 };
3682 assert_eq!(paragraph.id.0, "n3");
3683 assert_eq!(paragraph.tag_name, "p");
3684
3685 let html = generate_static_html(&template_graph);
3686 assert_eq!(
3687 html,
3688 "<h1 data-presolve-node=\"n1\">Title</h1><p data-presolve-node=\"n3\" data-presolve-bindings=\"this.label\">Status: <!-- presolve-binding:n4:this.label -->Ready</p><span data-presolve-node=\"n5\">Done</span>\n"
3689 );
3690
3691 let manifest = build_template_manifest(&component_graph, &template_graph);
3692 assert_eq!(
3693 manifest.components[0].template.nodes,
3694 vec![
3695 ManifestNode::Element {
3696 id: "n1".to_string(),
3697 tag: "h1".to_string(),
3698 },
3699 ManifestNode::Element {
3700 id: "n3".to_string(),
3701 tag: "p".to_string(),
3702 },
3703 ManifestNode::Binding {
3704 id: "n4".to_string(),
3705 expression: "this.label".to_string(),
3706 initial_value: Some(SerializableValue::String("Ready".to_string())),
3707 target: None,
3708 element: None,
3709 attribute: None,
3710 },
3711 ManifestNode::Element {
3712 id: "n5".to_string(),
3713 tag: "span".to_string(),
3714 },
3715 ]
3716 );
3717 }
3718
3719 #[test]
3720 fn builds_conditional_template_boundaries_and_manifest_branch_html() {
3721 let source = include_str!(
3722 "../../../fixtures/0017-conditional-rendering/input/ConditionalStatus.tsx"
3723 );
3724
3725 let parsed = presolve_parser::parse_file(
3726 "fixtures/0017-conditional-rendering/input/ConditionalStatus.tsx",
3727 source,
3728 );
3729
3730 let component_graph = build_component_graph(&parsed);
3731 assert!(component_graph.diagnostics.is_empty());
3732
3733 let template_graph = build_template_graph(&component_graph);
3734 let root = template_graph.templates[0]
3735 .root
3736 .as_ref()
3737 .expect("expected root element");
3738
3739 assert_eq!(root.id.0, "n0");
3740 assert_eq!(root.attributes[1].name, "data-presolve-bindings");
3741
3742 let TemplateChild::Conditional(conditional) = &root.children[0] else {
3743 panic!("expected conditional child");
3744 };
3745
3746 assert_eq!(conditional.id.0, "n1");
3747 assert_eq!(conditional.start_id.0, "n2");
3748 assert_eq!(conditional.end_id.0, "n3");
3749 assert_eq!(conditional.condition, "this.enabled");
3750 assert_eq!(
3751 conditional.initial_value,
3752 Some(SerializableValue::Boolean(true))
3753 );
3754
3755 let TemplateChild::Element(when_true) = &conditional.when_true[0] else {
3756 panic!("expected true branch element");
3757 };
3758 assert_eq!(when_true.id.0, "n4");
3759 assert_eq!(when_true.tag_name, "span");
3760
3761 let TemplateChild::Element(when_false) = &conditional.when_false[0] else {
3762 panic!("expected false branch element");
3763 };
3764 assert_eq!(when_false.id.0, "n5");
3765 assert_eq!(when_false.tag_name, "span");
3766
3767 let html = generate_static_html(&template_graph);
3768 assert_eq!(
3769 html,
3770 "<button data-presolve-node=\"n0\" data-presolve-on-click=\"this.toggle\" data-presolve-bindings=\"this.enabled\"><!-- presolve-conditional-start:n2:this.enabled --><span data-presolve-node=\"n4\">On</span><!-- presolve-conditional-end:n3 --></button>\n"
3771 );
3772
3773 let manifest = build_template_manifest(&component_graph, &template_graph);
3774 assert_eq!(
3775 manifest.components[0].template.nodes,
3776 vec![
3777 ManifestNode::Element {
3778 id: "n0".to_string(),
3779 tag: "button".to_string(),
3780 },
3781 ManifestNode::Conditional {
3782 id: "n1".to_string(),
3783 start: "n2".to_string(),
3784 end: "n3".to_string(),
3785 condition: "this.enabled".to_string(),
3786 initial_value: Some(SerializableValue::Boolean(true)),
3787 when_true_html: "<span data-presolve-node=\"n4\">On</span>".to_string(),
3788 when_false_html: "<span data-presolve-node=\"n5\">Off</span>".to_string(),
3789 },
3790 ]
3791 );
3792 }
3793
3794 #[test]
3795 fn builds_logical_and_conditional_with_empty_false_branch() {
3796 let source = include_str!(
3797 "../../../fixtures/0018-logical-and-conditional/input/LogicalAndStatus.tsx"
3798 );
3799
3800 let parsed = presolve_parser::parse_file(
3801 "fixtures/0018-logical-and-conditional/input/LogicalAndStatus.tsx",
3802 source,
3803 );
3804
3805 let component_graph = build_component_graph(&parsed);
3806 assert!(component_graph.diagnostics.is_empty());
3807
3808 let template_graph = build_template_graph(&component_graph);
3809 let root = template_graph.templates[0]
3810 .root
3811 .as_ref()
3812 .expect("expected root element");
3813
3814 let TemplateChild::Conditional(conditional) = &root.children[0] else {
3815 panic!("expected conditional child");
3816 };
3817
3818 assert_eq!(conditional.when_true.len(), 1);
3819 assert!(conditional.when_false.is_empty());
3820
3821 let manifest = build_template_manifest(&component_graph, &template_graph);
3822 assert_eq!(
3823 manifest.components[0].template.nodes[1],
3824 ManifestNode::Conditional {
3825 id: "n1".to_string(),
3826 start: "n2".to_string(),
3827 end: "n3".to_string(),
3828 condition: "this.enabled".to_string(),
3829 initial_value: Some(SerializableValue::Boolean(true)),
3830 when_true_html: "<span data-presolve-node=\"n4\">On</span>".to_string(),
3831 when_false_html: String::new(),
3832 }
3833 );
3834 }
3835
3836 #[test]
3837 fn builds_empty_keyed_list_from_a_serializable_array() {
3838 let source =
3839 include_str!("../../../fixtures/0019-keyed-list-semantics/input/KeyedList.tsx");
3840
3841 let parsed = presolve_parser::parse_file(
3842 "fixtures/0019-keyed-list-semantics/input/KeyedList.tsx",
3843 source,
3844 );
3845
3846 let component_graph = build_component_graph(&parsed);
3847 assert!(component_graph.diagnostics.is_empty());
3848
3849 let template_graph = build_template_graph(&component_graph);
3850 let root = template_graph.templates[0]
3851 .root
3852 .as_ref()
3853 .expect("expected root element");
3854
3855 assert_eq!(root.id.0, "n0");
3856 assert_eq!(root.attributes[0].name, "data-presolve-bindings");
3857
3858 let TemplateChild::List(list) = &root.children[0] else {
3859 panic!("expected keyed list child");
3860 };
3861
3862 assert_eq!(list.id.0, "n1");
3863 assert_eq!(list.start_id.0, "n2");
3864 assert_eq!(list.end_id.0, "n3");
3865 assert_eq!(list.iterable, "this.items");
3866 assert_eq!(
3867 list.initial_value,
3868 Some(SerializableValue::Array(Vec::new()))
3869 );
3870 assert_eq!(list.item_variable, "item");
3871 assert_eq!(list.index_variable.as_deref(), Some("index"));
3872 assert_eq!(list.key_expression, "item.id");
3873
3874 let TemplateChild::Element(item_template) = &list.item_template[0] else {
3875 panic!("expected list item element");
3876 };
3877 assert_eq!(item_template.id.0, "n4");
3878 assert_eq!(item_template.tag_name, "li");
3879
3880 assert_eq!(
3881 generate_static_html(&template_graph),
3882 "<ul data-presolve-node=\"n0\" data-presolve-bindings=\"this.items\"><!-- presolve-list-start:n2:this.items --><!-- presolve-list-end:n3 --></ul>\n"
3883 );
3884
3885 let manifest = build_template_manifest(&component_graph, &template_graph);
3886 assert_eq!(
3887 manifest.components[0].template.nodes,
3888 vec![
3889 ManifestNode::Element {
3890 id: "n0".to_string(),
3891 tag: "ul".to_string(),
3892 },
3893 ManifestNode::List {
3894 id: "n1".to_string(),
3895 start: "n2".to_string(),
3896 end: "n3".to_string(),
3897 iterable: "this.items".to_string(),
3898 initial_value: Some(SerializableValue::Array(Vec::new())),
3899 item_variable: "item".to_string(),
3900 index_variable: Some("index".to_string()),
3901 key_expression: "item.id".to_string(),
3902 item_root: "n4".to_string(),
3903 item_template_html: "<li data-presolve-node=\"n4:__ez_list_key__\" data-presolve-bindings=\"index,item.label\"><!-- presolve-binding:n5:__ez_list_key__:index -->__ez_list_index__<!-- presolve-list-binding-end:n5:__ez_list_key__ -->: <!-- presolve-binding:n6:__ez_list_key__:item.label --><!-- presolve-list-binding-end:n6:__ez_list_key__ --></li>".to_string(),
3904 },
3905 ]
3906 );
3907 }
3908
3909 #[test]
3910 fn preserves_recursive_object_values_in_component_and_manifest_models() {
3911 let source = include_str!(
3912 "../../../fixtures/0023-recursive-object-values/input/RecursiveObjectValues.tsx"
3913 );
3914 let parsed = presolve_parser::parse_file(
3915 "fixtures/0023-recursive-object-values/input/RecursiveObjectValues.tsx",
3916 source,
3917 );
3918
3919 let component_graph = build_component_graph(&parsed);
3920 assert!(component_graph.diagnostics.is_empty());
3921
3922 let profile = SerializableValue::Object(BTreeMap::from([
3923 (
3924 "name".to_string(),
3925 SerializableValue::String("North".to_string()),
3926 ),
3927 (
3928 "settings".to_string(),
3929 SerializableValue::Object(BTreeMap::from([
3930 ("enabled".to_string(), SerializableValue::Boolean(true)),
3931 (
3932 "tags".to_string(),
3933 SerializableValue::Array(vec![
3934 SerializableValue::String("compiler".to_string()),
3935 SerializableValue::Object(BTreeMap::from([
3936 (
3937 "name".to_string(),
3938 SerializableValue::String("runtime".to_string()),
3939 ),
3940 (
3941 "rank".to_string(),
3942 SerializableValue::Number("2".to_string()),
3943 ),
3944 ])),
3945 ]),
3946 ),
3947 ])),
3948 ),
3949 ]));
3950 assert_eq!(
3951 component_graph.components[0].state_fields[0].initial_value,
3952 Some(profile.clone())
3953 );
3954
3955 let template_graph = build_template_graph(&component_graph);
3956 let manifest = build_template_manifest(&component_graph, &template_graph);
3957 assert_eq!(
3958 manifest.components[0].template.nodes[3],
3959 ManifestNode::Binding {
3960 id: "n3".to_string(),
3961 expression: "this.profile".to_string(),
3962 initial_value: Some(profile),
3963 target: None,
3964 element: None,
3965 attribute: None,
3966 }
3967 );
3968 assert!(matches!(
3969 manifest.components[0].actions[0].operand,
3970 Some(SerializableValue::Object(_))
3971 ));
3972 }
3973
3974 #[test]
3975 fn renders_static_object_list_members_and_keys() {
3976 let source = include_str!(
3977 "../../../fixtures/0024-static-object-keyed-list/input/StaticObjectKeyedList.tsx"
3978 );
3979 let parsed = presolve_parser::parse_file(
3980 "fixtures/0024-static-object-keyed-list/input/StaticObjectKeyedList.tsx",
3981 source,
3982 );
3983 let component_graph = build_component_graph(&parsed);
3984 assert!(component_graph.diagnostics.is_empty());
3985
3986 let template_graph = build_template_graph(&component_graph);
3987 assert_eq!(
3988 generate_static_html(&template_graph),
3989 "<ol data-presolve-node=\"n0\" data-presolve-bindings=\"this.items\"><!-- presolve-list-start:n2:this.items --><li data-presolve-node=\"n4:north\" data-presolve-bindings=\"index,item.label,item.details.region\"><!-- presolve-binding:n5:north:index -->0<!-- presolve-list-binding-end:n5:north -->: <!-- presolve-binding:n6:north:item.label -->North<!-- presolve-list-binding-end:n6:north --> (<!-- presolve-binding:n7:north:item.details.region -->west<!-- presolve-list-binding-end:n7:north -->)</li><li data-presolve-node=\"n4:south\" data-presolve-bindings=\"index,item.label,item.details.region\"><!-- presolve-binding:n5:south:index -->1<!-- presolve-list-binding-end:n5:south -->: <!-- presolve-binding:n6:south:item.label -->South<!-- presolve-list-binding-end:n6:south --> (<!-- presolve-binding:n7:south:item.details.region -->east<!-- presolve-list-binding-end:n7:south -->)</li><!-- presolve-list-end:n3 --></ol>\n"
3990 );
3991 }
3992
3993 #[test]
3994 fn renders_initial_keyed_list_items_from_a_serializable_array() {
3995 let source =
3996 include_str!("../../../fixtures/0020-static-keyed-list/input/StaticKeyedList.tsx");
3997
3998 let parsed = presolve_parser::parse_file(
3999 "fixtures/0020-static-keyed-list/input/StaticKeyedList.tsx",
4000 source,
4001 );
4002 let component_graph = build_component_graph(&parsed);
4003 assert!(component_graph.diagnostics.is_empty());
4004
4005 let template_graph = build_template_graph(&component_graph);
4006 let root = template_graph.templates[0]
4007 .root
4008 .as_ref()
4009 .expect("expected root element");
4010 let TemplateChild::List(list) = &root.children[0] else {
4011 panic!("expected keyed list child");
4012 };
4013
4014 assert_eq!(
4015 list.initial_value,
4016 Some(SerializableValue::Array(vec![
4017 SerializableValue::String("North".to_string()),
4018 SerializableValue::String("South".to_string()),
4019 ]))
4020 );
4021
4022 assert_eq!(
4023 generate_static_html(&template_graph),
4024 "<ol data-presolve-node=\"n0\" data-presolve-bindings=\"this.labels\"><!-- presolve-list-start:n2:this.labels --><li data-presolve-node=\"n4:North\" data-presolve-bindings=\"index,label\"><!-- presolve-binding:n5:North:index -->0<!-- presolve-list-binding-end:n5:North -->: <!-- presolve-binding:n6:North:label -->North<!-- presolve-list-binding-end:n6:North --></li><li data-presolve-node=\"n4:South\" data-presolve-bindings=\"index,label\"><!-- presolve-binding:n5:South:index -->1<!-- presolve-list-binding-end:n5:South -->: <!-- presolve-binding:n6:South:label -->South<!-- presolve-list-binding-end:n6:South --></li><!-- presolve-list-end:n3 --></ol>\n"
4025 );
4026
4027 let manifest = build_template_manifest(&component_graph, &template_graph);
4028 assert_eq!(
4029 manifest.components[0].template.nodes,
4030 vec![
4031 ManifestNode::Element {
4032 id: "n0".to_string(),
4033 tag: "ol".to_string(),
4034 },
4035 ManifestNode::List {
4036 id: "n1".to_string(),
4037 start: "n2".to_string(),
4038 end: "n3".to_string(),
4039 iterable: "this.labels".to_string(),
4040 initial_value: Some(SerializableValue::Array(vec![
4041 SerializableValue::String("North".to_string()),
4042 SerializableValue::String("South".to_string()),
4043 ])),
4044 item_variable: "label".to_string(),
4045 index_variable: Some("index".to_string()),
4046 key_expression: "label".to_string(),
4047 item_root: "n4".to_string(),
4048 item_template_html: "<li data-presolve-node=\"n4:__ez_list_key__\" data-presolve-bindings=\"index,label\"><!-- presolve-binding:n5:__ez_list_key__:index -->__ez_list_index__<!-- presolve-list-binding-end:n5:__ez_list_key__ -->: <!-- presolve-binding:n6:__ez_list_key__:label -->__ez_list_item__<!-- presolve-list-binding-end:n6:__ez_list_key__ --></li>".to_string(),
4049 },
4050 ]
4051 );
4052 }
4053
4054 #[test]
4055 fn builds_template_manifest_for_nested_jsx() {
4056 let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
4057
4058 let parsed =
4059 presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
4060
4061 let component_graph = build_component_graph(&parsed);
4062 let template_graph = build_template_graph(&component_graph);
4063 let manifest = build_template_manifest(&component_graph, &template_graph);
4064
4065 assert_eq!(manifest.schema_version, 1);
4066 assert_eq!(manifest.components.len(), 1);
4067
4068 let component = &manifest.components[0];
4069 assert_eq!(component.name, "NestedCounter");
4070
4071 assert_eq!(
4072 component.template.nodes,
4073 vec![
4074 ManifestNode::Element {
4075 id: "n0".to_string(),
4076 tag: "section".to_string(),
4077 },
4078 ManifestNode::Element {
4079 id: "n1".to_string(),
4080 tag: "button".to_string(),
4081 },
4082 ManifestNode::Binding {
4083 id: "n2".to_string(),
4084 expression: "this.count".to_string(),
4085 initial_value: Some(SerializableValue::Number("0".to_string())),
4086 target: None,
4087 element: None,
4088 attribute: None,
4089 }
4090 ]
4091 );
4092
4093 assert_eq!(
4094 component.template.events,
4095 vec![ManifestEvent {
4096 node: "n1".to_string(),
4097 kind: None,
4098 event: "click".to_string(),
4099 handler: "this.increment".to_string(),
4100 arguments: Vec::new(),
4101 method_id: None,
4102 action_batch_id: None,
4103 }]
4104 );
4105
4106 assert_eq!(
4107 component.actions,
4108 vec![ManifestAction {
4109 method: "increment".to_string(),
4110 method_id: None,
4111 action_batch_id: None,
4112 operation: ManifestOperation::Increment,
4113 field: "count".to_string(),
4114 storage_id: None,
4115 operand: None,
4116 }]
4117 );
4118
4119 let manifest_json = template_manifest_json(&manifest);
4120 let manifest_value: serde_json::Value =
4121 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4122
4123 assert_eq!(manifest_value["schema_version"], serde_json::json!(1));
4124 }
4125
4126 #[test]
4127 fn builds_template_manifest_for_decrement_action() {
4128 let source =
4129 include_str!("../../../fixtures/0009-decrement-counter/input/DecrementCounter.tsx");
4130
4131 let parsed = presolve_parser::parse_file(
4132 "fixtures/0009-decrement-counter/input/DecrementCounter.tsx",
4133 source,
4134 );
4135
4136 let component_graph = build_component_graph(&parsed);
4137 let template_graph = build_template_graph(&component_graph);
4138 let manifest = build_template_manifest(&component_graph, &template_graph);
4139
4140 assert_eq!(
4141 manifest.components[0].actions,
4142 vec![ManifestAction {
4143 method: "decrement".to_string(),
4144 method_id: None,
4145 action_batch_id: None,
4146 operation: ManifestOperation::Decrement,
4147 field: "count".to_string(),
4148 storage_id: None,
4149 operand: None,
4150 }]
4151 );
4152
4153 let manifest_json = template_manifest_json(&manifest);
4154 let manifest_value: serde_json::Value =
4155 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4156
4157 assert_eq!(
4158 manifest_value["components"][0]["actions"][0]["operation"],
4159 serde_json::json!("decrement")
4160 );
4161 }
4162
4163 #[test]
4164 fn builds_template_manifest_for_add_and_subtract_assign_actions() {
4165 let source =
4166 include_str!("../../../fixtures/0010-add-subtract-assign/input/StepCounter.tsx");
4167
4168 let parsed = presolve_parser::parse_file(
4169 "fixtures/0010-add-subtract-assign/input/StepCounter.tsx",
4170 source,
4171 );
4172
4173 let component_graph = build_component_graph(&parsed);
4174 let template_graph = build_template_graph(&component_graph);
4175 let manifest = build_template_manifest(&component_graph, &template_graph);
4176
4177 assert_eq!(
4178 manifest.components[0].actions,
4179 vec![
4180 ManifestAction {
4181 method: "addTwo".to_string(),
4182 method_id: None,
4183 action_batch_id: None,
4184 operation: ManifestOperation::AddAssign,
4185 field: "count".to_string(),
4186 storage_id: None,
4187 operand: Some(SerializableValue::Number("2".to_string())),
4188 },
4189 ManifestAction {
4190 method: "subtractThree".to_string(),
4191 method_id: None,
4192 action_batch_id: None,
4193 operation: ManifestOperation::SubtractAssign,
4194 field: "count".to_string(),
4195 storage_id: None,
4196 operand: Some(SerializableValue::Number("3".to_string())),
4197 }
4198 ]
4199 );
4200
4201 let manifest_json = template_manifest_json(&manifest);
4202 let manifest_value: serde_json::Value =
4203 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4204
4205 assert_eq!(
4206 manifest_value["components"][0]["actions"][0]["operation"],
4207 serde_json::json!("add_assign")
4208 );
4209 assert_eq!(
4210 manifest_value["components"][0]["actions"][0]["operand"],
4211 serde_json::json!("2")
4212 );
4213 assert_eq!(
4214 manifest_value["components"][0]["actions"][1]["operation"],
4215 serde_json::json!("subtract_assign")
4216 );
4217 assert_eq!(
4218 manifest_value["components"][0]["actions"][1]["operand"],
4219 serde_json::json!("3")
4220 );
4221 }
4222
4223 #[test]
4224 fn builds_template_manifest_for_direct_assignment_action() {
4225 let source =
4226 include_str!("../../../fixtures/0011-direct-assignment/input/ResetCounter.tsx");
4227
4228 let parsed = presolve_parser::parse_file(
4229 "fixtures/0011-direct-assignment/input/ResetCounter.tsx",
4230 source,
4231 );
4232
4233 let component_graph = build_component_graph(&parsed);
4234 let template_graph = build_template_graph(&component_graph);
4235 let manifest = build_template_manifest(&component_graph, &template_graph);
4236
4237 assert_eq!(
4238 manifest.components[0].actions,
4239 vec![ManifestAction {
4240 method: "reset".to_string(),
4241 method_id: None,
4242 action_batch_id: None,
4243 operation: ManifestOperation::Assign,
4244 field: "count".to_string(),
4245 storage_id: None,
4246 operand: Some(SerializableValue::Number("0".to_string())),
4247 }]
4248 );
4249
4250 let manifest_json = template_manifest_json(&manifest);
4251 let manifest_value: serde_json::Value =
4252 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4253
4254 assert_eq!(
4255 manifest_value["components"][0]["actions"][0]["operation"],
4256 serde_json::json!("assign")
4257 );
4258 assert_eq!(
4259 manifest_value["components"][0]["actions"][0]["operand"],
4260 serde_json::json!("0")
4261 );
4262 }
4263
4264 #[test]
4265 fn builds_template_manifest_for_boolean_toggle_action() {
4266 let source = include_str!("../../../fixtures/0012-boolean-toggle/input/ToggleFlag.tsx");
4267
4268 let parsed = presolve_parser::parse_file(
4269 "fixtures/0012-boolean-toggle/input/ToggleFlag.tsx",
4270 source,
4271 );
4272
4273 let component_graph = build_component_graph(&parsed);
4274 let template_graph = build_template_graph(&component_graph);
4275 let manifest = build_template_manifest(&component_graph, &template_graph);
4276
4277 assert_eq!(
4278 manifest.components[0].actions,
4279 vec![ManifestAction {
4280 method: "toggle".to_string(),
4281 method_id: None,
4282 action_batch_id: None,
4283 operation: ManifestOperation::Toggle,
4284 field: "enabled".to_string(),
4285 storage_id: None,
4286 operand: None,
4287 }]
4288 );
4289
4290 let manifest_json = template_manifest_json(&manifest);
4291 let manifest_value: serde_json::Value =
4292 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4293
4294 assert_eq!(
4295 manifest_value["components"][0]["actions"][0]["operation"],
4296 serde_json::json!("toggle")
4297 );
4298 assert!(manifest_value["components"][0]["actions"][0]
4299 .get("operand")
4300 .is_none());
4301 }
4302
4303 #[test]
4304 fn builds_template_manifest_for_multi_step_action_in_source_order() {
4305 let source =
4306 include_str!("../../../fixtures/0013-multi-step-action/input/BatchActionCounter.tsx");
4307
4308 let parsed = presolve_parser::parse_file(
4309 "fixtures/0013-multi-step-action/input/BatchActionCounter.tsx",
4310 source,
4311 );
4312
4313 let component_graph = build_component_graph(&parsed);
4314 let template_graph = build_template_graph(&component_graph);
4315 let manifest = build_template_manifest(&component_graph, &template_graph);
4316
4317 assert_eq!(
4318 manifest.components[0].actions,
4319 vec![
4320 ManifestAction {
4321 method: "apply".to_string(),
4322 method_id: None,
4323 action_batch_id: None,
4324 operation: ManifestOperation::AddAssign,
4325 field: "count".to_string(),
4326 storage_id: None,
4327 operand: Some(SerializableValue::Number("2".to_string())),
4328 },
4329 ManifestAction {
4330 method: "apply".to_string(),
4331 method_id: None,
4332 action_batch_id: None,
4333 operation: ManifestOperation::Decrement,
4334 field: "count".to_string(),
4335 storage_id: None,
4336 operand: None,
4337 },
4338 ManifestAction {
4339 method: "apply".to_string(),
4340 method_id: None,
4341 action_batch_id: None,
4342 operation: ManifestOperation::Assign,
4343 field: "count".to_string(),
4344 storage_id: None,
4345 operand: Some(SerializableValue::Number("8".to_string())),
4346 },
4347 ManifestAction {
4348 method: "apply".to_string(),
4349 method_id: None,
4350 action_batch_id: None,
4351 operation: ManifestOperation::Increment,
4352 field: "count".to_string(),
4353 storage_id: None,
4354 operand: None,
4355 },
4356 ManifestAction {
4357 method: "apply".to_string(),
4358 method_id: None,
4359 action_batch_id: None,
4360 operation: ManifestOperation::Toggle,
4361 field: "enabled".to_string(),
4362 storage_id: None,
4363 operand: None,
4364 }
4365 ]
4366 );
4367
4368 let manifest_json = template_manifest_json(&manifest);
4369 let manifest_value: serde_json::Value =
4370 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4371
4372 let actions = manifest_value["components"][0]["actions"]
4373 .as_array()
4374 .expect("manifest actions should be an array");
4375
4376 assert_eq!(
4377 actions
4378 .iter()
4379 .map(|action| action["operation"].as_str().expect("operation is a string"))
4380 .collect::<Vec<_>>(),
4381 vec!["add_assign", "decrement", "assign", "increment", "toggle"]
4382 );
4383 assert_eq!(actions[0]["method"], serde_json::json!("apply"));
4384 assert_eq!(actions[4]["method"], serde_json::json!("apply"));
4385 assert!(actions[1].get("operand").is_none());
4386 assert!(actions[3].get("operand").is_none());
4387 assert!(actions[4].get("operand").is_none());
4388 }
4389
4390 #[test]
4391 fn builds_template_manifest_for_dynamic_attribute_bindings() {
4392 let source = include_str!(
4393 "../../../fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx"
4394 );
4395
4396 let parsed = presolve_parser::parse_file(
4397 "fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx",
4398 source,
4399 );
4400
4401 let component_graph = build_component_graph(&parsed);
4402 let template_graph = build_template_graph(&component_graph);
4403 let manifest = build_template_manifest(&component_graph, &template_graph);
4404 let manifest_json = template_manifest_json(&manifest);
4405 let manifest_value: serde_json::Value =
4406 serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4407
4408 assert_eq!(
4409 manifest.components[0].template.nodes[1],
4410 ManifestNode::Binding {
4411 id: "n1".to_string(),
4412 expression: "this.disabled".to_string(),
4413 initial_value: Some(SerializableValue::Boolean(false)),
4414 target: Some(ManifestBindingTarget::Attribute),
4415 element: Some("n0".to_string()),
4416 attribute: Some("disabled".to_string()),
4417 }
4418 );
4419 assert_eq!(
4420 manifest_value["components"][0]["template"]["nodes"][1]["target"],
4421 serde_json::json!("attribute")
4422 );
4423 assert_eq!(
4424 manifest_value["components"][0]["template"]["nodes"][1]["element"],
4425 serde_json::json!("n0")
4426 );
4427 assert_eq!(
4428 manifest_value["components"][0]["template"]["nodes"][1]["attribute"],
4429 serde_json::json!("disabled")
4430 );
4431 assert!(manifest_value["components"][0]["template"]["nodes"][3]
4432 .get("target")
4433 .is_none());
4434 }
4435
4436 #[test]
4437 fn generates_standalone_page_with_embedded_manifest() {
4438 let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
4439
4440 let parsed =
4441 presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
4442
4443 let component_graph = build_component_graph(&parsed);
4444 let template_graph = build_template_graph(&component_graph);
4445 let html = generate_static_html(&template_graph);
4446 let manifest = build_template_manifest(&component_graph, &template_graph);
4447 let page = generate_standalone_page("NestedCounter", &html, &manifest);
4448
4449 assert!(page.starts_with("<!doctype html>\n"));
4450 assert!(page.contains("<title>NestedCounter</title>"));
4451 assert!(page.contains("<section data-presolve-node=\"n0\">"));
4452 assert!(page.contains("id=\"presolve-template-manifest\""));
4453 assert!(page.contains("\"name\": \"NestedCounter\""));
4454 assert!(page.contains("<script src=\"./runtime.js\" defer></script>"));
4455 }
4456}