feat(semantic): support scope descendents starting from a certain scope. (#1629)

@Boshen 

The `ScopeTree.descendants` function would return all scopes starting
from the root, and wasn't truly descendants from a specific scope. To
improve this, I've renamed this function to `descendants_from_root` and
have introduced a new `descendants` function that does support from a
specific scope.

Furthermore, I've introduced helper functions to `SymbolTree` to make
reading symbols/scopes easier.

To verify this functionality, I enabled the `function_name` transformer
(and fixed it), and ran some example transforms. Here's the input:

```js
let fn;

fn = function (a, b, c) {
  const d = "";
};

const func = function (arg) {
  {
    const value = "";
  }
};

const f = function (f) {};
```

And the output using _the old implementation_. Note that all function
names are suffixed with a number, this is incorrect, since it was
inheriting far too many scopes.

```js
let fn;
fn = function fn1(a, b, c) {
	const d = '';
};
const func = function func1(arg) {
	{
		const value = '';
	}
};
const f = function f2(f) {
};
```

And here's the output with the new implementation. Note that only `f` is
suffixed with a number. That's because it has a shadowed argument of the
same name.

```js
let fn;
fn = function fn(a, b, c) {
	const d = '';
};
const func = function func(arg) {
	{
		const value = '';
	}
};
const f = function f1(f) {
};
```
This commit is contained in:
Miles Johnson 2023-12-07 01:29:11 -08:00 committed by GitHub
parent aa9bdeb8ee
commit c6ad6603a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 54 deletions

View file

@ -86,7 +86,7 @@ impl ManglerBuilder {
let mut max_slot_for_scope = vec![0; scope_tree.len()]; let mut max_slot_for_scope = vec![0; scope_tree.len()];
// Walk the scope tree and compute the slot number for each scope // Walk the scope tree and compute the slot number for each scope
for scope_id in scope_tree.descendants() { for scope_id in scope_tree.descendants_from_root() {
let bindings = scope_tree.get_bindings(scope_id); let bindings = scope_tree.get_bindings(scope_id);
// The current slot number is continued by the maximum slot from the parent scope // The current slot number is continued by the maximum slot from the parent scope
let parent_max_slot = scope_tree let parent_max_slot = scope_tree

View file

@ -19,7 +19,12 @@ type UnresolvedReferences = FxHashMap<Atom, Vec<ReferenceId>>;
/// `SoA` (Struct of Arrays) for memory efficiency. /// `SoA` (Struct of Arrays) for memory efficiency.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct ScopeTree { pub struct ScopeTree {
/// Maps a scope to the parent scope it belongs in
parent_ids: IndexVec<ScopeId, Option<ScopeId>>, parent_ids: IndexVec<ScopeId, Option<ScopeId>>,
/// Maps a scope to direct children scopes
child_ids: FxHashMap<ScopeId, Vec<ScopeId>>,
flags: IndexVec<ScopeId, ScopeFlags>, flags: IndexVec<ScopeId, ScopeFlags>,
bindings: IndexVec<ScopeId, Bindings>, bindings: IndexVec<ScopeId, Bindings>,
unresolved_references: IndexVec<ScopeId, UnresolvedReferences>, unresolved_references: IndexVec<ScopeId, UnresolvedReferences>,
@ -38,7 +43,30 @@ impl ScopeTree {
std::iter::successors(Some(scope_id), |scope_id| self.parent_ids[*scope_id]) std::iter::successors(Some(scope_id), |scope_id| self.parent_ids[*scope_id])
} }
pub fn descendants(&self) -> impl Iterator<Item = ScopeId> + '_ { pub fn descendants(&self, scope_id: ScopeId) -> impl Iterator<Item = ScopeId> + '_ {
// Has to be a `fn` and pass arguments because we can't
// have recursive closures
fn add_to_list(
parent_id: ScopeId,
child_ids: &FxHashMap<ScopeId, Vec<ScopeId>>,
items: &mut Vec<ScopeId>,
) {
if let Some(children) = child_ids.get(&parent_id) {
for child_id in children {
items.push(*child_id);
add_to_list(*child_id, child_ids, items);
}
}
}
let mut list = vec![];
add_to_list(scope_id, &self.child_ids, &mut list);
list.into_iter()
}
pub fn descendants_from_root(&self) -> impl Iterator<Item = ScopeId> + '_ {
self.parent_ids.iter_enumerated().map(|(scope_id, _)| scope_id) self.parent_ids.iter_enumerated().map(|(scope_id, _)| scope_id)
} }
@ -98,6 +126,11 @@ impl ScopeTree {
_ = self.flags.push(flags); _ = self.flags.push(flags);
_ = self.bindings.push(Bindings::default()); _ = self.bindings.push(Bindings::default());
_ = self.unresolved_references.push(UnresolvedReferences::default()); _ = self.unresolved_references.push(UnresolvedReferences::default());
if let Some(parent_id) = parent_id {
self.child_ids.entry(parent_id).or_default().push(scope_id);
}
scope_id scope_id
} }

