Compiler apis & .swcrc improvement (#434)

- Expose high-level compiler apis (#431)
 - Support multiple entries in .swcrc (#414)
This commit is contained in:
강동윤 2019-10-25 10:11:24 +09:00 committed by GitHub
parent 2add7a08ef
commit ae3326cd9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1165 additions and 18 deletions

View File

@ -57,7 +57,7 @@ deploy:
provider: pages
skip_cleanup: true
github_token: $GH_TOKEN
email: kdy1@outlook.kr
email: kdy1997.dev@gmail.com
name: "강동윤"
on:
branch: master

View File

@ -3,11 +3,12 @@
[package]
name = "swc"
version = "0.1.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc/"
description = "Speedy web compiler"
edition = "2018"
[lib]
name = "swc"
@ -16,7 +17,18 @@ name = "swc"
swc_atoms = { path ="./atoms" }
swc_common = { path ="./common" }
swc_ecmascript = { path ="./ecmascript" }
sourcemap = "2.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sourcemap = "2"
failure = "0.1"
path-clean = "0.1"
lazy_static = "1"
hashbrown = "0.5"
regex = "1"
chashmap = "2.2.2"
[[example]]
name = "usage"
[profile.bench]
lto = true

View File

@ -2,7 +2,7 @@
name = "swc_atoms"
build = "build.rs"
version = "0.1.2"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_atoms/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_common"
version = "0.3.1"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_common/"

View File

@ -108,6 +108,12 @@ pub struct SourceMap {
doctest_offset: Option<(FileName, isize)>,
}
impl Default for SourceMap {
fn default() -> Self {
Self::new(FilePathMapping::empty())
}
}
impl SourceMap {
pub fn new(path_mapping: FilePathMapping) -> SourceMap {
SourceMap {

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecmascript"
version = "0.1.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecmascript/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_ast"
version = "0.9.3"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_ast/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_codegen"
version = "0.7.2"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_codegen/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_codegen_macros"
version = "0.3.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_codegen_macros/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_lints"
version = "0.1.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
edition = "2018"
publish = false

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_parser"
version = "0.10.3"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_parser/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_parser_macros"
version = "0.3.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_parser_macros/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_ecma_transforms"
version = "0.1.2"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_ecma_transforms/"

27
examples/usage.rs Normal file
View File

@ -0,0 +1,27 @@
extern crate swc;
use std::{path::Path, sync::Arc};
use swc::{
common::{
errors::{ColorConfig, Handler},
SourceMap,
},
config::Options,
};
fn main() {
let cm = Arc::<SourceMap>::default();
let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
let c = swc::Compiler::new(cm.clone(), handler);
let fm = cm
.load_file(Path::new("foo.js"))
.expect("failed to load file");
c.process_js_file(
fm,
Options {
..Default::default()
},
);
}

View File

@ -1,7 +1,7 @@
[package]
name = "ast_node"
version = "0.4.2"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/ast_node/"

View File

@ -1,7 +1,7 @@
[package]
name = "swc_macros_common"
version = "0.2.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/swc_macros_common/"

View File

@ -1,7 +1,7 @@
[package]
name = "enum_kind"
version = "0.1.3"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/enum_kind/"

View File

@ -1,7 +1,7 @@
[package]
name = "string_enum"
version = "0.2.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/string_enum/"

106
src/builder.rs Normal file
View File

@ -0,0 +1,106 @@
use crate::config::{GlobalPassOption, JscTarget, ModuleConfig};
use atoms::JsWord;
use common::{errors::Handler, SourceMap};
use ecmascript::{
ast::Module,
transforms::{
chain_at, compat, const_modules, fixer, helpers, hygiene, modules,
pass::{JoinedPass, Optional, Pass},
typescript,
},
};
use hashbrown::hash_map::HashMap;
use std::sync::Arc;
/// Builder is used to create a high performance `Compiler`.
pub struct PassBuilder<'a, 'b, P: Pass> {
cm: &'a Arc<SourceMap>,
handler: &'b Handler,
pass: P,
target: JscTarget,
}
impl<'a, 'b, P: Pass> PassBuilder<'a, 'b, P> {
pub fn new(cm: &'a Arc<SourceMap>, handler: &'b Handler, pass: P) -> Self {
PassBuilder {
cm,
handler,
pass,
target: JscTarget::Es5,
}
}
pub fn then<N>(self, next: N) -> PassBuilder<'a, 'b, JoinedPass<P, N, Module>> {
let pass = chain_at!(Module, self.pass, next);
PassBuilder {
cm: self.cm,
handler: self.handler,
pass,
target: self.target,
}
}
pub fn const_modules(
self,
globals: HashMap<JsWord, HashMap<JsWord, String>>,
) -> PassBuilder<'a, 'b, impl Pass> {
self.then(const_modules(globals))
}
pub fn inline_globals(self, c: GlobalPassOption) -> PassBuilder<'a, 'b, impl Pass> {
let pass = c.build(&self.cm, &self.handler);
self.then(pass)
}
pub fn strip_typescript(self) -> PassBuilder<'a, 'b, impl Pass> {
self.then(typescript::strip())
}
pub fn target(mut self, target: JscTarget) -> Self {
self.target = target;
self
}
/// # Arguments
/// ## module
/// - Use `None` if you want swc to emit import statements.
///
///
/// Returned pass includes
///
/// - compatibility helper
/// - module handler
/// - helper injector
/// - identifier hygiene handler
/// - fixer
pub fn finalize(self, module: Option<ModuleConfig>) -> impl Pass {
let need_interop_analysis = match module {
Some(ModuleConfig::CommonJs(ref c)) => !c.no_interop,
Some(ModuleConfig::Amd(ref c)) => !c.config.no_interop,
Some(ModuleConfig::Umd(ref c)) => !c.config.no_interop,
None => false,
};
chain_at!(
Module,
self.pass,
// compat
Optional::new(compat::es2018(), self.target <= JscTarget::Es2018),
Optional::new(compat::es2017(), self.target <= JscTarget::Es2017),
Optional::new(compat::es2016(), self.target <= JscTarget::Es2016),
Optional::new(compat::es2015(), self.target <= JscTarget::Es2015),
Optional::new(compat::es3(), self.target <= JscTarget::Es3),
// module / helper
Optional::new(
modules::import_analysis::import_analyzer(),
need_interop_analysis
),
helpers::InjectHelpers,
ModuleConfig::build(self.cm.clone(), module),
// hygiene
hygiene(),
// fixer
fixer(),
)
}
}

29
src/config/array.json Normal file
View File

@ -0,0 +1,29 @@
[
{
"test": "\\.ts",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": false
}
}
},
{
"test": "\\.js",
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": false,
"dynamicImport": false,
"numericSeparator": false,
"classPrivateProperty": false,
"privateMethod": false,
"classProperty": false,
"functionBind": false,
"decorators": false,
"decoratorsBeforeExport": false
}
}
}
]

609
src/config/mod.rs Normal file
View File

@ -0,0 +1,609 @@
use crate::{builder::PassBuilder, error::Error};
use atoms::JsWord;
use chashmap::CHashMap;
use common::{errors::Handler, FileName, SourceMap};
use ecmascript::{
ast::{Expr, Module, ModuleItem, Stmt},
parser::{Parser, Session as ParseSess, SourceFileInput, Syntax},
transforms::{
chain_at, const_modules, modules,
pass::{noop, Optional, Pass},
proposals::{class_properties, decorators, export},
react, resolver, simplifier, typescript, InlineGlobals,
},
};
use hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{
env,
path::{Path, PathBuf},
sync::Arc,
};
#[cfg(test)]
mod tests;
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ParseOptions {
#[serde(default)]
pub comments: bool,
#[serde(flatten)]
pub syntax: Syntax,
}
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Options {
#[serde(flatten, default)]
pub config: Option<Config>,
#[serde(default = "default_cwd")]
pub cwd: PathBuf,
#[serde(default)]
pub caller: Option<CallerOptions>,
#[serde(default)]
pub filename: String,
#[serde(default)]
pub config_file: Option<ConfigFile>,
#[serde(default)]
pub root: Option<PathBuf>,
#[serde(default)]
pub root_mode: RootMode,
#[serde(default = "default_swcrc")]
pub swcrc: bool,
#[serde(default)]
pub swcrc_roots: Option<PathBuf>,
#[serde(default = "default_env_name")]
pub env_name: String,
#[serde(default)]
pub input_source_map: Option<InputSourceMap>,
#[serde(default)]
pub source_maps: Option<SourceMapsConfig>,
#[serde(default)]
pub source_file_name: Option<String>,
#[serde(default)]
pub source_root: Option<String>,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SourceMapsConfig {
Bool(bool),
Str(String),
}
impl Default for SourceMapsConfig {
fn default() -> Self {
SourceMapsConfig::Bool(true)
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum InputSourceMap {
Bool(bool),
Str(String),
}
impl Default for InputSourceMap {
fn default() -> Self {
InputSourceMap::Bool(true)
}
}
impl Options {
pub fn build(
&self,
cm: &Arc<SourceMap>,
handler: &Handler,
config: Option<Config>,
) -> BuiltConfig<impl Pass> {
let mut config = config.unwrap_or_else(|| Default::default());
if let Some(ref c) = self.config {
config.merge(c)
}
let JscConfig {
transform,
syntax,
external_helpers,
target,
} = config.jsc;
let syntax = syntax.unwrap_or_default();
let transform = transform.unwrap_or_default();
let const_modules = {
let enabled = transform.const_modules.is_some();
let config = transform.const_modules.unwrap_or_default();
let globals = config.globals;
Optional::new(const_modules(globals), enabled)
};
let optimizer = transform.optimizer;
let enable_optimizer = optimizer.is_some();
let pass = if let Some(opts) =
optimizer.map(|o| o.globals.unwrap_or_else(|| Default::default()))
{
opts.build(cm, handler)
} else {
GlobalPassOption::default().build(cm, handler)
};
let pass = chain_at!(
Module,
// handle jsx
Optional::new(react::react(cm.clone(), transform.react), syntax.jsx()),
Optional::new(typescript::strip(), syntax.typescript()),
resolver(),
const_modules,
pass,
Optional::new(decorators(), syntax.decorators()),
Optional::new(class_properties(), syntax.class_props()),
Optional::new(
export(),
syntax.export_default_from() || syntax.export_namespace_from()
),
Optional::new(simplifier(), enable_optimizer),
);
let pass = PassBuilder::new(&cm, &handler, pass)
.target(target)
.finalize(config.module);
BuiltConfig {
minify: config.minify.unwrap_or(false),
pass,
external_helpers,
syntax,
source_maps: self
.source_maps
.as_ref()
.map(|s| match s {
SourceMapsConfig::Bool(v) => *v,
// TODO: Handle source map
SourceMapsConfig::Str(_) => true,
})
.unwrap_or(false),
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum RootMode {
#[serde(rename = "root")]
Root,
#[serde(rename = "upward")]
Upward,
#[serde(rename = "upward-optional")]
UpwardOptional,
}
impl Default for RootMode {
fn default() -> Self {
RootMode::Root
}
}
const fn default_swcrc() -> bool {
true
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ConfigFile {
Bool(bool),
Str(String),
}
impl Default for ConfigFile {
fn default() -> Self {
ConfigFile::Bool(true)
}
}
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallerOptions {
pub name: String,
}
fn default_cwd() -> PathBuf {
::std::env::current_dir().unwrap()
}
/// `.swcrc` file
#[derive(Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Rc {
Single(Config),
Multi(Vec<Config>),
}
impl Rc {
pub fn into_config(self, filename: Option<&Path>) -> Result<Config, Error> {
let cs = match self {
Rc::Single(c) => match filename {
Some(filename) => {
if c.matches(filename)? {
return Ok(c);
} else {
return Err(Error::Unmatched);
}
}
// TODO
None => return Ok(c),
},
Rc::Multi(cs) => cs,
};
match filename {
Some(filename) => {
for c in cs {
if c.matches(filename)? {
return Ok(c);
}
}
}
// TODO
None => {
let mut first = None;
for c in cs {
if c.test.is_none() {
return Ok(c);
}
if first.is_none() {
first = Some(c);
}
}
return Ok(first.unwrap_or_default());
}
}
Err(Error::Unmatched)
}
}
/// A single object in the `.swcrc` file
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Config {
#[serde(default)]
pub test: Option<String>,
#[serde(default)]
pub jsc: JscConfig,
#[serde(default)]
pub module: Option<ModuleConfig>,
#[serde(default)]
pub minify: Option<bool>,
}
impl Config {
pub fn matches(&self, filename: &Path) -> Result<bool, Error> {
lazy_static! {
static ref CACHE: CHashMap<String, Regex> = Default::default();
}
match self.test {
Some(ref test) => {
if !CACHE.contains_key(&*test) {
let re = Regex::new(&test).map_err(|err| Error::InvalidRegex { err })?;
CACHE.insert(test.clone(), re);
}
let re = CACHE.get(&*test).unwrap();
Ok(re.is_match(&filename.display().to_string()))
}
None => Ok(true),
}
}
}
/// One `BuiltConfig` per a directory with swcrc
pub struct BuiltConfig<P: Pass> {
pub pass: P,
pub syntax: Syntax,
pub minify: bool,
pub external_helpers: bool,
pub source_maps: bool,
}
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct JscConfig {
#[serde(rename = "parser", default)]
pub syntax: Option<Syntax>,
#[serde(default)]
pub transform: Option<TransformConfig>,
#[serde(default)]
pub external_helpers: bool,
#[serde(default)]
pub target: JscTarget,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)]
pub enum JscTarget {
#[serde(rename = "es3")]
Es3,
#[serde(rename = "es5")]
Es5,
#[serde(rename = "es2015")]
Es2015,
#[serde(rename = "es2016")]
Es2016,
#[serde(rename = "es2017")]
Es2017,
#[serde(rename = "es2018")]
Es2018,
#[serde(rename = "es2019")]
Es2019,
}
impl Default for JscTarget {
fn default() -> Self {
JscTarget::Es3
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
#[serde(tag = "type")]
pub enum ModuleConfig {
#[serde(rename = "commonjs")]
CommonJs(modules::common_js::Config),
#[serde(rename = "umd")]
Umd(modules::umd::Config),
#[serde(rename = "amd")]
Amd(modules::amd::Config),
}
impl ModuleConfig {
pub fn build(cm: Arc<SourceMap>, config: Option<ModuleConfig>) -> Box<dyn Pass> {
match config {
None => box noop(),
Some(ModuleConfig::CommonJs(config)) => box modules::common_js::common_js(config),
Some(ModuleConfig::Umd(config)) => box modules::umd::umd(cm, config),
Some(ModuleConfig::Amd(config)) => box modules::amd::amd(config),
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct TransformConfig {
#[serde(default)]
pub react: react::Options,
#[serde(default)]
pub const_modules: Option<ConstModulesConfig>,
#[serde(default)]
pub optimizer: Option<OptimizerConfig>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ConstModulesConfig {
#[serde(default)]
pub globals: HashMap<JsWord, HashMap<JsWord, String>>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct OptimizerConfig {
#[serde(default)]
pub globals: Option<GlobalPassOption>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct GlobalPassOption {
#[serde(default)]
pub vars: HashMap<String, String>,
#[serde(default = "default_envs")]
pub envs: HashSet<String>,
}
fn default_envs() -> HashSet<String> {
let mut v = HashSet::default();
v.insert(String::from("NODE_ENV"));
v.insert(String::from("SWC_ENV"));
v
}
impl GlobalPassOption {
pub fn build(self, cm: &SourceMap, handler: &Handler) -> InlineGlobals {
fn mk_map(
cm: &SourceMap,
handler: &Handler,
values: impl Iterator<Item = (String, String)>,
is_env: bool,
) -> HashMap<JsWord, Expr> {
let mut m = HashMap::default();
for (k, v) in values {
let v = if is_env {
format!("'{}'", v)
} else {
(*v).into()
};
let v_str = v.clone();
let fm = cm.new_source_file(FileName::Custom(format!("GLOBAL.{}", k)), v);
let session = ParseSess { handler };
let mut module = Parser::new(
session,
Syntax::Es(Default::default()),
SourceFileInput::from(&*fm),
None,
)
.parse_module()
.map_err(|mut e| {
e.emit();
()
})
.unwrap_or_else(|()| {
panic!(
"failed to parse global variable {}=`{}` as module",
k, v_str
)
});
let expr = match module.body.pop() {
Some(ModuleItem::Stmt(Stmt::Expr(box expr))) => expr,
_ => panic!("{} is not a valid expression", v_str),
};
m.insert((*k).into(), expr);
}
m
}
let envs = self.envs;
InlineGlobals {
globals: mk_map(cm, handler, self.vars.into_iter(), false),
envs: mk_map(
cm,
handler,
env::vars().filter(|(k, _)| envs.contains(&*k)),
true,
),
}
}
}
fn default_env_name() -> String {
match env::var("SWC_ENV") {
Ok(v) => return v,
Err(_) => {}
}
match env::var("NODE_ENV") {
Ok(v) => return v,
Err(_) => return "development".into(),
}
}
pub trait Merge {
/// Apply overrides from `from`
fn merge(&mut self, from: &Self);
}
impl<T: Clone> Merge for Option<T>
where
T: Merge,
{
fn merge(&mut self, from: &Option<T>) {
match *from {
Some(ref from) => match *self {
Some(ref mut v) => v.merge(from),
None => *self = Some(from.clone()),
},
// no-op
None => {}
}
}
}
impl Merge for Config {
fn merge(&mut self, from: &Self) {
self.jsc.merge(&from.jsc);
self.module.merge(&from.module);
self.minify.merge(&from.minify)
}
}
impl Merge for JscConfig {
fn merge(&mut self, from: &Self) {
self.syntax.merge(&from.syntax);
self.transform.merge(&from.transform);
self.target.merge(&from.target);
self.external_helpers.merge(&from.external_helpers);
}
}
impl Merge for JscTarget {
fn merge(&mut self, from: &Self) {
if *self < *from {
*self = *from
}
}
}
impl Merge for Option<ModuleConfig> {
fn merge(&mut self, from: &Self) {
match *from {
Some(ref c2) => *self = Some(c2.clone()),
None => {}
}
}
}
impl Merge for bool {
fn merge(&mut self, from: &Self) {
*self |= *from
}
}
impl Merge for Syntax {
fn merge(&mut self, from: &Self) {
*self = *from;
}
}
impl Merge for TransformConfig {
fn merge(&mut self, from: &Self) {
self.optimizer.merge(&from.optimizer);
self.const_modules.merge(&from.const_modules);
self.react.merge(&from.react);
}
}
impl Merge for OptimizerConfig {
fn merge(&mut self, from: &Self) {
self.globals.merge(&from.globals)
}
}
impl Merge for GlobalPassOption {
fn merge(&mut self, from: &Self) {
*self = from.clone();
}
}
impl Merge for react::Options {
fn merge(&mut self, from: &Self) {
*self = from.clone();
}
}
impl Merge for ConstModulesConfig {
fn merge(&mut self, from: &Self) {
*self = from.clone()
}
}

9
src/config/object.json Normal file
View File

@ -0,0 +1,9 @@
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": false
}
}
}

12
src/config/tests.rs Normal file
View File

@ -0,0 +1,12 @@
use super::Rc;
use serde_json;
#[test]
fn object() {
let _: Rc = serde_json::from_str(include_str!("object.json")).expect("failed to parse");
}
#[test]
fn array() {
let _: Rc = serde_json::from_str(include_str!("array.json")).expect("failed to parse");
}

54
src/error.rs Normal file
View File

@ -0,0 +1,54 @@
use failure::Fail;
use lazy_static::lazy_static;
use regex;
use serde_json;
use sourcemap;
use std::{io, string::FromUtf8Error};
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "failed to read config file: {}", err)]
FailedToReadConfigFile { err: io::Error },
#[fail(display = "failed to parse config file: {}", err)]
FailedToParseConfigFile { err: serde_json::error::Error },
#[fail(display = "failed to parse module")]
FailedToParseModule {},
#[fail(display = "failed to read module: {}", err)]
FailedToReadModule { err: io::Error },
#[fail(display = "failed to emit module: {}", err)]
FailedToEmitModule { err: io::Error },
#[fail(display = "failed to write sourcemap: {}", err)]
FailedToWriteSourceMap { err: sourcemap::Error },
#[fail(display = "sourcemap is not utf8: {}", err)]
SourceMapNotUtf8 { err: FromUtf8Error },
#[fail(display = "invalid regexp: {}", err)]
InvalidRegex { err: regex::Error },
/* #[fail(display = "generated code is not utf8: {}", err)]
* GeneratedCodeNotUtf8 { err: FromUtf8Error }, */
/// This means `test` field in .swcrc file did not matched the compiling
/// file.
#[fail(display = "unmatched")]
Unmatched,
}
/// Returns true if `SWC_DEBUG` environment is set to `1` or `true`.
pub(crate) fn debug() -> bool {
lazy_static! {
static ref DEBUG: bool = {
match ::std::env::var("SWC_DEBUG") {
Ok(ref v) if v == "1" || v.eq_ignore_ascii_case("true") => true,
_ => false,
}
};
};
*DEBUG
}

View File

@ -1,4 +1,287 @@
#![feature(box_syntax, box_patterns)]
pub extern crate sourcemap;
pub extern crate swc_atoms as atoms;
pub extern crate swc_common as common;
pub extern crate swc_ecmascript as ecmascript;
mod builder;
pub mod config;
pub mod error;
pub use crate::builder::PassBuilder;
use crate::{
config::{BuiltConfig, ConfigFile, Merge, Options, Rc, RootMode},
error::Error,
};
use common::{
comments::Comments, errors::Handler, FileName, FoldWith, Globals, SourceFile, SourceMap,
GLOBALS,
};
use ecmascript::{
ast::Module,
codegen::{self, Emitter},
parser::{Parser, Session as ParseSess, Syntax},
transforms::{
helpers::{self, Helpers},
util,
},
};
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>,
handler: Handler,
}
#[derive(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 {
/// 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, op)
}
/// This method parses a javascript / typescript file
pub fn parse_js(
&self,
fm: Arc<SourceFile>,
syntax: Syntax,
comments: Option<&Comments>,
) -> Result<Module, Error> {
self.run(|| {
let session = ParseSess {
handler: &self.handler,
};
let mut parser = Parser::new(session, syntax, SourceFileInput::from(&*fm), comments);
let module = parser.parse_module().map_err(|mut e| {
e.emit();
Error::FailedToParseModule {}
})?;
Ok(module)
})
}
pub fn print(
&self,
module: &Module,
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,
pos_of_leading_comments: Default::default(),
};
emitter
.emit_module(&module)
.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(),
}
}
/// 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,
..
} = 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, 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,
Some(config_file.into_config(Some(path))?),
);
return Ok(built);
}
}
_ => {}
}
let built = opts.build(
&self.cm,
&self.handler,
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 comments = Default::default();
let module = self.parse_js(
fm.clone(),
config.syntax,
if config.minify { None } else { Some(&comments) },
)?;
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, &comments, config.source_maps, config.minify)
})
}
}
struct MyHandlers;
impl ecmascript::codegen::Handlers for MyHandlers {}

View File

@ -1,7 +1,7 @@
[package]
name = "testing"
version = "0.3.1"
authors = ["강동윤 <kdy1@outlook.kr>"]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
documentation = "https://swc-project.github.io/rustdoc/testing/"