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