diff --git a/prisma-fmt/src/code_actions.rs b/prisma-fmt/src/code_actions.rs index 371e791e49cd..a6e07c2f7955 100644 --- a/prisma-fmt/src/code_actions.rs +++ b/prisma-fmt/src/code_actions.rs @@ -208,15 +208,15 @@ fn create_missing_attribute<'a>( } fn range_after_span(schema: &str, span: Span) -> Range { - let start = crate::offset_to_position(span.end - 1, schema); - let end = crate::offset_to_position(span.end, schema); + let start = crate::offset_to_position(span.end() - 1, schema); + let end = crate::offset_to_position(span.end(), schema); Range { start, end } } fn span_to_range(schema: &str, span: Span) -> Range { - let start = crate::offset_to_position(span.start, schema); - let end = crate::offset_to_position(span.end, schema); + let start = crate::offset_to_position(span.start(), schema); + let end = crate::offset_to_position(span.end(), schema); Range { start, end } } diff --git a/prisma-fmt/src/code_actions/multi_schema.rs b/prisma-fmt/src/code_actions/multi_schema.rs index aa5aaad05175..2cb70ea2832b 100644 --- a/prisma-fmt/src/code_actions/multi_schema.rs +++ b/prisma-fmt/src/code_actions/multi_schema.rs @@ -142,7 +142,7 @@ pub(super) fn add_schema_to_schemas( formatted_attribute, true, // todo: update spans so that we can just append to the end of the _inside_ of the array. Instead of needing to re-append the `]` or taking the span end -1 - Span::new(span.start, span.end - 1, psl::parser_database::FileId::ZERO), + Span::new(span.start(), span.end() - 1, psl::parser_database::FileId::ZERO), params, ) } diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index ada79cd7290b..ece8fb20110c 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -230,7 +230,7 @@ pub(crate) fn range_to_span(range: Range, document: &str) -> ast::Span { /// Gives the LSP position right after the given span. pub(crate) fn position_after_span(span: ast::Span, document: &str) -> Position { - offset_to_position(span.end - 1, document) + offset_to_position(span.end() - 1, document) } /// Converts a byte offset to an LSP position, if the given offset diff --git a/prisma-fmt/src/lint.rs b/prisma-fmt/src/lint.rs index a52a8105aff8..1fce019abe3b 100644 --- a/prisma-fmt/src/lint.rs +++ b/prisma-fmt/src/lint.rs @@ -16,8 +16,8 @@ pub(crate) fn run(schema: &str) -> String { .errors() .iter() .map(|err: &DatamodelError| MiniError { - start: err.span().start, - end: err.span().end, + start: err.span().start(), + end: err.span().end(), text: err.message().to_string(), is_warning: false, }) @@ -27,8 +27,8 @@ pub(crate) fn run(schema: &str) -> String { .warnings() .iter() .map(|warn: &DatamodelWarning| MiniError { - start: warn.span().start, - end: warn.span().end, + start: warn.span().start(), + end: warn.span().end(), text: warn.message().to_owned(), is_warning: true, }) diff --git a/psl/diagnostics/Cargo.toml b/psl/diagnostics/Cargo.toml index a9bc863484eb..fc4a9b118c55 100644 --- a/psl/diagnostics/Cargo.toml +++ b/psl/diagnostics/Cargo.toml @@ -3,6 +3,9 @@ name = "diagnostics" version = "0.1.0" edition = "2021" +[features] +full-spans = [] + [dependencies] colored = "2" pest = "2.1.3" diff --git a/psl/diagnostics/src/pretty_print.rs b/psl/diagnostics/src/pretty_print.rs index d8a9dd8c0dab..ef3c38e9eec7 100644 --- a/psl/diagnostics/src/pretty_print.rs +++ b/psl/diagnostics/src/pretty_print.rs @@ -17,8 +17,8 @@ pub(crate) fn pretty_print( description: &str, colorer: &'static dyn DiagnosticColorer, ) -> std::io::Result<()> { - let start_line_number = text[..span.start].matches('\n').count(); - let end_line_number = text[..span.end].matches('\n').count(); + let start_line_number = text[..span.start()].matches('\n').count(); + let end_line_number = text[..span.end()].matches('\n').count(); let file_lines = text.split('\n').collect::>(); let chars_in_line_before: usize = file_lines[..start_line_number].iter().map(|l| l.len()).sum(); @@ -27,8 +27,8 @@ pub(crate) fn pretty_print( let line = &file_lines[start_line_number]; - let start_in_line = span.start - chars_in_line_before; - let end_in_line = std::cmp::min(start_in_line + (span.end - span.start), line.len()); + let start_in_line = span.start() - chars_in_line_before; + let end_in_line = std::cmp::min(start_in_line + (span.end() - span.start()), line.len()); let prefix = &line[..start_in_line]; let offending = colorer.primary_color(&line[start_in_line..end_in_line]).bold(); diff --git a/psl/diagnostics/src/span.rs b/psl/diagnostics/src/span.rs index 42110aa29792..ec3d1914d01d 100644 --- a/psl/diagnostics/src/span.rs +++ b/psl/diagnostics/src/span.rs @@ -1,53 +1,9 @@ -/// The stable identifier for a PSL file. -#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq, PartialOrd, Ord)] -pub struct FileId(pub u32); // we can't encapsulate because it would be a circular crate - // dependency between diagnostics and parser-database - -impl FileId { - pub const ZERO: FileId = FileId(0); - pub const MAX: FileId = FileId(u32::MAX); -} - -/// Represents a location in a datamodel's text representation. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Span { - pub start: usize, - pub end: usize, - pub file_id: FileId, -} - -impl Span { - /// Constructor. - pub fn new(start: usize, end: usize, file_id: FileId) -> Span { - Span { start, end, file_id } - } - - /// Creates a new empty span. - pub fn empty() -> Span { - Span { - start: 0, - end: 0, - file_id: FileId::ZERO, - } - } - - /// Is the given position inside the span? (boundaries included) - pub fn contains(&self, position: usize) -> bool { - position >= self.start && position <= self.end - } - - /// Is the given span overlapping with the current span. - pub fn overlaps(self, other: Span) -> bool { - self.contains(other.start) || self.contains(other.end) - } -} - -impl From<(FileId, pest::Span<'_>)> for Span { - fn from((file_id, s): (FileId, pest::Span<'_>)) -> Self { - Span { - start: s.start(), - end: s.end(), - file_id, - } - } -} +#[cfg(feature = "full-spans")] +mod full; +#[cfg(not(feature = "full-spans"))] +mod noop; + +#[cfg(feature = "full-spans")] +pub use full::{FileId, Span}; +#[cfg(not(feature = "full-spans"))] +pub use noop::{FileId, Span}; diff --git a/psl/diagnostics/src/span/full.rs b/psl/diagnostics/src/span/full.rs new file mode 100644 index 000000000000..2d94ab23f604 --- /dev/null +++ b/psl/diagnostics/src/span/full.rs @@ -0,0 +1,71 @@ +/// The stable identifier for a PSL file. +#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq, PartialOrd, Ord)] +pub struct FileId(pub u32); // we can't encapsulate because it would be a circular crate + // dependency between diagnostics and parser-database + +impl FileId { + pub const ZERO: FileId = FileId(0); + pub const MAX: FileId = FileId(u32::MAX); +} + +/// Represents a location in a datamodel's text representation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span { + start: usize, + end: usize, + file_id: FileId, +} + +impl Span { + /// Constructor. + pub fn new(start: usize, end: usize, file_id: FileId) -> Span { + Span { start, end, file_id } + } + + pub fn start(&self) -> usize { + self.start + } + pub fn set_start(&mut self, start: usize) { + self.start = start + } + + pub fn end(&self) -> usize { + self.end + } + pub fn set_end(&mut self, end: usize) { + self.end = end + } + + pub fn file_id(&self) -> FileId { + self.file_id + } + + /// Creates a new empty span. + pub fn empty() -> Span { + Span { + start: 0, + end: 0, + file_id: FileId::ZERO, + } + } + + /// Is the given position inside the span? (boundaries included) + pub fn contains(&self, position: usize) -> bool { + position >= self.start && position <= self.end + } + + /// Is the given span overlapping with the current span. + pub fn overlaps(self, other: Span) -> bool { + self.contains(other.start) || self.contains(other.end) + } +} + +impl From<(FileId, pest::Span<'_>)> for Span { + fn from((file_id, s): (FileId, pest::Span<'_>)) -> Self { + Span { + start: s.start(), + end: s.end(), + file_id, + } + } +} diff --git a/psl/diagnostics/src/span/noop.rs b/psl/diagnostics/src/span/noop.rs new file mode 100644 index 000000000000..4d2519424a36 --- /dev/null +++ b/psl/diagnostics/src/span/noop.rs @@ -0,0 +1,56 @@ +/// The stable identifier for a PSL file. +#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq, PartialOrd, Ord)] +pub struct FileId(pub u32); // we can't encapsulate because it would be a circular crate + // dependency between diagnostics and parser-database + +impl FileId { + pub const ZERO: FileId = FileId(0); + pub const MAX: FileId = FileId(u32::MAX); +} + +/// Represents a location in a datamodel's text representation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span {} + +impl Span { + /// Constructor. + pub fn new(_start: usize, _end: usize, _file_id: FileId) -> Span { + Span {} + } + + pub fn start(&self) -> usize { + 0 + } + + pub fn set_start(&mut self, _start: usize) {} + + pub fn end(&self) -> usize { + 0 + } + pub fn set_end(&mut self, _end: usize) {} + + pub fn file_id(&self) -> FileId { + FileId::ZERO + } + + /// Creates a new empty span. + pub fn empty() -> Span { + Span {} + } + + /// Is the given position inside the span? (boundaries included) + pub fn contains(&self, _position: usize) -> bool { + false + } + + /// Is the given span overlapping with the current span. + pub fn overlaps(self, _other: Span) -> bool { + false + } +} + +impl From<(FileId, pest::Span<'_>)> for Span { + fn from((_file_id, _s): (FileId, pest::Span<'_>)) -> Self { + Span {} + } +} diff --git a/psl/parser-database/src/walkers/model.rs b/psl/parser-database/src/walkers/model.rs index e4290a1a00f7..6c132d6ada16 100644 --- a/psl/parser-database/src/walkers/model.rs +++ b/psl/parser-database/src/walkers/model.rs @@ -213,7 +213,7 @@ impl<'db> ModelWalker<'db> { }; let src = self.db.source(self.id.0); - let start = field.ast_field().span().start; + let start = field.ast_field().span().start(); let mut spaces = 0; @@ -238,7 +238,7 @@ impl<'db> ModelWalker<'db> { }; let src = self.db.source(self.id.0); - let start = field.ast_field().span().end - 2; + let start = field.ast_field().span().end() - 2; match src.chars().nth(start) { Some('\r') => NewlineType::Windows, diff --git a/psl/psl-core/Cargo.toml b/psl/psl-core/Cargo.toml index eb2e59f3d489..a357bd4b662c 100644 --- a/psl/psl-core/Cargo.toml +++ b/psl/psl-core/Cargo.toml @@ -10,6 +10,7 @@ mysql = [] cockroachdb = [] mssql = [] mongodb = [] +full-spans = ["diagnostics/full-spans"] [dependencies] diagnostics = { path = "../diagnostics" } diff --git a/psl/psl-core/src/configuration/datasource.rs b/psl/psl-core/src/configuration/datasource.rs index 2f81674ce745..6483ca5704b0 100644 --- a/psl/psl-core/src/configuration/datasource.rs +++ b/psl/psl-core/src/configuration/datasource.rs @@ -252,7 +252,7 @@ impl Datasource { } pub fn url_defined(&self) -> bool { - self.url_span.end > self.url_span.start + self.url_span.end() > self.url_span.start() } pub fn direct_url_defined(&self) -> bool { diff --git a/psl/psl-core/src/lib.rs b/psl/psl-core/src/lib.rs index 9d1877bd26da..bc3db0cdc8b8 100644 --- a/psl/psl-core/src/lib.rs +++ b/psl/psl-core/src/lib.rs @@ -57,7 +57,7 @@ impl ValidatedSchema { let mut out = Vec::new(); for error in self.diagnostics.errors() { - let (file_name, source, _) = &self.db[error.span().file_id]; + let (file_name, source, _) = &self.db[error.span().file_id()]; error.pretty_print(&mut out, file_name, source.as_str()).unwrap(); } @@ -85,35 +85,35 @@ pub fn validate(file: SourceFile, connectors: ConnectorRegistry<'_>) -> Validate /// The most general API for dealing with Prisma schemas. It accumulates what analysis and /// validation information it can, and returns it along with any error and warning diagnostics. -pub fn validate_multi_file(files: Vec<(String, SourceFile)>, connectors: ConnectorRegistry<'_>) -> ValidatedSchema { - assert!( - !files.is_empty(), - "psl::validate_multi_file() must be called with at least one file" - ); - let mut diagnostics = Diagnostics::new(); - let db = ParserDatabase::new(files, &mut diagnostics); - - // TODO: the bulk of configuration block analysis should be part of ParserDatabase::new(). - let mut configuration = Configuration::default(); - for ast in db.iter_asts() { - let new_config = validate_configuration(ast, &mut diagnostics, connectors); - - configuration.datasources.extend(new_config.datasources.into_iter()); - configuration.generators.extend(new_config.generators.into_iter()); - configuration.warnings.extend(new_config.warnings.into_iter()); - } - - let datasources = &configuration.datasources; - let out = validate::validate(db, datasources, configuration.preview_features(), diagnostics); - - ValidatedSchema { - diagnostics: out.diagnostics, - configuration, - connector: out.connector, - db: out.db, - relation_mode: out.relation_mode, - } -} +// pub fn validate_multi_file(files: Vec<(String, SourceFile)>, connectors: ConnectorRegistry<'_>) -> ValidatedSchema { +// assert!( +// !files.is_empty(), +// "psl::validate_multi_file() must be called with at least one file" +// ); +// let mut diagnostics = Diagnostics::new(); +// let db = ParserDatabase::new(files, &mut diagnostics); + +// // TODO: the bulk of configuration block analysis should be part of ParserDatabase::new(). +// let mut configuration = Configuration::default(); +// for ast in db.iter_asts() { +// let new_config = validate_configuration(ast, &mut diagnostics, connectors); + +// configuration.datasources.extend(new_config.datasources.into_iter()); +// configuration.generators.extend(new_config.generators.into_iter()); +// configuration.warnings.extend(new_config.warnings.into_iter()); +// } + +// let datasources = &configuration.datasources; +// let out = validate::validate(db, datasources, configuration.preview_features(), diagnostics); + +// ValidatedSchema { +// diagnostics: out.diagnostics, +// configuration, +// connector: out.connector, +// db: out.db, +// relation_mode: out.relation_mode, +// } +// } /// Retrieves a Prisma schema without validating it. /// You should only use this method when actually validating the schema is too expensive diff --git a/psl/psl-core/src/reformat.rs b/psl/psl-core/src/reformat.rs index 09d21c731b38..a9d9b7d95171 100644 --- a/psl/psl-core/src/reformat.rs +++ b/psl/psl-core/src/reformat.rs @@ -104,9 +104,9 @@ fn push_inline_relation_missing_arguments( let extra_args = extra_args.join(", "); let (prefix, suffix, position) = if relation_attribute.arguments.arguments.is_empty() { - ("(", ")", relation_attribute.span.end) + ("(", ")", relation_attribute.span.end()) } else { - (", ", "", relation_attribute.span.end - 1) + (", ", "", relation_attribute.span.end() - 1) }; ctx.missing_bits.push(MissingBit { @@ -137,7 +137,7 @@ fn push_missing_relation_attribute(inline_relation: walkers::InlineRelationWalke content.push(')'); ctx.missing_bits.push(MissingBit { - position: after_type(forward.ast_field().field_type.span().end, ctx.original_schema), + position: after_type(forward.ast_field().field_type.span().end(), ctx.original_schema), content, }) } @@ -168,7 +168,7 @@ fn push_missing_relation_fields(inline: walkers::InlineRelationWalker<'_>, ctx: let arity = if inline.is_one_to_one() { "?" } else { "[]" }; ctx.missing_bits.push(MissingBit { - position: inline.referenced_model().ast_model().span().end - 1, + position: inline.referenced_model().ast_model().span().end() - 1, content: format!("{referencing_model_name} {referencing_model_name}{arity} {ignore}\n"), }); } @@ -180,7 +180,7 @@ fn push_missing_relation_fields(inline: walkers::InlineRelationWalker<'_>, ctx: let fields_arg = fields_argument(inline); let references_arg = references_argument(inline); ctx.missing_bits.push(MissingBit { - position: inline.referencing_model().ast_model().span().end - 1, + position: inline.referencing_model().ast_model().span().end() - 1, content: format!("{field_name} {field_type}{arity} @relation({fields_arg}, {references_arg})\n"), }) } @@ -211,11 +211,11 @@ fn push_missing_scalar_fields(inline: walkers::InlineRelationWalker<'_>, ctx: &m let mut attributes: String = String::new(); if let Some((_datasource_name, _type_name, _args, span)) = field.blueprint.raw_native_type() { - attributes.push_str(&ctx.original_schema[span.start..span.end]); + attributes.push_str(&ctx.original_schema[span.start()..span.end()]); } ctx.missing_bits.push(MissingBit { - position: inline.referencing_model().ast_model().span().end - 1, + position: inline.referencing_model().ast_model().span().end() - 1, content: format!("{field_name} {field_type}{arity} {attributes}\n"), }); } diff --git a/psl/psl/Cargo.toml b/psl/psl/Cargo.toml index e230ffe8c443..6abf0c30be6a 100644 --- a/psl/psl/Cargo.toml +++ b/psl/psl/Cargo.toml @@ -10,7 +10,16 @@ mysql = ["psl-core/mysql"] cockroachdb = ["psl-core/cockroachdb"] mssql = ["psl-core/mssql"] mongodb = ["psl-core/mongodb"] -all = ["postgresql", "sqlite", "mysql", "cockroachdb", "mssql", "mongodb"] +full-spans = ["psl-core/full-spans"] +all = [ + "postgresql", + "sqlite", + "mysql", + "cockroachdb", + "mssql", + "mongodb", + "full-spans", +] [dependencies] psl-core = { path = "../psl-core" } diff --git a/psl/psl/src/lib.rs b/psl/psl/src/lib.rs index d1c38eaf4330..e906c5a89ff6 100644 --- a/psl/psl/src/lib.rs +++ b/psl/psl/src/lib.rs @@ -58,8 +58,6 @@ pub fn validate(file: SourceFile) -> ValidatedSchema { pub fn parse_without_validation(file: SourceFile, connector_registry: ConnectorRegistry<'_>) -> ValidatedSchema { psl_core::parse_without_validation(file, connector_registry) } -/// The most general API for dealing with Prisma schemas. It accumulates what analysis and -/// validation information it can, and returns it along with any error and warning diagnostics. -pub fn validate_multi_file(files: Vec<(String, SourceFile)>) -> ValidatedSchema { - psl_core::validate_multi_file(files, builtin_connectors::BUILTIN_CONNECTORS) -} +// pub fn validate_multi_file(files: Vec<(String, SourceFile)>) -> ValidatedSchema { +// psl_core::validate_multi_file(files, builtin_connectors::BUILTIN_CONNECTORS) +// } diff --git a/psl/schema-ast/src/ast/find_at_position.rs b/psl/schema-ast/src/ast/find_at_position.rs index 3cf597ebd2e5..9377a978adea 100644 --- a/psl/schema-ast/src/ast/find_at_position.rs +++ b/psl/schema-ast/src/ast/find_at_position.rs @@ -26,9 +26,9 @@ impl ast::SchemaAst { let top_idx = self.tops.binary_search_by(|top| { let span = top.span(); - if span.start > position { + if span.start() > position { Ordering::Greater - } else if span.end < position { + } else if span.end() < position { Ordering::Less } else { Ordering::Equal @@ -136,16 +136,16 @@ impl<'ast> FieldPosition<'ast> { .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), ) .collect(); - spans.sort_by_key(|(_, span)| span.start); + spans.sort_by_key(|(_, span)| span.start()); let mut arg_name = None; - for (name, _) in spans.iter().take_while(|(_, span)| span.start < position) { + for (name, _) in spans.iter().take_while(|(_, span)| span.start() < position) { arg_name = Some(*name); } // If the cursor is after a trailing comma, we're not in an argument. if let Some(span) = attr.arguments.trailing_comma { - if position > span.start { + if position > span.start() { arg_name = None; } } @@ -184,16 +184,16 @@ impl<'ast> EnumValuePosition<'ast> { .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), ) .collect(); - spans.sort_by_key(|(_, span)| span.start); + spans.sort_by_key(|(_, span)| span.start()); let mut arg_name = None; - for (name, _) in spans.iter().take_while(|(_, span)| span.start < position) { + for (name, _) in spans.iter().take_while(|(_, span)| span.start() < position) { arg_name = Some(*name); } // If the cursor is after a trailing comma, we're not in an argument. if let Some(span) = attr.arguments.trailing_comma { - if position > span.start { + if position > span.start() { arg_name = None; } } @@ -234,16 +234,16 @@ impl<'ast> AttributePosition<'ast> { ) .collect(); - spans.sort_by_key(|(_, span)| span.start); + spans.sort_by_key(|(_, span)| span.start()); let mut arg_name = None; - for (name, _) in spans.iter().take_while(|(_, span)| span.start < position) { + for (name, _) in spans.iter().take_while(|(_, span)| span.start() < position) { arg_name = Some(*name); } // If the cursor is after a trailing comma, we're not in an argument. if let Some(span) = attr.arguments.trailing_comma { - if position > span.start { + if position > span.start() { arg_name = None; } } @@ -289,16 +289,16 @@ impl<'ast> ExpressionPosition<'ast> { ) .collect(); - spans.sort_by_key(|(_, span)| span.start); + spans.sort_by_key(|(_, span)| span.start()); let mut arg_name = None; - for (name, _) in spans.iter().take_while(|(_, span)| span.start < position) { + for (name, _) in spans.iter().take_while(|(_, span)| span.start() < position) { arg_name = Some(*name); } // If the cursor is after a trailing comma, we're not in an argument. if let Some(span) = args.trailing_comma { - if position > span.start { + if position > span.start() { arg_name = None; } } diff --git a/psl/schema-ast/src/parser/parse_expression.rs b/psl/schema-ast/src/parser/parse_expression.rs index f252bbbc41bc..1d0010bf246b 100644 --- a/psl/schema-ast/src/parser/parse_expression.rs +++ b/psl/schema-ast/src/parser/parse_expression.rs @@ -115,8 +115,8 @@ fn parse_string_literal(token: Pair<'_>, diagnostics: &mut Diagnostics, file_id: } (_, c) => { let mut final_span: crate::ast::Span = (file_id, contents.as_span()).into(); - final_span.start += start; - final_span.end = final_span.start + 1 + c.len_utf8(); + final_span.set_start(final_span.start() + start); + final_span.set_end(final_span.start() + 1 + c.len_utf8()); diagnostics.push_error(DatamodelError::new_static( r"Unknown escape sequence. If the value is a windows-style path, `\` must be escaped as `\\`.", final_span, @@ -140,11 +140,12 @@ fn try_parse_unicode_codepoint( file_id: FileId, ) -> (usize, Option) { let unicode_sequence_error = |consumed| { - let span = crate::ast::Span { - start: slice_offset, - end: (slice_offset + slice.len()).min(slice_offset + consumed), + let span = crate::ast::Span::new( + slice_offset, + (slice_offset + slice.len()).min(slice_offset + consumed), file_id, - }; + ); + DatamodelError::new_static("Invalid unicode escape sequence.", span) };