Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add parse error kind and fix the invalid syntax quick fix code #1365

Merged
merged 1 commit into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions kclvm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions kclvm/error/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ serde = { version = "1.0", features = ["derive"] }
termize = "0.1.1"
indexmap = "1.0"
serde_json = "1.0"
thiserror = "1.0.61"
168 changes: 47 additions & 121 deletions kclvm/error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use diagnostic::Range;
use indexmap::IndexSet;
use kclvm_runtime::PanicInfo;
use std::{any::Any, sync::Arc};
use thiserror::Error;

pub use diagnostic::{Diagnostic, DiagnosticId, Level, Message, Position, Style};
pub use error::*;
Expand Down Expand Up @@ -327,6 +328,28 @@ impl From<PanicInfo> for Diagnostic {
}
}

#[derive(Error, Debug, Clone)]
pub enum ParseErrorMessage {
#[error("invalid token '!', consider using 'not '")]
InvalidTokenNot,
#[error("'else if' here is invalid in KCL, consider using the 'elif' keyword")]
InvalidTokenElseIf,
#[error("unterminated string")]
UnterminatedString,
#[error("unexpected character after line continuation character")]
CharAfterLineContinuationToken,
#[error("the semicolon ';' here is unnecessary, please remove it")]
RedundantSemicolon,
#[error("expected expression, got {0}")]
ExpectExpr(String),
#[error("invalid string interpolation expression: '{0}'")]
InvalidStringInterpolationExpr(String),
#[error("invalid joined string spec without #")]
InvalidJoinedStringSpec,
#[error("invalid joined string")]
InvalidJoinedStringExpr,
}

#[derive(Debug, Clone)]
pub enum ParseError {
UnexpectedToken {
Expand All @@ -335,18 +358,16 @@ pub enum ParseError {
span: Span,
},
Message {
message: ParseErrorMessage,
span: Span,
suggestions: Option<Vec<String>>,
},
String {
message: String,
span: Span,
fix_info: Option<FixInfo>,
},
}

#[derive(Debug, Clone)]
pub struct FixInfo {
pub suggestion: Option<String>,
pub replacement: Option<String>,
}

/// A single string error.
pub struct StringError(pub String);

Expand All @@ -364,11 +385,15 @@ impl ParseError {
}

/// New a message parse error with span.
pub fn message(message: String, span: Span, fix_info: Option<FixInfo>) -> Self {
pub fn message(
message: ParseErrorMessage,
span: Span,
suggestions: Option<Vec<String>>,
) -> Self {
ParseError::Message {
message,
span,
fix_info,
suggestions,
}
}
}
Expand All @@ -379,24 +404,14 @@ impl ParseError {
let span = match self {
ParseError::UnexpectedToken { span, .. } => span,
ParseError::Message { span, .. } => span,
ParseError::String { span, .. } => span,
};
let loc = sess.sm.lookup_char_pos(span.lo());
let pos: Position = loc.into();
let suggestions = match self {
ParseError::Message {
fix_info: Some(ref info),
..
} => Some(vec![
info.suggestion
.clone()
.unwrap_or_else(|| "No suggestion available".to_string()),
info.replacement.clone().unwrap_or_else(|| " ".to_string()),
]),
let start_pos = sess.sm.lookup_char_pos(span.lo()).into();
let end_pos = sess.sm.lookup_char_pos(span.hi()).into();
let suggestions = match &self {
ParseError::Message { suggestions, .. } => suggestions.clone(),
_ => None,
};

let (start_pos, end_pos) = self.generate_modified_range(&self.to_string(), &pos);

Ok(Diagnostic::new_with_code(
Level::Error,
&self.to_string(),
Expand All @@ -406,98 +421,6 @@ impl ParseError {
suggestions,
))
}

fn generate_modified_range(&self, msg: &str, pos: &Position) -> (Position, Position) {
match msg {
"invalid token '!', consider using 'not '" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"'else if' here is invalid in KCL, consider using the 'elif' keyword" => {
let start_column = pos.column.map(|col| col.saturating_sub(5)).unwrap_or(0);
let end_column = pos.column.map(|col| col.saturating_add(2)).unwrap_or(0);
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"error nesting on close paren"
| "mismatched closing delimiter"
| "error nesting on close brace" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"unterminated string" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"unexpected character after line continuation character" => {
let start_column = pos.column.unwrap_or(0);
let end_column = u32::MAX;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column.into()),
..pos.clone()
},
)
}
"the semicolon ';' here is unnecessary, please remove it" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
_ => (pos.clone(), pos.clone()),
}
}
}

impl ToString for ParseError {
Expand All @@ -507,6 +430,7 @@ impl ToString for ParseError {
format!("expected one of {expected:?} got {got}")
}
ParseError::Message { message, .. } => message.to_string(),
ParseError::String { message, .. } => message.to_string(),
}
}
}
Expand All @@ -523,11 +447,13 @@ impl SessionDiagnostic for ParseError {
diag.append_component(Box::new(format!(" {}\n", self.to_string())));
Ok(diag)
}
ParseError::Message {
message,
span,
fix_info: _,
} => {
ParseError::Message { message, span, .. } => {
let code_snippet = CodeSnippet::new(span, Arc::clone(&sess.sm));
diag.append_component(Box::new(code_snippet));
diag.append_component(Box::new(format!(" {message}\n")));
Ok(diag)
}
ParseError::String { message, span } => {
let code_snippet = CodeSnippet::new(span, Arc::clone(&sess.sm));
diag.append_component(Box::new(code_snippet));
diag.append_component(Box::new(format!(" {message}\n")));
Expand Down
Loading
Loading