View file

@ -39,6 +39,18 @@ impl SymbolTable {
self.spans.iter_enumerated().map(|(symbol_id, _)| symbol_id) self.spans.iter_enumerated().map(|(symbol_id, _)| symbol_id)
} }
pub fn get_symbol_id_from_span(&self, span: &Span) -> Option<SymbolId> {
self.spans
.iter_enumerated()
.find_map(|(symbol, inner_span)| if inner_span == span { Some(symbol) } else { None })
}
pub fn get_symbol_id_from_name(&self, name: &Atom) -> Option<SymbolId> {
self.names
.iter_enumerated()
.find_map(|(symbol, inner_name)| if inner_name == name { Some(symbol) } else { None })
}
pub fn get_span(&self, symbol_id: SymbolId) -> Span { pub fn get_span(&self, symbol_id: SymbolId) -> Span {
self.spans[symbol_id] self.spans[symbol_id]
} }
@ -63,6 +75,14 @@ impl SymbolTable {
self.scope_ids[symbol_id] self.scope_ids[symbol_id]
} }
pub fn get_scope_id_from_span(&self, span: &Span) -> Option<ScopeId> {
self.get_symbol_id_from_span(span).map(|symbol_id| self.get_scope_id(symbol_id))
}
pub fn get_scope_id_from_name(&self, name: &Atom) -> Option<ScopeId> {
self.get_symbol_id_from_name(name).map(|symbol_id| self.get_scope_id(symbol_id))
}
pub fn get_declaration(&self, symbol_id: SymbolId) -> AstNodeId { pub fn get_declaration(&self, symbol_id: SymbolId) -> AstNodeId {
self.declarations[symbol_id] self.declarations[symbol_id]
} }

View file

