1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 23:34:47 -05:00

feat(repl): Type stripping in the REPL (#10934)

This commit is contained in:
David Sherret 2021-06-21 15:13:25 -04:00 committed by GitHub
parent f9ff981daf
commit 2d2b5625e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 157 additions and 34 deletions

View file

@ -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 {
@ -2231,6 +2231,59 @@ mod integration {
assert!(err.is_empty());
}
#[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(
@ -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.contains("Unexpected identifier"));
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 token `>`"));
assert!(err.is_empty());
}

View file

@ -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);