Files
ValueScript/valuescript_compiler/src/scope_analysis.rs
2023-03-01 15:34:11 +11:00

1622 lines
47 KiB
Rust

use std::{cell::RefCell, collections::HashMap, collections::HashSet, rc::Rc};
use swc_common::Spanned;
use super::diagnostic::{Diagnostic, DiagnosticLevel};
use super::scope::Builtin;
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
pub enum NameId {
Span(swc_common::Span),
Builtin(Builtin),
}
// TODO: Make use of these in the next phase of the compiler, remove the
// allow(dead_code) attributes
#[derive(Clone)]
pub struct Capture {
#[allow(dead_code)]
ref_: swc_common::Span,
#[allow(dead_code)]
captor_id: OwnerId,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum NameType {
Var,
Let,
Const,
Param,
Function,
Class,
Import,
Builtin,
}
#[derive(Clone)]
pub struct Name {
id: NameId,
owner_id: OwnerId,
sym: swc_atoms::JsWord,
type_: NameType,
mutations: Vec<swc_common::Span>,
captures: Vec<Capture>,
}
#[derive(Default)]
pub struct ScopeAnalysis {
pub names: HashMap<NameId, Name>,
pub captures: HashMap<OwnerId, HashSet<swc_common::Span>>,
pub diagnostics: Vec<Diagnostic>,
}
impl ScopeAnalysis {
pub fn run(module: &swc_ecma_ast::Module) -> ScopeAnalysis {
let mut sa = ScopeAnalysis::default();
let scope = init_std_scope();
for builtin in vec![Builtin::Debug, Builtin::Math, Builtin::undefined] {
sa.names.insert(
NameId::Builtin(builtin),
Name {
id: NameId::Builtin(builtin),
owner_id: OwnerId::Module,
sym: swc_atoms::JsWord::from(format!("{}", builtin)),
type_: NameType::Builtin,
mutations: vec![],
captures: vec![],
},
);
}
sa.module_level_hoists(&scope, module);
for module_item in &module.body {
sa.module_item(&scope, module_item);
}
for (name_id, name) in &sa.names {
if name.captures.len() > 0 {
if name.type_ == NameType::Let {
match name_id {
NameId::Span(span) => {
sa.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Lint,
message: format!(
"`{}` should be declared using `const` because it is implicitly \
const due to capture",
name.sym
),
span: *span,
});
}
NameId::Builtin(_) => {
sa.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "Builtin should not have type_ let".to_string(),
span: swc_common::DUMMY_SP,
});
}
}
}
for mutation in &name.mutations {
sa.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: format!("Cannot mutate captured variable `{}`", name.sym),
span: *mutation,
});
}
}
}
return sa;
}
fn insert_name(&mut self, scope: &XScope, type_: NameType, origin_ident: &swc_ecma_ast::Ident) {
let name = Name {
id: NameId::Span(origin_ident.span),
owner_id: scope.borrow().owner_id.clone(),
sym: origin_ident.sym.clone(),
type_,
mutations: Vec::new(),
captures: Vec::new(),
};
self.names.insert(name.id.clone(), name.clone());
scope.set(
&origin_ident.sym,
name.id.clone(),
origin_ident.span,
&mut self.diagnostics,
);
}
fn insert_capture(&mut self, captor_id: &OwnerId, name_id: &NameId, ref_: swc_common::Span) {
let name = match self.names.get_mut(name_id) {
Some(name) => name,
None => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: format!("Expected name_id in names: {:?}", name_id),
span: ref_,
});
return;
}
};
self
.captures
.entry(captor_id.clone())
.or_insert_with(HashSet::new)
.insert(ref_);
name.captures.push(Capture {
ref_,
captor_id: captor_id.clone(),
});
}
fn module_item(&mut self, scope: &XScope, module_item: &swc_ecma_ast::ModuleItem) {
use swc_ecma_ast::ModuleDecl;
use swc_ecma_ast::ModuleItem;
match module_item {
ModuleItem::ModuleDecl(module_decl) => match module_decl {
ModuleDecl::Import(_) => {}
ModuleDecl::ExportDecl(ed) => {
self.decl(&scope, &ed.decl);
}
ModuleDecl::ExportNamed(_) => {}
ModuleDecl::ExportDefaultDecl(edd) => {
self.default_decl(&scope, &edd.decl);
}
ModuleDecl::ExportDefaultExpr(ede) => {
self.expr(&scope, &ede.expr);
}
ModuleDecl::ExportAll(_) => {}
ModuleDecl::TsImportEquals(ts_import_equals) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "TsImportEquals is not supported".to_string(),
span: ts_import_equals.span,
});
}
ModuleDecl::TsExportAssignment(ts_export_assignment) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "TsExportAssignment is not supported".to_string(),
span: ts_export_assignment.span,
});
}
ModuleDecl::TsNamespaceExport(ts_namespace_export) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "TsNamespaceExport is not supported".to_string(),
span: ts_namespace_export.span,
});
}
},
ModuleItem::Stmt(stmt) => {
self.stmt(&scope, &stmt);
}
};
}
fn import_decl(&mut self, scope: &XScope, import_decl: &swc_ecma_ast::ImportDecl) {
for specifier in &import_decl.specifiers {
self.import_specifier(&scope, specifier);
}
}
fn import_specifier(&mut self, scope: &XScope, import_specifier: &swc_ecma_ast::ImportSpecifier) {
use swc_ecma_ast::ImportSpecifier::*;
match import_specifier {
Named(named_specifier) => {
if named_specifier.is_type_only {
return;
}
self.insert_name(scope, NameType::Import, &named_specifier.local);
}
Default(default_specifier) => {
self.insert_name(scope, NameType::Import, &default_specifier.local);
}
Namespace(namespace_specifier) => {
self.insert_name(scope, NameType::Import, &namespace_specifier.local);
}
}
}
fn decl(&mut self, scope: &XScope, decl: &swc_ecma_ast::Decl) {
use swc_ecma_ast::Decl;
match decl {
Decl::Class(class_decl) => {
self.class_(scope, &Some(class_decl.ident.clone()), &class_decl.class);
}
Decl::Fn(fn_decl) => {
self.function(scope, &Some(fn_decl.ident.clone()), &fn_decl.function);
}
Decl::Var(var_decl) => {
for decl in &var_decl.decls {
self.var_declarator(&scope, var_decl.kind, decl);
}
}
Decl::TsInterface(_) => {}
Decl::TsTypeAlias(_) => {}
Decl::TsEnum(ts_enum) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO: Implement TsEnum declarations".to_string(),
span: ts_enum.span,
});
}
Decl::TsModule(ts_module) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "TsModule declaration is not supported".to_string(),
span: ts_module.span,
});
}
}
}
fn function(
&mut self,
scope: &XScope,
name: &Option<swc_ecma_ast::Ident>,
function: &swc_ecma_ast::Function,
) {
let child_scope = scope.nest(Some(OwnerId::Span(function.span.clone())));
if let Some(name) = name {
self.insert_name(&child_scope, NameType::Function, name);
}
for param in &function.params {
self.param_pat(&child_scope, &param.pat);
}
for body in &function.body {
self.function_level_hoists(&child_scope, &body);
self.block_stmt(&child_scope, &body);
}
}
fn module_level_hoists(&mut self, scope: &XScope, module: &swc_ecma_ast::Module) {
for item in &module.body {
self.module_level_hoists_item(scope, item);
}
}
fn module_level_hoists_item(&mut self, scope: &XScope, module_item: &swc_ecma_ast::ModuleItem) {
use swc_ecma_ast::ModuleDecl;
use swc_ecma_ast::ModuleItem;
match module_item {
ModuleItem::ModuleDecl(module_decl) => match module_decl {
ModuleDecl::Import(import_decl) => {
self.import_decl(&scope, import_decl);
}
ModuleDecl::ExportDecl(ed) => match &ed.decl {
swc_ecma_ast::Decl::Class(class_decl) => {
self.insert_name(&scope, NameType::Class, &class_decl.ident);
}
swc_ecma_ast::Decl::Fn(fn_decl) => {
self.insert_name(&scope, NameType::Function, &fn_decl.ident);
}
swc_ecma_ast::Decl::Var(var_decl) => {
let name_type = match var_decl.kind {
swc_ecma_ast::VarDeclKind::Const => NameType::Const,
swc_ecma_ast::VarDeclKind::Let => NameType::Let,
swc_ecma_ast::VarDeclKind::Var => NameType::Var,
};
for decl in &var_decl.decls {
for ident in self.get_pat_idents(&decl.name) {
self.insert_name(&scope, name_type, &ident);
}
}
}
swc_ecma_ast::Decl::TsInterface(_) => {}
swc_ecma_ast::Decl::TsTypeAlias(_) => {}
swc_ecma_ast::Decl::TsEnum(_) => {
// Diagnostic emitted after hoist processing
}
swc_ecma_ast::Decl::TsModule(_) => {
// Diagnostic emitted after hoist processing
}
},
ModuleDecl::ExportNamed(_) => {}
ModuleDecl::ExportDefaultDecl(edd) => match &edd.decl {
swc_ecma_ast::DefaultDecl::Class(class_decl) => {
if let Some(ident) = &class_decl.ident {
self.insert_name(&scope, NameType::Class, ident);
}
}
swc_ecma_ast::DefaultDecl::Fn(fn_decl) => {
if let Some(ident) = &fn_decl.ident {
self.insert_name(&scope, NameType::Function, ident);
}
}
swc_ecma_ast::DefaultDecl::TsInterfaceDecl(_) => {}
},
ModuleDecl::ExportDefaultExpr(_) => {}
ModuleDecl::ExportAll(_) => {}
ModuleDecl::TsImportEquals(_) => {
// Diagnostic emitted after hoist processing
}
ModuleDecl::TsExportAssignment(_) => {
// Diagnostic emitted after hoist processing
}
ModuleDecl::TsNamespaceExport(_) => {
// Diagnostic emitted after hoist processing
}
},
ModuleItem::Stmt(stmt) => {
self.function_level_hoists_stmt(&scope, stmt);
self.block_level_hoists_stmt(&scope, stmt);
}
};
}
fn function_level_hoists(&mut self, scope: &XScope, block: &swc_ecma_ast::BlockStmt) {
for stmt in &block.stmts {
self.function_level_hoists_stmt(scope, stmt);
}
}
fn function_level_hoists_stmt(&mut self, scope: &XScope, stmt: &swc_ecma_ast::Stmt) {
use swc_ecma_ast::Decl;
use swc_ecma_ast::Stmt;
match stmt {
Stmt::Decl(decl) => match decl {
Decl::Var(var_decl) => {
if var_decl.kind == swc_ecma_ast::VarDeclKind::Var {
for decl in &var_decl.decls {
for ident in self.get_pat_idents(&decl.name) {
self.insert_name(scope, NameType::Var, &ident);
}
}
}
}
_ => {}
},
Stmt::Block(block_stmt) => {
for stmt in &block_stmt.stmts {
self.function_level_hoists_stmt(scope, stmt);
}
}
Stmt::For(for_) => {
if let Some(swc_ecma_ast::VarDeclOrExpr::VarDecl(var_decl)) = &for_.init {
if var_decl.kind == swc_ecma_ast::VarDeclKind::Var {
for decl in &var_decl.decls {
for ident in self.get_pat_idents(&decl.name) {
self.insert_name(scope, NameType::Var, &ident);
}
}
}
}
}
Stmt::ForIn(for_in) => {
if let swc_ecma_ast::VarDeclOrPat::VarDecl(var_decl) = &for_in.left {
if var_decl.kind == swc_ecma_ast::VarDeclKind::Var {
for decl in &var_decl.decls {
for ident in self.get_pat_idents(&decl.name) {
self.insert_name(scope, NameType::Var, &ident);
}
}
}
}
}
Stmt::ForOf(for_of) => {
if let swc_ecma_ast::VarDeclOrPat::VarDecl(var_decl) = &for_of.left {
if var_decl.kind == swc_ecma_ast::VarDeclKind::Var {
for decl in &var_decl.decls {
for ident in self.get_pat_idents(&decl.name) {
self.insert_name(scope, NameType::Var, &ident);
}
}
}
}
}
_ => {}
}
}
fn block_level_hoists(&mut self, scope: &XScope, block: &swc_ecma_ast::BlockStmt) {
for stmt in &block.stmts {
self.block_level_hoists_stmt(scope, stmt);
}
}
fn block_level_hoists_stmt(&mut self, scope: &XScope, stmt: &swc_ecma_ast::Stmt) {
use swc_ecma_ast::Decl;
use swc_ecma_ast::Stmt;
match stmt {
Stmt::Decl(decl) => match decl {
Decl::Class(class) => {
self.insert_name(scope, NameType::Class, &class.ident);
}
Decl::Fn(fn_) => {
self.insert_name(scope, NameType::Function, &fn_.ident);
}
Decl::Var(var_decl) => {
self.block_level_hoists_var_decl(scope, var_decl);
}
Decl::TsInterface(_) => {}
Decl::TsTypeAlias(_) => {}
Decl::TsEnum(_) => {
// Diagnostic emitted after hoist processing
}
Decl::TsModule(_) => {
// Diagnostic emitted after hoist processing
}
},
_ => {}
}
}
fn block_level_hoists_var_decl(&mut self, scope: &XScope, var_decl: &swc_ecma_ast::VarDecl) {
let name_type = match var_decl.kind {
swc_ecma_ast::VarDeclKind::Var => return,
swc_ecma_ast::VarDeclKind::Let => NameType::Let,
swc_ecma_ast::VarDeclKind::Const => NameType::Const,
};
for decl in &var_decl.decls {
for ident in self.get_pat_idents(&decl.name) {
self.insert_name(scope, name_type, &ident);
}
}
}
fn get_pat_idents(&mut self, pat: &swc_ecma_ast::Pat) -> Vec<swc_ecma_ast::Ident> {
let mut idents = Vec::new();
self.get_pat_idents_impl(&mut idents, pat);
idents
}
fn get_pat_idents_impl(
&mut self,
idents: &mut Vec<swc_ecma_ast::Ident>,
pat: &swc_ecma_ast::Pat,
) {
use swc_ecma_ast::Pat;
match pat {
Pat::Ident(ident) => {
idents.push(ident.id.clone());
}
Pat::Array(array_pat) => {
for elem in &array_pat.elems {
if let Some(elem) = elem {
self.get_pat_idents_impl(idents, elem);
}
}
}
Pat::Rest(rest_pat) => {
self.get_pat_idents_impl(idents, &rest_pat.arg);
}
Pat::Object(object_pat) => {
for prop in &object_pat.props {
match prop {
swc_ecma_ast::ObjectPatProp::KeyValue(key_value) => {
self.get_pat_idents_impl(idents, &key_value.value);
}
swc_ecma_ast::ObjectPatProp::Assign(assign) => {
idents.push(assign.key.clone());
}
swc_ecma_ast::ObjectPatProp::Rest(rest) => {
self.get_pat_idents_impl(idents, &rest.arg);
}
}
}
}
Pat::Assign(assign_pat) => {
self.get_pat_idents_impl(idents, &assign_pat.left);
}
Pat::Expr(expr) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "Pattern expression not expected in this context".to_string(),
span: expr.span(),
});
}
Pat::Invalid(invalid) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Invalid pattern".to_string(),
span: invalid.span,
});
}
}
}
fn arrow(&mut self, scope: &XScope, arrow: &swc_ecma_ast::ArrowExpr) {
let child_scope = scope.nest(Some(OwnerId::Span(arrow.span.clone())));
for param in &arrow.params {
self.param_pat(&child_scope, param);
}
match &arrow.body {
swc_ecma_ast::BlockStmtOrExpr::BlockStmt(block) => {
self.block_stmt(&child_scope, block);
}
swc_ecma_ast::BlockStmtOrExpr::Expr(expr) => {
self.expr(&child_scope, expr);
}
}
}
fn var_declarator_pat(&mut self, scope: &XScope, type_: NameType, pat: &swc_ecma_ast::Pat) {
use swc_ecma_ast::Pat;
match pat {
Pat::Ident(_) => {}
Pat::Array(array) => {
for elem in &array.elems {
if let Some(elem) = elem {
self.var_declarator_pat(scope, type_, elem);
}
}
}
Pat::Object(object) => {
for prop in &object.props {
match prop {
swc_ecma_ast::ObjectPatProp::KeyValue(key_value) => {
self.prop_key(scope, &key_value.key);
self.var_declarator_pat(scope, type_, &key_value.value);
}
swc_ecma_ast::ObjectPatProp::Assign(assign) => {
if let Some(value) = &assign.value {
self.expr(scope, value);
}
}
swc_ecma_ast::ObjectPatProp::Rest(rest) => {
self.var_declarator_pat(scope, type_, &rest.arg);
}
}
}
}
Pat::Rest(rest) => {
self.var_declarator_pat(scope, type_, &rest.arg);
}
Pat::Assign(assign) => {
self.var_declarator_pat(scope, type_, &assign.left);
self.expr(scope, &assign.right);
}
Pat::Invalid(invalid) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Invalid pattern".to_string(),
span: invalid.span,
});
}
Pat::Expr(expr) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "Pattern expression not expected in declarator".to_string(),
span: expr.span(),
});
}
}
}
fn var_declarator(
&mut self,
scope: &XScope,
kind: swc_ecma_ast::VarDeclKind,
var_declarator: &swc_ecma_ast::VarDeclarator,
) {
let type_ = match kind {
swc_ecma_ast::VarDeclKind::Var => NameType::Var,
swc_ecma_ast::VarDeclKind::Let => NameType::Let,
swc_ecma_ast::VarDeclKind::Const => NameType::Const,
};
self.var_declarator_pat(scope, type_, &var_declarator.name);
for init in &var_declarator.init {
self.expr(scope, init);
}
}
fn default_decl(&mut self, scope: &XScope, default_decl: &swc_ecma_ast::DefaultDecl) {
use swc_ecma_ast::DefaultDecl;
match &default_decl {
DefaultDecl::Class(class_expr) => {
self.class_(&scope, &class_expr.ident, &class_expr.class);
}
DefaultDecl::Fn(fn_expr) => {
self.fn_expr(&scope, fn_expr);
}
DefaultDecl::TsInterfaceDecl(_) => {}
}
}
fn class_(
&mut self,
scope: &XScope,
ident: &Option<swc_ecma_ast::Ident>,
class_: &swc_ecma_ast::Class,
) {
let child_scope = scope.nest(Some(OwnerId::Span(class_.span)));
if let Some(ident) = ident {
self.insert_name(&child_scope, NameType::Class, ident);
}
for member in &class_.body {
self.class_member(&child_scope, member);
}
}
fn class_member(&mut self, scope: &XScope, class_member: &swc_ecma_ast::ClassMember) {
use swc_ecma_ast::ClassMember::*;
match class_member {
Constructor(constructor) => {
let child_scope = scope.nest(None);
for param in &constructor.params {
match param {
swc_ecma_ast::ParamOrTsParamProp::Param(param) => {
self.param_pat(&child_scope, &param.pat);
}
swc_ecma_ast::ParamOrTsParamProp::TsParamProp(ts_param_prop) => {
match &ts_param_prop.param {
swc_ecma_ast::TsParamPropParam::Ident(ident) => {
self.insert_name(&child_scope, NameType::Param, &ident.id);
}
swc_ecma_ast::TsParamPropParam::Assign(assign) => {
self.param_pat(&child_scope, &assign.left);
self.expr(&child_scope, &assign.right);
}
}
}
}
}
for body in &constructor.body {
self.block_stmt(&child_scope, body);
}
}
Method(method) => {
self.function(scope, &None, &method.function);
}
ClassProp(class_prop) => {
self.prop_key(scope, &class_prop.key);
if let Some(value) = &class_prop.value {
self.expr(scope, value);
}
}
TsIndexSignature(_) => {
// Example: `[key: string]: string`
// Type only. Nothing to do.
}
PrivateMethod(private_method) => {
// The .key of a private method can only be an identifier, so .prop_key
// is not needed.
self.function(scope, &None, &private_method.function);
}
PrivateProp(private_prop) => {
// The .key of a private prop can only be an identifier, so .prop_key
// is not needed.
if let Some(value) = &private_prop.value {
self.expr(scope, value);
}
}
Empty(_) => {}
StaticBlock(static_block) => {
self.block_stmt(&scope, &static_block.body);
}
}
}
fn fn_expr(&mut self, scope: &XScope, fn_expr: &swc_ecma_ast::FnExpr) {
self.function(scope, &fn_expr.ident, &fn_expr.function);
}
fn expr(&mut self, scope: &XScope, expr: &swc_ecma_ast::Expr) {
use swc_ecma_ast::Expr;
match expr {
Expr::This(_) => {}
Expr::Ident(ident) => {
self.ident(scope, ident);
}
Expr::Lit(_) => {}
Expr::Array(array) => {
for elem in &array.elems {
if let Some(elem) = elem {
self.expr(scope, &elem.expr);
}
}
}
Expr::Object(object) => {
for prop_or_spread in &object.props {
self.prop_or_spread(scope, prop_or_spread);
}
}
Expr::Fn(fn_expr) => {
self.fn_expr(scope, fn_expr);
}
Expr::Unary(unary) => {
self.expr(scope, &unary.arg);
match unary.op {
swc_ecma_ast::UnaryOp::TypeOf => {}
swc_ecma_ast::UnaryOp::Void => {}
swc_ecma_ast::UnaryOp::Delete => {
self.mutate_expr(scope, &unary.arg);
}
swc_ecma_ast::UnaryOp::Plus => {}
swc_ecma_ast::UnaryOp::Minus => {}
swc_ecma_ast::UnaryOp::Tilde => {}
swc_ecma_ast::UnaryOp::Bang => {}
}
}
Expr::Update(update) => {
self.expr(scope, &update.arg);
self.mutate_expr(scope, &update.arg);
}
Expr::Bin(bin) => {
self.expr(scope, &bin.left);
self.expr(scope, &bin.right);
}
Expr::Assign(assign) => match &assign.left {
swc_ecma_ast::PatOrExpr::Pat(pat) => {
self.mutate_pat(scope, pat);
}
swc_ecma_ast::PatOrExpr::Expr(expr) => {
self.mutate_expr(scope, expr);
}
},
Expr::Seq(seq) => {
for expr in &seq.exprs {
self.expr(scope, expr);
}
}
Expr::Cond(cond) => {
self.expr(scope, &cond.test);
self.expr(scope, &cond.cons);
self.expr(scope, &cond.alt);
}
Expr::Yield(yield_) => {
if let Some(arg) = &yield_.arg {
self.expr(scope, arg);
}
}
Expr::Await(await_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "await is not supported".to_string(),
span: await_.span,
});
}
Expr::Member(member) => {
self.expr(scope, &member.obj);
}
Expr::Call(call) => {
match &call.callee {
swc_ecma_ast::Callee::Super(_) => {}
swc_ecma_ast::Callee::Import(_) => {}
swc_ecma_ast::Callee::Expr(expr) => {
self.expr(scope, expr);
}
}
for arg in &call.args {
self.expr(scope, &arg.expr);
}
}
Expr::New(new) => {
self.expr(scope, &new.callee);
if let Some(args) = &new.args {
for arg in args {
self.expr(scope, &arg.expr);
}
}
}
Expr::Paren(paren) => {
self.expr(scope, &paren.expr);
}
Expr::Tpl(tpl) => {
for elem in &tpl.exprs {
self.expr(scope, elem);
}
}
Expr::TaggedTpl(_) => {
// TODO (diagnostic emitted elsewhere)
}
Expr::Arrow(arrow) => {
self.arrow(scope, arrow);
}
Expr::Class(class_expr) => {
self.class_(scope, &class_expr.ident, &class_expr.class);
}
Expr::MetaProp(_) => {}
Expr::Invalid(_) => {}
Expr::TsTypeAssertion(_) => {}
Expr::TsConstAssertion(_) => {}
Expr::TsNonNull(_) => {}
Expr::TsAs(_) => {}
Expr::OptChain(_) => {}
Expr::SuperProp(super_prop) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: super_prop.span,
});
}
Expr::JSXMember(_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: expr.span(),
});
}
Expr::JSXNamespacedName(_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: expr.span(),
});
}
Expr::JSXEmpty(_) => {}
Expr::JSXElement(jsx_element) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: jsx_element.span,
});
}
Expr::JSXFragment(jsx_fragment) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: jsx_fragment.span,
});
}
Expr::TsInstantiation(ts_instantiation) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: ts_instantiation.span,
});
}
Expr::PrivateName(private_name) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: private_name.span,
});
}
}
}
fn mutate_expr(&mut self, scope: &XScope, expr: &swc_ecma_ast::Expr) {
use swc_ecma_ast::Expr;
match expr {
Expr::Ident(ident) => {
self.mutate_ident(scope, ident);
}
Expr::Member(member) => {
self.mutate_expr(scope, &member.obj);
}
Expr::Call(call) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Call expressions cannot be mutated".to_string(),
span: call.span,
});
}
Expr::New(new) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "New expressions cannot be mutated".to_string(),
span: new.span,
});
}
Expr::Paren(paren) => {
self.mutate_expr(scope, &paren.expr);
}
Expr::Tpl(tpl) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Template literals cannot be mutated".to_string(),
span: tpl.span,
});
}
Expr::TaggedTpl(tagged_tpl) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Tagged template literals cannot be mutated".to_string(),
span: tagged_tpl.span,
});
}
Expr::Arrow(arrow) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Arrow functions cannot be mutated".to_string(),
span: arrow.span,
});
}
Expr::Class(class) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Class expressions cannot be mutated".to_string(),
span: class.class.span,
});
}
Expr::MetaProp(meta_prop) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Meta properties cannot be mutated".to_string(),
span: meta_prop.span,
});
}
Expr::Invalid(invalid) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Invalid expression".to_string(),
span: invalid.span,
});
}
Expr::TsTypeAssertion(ts_type_assertion) => {
self.mutate_expr(scope, &ts_type_assertion.expr);
}
Expr::TsConstAssertion(ts_const_assertion) => {
self.mutate_expr(scope, &ts_const_assertion.expr);
}
Expr::TsNonNull(ts_non_null) => {
self.mutate_expr(scope, &ts_non_null.expr);
}
Expr::TsAs(as_expr) => {
self.mutate_expr(scope, &as_expr.expr);
}
Expr::OptChain(opt_chain) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Optional property accesses (a?.b) cannot be mutated".to_string(),
span: opt_chain.span,
});
}
Expr::This(_) => {
// TODO: Add capture+mutation analysis for `this`.
}
Expr::Array(array) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Mutating a (non-pattern) array expression is not valid. \
This is an unusual case that can occur with things like [a,b]+=c."
.to_string(),
span: array.span,
});
}
Expr::Object(object) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Mutating a (non-pattern) object expression is not valid. \
This is an unusual case - it's not clear whether SWC ever emit it. \
Please consider creating an issue: \
https://github.com/ValueScript/issues/new."
.to_string(),
span: object.span,
});
}
Expr::Fn(fn_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: fn_.function.span,
});
}
Expr::Unary(unary) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: unary.span,
});
}
Expr::Update(update) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: update.span,
});
}
Expr::Bin(bin) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: bin.span,
});
}
Expr::Assign(assign) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: assign.span,
});
}
Expr::SuperProp(super_prop) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: super_prop.span,
});
}
Expr::Cond(cond) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: cond.span,
});
}
Expr::Seq(seq) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: seq.span,
});
}
Expr::Lit(_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: expr.span(),
});
}
Expr::Yield(yield_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: yield_.span,
});
}
Expr::Await(await_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "await is not supported".to_string(),
span: await_.span,
});
}
Expr::JSXMember(_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: expr.span(),
});
}
Expr::JSXNamespacedName(_) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: expr.span(),
});
}
Expr::JSXEmpty(_) => {}
Expr::JSXElement(jsx_element) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: jsx_element.span,
});
}
Expr::JSXFragment(jsx_fragment) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: jsx_fragment.span,
});
}
Expr::TsInstantiation(ts_instantiation) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: ts_instantiation.span,
});
}
Expr::PrivateName(private_name) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO".to_string(),
span: private_name.span,
});
}
}
}
fn mutate_pat(&mut self, scope: &XScope, pat: &swc_ecma_ast::Pat) {
use swc_ecma_ast::Pat;
match pat {
Pat::Ident(ident) => {
self.mutate_ident(scope, &ident.id);
}
Pat::Array(array_pat) => {
for elem in &array_pat.elems {
if let Some(elem) = elem {
self.mutate_pat(scope, elem);
}
}
}
Pat::Rest(rest_pat) => {
self.mutate_pat(scope, &rest_pat.arg);
}
Pat::Object(object_pat) => {
for prop in &object_pat.props {
match prop {
swc_ecma_ast::ObjectPatProp::KeyValue(key_value) => {
self.mutate_pat(scope, &key_value.value);
}
swc_ecma_ast::ObjectPatProp::Assign(assign) => {
// TODO: How is `({ y: x = 3 } = { y: 4 })` handled?
self.mutate_ident(scope, &assign.key);
if let Some(value) = &assign.value {
// Note: Generally mutate_* only processes the mutation aspect
// of an expression, but here, because this only occurs in the
// context of mutation, we call back into expr. This is
// consistent with calling into expr from var_declarator_pat and
// param_pat.
self.expr(scope, value);
}
}
swc_ecma_ast::ObjectPatProp::Rest(rest) => {
self.mutate_pat(scope, &rest.arg);
}
}
}
}
Pat::Assign(assign_pat) => {
self.mutate_pat(scope, &assign_pat.left);
self.expr(scope, &assign_pat.right);
}
Pat::Invalid(invalid) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Invalid pattern".to_string(),
span: invalid.span,
});
}
Pat::Expr(expr) => {
self.mutate_expr(&scope, expr);
}
}
}
fn mutate_ident(&mut self, scope: &XScope, ident: &swc_ecma_ast::Ident) {
let name_id = match scope.get(&ident.sym) {
Some(name_id) => name_id,
None => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Unresolved reference".to_string(),
span: ident.span,
});
return;
}
};
let name = match self.names.get_mut(&name_id) {
Some(name) => name,
None => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "Expected name_id in names".to_string(),
span: ident.span,
});
return;
}
};
name.mutations.push(ident.span);
}
fn ident(&mut self, scope: &XScope, ident: &swc_ecma_ast::Ident) {
let name_id = match scope.get(&ident.sym) {
Some(name_id) => name_id,
None => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Unresolved reference".to_string(),
span: ident.span,
});
return;
}
};
let name = match self.names.get(&name_id) {
Some(name) => name,
None => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "Expected name_id in names".to_string(),
span: ident.span,
});
return;
}
};
if &name.owner_id != &scope.borrow().owner_id {
self.insert_capture(&scope.borrow().owner_id, &name_id, ident.span);
}
}
fn prop_key(&mut self, scope: &XScope, prop_name: &swc_ecma_ast::PropName) {
use swc_ecma_ast::PropName;
match prop_name {
PropName::Ident(_) => {}
PropName::Str(_) => {}
PropName::Num(_) => {}
PropName::Computed(computed) => {
self.expr(scope, &computed.expr);
}
PropName::BigInt(_) => {}
}
}
fn prop_or_spread(&mut self, scope: &XScope, prop_or_spread: &swc_ecma_ast::PropOrSpread) {
use swc_ecma_ast::PropOrSpread;
match prop_or_spread {
PropOrSpread::Prop(prop) => match &**prop {
swc_ecma_ast::Prop::Shorthand(ident) => {
self.ident(scope, &ident);
}
swc_ecma_ast::Prop::KeyValue(key_value) => {
self.prop_key(scope, &key_value.key);
self.expr(scope, &key_value.value);
}
swc_ecma_ast::Prop::Getter(getter) => {
self.prop_key(scope, &getter.key);
if let Some(body) = &getter.body {
self.block_stmt(&scope, body);
}
}
swc_ecma_ast::Prop::Setter(setter) => {
self.prop_key(scope, &setter.key);
self.param_pat(scope, &setter.param);
if let Some(body) = &setter.body {
self.block_stmt(&scope, body);
}
}
swc_ecma_ast::Prop::Method(method) => {
self.prop_key(scope, &method.key);
self.function(scope, &None, &method.function);
}
swc_ecma_ast::Prop::Assign(assign) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::InternalError,
message: "TODO: implement property assignments (what are these?)".to_string(),
span: assign.key.span, // TODO: Proper span of assign
});
}
},
PropOrSpread::Spread(spread) => {
self.expr(scope, &spread.expr);
}
}
}
fn stmt(&mut self, scope: &XScope, stmt: &swc_ecma_ast::Stmt) {
use swc_ecma_ast::Stmt;
match stmt {
Stmt::Block(block) => {
self.block_stmt(&scope, block);
}
Stmt::Empty(_) => {}
Stmt::Debugger(_) => {}
Stmt::With(with) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Not supported: with statement".to_string(),
span: with.span,
});
}
Stmt::Return(return_) => {
if let Some(arg) = &return_.arg {
self.expr(&scope, arg);
}
}
Stmt::Labeled(labeled_stmt) => {
self.stmt(&scope, &labeled_stmt.body);
}
Stmt::Break(_) => {}
Stmt::Continue(_) => {}
Stmt::If(if_) => {
self.expr(&scope, &if_.test);
self.stmt(&scope, &if_.cons);
for alt in &if_.alt {
self.stmt(&scope, alt);
}
}
Stmt::Switch(switch_) => {
self.expr(&scope, &switch_.discriminant);
let child_scope = scope.nest(None);
for case in &switch_.cases {
for test in &case.test {
self.expr(&child_scope, test);
}
for stmt in &case.cons {
self.stmt(&child_scope, stmt);
}
}
}
Stmt::Throw(throw_) => {
self.expr(&scope, &throw_.arg);
}
Stmt::Try(try_) => {
self.block_stmt(&scope, &try_.block);
for catch in &try_.handler {
let child_scope = scope.nest(None);
for param in &catch.param {
// TODO: Associate the catch param with the catch block
self.param_pat(&child_scope, param);
}
self.block_stmt(&child_scope, &catch.body);
}
for finally in &try_.finalizer {
self.block_stmt(&scope, finally);
}
}
Stmt::While(while_) => {
self.expr(&scope, &while_.test);
self.stmt(&scope, &while_.body);
}
Stmt::DoWhile(do_while_) => {
self.stmt(&scope, &do_while_.body);
self.expr(&scope, &do_while_.test);
}
Stmt::For(for_) => {
let child_scope = scope.nest(None);
for init in &for_.init {
if let swc_ecma_ast::VarDeclOrExpr::VarDecl(var_decl) = init {
self.block_level_hoists_var_decl(&child_scope, var_decl);
}
match init {
swc_ecma_ast::VarDeclOrExpr::VarDecl(var_decl) => {
self.var_decl(&child_scope, var_decl);
}
swc_ecma_ast::VarDeclOrExpr::Expr(expr) => {
self.expr(&child_scope, expr);
}
};
}
for test in &for_.test {
self.expr(&child_scope, test);
}
for update in &for_.update {
self.expr(&child_scope, update);
}
self.stmt(&child_scope, &for_.body);
}
Stmt::ForIn(for_in) => {
let child_scope = scope.nest(None);
match &for_in.left {
swc_ecma_ast::VarDeclOrPat::VarDecl(var_decl) => {
self.block_level_hoists_var_decl(&child_scope, var_decl);
self.var_decl(&child_scope, var_decl);
}
swc_ecma_ast::VarDeclOrPat::Pat(pat) => {
self.param_pat(&child_scope, pat);
}
}
self.expr(&child_scope, &for_in.right);
self.stmt(&child_scope, &for_in.body);
}
Stmt::ForOf(for_of) => {
let child_scope = scope.nest(None);
match &for_of.left {
swc_ecma_ast::VarDeclOrPat::VarDecl(var_decl) => {
self.block_level_hoists_var_decl(&child_scope, var_decl);
self.var_decl(&child_scope, var_decl);
}
swc_ecma_ast::VarDeclOrPat::Pat(pat) => {
self.param_pat(&child_scope, pat);
}
}
self.expr(&child_scope, &for_of.right);
self.stmt(&child_scope, &for_of.body);
}
Stmt::Decl(decl) => {
self.decl(&scope, decl);
}
Stmt::Expr(expr) => {
self.expr(&scope, &expr.expr);
}
};
}
fn block_stmt(&mut self, scope: &XScope, block_stmt: &swc_ecma_ast::BlockStmt) {
let child_scope = scope.nest(None);
self.block_level_hoists(&child_scope, block_stmt);
for stmt in &block_stmt.stmts {
self.stmt(&child_scope, stmt);
}
}
fn param_pat(&mut self, scope: &XScope, param_pat: &swc_ecma_ast::Pat) {
use swc_ecma_ast::Pat;
match param_pat {
Pat::Ident(ident) => {
self.insert_name(&scope, NameType::Param, &ident.id);
}
Pat::Array(array_pat) => {
for elem in &array_pat.elems {
match elem {
Some(pat) => self.param_pat(&scope, pat),
None => {}
}
}
}
Pat::Rest(rest_pat) => {
self.param_pat(&scope, &rest_pat.arg);
}
Pat::Object(object_pat) => {
for prop in &object_pat.props {
match prop {
swc_ecma_ast::ObjectPatProp::KeyValue(key_value) => {
self.param_pat(&scope, &key_value.value);
}
swc_ecma_ast::ObjectPatProp::Assign(assign) => {
self.insert_name(&scope, NameType::Param, &assign.key);
if let Some(default) = &assign.value {
self.expr(&scope, default);
}
}
swc_ecma_ast::ObjectPatProp::Rest(rest) => {
self.param_pat(&scope, &rest.arg);
}
}
}
}
Pat::Assign(assign_pat) => {
self.param_pat(&scope, &assign_pat.left);
self.expr(&scope, &assign_pat.right);
}
Pat::Expr(expr) => {
self.expr(&scope, &expr);
}
Pat::Invalid(invalid) => {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Invalid pattern".to_string(),
span: invalid.span,
});
}
}
}
fn var_decl(&mut self, scope: &XScope, var_decl: &swc_ecma_ast::VarDecl) {
for decl in &var_decl.decls {
self.var_declarator(&scope, var_decl.kind, decl);
}
}
}
struct XScopeData {
pub owner_id: OwnerId,
pub name_map: HashMap<swc_atoms::JsWord, NameId>,
pub parent: Option<Rc<RefCell<XScopeData>>>,
}
type XScope = Rc<RefCell<XScopeData>>;
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum OwnerId {
Span(swc_common::Span),
Module,
}
trait XScopeTrait {
fn get(&self, name: &swc_atoms::JsWord) -> Option<NameId>;
fn set(
&self,
name: &swc_atoms::JsWord,
name_id: NameId,
span: swc_common::Span,
diagnostics: &mut Vec<Diagnostic>,
);
fn nest(&self, name_owner_location: Option<OwnerId>) -> Rc<RefCell<XScopeData>>;
}
impl XScopeTrait for XScope {
fn get(&self, name: &swc_atoms::JsWord) -> Option<NameId> {
match self.borrow().name_map.get(name) {
Some(mapped_name) => Some(mapped_name.clone()),
None => match &self.borrow().parent {
Some(parent) => parent.get(name),
None => None,
},
}
}
fn set(
&self,
name: &swc_atoms::JsWord,
name_id: NameId,
span: swc_common::Span,
diagnostics: &mut Vec<Diagnostic>,
) {
let old_mapping = self.borrow_mut().name_map.insert(name.clone(), name_id);
if old_mapping.is_some() {
diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: "Scope overwrite occurred (TODO: being permissive about this)".to_string(),
span,
});
}
}
fn nest(&self, name_owner_location: Option<OwnerId>) -> Rc<RefCell<XScopeData>> {
return Rc::new(RefCell::new(XScopeData {
owner_id: name_owner_location.unwrap_or(self.borrow().owner_id.clone()),
name_map: Default::default(),
parent: Some(self.clone()),
}));
}
}
fn init_std_scope() -> XScope {
return Rc::new(RefCell::new(XScopeData {
owner_id: OwnerId::Module,
name_map: HashMap::from([
(swc_atoms::js_word!("Math"), NameId::Builtin(Builtin::Math)),
(
swc_atoms::JsWord::from("Debug"),
NameId::Builtin(Builtin::Debug),
),
(
swc_atoms::js_word!("undefined"),
NameId::Builtin(Builtin::undefined),
),
]),
parent: None,
}))
.nest(None);
}