Skip to main content

presolve_parser/
oxc_adapter.rs

1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use oxc_allocator::Allocator;
5use oxc_ast::ast::{
6    Argument, AssignmentTarget, BindingPatternKind, ChainElement, ClassElement, Declaration,
7    ExportDefaultDeclarationKind, Expression, ImportDeclarationSpecifier, JSXAttributeItem,
8    JSXAttributeName, JSXAttributeValue, JSXChild, JSXElementName, JSXExpression, JSXFragment,
9    JSXMemberExpression, JSXMemberExpressionObject, ModuleExportName, ObjectPropertyKind, Program,
10    PropertyKey, PropertyKind, SimpleAssignmentTarget, Statement,
11};
12use oxc_ast_visit::{walk, Visit};
13use oxc_diagnostics::Severity as OxcSeverity;
14use oxc_estree::{CompactTSSerializer, ESTree};
15use oxc_parser::Parser;
16use oxc_span::{GetSpan, SourceType, Span};
17
18use crate::model::{
19    ParseDiagnostic, ParseLabel, ParseSeverity, ParsedArithmeticExpression,
20    ParsedArithmeticExpressionKind, ParsedArithmeticOperator, ParsedCallArgument,
21    ParsedCallExpression, ParsedClass, ParsedClassHeritage, ParsedComparisonOperator,
22    ParsedComputedExpression, ParsedComputedExpressionKind, ParsedConstantExpression,
23    ParsedConstantExpressionKind, ParsedDecorator, ParsedEffectBody, ParsedEffectCleanup,
24    ParsedEffectExpression, ParsedEffectExpressionKind, ParsedEffectStatement,
25    ParsedEffectStatementKind, ParsedEventHandler, ParsedExport, ParsedExportKind,
26    ParsedExportSpecifier, ParsedFile, ParsedImport, ParsedImportSpecifier, ParsedInitializerCall,
27    ParsedInlineHandler, ParsedJsxAttribute, ParsedJsxAttributeValue, ParsedJsxChild,
28    ParsedJsxConditional, ParsedJsxElement, ParsedJsxFragment, ParsedJsxList, ParsedJsxNode,
29    ParsedLocalVariable, ParsedLogicalOperator, ParsedMethod, ParsedMethodCall,
30    ParsedMethodParameter, ParsedProperty, ParsedSerializableValue, ParsedSourceAst,
31    ParsedStateOperation, ParsedStateUpdate, ParsedStaticMemberDesignator,
32    ParsedThisMemberDesignator, ParsedTypeAlias, ParsedTypeAnnotation, ParsedUnaryOperator,
33    ParsedUnsupportedEffectStatementKind, ParsedValidationRuleArgument,
34    ParsedValidationRuleArgumentKind, ParsedValidationRuleExpression,
35    ParsedValidationRuleExpressionKind, SourceSpan,
36};
37
38pub fn parse_file(path: impl AsRef<Path>, source: &str) -> ParsedFile {
39    let path = path.as_ref();
40    let source_type = SourceType::from_path(path)
41        .unwrap_or_default()
42        .with_typescript(true)
43        .with_jsx(true);
44
45    let allocator = Allocator::default();
46    let ret = Parser::new(&allocator, source, source_type).parse();
47    let syntax = parse_source_ast(&ret.program, source);
48    let call_expressions = collect_call_expressions(&ret.program, source);
49
50    let ParsedProgramFacts {
51        classes,
52        type_aliases,
53        local_type_bindings,
54        local_value_bindings,
55        imports,
56        exports,
57    } = parse_program(&ret.program, source);
58    let diagnostics = ret
59        .errors
60        .iter()
61        .map(|diagnostic| parse_diagnostic(source, diagnostic))
62        .collect::<Vec<_>>();
63
64    ParsedFile {
65        path: PathBuf::from(path),
66        syntax,
67        classes,
68        type_aliases,
69        local_type_bindings,
70        local_value_bindings,
71        imports,
72        exports,
73        call_expressions,
74        diagnostics,
75    }
76}
77
78fn collect_call_expressions(program: &Program<'_>, source: &str) -> Vec<ParsedCallExpression> {
79    struct Collector<'a> {
80        source: &'a str,
81        calls: Vec<ParsedCallExpression>,
82    }
83
84    impl<'a> Visit<'a> for Collector<'a> {
85        fn visit_call_expression(&mut self, call: &oxc_ast::ast::CallExpression<'a>) {
86            let (member_object_span, member_property_span) = match &call.callee {
87                Expression::StaticMemberExpression(member) => (
88                    Some(source_span(self.source, member.object.span())),
89                    Some(source_span(self.source, member.property.span)),
90                ),
91                _ => (None, None),
92            };
93            self.calls.push(ParsedCallExpression {
94                callee_span: source_span(self.source, call.callee.span()),
95                member_object_span,
96                member_property_span,
97                span: source_span(self.source, call.span),
98                arguments: call
99                    .arguments
100                    .iter()
101                    .map(|argument| match argument {
102                        Argument::StringLiteral(value) => ParsedCallArgument::StringLiteral {
103                            value: value.value.to_string(),
104                            span: source_span(self.source, value.span),
105                        },
106                        _ => ParsedCallArgument::Other {
107                            span: source_span(self.source, argument.span()),
108                        },
109                    })
110                    .collect(),
111            });
112            walk::walk_call_expression(self, call);
113        }
114    }
115
116    let mut collector = Collector {
117        source,
118        calls: Vec::new(),
119    };
120    collector.visit_program(program);
121    collector
122        .calls
123        .sort_by_key(|call| (call.span.start, call.span.end));
124    collector.calls
125}
126
127fn parse_source_ast(program: &Program<'_>, source: &str) -> ParsedSourceAst {
128    let mut serializer = CompactTSSerializer::new(true);
129    program.serialize(&mut serializer);
130    ParsedSourceAst {
131        source: source.to_string(),
132        estree_json: serializer.into_string(),
133        span: source_span(source, program.span),
134    }
135}
136
137struct ParsedProgramFacts {
138    classes: Vec<ParsedClass>,
139    type_aliases: Vec<ParsedTypeAlias>,
140    local_type_bindings: Vec<String>,
141    local_value_bindings: Vec<String>,
142    imports: Vec<ParsedImport>,
143    exports: Vec<ParsedExport>,
144}
145
146fn parse_program(program: &Program<'_>, source: &str) -> ParsedProgramFacts {
147    let mut classes = Vec::new();
148    let mut type_aliases = Vec::new();
149    let mut local_type_bindings = Vec::new();
150    let mut local_value_bindings = Vec::new();
151    let mut imports = Vec::new();
152    let mut exports = Vec::new();
153
154    for statement in &program.body {
155        match statement {
156            Statement::ImportDeclaration(declaration) => {
157                imports.push(parse_import_declaration(declaration, source));
158                continue;
159            }
160            Statement::ExportNamedDeclaration(declaration) => {
161                exports.push(parse_named_export_declaration(declaration, source));
162                if let Some(declaration) = &declaration.declaration {
163                    retain_local_type_binding(declaration, &mut local_type_bindings);
164                    retain_local_value_binding(declaration, &mut local_value_bindings);
165                    if let Some(class) = parse_declaration(declaration, source) {
166                        classes.push(class);
167                    }
168                    if let Some(alias) = parse_type_alias_declaration(declaration, source) {
169                        type_aliases.push(alias);
170                    }
171                }
172                continue;
173            }
174            Statement::ExportDefaultDeclaration(declaration) => {
175                exports.push(parse_default_export_declaration(declaration, source));
176                if let ExportDefaultDeclarationKind::ClassDeclaration(class) =
177                    &declaration.declaration
178                {
179                    if let Some(class) = parse_class(class, source) {
180                        local_type_bindings.push(class.name.clone());
181                        classes.push(class);
182                    }
183                }
184                continue;
185            }
186            Statement::ExportAllDeclaration(declaration) => {
187                exports.push(ParsedExport {
188                    kind: ParsedExportKind::All,
189                    source: Some(declaration.source.value.to_string()),
190                    specifiers: declaration
191                        .exported
192                        .as_ref()
193                        .map(|exported| {
194                            vec![ParsedExportSpecifier {
195                                local: None,
196                                exported: module_export_name(exported),
197                            }]
198                        })
199                        .unwrap_or_default(),
200                    span: source_span(source, declaration.span),
201                });
202                continue;
203            }
204            _ => {}
205        }
206
207        if let Some(declaration) = statement.as_declaration() {
208            retain_local_type_binding(declaration, &mut local_type_bindings);
209            retain_local_value_binding(declaration, &mut local_value_bindings);
210            if let Some(class) = parse_declaration(declaration, source) {
211                classes.push(class);
212            }
213            if let Some(alias) = parse_type_alias_declaration(declaration, source) {
214                type_aliases.push(alias);
215            }
216        }
217    }
218
219    local_type_bindings.sort();
220    local_type_bindings.dedup();
221    local_value_bindings.sort();
222    local_value_bindings.dedup();
223    ParsedProgramFacts {
224        classes,
225        type_aliases,
226        local_type_bindings,
227        local_value_bindings,
228        imports,
229        exports,
230    }
231}
232
233fn retain_local_value_binding(declaration: &Declaration<'_>, bindings: &mut Vec<String>) {
234    if let Declaration::FunctionDeclaration(function) = declaration {
235        if let Some(id) = &function.id {
236            bindings.push(id.name.to_string());
237        }
238    }
239}
240
241fn retain_local_type_binding(declaration: &Declaration<'_>, bindings: &mut Vec<String>) {
242    let name = match declaration {
243        Declaration::ClassDeclaration(class) => class.id.as_ref().map(|id| id.name.as_str()),
244        Declaration::TSTypeAliasDeclaration(alias) => Some(alias.id.name.as_str()),
245        Declaration::TSInterfaceDeclaration(interface) => Some(interface.id.name.as_str()),
246        Declaration::TSEnumDeclaration(r#enum) => Some(r#enum.id.name.as_str()),
247        Declaration::TSImportEqualsDeclaration(import) => Some(import.id.name.as_str()),
248        Declaration::VariableDeclaration(_)
249        | Declaration::FunctionDeclaration(_)
250        | Declaration::TSModuleDeclaration(_) => None,
251    };
252    if let Some(name) = name {
253        bindings.push(name.to_string());
254    }
255}
256
257fn parse_type_alias_declaration(
258    declaration: &Declaration<'_>,
259    source: &str,
260) -> Option<ParsedTypeAlias> {
261    let Declaration::TSTypeAliasDeclaration(alias) = declaration else {
262        return None;
263    };
264    let type_span = source_span(source, alias.type_annotation.span());
265
266    Some(ParsedTypeAlias {
267        name: alias.id.name.to_string(),
268        type_text: source[type_span.start..type_span.end].trim().to_string(),
269        span: source_span(source, alias.span),
270        type_span,
271    })
272}
273
274fn parse_import_declaration(
275    declaration: &oxc_ast::ast::ImportDeclaration<'_>,
276    source: &str,
277) -> ParsedImport {
278    let specifiers = declaration
279        .specifiers
280        .as_ref()
281        .map(|specifiers| {
282            specifiers
283                .iter()
284                .map(|specifier| match specifier {
285                    ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
286                        ParsedImportSpecifier {
287                            imported: module_export_name(&specifier.imported),
288                            local: specifier.local.name.to_string(),
289                            local_span: source_span(source, specifier.local.span),
290                        }
291                    }
292                    ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => {
293                        ParsedImportSpecifier {
294                            imported: "default".to_string(),
295                            local: specifier.local.name.to_string(),
296                            local_span: source_span(source, specifier.local.span),
297                        }
298                    }
299                    ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => {
300                        ParsedImportSpecifier {
301                            imported: "*".to_string(),
302                            local: specifier.local.name.to_string(),
303                            local_span: source_span(source, specifier.local.span),
304                        }
305                    }
306                })
307                .collect()
308        })
309        .unwrap_or_default();
310
311    ParsedImport {
312        source: declaration.source.value.to_string(),
313        specifiers,
314        span: source_span(source, declaration.span),
315    }
316}
317
318fn parse_named_export_declaration(
319    declaration: &oxc_ast::ast::ExportNamedDeclaration<'_>,
320    source: &str,
321) -> ParsedExport {
322    let mut specifiers = declaration
323        .specifiers
324        .iter()
325        .map(|specifier| ParsedExportSpecifier {
326            local: Some(module_export_name(&specifier.local)),
327            exported: module_export_name(&specifier.exported),
328        })
329        .collect::<Vec<_>>();
330
331    if let Some(declaration) = &declaration.declaration {
332        specifiers.extend(named_declaration_exports(declaration));
333    }
334
335    ParsedExport {
336        kind: ParsedExportKind::Named,
337        source: declaration
338            .source
339            .as_ref()
340            .map(|source| source.value.to_string()),
341        specifiers,
342        span: source_span(source, declaration.span),
343    }
344}
345
346fn parse_default_export_declaration(
347    declaration: &oxc_ast::ast::ExportDefaultDeclaration<'_>,
348    source: &str,
349) -> ParsedExport {
350    ParsedExport {
351        kind: ParsedExportKind::Default,
352        source: None,
353        specifiers: vec![ParsedExportSpecifier {
354            local: default_declaration_name(&declaration.declaration),
355            exported: "default".to_string(),
356        }],
357        span: source_span(source, declaration.span),
358    }
359}
360
361fn named_declaration_exports(declaration: &Declaration<'_>) -> Vec<ParsedExportSpecifier> {
362    let name = match declaration {
363        Declaration::ClassDeclaration(class) => class.id.as_ref().map(|id| id.name.to_string()),
364        Declaration::FunctionDeclaration(function) => {
365            function.id.as_ref().map(|id| id.name.to_string())
366        }
367        Declaration::TSTypeAliasDeclaration(alias) => Some(alias.id.name.to_string()),
368        Declaration::VariableDeclaration(declaration) => {
369            let names = declaration
370                .declarations
371                .iter()
372                .filter_map(|declarator| binding_identifier_name(&declarator.id.kind))
373                .collect::<Vec<_>>();
374
375            return names
376                .into_iter()
377                .map(|name| ParsedExportSpecifier {
378                    local: Some(name.clone()),
379                    exported: name,
380                })
381                .collect();
382        }
383        _ => None,
384    };
385
386    name.map(|name| {
387        vec![ParsedExportSpecifier {
388            local: Some(name.clone()),
389            exported: name,
390        }]
391    })
392    .unwrap_or_default()
393}
394
395fn default_declaration_name(declaration: &ExportDefaultDeclarationKind<'_>) -> Option<String> {
396    match declaration {
397        ExportDefaultDeclarationKind::ClassDeclaration(class) => {
398            class.id.as_ref().map(|id| id.name.to_string())
399        }
400        ExportDefaultDeclarationKind::FunctionDeclaration(function) => {
401            function.id.as_ref().map(|id| id.name.to_string())
402        }
403        _ => None,
404    }
405}
406
407fn module_export_name(name: &ModuleExportName<'_>) -> String {
408    match name {
409        ModuleExportName::IdentifierName(name) => name.name.to_string(),
410        ModuleExportName::IdentifierReference(name) => name.name.to_string(),
411        ModuleExportName::StringLiteral(name) => name.value.to_string(),
412    }
413}
414
415fn parse_declaration(declaration: &Declaration<'_>, source: &str) -> Option<ParsedClass> {
416    let Declaration::ClassDeclaration(class) = declaration else {
417        return None;
418    };
419
420    parse_class(class, source)
421}
422
423fn parse_class(class: &oxc_ast::ast::Class<'_>, source: &str) -> Option<ParsedClass> {
424    let name = class
425        .id
426        .as_ref()
427        .map(|id| id.name.to_string())
428        .unwrap_or_else(|| "<anonymous>".to_string());
429
430    let decorators = normalized_decorators(
431        class
432            .decorators
433            .iter()
434            .filter_map(|decorator| parse_decorator(decorator, source))
435            .collect::<Vec<_>>(),
436    );
437
438    let mut properties = Vec::new();
439    let mut methods = Vec::new();
440
441    for element in &class.body.body {
442        match element {
443            ClassElement::PropertyDefinition(property) => {
444                if let Some(property) = parse_property(property, source) {
445                    properties.push(property);
446                }
447            }
448            ClassElement::MethodDefinition(method) => {
449                if let Some(method) = parse_method(method, source) {
450                    methods.push(method);
451                }
452            }
453            _ => {}
454        }
455    }
456
457    Some(ParsedClass {
458        name,
459        span: source_span(source, class.span),
460        heritage: class.super_class.as_ref().map(|base| {
461            let span = base.span();
462            ParsedClassHeritage {
463                base: source
464                    .get(span.start as usize..span.end as usize)
465                    .unwrap_or_default()
466                    .trim()
467                    .to_string(),
468                span: source_span(source, span),
469            }
470        }),
471        decorators,
472        properties,
473        methods,
474    })
475}
476
477fn parse_decorator(
478    decorator: &oxc_ast::ast::Decorator<'_>,
479    source: &str,
480) -> Option<ParsedDecorator> {
481    match &decorator.expression {
482        Expression::CallExpression(call) => {
483            let Expression::Identifier(callee) = &call.callee else {
484                return None;
485            };
486            Some(ParsedDecorator {
487                name: callee.name.to_string(),
488                is_invoked: true,
489                arguments: call.arguments.iter().map(argument_string_value).collect(),
490                argument: call.arguments.first().and_then(argument_string_value),
491                argument_count: call.arguments.len(),
492                argument_spans: call
493                    .arguments
494                    .iter()
495                    .map(|argument| source_span(source, argument.span()))
496                    .collect(),
497                static_member_argument: call
498                    .arguments
499                    .first()
500                    .and_then(|argument| parsed_static_member_designator(argument, source)),
501                this_member_argument: call
502                    .arguments
503                    .first()
504                    .and_then(|argument| parsed_this_member_designator(argument, source)),
505                validation_rule_expression: (callee.name == "validate")
506                    .then(|| call.arguments.first()?.as_expression())
507                    .flatten()
508                    .map(|expression| parsed_validation_rule_expression(expression, source)),
509                span: source_span(source, decorator.span),
510            })
511        }
512        Expression::Identifier(identifier) => Some(ParsedDecorator {
513            name: identifier.name.to_string(),
514            is_invoked: false,
515            arguments: Vec::new(),
516            argument: None,
517            argument_count: 0,
518            argument_spans: Vec::new(),
519            static_member_argument: None,
520            this_member_argument: None,
521            validation_rule_expression: None,
522            span: source_span(source, decorator.span),
523        }),
524        _ => None,
525    }
526}
527
528fn normalized_decorators(mut decorators: Vec<ParsedDecorator>) -> Vec<ParsedDecorator> {
529    if !decorators.iter().any(|decorator| {
530        matches!(
531            decorator.name.as_str(),
532            "form" | "field" | "validate" | "submit" | "serialize"
533        )
534    }) {
535        decorators.retain(|decorator| decorator.is_invoked);
536    }
537    decorators
538}
539
540fn parsed_validation_rule_expression(
541    expression: &Expression<'_>,
542    source: &str,
543) -> ParsedValidationRuleExpression {
544    let kind = match expression {
545        Expression::CallExpression(call) => ParsedValidationRuleExpressionKind::Call {
546            callee: match &call.callee {
547                Expression::Identifier(identifier) => Some(identifier.name.to_string()),
548                _ => None,
549            },
550            arguments: call
551                .arguments
552                .iter()
553                .map(|argument| parsed_validation_rule_argument(argument, source))
554                .collect(),
555        },
556        Expression::Identifier(identifier) => {
557            ParsedValidationRuleExpressionKind::Identifier(identifier.name.to_string())
558        }
559        _ => ParsedValidationRuleExpressionKind::Unsupported,
560    };
561    ParsedValidationRuleExpression {
562        kind,
563        span: source_span(source, expression.span()),
564    }
565}
566
567fn parsed_validation_rule_argument(
568    argument: &Argument<'_>,
569    source: &str,
570) -> ParsedValidationRuleArgument {
571    let span = source_span(source, argument.span());
572    let kind = argument.as_expression().map_or(
573        ParsedValidationRuleArgumentKind::Unsupported,
574        |expression| {
575            if let Expression::StringLiteral(literal) = expression {
576                return ParsedValidationRuleArgumentKind::StringLiteral(literal.value.to_string());
577            }
578            if let Some(designator) = parsed_this_member_expression(expression, source) {
579                return ParsedValidationRuleArgumentKind::ThisMember(designator);
580            }
581            parsed_constant_expression(expression, source).map_or(
582                ParsedValidationRuleArgumentKind::Unsupported,
583                ParsedValidationRuleArgumentKind::Constant,
584            )
585        },
586    );
587    ParsedValidationRuleArgument { kind, span }
588}
589
590fn parsed_this_member_designator(
591    argument: &Argument<'_>,
592    source: &str,
593) -> Option<ParsedThisMemberDesignator> {
594    parsed_this_member_expression(argument.as_expression()?, source)
595}
596
597fn parsed_this_member_expression(
598    expression: &Expression<'_>,
599    source: &str,
600) -> Option<ParsedThisMemberDesignator> {
601    let Expression::StaticMemberExpression(member) = expression else {
602        return None;
603    };
604    let Expression::ThisExpression(this) = &member.object else {
605        return None;
606    };
607    Some(ParsedThisMemberDesignator {
608        member: member.property.name.to_string(),
609        span: source_span(source, member.span),
610        this_span: source_span(source, this.span),
611        member_span: source_span(source, member.property.span),
612    })
613}
614
615fn parsed_static_member_designator(
616    argument: &Argument<'_>,
617    source: &str,
618) -> Option<ParsedStaticMemberDesignator> {
619    let Expression::StaticMemberExpression(member) = argument.as_expression()? else {
620        return None;
621    };
622    let Expression::Identifier(object) = &member.object else {
623        return None;
624    };
625    Some(ParsedStaticMemberDesignator {
626        object: object.name.to_string(),
627        member: member.property.name.to_string(),
628        span: source_span(source, member.span),
629        object_span: source_span(source, object.span),
630        member_span: source_span(source, member.property.span),
631    })
632}
633
634fn argument_string_value(argument: &Argument<'_>) -> Option<String> {
635    match argument {
636        Argument::StringLiteral(literal) => Some(literal.value.to_string()),
637        _ => None,
638    }
639}
640
641fn parse_property(
642    property: &oxc_ast::ast::PropertyDefinition<'_>,
643    source: &str,
644) -> Option<ParsedProperty> {
645    let decorators = normalized_decorators(
646        property
647            .decorators
648            .iter()
649            .filter_map(|decorator| parse_decorator(decorator, source))
650            .collect::<Vec<_>>(),
651    );
652    let (name, is_identifier_name) = match &property.key {
653        PropertyKey::StaticIdentifier(identifier) => (identifier.name.to_string(), true),
654        key => match property_key_name(key) {
655            Some(name) => (name, false),
656            None if decorators
657                .iter()
658                .any(|decorator| matches!(decorator.name.as_str(), "form" | "field")) =>
659            {
660                (
661                    format!("<unsupported:{}>", property.key.span().start),
662                    false,
663                )
664            }
665            None => return None,
666        },
667    };
668
669    let initializer = property.value.as_ref().and_then(expression_summary);
670    let initializer_call = property.value.as_ref().and_then(|expression| {
671        let Expression::CallExpression(call) = expression else {
672            return None;
673        };
674        Some(ParsedInitializerCall {
675            callee_span: source_span(source, call.callee.span()),
676            span: source_span(source, call.span),
677            argument_count: call.arguments.len(),
678            inline_handler: parsed_inline_handler(call, source),
679        })
680    });
681    let initializer_literal = property
682        .value
683        .as_ref()
684        .and_then(serializable_value_from_expression);
685    let initializer_expression = property
686        .value
687        .as_ref()
688        .and_then(|expression| parsed_computed_expression(expression, source));
689    let initializer_constant_expression = property
690        .value
691        .as_ref()
692        .and_then(|expression| parsed_constant_expression(expression, source));
693    let initializer_span = property
694        .value
695        .as_ref()
696        .map(|value| source_span(source, value.span()));
697
698    let state_initial_value = property.value.as_ref().and_then(state_initial_value);
699    let state_initial_expression = property
700        .value
701        .as_ref()
702        .and_then(|expression| state_initial_constant_expression(expression, source));
703    let type_annotation = property
704        .type_annotation
705        .as_ref()
706        .map(|annotation| parsed_type_annotation(annotation.span, source));
707    let state_type_annotation = (initializer.as_deref() == Some("state(...)"))
708        .then_some(type_annotation.clone())
709        .flatten();
710    let declaration_start = decorators
711        .first()
712        .map_or(property.span.start as usize, |decorator| {
713            decorator.span.start
714        });
715
716    Some(ParsedProperty {
717        name,
718        is_identifier_name,
719        decorators,
720        initializer_call,
721        initializer,
722        initializer_literal,
723        initializer_expression,
724        initializer_constant_expression,
725        initializer_span,
726        state_initial_value,
727        state_initial_expression,
728        state_type_annotation,
729        type_annotation,
730        name_span: source_span(source, property.key.span()),
731        is_static: property.r#static,
732        is_definite_assignment: property.definite,
733        is_declare: property.declare,
734        span: source_span_from_offsets(source, declaration_start, property.span.end as usize),
735    })
736}
737
738/// Retain general syntax facts for a single inline function argument.  This
739/// deliberately makes no inference from the callee spelling or field name.
740fn parsed_inline_handler(
741    call: &oxc_ast::ast::CallExpression<'_>,
742    source: &str,
743) -> Option<ParsedInlineHandler> {
744    let [argument] = call.arguments.as_slice() else {
745        return None;
746    };
747    match argument {
748        Argument::ArrowFunctionExpression(handler) => Some(parsed_inline_handler_body(
749            handler.span,
750            &handler.body,
751            handler.r#async,
752            handler.expression,
753            parsed_inline_handler_parameters(&handler.params, source),
754            source,
755        )),
756        Argument::FunctionExpression(handler) => Some(parsed_inline_handler_body(
757            handler.span,
758            handler.body.as_deref()?,
759            handler.r#async,
760            false,
761            parsed_inline_handler_parameters(&handler.params, source),
762            source,
763        )),
764        _ => None,
765    }
766}
767
768fn parsed_inline_handler_body(
769    span: Span,
770    body: &oxc_ast::ast::FunctionBody<'_>,
771    is_async: bool,
772    is_expression_body: bool,
773    parameters: Vec<ParsedMethodParameter>,
774    source: &str,
775) -> ParsedInlineHandler {
776    let mut local_variables = Vec::new();
777    let mut state_updates = Vec::new();
778    let mut unsupported_statement_spans = Vec::new();
779    for statement in &body.statements {
780        if let Some(update) = parsed_state_update(statement, source) {
781            state_updates.push(update);
782        } else if let locals @ [.., _] = parsed_local_variables(statement, source).as_slice() {
783            local_variables.extend_from_slice(locals);
784        } else if !matches!(statement, Statement::EmptyStatement(_)) {
785            unsupported_statement_spans.push(source_span(source, statement.span()));
786        }
787    }
788    ParsedInlineHandler {
789        span: source_span(source, span),
790        body_span: source_span(source, body.span),
791        is_async,
792        is_expression_body,
793        parameters,
794        local_variables,
795        state_updates,
796        unsupported_statement_spans,
797        effect_body: (!is_expression_body).then(|| parsed_inline_effect_body(body, source)),
798    }
799}
800
801fn parsed_inline_handler_parameters(
802    parameters: &oxc_ast::ast::FormalParameters<'_>,
803    source: &str,
804) -> Vec<ParsedMethodParameter> {
805    parameters
806        .items
807        .iter()
808        .filter_map(|parameter| {
809            Some(ParsedMethodParameter {
810                name: binding_identifier_name(&parameter.pattern.kind)?,
811                decorators: normalized_decorators(
812                    parameter
813                        .decorators
814                        .iter()
815                        .filter_map(|decorator| parse_decorator(decorator, source))
816                        .collect(),
817                ),
818                span: source_span(source, parameter.span),
819                type_annotation: parameter
820                    .pattern
821                    .type_annotation
822                    .as_ref()
823                    .map(|annotation| parsed_type_annotation(annotation.span, source)),
824            })
825        })
826        .collect()
827}
828
829fn parsed_type_annotation(span: Span, source: &str) -> ParsedTypeAnnotation {
830    let span = source_span(source, span);
831    let text = source[span.start..span.end]
832        .strip_prefix(':')
833        .expect("TypeScript annotation span should start with a colon")
834        .trim()
835        .to_string();
836
837    ParsedTypeAnnotation { text, span }
838}
839
840fn parse_method(method: &oxc_ast::ast::MethodDefinition<'_>, source: &str) -> Option<ParsedMethod> {
841    let name = property_key_name(&method.key)?;
842    let decorators = normalized_decorators(
843        method
844            .decorators
845            .iter()
846            .filter_map(|decorator| parse_decorator(decorator, source))
847            .collect::<Vec<_>>(),
848    );
849
850    let mut jsx_roots = Vec::new();
851    let mut bindings = Vec::new();
852    let mut state_updates = Vec::new();
853    let mut local_variables = Vec::new();
854    let mut return_values = Vec::new();
855    let mut calls = Vec::new();
856    let parameters = method
857        .value
858        .params
859        .items
860        .iter()
861        .filter_map(|parameter| {
862            Some(ParsedMethodParameter {
863                name: binding_identifier_name(&parameter.pattern.kind)?,
864                decorators: normalized_decorators(
865                    parameter
866                        .decorators
867                        .iter()
868                        .filter_map(|decorator| parse_decorator(decorator, source))
869                        .collect(),
870                ),
871                span: source_span(source, parameter.span),
872                type_annotation: parameter
873                    .pattern
874                    .type_annotation
875                    .as_ref()
876                    .map(|annotation| parsed_type_annotation(annotation.span, source)),
877            })
878        })
879        .collect();
880
881    if let Some(body) = &method.value.body {
882        for statement in &body.statements {
883            parse_statement_for_jsx(statement, source, &mut jsx_roots, &mut bindings);
884            if let Some(update) = parsed_state_update(statement, source) {
885                state_updates.push(update);
886            }
887            local_variables.extend(parsed_local_variables(statement, source));
888            if let Some(value) = parsed_return_value(statement) {
889                return_values.push(value);
890            }
891            collect_method_calls(statement, source, &mut calls);
892        }
893    }
894
895    // Getter expression retention is syntax only. A later canonical V2
896    // analysis decides whether a getter is computed; decorators are not a
897    // parser recognition condition.
898    let computed_expression = (method.kind == oxc_ast::ast::MethodDefinitionKind::Get)
899        .then(|| {
900            let body = method.value.body.as_ref()?;
901            let [Statement::ReturnStatement(return_statement)] = body.statements.as_slice() else {
902                return None;
903            };
904            parsed_computed_expression(return_statement.argument.as_ref()?, source)
905        })
906        .flatten();
907    let effect_body = decorators
908        .iter()
909        .any(|decorator| decorator.name == "effect")
910        .then(|| {
911            method
912                .value
913                .body
914                .as_ref()
915                .map(|body| parsed_effect_body(body, source))
916        })
917        .flatten();
918
919    Some(ParsedMethod {
920        name,
921        span: source_span(source, method.span),
922        decorators,
923        is_getter: method.kind == oxc_ast::ast::MethodDefinitionKind::Get,
924        is_setter: method.kind == oxc_ast::ast::MethodDefinitionKind::Set,
925        is_async: method.value.r#async,
926        is_static: method.r#static,
927        jsx_roots,
928        bindings,
929        state_updates,
930        local_variables,
931        parameters,
932        return_type_annotation: method
933            .value
934            .return_type
935            .as_ref()
936            .map(|annotation| parsed_type_annotation(annotation.span, source)),
937        return_values,
938        computed_expression,
939        effect_body,
940        calls,
941    })
942}
943
944fn parsed_effect_body(body: &oxc_ast::ast::FunctionBody<'_>, source: &str) -> ParsedEffectBody {
945    parsed_effect_body_with_cleanup(body, source, false)
946}
947
948fn parsed_inline_effect_body(
949    body: &oxc_ast::ast::FunctionBody<'_>,
950    source: &str,
951) -> ParsedEffectBody {
952    parsed_effect_body_with_cleanup(body, source, true)
953}
954
955fn parsed_effect_body_with_cleanup(
956    body: &oxc_ast::ast::FunctionBody<'_>,
957    source: &str,
958    allow_cleanup: bool,
959) -> ParsedEffectBody {
960    let final_statement = body.statements.len().saturating_sub(1);
961    let cleanup = allow_cleanup
962        .then(|| body.statements.last())
963        .flatten()
964        .and_then(|statement| parsed_effect_cleanup(statement, source));
965    ParsedEffectBody {
966        statements: body
967            .statements
968            .iter()
969            .enumerate()
970            .filter(|(index, _)| cleanup.is_none() || *index != final_statement)
971            .map(|(index, statement)| {
972                parsed_effect_statement(statement, index == final_statement, source)
973            })
974            .collect(),
975        cleanup,
976    }
977}
978
979fn parsed_effect_cleanup(statement: &Statement<'_>, source: &str) -> Option<ParsedEffectCleanup> {
980    let Statement::ReturnStatement(return_statement) = statement else {
981        return None;
982    };
983    let expression = return_statement.argument.as_ref()?;
984    let (span, is_async, body) = match expression {
985        Expression::ArrowFunctionExpression(handler) if !handler.expression => {
986            (handler.span, handler.r#async, handler.body.as_ref())
987        }
988        Expression::FunctionExpression(handler) => {
989            (handler.span, handler.r#async, handler.body.as_deref()?)
990        }
991        _ => return None,
992    };
993    Some(ParsedEffectCleanup {
994        span: source_span(source, span),
995        is_async,
996        body: Box::new(parsed_effect_body(body, source)),
997    })
998}
999
1000fn parsed_effect_statement(
1001    statement: &Statement<'_>,
1002    is_final: bool,
1003    source: &str,
1004) -> ParsedEffectStatement {
1005    let span = source_span(source, statement.span());
1006    let kind = match statement {
1007        Statement::EmptyStatement(_) => ParsedEffectStatementKind::Empty,
1008        Statement::ExpressionStatement(statement) => match &statement.expression {
1009            Expression::AssignmentExpression(assignment) if assignment.operator.as_str() == "=" => {
1010                match (
1011                    parsed_effect_assignment_target(&assignment.left, source),
1012                    parsed_effect_expression(&assignment.right, source),
1013                ) {
1014                    (Some(target), Some(value)) => {
1015                        ParsedEffectStatementKind::StaticMemberAssignment { target, value }
1016                    }
1017                    _ => ParsedEffectStatementKind::Unsupported(
1018                        ParsedUnsupportedEffectStatementKind::UnsupportedExpression,
1019                    ),
1020                }
1021            }
1022            Expression::AssignmentExpression(_) | Expression::UpdateExpression(_) => {
1023                ParsedEffectStatementKind::Unsupported(
1024                    ParsedUnsupportedEffectStatementKind::CompoundAssignment,
1025                )
1026            }
1027            Expression::CallExpression(call) => match (
1028                parsed_effect_expression(&call.callee, source),
1029                call.arguments
1030                    .iter()
1031                    .map(|argument| {
1032                        argument
1033                            .as_expression()
1034                            .and_then(|expression| parsed_effect_expression(expression, source))
1035                    })
1036                    .collect::<Option<Vec<_>>>(),
1037            ) {
1038                (Some(callee), Some(arguments)) => {
1039                    ParsedEffectStatementKind::CapabilityCall { callee, arguments }
1040                }
1041                _ => ParsedEffectStatementKind::Unsupported(
1042                    ParsedUnsupportedEffectStatementKind::UnsupportedExpression,
1043                ),
1044            },
1045            _ => ParsedEffectStatementKind::Unsupported(
1046                ParsedUnsupportedEffectStatementKind::UnsupportedExpression,
1047            ),
1048        },
1049        Statement::ReturnStatement(statement) => match &statement.argument {
1050            None if is_final => ParsedEffectStatementKind::EffectReturn { value: None },
1051            Some(value) => parsed_effect_expression(value, source).map_or_else(
1052                || {
1053                    ParsedEffectStatementKind::Unsupported(
1054                        ParsedUnsupportedEffectStatementKind::CleanupReturnCandidate,
1055                    )
1056                },
1057                |value| ParsedEffectStatementKind::EffectReturn { value: Some(value) },
1058            ),
1059            None => ParsedEffectStatementKind::Unsupported(
1060                ParsedUnsupportedEffectStatementKind::UnsupportedExpression,
1061            ),
1062        },
1063        statement if statement.as_declaration().is_some() => {
1064            ParsedEffectStatementKind::Unsupported(
1065                ParsedUnsupportedEffectStatementKind::LocalDeclaration,
1066            )
1067        }
1068        Statement::IfStatement(_) | Statement::SwitchStatement(_) => {
1069            ParsedEffectStatementKind::Unsupported(ParsedUnsupportedEffectStatementKind::Branch)
1070        }
1071        Statement::ForInStatement(_)
1072        | Statement::ForOfStatement(_)
1073        | Statement::ForStatement(_)
1074        | Statement::WhileStatement(_)
1075        | Statement::DoWhileStatement(_) => {
1076            ParsedEffectStatementKind::Unsupported(ParsedUnsupportedEffectStatementKind::Loop)
1077        }
1078        Statement::BlockStatement(_) => ParsedEffectStatementKind::Unsupported(
1079            ParsedUnsupportedEffectStatementKind::NestedBlock,
1080        ),
1081        Statement::TryStatement(_) | Statement::ThrowStatement(_) => {
1082            ParsedEffectStatementKind::Unsupported(
1083                ParsedUnsupportedEffectStatementKind::ExceptionHandling,
1084            )
1085        }
1086        _ => ParsedEffectStatementKind::Unsupported(
1087            ParsedUnsupportedEffectStatementKind::UnsupportedExpression,
1088        ),
1089    };
1090    ParsedEffectStatement { kind, span }
1091}
1092
1093fn parsed_effect_assignment_target(
1094    target: &AssignmentTarget<'_>,
1095    source: &str,
1096) -> Option<ParsedEffectExpression> {
1097    let AssignmentTarget::StaticMemberExpression(member) = target else {
1098        return None;
1099    };
1100    parsed_effect_static_member(
1101        &member.object,
1102        member.property.name.as_str(),
1103        member.span,
1104        source,
1105    )
1106}
1107
1108fn parsed_effect_expression(
1109    expression: &Expression<'_>,
1110    source: &str,
1111) -> Option<ParsedEffectExpression> {
1112    if let Expression::ParenthesizedExpression(parenthesized) = expression {
1113        return parsed_effect_expression(&parenthesized.expression, source);
1114    }
1115    if let Some(value) = serializable_value_from_expression(expression) {
1116        return Some(ParsedEffectExpression {
1117            kind: ParsedEffectExpressionKind::Literal(value),
1118            span: source_span(source, expression.span()),
1119        });
1120    }
1121    match expression {
1122        Expression::Identifier(identifier) => Some(ParsedEffectExpression {
1123            kind: ParsedEffectExpressionKind::Identifier(identifier.name.to_string()),
1124            span: source_span(source, identifier.span),
1125        }),
1126        Expression::ThisExpression(this) => Some(ParsedEffectExpression {
1127            kind: ParsedEffectExpressionKind::Identifier("this".to_string()),
1128            span: source_span(source, this.span),
1129        }),
1130        Expression::StaticMemberExpression(member) => parsed_effect_static_member(
1131            &member.object,
1132            member.property.name.as_str(),
1133            member.span,
1134            source,
1135        ),
1136        Expression::BinaryExpression(binary) => {
1137            let kind = match binary.operator.as_str() {
1138                "+" => ParsedEffectExpressionKind::Arithmetic {
1139                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1140                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1141                    operator: ParsedArithmeticOperator::Add,
1142                },
1143                "-" => ParsedEffectExpressionKind::Arithmetic {
1144                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1145                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1146                    operator: ParsedArithmeticOperator::Subtract,
1147                },
1148                "*" => ParsedEffectExpressionKind::Arithmetic {
1149                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1150                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1151                    operator: ParsedArithmeticOperator::Multiply,
1152                },
1153                "/" => ParsedEffectExpressionKind::Arithmetic {
1154                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1155                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1156                    operator: ParsedArithmeticOperator::Divide,
1157                },
1158                "%" => ParsedEffectExpressionKind::Arithmetic {
1159                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1160                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1161                    operator: ParsedArithmeticOperator::Remainder,
1162                },
1163                "===" => ParsedEffectExpressionKind::Comparison {
1164                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1165                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1166                    operator: ParsedComparisonOperator::Equal,
1167                },
1168                "!==" => ParsedEffectExpressionKind::Comparison {
1169                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1170                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1171                    operator: ParsedComparisonOperator::NotEqual,
1172                },
1173                "<" => ParsedEffectExpressionKind::Comparison {
1174                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1175                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1176                    operator: ParsedComparisonOperator::LessThan,
1177                },
1178                "<=" => ParsedEffectExpressionKind::Comparison {
1179                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1180                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1181                    operator: ParsedComparisonOperator::LessThanOrEqual,
1182                },
1183                ">" => ParsedEffectExpressionKind::Comparison {
1184                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1185                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1186                    operator: ParsedComparisonOperator::GreaterThan,
1187                },
1188                ">=" => ParsedEffectExpressionKind::Comparison {
1189                    left: Box::new(parsed_effect_expression(&binary.left, source)?),
1190                    right: Box::new(parsed_effect_expression(&binary.right, source)?),
1191                    operator: ParsedComparisonOperator::GreaterThanOrEqual,
1192                },
1193                _ => return None,
1194            };
1195            Some(ParsedEffectExpression {
1196                kind,
1197                span: source_span(source, binary.span),
1198            })
1199        }
1200        Expression::LogicalExpression(logical) => {
1201            let kind = match logical.operator.as_str() {
1202                "&&" => ParsedEffectExpressionKind::Logical {
1203                    left: Box::new(parsed_effect_expression(&logical.left, source)?),
1204                    right: Box::new(parsed_effect_expression(&logical.right, source)?),
1205                    operator: ParsedLogicalOperator::And,
1206                },
1207                "||" => ParsedEffectExpressionKind::Logical {
1208                    left: Box::new(parsed_effect_expression(&logical.left, source)?),
1209                    right: Box::new(parsed_effect_expression(&logical.right, source)?),
1210                    operator: ParsedLogicalOperator::Or,
1211                },
1212                "??" => ParsedEffectExpressionKind::NullishCoalescing {
1213                    left: Box::new(parsed_effect_expression(&logical.left, source)?),
1214                    right: Box::new(parsed_effect_expression(&logical.right, source)?),
1215                },
1216                _ => return None,
1217            };
1218            Some(ParsedEffectExpression {
1219                kind,
1220                span: source_span(source, logical.span),
1221            })
1222        }
1223        Expression::UnaryExpression(unary) => {
1224            let operator = match unary.operator.as_str() {
1225                "!" => ParsedUnaryOperator::Not,
1226                "+" => ParsedUnaryOperator::Plus,
1227                "-" => ParsedUnaryOperator::Minus,
1228                _ => return None,
1229            };
1230            Some(ParsedEffectExpression {
1231                kind: ParsedEffectExpressionKind::Unary {
1232                    operand: Box::new(parsed_effect_expression(&unary.argument, source)?),
1233                    operator,
1234                },
1235                span: source_span(source, unary.span),
1236            })
1237        }
1238        _ => None,
1239    }
1240}
1241
1242fn parsed_effect_static_member(
1243    object: &Expression<'_>,
1244    property: &str,
1245    span: Span,
1246    source: &str,
1247) -> Option<ParsedEffectExpression> {
1248    let object = parsed_effect_expression(object, source)?;
1249    let kind = if matches!(&object.kind, ParsedEffectExpressionKind::Identifier(name) if name == "this")
1250    {
1251        ParsedEffectExpressionKind::ThisMember(property.to_string())
1252    } else {
1253        ParsedEffectExpressionKind::MemberAccess {
1254            object: Box::new(object),
1255            property: property.to_string(),
1256        }
1257    };
1258    Some(ParsedEffectExpression {
1259        kind,
1260        span: source_span(source, span),
1261    })
1262}
1263
1264fn collect_method_calls(
1265    statement: &Statement<'_>,
1266    source: &str,
1267    calls: &mut Vec<ParsedMethodCall>,
1268) {
1269    let expression = match statement {
1270        Statement::ExpressionStatement(statement) => Some(&statement.expression),
1271        Statement::ReturnStatement(statement) => statement.argument.as_ref(),
1272        _ => None,
1273    };
1274    if let Some(expression) = expression {
1275        collect_calls_from_expression(expression, source, calls);
1276    }
1277}
1278
1279fn collect_calls_from_expression(
1280    expression: &Expression<'_>,
1281    source: &str,
1282    calls: &mut Vec<ParsedMethodCall>,
1283) {
1284    match expression {
1285        Expression::ParenthesizedExpression(parenthesized) => {
1286            collect_calls_from_expression(&parenthesized.expression, source, calls);
1287        }
1288        Expression::CallExpression(call) => {
1289            if let Some(callee) = expression_summary(&call.callee) {
1290                calls.push(ParsedMethodCall {
1291                    callee,
1292                    span: source_span(source, call.span),
1293                });
1294            }
1295            collect_calls_from_expression(&call.callee, source, calls);
1296            for argument in &call.arguments {
1297                if let Some(expression) = argument.as_expression() {
1298                    collect_calls_from_expression(expression, source, calls);
1299                }
1300            }
1301        }
1302        Expression::BinaryExpression(binary) => {
1303            collect_calls_from_expression(&binary.left, source, calls);
1304            collect_calls_from_expression(&binary.right, source, calls);
1305        }
1306        Expression::LogicalExpression(logical) => {
1307            collect_calls_from_expression(&logical.left, source, calls);
1308            collect_calls_from_expression(&logical.right, source, calls);
1309        }
1310        Expression::UnaryExpression(unary) => {
1311            collect_calls_from_expression(&unary.argument, source, calls);
1312        }
1313        _ => {}
1314    }
1315}
1316
1317fn parsed_computed_expression(
1318    expression: &Expression<'_>,
1319    source: &str,
1320) -> Option<ParsedComputedExpression> {
1321    if let Expression::ParenthesizedExpression(parenthesized) = expression {
1322        return parsed_computed_expression(&parenthesized.expression, source);
1323    }
1324
1325    if let Some(value) = serializable_value_from_expression(expression) {
1326        return Some(ParsedComputedExpression {
1327            kind: ParsedComputedExpressionKind::Literal(value),
1328            span: source_span(source, expression.span()),
1329        });
1330    }
1331
1332    match expression {
1333        Expression::TemplateLiteral(template) => Some(ParsedComputedExpression {
1334            kind: ParsedComputedExpressionKind::Template {
1335                quasis: template
1336                    .quasis
1337                    .iter()
1338                    .map(|quasi| quasi.value.cooked.as_ref().map(ToString::to_string))
1339                    .collect::<Option<Vec<_>>>()?,
1340                expressions: template
1341                    .expressions
1342                    .iter()
1343                    .map(|expression| parsed_computed_expression(expression, source))
1344                    .collect::<Option<Vec<_>>>()?,
1345            },
1346            span: source_span(source, template.span),
1347        }),
1348        Expression::CallExpression(call) => {
1349            let callee = expression_summary(&call.callee)?;
1350            let arguments = call
1351                .arguments
1352                .iter()
1353                .map(|argument| parsed_computed_expression(argument.as_expression()?, source))
1354                .collect::<Option<Vec<_>>>()?;
1355            Some(ParsedComputedExpression {
1356                kind: ParsedComputedExpressionKind::Call { callee, arguments },
1357                span: source_span(source, call.span),
1358            })
1359        }
1360        Expression::StaticMemberExpression(member) => {
1361            let property = member.property.name.to_string();
1362            let kind = if matches!(&member.object, Expression::ThisExpression(_)) {
1363                ParsedComputedExpressionKind::ThisMember(property)
1364            } else {
1365                ParsedComputedExpressionKind::MemberAccess {
1366                    object: Box::new(parsed_computed_expression(&member.object, source)?),
1367                    property,
1368                    optional: member.optional,
1369                }
1370            };
1371            Some(ParsedComputedExpression {
1372                kind,
1373                span: source_span(source, member.span),
1374            })
1375        }
1376        Expression::ChainExpression(chain) => match &chain.expression {
1377            ChainElement::StaticMemberExpression(member) => Some(ParsedComputedExpression {
1378                kind: ParsedComputedExpressionKind::MemberAccess {
1379                    object: Box::new(parsed_computed_expression(&member.object, source)?),
1380                    property: member.property.name.to_string(),
1381                    optional: true,
1382                },
1383                span: source_span(source, chain.span),
1384            }),
1385            _ => None,
1386        },
1387        Expression::ComputedMemberExpression(member) => {
1388            let index = parsed_computed_expression(&member.expression, source)?;
1389            let supported_index = match &index.kind {
1390                ParsedComputedExpressionKind::Literal(ParsedSerializableValue::String(_)) => true,
1391                ParsedComputedExpressionKind::Literal(ParsedSerializableValue::Number(value)) => {
1392                    value.parse::<u64>().is_ok()
1393                }
1394                _ => false,
1395            };
1396            if !supported_index {
1397                return None;
1398            }
1399            Some(ParsedComputedExpression {
1400                kind: ParsedComputedExpressionKind::IndexAccess {
1401                    object: Box::new(parsed_computed_expression(&member.object, source)?),
1402                    index: Box::new(index),
1403                },
1404                span: source_span(source, member.span),
1405            })
1406        }
1407        Expression::ConditionalExpression(conditional) => Some(ParsedComputedExpression {
1408            kind: ParsedComputedExpressionKind::Conditional {
1409                condition: Box::new(parsed_computed_expression(&conditional.test, source)?),
1410                when_true: Box::new(parsed_computed_expression(&conditional.consequent, source)?),
1411                when_false: Box::new(parsed_computed_expression(&conditional.alternate, source)?),
1412            },
1413            span: source_span(source, conditional.span),
1414        }),
1415        Expression::BinaryExpression(binary) => {
1416            let operator = match binary.operator.as_str() {
1417                "+" => ParsedComputedExpressionKind::Arithmetic {
1418                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1419                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1420                    operator: ParsedArithmeticOperator::Add,
1421                },
1422                "-" => ParsedComputedExpressionKind::Arithmetic {
1423                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1424                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1425                    operator: ParsedArithmeticOperator::Subtract,
1426                },
1427                "*" => ParsedComputedExpressionKind::Arithmetic {
1428                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1429                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1430                    operator: ParsedArithmeticOperator::Multiply,
1431                },
1432                "/" => ParsedComputedExpressionKind::Arithmetic {
1433                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1434                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1435                    operator: ParsedArithmeticOperator::Divide,
1436                },
1437                "%" => ParsedComputedExpressionKind::Arithmetic {
1438                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1439                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1440                    operator: ParsedArithmeticOperator::Remainder,
1441                },
1442                "===" => ParsedComputedExpressionKind::Comparison {
1443                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1444                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1445                    operator: ParsedComparisonOperator::Equal,
1446                },
1447                "!==" => ParsedComputedExpressionKind::Comparison {
1448                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1449                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1450                    operator: ParsedComparisonOperator::NotEqual,
1451                },
1452                "<" => ParsedComputedExpressionKind::Comparison {
1453                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1454                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1455                    operator: ParsedComparisonOperator::LessThan,
1456                },
1457                "<=" => ParsedComputedExpressionKind::Comparison {
1458                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1459                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1460                    operator: ParsedComparisonOperator::LessThanOrEqual,
1461                },
1462                ">" => ParsedComputedExpressionKind::Comparison {
1463                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1464                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1465                    operator: ParsedComparisonOperator::GreaterThan,
1466                },
1467                ">=" => ParsedComputedExpressionKind::Comparison {
1468                    left: Box::new(parsed_computed_expression(&binary.left, source)?),
1469                    right: Box::new(parsed_computed_expression(&binary.right, source)?),
1470                    operator: ParsedComparisonOperator::GreaterThanOrEqual,
1471                },
1472                _ => return None,
1473            };
1474            Some(ParsedComputedExpression {
1475                kind: operator,
1476                span: source_span(source, binary.span),
1477            })
1478        }
1479        Expression::LogicalExpression(logical) => {
1480            let kind = match logical.operator.as_str() {
1481                "&&" => ParsedComputedExpressionKind::Logical {
1482                    left: Box::new(parsed_computed_expression(&logical.left, source)?),
1483                    right: Box::new(parsed_computed_expression(&logical.right, source)?),
1484                    operator: ParsedLogicalOperator::And,
1485                },
1486                "||" => ParsedComputedExpressionKind::Logical {
1487                    left: Box::new(parsed_computed_expression(&logical.left, source)?),
1488                    right: Box::new(parsed_computed_expression(&logical.right, source)?),
1489                    operator: ParsedLogicalOperator::Or,
1490                },
1491                "??" => ParsedComputedExpressionKind::NullishCoalescing {
1492                    left: Box::new(parsed_computed_expression(&logical.left, source)?),
1493                    right: Box::new(parsed_computed_expression(&logical.right, source)?),
1494                },
1495                _ => return None,
1496            };
1497            Some(ParsedComputedExpression {
1498                kind,
1499                span: source_span(source, logical.span),
1500            })
1501        }
1502        Expression::UnaryExpression(unary) => {
1503            let operator = match unary.operator.as_str() {
1504                "!" => ParsedUnaryOperator::Not,
1505                "+" => ParsedUnaryOperator::Plus,
1506                "-" => ParsedUnaryOperator::Minus,
1507                _ => return None,
1508            };
1509            Some(ParsedComputedExpression {
1510                kind: ParsedComputedExpressionKind::Unary {
1511                    operand: Box::new(parsed_computed_expression(&unary.argument, source)?),
1512                    operator,
1513                },
1514                span: source_span(source, unary.span),
1515            })
1516        }
1517        _ => None,
1518    }
1519}
1520
1521fn parsed_return_value(statement: &Statement<'_>) -> Option<ParsedSerializableValue> {
1522    let Statement::ReturnStatement(return_statement) = statement else {
1523        return None;
1524    };
1525    serializable_value_from_expression(return_statement.argument.as_ref()?)
1526}
1527
1528fn parsed_local_variables(statement: &Statement<'_>, source: &str) -> Vec<ParsedLocalVariable> {
1529    let Some(Declaration::VariableDeclaration(declaration)) = statement.as_declaration() else {
1530        return Vec::new();
1531    };
1532    declaration
1533        .declarations
1534        .iter()
1535        .filter_map(|declarator| {
1536            let name = binding_identifier_name(&declarator.id.kind)?;
1537            let value = serializable_value_from_expression(declarator.init.as_ref()?)?;
1538            Some(ParsedLocalVariable {
1539                name,
1540                value,
1541                span: source_span(source, declarator.span),
1542            })
1543        })
1544        .collect()
1545}
1546
1547fn parsed_state_update(statement: &Statement<'_>, source: &str) -> Option<ParsedStateUpdate> {
1548    let Statement::ExpressionStatement(statement) = statement else {
1549        return None;
1550    };
1551
1552    match &statement.expression {
1553        Expression::UpdateExpression(update) => parsed_update_state_update(update, source),
1554        Expression::AssignmentExpression(assignment) => {
1555            parsed_assignment_state_update(assignment, source)
1556        }
1557        _ => None,
1558    }
1559}
1560
1561fn parsed_update_state_update(
1562    update: &oxc_ast::ast::UpdateExpression<'_>,
1563    source: &str,
1564) -> Option<ParsedStateUpdate> {
1565    let operation = match update.operator.as_str() {
1566        "++" => ParsedStateOperation::Increment,
1567        "--" => ParsedStateOperation::Decrement,
1568        _ => return None,
1569    };
1570
1571    let field = this_assignment_target_field(&update.argument)?;
1572
1573    Some(ParsedStateUpdate {
1574        field,
1575        operation,
1576        span: source_span(source, update.span),
1577    })
1578}
1579
1580fn parsed_assignment_state_update(
1581    assignment: &oxc_ast::ast::AssignmentExpression<'_>,
1582    source: &str,
1583) -> Option<ParsedStateUpdate> {
1584    let field = this_assignment_target_field_from_assignment_target(&assignment.left)?;
1585
1586    let operation = match assignment.operator.as_str() {
1587        "+=" => {
1588            ParsedStateOperation::AddAssign(serializable_value_from_expression(&assignment.right)?)
1589        }
1590        "-=" => ParsedStateOperation::SubtractAssign(serializable_value_from_expression(
1591            &assignment.right,
1592        )?),
1593        "=" if toggled_this_field(&assignment.right).as_deref() == Some(field.as_str()) => {
1594            ParsedStateOperation::Toggle
1595        }
1596        "=" => match &assignment.right {
1597            Expression::Identifier(identifier) => {
1598                ParsedStateOperation::AssignParameter(identifier.name.to_string())
1599            }
1600            expression => {
1601                ParsedStateOperation::Assign(serializable_value_from_expression(expression)?)
1602            }
1603        },
1604        _ => return None,
1605    };
1606
1607    Some(ParsedStateUpdate {
1608        field,
1609        operation,
1610        span: source_span(source, assignment.span),
1611    })
1612}
1613
1614fn this_assignment_target_field(target: &SimpleAssignmentTarget<'_>) -> Option<String> {
1615    let SimpleAssignmentTarget::StaticMemberExpression(member) = target else {
1616        return None;
1617    };
1618
1619    let Expression::ThisExpression(_) = &member.object else {
1620        return None;
1621    };
1622
1623    Some(member.property.name.to_string())
1624}
1625
1626fn this_assignment_target_field_from_assignment_target(
1627    target: &AssignmentTarget<'_>,
1628) -> Option<String> {
1629    let AssignmentTarget::StaticMemberExpression(member) = target else {
1630        return None;
1631    };
1632
1633    let Expression::ThisExpression(_) = &member.object else {
1634        return None;
1635    };
1636
1637    Some(member.property.name.to_string())
1638}
1639
1640fn parse_statement_for_jsx(
1641    statement: &Statement<'_>,
1642    source: &str,
1643    jsx_roots: &mut Vec<ParsedJsxNode>,
1644    bindings: &mut Vec<String>,
1645) {
1646    if let Statement::ReturnStatement(return_statement) = statement {
1647        if let Some(argument) = &return_statement.argument {
1648            parse_expression_for_jsx(argument, source, jsx_roots, bindings);
1649        }
1650    }
1651}
1652
1653fn parse_expression_for_jsx(
1654    expression: &Expression<'_>,
1655    source: &str,
1656    jsx_roots: &mut Vec<ParsedJsxNode>,
1657    bindings: &mut Vec<String>,
1658) {
1659    match expression {
1660        Expression::ParenthesizedExpression(parenthesized) => {
1661            parse_expression_for_jsx(&parenthesized.expression, source, jsx_roots, bindings);
1662        }
1663        Expression::JSXElement(element) => {
1664            for child in &element.children {
1665                parse_jsx_child(child, bindings);
1666            }
1667
1668            if let Some(element) = parsed_jsx_element(element, source) {
1669                jsx_roots.push(ParsedJsxNode::Element(element));
1670            }
1671        }
1672        Expression::JSXFragment(fragment) => {
1673            for child in &fragment.children {
1674                parse_jsx_child(child, bindings);
1675            }
1676
1677            jsx_roots.push(ParsedJsxNode::Fragment(parsed_jsx_fragment(
1678                fragment, source,
1679            )));
1680        }
1681        _ => {}
1682    }
1683}
1684
1685fn parse_jsx_child(child: &JSXChild<'_>, bindings: &mut Vec<String>) {
1686    match child {
1687        JSXChild::ExpressionContainer(container) => {
1688            if let Some(binding) = jsx_expression_binding_summary(&container.expression) {
1689                bindings.push(binding);
1690            }
1691        }
1692        JSXChild::Element(element) => {
1693            for child in &element.children {
1694                parse_jsx_child(child, bindings);
1695            }
1696        }
1697        JSXChild::Fragment(fragment) => {
1698            for child in &fragment.children {
1699                parse_jsx_child(child, bindings);
1700            }
1701        }
1702        _ => {}
1703    }
1704}
1705
1706fn parsed_jsx_child(child: &JSXChild<'_>, source: &str) -> Option<ParsedJsxChild> {
1707    match child {
1708        JSXChild::Text(text) => {
1709            let normalized = normalize_jsx_text(&text.value);
1710
1711            if normalized.is_empty() {
1712                None
1713            } else {
1714                Some(ParsedJsxChild::Text {
1715                    value: normalized,
1716                    span: jsx_text_value_span(source, text.value.as_str(), text.span),
1717                })
1718            }
1719        }
1720        JSXChild::ExpressionContainer(container) => parsed_jsx_expression_child(
1721            &container.expression,
1722            source,
1723            source_span(source, container.span),
1724        ),
1725        JSXChild::Element(element) => {
1726            parsed_jsx_element(element, source).map(ParsedJsxChild::Element)
1727        }
1728        JSXChild::Fragment(fragment) => Some(ParsedJsxChild::Fragment(parsed_jsx_fragment(
1729            fragment, source,
1730        ))),
1731        _ => None,
1732    }
1733}
1734
1735fn parsed_jsx_expression_child(
1736    expression: &JSXExpression<'_>,
1737    source: &str,
1738    span: SourceSpan,
1739) -> Option<ParsedJsxChild> {
1740    let expression = expression.as_expression()?;
1741
1742    if let Expression::CallExpression(call) = expression {
1743        if let Some(list) = parsed_jsx_list(call, source) {
1744            return Some(ParsedJsxChild::List(list));
1745        }
1746    }
1747
1748    if let Expression::ConditionalExpression(conditional) = expression {
1749        return parsed_jsx_conditional(conditional, source).map(ParsedJsxChild::Conditional);
1750    }
1751
1752    if let Expression::LogicalExpression(logical) = expression {
1753        return parsed_jsx_logical_and(logical, source).map(ParsedJsxChild::Conditional);
1754    }
1755
1756    expression_summary(expression).map(|expression| ParsedJsxChild::Binding { expression, span })
1757}
1758
1759fn parsed_jsx_list(call: &oxc_ast::ast::CallExpression<'_>, source: &str) -> Option<ParsedJsxList> {
1760    let Expression::StaticMemberExpression(member) = &call.callee else {
1761        return None;
1762    };
1763
1764    if member.property.name != "map" || call.arguments.len() != 1 {
1765        return None;
1766    }
1767
1768    let iterable = expression_summary(&member.object)?;
1769    let Argument::ArrowFunctionExpression(callback) = &call.arguments[0] else {
1770        return None;
1771    };
1772
1773    if callback.params.rest.is_some() || !(1..=2).contains(&callback.params.items.len()) {
1774        return None;
1775    }
1776
1777    let item_variable = binding_identifier_name(&callback.params.items[0].pattern.kind)?;
1778    let index_variable = match callback.params.items.get(1) {
1779        Some(parameter) => Some(binding_identifier_name(&parameter.pattern.kind)?),
1780        None => None,
1781    };
1782    let item_template = parsed_jsx_node_from_expression(callback.get_expression()?, source)?;
1783    let key_expression = key_expression_from_jsx_node(&item_template).unwrap_or_default();
1784
1785    Some(ParsedJsxList {
1786        iterable,
1787        item_variable,
1788        index_variable,
1789        key_expression,
1790        span: source_span(source, call.span),
1791        item_template,
1792    })
1793}
1794
1795fn binding_identifier_name(pattern: &BindingPatternKind<'_>) -> Option<String> {
1796    let BindingPatternKind::BindingIdentifier(identifier) = pattern else {
1797        return None;
1798    };
1799
1800    Some(identifier.name.to_string())
1801}
1802
1803fn key_expression_from_jsx_node(node: &ParsedJsxNode) -> Option<String> {
1804    let ParsedJsxNode::Element(element) = node else {
1805        return None;
1806    };
1807
1808    element.attributes.iter().find_map(|attribute| {
1809        if attribute.name != "key" {
1810            return None;
1811        }
1812
1813        let ParsedJsxAttributeValue::Expression(Some(expression)) = &attribute.value else {
1814            return None;
1815        };
1816
1817        Some(expression.clone())
1818    })
1819}
1820
1821fn parsed_jsx_conditional(
1822    conditional: &oxc_ast::ast::ConditionalExpression<'_>,
1823    source: &str,
1824) -> Option<ParsedJsxConditional> {
1825    let condition = expression_summary(&conditional.test)?;
1826    let when_true = parsed_jsx_node_from_expression(&conditional.consequent, source)?;
1827    let when_false = parsed_jsx_node_from_expression(&conditional.alternate, source)?;
1828
1829    Some(ParsedJsxConditional {
1830        condition,
1831        span: source_span(source, conditional.span),
1832        when_true,
1833        when_false: Some(when_false),
1834    })
1835}
1836
1837fn parsed_jsx_logical_and(
1838    logical: &oxc_ast::ast::LogicalExpression<'_>,
1839    source: &str,
1840) -> Option<ParsedJsxConditional> {
1841    if logical.operator.as_str() != "&&" {
1842        return None;
1843    }
1844
1845    let condition = expression_summary(&logical.left)?;
1846    let when_true = parsed_jsx_node_from_expression(&logical.right, source)?;
1847
1848    Some(ParsedJsxConditional {
1849        condition,
1850        span: source_span(source, logical.span),
1851        when_true,
1852        when_false: None,
1853    })
1854}
1855
1856fn parsed_jsx_node_from_expression(
1857    expression: &Expression<'_>,
1858    source: &str,
1859) -> Option<ParsedJsxNode> {
1860    match expression {
1861        Expression::ParenthesizedExpression(parenthesized) => {
1862            parsed_jsx_node_from_expression(&parenthesized.expression, source)
1863        }
1864        Expression::JSXElement(element) => {
1865            parsed_jsx_element(element, source).map(ParsedJsxNode::Element)
1866        }
1867        Expression::JSXFragment(fragment) => Some(ParsedJsxNode::Fragment(parsed_jsx_fragment(
1868            fragment, source,
1869        ))),
1870        _ => None,
1871    }
1872}
1873
1874fn parsed_jsx_fragment(fragment: &JSXFragment<'_>, source: &str) -> ParsedJsxFragment {
1875    ParsedJsxFragment {
1876        span: source_span(source, fragment.span),
1877        children: fragment
1878            .children
1879            .iter()
1880            .filter_map(|child| parsed_jsx_child(child, source))
1881            .collect(),
1882    }
1883}
1884
1885fn parsed_jsx_element(
1886    element: &oxc_ast::ast::JSXElement<'_>,
1887    source: &str,
1888) -> Option<ParsedJsxElement> {
1889    let name =
1890        jsx_element_name(&element.opening_element.name).unwrap_or_else(|| "<unknown>".to_string());
1891
1892    let attributes = element
1893        .opening_element
1894        .attributes
1895        .iter()
1896        .map(|attribute| parsed_jsx_attribute(attribute, source))
1897        .collect::<Vec<_>>();
1898
1899    let event_handlers = element
1900        .opening_element
1901        .attributes
1902        .iter()
1903        .filter_map(|attribute| jsx_event_handler(attribute, source))
1904        .collect::<Vec<_>>();
1905
1906    let children = element
1907        .children
1908        .iter()
1909        .filter_map(|child| parsed_jsx_child(child, source))
1910        .collect::<Vec<_>>();
1911
1912    Some(ParsedJsxElement {
1913        name,
1914        name_span: source_span(source, element.opening_element.name.span()),
1915        span: source_span(source, element.span),
1916        attributes,
1917        event_handlers,
1918        children,
1919    })
1920}
1921
1922fn normalize_jsx_text(value: &str) -> String {
1923    value.split_whitespace().collect::<Vec<_>>().join(" ")
1924}
1925
1926fn jsx_text_value_span(source: &str, value: &str, span: Span) -> SourceSpan {
1927    let leading_whitespace = value
1928        .char_indices()
1929        .find(|(_, character)| !character.is_whitespace())
1930        .map_or(value.len(), |(index, _)| index);
1931    let trailing_whitespace = value
1932        .char_indices()
1933        .rev()
1934        .find(|(_, character)| !character.is_whitespace())
1935        .map_or(value.len(), |(index, character)| {
1936            index + character.len_utf8()
1937        });
1938
1939    source_span_from_offsets(
1940        source,
1941        span.start as usize + leading_whitespace,
1942        span.start as usize + trailing_whitespace,
1943    )
1944}
1945
1946fn property_key_name(key: &PropertyKey<'_>) -> Option<String> {
1947    match key {
1948        PropertyKey::StaticIdentifier(identifier) => Some(identifier.name.to_string()),
1949        PropertyKey::PrivateIdentifier(identifier) => Some(format!("#{}", identifier.name)),
1950        PropertyKey::StringLiteral(literal) => Some(literal.value.to_string()),
1951        PropertyKey::NumericLiteral(literal) => Some(literal.raw.as_ref()?.to_string()),
1952        _ => None,
1953    }
1954}
1955
1956fn jsx_expression_summary(expression: &JSXExpression<'_>) -> Option<String> {
1957    if let Some(expression) = expression.as_expression() {
1958        return expression_summary(expression);
1959    }
1960
1961    None
1962}
1963
1964fn jsx_expression_binding_summary(expression: &JSXExpression<'_>) -> Option<String> {
1965    let expression = expression.as_expression()?;
1966
1967    if let Expression::ConditionalExpression(conditional) = expression {
1968        return expression_summary(&conditional.test);
1969    }
1970
1971    if let Expression::LogicalExpression(logical) = expression {
1972        if logical.operator.as_str() == "&&" {
1973            return expression_summary(&logical.left);
1974        }
1975    }
1976
1977    if let Expression::CallExpression(call) = expression {
1978        if let Some(iterable) = list_iterable_dependency(call) {
1979            return Some(iterable);
1980        }
1981    }
1982
1983    expression_summary(expression)
1984}
1985
1986fn list_iterable_dependency(call: &oxc_ast::ast::CallExpression<'_>) -> Option<String> {
1987    let Expression::StaticMemberExpression(member) = &call.callee else {
1988        return None;
1989    };
1990
1991    if member.property.name != "map" {
1992        return None;
1993    }
1994
1995    expression_summary(&member.object)
1996}
1997
1998fn expression_summary(expression: &Expression<'_>) -> Option<String> {
1999    match expression {
2000        Expression::CallExpression(call) => {
2001            let callee = expression_summary(&call.callee)?;
2002            Some(format!("{callee}(...)"))
2003        }
2004        Expression::Identifier(identifier) => Some(identifier.name.to_string()),
2005        Expression::ThisExpression(_) => Some("this".to_string()),
2006        Expression::StaticMemberExpression(member) => {
2007            let object = expression_summary(&member.object)?;
2008            Some(format!("{object}.{}", member.property.name))
2009        }
2010        Expression::NumericLiteral(literal) => Some(literal.raw.as_ref()?.to_string()),
2011        Expression::StringLiteral(literal) => Some(format!("{:?}", literal.value.as_str())),
2012        _ => None,
2013    }
2014}
2015
2016fn state_initial_value(expression: &Expression<'_>) -> Option<ParsedSerializableValue> {
2017    let Expression::CallExpression(call) = expression else {
2018        return None;
2019    };
2020
2021    let Expression::Identifier(callee) = &call.callee else {
2022        return None;
2023    };
2024
2025    if callee.name != "state" {
2026        return None;
2027    }
2028
2029    call.arguments.first().and_then(state_argument_literal)
2030}
2031
2032fn state_initial_constant_expression(
2033    expression: &Expression<'_>,
2034    source: &str,
2035) -> Option<ParsedConstantExpression> {
2036    let Expression::CallExpression(call) = expression else {
2037        return None;
2038    };
2039    let Expression::Identifier(callee) = &call.callee else {
2040        return None;
2041    };
2042    if callee.name != "state" || call.arguments.len() != 1 {
2043        return None;
2044    }
2045
2046    let expression = parsed_constant_expression(call.arguments[0].as_expression()?, source)?;
2047    (!matches!(
2048        expression.kind,
2049        ParsedConstantExpressionKind::Primitive(_) | ParsedConstantExpressionKind::Boolean(_)
2050    ))
2051    .then_some(expression)
2052}
2053
2054fn parsed_constant_expression(
2055    expression: &Expression<'_>,
2056    source: &str,
2057) -> Option<ParsedConstantExpression> {
2058    if let Expression::ParenthesizedExpression(parenthesized) = expression {
2059        return parsed_constant_expression(&parenthesized.expression, source);
2060    }
2061    if let Expression::BooleanLiteral(literal) = expression {
2062        return Some(ParsedConstantExpression {
2063            kind: ParsedConstantExpressionKind::Boolean(literal.value),
2064            span: source_span(source, literal.span),
2065        });
2066    }
2067    if let Some(primitive) = parsed_primitive_constant_expression(expression, source) {
2068        return Some(primitive);
2069    }
2070    if let Some(unary) = parsed_unary_constant_expression(expression, source) {
2071        return Some(unary);
2072    }
2073    if let Some(logical) = parsed_logical_expression(expression, source) {
2074        return Some(logical);
2075    }
2076    if let Some(comparison) = parsed_comparison_expression(expression, source) {
2077        return Some(comparison);
2078    }
2079    if let Some(nullish) = parsed_nullish_coalescing_expression(expression, source) {
2080        return Some(nullish);
2081    }
2082
2083    let arithmetic = parsed_arithmetic_expression(expression, source)?;
2084    matches!(
2085        arithmetic.kind,
2086        ParsedArithmeticExpressionKind::Binary { .. }
2087    )
2088    .then_some(ParsedConstantExpression {
2089        span: arithmetic.span,
2090        kind: ParsedConstantExpressionKind::Arithmetic(arithmetic),
2091    })
2092}
2093
2094fn parsed_unary_constant_expression(
2095    expression: &Expression<'_>,
2096    source: &str,
2097) -> Option<ParsedConstantExpression> {
2098    let Expression::UnaryExpression(unary) = expression else {
2099        return None;
2100    };
2101    let operator = match unary.operator.as_str() {
2102        "!" => ParsedUnaryOperator::Not,
2103        "+" => ParsedUnaryOperator::Plus,
2104        "-" => ParsedUnaryOperator::Minus,
2105        _ => return None,
2106    };
2107    let operand = parsed_constant_expression(&unary.argument, source)?;
2108    let valid = match operator {
2109        ParsedUnaryOperator::Not => matches!(
2110            operand.kind,
2111            ParsedConstantExpressionKind::Boolean(_)
2112                | ParsedConstantExpressionKind::Comparison { .. }
2113                | ParsedConstantExpressionKind::Logical { .. }
2114        ),
2115        ParsedUnaryOperator::Plus | ParsedUnaryOperator::Minus => matches!(
2116            operand.kind,
2117            ParsedConstantExpressionKind::Primitive(ParsedSerializableValue::Number(_))
2118                | ParsedConstantExpressionKind::Arithmetic(_)
2119        ),
2120    };
2121    valid.then_some(ParsedConstantExpression {
2122        kind: ParsedConstantExpressionKind::Unary {
2123            operator,
2124            operand: Box::new(operand),
2125        },
2126        span: source_span(source, unary.span),
2127    })
2128}
2129
2130fn parsed_primitive_constant_expression(
2131    expression: &Expression<'_>,
2132    source: &str,
2133) -> Option<ParsedConstantExpression> {
2134    let (value, span) = match expression {
2135        Expression::NullLiteral(literal) => (ParsedSerializableValue::Null, literal.span),
2136        Expression::NumericLiteral(literal) => (
2137            ParsedSerializableValue::Number(literal.raw.as_ref()?.to_string()),
2138            literal.span,
2139        ),
2140        Expression::StringLiteral(literal) => (
2141            ParsedSerializableValue::String(literal.value.to_string()),
2142            literal.span,
2143        ),
2144        _ => return None,
2145    };
2146
2147    Some(ParsedConstantExpression {
2148        kind: ParsedConstantExpressionKind::Primitive(value),
2149        span: source_span(source, span),
2150    })
2151}
2152
2153fn parsed_logical_expression(
2154    expression: &Expression<'_>,
2155    source: &str,
2156) -> Option<ParsedConstantExpression> {
2157    let Expression::LogicalExpression(logical) = expression else {
2158        return None;
2159    };
2160    let operator = match logical.operator.as_str() {
2161        "&&" => ParsedLogicalOperator::And,
2162        "||" => ParsedLogicalOperator::Or,
2163        _ => return None,
2164    };
2165
2166    Some(ParsedConstantExpression {
2167        kind: ParsedConstantExpressionKind::Logical {
2168            operator,
2169            left: Box::new(parsed_boolean_constant_expression(&logical.left, source)?),
2170            right: Box::new(parsed_boolean_constant_expression(&logical.right, source)?),
2171        },
2172        span: source_span(source, logical.span),
2173    })
2174}
2175
2176fn parsed_nullish_coalescing_expression(
2177    expression: &Expression<'_>,
2178    source: &str,
2179) -> Option<ParsedConstantExpression> {
2180    let Expression::LogicalExpression(logical) = expression else {
2181        return None;
2182    };
2183    if logical.operator.as_str() != "??" {
2184        return None;
2185    }
2186
2187    Some(ParsedConstantExpression {
2188        kind: ParsedConstantExpressionKind::NullishCoalescing {
2189            left: Box::new(parsed_nullish_constant_expression(&logical.left, source)?),
2190            right: Box::new(parsed_nullish_constant_expression(&logical.right, source)?),
2191        },
2192        span: source_span(source, logical.span),
2193    })
2194}
2195
2196fn parsed_boolean_constant_expression(
2197    expression: &Expression<'_>,
2198    source: &str,
2199) -> Option<ParsedConstantExpression> {
2200    let expression = parsed_constant_expression(expression, source)?;
2201    matches!(
2202        expression.kind,
2203        ParsedConstantExpressionKind::Boolean(_)
2204            | ParsedConstantExpressionKind::Comparison { .. }
2205            | ParsedConstantExpressionKind::Logical { .. }
2206    )
2207    .then_some(expression)
2208}
2209
2210fn parsed_nullish_constant_expression(
2211    expression: &Expression<'_>,
2212    source: &str,
2213) -> Option<ParsedConstantExpression> {
2214    let expression = parsed_constant_expression(expression, source)?;
2215    matches!(
2216        expression.kind,
2217        ParsedConstantExpressionKind::Primitive(_)
2218            | ParsedConstantExpressionKind::Boolean(_)
2219            | ParsedConstantExpressionKind::Arithmetic(_)
2220            | ParsedConstantExpressionKind::Comparison { .. }
2221            | ParsedConstantExpressionKind::Logical { .. }
2222            | ParsedConstantExpressionKind::NullishCoalescing { .. }
2223    )
2224    .then_some(expression)
2225}
2226
2227fn parsed_comparison_expression(
2228    expression: &Expression<'_>,
2229    source: &str,
2230) -> Option<ParsedConstantExpression> {
2231    if let Expression::ParenthesizedExpression(parenthesized) = expression {
2232        return parsed_comparison_expression(&parenthesized.expression, source);
2233    }
2234    let Expression::BinaryExpression(binary) = expression else {
2235        return None;
2236    };
2237    let operator = match binary.operator.as_str() {
2238        "===" => ParsedComparisonOperator::Equal,
2239        "!==" => ParsedComparisonOperator::NotEqual,
2240        "<" => ParsedComparisonOperator::LessThan,
2241        "<=" => ParsedComparisonOperator::LessThanOrEqual,
2242        ">" => ParsedComparisonOperator::GreaterThan,
2243        ">=" => ParsedComparisonOperator::GreaterThanOrEqual,
2244        _ => return None,
2245    };
2246
2247    Some(ParsedConstantExpression {
2248        kind: ParsedConstantExpressionKind::Comparison {
2249            operator,
2250            left: parsed_arithmetic_expression(&binary.left, source)?,
2251            right: parsed_arithmetic_expression(&binary.right, source)?,
2252        },
2253        span: source_span(source, binary.span),
2254    })
2255}
2256
2257fn parsed_arithmetic_expression(
2258    expression: &Expression<'_>,
2259    source: &str,
2260) -> Option<ParsedArithmeticExpression> {
2261    match expression {
2262        Expression::ParenthesizedExpression(parenthesized) => {
2263            parsed_arithmetic_expression(&parenthesized.expression, source)
2264        }
2265        Expression::NumericLiteral(literal) => Some(ParsedArithmeticExpression {
2266            kind: ParsedArithmeticExpressionKind::Number(literal.raw.as_ref()?.to_string()),
2267            span: source_span(source, literal.span),
2268        }),
2269        Expression::BinaryExpression(binary) => {
2270            let operator = match binary.operator.as_str() {
2271                "+" => ParsedArithmeticOperator::Add,
2272                "-" => ParsedArithmeticOperator::Subtract,
2273                "*" => ParsedArithmeticOperator::Multiply,
2274                "/" => ParsedArithmeticOperator::Divide,
2275                "%" => ParsedArithmeticOperator::Remainder,
2276                _ => return None,
2277            };
2278            let left = parsed_arithmetic_expression(&binary.left, source)?;
2279            let right = parsed_arithmetic_expression(&binary.right, source)?;
2280
2281            Some(ParsedArithmeticExpression {
2282                kind: ParsedArithmeticExpressionKind::Binary {
2283                    operator,
2284                    left: Box::new(left),
2285                    right: Box::new(right),
2286                },
2287                span: source_span(source, binary.span),
2288            })
2289        }
2290        _ => None,
2291    }
2292}
2293
2294fn state_argument_literal(argument: &Argument<'_>) -> Option<ParsedSerializableValue> {
2295    serializable_value_from_expression(argument.as_expression()?)
2296}
2297
2298fn serializable_value_from_expression(
2299    expression: &Expression<'_>,
2300) -> Option<ParsedSerializableValue> {
2301    match expression {
2302        Expression::NullLiteral(_) => Some(ParsedSerializableValue::Null),
2303        Expression::NumericLiteral(literal) => literal
2304            .raw
2305            .as_ref()
2306            .map(ToString::to_string)
2307            .map(ParsedSerializableValue::Number),
2308        Expression::StringLiteral(literal) => {
2309            Some(ParsedSerializableValue::String(literal.value.to_string()))
2310        }
2311        Expression::BooleanLiteral(literal) => {
2312            Some(ParsedSerializableValue::Boolean(literal.value))
2313        }
2314        Expression::ArrayExpression(array) => array
2315            .elements
2316            .iter()
2317            .map(|element| serializable_value_from_expression(element.as_expression()?))
2318            .collect::<Option<Vec<_>>>()
2319            .map(ParsedSerializableValue::Array),
2320        Expression::ObjectExpression(object) => object
2321            .properties
2322            .iter()
2323            .map(|property| {
2324                let ObjectPropertyKind::ObjectProperty(property) = property else {
2325                    return None;
2326                };
2327
2328                if property.computed || property.method || property.kind != PropertyKind::Init {
2329                    return None;
2330                }
2331
2332                let key = object_property_key_name(&property.key)?;
2333                let value = serializable_value_from_expression(&property.value)?;
2334                Some((key, value))
2335            })
2336            .collect::<Option<BTreeMap<_, _>>>()
2337            .map(ParsedSerializableValue::Object),
2338        _ => None,
2339    }
2340}
2341
2342fn object_property_key_name(key: &PropertyKey<'_>) -> Option<String> {
2343    match key {
2344        PropertyKey::StaticIdentifier(_)
2345        | PropertyKey::StringLiteral(_)
2346        | PropertyKey::NumericLiteral(_) => property_key_name(key),
2347        PropertyKey::PrivateIdentifier(_) => None,
2348        _ => None,
2349    }
2350}
2351
2352fn toggled_this_field(expression: &Expression<'_>) -> Option<String> {
2353    let Expression::UnaryExpression(unary) = expression else {
2354        return None;
2355    };
2356
2357    if unary.operator.as_str() != "!" {
2358        return None;
2359    }
2360
2361    this_member_expression_field(&unary.argument)
2362}
2363
2364fn this_member_expression_field(expression: &Expression<'_>) -> Option<String> {
2365    let Expression::StaticMemberExpression(member) = expression else {
2366        return None;
2367    };
2368
2369    let Expression::ThisExpression(_) = &member.object else {
2370        return None;
2371    };
2372
2373    Some(member.property.name.to_string())
2374}
2375
2376fn jsx_element_name(name: &JSXElementName<'_>) -> Option<String> {
2377    match name {
2378        JSXElementName::Identifier(identifier) => Some(identifier.name.to_string()),
2379        JSXElementName::IdentifierReference(identifier) => Some(identifier.name.to_string()),
2380        JSXElementName::NamespacedName(namespaced) => Some(format!(
2381            "{}:{}",
2382            namespaced.namespace.name, namespaced.name.name
2383        )),
2384        JSXElementName::MemberExpression(member) => Some(jsx_member_expression_name(member)),
2385        JSXElementName::ThisExpression(_) => Some("this".to_string()),
2386    }
2387}
2388
2389fn jsx_member_expression_name(member: &JSXMemberExpression<'_>) -> String {
2390    let object = match &member.object {
2391        JSXMemberExpressionObject::IdentifierReference(identifier) => identifier.name.to_string(),
2392        JSXMemberExpressionObject::MemberExpression(member) => jsx_member_expression_name(member),
2393        JSXMemberExpressionObject::ThisExpression(_) => "this".to_string(),
2394    };
2395    format!("{object}.{}", member.property.name)
2396}
2397
2398fn parsed_jsx_attribute(attribute: &JSXAttributeItem<'_>, source: &str) -> ParsedJsxAttribute {
2399    match attribute {
2400        JSXAttributeItem::Attribute(attribute) => {
2401            let name = jsx_attribute_name(&attribute.name);
2402            let expression = attribute.value.as_ref().and_then(|value| match value {
2403                JSXAttributeValue::ExpressionContainer(container) => {
2404                    container.expression.as_expression()
2405                }
2406                _ => None,
2407            });
2408            let value = match &attribute.value {
2409                Some(JSXAttributeValue::StringLiteral(literal)) => {
2410                    ParsedJsxAttributeValue::Static(literal.value.to_string())
2411                }
2412                Some(JSXAttributeValue::ExpressionContainer(container)) => {
2413                    ParsedJsxAttributeValue::Expression(jsx_expression_summary(
2414                        &container.expression,
2415                    ))
2416                }
2417                Some(JSXAttributeValue::Element(_)) | Some(JSXAttributeValue::Fragment(_)) => {
2418                    ParsedJsxAttributeValue::Unsupported
2419                }
2420                None => ParsedJsxAttributeValue::Boolean,
2421            };
2422
2423            ParsedJsxAttribute {
2424                name,
2425                value,
2426                name_span: source_span(source, attribute.name.span()),
2427                value_span: attribute
2428                    .value
2429                    .as_ref()
2430                    .map(|value| source_span(source, value.span())),
2431                expression_span: expression
2432                    .map(|expression| source_span(source, expression.span())),
2433                this_member: expression
2434                    .and_then(|expression| parsed_this_member_expression(expression, source)),
2435                constant_value: match &attribute.value {
2436                    Some(JSXAttributeValue::StringLiteral(literal)) => {
2437                        Some(ParsedSerializableValue::String(literal.value.to_string()))
2438                    }
2439                    Some(JSXAttributeValue::ExpressionContainer(container)) => container
2440                        .expression
2441                        .as_expression()
2442                        .and_then(serializable_value_from_expression),
2443                    _ => None,
2444                },
2445                span: source_span(source, attribute.span),
2446            }
2447        }
2448        JSXAttributeItem::SpreadAttribute(spread) => ParsedJsxAttribute {
2449            name: "{...}".to_string(),
2450            value: ParsedJsxAttributeValue::Spread(expression_summary(&spread.argument)),
2451            name_span: source_span(source, spread.span),
2452            value_span: Some(source_span(source, spread.argument.span())),
2453            expression_span: Some(source_span(source, spread.argument.span())),
2454            this_member: parsed_this_member_expression(&spread.argument, source),
2455            constant_value: serializable_value_from_expression(&spread.argument),
2456            span: source_span(source, spread.span),
2457        },
2458    }
2459}
2460
2461fn jsx_attribute_name(name: &JSXAttributeName<'_>) -> String {
2462    match name {
2463        JSXAttributeName::Identifier(identifier) => identifier.name.to_string(),
2464        JSXAttributeName::NamespacedName(namespaced) => {
2465            format!("{}:{}", namespaced.namespace.name, namespaced.name.name)
2466        }
2467    }
2468}
2469
2470fn jsx_event_handler(attribute: &JSXAttributeItem<'_>, source: &str) -> Option<ParsedEventHandler> {
2471    let JSXAttributeItem::Attribute(attribute) = attribute else {
2472        return None;
2473    };
2474
2475    let attribute_name = match &attribute.name {
2476        JSXAttributeName::Identifier(identifier) => identifier.name.as_str(),
2477        JSXAttributeName::NamespacedName(_) => return None,
2478    };
2479
2480    let event = jsx_event_type(attribute_name)?;
2481
2482    let Some(JSXAttributeValue::ExpressionContainer(container)) = &attribute.value else {
2483        return None;
2484    };
2485
2486    let (handler, arguments) = jsx_expression_event_handler_ref(&container.expression)?;
2487
2488    Some(ParsedEventHandler {
2489        event,
2490        handler,
2491        arguments,
2492        span: source_span(source, attribute.span),
2493    })
2494}
2495
2496fn jsx_event_type(attribute_name: &str) -> Option<String> {
2497    let name = attribute_name.strip_prefix("on")?;
2498
2499    if !name.chars().next().is_some_and(char::is_uppercase) {
2500        return None;
2501    }
2502
2503    let mut chars = name.chars();
2504    let first = chars.next()?.to_ascii_lowercase();
2505    let rest = chars.collect::<String>();
2506
2507    Some(format!("{first}{rest}"))
2508}
2509
2510fn jsx_expression_event_handler_ref(
2511    expression: &JSXExpression<'_>,
2512) -> Option<(String, Vec<ParsedSerializableValue>)> {
2513    let expression = expression.as_expression()?;
2514    expression_event_handler_ref(expression)
2515}
2516
2517fn expression_event_handler_ref(
2518    expression: &Expression<'_>,
2519) -> Option<(String, Vec<ParsedSerializableValue>)> {
2520    match expression {
2521        Expression::ArrowFunctionExpression(arrow) => {
2522            for statement in &arrow.body.statements {
2523                if let Statement::ExpressionStatement(statement) = statement {
2524                    if let Some(reference) = expression_event_handler_ref(&statement.expression) {
2525                        return Some(reference);
2526                    }
2527                }
2528            }
2529
2530            None
2531        }
2532        Expression::CallExpression(call) => Some((
2533            expression_summary(&call.callee)?,
2534            call.arguments
2535                .iter()
2536                .map(|argument| serializable_value_from_expression(argument.as_expression()?))
2537                .collect::<Option<Vec<_>>>()?,
2538        )),
2539        Expression::StaticMemberExpression(_) => {
2540            Some((expression_summary(expression)?, Vec::new()))
2541        }
2542        _ => None,
2543    }
2544}
2545
2546fn parse_diagnostic(source: &str, diagnostic: &oxc_diagnostics::OxcDiagnostic) -> ParseDiagnostic {
2547    let severity = match diagnostic.severity {
2548        OxcSeverity::Advice => ParseSeverity::Info,
2549        OxcSeverity::Warning => ParseSeverity::Warning,
2550        OxcSeverity::Error => ParseSeverity::Error,
2551    };
2552
2553    let labels = diagnostic
2554        .labels
2555        .as_ref()
2556        .map(|labels| {
2557            labels
2558                .iter()
2559                .map(|label| {
2560                    let start = label.offset();
2561                    let end = start + label.len();
2562
2563                    ParseLabel {
2564                        span: source_span_from_offsets(source, start, end),
2565                    }
2566                })
2567                .collect::<Vec<_>>()
2568        })
2569        .unwrap_or_default();
2570
2571    ParseDiagnostic {
2572        message: diagnostic.message.to_string(),
2573        severity,
2574        labels,
2575    }
2576}
2577
2578fn source_span(source: &str, span: Span) -> SourceSpan {
2579    source_span_from_offsets(source, span.start as usize, span.end as usize)
2580}
2581
2582fn source_span_from_offsets(source: &str, start: usize, end: usize) -> SourceSpan {
2583    let (line, column) = line_column_at(source, start);
2584
2585    SourceSpan {
2586        start,
2587        end,
2588        line,
2589        column,
2590    }
2591}
2592
2593fn line_column_at(source: &str, offset: usize) -> (usize, usize) {
2594    let clamped = offset.min(source.len());
2595    let prefix = &source[..clamped];
2596
2597    let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
2598
2599    let column = prefix
2600        .rsplit_once('\n')
2601        .map_or(prefix.len() + 1, |(_, tail)| tail.len() + 1);
2602
2603    (line, column)
2604}