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