Skip to content

Commit

Permalink
feat(transformer/async-to-generator): support inferring the function …
Browse files Browse the repository at this point in the history
…name from the ObjectPropertyValue's key (#7201)

Support for inferring function name from ObjectPropertyValue's key

For example:
```js
({ foo: async function() {} })
```

After this, we will able to infer `foo` for the object method
  • Loading branch information
Dunqing committed Nov 8, 2024
1 parent 3a20b90 commit 1910227
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 23 deletions.
88 changes: 72 additions & 16 deletions crates/oxc_transformer/src/es2017/async_to_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@
use std::mem;

use oxc_allocator::Box as ArenaBox;
use oxc_ast::{ast::*, Visit, NONE};
use oxc_allocator::{Box as ArenaBox, String as ArenaString};
use oxc_ast::{ast::*, AstBuilder, Visit, NONE};
use oxc_semantic::{ReferenceFlags, ScopeFlags, ScopeId, SymbolFlags};
use oxc_span::{Atom, GetSpan, SPAN};
use oxc_syntax::{
identifier::{is_identifier_name, is_identifier_part, is_identifier_start},
keyword::is_reserved_keyword,
};
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};

use crate::{common::helper_loader::Helper, TransformCtx};
Expand Down Expand Up @@ -287,9 +291,7 @@ impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
let params = Self::create_placeholder_params(&params, scope_id, ctx);
let statements = ctx.ast.vec1(Self::create_apply_call_statement(&bound_ident, ctx));
let body = ctx.ast.alloc_function_body(SPAN, ctx.ast.vec(), statements);
let id = id.or_else(|| {
Self::infer_function_id_from_variable_declarator(wrapper_scope_id, ctx)
});
let id = id.or_else(|| Self::infer_function_id_from_parent_node(wrapper_scope_id, ctx));
Self::create_function(id, params, body, scope_id, ctx)
};

Expand Down Expand Up @@ -425,7 +427,7 @@ impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
let params = ctx.alloc(ctx.ast.move_formal_parameters(&mut arrow.params));
let generator_function_id = arrow.scope_id();
ctx.scopes_mut().get_flags_mut(generator_function_id).remove(ScopeFlags::Arrow);
let function_name = Self::get_function_name_from_parent_variable_declarator(ctx);
let function_name = Self::infer_function_name_from_parent_node(ctx);

if function_name.is_none() && !Self::is_function_length_affected(&params) {
return self.create_async_to_generator_call(
Expand Down Expand Up @@ -479,25 +481,79 @@ impl<'a, 'ctx> AsyncGeneratorExecutor<'a, 'ctx> {
}
}

/// Infers the function id from [`Ancestor::VariableDeclaratorInit`].
fn infer_function_id_from_variable_declarator(
/// Infers the function id from [`TraverseCtx::parent`].
fn infer_function_id_from_parent_node(
scope_id: ScopeId,
ctx: &mut TraverseCtx<'a>,
) -> Option<BindingIdentifier<'a>> {
let name = Self::get_function_name_from_parent_variable_declarator(ctx)?;
let name = Self::infer_function_name_from_parent_node(ctx)?;
Some(
ctx.generate_binding(name, scope_id, SymbolFlags::FunctionScopedVariable)
.create_binding_identifier(ctx),
)
}

fn get_function_name_from_parent_variable_declarator(
ctx: &mut TraverseCtx<'a>,
) -> Option<Atom<'a>> {
let Ancestor::VariableDeclaratorInit(declarator) = ctx.parent() else {
return None;
};
declarator.id().get_binding_identifier().map(|id| id.name.clone())
/// Infers the function name from the [`TraverseCtx::parent`].
fn infer_function_name_from_parent_node(ctx: &mut TraverseCtx<'a>) -> Option<Atom<'a>> {
match ctx.parent() {
// infer `foo` from `const foo = async function() {}`
Ancestor::VariableDeclaratorInit(declarator) => {
declarator.id().get_binding_identifier().map(|id| id.name.clone())
}
// infer `foo` from `({ foo: async function() {} })`
Ancestor::ObjectPropertyValue(property) if !*property.method() => {
property.key().static_name().map(|key| Self::normalize_function_name(&key, ctx.ast))
}
_ => None,
}
}

/// Normalizes the function name.
///
/// Examples:
///
/// // Valid
/// * `foo` -> `foo`
/// // Contains space
/// * `foo bar` -> `foo_bar`
/// // Reserved keyword
/// * `this` -> `_this`
/// * `arguments` -> `_arguments`
fn normalize_function_name(input: &str, ast: AstBuilder<'a>) -> Atom<'a> {
if !is_reserved_keyword(input) && is_identifier_name(input) {
return ast.atom(input);
}

let mut name = ArenaString::with_capacity_in(input.len() + 1, ast.allocator);
let mut capitalize_next = false;

let mut chars = input.chars();
if let Some(first) = chars.next() {
if is_identifier_start(first) {
name.push(first);
}
}

for c in chars {
if c == ' ' {
name.push('_');
} else if !is_identifier_part(c) {
capitalize_next = true;
} else if capitalize_next {
name.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
name.push(c);
}
}

if name.is_empty() {
return ast.atom("_");
} else if is_reserved_keyword(name.as_str()) {
name.insert(0, '_');
}

ast.atom(name.into_bump_str())
}

/// Creates a [`Function`] with the specified params, body and scope_id.
Expand Down
12 changes: 6 additions & 6 deletions tasks/coverage/snapshots/semantic_typescript.snap
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ Bindings mismatch:
after transform: ScopeId(22): ["T", "value"]
rebuilt : ScopeId(29): ["value"]
Symbol flags mismatch for "_asyncToGenerator":
after transform: SymbolId(26): SymbolFlags(Import)
after transform: SymbolId(27): SymbolFlags(Import)
rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable)
Unresolved references mismatch:
after transform: ["Promise", "arguments", "require"]
Expand Down Expand Up @@ -5338,17 +5338,17 @@ semantic error: Bindings mismatch:
after transform: ScopeId(0): ["LoadCallback", "_asyncToGenerator", "cb1", "cb2", "cb3", "fn1"]
rebuilt : ScopeId(0): ["_asyncToGenerator", "cb1", "cb2", "cb3", "fn1"]
Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(3), ScopeId(5), ScopeId(7), ScopeId(8), ScopeId(12), ScopeId(14), ScopeId(15), ScopeId(17)]
rebuilt : ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(4), ScopeId(6), ScopeId(10), ScopeId(13)]
after transform: ScopeId(0): [ScopeId(1), ScopeId(2), ScopeId(5), ScopeId(7), ScopeId(8), ScopeId(12), ScopeId(14), ScopeId(16), ScopeId(17), ScopeId(19)]
rebuilt : ScopeId(0): [ScopeId(1), ScopeId(4), ScopeId(6), ScopeId(8), ScopeId(12), ScopeId(15)]
Symbol flags mismatch for "_asyncToGenerator":
after transform: SymbolId(10): SymbolFlags(Import)
after transform: SymbolId(12): SymbolFlags(Import)
rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable)
Unresolved references mismatch:
after transform: ["Promise", "Record", "StateMachine", "arguments", "createMachine", "load", "require"]
rebuilt : ["Promise", "arguments", "createMachine", "load", "require"]
Unresolved reference IDs mismatch for "Promise":
after transform: [ReferenceId(2), ReferenceId(7), ReferenceId(8), ReferenceId(9), ReferenceId(11), ReferenceId(12), ReferenceId(13)]
rebuilt : [ReferenceId(3), ReferenceId(5), ReferenceId(7)]
rebuilt : [ReferenceId(3), ReferenceId(7), ReferenceId(9)]

