-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(es/typescript): preserve Flow component type semantics #12090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
c412458
ea9faf1
a1ce40a
0a36bef
29e5518
6f4ff0c
96c8e8b
be0d456
96e4f18
0889408
37e28bd
0cc3704
7f2ce67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| swc: patch | ||
| swc_core: major | ||
| swc_ecma_ast: major | ||
| swc_ecma_codegen: patch | ||
| swc_ecma_hooks: patch | ||
| swc_ecma_parser: major | ||
| swc_ecma_react_compiler: patch | ||
| swc_ecma_transforms_typescript: patch | ||
| swc_ecma_visit: patch | ||
| swc_estree_compat: patch | ||
| --- | ||
|
|
||
| fix(es/typescript): Preserve Flow component type semantics | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ use swc::{ | |
| use swc_common::FileName; | ||
| use swc_ecma_ast::EsVersion; | ||
| use swc_ecma_parser::{parse_file_as_program, EsSyntax, FlowSyntax, Syntax}; | ||
| use swc_ecma_testing::{exec_node_js, JsExecOptions}; | ||
| use testing::Tester; | ||
|
|
||
| #[testing::fixture("../swc_ecma_parser/tests/flow/**/*.js")] | ||
|
|
@@ -97,6 +98,60 @@ fn flow_strip_correctness(input: PathBuf) { | |
| .unwrap(); | ||
| } | ||
|
|
||
| #[test] | ||
| fn issue_12045_component_arrow_supports_react_native_mock_access() { | ||
|
Comment on lines
+101
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This adds the React Native mock regression as an inline AGENTS.md reference: AGENTS.md:L47-L47 Useful? React with 👍 / 👎. |
||
| Tester::new() | ||
| .print_errors(|cm, handler| { | ||
| let compiler = Compiler::new(cm.clone()); | ||
| let fm = cm.new_source_file( | ||
| FileName::Custom("issue-12045.js".into()).into(), | ||
| "const MyComponent: component(ref?: mixed, ...props: mixed) = ({ ref, ...rest }) \ | ||
| => null;", | ||
| ); | ||
| let output = compiler | ||
| .process_js_file( | ||
| fm, | ||
| &handler, | ||
| &Options { | ||
| swcrc: false, | ||
| config: Config { | ||
| jsc: JscConfig { | ||
| syntax: Some(Syntax::Flow(FlowSyntax { | ||
| components: true, | ||
| ..Default::default() | ||
| })), | ||
| target: Some(EsVersion::Es2022), | ||
| ..Default::default() | ||
| }, | ||
| ..Default::default() | ||
| }, | ||
| ..Default::default() | ||
| }, | ||
| ) | ||
| .expect("failed to compile Flow component arrow"); | ||
|
|
||
| assert!( | ||
| output | ||
| .code | ||
| .contains("const MyComponent = function MyComponent("), | ||
| "expected a named component function, got: {}", | ||
| output.code | ||
| ); | ||
|
|
||
| let runtime = format!( | ||
| "{}\nconst RealComponent = MyComponent;\nconst constructor = \ | ||
| RealComponent.prototype.constructor;\nconsole.log(constructor === MyComponent);", | ||
| output.code | ||
| ); | ||
| let stdout = exec_node_js(&runtime, JsExecOptions::default()) | ||
| .expect("React Native mock prototype access should execute"); | ||
| assert_eq!(stdout.trim(), "true"); | ||
|
|
||
| Ok(()) | ||
| }) | ||
| .unwrap(); | ||
| } | ||
|
|
||
| fn load_flow_syntax(config_path: PathBuf, is_jsx: bool) -> FlowSyntax { | ||
| let mut flow_syntax = FlowSyntax { | ||
| jsx: is_jsx, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -290,6 +290,7 @@ impl MacroNode for TsFnOrConstructorType { | |
| match self { | ||
| TsFnOrConstructorType::TsFnType(n) => emit!(n), | ||
| TsFnOrConstructorType::TsConstructorType(n) => emit!(n), | ||
| TsFnOrConstructorType::TsComponentType(n) => emit!(n), | ||
| #[cfg(swc_ast_unknown)] | ||
| _ => return Err(unknown_error()), | ||
| } | ||
|
|
@@ -332,6 +333,80 @@ impl MacroNode for TsFnType { | |
| } | ||
| } | ||
|
|
||
| #[node_impl] | ||
| impl MacroNode for TsComponentType { | ||
| fn emit(&mut self, emitter: &mut Macro) -> Result { | ||
| emitter.emit_leading_comments_of_span(self.span(), false)?; | ||
|
|
||
| keyword!(emitter, "component"); | ||
| emit!(self.type_params); | ||
|
|
||
| punct!(emitter, "("); | ||
| // Flow component parameters are stored as a single props object | ||
| // pattern. Emit its properties without object-pattern braces to | ||
| // reconstruct `component(prop: Type, ...rest: Type)` syntax. | ||
| if let [TsFnParam::Object(props)] = self.params.as_slice() { | ||
| for (index, prop) in props.props.iter().enumerate() { | ||
| if index != 0 { | ||
| punct!(emitter, ","); | ||
| formatting_space!(emitter); | ||
| } | ||
|
|
||
| match prop { | ||
| ObjectPatProp::KeyValue(prop) => { | ||
| let is_shorthand = match prop.value.as_ref() { | ||
| Pat::Ident(binding) => match &prop.key { | ||
| PropName::Ident(key) => key.sym == binding.id.sym, | ||
| _ => false, | ||
| }, | ||
| Pat::Assign(assign) => match assign.left.as_ref() { | ||
| Pat::Ident(binding) => match &prop.key { | ||
| PropName::Ident(key) => key.sym == binding.id.sym, | ||
| _ => false, | ||
| }, | ||
| _ => false, | ||
| }, | ||
| _ => false, | ||
| }; | ||
|
|
||
| if is_shorthand { | ||
| emit!(prop.value); | ||
| } else { | ||
| emit!(prop.key); | ||
| formatting_space!(emitter); | ||
| keyword!(emitter, "as"); | ||
| formatting_space!(emitter); | ||
|
Comment on lines
+379
to
+381
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When minifying an aliased component prop such as Useful? React with 👍 / 👎. |
||
| emit!(prop.value); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Flow component props that are both optional and aliased, e.g. Useful? React with 👍 / 👎.
Comment on lines
+377
to
+382
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a component type with a string-literal prop written without an alias, e.g. Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| ObjectPatProp::Assign(prop) => emit!(prop), | ||
| ObjectPatProp::Rest(prop) => emit!(prop), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a component type uses Flow's spread-prop rest syntax like Useful? React with 👍 / 👎. |
||
| #[cfg(swc_ast_unknown)] | ||
| _ => return Err(unknown_error()), | ||
| } | ||
| } | ||
| } else { | ||
| emitter.emit_list(self.span, Some(&self.params), ListFormat::Parameters)?; | ||
| } | ||
| punct!(emitter, ")"); | ||
|
|
||
| if !matches!( | ||
| self.type_ann.type_ann.as_ref(), | ||
| TsType::TsKeywordType(TsKeywordType { | ||
| kind: TsKeywordTypeKind::TsAnyKeyword, | ||
| .. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the input explicitly says Useful? React with 👍 / 👎. |
||
| }) | ||
| ) { | ||
| formatting_space!(emitter); | ||
| keyword!(emitter, "renders"); | ||
| space!(emitter); | ||
| emit!(self.type_ann); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[node_impl] | ||
| impl MacroNode for TsImportEqualsDecl { | ||
| fn emit(&mut self, emitter: &mut Macro) -> Result { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changeset records
swc_ecma_visitas a patch, but the same commit addsNodeRef::TsComponentTypeto the publicNodeRefenum incrates/swc_ecma_visit/src/generated.rs, which downstream users can exhaustively match. Publishing this as a patch can ship a semver-breaking API under the existing major version; bumpswc_ecma_visitas major or avoid changing that public enum.AGENTS.md reference: AGENTS.md:L41-L41
Useful? React with 👍 / 👎.