mirror of
https://github.com/swc-project/swc.git
synced 2024-11-23 17:54:15 +03:00
fix(webpack/ast): Handle typescript (#3045)
swc_webpack_ast: - Add `proces_file`. - Add support for typescript.
This commit is contained in:
parent
9b36abbe75
commit
24179bd9be
@ -9,6 +9,7 @@ export RUST_BACKTRACE=1
|
||||
cargo test --no-run
|
||||
UPDATE=1 cargo test -q
|
||||
|
||||
(cd next.js/packages/next-swc && yarn build-native)
|
||||
(cd next.js/packages/next-swc && yarn build-native --release)
|
||||
|
||||
(cd next.js && NEXT_PRIVATE_LOCAL_WEBPACK5=1 yarn next dev test/integration/production)
|
||||
(cd next.js && NEXT_PRIVATE_LOCAL_WEBPACK5=1 yarn testheadless $@ || true)
|
||||
(cd next.js && NEXT_PRIVATE_LOCAL_WEBPACK5=1 yarn next dev $@)
|
||||
|
@ -1,7 +1,12 @@
|
||||
use crate::reducer::ast_reducer;
|
||||
use anyhow::{Context, Error};
|
||||
use swc_common::{sync::Lrc, Mark, SourceFile, SourceMap};
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use serde::Serialize;
|
||||
use swc_common::{
|
||||
errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceFile, SourceMap,
|
||||
GLOBALS,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax, TsConfig};
|
||||
use swc_ecma_transforms_base::resolver::resolver_with_mark;
|
||||
use swc_ecma_visit::VisitMutWith;
|
||||
use swc_estree_ast::flavor::Flavor;
|
||||
@ -10,6 +15,73 @@ use swc_timer::timer;
|
||||
|
||||
pub mod reducer;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AstOutput {
|
||||
ast: String,
|
||||
src: Option<Lrc<String>>,
|
||||
}
|
||||
|
||||
pub fn process_file<F>(load_file: F, include_src: bool) -> Result<AstOutput, Error>
|
||||
where
|
||||
F: FnOnce(&Lrc<SourceMap>) -> Result<Lrc<SourceFile>, Error>,
|
||||
{
|
||||
let globals = Globals::new();
|
||||
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
|
||||
let fm = load_file(&cm).context("failed to load file")?;
|
||||
|
||||
// Default
|
||||
let syntax = Syntax::Es(EsConfig {
|
||||
jsx: true,
|
||||
..Default::default()
|
||||
});
|
||||
let syntax = match &fm.name {
|
||||
FileName::Real(path) => match path.extension() {
|
||||
Some(ext) => {
|
||||
if ext == "tsx" {
|
||||
Syntax::Typescript(TsConfig {
|
||||
tsx: true,
|
||||
no_early_errors: true,
|
||||
..Default::default()
|
||||
})
|
||||
} else if ext == "ts" {
|
||||
Syntax::Typescript(TsConfig {
|
||||
no_early_errors: true,
|
||||
..Default::default()
|
||||
})
|
||||
} else {
|
||||
syntax
|
||||
}
|
||||
}
|
||||
_ => syntax,
|
||||
},
|
||||
_ => syntax,
|
||||
};
|
||||
|
||||
let module = {
|
||||
let lexer = Lexer::new(syntax, EsVersion::latest(), StringInput::from(&*fm), None);
|
||||
let mut parser = Parser::new_from(lexer);
|
||||
|
||||
parser.parse_module().map_err(|err| {
|
||||
HANDLER.with(|h| {
|
||||
err.into_diagnostic(h).emit();
|
||||
anyhow!("failed to parse module")
|
||||
})
|
||||
})?
|
||||
};
|
||||
|
||||
let ast = GLOBALS.set(&globals, || webpack_ast(cm.clone(), fm.clone(), module))?;
|
||||
|
||||
Ok(AstOutput {
|
||||
ast,
|
||||
src: if include_src {
|
||||
Some(fm.src.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// `n` is expected to be pure (`resolver` is not applied)
|
||||
pub fn webpack_ast(
|
||||
cm: Lrc<SourceMap>,
|
||||
@ -27,6 +99,7 @@ pub fn webpack_ast(
|
||||
let _timer = timer!("resolver");
|
||||
n.visit_mut_with(&mut resolver_with_mark(top_level_mark));
|
||||
}
|
||||
|
||||
{
|
||||
n.visit_mut_with(&mut ast_reducer(top_level_mark));
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use self::flatten::contains_import;
|
||||
use self::{flatten::contains_import, typescript::ts_remover};
|
||||
use std::{iter::once, sync::Arc};
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::{
|
||||
collections::AHashSet,
|
||||
collections::{AHashMap, AHashSet},
|
||||
pass::{Repeat, Repeated},
|
||||
util::take::Take,
|
||||
Mark, Span, Spanned, SyntaxContext,
|
||||
Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
|
||||
};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::{ident::IdentLike, Id, IsEmpty, StmtLike, StmtOrModuleItem};
|
||||
@ -13,6 +13,7 @@ use swc_ecma_visit::{Visit, VisitMut, VisitMutWith, VisitWith};
|
||||
use swc_timer::timer;
|
||||
|
||||
mod flatten;
|
||||
mod typescript;
|
||||
|
||||
/// # Usage
|
||||
///
|
||||
@ -68,11 +69,24 @@ pub fn ast_reducer(top_level_mark: Mark) -> impl VisitMut {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct BindingInfo {
|
||||
used_as_type: bool,
|
||||
used_as_var: bool,
|
||||
}
|
||||
|
||||
struct Analyzer {
|
||||
amd_requires: AHashSet<Id>,
|
||||
used_refs: AHashMap<Id, BindingInfo>,
|
||||
}
|
||||
|
||||
impl Visit for Analyzer {
|
||||
fn visit_assign_pat_prop(&mut self, p: &AssignPatProp) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
self.used_refs.entry(p.key.to_id()).or_default().used_as_var = true;
|
||||
}
|
||||
|
||||
fn visit_call_expr(&mut self, e: &CallExpr) {
|
||||
e.visit_children_with(self);
|
||||
|
||||
@ -124,6 +138,64 @@ impl Visit for Analyzer {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, e: &Expr) {
|
||||
e.visit_children_with(self);
|
||||
|
||||
match e {
|
||||
Expr::Ident(i) => {
|
||||
self.used_refs.entry(i.to_id()).or_default().used_as_var = true;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_member_expr(&mut self, e: &MemberExpr) {
|
||||
e.obj.visit_with(self);
|
||||
|
||||
if e.computed {
|
||||
e.prop.visit_with(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &Pat) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
match p {
|
||||
Pat::Ident(s) => {
|
||||
self.used_refs.entry(s.to_id()).or_default().used_as_var = true;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_prop(&mut self, p: &Prop) {
|
||||
p.visit_children_with(self);
|
||||
|
||||
match p {
|
||||
Prop::Shorthand(s) => {
|
||||
self.used_refs.entry(s.to_id()).or_default().used_as_var = true;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_ts_type_ref(&mut self, ty: &TsTypeRef) {
|
||||
fn left_most(n: &TsEntityName) -> &Ident {
|
||||
match n {
|
||||
TsEntityName::Ident(i) => i,
|
||||
TsEntityName::TsQualifiedName(q) => left_most(&q.left),
|
||||
}
|
||||
}
|
||||
|
||||
ty.visit_children_with(self);
|
||||
|
||||
let left = left_most(&ty.type_name);
|
||||
self.used_refs.entry(left.to_id()).or_default().used_as_type = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -131,6 +203,9 @@ struct ScopeData {
|
||||
imported_ids: AHashSet<Id>,
|
||||
/// amd `require` modules.
|
||||
amd_requires: AHashSet<Id>,
|
||||
|
||||
#[allow(unused)]
|
||||
used_refs: AHashMap<Id, BindingInfo>,
|
||||
}
|
||||
|
||||
impl ScopeData {
|
||||
@ -160,7 +235,8 @@ impl ScopeData {
|
||||
}
|
||||
|
||||
let mut analyzer = Analyzer {
|
||||
amd_requires: AHashSet::default(),
|
||||
amd_requires: Default::default(),
|
||||
used_refs: Default::default(),
|
||||
};
|
||||
|
||||
items.visit_with(&mut analyzer);
|
||||
@ -168,6 +244,7 @@ impl ScopeData {
|
||||
ScopeData {
|
||||
imported_ids,
|
||||
amd_requires: analyzer.amd_requires,
|
||||
used_refs: analyzer.used_refs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -791,22 +868,6 @@ impl VisitMut for ReduceAst {
|
||||
*e = *expr.arg.take();
|
||||
}
|
||||
|
||||
Expr::TsAs(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TsConstAssertion(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TsTypeAssertion(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TsNonNull(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TaggedTpl(expr) => {
|
||||
let mut exprs = Vec::with_capacity(expr.tpl.exprs.len() + 1);
|
||||
exprs.push(expr.tag.take());
|
||||
@ -1318,9 +1379,17 @@ impl VisitMut for ReduceAst {
|
||||
|
||||
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
|
||||
if !self.collected_data {
|
||||
let _timer = timer!("analyze before reducing");
|
||||
self.collected_data = true;
|
||||
self.data = Arc::new(ScopeData::analyze(&stmts));
|
||||
|
||||
{
|
||||
let _timer = timer!("analyze before reducing");
|
||||
self.data = Arc::new(ScopeData::analyze(&stmts));
|
||||
}
|
||||
{
|
||||
let _timer = timer!("remove typescript nodes");
|
||||
|
||||
stmts.visit_mut_with(&mut ts_remover());
|
||||
}
|
||||
}
|
||||
|
||||
let _timer = timer!("reduce ast (single pass)");
|
||||
@ -1438,7 +1507,12 @@ impl VisitMut for ReduceAst {
|
||||
}
|
||||
|
||||
match pat {
|
||||
Pat::Assign(..) => {}
|
||||
Pat::Assign(..) => {
|
||||
let old = self.can_remove_pat;
|
||||
self.can_remove_pat = false;
|
||||
pat.visit_mut_children_with(self);
|
||||
self.can_remove_pat = old;
|
||||
}
|
||||
_ => {
|
||||
pat.visit_mut_children_with(self);
|
||||
}
|
||||
@ -1566,7 +1640,7 @@ impl VisitMut for ReduceAst {
|
||||
fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
|
||||
match stmt {
|
||||
Stmt::Debugger(_) | Stmt::Break(_) | Stmt::Continue(_) => {
|
||||
*stmt = Stmt::Empty(EmptyStmt { span: stmt.span() });
|
||||
*stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP });
|
||||
return;
|
||||
}
|
||||
|
||||
|
106
crates/swc_webpack_ast/src/reducer/typescript.rs
Normal file
106
crates/swc_webpack_ast/src/reducer/typescript.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use swc_common::util::take::Take;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_visit::{VisitMut, VisitMutWith};
|
||||
|
||||
pub fn ts_remover() -> impl VisitMut {
|
||||
TsRemover {}
|
||||
}
|
||||
|
||||
struct TsRemover {}
|
||||
|
||||
impl VisitMut for TsRemover {
|
||||
fn visit_mut_array_pat(&mut self, p: &mut ArrayPat) {
|
||||
p.visit_mut_children_with(self);
|
||||
|
||||
p.optional = false;
|
||||
}
|
||||
|
||||
fn visit_mut_expr(&mut self, e: &mut Expr) {
|
||||
e.visit_mut_children_with(self);
|
||||
|
||||
match e {
|
||||
Expr::TsAs(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TsConstAssertion(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TsTypeAssertion(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
Expr::TsNonNull(expr) => {
|
||||
*e = *expr.expr.take();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_ident(&mut self, i: &mut Ident) {
|
||||
i.visit_mut_children_with(self);
|
||||
|
||||
i.optional = false;
|
||||
}
|
||||
|
||||
fn visit_mut_module_item(&mut self, s: &mut ModuleItem) {
|
||||
s.visit_mut_children_with(self);
|
||||
|
||||
match s {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl: Decl::TsInterface(_) | Decl::TsTypeAlias(_),
|
||||
..
|
||||
}))
|
||||
| ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
|
||||
type_only: true, ..
|
||||
}))
|
||||
| ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
|
||||
type_only: true,
|
||||
..
|
||||
})) => {
|
||||
s.take();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_object_pat(&mut self, p: &mut ObjectPat) {
|
||||
p.visit_mut_children_with(self);
|
||||
|
||||
p.optional = false;
|
||||
}
|
||||
|
||||
fn visit_mut_opt_ts_type(&mut self, ty: &mut Option<Box<TsType>>) {
|
||||
*ty = None;
|
||||
}
|
||||
|
||||
fn visit_mut_opt_ts_type_ann(&mut self, ty: &mut Option<TsTypeAnn>) {
|
||||
*ty = None;
|
||||
}
|
||||
|
||||
fn visit_mut_opt_ts_type_param_decl(&mut self, t: &mut Option<TsTypeParamDecl>) {
|
||||
*t = None;
|
||||
}
|
||||
|
||||
fn visit_mut_opt_ts_type_param_instantiation(
|
||||
&mut self,
|
||||
t: &mut Option<TsTypeParamInstantiation>,
|
||||
) {
|
||||
*t = None;
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
|
||||
s.visit_mut_children_with(self);
|
||||
|
||||
match s {
|
||||
Stmt::Decl(Decl::TsTypeAlias(..) | Decl::TsInterface(..)) => {
|
||||
s.take();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
use swc_common::{chain, Mark};
|
||||
use swc_common::{chain, Mark, Span};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax, TsConfig};
|
||||
use swc_ecma_transforms_base::resolver::resolver_with_mark;
|
||||
@ -33,14 +33,24 @@ fn fixture(input: PathBuf) {
|
||||
struct AssertValid;
|
||||
|
||||
impl VisitMut for AssertValid {
|
||||
fn visit_mut_array_pat(&mut self, a: &mut ArrayPat) {
|
||||
if a.optional {
|
||||
panic!("found an optional pattern: {:?}", a)
|
||||
}
|
||||
|
||||
a.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_empty_stmt(&mut self, i: &mut EmptyStmt) {
|
||||
panic!("found empty: {:?}", i)
|
||||
}
|
||||
|
||||
fn visit_mut_ident(&mut self, i: &mut Ident) {
|
||||
if i.span.is_dummy() {
|
||||
panic!("found an identifier with dummy span: {:?}", i)
|
||||
if i.optional {
|
||||
panic!("found an optional pattern: {:?}", i)
|
||||
}
|
||||
|
||||
i.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_if_stmt(&mut self, s: &mut IfStmt) {
|
||||
@ -59,11 +69,27 @@ impl VisitMut for AssertValid {
|
||||
fn visit_mut_module(&mut self, m: &mut Module) {
|
||||
dbg!(&*m);
|
||||
|
||||
m.visit_mut_children_with(self);
|
||||
m.body.visit_mut_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_object_pat(&mut self, pat: &mut ObjectPat) {
|
||||
if pat.optional {
|
||||
panic!("found a optional pattern: {:?}", pat)
|
||||
}
|
||||
|
||||
pat.visit_mut_children_with(self);
|
||||
}
|
||||
|
||||
fn visit_mut_span(&mut self, sp: &mut Span) {
|
||||
assert!(!sp.is_dummy());
|
||||
}
|
||||
|
||||
fn visit_mut_ts_type(&mut self, ty: &mut TsType) {
|
||||
panic!("found a typescript type: {:?}", ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[testing::fixture("../swc_ecma_parser/tests/typescript/tsc/**/input.ts")]
|
||||
#[testing::fixture("../swc_ecma_parser/tests/typescript/**/input.ts")]
|
||||
#[testing::fixture("../swc/tests/tsc-references/**/output.js")]
|
||||
fn assert_no_invalid(input: PathBuf) {
|
||||
testing::run_test(false, |cm, _handler| {
|
||||
|
@ -53,8 +53,7 @@ class Container extends React.Component {
|
||||
}
|
||||
export const emitter = mitt();
|
||||
let CachedComponent;
|
||||
export async function initNext(opts = {
|
||||
}) {
|
||||
export async function initNext() {
|
||||
let initialErr;
|
||||
const appEntrypoint = null;
|
||||
const { component: app , exports: mod } = null;
|
||||
|
@ -78,8 +78,7 @@ class Container {
|
||||
const emitter = null;
|
||||
exports.emitter = null;
|
||||
let CachedComponent;
|
||||
(function*(opts = {
|
||||
}) {
|
||||
(function*() {
|
||||
let initialErr;
|
||||
const appEntrypoint = null;
|
||||
const { component: app , exports: mod } = null;
|
||||
|
Loading…
Reference in New Issue
Block a user