summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2021-06-21 15:13:25 -0400
committerGitHub <noreply@github.com>2021-06-21 15:13:25 -0400
commit2d2b5625e04a466362c9a4afb05e2f559c4fb4b0 (patch)
tree09dbb655a649fee6d05e2179be7a9ac7d104523e
parentf9ff981daf6931a01e1516db0b5714e7a94f145b (diff)
feat(repl): Type stripping in the REPL (#10934)
-rw-r--r--cli/tests/integration_tests.rs84
-rw-r--r--cli/tools/repl.rs107
2 files changed, 157 insertions, 34 deletions
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index c50d80323..081ea40e5 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -2022,9 +2022,9 @@ mod integration {
let mut output = String::new();
master.read_to_string(&mut output).unwrap();
- assert!(output.contains("Unexpected token ')'"));
- assert!(output.contains("Unexpected token ']'"));
- assert!(output.contains("Unexpected token '}'"));
+ assert!(output.contains("Unexpected token `)`"));
+ assert!(output.contains("Unexpected token `]`"));
+ assert!(output.contains("Unexpected token `}`"));
fork.wait().unwrap();
} else {
@@ -2232,6 +2232,59 @@ mod integration {
}
#[test]
+ fn typescript() {
+ let (out, err) = util::run_and_collect_output(
+ true,
+ "repl",
+ Some(vec![
+ "function add(a: number, b: number) { return a + b }",
+ "const result: number = add(1, 2) as number;",
+ "result",
+ ]),
+ Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
+ false,
+ );
+ assert!(out.ends_with("undefined\nundefined\n3\n"));
+ assert!(err.is_empty());
+ }
+
+ #[test]
+ fn typescript_declarations() {
+ let (out, err) = util::run_and_collect_output(
+ true,
+ "repl",
+ Some(vec![
+ "namespace Test { export enum Values { A, B, C } }",
+ "Test.Values.A",
+ "Test.Values.C",
+ "interface MyInterface { prop: string; }",
+ "type MyTypeAlias = string;",
+ ]),
+ Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
+ false,
+ );
+ assert!(out.ends_with("undefined\n0\n2\nundefined\nundefined\n"));
+ assert!(err.is_empty());
+ }
+
+ #[test]
+ fn typescript_decorators() {
+ let (out, err) = util::run_and_collect_output(
+ true,
+ "repl",
+ Some(vec![
+ "function dec(target) { target.prototype.test = () => 2; }",
+ "@dec class Test {}",
+ "new Test().test()",
+ ]),
+ Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
+ false,
+ );
+ assert!(out.ends_with("undefined\nundefined\n2\n"));
+ assert!(err.is_empty());
+ }
+
+ #[test]
fn eof() {
let (out, err) = util::run_and_collect_output(
true,
@@ -2362,11 +2415,30 @@ mod integration {
let (out, err) = util::run_and_collect_output(
true,
"repl",
- Some(vec!["syntax error"]),
- None,
+ Some(vec![
+ "syntax error",
+ "2", // ensure it keeps accepting input after
+ ]),
+ Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
+ false,
+ );
+ assert!(
+ out.ends_with("parse error: Expected ';', '}' or <eof> at 1:7\n2\n")
+ );
+ assert!(err.is_empty());
+ }
+
+ #[test]
+ fn syntax_error_jsx() {
+ // JSX is not supported in the REPL
+ let (out, err) = util::run_and_collect_output(
+ true,
+ "repl",
+ Some(vec!["const element = <div />;"]),
+ Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
false,
);
- assert!(out.contains("Unexpected identifier"));
+ assert!(out.contains("Unexpected token `>`"));
assert!(err.is_empty());
}
diff --git a/cli/tools/repl.rs b/cli/tools/repl.rs
index 67e68c0cd..069d3e417 100644
--- a/cli/tools/repl.rs
+++ b/cli/tools/repl.rs
@@ -1,6 +1,8 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ast;
+use crate::ast::Diagnostic;
+use crate::ast::ImportsNotUsedAsValues;
use crate::ast::TokenOrComment;
use crate::colors;
use crate::media_type::MediaType;
@@ -187,7 +189,7 @@ impl Validator for EditorHelper {
let mut stack: Vec<Token> = Vec::new();
let mut in_template = false;
- for item in ast::lex("", ctx.input(), &MediaType::JavaScript) {
+ for item in ast::lex("", ctx.input(), &MediaType::TypeScript) {
if let TokenOrComment::Token(token) = item.inner {
match token {
Token::BackQuote => in_template = !in_template,
@@ -247,7 +249,7 @@ impl Highlighter for EditorHelper {
fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
let mut out_line = String::from(line);
- for item in ast::lex("", line, &MediaType::JavaScript) {
+ for item in ast::lex("", line, &MediaType::TypeScript) {
// Adding color adds more bytes to the string,
// so an offset is needed to stop spans falling out of sync.
let offset = out_line.len() - line.len();
@@ -439,7 +441,48 @@ impl ReplSession {
self.worker.run_event_loop(false).await
}
- pub async fn evaluate_line(&mut self, line: &str) -> Result<Value, AnyError> {
+ pub async fn evaluate_line_and_get_output(
+ &mut self,
+ line: &str,
+ ) -> Result<String, AnyError> {
+ match self.evaluate_line_with_object_wrapping(line).await {
+ Ok(evaluate_response) => {
+ let evaluate_result = evaluate_response.get("result").unwrap();
+ let evaluate_exception_details =
+ evaluate_response.get("exceptionDetails");
+
+ if evaluate_exception_details.is_some() {
+ self.set_last_thrown_error(evaluate_result).await?;
+ } else {
+ self.set_last_eval_result(evaluate_result).await?;
+ }
+
+ let value = self.get_eval_value(evaluate_result).await?;
+ Ok(match evaluate_exception_details {
+ Some(_) => format!("Uncaught {}", value),
+ None => value,
+ })
+ }
+ Err(err) => {
+ // handle a parsing diagnostic
+ match err.downcast_ref::<Diagnostic>() {
+ Some(diagnostic) => Ok(format!(
+ "{}: {} at {}:{}",
+ colors::red("parse error"),
+ diagnostic.message,
+ diagnostic.location.line,
+ diagnostic.location.col
+ )),
+ None => Err(err),
+ }
+ }
+ }
+ }
+
+ async fn evaluate_line_with_object_wrapping(
+ &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
// to match the behavior found in a typical prompt including browser developer tools.
@@ -451,9 +494,7 @@ impl ReplSession {
line.to_string()
};
- let evaluate_response = self
- .evaluate_expression(&format!("'use strict'; void 0;\n{}", &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.
@@ -461,9 +502,7 @@ impl ReplSession {
if evaluate_response.get("exceptionDetails").is_some()
&& wrapped_line != line
{
- self
- .evaluate_expression(&format!("'use strict'; void 0;\n{}", &line))
- .await?
+ self.evaluate_ts_expression(&line).await?
} else {
evaluate_response
};
@@ -471,7 +510,7 @@ impl ReplSession {
Ok(evaluate_response)
}
- pub async fn set_last_thrown_error(
+ async fn set_last_thrown_error(
&mut self,
error: &Value,
) -> Result<(), AnyError> {
@@ -488,7 +527,7 @@ impl ReplSession {
Ok(())
}
- pub async fn set_last_eval_result(
+ async fn set_last_eval_result(
&mut self,
evaluate_result: &Value,
) -> Result<(), AnyError> {
@@ -529,6 +568,34 @@ impl ReplSession {
Ok(value.to_string())
}
+ async fn evaluate_ts_expression(
+ &mut self,
+ expression: &str,
+ ) -> Result<Value, AnyError> {
+ let parsed_module =
+ crate::ast::parse("repl.ts", &expression, &crate::MediaType::TypeScript)?;
+
+ let transpiled_src = parsed_module
+ .transpile(&crate::ast::EmitOptions {
+ emit_metadata: false,
+ source_map: false,
+ inline_source_map: false,
+ imports_not_used_as_values: ImportsNotUsedAsValues::Preserve,
+ // JSX is not supported in the REPL
+ transform_jsx: false,
+ jsx_factory: "React.createElement".into(),
+ jsx_fragment_factory: "React.Fragment".into(),
+ })?
+ .0;
+
+ self
+ .evaluate_expression(&format!(
+ "'use strict'; void 0;\n{}",
+ transpiled_src
+ ))
+ .await
+ }
+
async fn evaluate_expression(
&mut self,
expression: &str,
@@ -615,7 +682,7 @@ pub async fn run(
.await;
match line {
Ok(line) => {
- let evaluate_response = repl_session.evaluate_line(&line).await?;
+ let output = repl_session.evaluate_line_and_get_output(&line).await?;
// We check for close and break here instead of making it a loop condition to get
// consistent behavior in when the user evaluates a call to close().
@@ -623,22 +690,6 @@ pub async fn run(
break;
}
- let evaluate_result = evaluate_response.get("result").unwrap();
- let evaluate_exception_details =
- evaluate_response.get("exceptionDetails");
-
- if evaluate_exception_details.is_some() {
- repl_session.set_last_thrown_error(evaluate_result).await?;
- } else {
- repl_session.set_last_eval_result(evaluate_result).await?;
- }
-
- let value = repl_session.get_eval_value(evaluate_result).await?;
- let output = match evaluate_exception_details {
- Some(_) => format!("Uncaught {}", value),
- None => value,
- };
-
println!("{}", output);
editor.add_history_entry(line);