mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17: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();
|
let mut output = String::new();
|
||||||
master.read_to_string(&mut output).unwrap();
|
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();
|
fork.wait().unwrap();
|
||||||
} else {
|
} else {
|
||||||
|
@ -2231,6 +2231,59 @@ mod integration {
|
||||||
assert!(err.is_empty());
|
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]
|
#[test]
|
||||||
fn eof() {
|
fn eof() {
|
||||||
let (out, err) = util::run_and_collect_output(
|
let (out, err) = util::run_and_collect_output(
|
||||||
|
@ -2362,11 +2415,30 @@ mod integration {
|
||||||
let (out, err) = util::run_and_collect_output(
|
let (out, err) = util::run_and_collect_output(
|
||||||
true,
|
true,
|
||||||
"repl",
|
"repl",
|
||||||
Some(vec!["syntax error"]),
|
Some(vec![
|
||||||
None,
|
"syntax error",
|
||||||
|
"2", // ensure it keeps accepting input after
|
||||||
|
]),
|
||||||
|
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
|
||||||
false,
|
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());
|
assert!(err.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use crate::ast;
|
use crate::ast;
|
||||||
|
use crate::ast::Diagnostic;
|
||||||
|
use crate::ast::ImportsNotUsedAsValues;
|
||||||
use crate::ast::TokenOrComment;
|
use crate::ast::TokenOrComment;
|
||||||
use crate::colors;
|
use crate::colors;
|
||||||
use crate::media_type::MediaType;
|
use crate::media_type::MediaType;
|
||||||
|
@ -187,7 +189,7 @@ impl Validator for EditorHelper {
|
||||||
let mut stack: Vec<Token> = Vec::new();
|
let mut stack: Vec<Token> = Vec::new();
|
||||||
let mut in_template = false;
|
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 {
|
if let TokenOrComment::Token(token) = item.inner {
|
||||||
match token {
|
match token {
|
||||||
Token::BackQuote => in_template = !in_template,
|
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> {
|
fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
|
||||||
let mut out_line = String::from(line);
|
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,
|
// Adding color adds more bytes to the string,
|
||||||
// so an offset is needed to stop spans falling out of sync.
|
// so an offset is needed to stop spans falling out of sync.
|
||||||
let offset = out_line.len() - line.len();
|
let offset = out_line.len() - line.len();
|
||||||
|
@ -439,7 +441,48 @@ impl ReplSession {
|
||||||
self.worker.run_event_loop(false).await
|
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
|
// 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
|
// 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.
|
// to match the behavior found in a typical prompt including browser developer tools.
|
||||||
|
@ -451,9 +494,7 @@ impl ReplSession {
|
||||||
line.to_string()
|
line.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let evaluate_response = self
|
let evaluate_response = self.evaluate_ts_expression(&wrapped_line).await?;
|
||||||
.evaluate_expression(&format!("'use strict'; void 0;\n{}", &wrapped_line))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// If that fails, we retry it without wrapping in parens letting the error bubble up to the
|
// If that fails, we retry it without wrapping in parens letting the error bubble up to the
|
||||||
// user if it is still an error.
|
// user if it is still an error.
|
||||||
|
@ -461,9 +502,7 @@ impl ReplSession {
|
||||||
if evaluate_response.get("exceptionDetails").is_some()
|
if evaluate_response.get("exceptionDetails").is_some()
|
||||||
&& wrapped_line != line
|
&& wrapped_line != line
|
||||||
{
|
{
|
||||||
self
|
self.evaluate_ts_expression(&line).await?
|
||||||
.evaluate_expression(&format!("'use strict'; void 0;\n{}", &line))
|
|
||||||
.await?
|
|
||||||
} else {
|
} else {
|
||||||
evaluate_response
|
evaluate_response
|
||||||
};
|
};
|
||||||
|
@ -471,7 +510,7 @@ impl ReplSession {
|
||||||
Ok(evaluate_response)
|
Ok(evaluate_response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_last_thrown_error(
|
async fn set_last_thrown_error(
|
||||||
&mut self,
|
&mut self,
|
||||||
error: &Value,
|
error: &Value,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -488,7 +527,7 @@ impl ReplSession {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_last_eval_result(
|
async fn set_last_eval_result(
|
||||||
&mut self,
|
&mut self,
|
||||||
evaluate_result: &Value,
|
evaluate_result: &Value,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
@ -529,6 +568,34 @@ impl ReplSession {
|
||||||
Ok(value.to_string())
|
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(
|
async fn evaluate_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
expression: &str,
|
expression: &str,
|
||||||
|
@ -615,7 +682,7 @@ pub async fn run(
|
||||||
.await;
|
.await;
|
||||||
match line {
|
match line {
|
||||||
Ok(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
|
// 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().
|
// consistent behavior in when the user evaluates a call to close().
|
||||||
|
@ -623,22 +690,6 @@ pub async fn run(
|
||||||
break;
|
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);
|
println!("{}", output);
|
||||||
|
|
||||||
editor.add_history_entry(line);
|
editor.add_history_entry(line);
|
||||||
|
|
Loading…
Add table
Reference in a new issue