feat(es/compat): Check for duplicate private names and undefined private names (#3613)

This commit is contained in:
Austaras 2022-02-18 15:23:18 +08:00 committed by GitHub
parent 6d5347100f
commit 5c03551341
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 109 additions and 36 deletions

View File

@ -1,13 +1,17 @@
#![allow(dead_code)]
use swc_common::{collections::AHashSet, util::take::Take, Mark, Spanned, SyntaxContext, DUMMY_SP};
use swc_common::{
collections::{AHashMap, AHashSet},
util::take::Take,
Mark, Spanned, SyntaxContext, DUMMY_SP,
};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::{helper, perf::Check};
use swc_ecma_transforms_classes::super_field::SuperFieldAccessFolder;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::{
alias_ident_for, alias_if_required, constructor::inject_after_super, default_constructor,
private_ident, quote_ident, undefined, ExprFactory, ModuleItemLike, StmtLike,
private_ident, quote_ident, undefined, ExprFactory, ModuleItemLike, StmtLike, HANDLER,
};
use swc_ecma_visit::{
as_folder, noop_visit_mut_type, noop_visit_type, Fold, Visit, VisitMut, VisitMutWith, VisitWith,
@ -16,8 +20,8 @@ use swc_ecma_visit::{
use self::{
class_name_tdz::ClassNameTdzFolder,
private_field::{
visit_private_in_expr, BrandCheckHandler, Private, PrivateAccessVisitor, PrivateKind,
PrivateRecord,
dup_private_method, visit_private_in_expr, BrandCheckHandler, Private,
PrivateAccessVisitor, PrivateKind, PrivateRecord,
},
this_in_static::{NewTargetInProp, ThisInStaticFolder},
used_name::UsedNameCollector,
@ -313,29 +317,64 @@ impl ClassProperties {
let private = Private {
mark: Mark::fresh(Mark::root()),
class_name: class_ident.clone(),
ident: class
.body
.iter()
.filter_map(|member| match member {
ClassMember::PrivateMethod(method) => Some((
method.key.id.sym.clone(),
PrivateKind {
is_method: true,
is_static: method.is_static,
},
)),
ident: {
let mut private_map = AHashMap::default();
ClassMember::PrivateProp(prop) => Some((
prop.key.id.sym.clone(),
PrivateKind {
is_method: false,
is_static: prop.is_static,
},
)),
for member in class.body.iter() {
match member {
ClassMember::PrivateMethod(method) => {
if let Some(kind) = private_map.get_mut(&method.key.id.sym) {
if dup_private_method(kind, method) {
let error =
format!("duplicate private name #{}.", method.key.id.sym);
HANDLER.with(|handler| {
handler.struct_span_err(method.key.id.span, &error).emit()
});
} else {
match method.kind {
MethodKind::Getter => kind.has_getter = true,
MethodKind::Setter => kind.has_setter = true,
MethodKind::Method => unreachable!(),
}
}
} else {
private_map.insert(
method.key.id.sym.clone(),
PrivateKind {
is_method: true,
is_static: method.is_static,
has_getter: method.kind == MethodKind::Getter,
has_setter: method.kind == MethodKind::Setter,
},
);
}
}
_ => None,
})
.collect(),
ClassMember::PrivateProp(prop) => {
if private_map.get(&prop.key.id.sym).is_some() {
let error = format!("duplicate private name #{}.", prop.key.id.sym);
HANDLER.with(|handler| {
handler.struct_span_err(prop.key.id.span, &error).emit()
});
} else {
private_map.insert(
prop.key.id.sym.clone(),
PrivateKind {
is_method: false,
is_static: prop.is_static,
has_getter: false,
has_setter: false,
},
);
};
}
_ => (),
};
}
private_map
},
};
self.private.push(private);

View File

@ -8,7 +8,9 @@ use swc_common::{
};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helper;
use swc_ecma_utils::{alias_ident_for, alias_if_required, prepend, quote_ident, ExprFactory};
use swc_ecma_utils::{
alias_ident_for, alias_if_required, prepend, quote_ident, ExprFactory, HANDLER,
};
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
pub(super) struct Private {
@ -40,21 +42,25 @@ impl PrivateRecord {
self.0.pop();
}
pub fn get(&self, name: &JsWord) -> (Mark, PrivateKind, &Ident) {
pub fn get(&self, name: &Ident) -> (Mark, PrivateKind, &Ident) {
for p in self.0.iter().rev() {
if let Some(kind) = p.ident.get(name) {
return (p.mark, kind.clone(), &p.class_name);
if let Some(kind) = p.ident.get(&name.sym) {
return (p.mark, *kind, &p.class_name);
}
}
// TODO: better error information with span
panic!("Private name #{} is not defined.", name);
let error = format!("private name #{} is not defined.", name.sym);
HANDLER.with(|handler| handler.struct_span_err(name.span, &error).emit());
(Mark::root(), PrivateKind::default(), &self.0[0].class_name)
}
}
#[derive(Clone, PartialEq)]
#[derive(Copy, Clone, PartialEq, Default)]
pub(super) struct PrivateKind {
pub is_static: bool,
pub is_method: bool,
pub has_getter: bool,
pub has_setter: bool,
}
pub(super) struct BrandCheckHandler<'a> {
@ -93,7 +99,11 @@ impl VisitMut for BrandCheckHandler<'_> {
self.names.insert(n.id.sym.clone());
let (mark, kind, class_name) = self.private.get(&n.id.sym);
let (mark, kind, class_name) = self.private.get(&n.id);
if mark == Mark::root() {
return;
}
if kind.is_static {
*e = Expr::Bin(BinExpr {
@ -191,7 +201,11 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
let obj = arg.obj.clone();
let (mark, kind, class_name) = self.private.get(&n.id.sym);
let (mark, kind, class_name) = self.private.get(&n.id);
if mark == Mark::root() {
return;
}
let ident = Ident::new(format!("_{}", n.id.sym).into(), n.id.span.apply_mark(mark));
let var = alias_ident_for(&obj, "_ref");
@ -322,7 +336,11 @@ impl<'a> VisitMut for PrivateAccessVisitor<'a> {
let obj = left.obj.clone();
let (mark, kind, class_name) = self.private.get(&n.id.sym);
let (mark, kind, class_name) = self.private.get(&n.id);
if mark == Mark::root() {
return;
}
let ident = Ident::new(format!("_{}", n.id.sym).into(), n.id.span.apply_mark(mark));
let var = alias_ident_for(&obj, "_ref");
@ -520,7 +538,11 @@ impl<'a> PrivateAccessVisitor<'a> {
let mut obj = e.obj.take();
let (mark, kind, class_name) = self.private.get(&n.id.sym);
let (mark, kind, class_name) = self.private.get(&n.id);
if mark == Mark::root() {
return (Expr::dummy(), None);
}
let method_name = Ident::new(
n.id.sym.clone(),
n.id.span.with_ctxt(SyntaxContext::empty()).apply_mark(mark),
@ -665,3 +687,15 @@ impl<'a> PrivateAccessVisitor<'a> {
}
}
}
/// only getter and setter in same scope could coexist
pub(super) fn dup_private_method(kind: &PrivateKind, method: &PrivateMethod) -> bool {
if !kind.is_method || kind.is_static != method.is_static || method.kind == MethodKind::Method {
return true;
}
!matches!(
(method.kind, kind.has_getter, kind.has_setter),
(MethodKind::Getter, false, true) | (MethodKind::Setter, true, false)
)
}