mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 13:11:31 +03:00
f6aabfce9c
swc_common: - Add `AHashMap`. - Add `AHashSet`.
311 lines
8.8 KiB
Rust
311 lines
8.8 KiB
Rust
use pretty_assertions::assert_eq;
|
|
use serde::Deserialize;
|
|
use serde_json::Value;
|
|
use std::{
|
|
cmp::Ordering,
|
|
env,
|
|
fs::File,
|
|
io,
|
|
io::Read,
|
|
path::{Path, PathBuf},
|
|
};
|
|
use swc_common::{
|
|
collections::AHashMap, comments::SingleThreadedComments, input::StringInput, FromVariant, Mark,
|
|
};
|
|
use swc_ecma_ast::*;
|
|
use swc_ecma_codegen::Emitter;
|
|
use swc_ecma_parser::{EsConfig, Parser, Syntax};
|
|
use swc_ecma_preset_env::{preset_env, Config, FeatureOrModule, Mode, Targets, Version};
|
|
use swc_ecma_utils::drop_span;
|
|
use swc_ecma_visit::{as_folder, FoldWith, VisitMut};
|
|
use testing::{NormalizedOutput, Tester};
|
|
|
|
/// options.json file
|
|
#[derive(Debug, Deserialize)]
|
|
struct BabelOptions {
|
|
presets: Vec<(String, PresetConfig)>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
struct PresetConfig {
|
|
#[serde(default)]
|
|
pub use_built_ins: UseBuiltIns,
|
|
|
|
#[serde(default)]
|
|
pub corejs: CoreJs,
|
|
|
|
#[serde(default)]
|
|
pub modules: ModulesConfig,
|
|
|
|
#[serde(default)]
|
|
pub targets: Option<Targets>,
|
|
|
|
#[serde(default)]
|
|
pub include: Vec<FeatureOrModule>,
|
|
|
|
#[serde(default)]
|
|
pub exclude: Vec<FeatureOrModule>,
|
|
|
|
#[serde(default)]
|
|
pub force_all_transforms: bool,
|
|
|
|
#[serde(default)]
|
|
pub shipped_proposals: bool,
|
|
|
|
#[serde(default)]
|
|
pub config_path: String,
|
|
|
|
#[serde(default)]
|
|
pub debug: bool,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
#[serde(untagged)]
|
|
pub enum CoreJs {
|
|
Ver(Version),
|
|
Val(AHashMap<String, Value>),
|
|
}
|
|
|
|
impl Default for CoreJs {
|
|
fn default() -> Self {
|
|
Self::Ver(Version {
|
|
major: 2,
|
|
minor: 0,
|
|
patch: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(untagged)]
|
|
enum ModulesConfig {
|
|
Bool(bool),
|
|
}
|
|
|
|
impl Default for ModulesConfig {
|
|
fn default() -> Self {
|
|
ModulesConfig::Bool(false)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(untagged)]
|
|
enum UseBuiltIns {
|
|
Bool(bool),
|
|
Str(String),
|
|
}
|
|
|
|
impl Default for UseBuiltIns {
|
|
fn default() -> Self {
|
|
UseBuiltIns::Bool(false)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, FromVariant)]
|
|
enum Error {
|
|
Io(io::Error),
|
|
Var(env::VarError),
|
|
WalkDir(walkdir::Error),
|
|
Json(serde_json::Error),
|
|
Msg(String),
|
|
}
|
|
|
|
fn exec(c: PresetConfig, dir: PathBuf) -> Result<(), Error> {
|
|
println!("Config: {:?}", c);
|
|
|
|
Tester::new()
|
|
.print_errors(|cm, handler| {
|
|
let mut pass = preset_env(
|
|
Mark::fresh(Mark::root()),
|
|
Some(SingleThreadedComments::default()),
|
|
Config {
|
|
debug: c.debug,
|
|
mode: match c.use_built_ins {
|
|
UseBuiltIns::Bool(false) => None,
|
|
UseBuiltIns::Str(ref s) if s == "usage" => Some(Mode::Usage),
|
|
UseBuiltIns::Str(ref s) if s == "entry" => Some(Mode::Entry),
|
|
v => unreachable!("invalid: {:?}", v),
|
|
},
|
|
skip: vec![],
|
|
// TODO
|
|
loose: true,
|
|
// TODO
|
|
dynamic_import: true,
|
|
bugfixes: false,
|
|
include: c.include,
|
|
exclude: c.exclude,
|
|
core_js: match c.corejs {
|
|
CoreJs::Ver(v) => Some(v),
|
|
ref s => unimplemented!("Unknown core js version: {:?}", s),
|
|
},
|
|
force_all_transforms: c.force_all_transforms,
|
|
shipped_proposals: c.shipped_proposals,
|
|
targets: c.targets,
|
|
path: std::env::current_dir().unwrap(),
|
|
},
|
|
);
|
|
|
|
let print = |m: &Module| {
|
|
let mut buf = vec![];
|
|
{
|
|
let mut emitter = Emitter {
|
|
cfg: swc_ecma_codegen::Config { minify: false },
|
|
comments: None,
|
|
cm: cm.clone(),
|
|
wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new(
|
|
cm.clone(),
|
|
"\n",
|
|
&mut buf,
|
|
None,
|
|
)),
|
|
};
|
|
|
|
emitter.emit_module(m).expect("failed to emit module");
|
|
}
|
|
String::from_utf8(buf).expect("invalid utf8 character detected")
|
|
};
|
|
|
|
let fm = cm
|
|
.load_file(&dir.join("input.mjs"))
|
|
.expect("failed to load file");
|
|
let mut p = Parser::new(
|
|
Syntax::Es(EsConfig {
|
|
dynamic_import: true,
|
|
..Default::default()
|
|
}),
|
|
StringInput::from(&*fm),
|
|
None,
|
|
);
|
|
|
|
let module = p
|
|
.parse_module()
|
|
.map_err(|e| e.into_diagnostic(&handler).emit())?;
|
|
|
|
for e in p.take_errors() {
|
|
e.into_diagnostic(&handler).emit()
|
|
}
|
|
|
|
let actual = module.fold_with(&mut pass);
|
|
|
|
// debug mode?
|
|
if dir.join("stdout.txt").exists() {
|
|
let mut out = read(&dir.join("stdout.txt"));
|
|
|
|
if dir.join("stderr.txt").exists() {
|
|
out.push_str("\n\n");
|
|
out.push_str(&read(&dir.join("stderr.txt")));
|
|
}
|
|
|
|
return Ok(());
|
|
};
|
|
|
|
let actual_src = print(&actual);
|
|
if let Ok(..) = env::var("UPDATE") {
|
|
NormalizedOutput::from(actual_src.clone())
|
|
.compare_to_file(dir.join("output.mjs"))
|
|
.unwrap();
|
|
}
|
|
|
|
// It's normal transform test.
|
|
let expected = {
|
|
let fm = cm
|
|
.load_file(&dir.join("output.mjs"))
|
|
.expect("failed to load output file");
|
|
|
|
let mut p = Parser::new(
|
|
Syntax::Es(EsConfig {
|
|
dynamic_import: true,
|
|
..Default::default()
|
|
}),
|
|
StringInput::from(&*fm),
|
|
None,
|
|
);
|
|
|
|
let mut m = p
|
|
.parse_module()
|
|
.map_err(|e| e.into_diagnostic(&handler).emit())?;
|
|
|
|
for e in p.take_errors() {
|
|
e.into_diagnostic(&handler).emit()
|
|
}
|
|
|
|
m.body.sort_by(|a, b| match *a {
|
|
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
|
|
ref specifiers,
|
|
ref src,
|
|
..
|
|
})) if specifiers.is_empty() && src.value.starts_with("core-js/modules") => {
|
|
match *b {
|
|
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
|
|
specifiers: ref rs,
|
|
src: ref rsrc,
|
|
..
|
|
})) if rs.is_empty() && rsrc.value.starts_with("core-js/modules") => {
|
|
src.value.cmp(&rsrc.value)
|
|
}
|
|
|
|
_ => Ordering::Equal,
|
|
}
|
|
}
|
|
_ => Ordering::Equal,
|
|
});
|
|
|
|
m
|
|
};
|
|
|
|
let expected_src = print(&expected);
|
|
|
|
if drop_span(actual.fold_with(&mut as_folder(Normalizer)))
|
|
== drop_span(expected.fold_with(&mut as_folder(Normalizer)))
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
if actual_src != expected_src {
|
|
panic!(
|
|
r#"assertion failed: `(left == right)`
|
|
{}"#,
|
|
::testing::diff(&actual_src, &expected_src),
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.expect("failed to execute");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn read(p: &Path) -> String {
|
|
let mut buf = String::new();
|
|
let mut f = File::open(p).expect("failed to open file");
|
|
f.read_to_string(&mut buf).expect("failed to read file");
|
|
|
|
buf
|
|
}
|
|
|
|
#[testing::fixture("tests/fixtures/**/input.mjs")]
|
|
fn fixture(input: PathBuf) {
|
|
let entry_dir = input.parent().unwrap().to_path_buf();
|
|
println!("File: {}", entry_dir.display());
|
|
|
|
let cfg: BabelOptions =
|
|
serde_json::from_reader(File::open(entry_dir.join("options.json")).unwrap())
|
|
.map_err(|err| Error::Msg(format!("failed to parse options.json: {}", err)))
|
|
.unwrap();
|
|
assert_eq!(cfg.presets.len(), 1);
|
|
let cfg = cfg.presets.into_iter().map(|v| v.1).next().unwrap();
|
|
|
|
exec(cfg, entry_dir).expect("failed to run test")
|
|
}
|
|
|
|
struct Normalizer;
|
|
|
|
impl VisitMut for Normalizer {
|
|
fn visit_mut_str(&mut self, n: &mut Str) {
|
|
n.kind = Default::default();
|
|
}
|
|
}
|