diff --git a/cli/tools/repl/editor.rs b/cli/tools/repl/editor.rs index 9957d1eaf3..f8302367ab 100644 --- a/cli/tools/repl/editor.rs +++ b/cli/tools/repl/editor.rs @@ -235,64 +235,80 @@ impl Validator for EditorHelper { &self, ctx: &mut ValidationContext, ) -> Result { - let mut stack: Vec = Vec::new(); - let mut in_template = false; + Ok(validate(ctx.input())) + } +} - 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)); +fn validate(input: &str) -> ValidationResult { + let line_info = text_lines::TextLines::new(input); + let mut stack: Vec = 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::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::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 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 { @@ -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(_))); + } +}