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