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