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(¶meter.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(¶meter.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}