@ -21,6 +21,7 @@ fn main() {
let source_text = std::fs::read_to_string(path).expect("{name} not found"); let source_text = std::fs::read_to_string(path).expect("{name} not found");
let allocator = Allocator::default(); let allocator = Allocator::default();
let source_type = SourceType::from_path(path).unwrap(); let source_type = SourceType::from_path(path).unwrap();
let ret = Parser::new(&allocator, &source_text, source_type).parse(); let ret = Parser::new(&allocator, &source_text, source_type).parse();
if !ret.errors.is_empty() { if !ret.errors.is_empty() {
@ -31,10 +32,8 @@ fn main() {
return; return;
} }
let codegen_options = CodegenOptions;
let printed = Codegen::<false>::new(source_text.len(), codegen_options).build(&ret.program);
println!("Original:\n"); println!("Original:\n");
println!("{printed}\n"); println!("{source_text}\n");
let semantic = SemanticBuilder::new(&source_text, source_type) let semantic = SemanticBuilder::new(&source_text, source_type)
.with_trivias(ret.trivias) .with_trivias(ret.trivias)
@ -43,7 +42,7 @@ fn main() {
let program = allocator.alloc(ret.program); let program = allocator.alloc(ret.program);
let transform_options = TransformOptions { let transform_options = TransformOptions {
target: TransformTarget::ES2015, target: TransformTarget::ES5,
react_jsx: Some(ReactJsxOptions { react_jsx: Some(ReactJsxOptions {
runtime: Some(ReactJsxRuntimeOption::Valid(ReactJsxRuntime::Classic)), runtime: Some(ReactJsxRuntimeOption::Valid(ReactJsxRuntime::Classic)),
..ReactJsxOptions::default() ..ReactJsxOptions::default()
@ -52,7 +51,7 @@ fn main() {
}; };
Transformer::new(&allocator, source_type, semantic, transform_options).build(program).unwrap(); Transformer::new(&allocator, source_type, semantic, transform_options).build(program).unwrap();
let printed = Codegen::<false>::new(source_text.len(), codegen_options).build(program); let printed = Codegen::<false>::new(source_text.len(), CodegenOptions).build(program);
println!("Transformed:\n"); println!("Transformed:\n");
println!("{printed}"); println!("{printed}");
} }

View file

@ -1,7 +1,8 @@
use std::rc::Rc; use std::rc::Rc;
// use lazy_static::lazy_static; // use lazy_static::lazy_static;
use oxc_ast::{ast::*, AstBuilder, Visit}; use oxc_ast::{ast::*, AstBuilder};
use oxc_semantic::ScopeId;
use oxc_span::{Atom, Span}; use oxc_span::{Atom, Span};
use oxc_syntax::operator::AssignmentOperator; use oxc_syntax::operator::AssignmentOperator;
use oxc_syntax::unicode_id_start::is_id_continue; use oxc_syntax::unicode_id_start::is_id_continue;
@ -10,6 +11,7 @@ use oxc_syntax::unicode_id_start::is_id_continue;
use crate::context::TransformerCtx; use crate::context::TransformerCtx;
use crate::options::TransformOptions; use crate::options::TransformOptions;
use crate::utils::is_valid_identifier; use crate::utils::is_valid_identifier;
use crate::TransformTarget;
/// ES2015: Function Name /// ES2015: Function Name
/// ///
@ -24,18 +26,16 @@ pub struct FunctionName<'a> {
impl<'a> FunctionName<'a> { impl<'a> FunctionName<'a> {
pub fn new( pub fn new(
_ast: &Rc<AstBuilder<'a>>, ast: Rc<AstBuilder<'a>>,
_ctx: &TransformerCtx<'a>, ctx: TransformerCtx<'a>,
_options: &TransformOptions, options: &TransformOptions,
) -> Option<Self> { ) -> Option<Self> {
// Disabled for now (options.target < TransformTarget::ES2015 || options.function_name).then(|| Self {
None _ast: ast,
// (options.target < TransformTarget::ES2015 || options.function_name).then(|| Self { ctx,
// ast, // TODO hook up the plugin
// ctx, unicode_escapes: true,
// // TODO hook up the plugin })
// unicode_escapes: true,
// })
} }
pub fn transform_assignment_expression(&mut self, expr: &mut AssignmentExpression<'a>) { pub fn transform_assignment_expression(&mut self, expr: &mut AssignmentExpression<'a>) {
@ -47,7 +47,9 @@ impl<'a> FunctionName<'a> {
if let Some(id) = if let Some(id) =
create_valid_identifier(target.span, target.name.clone(), self.unicode_escapes) create_valid_identifier(target.span, target.name.clone(), self.unicode_escapes)
{ {
self.transform_expression(&mut expr.right, id); let scope_id = self.ctx.symbols().get_scope_id_from_span(&target.span);
self.transform_expression(&mut expr.right, id, scope_id);
} }
} }
} }
@ -60,22 +62,28 @@ impl<'a> FunctionName<'a> {
continue; continue;
} }
let id = match &property.key { let (id, scope_id) = match &property.key {
PropertyKey::Identifier(ident) => create_valid_identifier( PropertyKey::Identifier(ident) => (
ident.span, create_valid_identifier(
ident.name.clone(), ident.span,
self.unicode_escapes, ident.name.clone(),
self.unicode_escapes,
),
self.ctx.symbols().get_scope_id_from_span(&ident.span),
), ),
PropertyKey::PrivateIdentifier(ident) => create_valid_identifier( PropertyKey::PrivateIdentifier(ident) => (
ident.span, create_valid_identifier(
ident.name.clone(), ident.span,
self.unicode_escapes, ident.name.clone(),
self.unicode_escapes,
),
self.ctx.symbols().get_scope_id_from_span(&ident.span),
), ),
PropertyKey::Expression(_) => continue, PropertyKey::Expression(_) => continue,
}; };
if let Some(id) = id { if let Some(id) = id {
self.transform_expression(&mut property.value, id); self.transform_expression(&mut property.value, id, scope_id);
} }
} }
} }
@ -89,26 +97,36 @@ impl<'a> FunctionName<'a> {
if let Some(id) = if let Some(id) =
create_valid_identifier(ident.span, ident.name.clone(), self.unicode_escapes) create_valid_identifier(ident.span, ident.name.clone(), self.unicode_escapes)
{ {
self.transform_expression(init, id); let scope_id = match ident.symbol_id.get() {
Some(symbol_id) => Some(self.ctx.symbols().get_scope_id(symbol_id)),
None => self.ctx.symbols().get_scope_id_from_span(&ident.span),
};
self.transform_expression(init, id, scope_id);
} }
}; };
} }
// Internal only // Internal only
fn transform_expression(&mut self, expr: &mut Expression<'a>, mut id: BindingIdentifier) { fn transform_expression(
&mut self,
expr: &mut Expression<'a>,
mut id: BindingIdentifier,
scope_id: Option<ScopeId>,
) {
// function () {} -> function name() {} // function () {} -> function name() {}
if let Expression::FunctionExpression(func) = expr { if let Expression::FunctionExpression(func) = expr {
let scopes = self.ctx.scopes();
let mut count = 0; let mut count = 0;
// let mut finder = IdentFinder { id, found: 0 };
// finder.visit_expression(expr);
// Check for nested params/vars of the same name // Check for nested params/vars of the same name
for scope in scopes.descendants() { if let Some(scope_id) = scope_id {
for binding in scopes.get_bindings(scope) { let scopes = self.ctx.scopes();
if binding.0 == &id.name {
count += 1; for scope in scopes.descendants(scope_id) {
for binding in scopes.get_bindings(scope) {
if binding.0 == &id.name {
count += 1;
}
} }
} }
} }
@ -125,19 +143,6 @@ impl<'a> FunctionName<'a> {
} }
} }
struct IdentFinder {
id: BindingIdentifier,
found: usize,
}
impl<'a> Visit<'a> for IdentFinder {
fn visit_binding_identifier(&mut self, ident: &BindingIdentifier) {
if ident.name == self.id.name {
self.found += 1;
}
}
}
// https://github.com/babel/babel/blob/main/packages/babel-helper-function-name/src/index.ts // https://github.com/babel/babel/blob/main/packages/babel-helper-function-name/src/index.ts
// https://github.com/babel/babel/blob/main/packages/babel-types/src/converters/toBindingIdentifierName.ts#L3 // https://github.com/babel/babel/blob/main/packages/babel-types/src/converters/toBindingIdentifierName.ts#L3
// https://github.com/babel/babel/blob/main/packages/babel-types/src/converters/toIdentifier.ts#L4 // https://github.com/babel/babel/blob/main/packages/babel-types/src/converters/toIdentifier.ts#L4

View file

@ -98,7 +98,7 @@ impl<'a> Transformer<'a> {
// es2016 // es2016
es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options), es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options),
// es2015 // es2015
es2015_function_name: FunctionName::new(&ast, &ctx.clone(), &options), es2015_function_name: FunctionName::new(Rc::clone(&ast), ctx.clone(), &options),
es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options), es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options),
es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options), es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options),
// other // other