swc/crates/swc_ecma_preset_env/tests/test.rs
2022-02-22 08:05:54 +00:00

317 lines
9.0 KiB
Rust

#![allow(dead_code)]
use std::{
cmp::Ordering,
env,
fs::File,
io,
io::Read,
path::{Path, PathBuf},
};
use pretty_assertions::assert_eq;
use serde::Deserialize;
use serde_json::Value;
use swc_common::{
chain, 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_transforms::fixer;
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),
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 = chain!(
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(),
},
),
fixer(None)
);
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 {
static_blocks: 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 {
..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();
}
}