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:
parent
f9ff981daf
commit
2d2b5625e0
2 changed files with 157 additions and 34 deletions
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue