summaryrefslogtreecommitdiff
path: root/cli/ast/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/ast/mod.rs')
-rw-r--r--cli/ast/mod.rs235
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,")
);