Skip to content

Commit

Permalink
fix(repl): improve validator to mark more code as incomplete (denolan…
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Jan 16, 2023
1 parent df4d0c5 commit 9d3483d
Showing 1 changed file with 85 additions and 48 deletions.
133 changes: 85 additions & 48 deletions cli/tools/repl/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,64 +235,80 @@ impl Validator for EditorHelper {
&self,
ctx: &mut ValidationContext,
) -> Result<ValidationResult, ReadlineError> {
let mut stack: Vec<Token> = Vec::new();
let mut in_template = false;

for item in deno_ast::lex(ctx.input(), deno_ast::MediaType::TypeScript) {
if let deno_ast::TokenOrComment::Token(token) = item.inner {
match token {
Token::BinOp(BinOpToken::Div)
| Token::AssignOp(AssignOp::DivAssign) => {
// it's too complicated to write code to detect regular expression literals
// which are no longer tokenized, so if a `/` or `/=` happens, then we bail
return Ok(ValidationResult::Valid(None));
Ok(validate(ctx.input()))
}
}

fn validate(input: &str) -> ValidationResult {
let line_info = text_lines::TextLines::new(input);
let mut stack: Vec<Token> = Vec::new();
let mut in_template = false;
let mut div_token_count_on_current_line = 0;
let mut last_line_index = 0;

for item in deno_ast::lex(input, deno_ast::MediaType::TypeScript) {
let current_line_index = line_info.line_index(item.range.start);
if current_line_index != last_line_index {
div_token_count_on_current_line = 0;
last_line_index = current_line_index;
}
if let deno_ast::TokenOrComment::Token(token) = item.inner {
match token {
Token::BinOp(BinOpToken::Div)
| Token::AssignOp(AssignOp::DivAssign) => {
// it's too complicated to write code to detect regular expression literals
// which are no longer tokenized, so if a `/` or `/=` happens twice on the same
// line, then we bail
div_token_count_on_current_line += 1;
if div_token_count_on_current_line >= 2 {
return ValidationResult::Valid(None);
}
Token::BackQuote => in_template = !in_template,
Token::LParen
| Token::LBracket
| Token::LBrace
| Token::DollarLBrace => stack.push(token),
Token::RParen | Token::RBracket | Token::RBrace => {
match (stack.pop(), token) {
(Some(Token::LParen), Token::RParen)
| (Some(Token::LBracket), Token::RBracket)
| (Some(Token::LBrace), Token::RBrace)
| (Some(Token::DollarLBrace), Token::RBrace) => {}
(Some(left), _) => {
return Ok(ValidationResult::Invalid(Some(format!(
"Mismatched pairs: {:?} is not properly closed",
left
))))
}
(None, _) => {
// While technically invalid when unpaired, it should be V8's task to output error instead.
// Thus marked as valid with no info.
return Ok(ValidationResult::Valid(None));
}
}
Token::BackQuote => in_template = !in_template,
Token::LParen
| Token::LBracket
| Token::LBrace
| Token::DollarLBrace => stack.push(token),
Token::RParen | Token::RBracket | Token::RBrace => {
match (stack.pop(), token) {
(Some(Token::LParen), Token::RParen)
| (Some(Token::LBracket), Token::RBracket)
| (Some(Token::LBrace), Token::RBrace)
| (Some(Token::DollarLBrace), Token::RBrace) => {}
(Some(left), _) => {
return ValidationResult::Invalid(Some(format!(
"Mismatched pairs: {:?} is not properly closed",
left
)))
}
(None, _) => {
// While technically invalid when unpaired, it should be V8's task to output error instead.
// Thus marked as valid with no info.
return ValidationResult::Valid(None);
}
}
Token::Error(error) => {
match error.kind() {
// If there is unterminated template, it continues to read input.
SyntaxError::UnterminatedTpl => {}
_ => {
// If it failed parsing, it should be V8's task to output error instead.
// Thus marked as valid with no info.
return Ok(ValidationResult::Valid(None));
}
}
Token::Error(error) => {
match error.kind() {
// If there is unterminated template, it continues to read input.
SyntaxError::UnterminatedTpl => {}
_ => {
// If it failed parsing, it should be V8's task to output error instead.
// Thus marked as valid with no info.
return ValidationResult::Valid(None);
}
}
_ => {}
}
_ => {}
}
}
}

if !stack.is_empty() || in_template {
return Ok(ValidationResult::Incomplete);
}

Ok(ValidationResult::Valid(None))
if !stack.is_empty() || in_template {
return ValidationResult::Incomplete;
}

ValidationResult::Valid(None)
}

impl Highlighter for EditorHelper {
Expand Down Expand Up @@ -512,3 +528,24 @@ impl ConditionalEventHandler for TabEventHandler {
}
}
}

#[cfg(test)]
mod test {
use rustyline::validate::ValidationResult;

use super::validate;

#[test]
fn validate_only_one_forward_slash_per_line() {
let code = r#"function test(arr){
if( arr.length <= 1) return arr.map(a => a / 2)
let left = test( arr.slice( 0 , arr.length/2 ) )"#;
assert!(matches!(validate(code), ValidationResult::Incomplete));
}

#[test]
fn validate_regex_looking_code() {
let code = r#"/testing/;"#;
assert!(matches!(validate(code), ValidationResult::Valid(_)));
}
}

0 comments on commit 9d3483d

Please sign in to comment.