mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 21:54:36 +03:00
338 lines
9.8 KiB
Rust
338 lines
9.8 KiB
Rust
#![feature(box_syntax, box_patterns)]
|
|
|
|
pub use atoms;
|
|
pub use common;
|
|
pub use ecmascript;
|
|
pub use sourcemap;
|
|
|
|
mod builder;
|
|
pub mod config;
|
|
pub mod error;
|
|
|
|
pub use crate::builder::PassBuilder;
|
|
use crate::{
|
|
config::{BuiltConfig, ConfigFile, JscTarget, Merge, Options, Rc, RootMode},
|
|
error::Error,
|
|
};
|
|
use common::{
|
|
comments::Comments, errors::Handler, FileName, FoldWith, Globals, SourceFile, SourceMap,
|
|
GLOBALS,
|
|
};
|
|
use ecmascript::{
|
|
ast::Program,
|
|
codegen::{self, Emitter},
|
|
parser::{lexer::Lexer, Parser, Session as ParseSess, Syntax},
|
|
transforms::{
|
|
helpers::{self, Helpers},
|
|
util,
|
|
util::COMMENTS,
|
|
},
|
|
};
|
|
pub use ecmascript::{
|
|
parser::SourceFileInput,
|
|
transforms::{chain_at, pass::Pass},
|
|
};
|
|
use serde::Serialize;
|
|
use sourcemap::SourceMapBuilder;
|
|
use std::{fs::File, path::Path, sync::Arc};
|
|
|
|
pub struct Compiler {
|
|
/// swc uses rustc's span interning.
|
|
///
|
|
/// The `Globals` struct contains span interner.
|
|
globals: Globals,
|
|
/// CodeMap
|
|
pub cm: Arc<SourceMap>,
|
|
pub handler: Handler,
|
|
comments: Comments,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct TransformOutput {
|
|
pub code: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub map: Option<String>,
|
|
}
|
|
|
|
/// These are **low-level** apis.
|
|
impl Compiler {
|
|
pub fn comments(&self) -> &Comments {
|
|
&self.comments
|
|
}
|
|
|
|
/// Runs `op` in current compiler's context.
|
|
///
|
|
/// Note: Other methods of `Compiler` already uses this internally.
|
|
pub fn run<R, F>(&self, op: F) -> R
|
|
where
|
|
F: FnOnce() -> R,
|
|
{
|
|
GLOBALS.set(&self.globals, || {
|
|
//
|
|
COMMENTS.set(&self.comments, || {
|
|
//
|
|
op()
|
|
})
|
|
})
|
|
}
|
|
|
|
/// This method parses a javascript / typescript file
|
|
pub fn parse_js(
|
|
&self,
|
|
fm: Arc<SourceFile>,
|
|
target: JscTarget,
|
|
syntax: Syntax,
|
|
is_module: bool,
|
|
parse_comments: bool,
|
|
) -> Result<Program, Error> {
|
|
self.run(|| {
|
|
let session = ParseSess {
|
|
handler: &self.handler,
|
|
};
|
|
let lexer = Lexer::new(
|
|
session,
|
|
syntax,
|
|
target,
|
|
SourceFileInput::from(&*fm),
|
|
if parse_comments {
|
|
Some(&self.comments)
|
|
} else {
|
|
None
|
|
},
|
|
);
|
|
let mut parser = Parser::new_from(session, lexer);
|
|
let program = if is_module {
|
|
parser
|
|
.parse_module()
|
|
.map_err(|mut e| {
|
|
e.emit();
|
|
Error::FailedToParseModule {}
|
|
})
|
|
.map(Program::Module)?
|
|
} else {
|
|
parser
|
|
.parse_script()
|
|
.map_err(|mut e| {
|
|
e.emit();
|
|
Error::FailedToParseModule {}
|
|
})
|
|
.map(Program::Script)?
|
|
};
|
|
|
|
Ok(program)
|
|
})
|
|
}
|
|
|
|
pub fn print(
|
|
&self,
|
|
program: &Program,
|
|
fm: Arc<SourceFile>,
|
|
comments: &Comments,
|
|
source_map: bool,
|
|
minify: bool,
|
|
) -> Result<TransformOutput, Error> {
|
|
self.run(|| {
|
|
let mut src_map_builder = SourceMapBuilder::new(None);
|
|
|
|
match fm.name {
|
|
FileName::Real(ref p) => {
|
|
let id = src_map_builder.add_source(&p.display().to_string());
|
|
src_map_builder.set_source_contents(id, Some(&fm.src));
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let src = {
|
|
let mut buf = vec![];
|
|
{
|
|
let handlers = box MyHandlers;
|
|
let mut emitter = Emitter {
|
|
cfg: codegen::Config { minify },
|
|
comments: if minify { None } else { Some(&comments) },
|
|
cm: self.cm.clone(),
|
|
wr: box codegen::text_writer::JsWriter::new(
|
|
self.cm.clone(),
|
|
"\n",
|
|
&mut buf,
|
|
if source_map {
|
|
Some(&mut src_map_builder)
|
|
} else {
|
|
None
|
|
},
|
|
),
|
|
handlers,
|
|
};
|
|
|
|
emitter
|
|
.emit_program(&program)
|
|
.map_err(|err| Error::FailedToEmitModule { err })?;
|
|
}
|
|
// Invalid utf8 is valid in javascript world.
|
|
unsafe { String::from_utf8_unchecked(buf) }
|
|
};
|
|
Ok(TransformOutput {
|
|
code: src,
|
|
map: if source_map {
|
|
let mut buf = vec![];
|
|
src_map_builder
|
|
.into_sourcemap()
|
|
.to_writer(&mut buf)
|
|
.map_err(|err| Error::FailedToWriteSourceMap { err })?;
|
|
let map =
|
|
String::from_utf8(buf).map_err(|err| Error::SourceMapNotUtf8 { err })?;
|
|
Some(map)
|
|
} else {
|
|
None
|
|
},
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
/// High-level apis.
|
|
impl Compiler {
|
|
pub fn new(cm: Arc<SourceMap>, handler: Handler) -> Self {
|
|
Compiler {
|
|
cm,
|
|
handler,
|
|
globals: Globals::new(),
|
|
comments: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// This method handles merging of config.
|
|
pub fn config_for_file(
|
|
&self,
|
|
opts: &Options,
|
|
fm: &SourceFile,
|
|
) -> Result<BuiltConfig<impl Pass>, Error> {
|
|
let Options {
|
|
ref root,
|
|
root_mode,
|
|
swcrc,
|
|
config_file,
|
|
is_module,
|
|
..
|
|
} = opts;
|
|
let root = root
|
|
.clone()
|
|
.unwrap_or_else(|| ::std::env::current_dir().unwrap());
|
|
|
|
let config_file = match config_file {
|
|
Some(ConfigFile::Str(ref s)) => {
|
|
let path = Path::new(s);
|
|
let r = File::open(&path).map_err(|err| Error::FailedToReadConfigFile { err })?;
|
|
let config: Rc = serde_json::from_reader(r)
|
|
.map_err(|err| Error::FailedToParseConfigFile { err })?;
|
|
Some(config)
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
match fm.name {
|
|
FileName::Real(ref path) => {
|
|
if *swcrc {
|
|
let mut parent = path.parent();
|
|
while let Some(dir) = parent {
|
|
let swcrc = dir.join(".swcrc");
|
|
|
|
if swcrc.exists() {
|
|
let r = File::open(&swcrc)
|
|
.map_err(|err| Error::FailedToReadConfigFile { err })?;
|
|
let mut config = serde_json::from_reader(r)
|
|
.map_err(|err| Error::FailedToParseConfigFile { err })
|
|
.and_then(|rc: Rc| rc.into_config(Some(path)))?;
|
|
|
|
if let Some(config_file) = config_file {
|
|
config.merge(&config_file.into_config(Some(path))?)
|
|
}
|
|
let built =
|
|
opts.build(&self.cm, &self.handler, *is_module, Some(config));
|
|
return Ok(built);
|
|
}
|
|
|
|
if dir == root && *root_mode == RootMode::Root {
|
|
break;
|
|
}
|
|
parent = dir.parent();
|
|
}
|
|
}
|
|
|
|
if let Some(config_file) = config_file {
|
|
let built = opts.build(
|
|
&self.cm,
|
|
&self.handler,
|
|
*is_module,
|
|
Some(config_file.into_config(Some(path))?),
|
|
);
|
|
return Ok(built);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let built = opts.build(
|
|
&self.cm,
|
|
&self.handler,
|
|
*is_module,
|
|
match config_file {
|
|
Some(config_file) => Some(config_file.into_config(None)?),
|
|
None => None,
|
|
},
|
|
);
|
|
Ok(built)
|
|
}
|
|
|
|
pub fn process_js_file(
|
|
&self,
|
|
fm: Arc<SourceFile>,
|
|
opts: &Options,
|
|
) -> Result<TransformOutput, Error> {
|
|
let config = self.run(|| self.config_for_file(opts, &*fm))?;
|
|
|
|
self.process_js(fm, config)
|
|
}
|
|
|
|
/// You can use custom pass with this method.
|
|
///
|
|
/// There exists a [PassBuilder] to help building custom passes.
|
|
pub fn process_js(
|
|
&self,
|
|
fm: Arc<SourceFile>,
|
|
config: BuiltConfig<impl Pass>,
|
|
) -> Result<TransformOutput, Error> {
|
|
self.run(|| {
|
|
if error::debug() {
|
|
eprintln!("processing js file: {:?}", fm)
|
|
}
|
|
|
|
let module = self.parse_js(
|
|
fm.clone(),
|
|
config.target,
|
|
config.syntax,
|
|
config.is_module,
|
|
!config.minify,
|
|
)?;
|
|
let mut pass = config.pass;
|
|
let module = helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
|
|
util::HANDLER.set(&self.handler, || {
|
|
// Fold module
|
|
module.fold_with(&mut pass)
|
|
})
|
|
});
|
|
|
|
self.print(
|
|
&module,
|
|
fm,
|
|
&self.comments,
|
|
config.source_maps,
|
|
config.minify,
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
struct MyHandlers;
|
|
|
|
impl ecmascript::codegen::Handlers for MyHandlers {}
|