refactor: make ParsedModule implement Sync (#11581)

This commit is contained in:
David Sherret 2021-08-06 10:36:16 -04:00 committed by GitHub
parent e9ddc7a41a
commit 466d3df9d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 436 additions and 230 deletions

View file

@ -11,10 +11,13 @@ use std::error::Error;
use std::fmt;
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use swc_common::chain;
use swc_common::comments::Comment;
use swc_common::comments::CommentKind;
use swc_common::comments::Comments;
use swc_common::comments::SingleThreadedComments;
use swc_common::BytePos;
use swc_common::FileName;
use swc_common::Globals;
use swc_common::SourceFile;
@ -43,13 +46,20 @@ use swc_ecmascript::transforms::react;
use swc_ecmascript::transforms::typescript;
use swc_ecmascript::visit::FoldWith;
mod bundle_hook;
mod comments;
mod source_file_info;
mod transforms;
pub use bundle_hook::BundleHook;
use comments::MultiThreadedComments;
use source_file_info::SourceFileInfo;
static TARGET: JscTarget = JscTarget::Es2020;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Location {
pub filename: String,
pub specifier: String,
pub line: usize,
pub col: usize,
}
@ -65,7 +75,7 @@ impl From<swc_common::Loc> for Location {
};
Location {
filename,
specifier: filename,
line: swc_loc.line,
col: swc_loc.col_display,
}
@ -74,13 +84,13 @@ impl From<swc_common::Loc> for Location {
impl From<Location> for ModuleSpecifier {
fn from(loc: Location) -> Self {
resolve_url_or_path(&loc.filename).unwrap()
resolve_url_or_path(&loc.specifier).unwrap()
}
}
impl std::fmt::Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.filename, self.line, self.col)
write!(f, "{}:{}:{}", self.specifier, self.line, self.col)
}
}
@ -239,18 +249,15 @@ fn strip_config_from_emit_options(
/// processing.
#[derive(Clone)]
pub struct ParsedModule {
comments: SingleThreadedComments,
leading_comments: Vec<Comment>,
info: Arc<SourceFileInfo>,
comments: MultiThreadedComments,
pub module: Module,
pub source_map: Rc<SourceMap>,
source_file: Rc<SourceFile>,
}
impl fmt::Debug for ParsedModule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ParsedModule")
.field("comments", &self.comments)
.field("leading_comments", &self.leading_comments)
.field("module", &self.module)
.finish()
}
@ -265,28 +272,20 @@ impl ParsedModule {
/// Get the module's leading comments, where triple slash directives might
/// be located.
pub fn get_leading_comments(&self) -> Vec<Comment> {
self.leading_comments.clone()
self
.comments
.get_leading(self.module.span.lo)
.unwrap_or_else(Vec::new)
}
/// Get the module's comments.
/// Get the module's comments sorted by position.
pub fn get_comments(&self) -> Vec<Comment> {
let mut comments = Vec::new();
let (leading_comments, trailing_comments) = self.comments.borrow_all();
for value in leading_comments.values() {
comments.append(&mut value.clone());
}
for value in trailing_comments.values() {
comments.append(&mut value.clone());
}
comments
self.comments.get_vec()
}
/// Get a location for a given span within the module.
pub fn get_location(&self, span: &Span) -> Location {
self.source_map.lookup_char_pos(span.lo).into()
/// Get a location for a given position within the module.
pub fn get_location(&self, pos: BytePos) -> Location {
self.info.get_location(pos)
}
/// Transform a TypeScript file into a JavaScript file, based on the supplied
@ -298,10 +297,14 @@ impl ParsedModule {
options: &EmitOptions,
) -> Result<(String, Option<String>), AnyError> {
let program = Program::Module(self.module);
let source_map = Rc::new(SourceMap::default());
let file_name = FileName::Custom(self.info.specifier.clone());
source_map.new_source_file(file_name, self.info.text.clone());
let comments = self.comments.as_single_threaded(); // needs to be mutable
let jsx_pass = react::react(
self.source_map.clone(),
Some(&self.comments),
source_map.clone(),
Some(&comments),
react::Options {
pragma: options.jsx_factory.clone(),
pragma_frag: options.jsx_fragment_factory.clone(),
@ -324,7 +327,7 @@ impl ParsedModule {
typescript::strip::strip_with_config(strip_config_from_emit_options(
options
)),
fixer(Some(&self.comments)),
fixer(Some(&comments)),
hygiene(),
);
@ -338,7 +341,7 @@ impl ParsedModule {
let mut buf = vec![];
{
let writer = Box::new(JsWriter::new(
self.source_map.clone(),
source_map.clone(),
"\n",
&mut buf,
Some(&mut src_map_buf),
@ -346,8 +349,8 @@ impl ParsedModule {
let config = swc_ecmascript::codegen::Config { minify: false };
let mut emitter = swc_ecmascript::codegen::Emitter {
cfg: config,
comments: Some(&self.comments),
cm: self.source_map.clone(),
comments: Some(&comments),
cm: source_map.clone(),
wr: writer,
};
program.emit_with(&mut emitter)?;
@ -356,8 +359,7 @@ impl ParsedModule {
let mut map: Option<String> = None;
{
let mut buf = Vec::new();
self
.source_map
source_map
.build_source_map_from(&mut src_map_buf, None)
.to_writer(&mut buf)?;
@ -373,41 +375,7 @@ impl ParsedModule {
}
}
pub fn parse_with_source_map(
specifier: &str,
source: &str,
media_type: &MediaType,
source_map: Rc<SourceMap>,
) -> Result<ParsedModule, AnyError> {
let source_file = source_map.new_source_file(
FileName::Custom(specifier.to_string()),
source.to_string(),
);
let syntax = get_syntax(media_type);
let input = StringInput::from(&*source_file);
let comments = SingleThreadedComments::default();
let lexer = Lexer::new(syntax, TARGET, input, Some(&comments));
let mut parser = swc_ecmascript::parser::Parser::new_from(lexer);
let sm = &source_map;
let module = parser.parse_module().map_err(move |err| Diagnostic {
location: sm.lookup_char_pos(err.span().lo).into(),
message: err.into_kind().msg().to_string(),
})?;
let leading_comments =
comments.with_leading(module.span.lo, |comments| comments.to_vec());
Ok(ParsedModule {
comments,
leading_comments,
module,
source_map,
source_file,
})
}
/// For a given specifier, source, and media type, parse the source of the
/// For a given specifier, source, and media type, parse the text of the
/// module and return a representation which can be further processed.
///
/// # Arguments
@ -424,8 +392,16 @@ pub fn parse(
source: &str,
media_type: &MediaType,
) -> Result<ParsedModule, AnyError> {
let source_map = Rc::new(SourceMap::default());
parse_with_source_map(specifier, source, media_type, source_map)
let info = SourceFileInfo::new(specifier, source);
let input =
StringInput::new(source, BytePos(0), BytePos(source.len() as u32));
let (comments, module) = parse_string_input(&info, input, media_type)?;
Ok(ParsedModule {
info: Arc::new(info),
comments: MultiThreadedComments::from_single_threaded(comments),
module,
})
}
pub enum TokenOrComment {
@ -453,21 +429,12 @@ fn flatten_comments(
comments.into_iter().flat_map(|el| el.1)
}
pub fn lex(
specifier: &str,
source: &str,
media_type: &MediaType,
) -> Vec<LexedItem> {
let source_map = SourceMap::default();
let source_file = source_map.new_source_file(
FileName::Custom(specifier.to_string()),
source.to_string(),
);
pub fn lex(source: &str, media_type: &MediaType) -> Vec<LexedItem> {
let comments = SingleThreadedComments::default();
let lexer = Lexer::new(
get_syntax(media_type),
TARGET,
StringInput::from(source_file.as_ref()),
StringInput::new(source, BytePos(0), BytePos(source.len() as u32)),
Some(&comments),
);
@ -494,19 +461,24 @@ pub fn lex(
/// A low level function which transpiles a source module into an swc
/// SourceFile.
pub fn transpile_module(
filename: &str,
src: &str,
specifier: &str,
source: &str,
media_type: &MediaType,
emit_options: &EmitOptions,
globals: &Globals,
cm: Rc<SourceMap>,
) -> Result<(Rc<SourceFile>, Module), AnyError> {
let parsed_module =
parse_with_source_map(filename, src, media_type, cm.clone())?;
let info = SourceFileInfo::new(specifier, source);
let source_file = cm.new_source_file(
FileName::Custom(specifier.to_string()),
source.to_string(),
);
let input = StringInput::from(&*source_file);
let (comments, module) = parse_string_input(&info, input, media_type)?;
let jsx_pass = react::react(
cm,
Some(&parsed_module.comments),
Some(&comments),
react::Options {
pragma: emit_options.jsx_factory.clone(),
pragma_frag: emit_options.jsx_fragment_factory.clone(),
@ -526,12 +498,9 @@ pub fn transpile_module(
typescript::strip::strip_with_config(strip_config_from_emit_options(
emit_options
)),
fixer(Some(&parsed_module.comments)),
fixer(Some(&comments)),
);
let source_file = parsed_module.source_file.clone();
let module = parsed_module.module;
let module = swc_common::GLOBALS.set(globals, || {
helpers::HELPERS.set(&helpers::Helpers::new(false), || {
module.fold_with(&mut passes)
@ -541,55 +510,22 @@ pub fn transpile_module(
Ok((source_file, module))
}
pub struct BundleHook;
fn parse_string_input(
info: &SourceFileInfo,
input: StringInput,
media_type: &MediaType,
) -> Result<(SingleThreadedComments, Module), AnyError> {
let syntax = get_syntax(media_type);
let comments = SingleThreadedComments::default();
let lexer = Lexer::new(syntax, TARGET, input, Some(&comments));
let mut parser = swc_ecmascript::parser::Parser::new_from(lexer);
impl swc_bundler::Hook for BundleHook {
fn get_import_meta_props(
&self,
span: swc_common::Span,
module_record: &swc_bundler::ModuleRecord,
) -> Result<Vec<swc_ecmascript::ast::KeyValueProp>, AnyError> {
use swc_ecmascript::ast;
let module = parser.parse_module().map_err(|err| Diagnostic {
location: info.get_location(err.span().lo),
message: err.into_kind().msg().to_string(),
})?;
// we use custom file names, and swc "wraps" these in `<` and `>` so, we
// want to strip those back out.
let mut value = module_record.file_name.to_string();
value.pop();
value.remove(0);
Ok(vec![
ast::KeyValueProp {
key: ast::PropName::Ident(ast::Ident::new("url".into(), span)),
value: Box::new(ast::Expr::Lit(ast::Lit::Str(ast::Str {
span,
value: value.into(),
kind: ast::StrKind::Synthesized,
has_escape: false,
}))),
},
ast::KeyValueProp {
key: ast::PropName::Ident(ast::Ident::new("main".into(), span)),
value: Box::new(if module_record.is_entry {
ast::Expr::Member(ast::MemberExpr {
span,
obj: ast::ExprOrSuper::Expr(Box::new(ast::Expr::MetaProp(
ast::MetaPropExpr {
meta: ast::Ident::new("import".into(), span),
prop: ast::Ident::new("meta".into(), span),
},
))),
prop: Box::new(ast::Expr::Ident(ast::Ident::new(
"main".into(),
span,
))),
computed: false,
})
} else {
ast::Expr::Lit(ast::Lit::Bool(ast::Bool { span, value: false }))
}),
},
])
}
Ok((comments, module))
}
#[cfg(test)]