tasks/coverage/typescript/tests/cases/compiler/contextuallyTypeGeneratorReturnTypeFromUnion.ts
semantic error: Bindings mismatch:
Expand Down Expand Up @@ -55663,7 +55663,7 @@ semantic error: Scope children mismatch:
after transform: ScopeId(0): [ScopeId(1), ScopeId(2)]
rebuilt : ScopeId(0): [ScopeId(1)]
Symbol flags mismatch for "_asyncToGenerator":
after transform: SymbolId(10): SymbolFlags(Import)
after transform: SymbolId(11): SymbolFlags(Import)
rebuilt : SymbolId(0): SymbolFlags(FunctionScopedVariable)

tasks/coverage/typescript/tests/cases/conformance/types/rest/objectRestParameter.ts
Expand Down
2 changes: 1 addition & 1 deletion tasks/transform_conformance/snapshots/oxc.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: d20b314c

Passed: 79/88
Passed: 80/89

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const Normal = {
foo: async () => {
console.log(log)
}
}

const StringLiteralKey = {
['bar']: async () => {
}
}

const EmptyStringLiteralKey = {
['']: async () => {
console.log(this)
}
}

const InvalidStringLiteralKey = {
['#']: async () => {},
['this']: async () => {},
['#default']: async () => {},
['O X C']: async () => {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
var _this = this;
const Normal = {
foo: function () {
var _ref = babelHelpers.asyncToGenerator(function* () {
console.log(log);
});
return function foo() {
return _ref.apply(this, arguments);
};
}()
};
const StringLiteralKey = {
['bar']: function () {
var _ref2 = babelHelpers.asyncToGenerator(function* () {});
return function bar() {
return _ref2.apply(this, arguments);
};
}()
};
const EmptyStringLiteralKey = {
['']: function () {
var _ref3 = babelHelpers.asyncToGenerator(function* () {
console.log(_this);
});
return function _() {
return _ref3.apply(this, arguments);
};
}()
};
const InvalidStringLiteralKey = {
['#']: function () {
var _ref4 = babelHelpers.asyncToGenerator(function* () {});
return function _() {
return _ref4.apply(this, arguments);
};
}(),
['this']: function () {
var _ref5 = babelHelpers.asyncToGenerator(function* () {});
return function _this() {
return _ref5.apply(this, arguments);
};
}(),
['#default']: function () {
var _ref6 = babelHelpers.asyncToGenerator(function* () {});
return function _default() {
return _ref6.apply(this, arguments);
};
}(),
['O X C']: function () {
var _ref7 = babelHelpers.asyncToGenerator(function* () {});
return function O_X_C() {
return _ref7.apply(this, arguments);
};
}()
};

0 comments on commit 1910227

Please sign in to comment.