From 34ace0eb2704183d2c05b60b52fba5c43c13f303 Mon Sep 17 00:00:00 2001 From: Sophie <29753584+Druue@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:15:15 +0200 Subject: [PATCH] tech(fmt/psl): prep-work for further language-server support (#4933) add documentation helper to Top add name helper to FieldType Added more positions: ModelPosition::Name FieldPosition::{Name,Type} SourcePosition::Name GeneratorPosition CompositeTypePosition split out find_at_position added find_at_position doc Added topwalker some clean-up of publicity added indexing for generatorid on schema ast --- prisma-fmt/src/code_actions/relation_mode.rs | 9 +- psl/parser-database/src/lib.rs | 33 +- psl/parser-database/src/names.rs | 10 +- .../src/names/reserved_model_names.rs | 2 +- psl/parser-database/src/walkers.rs | 18 + psl/parser-database/src/walkers/enum.rs | 4 +- psl/parser-database/src/walkers/model.rs | 2 +- psl/parser-database/src/walkers/top.rs | 29 ++ .../src/validate/datasource_loader.rs | 10 +- psl/psl-core/src/validate/generator_loader.rs | 11 +- psl/schema-ast/src/ast.rs | 8 + psl/schema-ast/src/ast/config.rs | 10 +- psl/schema-ast/src/ast/enum.rs | 2 +- psl/schema-ast/src/ast/field.rs | 7 + psl/schema-ast/src/ast/find_at_position.rs | 357 ++---------------- .../src/ast/find_at_position/attribute.rs | 60 +++ .../ast/find_at_position/composite_type.rs | 43 +++ .../src/ast/find_at_position/datasource.rs | 48 +++ .../src/ast/find_at_position/enum.rs | 115 ++++++ .../src/ast/find_at_position/expression.rs | 72 ++++ .../src/ast/find_at_position/field.rs | 84 +++++ .../src/ast/find_at_position/generator.rs | 28 ++ .../src/ast/find_at_position/model.rs | 61 +++ .../src/ast/find_at_position/property.rs | 59 +++ psl/schema-ast/src/ast/source_config.rs | 2 +- psl/schema-ast/src/ast/top.rs | 12 + 26 files changed, 728 insertions(+), 368 deletions(-) create mode 100644 psl/parser-database/src/walkers/top.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/attribute.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/composite_type.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/datasource.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/enum.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/expression.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/field.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/generator.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/model.rs create mode 100644 psl/schema-ast/src/ast/find_at_position/property.rs diff --git a/prisma-fmt/src/code_actions/relation_mode.rs b/prisma-fmt/src/code_actions/relation_mode.rs index 0367e65d7169..53bd556c9cdc 100644 --- a/prisma-fmt/src/code_actions/relation_mode.rs +++ b/prisma-fmt/src/code_actions/relation_mode.rs @@ -1,5 +1,8 @@ use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand}; -use psl::{parser_database::walkers::CompleteInlineRelationWalker, schema_ast::ast::SourceConfig}; +use psl::{ + parser_database::walkers::CompleteInlineRelationWalker, + schema_ast::ast::{SourceConfig, WithIdentifier, WithName}, +}; use super::CodeActionsContext; @@ -8,7 +11,7 @@ pub(crate) fn edit_referential_integrity( context: &CodeActionsContext<'_>, source: &SourceConfig, ) { - let prop = match source.properties.iter().find(|p| p.name.name == "referentialIntegrity") { + let prop = match source.properties.iter().find(|p| p.name() == "referentialIntegrity") { Some(prop) => prop, None => return, }; @@ -21,7 +24,7 @@ pub(crate) fn edit_referential_integrity( context.initiating_file_source(), "relationMode".to_owned(), false, - prop.name.span, + prop.identifier().span, ) else { return; }; diff --git a/psl/parser-database/src/lib.rs b/psl/parser-database/src/lib.rs index e57d23415c97..5764248eff36 100644 --- a/psl/parser-database/src/lib.rs +++ b/psl/parser-database/src/lib.rs @@ -168,11 +168,6 @@ impl ParserDatabase { &self.asts.0.first().unwrap().2 } - /// Iterate all parsed ASTs. - pub fn iter_asts(&self) -> impl Iterator { - self.asts.iter().map(|(_, _, _, ast)| ast) - } - /// Returns file id by name pub fn file_id(&self, file_name: &str) -> Option { self.asts @@ -180,14 +175,9 @@ impl ParserDatabase { .find_map(|(file_id, name, _, _)| if name == file_name { Some(file_id) } else { None }) } - /// Iterate all parsed ASTs, consuming parser database - pub fn into_iter_asts(self) -> impl Iterator { - self.asts.into_iter().map(|(_, _, _, ast)| ast) - } - - /// Iterate all file ids - pub fn iter_file_ids(&self) -> impl Iterator + '_ { - self.asts.iter().map(|(file_id, _, _, _)| file_id) + /// The name of the file. + pub fn file_name(&self, file_id: FileId) -> &str { + self.asts[file_id].0.as_str() } /// A parsed AST. @@ -223,6 +213,16 @@ impl ParserDatabase { self.asts[file_id].1.as_str() } + /// Iterate all parsed ASTs, consuming parser database + pub fn into_iter_asts(self) -> impl Iterator { + self.asts.into_iter().map(|(_, _, _, ast)| ast) + } + + /// Iterate all parsed ASTs. + pub fn iter_asts(&self) -> impl Iterator { + self.asts.iter().map(|(_, _, _, ast)| ast) + } + /// Iterate all source file contents. pub fn iter_sources(&self) -> impl Iterator { self.asts.iter().map(|ast| ast.2.as_str()) @@ -233,11 +233,10 @@ impl ParserDatabase { self.asts.iter().map(|ast| (ast.1.as_str(), ast.2)) } - /// The name of the file. - pub fn file_name(&self, file_id: FileId) -> &str { - self.asts[file_id].0.as_str() + /// Iterate all file ids + pub fn iter_file_ids(&self) -> impl Iterator + '_ { + self.asts.iter().map(|(file_id, _, _, _)| file_id) } - /// Iterate all datasources defined in the schema pub fn datasources(&self) -> impl Iterator { self.iter_asts().flat_map(|ast| ast.sources()) diff --git a/psl/parser-database/src/names.rs b/psl/parser-database/src/names.rs index dff646ca5101..4f2cca1bc5cb 100644 --- a/psl/parser-database/src/names.rs +++ b/psl/parser-database/src/names.rs @@ -41,7 +41,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { let namespace = match (top_id, top) { (_, ast::Top::Enum(ast_enum)) => { tmp_names.clear(); - validate_identifier(&ast_enum.name, "Enum", ctx); + validate_identifier(ast_enum.identifier(), "Enum", ctx); validate_enum_name(ast_enum, ctx.diagnostics); validate_attribute_identifiers(ast_enum, ctx); @@ -51,7 +51,7 @@ pub(super) fn resolve_names(ctx: &mut Context<'_>) { if !tmp_names.insert(&value.name.name) { ctx.push_error(DatamodelError::new_duplicate_enum_value_error( - &ast_enum.name.name, + ast_enum.name(), &value.name.name, value.span, )) @@ -186,11 +186,11 @@ fn check_for_duplicate_properties<'a>( ) { tmp_names.clear(); for arg in props { - if !tmp_names.insert(&arg.name.name) { + if !tmp_names.insert(arg.name()) { ctx.push_error(DatamodelError::new_duplicate_config_key_error( &format!("{} \"{}\"", top.get_type(), top.name()), - &arg.name.name, - arg.name.span, + arg.name(), + arg.identifier().span, )); } } diff --git a/psl/parser-database/src/names/reserved_model_names.rs b/psl/parser-database/src/names/reserved_model_names.rs index 1c99a39f6933..c0c3d88019e2 100644 --- a/psl/parser-database/src/names/reserved_model_names.rs +++ b/psl/parser-database/src/names/reserved_model_names.rs @@ -25,7 +25,7 @@ pub(crate) fn validate_model_name(ast_model: &ast::Model, block_type: &'static s } pub(crate) fn validate_enum_name(ast_enum: &ast::Enum, diagnostics: &mut Diagnostics) { - if !is_reserved_type_name(&ast_enum.name.name) { + if !is_reserved_type_name(ast_enum.name()) { return; } diff --git a/psl/parser-database/src/walkers.rs b/psl/parser-database/src/walkers.rs index 3dce95620996..ee1058f9c2fc 100644 --- a/psl/parser-database/src/walkers.rs +++ b/psl/parser-database/src/walkers.rs @@ -14,6 +14,7 @@ mod model; mod relation; mod relation_field; mod scalar_field; +mod top; pub use crate::types::RelationFieldId; pub use composite_type::*; @@ -26,6 +27,7 @@ pub use relation::*; pub use relation_field::*; pub use scalar_field::*; use schema_ast::ast::{NewlineType, WithSpan}; +pub use top::*; use crate::{ast, FileId}; @@ -66,12 +68,22 @@ pub(crate) fn newline(source: &str, span: Span) -> NewlineType { } impl crate::ParserDatabase { + /// Iterate all top level blocks. fn iter_tops(&self) -> impl Iterator + '_ { self.asts .iter() .flat_map(move |(file_id, _, _, ast)| ast.iter_tops().map(move |(top_id, top)| (file_id, top_id, top))) } + /// Intern any top by name. + pub fn find_top<'db>(&'db self, name: &str) -> Option> { + self.interner + .lookup(name) + .and_then(|name_id| self.names.tops.get(&name_id)) + .map(|(file_id, top_id)| (*file_id, *top_id)) + .map(|(file_id, top_id)| self.walk((file_id, top_id))) + } + /// Find an enum by name. pub fn find_enum<'db>(&'db self, name: &str) -> Option> { self.interner @@ -104,6 +116,12 @@ impl crate::ParserDatabase { Walker { db: self, id } } + /// Walk all tops in the schema. + pub fn walk_tops(&self) -> impl Iterator> { + self.iter_tops() + .map(move |(file_id, top_id, _)| self.walk((file_id, top_id))) + } + /// Walk all enums in the schema. pub fn walk_enums(&self) -> impl Iterator> { self.iter_tops() diff --git a/psl/parser-database/src/walkers/enum.rs b/psl/parser-database/src/walkers/enum.rs index 2b85e76237d4..8059ad73e5d3 100644 --- a/psl/parser-database/src/walkers/enum.rs +++ b/psl/parser-database/src/walkers/enum.rs @@ -1,5 +1,5 @@ use crate::{ - ast::{self, IndentationType, NewlineType, WithDocumentation}, + ast::{self, IndentationType, NewlineType, WithDocumentation, WithName}, types, walkers::{newline, Walker}, }; @@ -16,7 +16,7 @@ impl<'db> EnumWalker<'db> { /// The name of the enum. pub fn name(self) -> &'db str { - &self.ast_enum().name.name + self.ast_enum().name() } /// The AST node. diff --git a/psl/parser-database/src/walkers/model.rs b/psl/parser-database/src/walkers/model.rs index 4bd7110a9e6c..088302095f3d 100644 --- a/psl/parser-database/src/walkers/model.rs +++ b/psl/parser-database/src/walkers/model.rs @@ -17,7 +17,7 @@ use crate::{ }; /// A `model` declaration in the Prisma schema. -pub type ModelWalker<'db> = super::Walker<'db, (FileId, ast::ModelId)>; +pub type ModelWalker<'db> = super::Walker<'db, crate::ModelId>; impl<'db> ModelWalker<'db> { /// The name of the model. diff --git a/psl/parser-database/src/walkers/top.rs b/psl/parser-database/src/walkers/top.rs new file mode 100644 index 000000000000..6c439f9937f9 --- /dev/null +++ b/psl/parser-database/src/walkers/top.rs @@ -0,0 +1,29 @@ +use crate::{ + ast::{self, WithSpan}, + FileId, +}; + +/// Any top declaration in the Prisma schema. +pub type TopWalker<'db> = super::Walker<'db, crate::TopId>; + +impl<'db> TopWalker<'db> { + /// The name of the model. + pub fn name(self) -> &'db str { + self.ast_top().name() + } + + /// The ID of the file containing the model. + pub fn file_id(self) -> FileId { + self.id.0 + } + + /// Is the model defined in a specific file? + pub fn is_defined_in_file(self, file_id: FileId) -> bool { + self.ast_top().span().file_id == file_id + } + + /// The AST node. + pub fn ast_top(self) -> &'db ast::Top { + &self.db.asts[self.id] + } +} diff --git a/psl/psl-core/src/validate/datasource_loader.rs b/psl/psl-core/src/validate/datasource_loader.rs index fe43f4dd0d91..6ec62d6d10e3 100644 --- a/psl/psl-core/src/validate/datasource_loader.rs +++ b/psl/psl-core/src/validate/datasource_loader.rs @@ -1,5 +1,5 @@ use crate::{ - ast::{self, SourceConfig, Span}, + ast::{self, SourceConfig, Span, WithName}, configuration::StringFromEnvVar, datamodel_connector::RelationMode, diagnostics::{DatamodelError, Diagnostics}, @@ -40,7 +40,7 @@ pub(crate) fn load_datasources_from_ast( for src in ast_schema.sources() { diagnostics.push_error(DatamodelError::new_source_validation_error( "You defined more than one datasource. This is not allowed yet because support for multiple databases has not been implemented yet.", - &src.name.name, + src.name(), src.span, )); } @@ -54,15 +54,15 @@ fn lift_datasource( diagnostics: &mut Diagnostics, connectors: crate::ConnectorRegistry<'_>, ) -> Option { - let source_name = ast_source.name.name.as_str(); + let source_name = ast_source.name(); let mut args: HashMap<_, (_, &Expression)> = ast_source .properties .iter() .map(|arg| match &arg.value { - Some(expr) => Some((arg.name.name.as_str(), (arg.span, expr))), + Some(expr) => Some((arg.name(), (arg.span, expr))), None => { diagnostics.push_error(DatamodelError::new_config_property_missing_value_error( - &arg.name.name, + arg.name(), source_name, "datasource", ast_source.span, diff --git a/psl/psl-core/src/validate/generator_loader.rs b/psl/psl-core/src/validate/generator_loader.rs index 7d3794d78232..ecd1ae1975c1 100644 --- a/psl/psl-core/src/validate/generator_loader.rs +++ b/psl/psl-core/src/validate/generator_loader.rs @@ -10,6 +10,7 @@ use parser_database::{ ast::{self, Expression, WithDocumentation}, coerce, coerce_array, }; +use schema_ast::ast::WithName; use std::collections::HashMap; const PROVIDER_KEY: &str = "provider"; @@ -39,10 +40,10 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno .properties .iter() .map(|arg| match &arg.value { - Some(expr) => Some((arg.name.name.as_str(), expr)), + Some(expr) => Some((arg.name(), expr)), None => { diagnostics.push_error(DatamodelError::new_config_property_missing_value_error( - arg.name.name.as_str(), + arg.name(), generator_name, "generator", ast_generator.span, @@ -94,7 +95,7 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno .map(|(arr, span)| parse_and_validate_preview_features(arr, &ALL_PREVIEW_FEATURES, span, diagnostics)); for prop in &ast_generator.properties { - let is_first_class_prop = FIRST_CLASS_PROPERTIES.iter().any(|k| *k == prop.name.name); + let is_first_class_prop = FIRST_CLASS_PROPERTIES.iter().any(|k| *k == prop.name()); if is_first_class_prop { continue; } @@ -103,7 +104,7 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno Some(val) => GeneratorConfigValue::from(val), None => { diagnostics.push_error(DatamodelError::new_config_property_missing_value_error( - &prop.name.name, + prop.name(), generator_name, "generator", prop.span, @@ -112,7 +113,7 @@ fn lift_generator(ast_generator: &ast::GeneratorConfig, diagnostics: &mut Diagno } }; - properties.insert(prop.name.name.clone(), value); + properties.insert(prop.name().to_owned(), value); } Some(Generator { diff --git a/psl/schema-ast/src/ast.rs b/psl/schema-ast/src/ast.rs index a3380ea8b50a..0610348bb432 100644 --- a/psl/schema-ast/src/ast.rs +++ b/psl/schema-ast/src/ast.rs @@ -107,6 +107,14 @@ impl std::ops::Index for SchemaAst { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct GeneratorId(u32); +impl std::ops::Index for SchemaAst { + type Output = GeneratorConfig; + + fn index(&self, index: GeneratorId) -> &Self::Output { + self.tops[index.0 as usize].as_generator().unwrap() + } +} + /// An opaque identifier for a datasource block in a schema AST. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SourceId(u32); diff --git a/psl/schema-ast/src/ast/config.rs b/psl/schema-ast/src/ast/config.rs index d00ee5914910..8ab8fb09f7b4 100644 --- a/psl/schema-ast/src/ast/config.rs +++ b/psl/schema-ast/src/ast/config.rs @@ -1,5 +1,7 @@ use crate::ast::{Expression, Identifier, Span, WithSpan}; +use super::WithIdentifier; + /// A named property in a config block. /// /// ```ignore @@ -18,7 +20,7 @@ pub struct ConfigBlockProperty { /// ^^^ /// } /// ``` - pub name: Identifier, + pub(crate) name: Identifier, /// The property value. /// /// ```ignore @@ -37,3 +39,9 @@ impl WithSpan for ConfigBlockProperty { self.span } } + +impl WithIdentifier for ConfigBlockProperty { + fn identifier(&self) -> &Identifier { + &self.name + } +} diff --git a/psl/schema-ast/src/ast/enum.rs b/psl/schema-ast/src/ast/enum.rs index 33c7e3e8d230..6ef4e1326c96 100644 --- a/psl/schema-ast/src/ast/enum.rs +++ b/psl/schema-ast/src/ast/enum.rs @@ -33,7 +33,7 @@ pub struct Enum { /// enum Foo { ... } /// ^^^ /// ``` - pub name: Identifier, + pub(crate) name: Identifier, /// The values of the enum. /// /// ```ignore diff --git a/psl/schema-ast/src/ast/field.rs b/psl/schema-ast/src/ast/field.rs index aa2ed12bf9cb..3e355ecc2b41 100644 --- a/psl/schema-ast/src/ast/field.rs +++ b/psl/schema-ast/src/ast/field.rs @@ -145,6 +145,13 @@ impl FieldType { } } + pub fn name(&self) -> &str { + match self { + FieldType::Supported(supported) => &supported.name, + FieldType::Unsupported(name, _) => name, + } + } + pub fn as_unsupported(&self) -> Option<(&str, &Span)> { match self { FieldType::Unsupported(name, span) => Some((name, span)), diff --git a/psl/schema-ast/src/ast/find_at_position.rs b/psl/schema-ast/src/ast/find_at_position.rs index f8fa23368cdd..b1a5c458bf79 100644 --- a/psl/schema-ast/src/ast/find_at_position.rs +++ b/psl/schema-ast/src/ast/find_at_position.rs @@ -1,3 +1,23 @@ +mod attribute; +mod composite_type; +mod datasource; +mod r#enum; +mod expression; +mod field; +mod generator; +mod model; +mod property; + +pub use attribute::AttributePosition; +pub use composite_type::CompositeTypePosition; +pub use datasource::SourcePosition; +pub use expression::ExpressionPosition; +pub use field::FieldPosition; +pub use generator::GeneratorPosition; +pub use model::ModelPosition; +pub use property::PropertyPosition; +pub use r#enum::EnumPosition; + use crate::ast::{self, top_idx_to_top_id, traits::*}; impl ast::SchemaAst { @@ -9,11 +29,16 @@ impl ast::SchemaAst { SchemaPosition::Model(model_id, ModelPosition::new(&self[model_id], position)) } ast::TopId::Enum(enum_id) => SchemaPosition::Enum(enum_id, EnumPosition::new(&self[enum_id], position)), + ast::TopId::CompositeType(composite_type_id) => SchemaPosition::CompositeType( + composite_type_id, + CompositeTypePosition::new(&self[composite_type_id], position), + ), ast::TopId::Source(source_id) => { SchemaPosition::DataSource(source_id, SourcePosition::new(&self[source_id], position)) } - // Falling back to TopLevel as "not implemented" - _ => SchemaPosition::TopLevel, + ast::TopId::Generator(generator_id) => { + SchemaPosition::Generator(generator_id, GeneratorPosition::new(&self[generator_id], position)) + } }) // If no top matched, we're in between top-level items. This is normal and expected. .unwrap_or(SchemaPosition::TopLevel) @@ -48,330 +73,10 @@ pub enum SchemaPosition<'ast> { Model(ast::ModelId, ModelPosition<'ast>), /// In an enum Enum(ast::EnumId, EnumPosition<'ast>), + /// In a composite type + CompositeType(ast::CompositeTypeId, CompositeTypePosition<'ast>), /// In a datasource DataSource(ast::SourceId, SourcePosition<'ast>), -} - -/// A cursor position in a context. -#[derive(Debug)] -pub enum ModelPosition<'ast> { - /// In the model, but not somewhere more specific. - Model, - /// In an attribute (attr name, attr index, position). - ModelAttribute(&'ast str, usize, AttributePosition<'ast>), - /// In a field. - Field(ast::FieldId, FieldPosition<'ast>), -} - -impl<'ast> ModelPosition<'ast> { - fn new(model: &'ast ast::Model, position: usize) -> Self { - for (field_id, field) in model.iter_fields() { - if field.span().contains(position) { - return ModelPosition::Field(field_id, FieldPosition::new(field, position)); - } - } - - for (attr_id, attr) in model.attributes.iter().enumerate() { - if attr.span().contains(position) { - return ModelPosition::ModelAttribute(&attr.name.name, attr_id, AttributePosition::new(attr, position)); - } - } - - ModelPosition::Model - } -} - -/// A cursor position in a context. -#[derive(Debug)] -pub enum EnumPosition<'ast> { - /// In the enum, but not somewhere more specific. - Enum, - /// In an attribute (attr name, attr index, position). - EnumAttribute(&'ast str, usize, AttributePosition<'ast>), - /// In a value. - Value(ast::EnumValueId, EnumValuePosition<'ast>), -} - -impl<'ast> EnumPosition<'ast> { - fn new(r#enum: &'ast ast::Enum, position: usize) -> Self { - for (enum_value_id, value) in r#enum.iter_values() { - if value.span().contains(position) { - return EnumPosition::Value(enum_value_id, EnumValuePosition::new(value, position)); - } - } - - for (attr_id, attr) in r#enum.attributes.iter().enumerate() { - if attr.span().contains(position) { - return EnumPosition::EnumAttribute(&attr.name.name, attr_id, AttributePosition::new(attr, position)); - } - } - - EnumPosition::Enum - } -} - -/// In a scalar field. -#[derive(Debug)] -pub enum FieldPosition<'ast> { - /// Nowhere specific inside the field - Field, - /// In an attribute. (name, idx, optional arg) - Attribute(&'ast str, usize, Option<&'ast str>), -} - -impl<'ast> FieldPosition<'ast> { - fn new(field: &'ast ast::Field, position: usize) -> FieldPosition<'ast> { - for (attr_idx, attr) in field.attributes.iter().enumerate() { - if attr.span().contains(position) { - // We can't go by Span::contains() because we also care about the empty space - // between arguments and that's hard to capture in the pest grammar. - let mut spans: Vec<(Option<&str>, ast::Span)> = attr - .arguments - .iter() - .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) - .chain( - attr.arguments - .empty_arguments - .iter() - .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), - ) - .collect(); - spans.sort_by_key(|(_, span)| span.start); - let mut arg_name = None; - - 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 { - arg_name = None; - } - } - - return FieldPosition::Attribute(attr.name(), attr_idx, arg_name.flatten()); - } - } - - FieldPosition::Field - } -} - -/// In an enum value. -#[derive(Debug)] -pub enum EnumValuePosition<'ast> { - /// Nowhere specific inside the value - Value, - /// In an attribute. (name, idx, optional arg) - Attribute(&'ast str, usize, Option<&'ast str>), -} - -impl<'ast> EnumValuePosition<'ast> { - fn new(value: &'ast ast::EnumValue, position: usize) -> EnumValuePosition<'ast> { - for (attr_idx, attr) in value.attributes.iter().enumerate() { - if attr.span().contains(position) { - // We can't go by Span::contains() because we also care about the empty space - // between arguments and that's hard to capture in the pest grammar. - let mut spans: Vec<(Option<&str>, ast::Span)> = attr - .arguments - .iter() - .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) - .chain( - attr.arguments - .empty_arguments - .iter() - .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), - ) - .collect(); - spans.sort_by_key(|(_, span)| span.start); - let mut arg_name = None; - - 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 { - arg_name = None; - } - } - - return EnumValuePosition::Attribute(attr.name(), attr_idx, arg_name.flatten()); - } - } - - EnumValuePosition::Value - } -} - -/// In an model attribute definition -#[derive(Debug)] -pub enum AttributePosition<'ast> { - /// Nowhere specific inside the attribute (attribute name) - Attribute, - /// In an argument. (argument name) - Argument(&'ast str), - /// In an function argument. (function name, argument name) - FunctionArgument(&'ast str, &'ast str), -} - -impl<'ast> AttributePosition<'ast> { - fn new(attr: &'ast ast::Attribute, position: usize) -> Self { - if attr.span().contains(position) { - // We can't go by Span::contains() because we also care about the empty space - // between arguments and that's hard to capture in the pest grammar. - let mut spans: Vec<(Option<&str>, ast::Span)> = attr - .arguments - .iter() - .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) - .chain( - attr.arguments - .empty_arguments - .iter() - .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), - ) - .collect(); - - spans.sort_by_key(|(_, span)| span.start); - - let mut arg_name = None; - 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 { - arg_name = None; - } - } - - if let Some(arg_name) = arg_name.flatten() { - return Self::Argument(arg_name); - } - - if let Some(arg) = attr.arguments.iter().find(|arg| arg.span().contains(position)) { - if let ExpressionPosition::FunctionArgument(fun, name) = ExpressionPosition::new(&arg.value, position) { - return Self::FunctionArgument(fun, name); - } - } - } - - Self::Attribute - } -} - -#[derive(Debug)] -pub enum ExpressionPosition<'ast> { - Expression, - Value(&'ast str), - Function(&'ast str), - FunctionArgument(&'ast str, &'ast str), -} - -impl<'ast> ExpressionPosition<'ast> { - fn new(expr: &'ast ast::Expression, position: usize) -> Self { - match expr { - ast::Expression::NumericValue(val, span) if span.contains(position) => Self::Value(val), - ast::Expression::StringValue(val, span) if span.contains(position) => Self::Value(val), - ast::Expression::ConstantValue(val, span) if span.contains(position) => Self::Value(val), - ast::Expression::Function(name, args, span) if span.contains(position) => { - let mut spans: Vec<(Option<&str>, ast::Span)> = args - .arguments - .iter() - .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) - .chain( - args.empty_arguments - .iter() - .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), - ) - .collect(); - - spans.sort_by_key(|(_, span)| span.start); - - let mut arg_name = None; - 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 { - arg_name = None; - } - } - - if let Some(arg_name) = arg_name.flatten() { - Self::FunctionArgument(name, arg_name) - } else { - Self::Function(name) - } - } - ast::Expression::Array(exprs, span) if span.contains(position) => { - for expr in exprs.iter() { - match ExpressionPosition::new(expr, position) { - ExpressionPosition::Expression => (), - e => return e, - } - } - - Self::Expression - } - _ => Self::Expression, - } - } -} - -#[derive(Debug)] -pub enum SourcePosition<'ast> { - /// In the general datasource - Source, - /// In a property - Property(&'ast str, PropertyPosition<'ast>), - /// Outside of the braces - Outer, -} - -impl<'ast> SourcePosition<'ast> { - fn new(source: &'ast ast::SourceConfig, position: usize) -> Self { - for property in &source.properties { - if property.span.contains(position) { - return SourcePosition::Property(&property.name.name, PropertyPosition::new(property, position)); - } - } - - if source.inner_span.contains(position) { - return SourcePosition::Source; - } - - SourcePosition::Outer - } -} - -#[derive(Debug)] -pub enum PropertyPosition<'ast> { - Property, - Value(&'ast str), - FunctionValue(&'ast str), -} - -impl<'ast> PropertyPosition<'ast> { - fn new(property: &'ast ast::ConfigBlockProperty, position: usize) -> Self { - if let Some(val) = &property.value { - if val.span().contains(position) && val.is_function() { - let func = val.as_function().unwrap(); - - if func.0 == "env" { - return PropertyPosition::FunctionValue("env"); - } - } - } - if property.span.contains(position) && !property.name.span.contains(position) { - return PropertyPosition::Value(&property.name.name); - } - - PropertyPosition::Property - } + /// In a generator + Generator(ast::GeneratorId, GeneratorPosition<'ast>), } diff --git a/psl/schema-ast/src/ast/find_at_position/attribute.rs b/psl/schema-ast/src/ast/find_at_position/attribute.rs new file mode 100644 index 000000000000..07dea044176c --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/attribute.rs @@ -0,0 +1,60 @@ +use crate::ast::{self}; + +use super::{ExpressionPosition, WithSpan}; + +/// In an model attribute definition +#[derive(Debug)] +pub enum AttributePosition<'ast> { + /// Nowhere specific inside the attribute (attribute name) + Attribute, + /// In an argument. (argument name) + Argument(&'ast str), + /// In an function argument. (function name, argument name) + FunctionArgument(&'ast str, &'ast str), +} + +impl<'ast> AttributePosition<'ast> { + pub(crate) fn new(attr: &'ast ast::Attribute, position: usize) -> Self { + if attr.span().contains(position) { + // We can't go by Span::contains() because we also care about the empty space + // between arguments and that's hard to capture in the pest grammar. + let mut spans: Vec<(Option<&str>, ast::Span)> = attr + .arguments + .iter() + .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) + .chain( + attr.arguments + .empty_arguments + .iter() + .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), + ) + .collect(); + + spans.sort_by_key(|(_, span)| span.start); + + let mut arg_name = None; + 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 { + arg_name = None; + } + } + + if let Some(arg_name) = arg_name.flatten() { + return Self::Argument(arg_name); + } + + if let Some(arg) = attr.arguments.iter().find(|arg| arg.span().contains(position)) { + if let ExpressionPosition::FunctionArgument(fun, name) = ExpressionPosition::new(&arg.value, position) { + return Self::FunctionArgument(fun, name); + } + } + } + + Self::Attribute + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/composite_type.rs b/psl/schema-ast/src/ast/find_at_position/composite_type.rs new file mode 100644 index 000000000000..06c4b0d3078c --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/composite_type.rs @@ -0,0 +1,43 @@ +use crate::ast::{self}; + +use super::{FieldPosition, WithName}; + +#[derive(Debug)] +pub enum CompositeTypePosition<'ast> { + /// In the composite type, but no-where specific + CompositeType, + /// In the composite type's name. + /// ```prisma + /// type Address { + /// // ^^^^^^^ + /// street String + /// city String + /// } + /// ``` + Name(&'ast str), + /// In a field. + /// ```prisma + /// type Address { + /// street String + /// city String + /// // ^^^^^^^^^^^^^ + /// } + /// ``` + Field(ast::FieldId, FieldPosition<'ast>), +} + +impl<'ast> CompositeTypePosition<'ast> { + pub(crate) fn new(composite_type: &'ast ast::CompositeType, position: usize) -> Self { + if composite_type.name.span.contains(position) { + return CompositeTypePosition::Name(composite_type.name()); + } + + for (field_id, field) in composite_type.iter_fields() { + if field.span.contains(position) { + return CompositeTypePosition::Field(field_id, FieldPosition::new(field, position)); + } + } + + CompositeTypePosition::CompositeType + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/datasource.rs b/psl/schema-ast/src/ast/find_at_position/datasource.rs new file mode 100644 index 000000000000..4df3093c5d7c --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/datasource.rs @@ -0,0 +1,48 @@ +use super::{PropertyPosition, WithName}; +use crate::ast::{self}; + +#[derive(Debug)] +pub enum SourcePosition<'ast> { + /// In the general datasource + Source, + /// In the datasource's name + /// ```prisma + /// datasource db { + /// // ^^ + /// provider = "mongodb" + /// url = env("DATABASE_URL") + /// } + /// ``` + Name(&'ast str), + /// In a property + /// ```prisma + /// datasource db { + /// provider = "mongodb" + /// // ^^^^^^^^^^^^^^^^^^^^ + /// url = env("DATABASE_URL") + /// } + /// ``` + Property(&'ast str, PropertyPosition<'ast>), + /// Outside of the braces + Outer, +} + +impl<'ast> SourcePosition<'ast> { + pub(crate) fn new(source: &'ast ast::SourceConfig, position: usize) -> Self { + if source.name.span.contains(position) { + return SourcePosition::Name(source.name()); + } + + for property in &source.properties { + if property.span.contains(position) { + return SourcePosition::Property(&property.name.name, PropertyPosition::new(property, position)); + } + } + + if source.inner_span.contains(position) { + return SourcePosition::Source; + } + + SourcePosition::Outer + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/enum.rs b/psl/schema-ast/src/ast/find_at_position/enum.rs new file mode 100644 index 000000000000..3138ef36e4c3 --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/enum.rs @@ -0,0 +1,115 @@ +use super::{AttributePosition, WithName, WithSpan}; +use crate::ast::{self}; + +/// A cursor position in a context. +#[derive(Debug)] +pub enum EnumPosition<'ast> { + /// In the enum, but not somewhere more specific. + Enum, + /// In the enum's name. + /// ```prisma + /// enum Animal { + /// // ^^^^^^ + /// Dog + /// RedPanda + /// } + /// ``` + Name(&'ast str), + /// In an attribute (attr name, attr index, position). + /// ```prisma + /// enum Animal { + /// Dog + /// RedPanda + /// @@map("pet") + /// // ^^^^^^^ + /// } + /// ``` + EnumAttribute(&'ast str, usize, AttributePosition<'ast>), + /// In a value. + /// ```prisma + /// enum Animal { + /// Dog + /// RedPanda + /// // ^^^^^^^ + /// } + /// ``` + Value(ast::EnumValueId, EnumValuePosition<'ast>), +} + +impl<'ast> EnumPosition<'ast> { + pub(crate) fn new(r#enum: &'ast ast::Enum, position: usize) -> Self { + if r#enum.name.span.contains(position) { + return EnumPosition::Name(r#enum.name()); + } + + for (enum_value_id, value) in r#enum.iter_values() { + if value.span().contains(position) { + return EnumPosition::Value(enum_value_id, EnumValuePosition::new(value, position)); + } + } + + for (attr_id, attr) in r#enum.attributes.iter().enumerate() { + if attr.span().contains(position) { + return EnumPosition::EnumAttribute(&attr.name.name, attr_id, AttributePosition::new(attr, position)); + } + } + + EnumPosition::Enum + } +} + +/// In an enum value. +#[derive(Debug)] +pub enum EnumValuePosition<'ast> { + /// Nowhere specific inside the value + Value, + /// In an attribute. (name, idx, optional arg) + /// In a value. + /// ```prisma + /// enum Animal { + /// Dog + /// RedPanda @map("red_panda") + /// // ^^^^^^^^^^^^^^^^^ + /// } + /// ``` + Attribute(&'ast str, usize, Option<&'ast str>), +} + +impl<'ast> EnumValuePosition<'ast> { + fn new(value: &'ast ast::EnumValue, position: usize) -> EnumValuePosition<'ast> { + for (attr_idx, attr) in value.attributes.iter().enumerate() { + if attr.span().contains(position) { + // We can't go by Span::contains() because we also care about the empty space + // between arguments and that's hard to capture in the pest grammar. + let mut spans: Vec<(Option<&str>, ast::Span)> = attr + .arguments + .iter() + .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) + .chain( + attr.arguments + .empty_arguments + .iter() + .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), + ) + .collect(); + spans.sort_by_key(|(_, span)| span.start); + let mut arg_name = None; + + 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 { + arg_name = None; + } + } + + return EnumValuePosition::Attribute(attr.name(), attr_idx, arg_name.flatten()); + } + } + + EnumValuePosition::Value + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/expression.rs b/psl/schema-ast/src/ast/find_at_position/expression.rs new file mode 100644 index 000000000000..315c13db17c9 --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/expression.rs @@ -0,0 +1,72 @@ +use crate::ast::{self}; + +use super::WithSpan; + +#[derive(Debug)] +pub enum ExpressionPosition<'ast> { + Expression, + Value(&'ast str), + Function(&'ast str), + FunctionArgument(&'ast str, &'ast str), +} + +impl<'ast> ExpressionPosition<'ast> { + pub(crate) fn new(expr: &'ast ast::Expression, position: usize) -> Self { + match expr { + ast::Expression::NumericValue(val, span) if span.contains(position) => Self::Value(val), + ast::Expression::StringValue(val, span) if span.contains(position) => Self::Value(val), + ast::Expression::ConstantValue(val, span) if span.contains(position) => Self::Value(val), + ast::Expression::Function(name, args, span) if span.contains(position) => { + narrow_function_position(args, position, name) + } + ast::Expression::Array(exprs, span) if span.contains(position) => { + for expr in exprs.iter() { + match ExpressionPosition::new(expr, position) { + ExpressionPosition::Expression => (), + e => return e, + } + } + + Self::Expression + } + _ => Self::Expression, + } + } +} + +fn narrow_function_position<'ast>( + args: &'ast ast::ArgumentsList, + position: usize, + name: &'ast str, +) -> ExpressionPosition<'ast> { + let mut spans: Vec<(Option<&str>, ast::Span)> = args + .arguments + .iter() + .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) + .chain( + args.empty_arguments + .iter() + .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), + ) + .collect(); + + spans.sort_by_key(|(_, span)| span.start); + + let mut arg_name = None; + 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 { + arg_name = None; + } + } + + if let Some(arg_name) = arg_name.flatten() { + ExpressionPosition::FunctionArgument(name, arg_name) + } else { + ExpressionPosition::Function(name) + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/field.rs b/psl/schema-ast/src/ast/find_at_position/field.rs new file mode 100644 index 000000000000..3c358f3c6da7 --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/field.rs @@ -0,0 +1,84 @@ +use crate::ast::{self}; + +use super::{WithName, WithSpan}; + +/// In a scalar field. +#[derive(Debug)] +pub enum FieldPosition<'ast> { + /// Nowhere specific inside the field + Field, + /// In the field's name + /// ```prisma + /// model People { + /// id String @id + /// field Float + /// // ^^^^^ + /// } + /// ``` + Name(&'ast str), + /// In the field's type definition + /// ```prisma + /// model People { + /// id String @id + /// field Float + /// // ^^^^^ + /// } + /// ``` + Type(&'ast str), + /// In an attribute. (name, idx, optional arg) + /// ```prisma + /// model People { + /// id String @id + /// // ^^^^ + /// field Float + /// } + /// ``` + Attribute(&'ast str, usize, Option<&'ast str>), +} + +impl<'ast> FieldPosition<'ast> { + pub(crate) fn new(field: &'ast ast::Field, position: usize) -> FieldPosition<'ast> { + if field.name.span.contains(position) { + return FieldPosition::Name(field.name()); + } + + if field.field_type.span().contains(position) { + return FieldPosition::Type(field.field_type.name()); + } + + for (attr_idx, attr) in field.attributes.iter().enumerate() { + if attr.span().contains(position) { + // We can't go by Span::contains() because we also care about the empty space + // between arguments and that's hard to capture in the pest grammar. + let mut spans: Vec<(Option<&str>, ast::Span)> = attr + .arguments + .iter() + .map(|arg| (arg.name.as_ref().map(|n| n.name.as_str()), arg.span())) + .chain( + attr.arguments + .empty_arguments + .iter() + .map(|arg| (Some(arg.name.name.as_str()), arg.name.span())), + ) + .collect(); + spans.sort_by_key(|(_, span)| span.start); + let mut arg_name = None; + + 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 { + arg_name = None; + } + } + + return FieldPosition::Attribute(attr.name(), attr_idx, arg_name.flatten()); + } + } + + FieldPosition::Field + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/generator.rs b/psl/schema-ast/src/ast/find_at_position/generator.rs new file mode 100644 index 000000000000..852436b1773b --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/generator.rs @@ -0,0 +1,28 @@ +use super::{PropertyPosition, WithName}; +use crate::ast::{self}; + +#[derive(Debug)] +pub enum GeneratorPosition<'ast> { + /// In the general generator + Generator, + /// In the generator's name + Name(&'ast str), + /// In a property + Property(&'ast str, PropertyPosition<'ast>), +} + +impl<'ast> GeneratorPosition<'ast> { + pub(crate) fn new(source: &'ast ast::GeneratorConfig, position: usize) -> Self { + if source.name.span.contains(position) { + return GeneratorPosition::Name(source.name()); + } + + for property in &source.properties { + if property.span.contains(position) { + return GeneratorPosition::Property(&property.name.name, PropertyPosition::new(property, position)); + } + } + + GeneratorPosition::Generator + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/model.rs b/psl/schema-ast/src/ast/find_at_position/model.rs new file mode 100644 index 000000000000..a88a7b2ba25b --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/model.rs @@ -0,0 +1,61 @@ +use super::{AttributePosition, FieldPosition, WithName, WithSpan}; + +use crate::ast::{self}; + +/// A cursor position in a context. +#[derive(Debug)] +pub enum ModelPosition<'ast> { + /// In the model, but not somewhere more specific. + Model, + /// In the name of the model. + /// ```prisma + /// model People { + /// // ^^^^^^ + /// id String @id @map("_id") + /// SomeUser SomeUser[] + /// } + /// ``` + Name(&'ast str), + /// In an attribute (attr name, attr index, position). + /// ```prisma + /// model People { + /// id String @id @map("_id") + /// SomeUser SomeUser[] + /// + /// @@ignore + /// // ^^^^^^^^ + /// } + /// ``` + ModelAttribute(&'ast str, usize, AttributePosition<'ast>), + /// In a field. + /// ```prisma + /// model People { + /// id String @id @map("_id") + /// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + /// SomeUser SomeUser[] + /// } + /// ``` + Field(ast::FieldId, FieldPosition<'ast>), +} + +impl<'ast> ModelPosition<'ast> { + pub(crate) fn new(model: &'ast ast::Model, position: usize) -> Self { + if model.name.span.contains(position) { + return ModelPosition::Name(model.name()); + } + + for (field_id, field) in model.iter_fields() { + if field.span().contains(position) { + return ModelPosition::Field(field_id, FieldPosition::new(field, position)); + } + } + + for (attr_id, attr) in model.attributes.iter().enumerate() { + if attr.span().contains(position) { + return ModelPosition::ModelAttribute(&attr.name.name, attr_id, AttributePosition::new(attr, position)); + } + } + + ModelPosition::Model + } +} diff --git a/psl/schema-ast/src/ast/find_at_position/property.rs b/psl/schema-ast/src/ast/find_at_position/property.rs new file mode 100644 index 000000000000..245ced4ba294 --- /dev/null +++ b/psl/schema-ast/src/ast/find_at_position/property.rs @@ -0,0 +1,59 @@ +use crate::ast::{self}; + +use super::WithName; + +#[derive(Debug)] +pub enum PropertyPosition<'ast> { + Property, + /// In the property's name. + /// ```prisma + /// datasource db { + /// provider = "mongodb" + /// // ^^^^^^^^ + /// url = env("DATABASE_URL") + /// } + /// ``` + Name(&'ast str), + /// In the property's value. + /// ```prisma + /// datasource db { + /// provider = "mongodb" + /// // ^^^^^^^^^ + /// url = env("DATABASE_URL") + /// } + /// ``` + Value(&'ast str), + /// In the property's value - specifically a function. + /// ```prisma + /// datasource db { + /// provider = "mongodb" + /// url = env("DATABASE_URL") + /// // ^^^^^^^^^^^^^^^^^^^ + /// } + /// ``` + FunctionValue(&'ast str), +} + +impl<'ast> PropertyPosition<'ast> { + pub(crate) fn new(property: &'ast ast::ConfigBlockProperty, position: usize) -> Self { + if property.name.span.contains(position) { + return PropertyPosition::Name(property.name()); + } + + if let Some(val) = &property.value { + if val.span().contains(position) && val.is_function() { + let func = val.as_function().unwrap(); + + if func.0 == "env" { + return PropertyPosition::FunctionValue("env"); + } + } + } + if property.span.contains(position) && !property.name.span.contains(position) { + // TODO(@druue): this should actually just return the value string, not the name of the property the value is for + return PropertyPosition::Value(&property.name.name); + } + + PropertyPosition::Property + } +} diff --git a/psl/schema-ast/src/ast/source_config.rs b/psl/schema-ast/src/ast/source_config.rs index fba385008c88..31d75ce1a9a5 100644 --- a/psl/schema-ast/src/ast/source_config.rs +++ b/psl/schema-ast/src/ast/source_config.rs @@ -4,7 +4,7 @@ use super::{Comment, ConfigBlockProperty, Identifier, Span, WithDocumentation, W #[derive(Debug, Clone)] pub struct SourceConfig { /// Name of this source. - pub name: Identifier, + pub(crate) name: Identifier, /// Top-level configuration properties for this source. pub properties: Vec, /// The comments for this source block. diff --git a/psl/schema-ast/src/ast/top.rs b/psl/schema-ast/src/ast/top.rs index 42757eb97517..24357715b743 100644 --- a/psl/schema-ast/src/ast/top.rs +++ b/psl/schema-ast/src/ast/top.rs @@ -1,5 +1,7 @@ use crate::ast::{traits::WithSpan, CompositeType, Enum, GeneratorConfig, Identifier, Model, SourceConfig, Span}; +use super::WithDocumentation; + /// Enum for distinguishing between top-level entries #[derive(Debug, Clone)] pub enum Top { @@ -44,6 +46,16 @@ impl Top { &self.identifier().name } + pub fn documentation(&self) -> Option<&str> { + match self { + Top::CompositeType(t) => t.documentation(), + Top::Enum(t) => t.documentation(), + Top::Model(t) => t.documentation(), + Top::Source(t) => t.documentation(), + Top::Generator(t) => t.documentation(), + } + } + /// Try to interpret the item as a composite type declaration. pub fn as_composite_type(&self) -> Option<&CompositeType> { match self {