mirror of
https://github.com/swc-project/swc.git
synced 2024-11-27 13:38:33 +03:00
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:
parent
8af2173a33
commit
5e1003ec4c
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
});
|
||||
};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -0,0 +1,8 @@
|
||||
import { bar } from './Bar';
|
||||
|
||||
const makeX = (props) => {
|
||||
const _bar = props.bar;
|
||||
const { list } = _bar;
|
||||
|
||||
return list.map(() => bar);
|
||||
};
|
@ -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
|
||||
);
|
||||
};
|
@ -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",
|
||||
|
@ -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"]
|
||||
|
Loading…
Reference in New Issue
Block a user