Skip to content

Commit

Permalink
feat(lsp): report schema validation errors
Browse files Browse the repository at this point in the history
Extend the diagnostic capabilities by reporting validation errors from
`stef-compiler` as well.
  • Loading branch information
dnaka91 committed Dec 5, 2023
1 parent 02d1d40 commit ca1f300
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 25 deletions.
13 changes: 8 additions & 5 deletions crates/stef-compiler/src/validate/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ pub enum InvalidGenericType {
pub struct DuplicateGenericName {
/// Name of the parameter.
pub name: String,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Defined but unused type parameter.
Expand All @@ -37,8 +39,9 @@ pub struct DuplicateGenericName {
pub struct UnusedGeneric {
/// Name of the parameter.
pub name: String,
/// Source location of the declaration.
#[label("declared here")]
definition: Range<usize>,
pub declared: Range<usize>,
}

/// Ensure all generics in a struct are unique and used.
Expand All @@ -57,7 +60,7 @@ pub fn validate_struct_generics(value: &Struct<'_>) -> Result<(), InvalidGeneric
unvisited.into_iter().next().map_or(Ok(()), |(name, span)| {
Err(UnusedGeneric {
name: name.to_owned(),
definition: span.into(),
declared: span.into(),
}
.into())
})
Expand All @@ -81,7 +84,7 @@ pub fn validate_enum_generics(value: &Enum<'_>) -> Result<(), InvalidGenericType
unvisited.into_iter().next().map_or(Ok(()), |(name, span)| {
Err(UnusedGeneric {
name: name.to_owned(),
definition: span.into(),
declared: span.into(),
}
.into())
})
Expand Down
18 changes: 12 additions & 6 deletions crates/stef-compiler/src/validate/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ pub struct DuplicateVariantId {
pub name: String,
/// Name of the variant that used the ID for the first time.
pub other_name: String,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Duplicate ID for fields of a struct or enum variant.
Expand All @@ -58,10 +60,12 @@ pub struct DuplicateNamedFieldId {
pub name: String,
/// Name of the field that used the ID for the first time.
pub other_name: String,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Duplicate ID for unnamed fields.
Expand All @@ -75,10 +79,12 @@ pub struct DuplicateUnnamedFieldId {
pub position: usize,
/// 1-base position of the field that used the ID for the first time.
pub other_position: usize,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Ensure all IDs inside a struct are unique (which are the field IDs).
Expand Down
18 changes: 12 additions & 6 deletions crates/stef-compiler/src/validate/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ pub enum DuplicateName {
pub struct DuplicateVariantName {
/// Name of the variant.
pub name: String,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Duplicate name for fields of a struct or enum variant.
Expand All @@ -41,10 +43,12 @@ pub struct DuplicateVariantName {
pub struct DuplicateFieldName {
/// Name of the field.
pub name: String,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Duplicate name for definitions inside a module.
Expand All @@ -56,10 +60,12 @@ pub struct DuplicateFieldName {
pub struct DuplicateNameInModule {
/// Name of the declaration.
pub name: String,
/// Source location of the first occurrence.
#[label("first declared here")]
first: Range<usize>,
pub first: Range<usize>,
/// Source location of the duplicate.
#[label("used here again")]
second: Range<usize>,
pub second: Range<usize>,
}

/// Ensure all field names inside a struct are unique.
Expand Down
3 changes: 2 additions & 1 deletion crates/stef-compiler/src/validate/tuples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ use crate::highlight;
pub struct TupleSize {
/// The amount that's not allowed.
pub amount: InvalidTupleAmount,
/// Source location of the declaration.
#[label("declared here")]
declared: Range<usize>,
pub declared: Range<usize>,
}

/// Possible amount of tuple elements that are invalid.
Expand Down
68 changes: 63 additions & 5 deletions crates/stef-lsp/src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
use std::ops::Range;

use stef_compiler::validate;
use stef_parser::{
error::{
ParseAliasCause, ParseAttributeCause, ParseCommentError, ParseConstCause,
ParseDefinitionError, ParseEnumCause, ParseFieldsCause, ParseFieldsError,
ParseGenericsError, ParseIdError, ParseImportCause, ParseLiteralCause, ParseModuleCause,
ParseSchemaCause, ParseStructCause, ParseTypeCause, ParseTypeError,
ParseSchemaCause, ParseSchemaError, ParseStructCause, ParseTypeCause, ParseTypeError,
},
Schema,
};
use tower_lsp::lsp_types::{self as lsp, Diagnostic};
use tower_lsp::lsp_types::{self as lsp, Diagnostic, Url};

use crate::utf16;

pub fn compile(schema: &str) -> std::result::Result<Schema<'_>, Diagnostic> {
stef_parser::Schema::parse(schema, None).map_err(|e| match &e.cause {
pub fn compile(file: Url, schema: &str) -> std::result::Result<Schema<'_>, Diagnostic> {
let parsed = stef_parser::Schema::parse(schema, None)
.map_err(|e| parse_schema_diagnostic(schema, &e))?;

stef_compiler::validate_schema(&parsed)
.map_err(|e| validate_schema_diagnostic(file, schema, e))?;

Ok(parsed)
}

fn parse_schema_diagnostic(schema: &str, e: &ParseSchemaError) -> Diagnostic {
match &e.cause {
ParseSchemaCause::Parser(_) => {
Diagnostic::new_simple(get_range(schema, 0..schema.len()), e.to_string())
}
ParseSchemaCause::Definition(e) => parse_definition_diagnostic(schema, e),
})
}
}

fn parse_definition_diagnostic(schema: &str, e: &ParseDefinitionError) -> Diagnostic {
Expand Down Expand Up @@ -183,6 +194,53 @@ fn parse_id_diagnostic(schema: &str, e: &ParseIdError) -> Diagnostic {
Diagnostic::new_simple(get_range(schema, e.at.clone()), e.to_string())
}

fn validate_schema_diagnostic(file: Url, schema: &str, e: validate::Error) -> Diagnostic {
use validate::{DuplicateFieldId, DuplicateId, DuplicateName, Error, InvalidGenericType};

let (message, first, second) = match e {
Error::DuplicateId(e) => match e {
DuplicateId::EnumVariant(e) => (e.to_string(), e.first, e.second),
DuplicateId::Field(e) => match e {
DuplicateFieldId::Named(e) => (e.to_string(), e.first, e.second),
DuplicateFieldId::Unnamed(e) => (e.to_string(), e.first, e.second),
},
},
Error::DuplicateName(e) => match e {
DuplicateName::EnumVariant(e) => (e.to_string(), e.first, e.second),
DuplicateName::Field(e) => (e.to_string(), e.first, e.second),
DuplicateName::InModule(e) => (e.to_string(), e.first, e.second),
},
Error::InvalidGeneric(e) => match e {
InvalidGenericType::Duplicate(e) => (e.to_string(), e.first, e.second),
InvalidGenericType::Unused(e) => {
let message = e.to_string();
return Diagnostic::new_simple(get_range(schema, e.declared), message);
}
},
Error::TupleSize(e) => {
let message = e.to_string();
return Diagnostic::new_simple(get_range(schema, e.declared), message);
}
};

diagnostic_with_related(
get_range(schema, second),
message,
vec![lsp::DiagnosticRelatedInformation {
location: lsp::Location::new(file, get_range(schema, first)),
message: "first used here".to_owned(),
}],
)
}

fn diagnostic_with_related(
range: lsp::Range,
message: String,
related: Vec<lsp::DiagnosticRelatedInformation>,
) -> Diagnostic {
Diagnostic::new(range, None, None, None, message, Some(related), None)
}

#[allow(clippy::cast_possible_truncation)]
fn get_range(schema: &str, location: Range<usize>) -> lsp::Range {
let start_line = schema[..location.start].lines().count().saturating_sub(1);
Expand Down
4 changes: 2 additions & 2 deletions crates/stef-lsp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl LanguageServer for Backend {

let file = FileBuilder {
content: params.text_document.text,
schema_builder: |schema| compile::compile(schema),
schema_builder: |schema| compile::compile(params.text_document.uri.clone(), schema),
}
.build();

Expand All @@ -199,7 +199,7 @@ impl LanguageServer for Backend {

let file = FileBuilder {
content: params.content_changes.remove(0).text,
schema_builder: |schema| compile::compile(schema),
schema_builder: |schema| compile::compile(params.text_document.uri.clone(), schema),
}
.build();

Expand Down

0 comments on commit ca1f300

Please sign in to comment.