diff options
Diffstat (limited to 'cli/ast/mod.rs')
-rw-r--r-- | cli/ast/mod.rs | 565 |
1 files changed, 175 insertions, 390 deletions
diff --git a/cli/ast/mod.rs b/cli/ast/mod.rs index 232db1305..57117bf7b 100644 --- a/cli/ast/mod.rs +++ b/cli/ast/mod.rs @@ -1,62 +1,44 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use crate::config_file; -use crate::media_type::MediaType; use crate::text_encoding::strip_bom; +use deno_ast::get_syntax; +use deno_ast::swc::ast::Module; +use deno_ast::swc::ast::Program; +use deno_ast::swc::codegen::text_writer::JsWriter; +use deno_ast::swc::codegen::Node; +use deno_ast::swc::common::chain; +use deno_ast::swc::common::comments::SingleThreadedComments; +use deno_ast::swc::common::BytePos; +use deno_ast::swc::common::FileName; +use deno_ast::swc::common::Globals; +use deno_ast::swc::common::SourceMap; +use deno_ast::swc::common::Spanned; +use deno_ast::swc::parser::lexer::Lexer; +use deno_ast::swc::parser::StringInput; +use deno_ast::swc::transforms::fixer; +use deno_ast::swc::transforms::helpers; +use deno_ast::swc::transforms::hygiene; +use deno_ast::swc::transforms::pass::Optional; +use deno_ast::swc::transforms::proposals; +use deno_ast::swc::transforms::react; +use deno_ast::swc::transforms::typescript; +use deno_ast::swc::visit::FoldWith; +use deno_ast::Diagnostic; +use deno_ast::LineAndColumnDisplay; +use deno_ast::MediaType; +use deno_ast::ParsedSource; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_core::serde_json; use deno_core::ModuleSpecifier; -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; -use swc_common::SourceMap; -use swc_common::Span; -use swc_common::Spanned; -use swc_ecmascript::ast::Module; -use swc_ecmascript::ast::Program; -use swc_ecmascript::codegen::text_writer::JsWriter; -use swc_ecmascript::codegen::Node; -use swc_ecmascript::dep_graph::analyze_dependencies; -use swc_ecmascript::dep_graph::DependencyDescriptor; -use swc_ecmascript::parser::lexer::Lexer; -use swc_ecmascript::parser::token::Token; -use swc_ecmascript::parser::EsConfig; -use swc_ecmascript::parser::JscTarget; -use swc_ecmascript::parser::StringInput; -use swc_ecmascript::parser::Syntax; -use swc_ecmascript::parser::TsConfig; -use swc_ecmascript::transforms::fixer; -use swc_ecmascript::transforms::helpers; -use swc_ecmascript::transforms::hygiene; -use swc_ecmascript::transforms::pass::Optional; -use swc_ecmascript::transforms::proposals; -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 { @@ -65,9 +47,29 @@ pub struct Location { pub col: usize, } -impl From<swc_common::Loc> for Location { - fn from(swc_loc: swc_common::Loc) -> Self { - use swc_common::FileName::*; +impl Location { + pub fn from_pos(parsed_source: &ParsedSource, pos: BytePos) -> Self { + Location::from_line_and_column( + parsed_source.specifier().to_string(), + parsed_source.source().line_and_column_index(pos), + ) + } + + pub fn from_line_and_column( + specifier: String, + line_and_column: deno_ast::LineAndColumnIndex, + ) -> Self { + Location { + specifier, + line: line_and_column.line_index + 1, + col: line_and_column.column_index, + } + } +} + +impl From<deno_ast::swc::common::Loc> for Location { + fn from(swc_loc: deno_ast::swc::common::Loc) -> Self { + use deno_ast::swc::common::FileName::*; let filename = match &swc_loc.file.name { Real(path_buf) => path_buf.to_string_lossy().to_string(), @@ -78,7 +80,7 @@ impl From<swc_common::Loc> for Location { Location { specifier: filename, line: swc_loc.line, - col: swc_loc.col_display, + col: swc_loc.col.0, } } } @@ -95,60 +97,6 @@ impl std::fmt::Display for Location { } } -/// A diagnostic from the AST parser. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Diagnostic { - pub location: Location, - pub message: String, -} - -impl Error for Diagnostic {} - -impl fmt::Display for Diagnostic { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} at {}", self.message, self.location) - } -} - -fn get_es_config(jsx: bool) -> EsConfig { - EsConfig { - class_private_methods: true, - class_private_props: true, - class_props: true, - dynamic_import: true, - export_default_from: true, - export_namespace_from: true, - import_meta: true, - jsx, - nullish_coalescing: true, - num_sep: true, - optional_chaining: true, - top_level_await: true, - ..EsConfig::default() - } -} - -fn get_ts_config(tsx: bool, dts: bool) -> TsConfig { - TsConfig { - decorators: true, - dts, - dynamic_import: true, - tsx, - ..TsConfig::default() - } -} - -pub fn get_syntax(media_type: &MediaType) -> Syntax { - match media_type { - MediaType::JavaScript => Syntax::Es(get_es_config(false)), - MediaType::Jsx => Syntax::Es(get_es_config(true)), - MediaType::TypeScript => Syntax::Typescript(get_ts_config(false, false)), - MediaType::Dts => Syntax::Typescript(get_ts_config(false, true)), - MediaType::Tsx => Syntax::Typescript(get_ts_config(true, false)), - _ => Syntax::Es(get_es_config(false)), - } -} - #[derive(Debug, Clone)] pub enum ImportsNotUsedAsValues { Remove, @@ -246,222 +194,91 @@ fn strip_config_from_emit_options( } } -/// A logical structure to hold the value of a parsed module for further -/// processing. -#[derive(Clone)] -pub struct ParsedModule { - info: Arc<SourceFileInfo>, - comments: MultiThreadedComments, - pub module: Module, -} - -impl fmt::Debug for ParsedModule { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ParsedModule") - .field("comments", &self.comments) - .field("module", &self.module) - .finish() - } -} - -impl ParsedModule { - /// Return a vector of dependencies for the module. - pub fn analyze_dependencies(&self) -> Vec<DependencyDescriptor> { - analyze_dependencies(&self.module, &self.comments) - } - - /// Get the module's leading comments, where triple slash directives might - /// be located. - pub fn get_leading_comments(&self) -> Vec<Comment> { - self - .comments - .get_leading(self.module.span.lo) - .unwrap_or_else(Vec::new) - } - - /// Get the module's comments sorted by position. - pub fn get_comments(&self) -> Vec<Comment> { - self.comments.get_vec() - } - - /// 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 - /// options. - /// - /// The result is a tuple of the code and optional source map as strings. - pub fn transpile( - self, - 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( - source_map.clone(), - Some(&comments), - react::Options { - pragma: options.jsx_factory.clone(), - pragma_frag: options.jsx_fragment_factory.clone(), - // this will use `Object.assign()` instead of the `_extends` helper - // when spreading props. - use_builtins: true, - ..Default::default() - }, - ); - let mut passes = chain!( - Optional::new(jsx_pass, options.transform_jsx), - Optional::new(transforms::DownlevelImportsFolder, options.repl_imports), - Optional::new(transforms::StripExportsFolder, options.repl_imports), - proposals::decorators::decorators(proposals::decorators::Config { - legacy: true, - emit_metadata: options.emit_metadata - }), - // DownlevelImportsFolder::new(), // todo: make this conditional - helpers::inject_helpers(), - typescript::strip::strip_with_config(strip_config_from_emit_options( - options - )), - fixer(Some(&comments)), - hygiene(), - ); - - let program = swc_common::GLOBALS.set(&Globals::new(), || { - helpers::HELPERS.set(&helpers::Helpers::new(false), || { - program.fold_with(&mut passes) - }) - }); - - let mut src_map_buf = vec![]; - let mut buf = vec![]; - { - let writer = Box::new(JsWriter::new( - source_map.clone(), - "\n", - &mut buf, - Some(&mut src_map_buf), - )); - let config = swc_ecmascript::codegen::Config { minify: false }; - let mut emitter = swc_ecmascript::codegen::Emitter { - cfg: config, - comments: Some(&comments), - cm: source_map.clone(), - wr: writer, - }; - program.emit_with(&mut emitter)?; - } - let mut src = String::from_utf8(buf)?; - let mut map: Option<String> = None; - { - let mut buf = Vec::new(); - source_map - .build_source_map_from(&mut src_map_buf, None) - .to_writer(&mut buf)?; - - if options.inline_source_map { - src.push_str("//# sourceMappingURL=data:application/json;base64,"); - let encoded_map = base64::encode(buf); - src.push_str(&encoded_map); - } else { - map = Some(String::from_utf8(buf)?); - } - } - Ok((src, map)) - } -} - -/// For a given specifier, source, and media type, parse the text of the -/// module and return a representation which can be further processed. +/// Transform a TypeScript file into a JavaScript file, based on the supplied +/// options. /// -/// # Arguments -/// -/// - `specifier` - The module specifier for the module. -/// - `source` - The source code for the module. -/// - `media_type` - The media type for the module. -/// -// NOTE(bartlomieju): `specifier` has `&str` type instead of -// `&ModuleSpecifier` because runtime compiler APIs don't -// require valid module specifiers -pub fn parse( - specifier: &str, - source: &str, - media_type: &MediaType, -) -> Result<ParsedModule, AnyError> { - let source = strip_bom(source); - let info = SourceFileInfo::new(specifier, source); - let input = - StringInput::new(source, BytePos(0), BytePos(source.len() as u32)); - let (comments, module) = - parse_string_input(input, media_type).map_err(|err| Diagnostic { - location: info.get_location(err.span().lo), - message: err.into_kind().msg().to_string(), - })?; - - Ok(ParsedModule { - info: Arc::new(info), - comments: MultiThreadedComments::from_single_threaded(comments), - module, - }) -} - -pub enum TokenOrComment { - Token(Token), - Comment { kind: CommentKind, text: String }, -} - -pub struct LexedItem { - pub span: Span, - pub inner: TokenOrComment, -} - -impl LexedItem { - pub fn span_as_range(&self) -> Range<usize> { - self.span.lo.0 as usize..self.span.hi.0 as usize - } -} - -fn flatten_comments( - comments: SingleThreadedComments, -) -> impl Iterator<Item = Comment> { - let (leading, trailing) = comments.take_all(); - let mut comments = (*leading).clone().into_inner(); - comments.extend((*trailing).clone().into_inner()); - comments.into_iter().flat_map(|el| el.1) -} +/// The result is a tuple of the code and optional source map as strings. +pub fn transpile( + parsed_source: &ParsedSource, + options: &EmitOptions, +) -> Result<(String, Option<String>), AnyError> { + let program: Program = (*parsed_source.program()).clone(); + let source_map = Rc::new(SourceMap::default()); + let file_name = FileName::Custom(parsed_source.specifier().to_string()); + source_map + .new_source_file(file_name, parsed_source.source().text().to_string()); + let comments = parsed_source.comments().as_single_threaded(); // needs to be mutable -pub fn lex(source: &str, media_type: &MediaType) -> Vec<LexedItem> { - let comments = SingleThreadedComments::default(); - let lexer = Lexer::new( - get_syntax(media_type), - TARGET, - StringInput::new(source, BytePos(0), BytePos(source.len() as u32)), + let jsx_pass = react::react( + source_map.clone(), Some(&comments), + react::Options { + pragma: options.jsx_factory.clone(), + pragma_frag: options.jsx_fragment_factory.clone(), + // this will use `Object.assign()` instead of the `_extends` helper + // when spreading props. + use_builtins: true, + ..Default::default() + }, + ); + let mut passes = chain!( + Optional::new(jsx_pass, options.transform_jsx), + Optional::new(transforms::DownlevelImportsFolder, options.repl_imports), + Optional::new(transforms::StripExportsFolder, options.repl_imports), + proposals::decorators::decorators(proposals::decorators::Config { + legacy: true, + emit_metadata: options.emit_metadata + }), + // DownlevelImportsFolder::new(), // todo: make this conditional + helpers::inject_helpers(), + typescript::strip::strip_with_config(strip_config_from_emit_options( + options + )), + fixer(Some(&comments)), + hygiene(), ); - let mut tokens: Vec<LexedItem> = lexer - .map(|token| LexedItem { - span: token.span, - inner: TokenOrComment::Token(token.token), + let program = deno_ast::swc::common::GLOBALS.set(&Globals::new(), || { + helpers::HELPERS.set(&helpers::Helpers::new(false), || { + program.fold_with(&mut passes) }) - .collect(); - - tokens.extend(flatten_comments(comments).map(|comment| LexedItem { - span: comment.span, - inner: TokenOrComment::Comment { - kind: comment.kind, - text: comment.text, - }, - })); - - tokens.sort_by_key(|item| item.span.lo.0); + }); - tokens + let mut src_map_buf = vec![]; + let mut buf = vec![]; + { + let writer = Box::new(JsWriter::new( + source_map.clone(), + "\n", + &mut buf, + Some(&mut src_map_buf), + )); + let config = deno_ast::swc::codegen::Config { minify: false }; + let mut emitter = deno_ast::swc::codegen::Emitter { + cfg: config, + comments: Some(&comments), + cm: source_map.clone(), + wr: writer, + }; + program.emit_with(&mut emitter)?; + } + let mut src = String::from_utf8(buf)?; + let mut map: Option<String> = None; + { + let mut buf = Vec::new(); + source_map + .build_source_map_from(&mut src_map_buf, None) + .to_writer(&mut buf)?; + + if options.inline_source_map { + src.push_str("//# sourceMappingURL=data:application/json;base64,"); + let encoded_map = base64::encode(buf); + src.push_str(&encoded_map); + } else { + map = Some(String::from_utf8(buf)?); + } + } + Ok((src, map)) } /// A low level function which transpiles a source module into an swc @@ -469,22 +286,32 @@ pub fn lex(source: &str, media_type: &MediaType) -> Vec<LexedItem> { pub fn transpile_module( specifier: &str, source: &str, - media_type: &MediaType, + media_type: MediaType, emit_options: &EmitOptions, globals: &Globals, cm: Rc<SourceMap>, -) -> Result<(Rc<SourceFile>, Module), AnyError> { +) -> Result<(Rc<deno_ast::swc::common::SourceFile>, Module), AnyError> { let source = strip_bom(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(input, media_type).map_err(|err| Diagnostic { - location: cm.lookup_char_pos(err.span().lo).into(), + let comments = SingleThreadedComments::default(); + let syntax = get_syntax(media_type); + let lexer = Lexer::new(syntax, deno_ast::TARGET, input, Some(&comments)); + let mut parser = deno_ast::swc::parser::Parser::new_from(lexer); + let module = parser.parse_module().map_err(|err| { + let location = cm.lookup_char_pos(err.span().lo); + Diagnostic { + display_position: LineAndColumnDisplay { + line_number: location.line, + column_number: location.col_display + 1, + }, + specifier: specifier.to_string(), message: err.into_kind().msg().to_string(), - })?; + } + })?; let jsx_pass = react::react( cm, @@ -511,7 +338,7 @@ pub fn transpile_module( fixer(Some(&comments)), ); - let module = swc_common::GLOBALS.set(globals, || { + let module = deno_ast::swc::common::GLOBALS.set(globals, || { helpers::HELPERS.set(&helpers::Helpers::new(false), || { module.fold_with(&mut passes) }) @@ -520,69 +347,12 @@ pub fn transpile_module( Ok((source_file, module)) } -fn parse_string_input( - input: StringInput, - media_type: &MediaType, -) -> Result< - (SingleThreadedComments, Module), - swc_ecmascript::parser::error::Error, -> { - 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); - let module = parser.parse_module()?; - - Ok((comments, module)) -} - #[cfg(test)] mod tests { use super::*; - use std::collections::HashMap; - use swc_common::BytePos; - use swc_ecmascript::dep_graph::DependencyKind; - - #[test] - fn test_parsed_module_analyze_dependencies() { - let specifier = resolve_url_or_path("https://deno.land/x/mod.js").unwrap(); - let source = "import * as bar from './test.ts';\nconst foo = await import('./foo.ts');"; - let parsed_module = - parse(specifier.as_str(), source, &MediaType::JavaScript) - .expect("could not parse module"); - let actual = parsed_module.analyze_dependencies(); - assert_eq!( - actual, - vec![ - DependencyDescriptor { - kind: DependencyKind::Import, - is_dynamic: false, - leading_comments: Vec::new(), - span: Span::new(BytePos(0), BytePos(33), Default::default()), - specifier: "./test.ts".into(), - specifier_span: Span::new( - BytePos(21), - BytePos(32), - Default::default() - ), - import_assertions: HashMap::default(), - }, - DependencyDescriptor { - kind: DependencyKind::Import, - is_dynamic: true, - leading_comments: Vec::new(), - span: Span::new(BytePos(52), BytePos(70), Default::default()), - specifier: "./foo.ts".into(), - specifier_span: Span::new( - BytePos(59), - BytePos(69), - Default::default() - ), - import_assertions: HashMap::default(), - } - ] - ); - } + use deno_ast::parse_module; + use deno_ast::ParseParams; + use deno_ast::SourceTextInfo; #[test] fn test_transpile() { @@ -605,10 +375,15 @@ mod tests { } } "#; - let module = parse(specifier.as_str(), source, &MediaType::TypeScript) - .expect("could not parse module"); - let (code, maybe_map) = module - .transpile(&EmitOptions::default()) + let module = parse_module(ParseParams { + specifier: specifier.as_str().to_string(), + source: SourceTextInfo::from_string(source.to_string()), + media_type: deno_ast::MediaType::TypeScript, + capture_tokens: false, + maybe_syntax: None, + }) + .expect("could not parse module"); + let (code, maybe_map) = transpile(&module, &EmitOptions::default()) .expect("could not strip types"); assert!(code.starts_with("var D;\n(function(D) {\n")); assert!( @@ -628,10 +403,15 @@ mod tests { } } "#; - let module = parse(specifier.as_str(), source, &MediaType::Tsx) - .expect("could not parse module"); - let (code, _) = module - .transpile(&EmitOptions::default()) + let module = parse_module(ParseParams { + specifier: specifier.as_str().to_string(), + source: SourceTextInfo::from_string(source.to_string()), + media_type: deno_ast::MediaType::Tsx, + capture_tokens: false, + maybe_syntax: None, + }) + .expect("could not parse module"); + let (code, _) = transpile(&module, &EmitOptions::default()) .expect("could not strip types"); assert!(code.contains("React.createElement(\"div\", null")); } @@ -658,10 +438,15 @@ mod tests { } } "#; - let module = parse(specifier.as_str(), source, &MediaType::TypeScript) - .expect("could not parse module"); - let (code, _) = module - .transpile(&EmitOptions::default()) + let module = parse_module(ParseParams { + specifier: specifier.as_str().to_string(), + source: SourceTextInfo::from_string(source.to_string()), + media_type: deno_ast::MediaType::TypeScript, + capture_tokens: false, + maybe_syntax: None, + }) + .expect("could not parse module"); + let (code, _) = transpile(&module, &EmitOptions::default()) .expect("could not strip types"); assert!(code.contains("_applyDecoratedDescriptor(")); } |