diff --git a/crates/stef-compiler/src/validate/generics.rs b/crates/stef-compiler/src/validate/generics.rs index 5696145..598187c 100644 --- a/crates/stef-compiler/src/validate/generics.rs +++ b/crates/stef-compiler/src/validate/generics.rs @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Defined but unused type parameter. @@ -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, + pub declared: Range, } /// Ensure all generics in a struct are unique and used. @@ -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()) }) @@ -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()) }) diff --git a/crates/stef-compiler/src/validate/ids.rs b/crates/stef-compiler/src/validate/ids.rs index f33116c..9bb6ca8 100644 --- a/crates/stef-compiler/src/validate/ids.rs +++ b/crates/stef-compiler/src/validate/ids.rs @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Duplicate ID for fields of a struct or enum variant. @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Duplicate ID for unnamed fields. @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Ensure all IDs inside a struct are unique (which are the field IDs). diff --git a/crates/stef-compiler/src/validate/names.rs b/crates/stef-compiler/src/validate/names.rs index 6a27cc4..5d5c378 100644 --- a/crates/stef-compiler/src/validate/names.rs +++ b/crates/stef-compiler/src/validate/names.rs @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Duplicate name for fields of a struct or enum variant. @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Duplicate name for definitions inside a module. @@ -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, + pub first: Range, + /// Source location of the duplicate. #[label("used here again")] - second: Range, + pub second: Range, } /// Ensure all field names inside a struct are unique. diff --git a/crates/stef-compiler/src/validate/tuples.rs b/crates/stef-compiler/src/validate/tuples.rs index 9843258..ab78112 100644 --- a/crates/stef-compiler/src/validate/tuples.rs +++ b/crates/stef-compiler/src/validate/tuples.rs @@ -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, + pub declared: Range, } /// Possible amount of tuple elements that are invalid. diff --git a/crates/stef-lsp/src/compile.rs b/crates/stef-lsp/src/compile.rs index 0a45562..9563bd9 100644 --- a/crates/stef-lsp/src/compile.rs +++ b/crates/stef-lsp/src/compile.rs @@ -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, Diagnostic> { - stef_parser::Schema::parse(schema, None).map_err(|e| match &e.cause { +pub fn compile(file: Url, schema: &str) -> std::result::Result, 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 { @@ -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, +) -> Diagnostic { + Diagnostic::new(range, None, None, None, message, Some(related), None) +} + #[allow(clippy::cast_possible_truncation)] fn get_range(schema: &str, location: Range) -> lsp::Range { let start_line = schema[..location.start].lines().count().saturating_sub(1); diff --git a/crates/stef-lsp/src/main.rs b/crates/stef-lsp/src/main.rs index fc5c8c0..a3dff47 100644 --- a/crates/stef-lsp/src/main.rs +++ b/crates/stef-lsp/src/main.rs @@ -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(); @@ -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();