fix(es/transforms/base): Fix hygiene (#2299)

swc_ecma_transforms_base:
 - `hygiene`: Support usage-def conflict where def comes first. (#2297)
This commit is contained in:
Donny/강동윤 2021-09-24 19:26:01 +09:00 committed by GitHub
parent 8af2173a33
commit 5e1003ec4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 188 additions and 32 deletions

4
Cargo.lock generated
View File

@ -2739,7 +2739,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_base"
version = "0.31.3"
version = "0.31.4"
dependencies = [
"fxhash",
"once_cell",
@ -3402,7 +3402,7 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm"
version = "1.2.90"
version = "1.2.91"
dependencies = [
"anyhow",
"console_error_panic_hook",

View File

@ -3365,7 +3365,7 @@
isBatchingEventUpdates = !1, finishEventHandler();
}
}(function() {
var eventSystemFlags, nativeEvent6, nativeEventTarget, dispatchQueue, dispatchQueue1, domEventName, targetInst, nativeEvent1, nativeEventTarget1, eventSystemFlags1, dispatchQueue2, domEventName1, targetInst1, nativeEvent2, nativeEventTarget2;
var eventSystemFlags, nativeEvent8, nativeEventTarget, dispatchQueue, dispatchQueue1, domEventName, targetInst, nativeEvent1, nativeEventTarget1, eventSystemFlags1, dispatchQueue2, domEventName1, targetInst1, nativeEvent2, nativeEventTarget2;
(function(dispatchQueue3, domEventName, targetInst, nativeEvent3, nativeEventTarget3, eventSystemFlags2, targetContainer) {
var reactName = topLevelEventsToReactNames.get(domEventName);
if (void 0 !== reactName) {
@ -3447,14 +3447,14 @@
}
var inCapturePhase = (4 & (eventSystemFlags1 = eventSystemFlags = eventSystemFlags3)) != 0, accumulateTargetOnly = !inCapturePhase && "scroll" === domEventName, _listeners = accumulateSinglePhaseListeners(targetInst = ancestorInst, reactName, nativeEvent3.type, inCapturePhase, accumulateTargetOnly);
if (_listeners.length > 0) {
var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent3, nativeEventTarget1 = nativeEventTarget = getEventTarget(nativeEvent6 = nativeEvent4));
var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent3, nativeEventTarget1 = nativeEventTarget = getEventTarget(nativeEvent8 = nativeEvent4));
(dispatchQueue1 = dispatchQueue = []).push({
event: _event,
listeners: _listeners
});
}
}
})(dispatchQueue1, domEventName = domEventName2, targetInst, nativeEvent1 = nativeEvent6, nativeEventTarget1, eventSystemFlags1), (7 & eventSystemFlags1) == 0 && ((function(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
})(dispatchQueue1, domEventName = domEventName2, targetInst, nativeEvent1 = nativeEvent8, nativeEventTarget1, eventSystemFlags1), (7 & eventSystemFlags1) == 0 && ((function(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
var win, from, to, isOverEvent = "mouseover" === domEventName || "pointerover" === domEventName, isOutEvent = "mouseout" === domEventName || "pointerout" === domEventName;
if (isOverEvent && (16 & eventSystemFlags1) == 0) {
var related = nativeEvent.relatedTarget || nativeEvent.fromElement;
@ -3540,7 +3540,7 @@
null !== customData && (event.data = customData);
}
}
})(dispatchQueue2, domEventName1 = domEventName, targetInst1, nativeEvent2 = nativeEvent1, nativeEventTarget2 = nativeEventTarget1), (function(dispatchQueue, domEventName, targetInst, nativeEvent5, nativeEventTarget) {
})(dispatchQueue2, domEventName1 = domEventName, targetInst1, nativeEvent2 = nativeEvent1, nativeEventTarget2 = nativeEventTarget1), (function(dispatchQueue, domEventName, targetInst, nativeEvent7, nativeEventTarget) {
var chars;
if (!(chars = canUseTextInputEvent ? function(domEventName, nativeEvent) {
switch(domEventName){
@ -3556,14 +3556,14 @@
default:
return null;
}
}(domEventName, nativeEvent5) : function(domEventName, nativeEvent) {
var nativeEvent3;
}(domEventName, nativeEvent7) : function(domEventName, nativeEvent) {
var nativeEvent5;
if (isComposing) return "compositionend" === domEventName || !canUseCompositionEvent && isFallbackCompositionEnd(domEventName, nativeEvent) ? (root2 = null, startText = null, fallbackText = null, isComposing = !1, getData()) : null;
switch(domEventName){
case "paste":
return null;
case "keypress":
if (!(((nativeEvent3 = nativeEvent).ctrlKey || nativeEvent3.altKey || nativeEvent3.metaKey) && !(nativeEvent3.ctrlKey && nativeEvent3.altKey))) {
if (!(((nativeEvent5 = nativeEvent).ctrlKey || nativeEvent5.altKey || nativeEvent5.metaKey) && !(nativeEvent5.ctrlKey && nativeEvent5.altKey))) {
if (nativeEvent.char && nativeEvent.char.length > 1) return nativeEvent.char;
if (nativeEvent.which) return String.fromCharCode(nativeEvent.which);
}
@ -3573,10 +3573,10 @@
default:
return null;
}
}(domEventName, nativeEvent5))) return null;
}(domEventName, nativeEvent7))) return null;
var listeners = accumulateTwoPhaseListeners(targetInst1, "onBeforeInput");
if (listeners.length > 0) {
var event = new SyntheticInputEvent("onBeforeInput", "beforeinput", null, nativeEvent5, nativeEventTarget2);
var event = new SyntheticInputEvent("onBeforeInput", "beforeinput", null, nativeEvent7, nativeEventTarget2);
dispatchQueue2.push({
event: event,
listeners: listeners

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms_base"
repository = "https://github.com/swc-project/swc.git"
version = "0.31.3"
version = "0.31.4"
[dependencies]
fxhash = "0.2.1"

View File

@ -9,7 +9,7 @@ use std::{cell::RefCell, collections::HashMap};
use swc_atoms::{js_word, JsWord};
use swc_common::{chain, util::take::Take, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_utils::{ident::IdentLike, Id};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
mod ops;
@ -44,6 +44,16 @@ struct Hygiene<'a> {
type Contexts = SmallVec<[SyntaxContext; 32]>;
impl<'a> Hygiene<'a> {
/// Check `id` while exiting scope.
///
/// We handle this while exiting a scope. See check_enqueued for details.
fn enqueue_check(&mut self, id: Id) {
if self.current.check_queue.contains(&id) {
return;
}
self.current.check_queue.push(id);
}
fn add_decl(&mut self, ident: Ident) {
let ctxt = ident.span.ctxt();
@ -64,7 +74,7 @@ impl<'a> Hygiene<'a> {
{
let mut b = self.current.declared_symbols.borrow_mut();
let e = b.entry(sym.to_boxed_str()).or_default();
let e = b.entry(sym.clone()).or_default();
if !e.contains(&ctxt) {
e.push(ctxt);
}
@ -77,7 +87,7 @@ impl<'a> Hygiene<'a> {
while let Some(c) = cur {
let mut used = c.used.borrow_mut();
let e = used.entry(sym.to_boxed_str()).or_default();
let e = used.entry(sym.clone()).or_default();
if !e.contains(&ctxt) {
e.push(ctxt);
@ -125,14 +135,35 @@ impl<'a> Hygiene<'a> {
let ctxt = ident.span.ctxt();
{
let mut decl_cnt = 0;
let mut cur = Some(&self.current);
while let Some(c) = cur {
let b = c.declared_symbols.borrow();
if let Some(ctxts) = b.get(&ident.sym.clone()) {
decl_cnt += ctxts.len();
}
cur = c.parent;
}
if decl_cnt >= 2 {
self.enqueue_check(ident.to_id());
}
}
{
let mut need_work = false;
let mut is_cur = true;
let mut cur = Some(&self.current);
while let Some(c) = cur {
let mut used = c.used.borrow_mut();
let e = used.entry(ident.sym.to_boxed_str()).or_default();
let e = used.entry(ident.sym.clone()).or_default();
if !e.contains(&ctxt) {
e.push(ctxt);
@ -183,7 +214,7 @@ impl<'a> Hygiene<'a> {
let mut i = 0;
loop {
i += 1;
let sym = format!("{}{}", sym, i);
let sym = format!("{}{}", sym, i).into();
if !self.current.is_declared(&sym) {
break sym;
@ -196,9 +227,8 @@ impl<'a> Hygiene<'a> {
}
let sym = self.current.change_symbol(sym, ctxt);
let boxed_sym = sym.to_boxed_str();
{
let scope = self.current.scope_of(&boxed_sym, ctxt, self.var_kind);
let scope = self.current.scope_of(&sym, ctxt, self.var_kind);
// Update symbol list
let mut declared_symbols = scope.declared_symbols.borrow_mut();
@ -224,12 +254,12 @@ impl<'a> Hygiene<'a> {
// );
}
let old = declared_symbols.entry(sym.to_boxed_str()).or_default();
let old = declared_symbols.entry(sym.clone()).or_default();
old.retain(|c| *c != ctxt);
// debug_assert!(old.is_empty() || old.len() == 1);
let new = declared_symbols
.entry(renamed.clone().into_boxed_str())
.entry(renamed.clone().into())
.or_insert_with(|| Vec::with_capacity(2));
new.push(ctxt);
debug_assert!(new.len() == 1);
@ -276,10 +306,77 @@ impl VisitMut for MarkClearer {
}
impl<'a> Hygiene<'a> {
/// Move `check_queue` to `ops`.
///
/// # Implementation notes
////
/// This only handles variables declared in current scope.
/// You may think it's possible to avoid adding an [Id] to `check_queue`
/// entirely, but it's not possible because declarations can come after
/// usages.
fn check_enqueued(&mut self) {
if self.current.check_queue.is_empty() {
return;
}
for (sym, ctxt) in self.current.check_queue.take() {
// Check if it's in the current scope.
if let Some(decls) = self.current.declared_symbols.borrow().get(&sym) {
if !decls.contains(&ctxt) {
continue;
}
}
{
// We check for all used identifiers in current scope.
// If `ctxt` is the only one, it's fine.
// If other syntax context is used, we need to rename it.
let mut other_ctxts = vec![];
let used = self.current.used.borrow();
if let Some(ctxts) = used.get(&sym) {
'add_loop: for &cx in ctxts {
if cx == ctxt {
continue;
}
// Prevent duplicate
if other_ctxts.contains(&cx) {
continue;
}
// If a declaration name is going to be renamed, it's not a conflict.
let mut cur = Some(&self.current);
while let Some(c) = cur {
let ops = c.ops.borrow();
if ops.rename.contains_key(&(sym.clone(), cx)) {
continue 'add_loop;
}
cur = c.parent;
}
other_ctxts.push(cx);
}
}
if other_ctxts.is_empty() {
continue;
}
}
self.rename(sym, ctxt);
}
}
fn apply_ops<N>(&mut self, node: &mut N)
where
for<'o> N: VisitMutWith<Operator<'o>>,
{
self.check_enqueued();
let ops = self.current.ops.borrow();
let ids_to_remove = self.current.declared_symbols.borrow();
@ -300,7 +397,7 @@ impl<'a> Hygiene<'a> {
}
for ((sym, ctxt), _) in &ops.rename {
let e = used.entry(sym.to_boxed_str()).or_default();
let e = used.entry(sym.clone()).or_default();
if let Some(pos) = e.iter().position(|c| *c == *ctxt) {
e.remove(pos);
@ -406,13 +503,15 @@ struct Scope<'a> {
/// Kind of the scope.
pub kind: ScopeKind,
pub used: RefCell<FxHashMap<Box<str>, Vec<SyntaxContext>>>,
pub used: RefCell<FxHashMap<JsWord, Vec<SyntaxContext>>>,
/// All references declared in this scope
pub declared_symbols: RefCell<FxHashMap<Box<str>, Vec<SyntaxContext>>>,
pub declared_symbols: RefCell<FxHashMap<JsWord, Vec<SyntaxContext>>>,
pub(crate) ops: RefCell<Operations>,
pub renamed: FxHashSet<JsWord>,
check_queue: Vec<Id>,
}
impl<'a> Default for Scope<'a> {
@ -438,12 +537,13 @@ impl<'a> Scope<'a> {
ops: Default::default(),
renamed: Default::default(),
used: Default::default(),
check_queue: Default::default(),
}
}
fn scope_of(
&self,
sym: &Box<str>,
sym: &JsWord,
ctxt: SyntaxContext,
var_kind: Option<VarDeclKind>,
) -> &'a Scope<'_> {
@ -499,7 +599,7 @@ impl<'a> Scope<'a> {
let mut ctxts = smallvec![];
{
if let Some(cxs) = self.declared_symbols.get_mut().get(&*sym) {
if let Some(cxs) = self.declared_symbols.get_mut().get(&sym) {
if cxs.len() != 1 || cxs[0] != ctxt {
ctxts.extend_from_slice(&cxs);
}
@ -509,7 +609,7 @@ impl<'a> Scope<'a> {
let mut cur = self.parent;
while let Some(scope) = cur {
if let Some(cxs) = scope.declared_symbols.borrow().get(&*sym) {
if let Some(cxs) = scope.declared_symbols.borrow().get(&sym) {
if cxs.len() != 1 || cxs[0] != ctxt {
ctxts.extend_from_slice(&cxs);
}
@ -541,7 +641,7 @@ impl<'a> Scope<'a> {
sym
}
fn is_declared(&self, sym: &str) -> bool {
fn is_declared(&self, sym: &JsWord) -> bool {
if self.declared_symbols.borrow().contains_key(sym) {
return true;
}

View File

@ -1582,3 +1582,43 @@ fn issue_2211_2() {
",
);
}
#[test]
fn issue_2297_1() {
test(
|tester| {
let mark1 = Mark::fresh(Mark::root());
let mark2 = Mark::fresh(Mark::root());
let stmts = tester
.parse_stmts(
"actual1.js",
"
var _bar = require('./Bar');
var makeX = function(props) {
var _bar = props.bar;
var list = _bar.list;
return list.map(function() {
return _bar.bar;
});
};
",
)?
.fold_with(&mut OnceMarker::new(&[(
"_bar",
&[mark1, mark2, mark2, mark1],
)]));
Ok(stmts)
},
"
var _bar = require('./Bar');
var makeX = function(props) {
var _bar1 = props.bar;
var list = _bar1.list;
return list.map(function() {
return _bar.bar;
});
};
",
);
}

View File

@ -6151,9 +6151,9 @@ test!(
'use strict';
_classCallCheck(this, Foo);
};
let Test = function(Foo) {
let Test = function(Foo1) {
'use strict';
_inherits(Test, Foo);
_inherits(Test, Foo1);
function Test() {
_classCallCheck(this, Test);
return _possibleConstructorReturn(this, _getPrototypeOf(Test).apply(this, arguments));

View File

@ -0,0 +1,8 @@
import { bar } from './Bar';
const makeX = (props) => {
const _bar = props.bar;
const { list } = _bar;
return list.map(() => bar);
};

View File

@ -0,0 +1,8 @@
"use strict";
var _bar = require("./Bar");
const makeX = (props)=>{
const _bar1 = props.bar;
const { list } = _bar1;
return list.map(()=>_bar.bar
);
};

View File

@ -1,6 +1,6 @@
{
"name": "@swc/core",
"version": "1.2.90",
"version": "1.2.91",
"description": "Super-fast alternative for babel",
"homepage": "https://swc.rs",
"main": "./index.js",

View File

@ -6,7 +6,7 @@ license = "Apache-2.0 AND MIT"
name = "wasm"
publish = false
repository = "https://github.com/swc-project/swc.git"
version = "1.2.90"
version = "1.2.91"
[lib]
crate-type = ["cdylib"]