summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/Cargo.toml14
-rw-r--r--cli/ast/mod.rs235
-rw-r--r--cli/http_util.rs3
-rw-r--r--cli/lsp/code_lens.rs19
-rw-r--r--cli/tests/integration/repl_tests.rs12
-rw-r--r--cli/tests/testdata/bundle.test.out16
-rw-r--r--cli/tests/testdata/compiler_api_test.ts10
-rw-r--r--cli/tools/repl/mod.rs61
8 files changed, 251 insertions, 119 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 4c3997332..dd80d6dfb 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -39,26 +39,26 @@ winapi = "=0.3.9"
winres = "=0.1.11"
[dependencies]
-deno_ast = { version = "0.5.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
+deno_ast = { version = "0.7.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { version = "0.110.0", path = "../core" }
-deno_doc = "0.21.0"
-deno_graph = "0.12.0"
-deno_lint = { version = "0.19.0", features = ["docs"] }
+deno_doc = "0.22.0"
+deno_graph = "0.13.0"
+deno_lint = { version = "0.20.0", features = ["docs"] }
deno_runtime = { version = "0.36.0", path = "../runtime" }
atty = "=0.2.14"
base64 = "=0.13.0"
clap = "=2.33.3"
-data-url = "=0.1.0"
+data-url = "=0.1.1"
dissimilar = "=1.0.2"
dprint-plugin-json = "=0.13.2"
dprint-plugin-markdown = "=0.11.3"
-dprint-plugin-typescript = "=0.59.2"
+dprint-plugin-typescript = "=0.60.0"
encoding_rs = "=0.8.29"
env_logger = "=0.8.4"
fancy-regex = "=0.7.1"
http = "=0.2.4"
-import_map = "=0.3.3"
+import_map = "=0.4.0"
jsonc-parser = { version = "=0.17.0", features = ["serde"] }
lazy_static = "=1.4.0"
libc = "=0.2.106"
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,")
);
diff --git a/cli/http_util.rs b/cli/http_util.rs
index c66fa32d3..87ed7d598 100644
--- a/cli/http_util.rs
+++ b/cli/http_util.rs
@@ -149,7 +149,6 @@ mod tests {
use super::*;
use crate::version;
use deno_runtime::deno_fetch::create_http_client;
- use deno_runtime::deno_tls::rustls::RootCertStore;
use std::fs::read;
fn create_test_client() -> Client {
@@ -409,6 +408,8 @@ mod tests {
#[cfg(not(windows))]
#[tokio::test]
async fn test_fetch_with_empty_certificate_store() {
+ use deno_runtime::deno_tls::rustls::RootCertStore;
+
let _http_server_guard = test_util::http_server();
// Relies on external http server with a valid mozilla root CA cert.
let url = Url::parse("https://deno.land").unwrap();
diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs
index 75f46dba8..852f286ab 100644
--- a/cli/lsp/code_lens.rs
+++ b/cli/lsp/code_lens.rs
@@ -9,7 +9,6 @@ use super::tsc::NavigationTree;
use deno_ast::swc::ast;
use deno_ast::swc::common::Span;
-use deno_ast::swc::visit::Node;
use deno_ast::swc::visit::Visit;
use deno_ast::swc::visit::VisitWith;
use deno_ast::ParsedSource;
@@ -129,7 +128,7 @@ impl DenoTestCollector {
}
impl Visit for DenoTestCollector {
- fn visit_call_expr(&mut self, node: &ast::CallExpr, _parent: &dyn Node) {
+ fn visit_call_expr(&mut self, node: &ast::CallExpr) {
if let ast::ExprOrSuper::Expr(callee_expr) = &node.callee {
match callee_expr.as_ref() {
ast::Expr::Ident(ident) => {
@@ -155,7 +154,7 @@ impl Visit for DenoTestCollector {
}
}
- fn visit_var_decl(&mut self, node: &ast::VarDecl, _parent: &dyn Node) {
+ fn visit_var_decl(&mut self, node: &ast::VarDecl) {
for decl in &node.decls {
if let Some(init) = &decl.init {
match init.as_ref() {
@@ -401,12 +400,7 @@ fn collect_test(
if let Some(parsed_source) = parsed_source {
let mut collector =
DenoTestCollector::new(specifier.clone(), parsed_source.clone());
- parsed_source.module().visit_with(
- &ast::Invalid {
- span: deno_ast::swc::common::DUMMY_SP,
- },
- &mut collector,
- );
+ parsed_source.module().visit_with(&mut collector);
return Ok(collector.take());
}
}
@@ -564,12 +558,7 @@ mod tests {
.unwrap();
let mut collector =
DenoTestCollector::new(specifier, parsed_module.clone());
- parsed_module.module().visit_with(
- &ast::Invalid {
- span: deno_ast::swc::common::DUMMY_SP,
- },
- &mut collector,
- );
+ parsed_module.module().visit_with(&mut collector);
assert_eq!(
collector.take(),
vec![
diff --git a/cli/tests/integration/repl_tests.rs b/cli/tests/integration/repl_tests.rs
index 3dd5699c6..18e022cfe 100644
--- a/cli/tests/integration/repl_tests.rs
+++ b/cli/tests/integration/repl_tests.rs
@@ -63,12 +63,14 @@ fn pty_bad_input() {
fn pty_syntax_error_input() {
util::with_pty(&["repl"], |mut console| {
console.write_line("('\\u')");
- console.write_line("('");
+ console.write_line("'");
+ console.write_line("[{'a'}];");
console.write_line("close();");
let output = console.read_all_output();
+ assert!(output.contains("Expected 4 hex characters"));
assert!(output.contains("Unterminated string constant"));
- assert!(output.contains("Unexpected eof"));
+ assert!(output.contains("Expected a semicolon"));
});
}
@@ -267,7 +269,11 @@ fn typescript_declarations() {
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
- assert!(out.ends_with("undefined\n0\n2\nundefined\nundefined\n"));
+ let expected_end_text = "undefined\n0\n2\nundefined\nundefined\n";
+ assert_eq!(
+ &out[out.len() - expected_end_text.len()..],
+ expected_end_text
+ );
assert!(err.is_empty());
}
diff --git a/cli/tests/testdata/bundle.test.out b/cli/tests/testdata/bundle.test.out
index 030c09295..6b1c109d3 100644
--- a/cli/tests/testdata/bundle.test.out
+++ b/cli/tests/testdata/bundle.test.out
@@ -8,20 +8,20 @@ function returnsFoo() {
function printHello2() {
printHello();
}
-function returnsHi1() {
+function returnsHi() {
return "Hi";
}
-function returnsFoo21() {
+function returnsFoo2() {
return returnsFoo();
}
-function printHello31() {
+function printHello3() {
printHello2();
}
-function throwsError1() {
+function throwsError() {
throw Error("exception from mod1");
}
-export { returnsHi1 as returnsHi };
-export { returnsFoo21 as returnsFoo2 };
-export { printHello31 as printHello3 };
-export { throwsError1 as throwsError };
+export { returnsHi as returnsHi };
+export { returnsFoo2 as returnsFoo2 };
+export { printHello3 as printHello3 };
+export { throwsError as throwsError };
diff --git a/cli/tests/testdata/compiler_api_test.ts b/cli/tests/testdata/compiler_api_test.ts
index 42d6f54eb..b9755c29a 100644
--- a/cli/tests/testdata/compiler_api_test.ts
+++ b/cli/tests/testdata/compiler_api_test.ts
@@ -2,8 +2,8 @@
import {
assert,
assertEquals,
+ assertRejects,
assertStringIncludes,
- assertThrowsAsync,
} from "../../../test_util/std/testing/asserts.ts";
Deno.test({
@@ -268,7 +268,7 @@ Deno.test({
Object.keys(files).sort(),
["deno:///bundle.js", "deno:///bundle.js.map"].sort(),
);
- assert(files["deno:///bundle.js"].includes(`const bar1 = "bar"`));
+ assert(files["deno:///bundle.js"].includes(`const bar = "bar"`));
},
});
@@ -312,7 +312,7 @@ Deno.test({
Object.keys(files).sort(),
["deno:///bundle.js.map", "deno:///bundle.js"].sort(),
);
- assert(files["deno:///bundle.js"].includes(`const bar1 = "bar"`));
+ assert(files["deno:///bundle.js"].includes(`const bar = "bar"`));
},
});
@@ -430,7 +430,7 @@ Deno.test({
Deno.test({
name: `Deno.emit() - throws descriptive error when unable to load import map`,
async fn() {
- await assertThrowsAsync(
+ await assertRejects(
async () => {
await Deno.emit("/a.ts", {
bundle: "classic",
@@ -566,7 +566,7 @@ Deno.test({
{
sources: {
"file:///a.tsx": `/** @jsxImportSource https://example.com/jsx */
-
+
export function App() {
return (
<div><></></div>
diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs
index 925ede654..047b477cf 100644
--- a/cli/tools/repl/mod.rs
+++ b/cli/tools/repl/mod.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ast::transpile;
+use crate::ast::Diagnostics;
use crate::ast::ImportsNotUsedAsValues;
use crate::colors;
use crate::proc_state::ProcState;
@@ -507,6 +508,16 @@ impl ReplSession {
&mut self,
line: &str,
) -> Result<EvaluationOutput, AnyError> {
+ fn format_diagnostic(diagnostic: &deno_ast::Diagnostic) -> String {
+ format!(
+ "{}: {} at {}:{}",
+ colors::red("parse error"),
+ diagnostic.message(),
+ diagnostic.display_position.line_number,
+ diagnostic.display_position.column_number,
+ )
+ }
+
match self.evaluate_line_with_object_wrapping(line).await {
Ok(evaluate_response) => {
let evaluate_result = evaluate_response.get("result").unwrap();
@@ -528,14 +539,20 @@ impl ReplSession {
Err(err) => {
// handle a parsing diagnostic
match err.downcast_ref::<deno_ast::Diagnostic>() {
- Some(diagnostic) => Ok(EvaluationOutput::Error(format!(
- "{}: {} at {}:{}",
- colors::red("parse error"),
- diagnostic.message(),
- diagnostic.display_position.line_number,
- diagnostic.display_position.column_number,
- ))),
- None => Err(err),
+ Some(diagnostic) => {
+ Ok(EvaluationOutput::Error(format_diagnostic(diagnostic)))
+ }
+ None => match err.downcast_ref::<Diagnostics>() {
+ Some(diagnostics) => Ok(EvaluationOutput::Error(
+ diagnostics
+ .0
+ .iter()
+ .map(format_diagnostic)
+ .collect::<Vec<_>>()
+ .join("\n\n"),
+ )),
+ None => Err(err),
+ },
}
}
}
@@ -545,8 +562,8 @@ impl ReplSession {
&mut self,
line: &str,
) -> Result<Value, AnyError> {
- // It is a bit unexpected that { "foo": "bar" } is interpreted as a block
- // statement rather than an object literal so we interpret it as an expression statement
+ // Expressions like { "foo": "bar" } are interpreted as block expressions at the
+ // statement level rather than an object literal so we interpret it as an expression statement
// to match the behavior found in a typical prompt including browser developer tools.
let wrapped_line = if line.trim_start().starts_with('{')
&& !line.trim_end().ends_with(';')
@@ -556,20 +573,22 @@ impl ReplSession {
line.to_string()
};
- let evaluate_response = self.evaluate_ts_expression(&wrapped_line).await?;
+ let evaluate_response = self.evaluate_ts_expression(&wrapped_line).await;
// If that fails, we retry it without wrapping in parens letting the error bubble up to the
// user if it is still an error.
- let evaluate_response =
- if evaluate_response.get("exceptionDetails").is_some()
- && wrapped_line != line
- {
- self.evaluate_ts_expression(line).await?
- } else {
- evaluate_response
- };
-
- Ok(evaluate_response)
+ if wrapped_line != line
+ && (evaluate_response.is_err()
+ || evaluate_response
+ .as_ref()
+ .unwrap()
+ .get("exceptionDetails")
+ .is_some())
+ {
+ self.evaluate_ts_expression(line).await
+ } else {
+ evaluate_response
+ }
}
async fn set_last_thrown_error(