summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-01-16 09:45:06 -0500
committerGitHub <noreply@github.com>2023-01-16 09:45:06 -0500
commit9d3483d4eb730cf9a8db4cb7d96dc5381518e92e (patch)
tree87a6ed677a8c391be5711d257a4b4567b1ee4797
parentdf4d0c55c09a1dc6f227b0bce399202160693445 (diff)
fix(repl): improve validator to mark more code as incomplete (#17443)
Closes #17442
-rw-r--r--cli/tools/repl/editor.rs133
1 files changed, 85 insertions, 48 deletions
diff --git a/cli/tools/repl/editor.rs b/cli/tools/repl/editor.rs
index 9957d1eaf..f8302367a 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<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 {
@@ -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(_)));
+ }
+}