diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2021-12-08 19:12:14 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-08 19:12:14 -0500 |
commit | 1507b8c9843262d6514ed61fdba115671dfb7bfe (patch) | |
tree | 963e47cf71c5f4bdd457308c3f82becc106c9d27 /cli/ast/mod.rs | |
parent | 318f48f9adc486e95dcc5f86fd0a1848c6df51f6 (diff) |
fix: upgrade swc fixing many bundling and `--no-check` bugs (#13025)
Diffstat (limited to 'cli/ast/mod.rs')
-rw-r--r-- | cli/ast/mod.rs | 235 |
1 files changed, 176 insertions, 59 deletions
diff --git a/cli/ast/mod.rs b/cli/ast/mod.rs index ac3dd5887..464c89257 100644 --- a/cli/ast/mod.rs +++ b/cli/ast/mod.rs @@ -17,6 +17,8 @@ use deno_ast::swc::common::Globals; use deno_ast::swc::common::Mark; use deno_ast::swc::common::SourceMap; use deno_ast::swc::common::Spanned; +use deno_ast::swc::parser::error::Error as SwcError; +use deno_ast::swc::parser::error::SyntaxError; use deno_ast::swc::parser::lexer::Lexer; use deno_ast::swc::parser::StringInput; use deno_ast::swc::transforms::fixer; @@ -38,6 +40,7 @@ use deno_core::resolve_url_or_path; use deno_core::serde_json; use deno_core::ModuleSpecifier; use std::cell::RefCell; +use std::fmt; use std::rc::Rc; mod bundle_hook; @@ -103,6 +106,25 @@ impl std::fmt::Display for Location { } } +#[derive(Debug)] +pub struct Diagnostics(pub Vec<Diagnostic>); + +impl std::error::Error for Diagnostics {} + +impl fmt::Display for Diagnostics { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, diagnostic) in self.0.iter().enumerate() { + if i > 0 { + write!(f, "\n\n")?; + } + + write!(f, "{}", diagnostic)? + } + + Ok(()) + } +} + #[derive(Debug, Clone)] pub enum ImportsNotUsedAsValues { Remove, @@ -259,6 +281,7 @@ pub fn transpile( parsed_source: &ParsedSource, options: &EmitOptions, ) -> Result<(String, Option<String>), AnyError> { + ensure_no_fatal_diagnostics(parsed_source.diagnostics().iter())?; let program: Program = (*parsed_source.program()).clone(); let source_map = Rc::new(SourceMap::default()); let source_map_config = SourceMapConfig { @@ -333,20 +356,18 @@ pub fn transpile_module( let input = StringInput::from(&*source_file); let comments = SingleThreadedComments::default(); let syntax = get_syntax(media_type); - let lexer = Lexer::new(syntax, deno_ast::TARGET, input, Some(&comments)); + let lexer = Lexer::new(syntax, deno_ast::ES_VERSION, 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 { - specifier: specifier.to_string(), - span: err.span(), - display_position: LineAndColumnDisplay { - line_number: location.line, - column_number: location.col_display + 1, - }, - kind: err.into_kind(), - } - })?; + let module = parser + .parse_module() + .map_err(|e| swc_err_to_diagnostic(&cm, specifier, e))?; + let diagnostics = parser + .take_errors() + .into_iter() + .map(|e| swc_err_to_diagnostic(&cm, specifier, e)) + .collect::<Vec<_>>(); + + ensure_no_fatal_diagnostics(diagnostics.iter())?; let top_level_mark = Mark::fresh(Mark::root()); let program = fold_program( @@ -396,28 +417,25 @@ fn fold_program( comments: &SingleThreadedComments, top_level_mark: Mark, ) -> Result<Program, AnyError> { - let jsx_pass = chain!( - resolver_with_mark(top_level_mark), - 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, - runtime: if options.jsx_automatic { - Some(react::Runtime::Automatic) - } else { - None - }, - development: options.jsx_development, - import_source: options.jsx_import_source.clone().unwrap_or_default(), - ..Default::default() + 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, + runtime: if options.jsx_automatic { + Some(react::Runtime::Automatic) + } else { + None }, - top_level_mark, - ), + development: options.jsx_development, + import_source: options.jsx_import_source.clone().unwrap_or_default(), + ..Default::default() + }, + top_level_mark, ); let mut passes = chain!( Optional::new(transforms::DownlevelImportsFolder, options.repl_imports), @@ -427,10 +445,12 @@ fn fold_program( emit_metadata: options.emit_metadata }), helpers::inject_helpers(), + resolver_with_mark(top_level_mark), Optional::new( - typescript::strip::strip_with_config(strip_config_from_emit_options( - options - )), + typescript::strip::strip_with_config( + strip_config_from_emit_options(options), + top_level_mark + ), !options.transform_jsx ), Optional::new( @@ -457,18 +477,32 @@ fn fold_program( }); let diagnostics = diagnostics_cell.borrow(); - if let Some(diagnostic) = diagnostics.iter().find(|d| is_fatal_diagnostic(d)) - { + ensure_no_fatal_swc_diagnostics(&source_map, diagnostics.iter())?; + Ok(result) +} + +fn ensure_no_fatal_swc_diagnostics<'a>( + source_map: &SourceMap, + diagnostics: impl Iterator<Item = &'a SwcDiagnostic>, +) -> Result<(), AnyError> { + let fatal_diagnostics = diagnostics + .filter(|d| is_fatal_swc_diagnostic(d)) + .collect::<Vec<_>>(); + if !fatal_diagnostics.is_empty() { Err(anyhow!( "{}", - format_swc_diagnostic(&source_map, diagnostic) + fatal_diagnostics + .iter() + .map(|d| format_swc_diagnostic(source_map, d)) + .collect::<Vec<_>>() + .join("\n\n") )) } else { - Ok(result) + Ok(()) } } -fn is_fatal_diagnostic(diagnostic: &SwcDiagnostic) -> bool { +fn is_fatal_swc_diagnostic(diagnostic: &SwcDiagnostic) -> bool { use deno_ast::swc::common::errors::Level; match diagnostic.level { Level::Bug @@ -500,6 +534,51 @@ fn format_swc_diagnostic( } } +fn swc_err_to_diagnostic( + source_map: &SourceMap, + specifier: &ModuleSpecifier, + err: SwcError, +) -> Diagnostic { + let location = source_map.lookup_char_pos(err.span().lo); + Diagnostic { + specifier: specifier.to_string(), + span: err.span(), + display_position: LineAndColumnDisplay { + line_number: location.line, + column_number: location.col_display + 1, + }, + kind: err.into_kind(), + } +} + +fn ensure_no_fatal_diagnostics<'a>( + diagnostics: impl Iterator<Item = &'a Diagnostic>, +) -> Result<(), Diagnostics> { + let fatal_diagnostics = diagnostics + .filter(|d| is_fatal_syntax_error(&d.kind)) + .map(ToOwned::to_owned) + .collect::<Vec<_>>(); + if !fatal_diagnostics.is_empty() { + Err(Diagnostics(fatal_diagnostics)) + } else { + Ok(()) + } +} + +fn is_fatal_syntax_error(error_kind: &SyntaxError) -> bool { + matches!( + error_kind, + // expected identifier + SyntaxError::TS1003 | + // expected semi-colon + SyntaxError::TS1005 | + // expected expression + SyntaxError::TS1109 | + // unterminated string literal + SyntaxError::UnterminatedStrLit + ) +} + #[cfg(test)] mod tests { use super::*; @@ -507,28 +586,37 @@ mod tests { use deno_ast::ParseParams; use deno_ast::SourceTextInfo; + use pretty_assertions::assert_eq; + #[test] fn test_transpile() { let specifier = resolve_url_or_path("https://deno.land/x/mod.ts") .expect("could not resolve specifier"); let source = r#" - enum D { - A, - B, - C, - } +enum D { + A, + B, +} - export class A { - private b: string; - protected c: number = 1; - e: "foo"; - constructor (public d = D.A) { - const e = "foo" as const; - this.e = e; - } - } +namespace N { + export enum D { + A = "value" + } + export const Value = 5; +} + +export class A { + private b: string; + protected c: number = 1; + e: "foo"; + constructor (public d = D.A) { + const e = "foo" as const; + this.e = e; + console.log(N.Value); + } +} "#; - let module = parse_module(ParseParams { + let module = deno_ast::parse_module(ParseParams { specifier: specifier.as_str().to_string(), source: SourceTextInfo::from_string(source.to_string()), media_type: deno_ast::MediaType::TypeScript, @@ -536,10 +624,39 @@ mod tests { maybe_syntax: None, scope_analysis: false, }) - .expect("could not parse module"); + .unwrap(); let (code, maybe_map) = transpile(&module, &EmitOptions::default()) .expect("could not strip types"); - assert!(code.starts_with("var D;\n(function(D) {\n")); + let expected_text = r#"var D; +(function(D) { + D[D["A"] = 0] = "A"; + D[D["B"] = 1] = "B"; +})(D || (D = { +})); +var N; +(function(N1) { + let D; + (function(D) { + D["A"] = "value"; + })(D = N1.D || (N1.D = { + })); + N1.Value = 5; +})(N || (N = { +})); +export class A { + d; + b; + c = 1; + e; + constructor(d = D.A){ + this.d = d; + const e = "foo"; + this.e = e; + console.log(N.Value); + } +} +"#; + assert_eq!(&code[..expected_text.len()], expected_text); assert!( code.contains("\n//# sourceMappingURL=data:application/json;base64,") ); |