diff --git a/prisma-fmt/src/lib.rs b/prisma-fmt/src/lib.rs index 256b5744887b..f2838915bcbb 100644 --- a/prisma-fmt/src/lib.rs +++ b/prisma-fmt/src/lib.rs @@ -7,6 +7,7 @@ mod merge_schemas; mod native; mod offsets; mod preview; +mod references; mod schema_file_input; mod text_document_completion; mod validate; @@ -84,13 +85,30 @@ pub fn code_actions(schema_files: String, params: &str) -> String { let Ok(input) = serde_json::from_str::(&schema_files) else { warn!("Failed to parse schema file input"); - return serde_json::to_string(&text_document_completion::empty_completion_list()).unwrap(); + return serde_json::to_string(&code_actions::empty_code_actions()).unwrap(); }; let actions = code_actions::available_actions(input.into(), params); serde_json::to_string(&actions).unwrap() } +pub fn references(schema_files: String, params: &str) -> String { + let params: lsp_types::ReferenceParams = if let Ok(params) = serde_json::from_str(params) { + params + } else { + warn!("Failed to parse params to references() as ReferenceParams."); + return serde_json::to_string(&references::empty_references()).unwrap(); + }; + + let Ok(input) = serde_json::from_str::(&schema_files) else { + warn!("Failed to parse schema file input"); + return serde_json::to_string(&references::empty_references()).unwrap(); + }; + + let references = references::references(input.into(), params); + serde_json::to_string(&references).unwrap() +} + /// The two parameters are: /// - The [`SchemaFileInput`] to reformat, as a string. /// - An LSP diff --git a/prisma-fmt/src/references.rs b/prisma-fmt/src/references.rs new file mode 100644 index 000000000000..00da04548893 --- /dev/null +++ b/prisma-fmt/src/references.rs @@ -0,0 +1,245 @@ +use log::*; +use lsp_types::{Location, ReferenceParams, Url}; +use psl::{ + diagnostics::FileId, + error_tolerant_parse_configuration, + parser_database::ParserDatabase, + schema_ast::ast::{ + AttributePosition, CompositeTypePosition, EnumPosition, Field, FieldId, FieldPosition, FieldType, Identifier, + ModelId, ModelPosition, SchemaPosition, SourcePosition, Top, WithIdentifier, WithName, + }, + Diagnostics, SourceFile, +}; + +use crate::{offsets::position_to_offset, span_to_range, LSPContext}; + +pub(super) type ReferencesContext<'a> = LSPContext<'a, ReferenceParams>; + +pub(crate) fn empty_references() -> Vec { + Vec::new() +} + +fn empty_identifiers<'ast>() -> impl Iterator { + std::iter::empty() +} + +pub(crate) fn references(schema_files: Vec<(String, SourceFile)>, params: ReferenceParams) -> Vec { + let (_, config, _) = error_tolerant_parse_configuration(&schema_files); + + let db = { + let mut diag = Diagnostics::new(); + ParserDatabase::new(&schema_files, &mut diag) + }; + + let Some(initiating_file_id) = db.file_id(params.text_document_position.text_document.uri.as_str()) else { + warn!("Initating file name is not found in the schema"); + return empty_references(); + }; + + let initiating_doc = db.source(initiating_file_id); + + let position = if let Some(pos) = position_to_offset(¶ms.text_document_position.position, initiating_doc) { + pos + } else { + warn!("Received a position outside of the document boundaries in ReferenceParams"); + return empty_references(); + }; + + let target_position = db.ast(initiating_file_id).find_at_position(position); + + let ctx = ReferencesContext { + db: &db, + config: &config, + initiating_file_id, + params: ¶ms, + }; + + reference_locations_for_target(ctx, target_position) +} + +fn reference_locations_for_target(ctx: ReferencesContext<'_>, target: SchemaPosition) -> Vec { + let identifiers: Vec<&Identifier> = match target { + // Blocks + SchemaPosition::Model(model_id, ModelPosition::Name(name)) => { + let model = ctx.db.walk((ctx.initiating_file_id, model_id)); + + std::iter::once(model.ast_model().identifier()) + .chain(find_where_used_as_field_type(&ctx, name)) + .collect() + } + SchemaPosition::Enum(enum_id, EnumPosition::Name(name)) => { + let enm = ctx.db.walk((ctx.initiating_file_id, enum_id)); + + std::iter::once(enm.ast_enum().identifier()) + .chain(find_where_used_as_field_type(&ctx, name)) + .collect() + } + SchemaPosition::CompositeType(composite_id, CompositeTypePosition::Name(name)) => { + let ct = ctx.db.walk((ctx.initiating_file_id, composite_id)); + + std::iter::once(ct.ast_composite_type().identifier()) + .chain(find_where_used_as_field_type(&ctx, name)) + .collect() + } + SchemaPosition::DataSource(_, SourcePosition::Name(name)) => find_where_used_as_ds_name(&ctx, name) + .into_iter() + .chain(find_where_used_for_native_type(&ctx, name)) + .collect(), + + // Fields + SchemaPosition::Model(_, ModelPosition::Field(_, FieldPosition::Type(r#type))) + | SchemaPosition::CompositeType(_, CompositeTypePosition::Field(_, FieldPosition::Type(r#type))) => { + find_where_used_as_top_name(&ctx, r#type) + .into_iter() + .chain(find_where_used_as_field_type(&ctx, r#type)) + .collect() + } + + // Attributes + SchemaPosition::Model( + model_id, + ModelPosition::Field( + field_id, + FieldPosition::Attribute(_, _, AttributePosition::ArgumentValue(arg_name, arg_value)), + ), + ) => match arg_name { + Some("fields") => find_where_used_as_field_name(&ctx, arg_value.as_str(), model_id, ctx.initiating_file_id) + .into_iter() + .collect(), + Some("references") => { + let field = &ctx.db.ast(ctx.initiating_file_id)[model_id][field_id]; + let referenced_model = field.field_type.name(); + + let Some(ref_model_id) = ctx.db.find_model(referenced_model) else { + warn!("Could not find model with name: {}", referenced_model); + return empty_references(); + }; + + find_where_used_as_field_name(&ctx, arg_value.as_str(), ref_model_id.id.1, ref_model_id.id.0) + .into_iter() + .collect() + } + _ => vec![], + }, + + // ? This might make more sense to add as a definition rather than a reference + SchemaPosition::Model(_, ModelPosition::Field(_, FieldPosition::Attribute(name, _, _))) + | SchemaPosition::CompositeType(_, CompositeTypePosition::Field(_, FieldPosition::Attribute(name, _, _))) => { + match ctx.datasource().map(|ds| &ds.name) { + Some(ds_name) if name.contains(ds_name) => find_where_used_as_ds_name(&ctx, ds_name) + .into_iter() + .chain(find_where_used_for_native_type(&ctx, ds_name)) + .collect(), + _ => vec![], + } + } + + SchemaPosition::Model( + model_id, + ModelPosition::ModelAttribute(_attr_name, _, AttributePosition::ArgumentValue(_, arg_val)), + ) => find_where_used_as_field_name(&ctx, arg_val.as_str(), model_id, ctx.initiating_file_id) + .into_iter() + .collect(), + + _ => vec![], + }; + + identifiers + .iter() + .filter_map(|ident| ident_to_location(ident, &ctx)) + .collect() +} + +fn find_where_used_as_field_name<'ast>( + ctx: &'ast ReferencesContext<'_>, + name: &str, + model_id: ModelId, + file_id: FileId, +) -> Option<&'ast Identifier> { + let model = ctx.db.walk((file_id, model_id)); + + match model.scalar_fields().find(|field| field.name() == name) { + Some(field) => Some(field.ast_field().identifier()), + None => None, + } +} + +fn find_where_used_for_native_type<'ast>( + ctx: &ReferencesContext<'ast>, + name: &'ast str, +) -> impl Iterator { + fn find_native_type_locations<'ast>( + name: &'ast str, + fields: impl Iterator + 'ast, + ) -> Box + 'ast> { + Box::new(fields.filter_map(move |field| { + field + .1 + .attributes + .iter() + .find(|attr| extract_ds_from_native_type(attr.name()) == Some(name)) + .map(|attr| attr.identifier()) + })) + } + + ctx.db.walk_tops().flat_map(move |top| match top.ast_top() { + Top::CompositeType(composite_type) => find_native_type_locations(name, composite_type.iter_fields()), + Top::Model(model) => find_native_type_locations(name, model.iter_fields()), + + Top::Enum(_) | Top::Source(_) | Top::Generator(_) => Box::new(empty_identifiers()), + }) +} + +fn find_where_used_as_field_type<'ast>( + ctx: &'ast ReferencesContext<'_>, + name: &'ast str, +) -> impl Iterator { + fn get_relevent_identifiers<'a>( + fields: impl Iterator, + name: &str, + ) -> Vec<&'a Identifier> { + fields + .filter_map(|(_id, field)| match &field.field_type { + FieldType::Supported(id) if id.name == name => Some(id), + _ => None, + }) + .collect() + } + + ctx.db.walk_tops().flat_map(|top| match top.ast_top() { + Top::Model(model) => get_relevent_identifiers(model.iter_fields(), name), + Top::CompositeType(composite_type) => get_relevent_identifiers(composite_type.iter_fields(), name), + // * Cannot contain field types + Top::Enum(_) | Top::Source(_) | Top::Generator(_) => vec![], + }) +} + +fn find_where_used_as_top_name<'ast>(ctx: &'ast ReferencesContext<'_>, name: &'ast str) -> Option<&'ast Identifier> { + ctx.db.find_top(name).map(|top| top.ast_top().identifier()) +} + +fn find_where_used_as_ds_name<'ast>(ctx: &'ast ReferencesContext<'_>, name: &'ast str) -> Option<&'ast Identifier> { + ctx.db + .find_source(name) + .map(|source| ctx.db.ast(source.0)[source.1].identifier()) +} + +fn extract_ds_from_native_type(attr_name: &str) -> Option<&str> { + attr_name.split('.').next() +} + +fn ident_to_location<'ast>(id: &'ast Identifier, ctx: &'ast ReferencesContext<'_>) -> Option { + let file_id = id.span.file_id; + + let source = ctx.db.source(file_id); + let range = span_to_range(id.span, source); + let file_name = ctx.db.file_name(file_id); + + let uri = if let Ok(uri) = Url::parse(file_name) { + uri + } else { + return None; + }; + + Some(Location { uri, range }) +} diff --git a/prisma-fmt/src/text_document_completion.rs b/prisma-fmt/src/text_document_completion.rs index d1234c80b40c..b791a69c3515 100644 --- a/prisma-fmt/src/text_document_completion.rs +++ b/prisma-fmt/src/text_document_completion.rs @@ -5,7 +5,8 @@ use lsp_types::*; use psl::{ diagnostics::Span, error_tolerant_parse_configuration, - parser_database::{ast, ParserDatabase, SourceFile}, + parser_database::{ast, ParserDatabase, ReferentialAction, SourceFile}, + schema_ast::ast::AttributePosition, Diagnostics, PreviewFeature, }; @@ -85,19 +86,46 @@ fn push_ast_completions(ctx: CompletionContext<'_>, completion_list: &mut Comple .relation_mode() .unwrap_or_else(|| ctx.connector().default_relation_mode()); - match ctx.db.ast(ctx.initiating_file_id).find_at_position(position) { + let find_at_position = ctx.db.ast(ctx.initiating_file_id).find_at_position(position); + + fn push_referential_action(completion_list: &mut CompletionList, referential_action: ReferentialAction) { + completion_list.items.push(CompletionItem { + label: referential_action.as_str().to_owned(), + kind: Some(CompletionItemKind::ENUM), + // what is the difference between detail and documentation? + detail: Some(referential_action.documentation().to_owned()), + ..Default::default() + }); + } + + match find_at_position { ast::SchemaPosition::Model( _model_id, - ast::ModelPosition::Field(_, ast::FieldPosition::Attribute("relation", _, Some(attr_name))), + ast::ModelPosition::Field( + _, + ast::FieldPosition::Attribute("relation", _, AttributePosition::Argument(attr_name)), + ), ) if attr_name == "onDelete" || attr_name == "onUpdate" => { for referential_action in ctx.connector().referential_actions(&relation_mode).iter() { - completion_list.items.push(CompletionItem { - label: referential_action.as_str().to_owned(), - kind: Some(CompletionItemKind::ENUM), - // what is the difference between detail and documentation? - detail: Some(referential_action.documentation().to_owned()), - ..Default::default() - }); + push_referential_action(completion_list, referential_action); + } + } + + ast::SchemaPosition::Model( + _model_id, + ast::ModelPosition::Field( + _, + ast::FieldPosition::Attribute("relation", _, AttributePosition::ArgumentValue(attr_name, value)), + ), + ) => { + if let Some(attr_name) = attr_name { + if attr_name == "onDelete" || attr_name == "onUpdate" { + ctx.connector() + .referential_actions(&relation_mode) + .iter() + .filter(|ref_action| ref_action.to_string().starts_with(&value)) + .for_each(|ref_action| push_referential_action(completion_list, ref_action)); + } } } diff --git a/prisma-fmt/tests/references/mod.rs b/prisma-fmt/tests/references/mod.rs new file mode 100644 index 000000000000..cf3a59fec326 --- /dev/null +++ b/prisma-fmt/tests/references/mod.rs @@ -0,0 +1,2 @@ +mod test_api; +mod tests; diff --git a/prisma-fmt/tests/references/scenarios/composite_type_as_type/a.prisma b/prisma-fmt/tests/references/scenarios/composite_type_as_type/a.prisma new file mode 100644 index 000000000000..0c4f8cbe2e10 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/composite_type_as_type/a.prisma @@ -0,0 +1,14 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +type Address { + city String + postCode String +} diff --git a/prisma-fmt/tests/references/scenarios/composite_type_as_type/b.prisma b/prisma-fmt/tests/references/scenarios/composite_type_as_type/b.prisma new file mode 100644 index 000000000000..92839a1f9ac3 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/composite_type_as_type/b.prisma @@ -0,0 +1,5 @@ +model User { + id String @id @map("_id") + authorId String + address Add<|>ress +} diff --git a/prisma-fmt/tests/references/scenarios/composite_type_as_type/result.json b/prisma-fmt/tests/references/scenarios/composite_type_as_type/result.json new file mode 100644 index 000000000000..d02299ab411e --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/composite_type_as_type/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 5 + }, + "end": { + "line": 10, + "character": 12 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 3, + "character": 11 + }, + "end": { + "line": 3, + "character": 18 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/composite_type_name/a.prisma b/prisma-fmt/tests/references/scenarios/composite_type_name/a.prisma new file mode 100644 index 000000000000..30df23f42ecb --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/composite_type_name/a.prisma @@ -0,0 +1,14 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +type Add<|>ress { +city String +postCode String +} diff --git a/prisma-fmt/tests/references/scenarios/composite_type_name/b.prisma b/prisma-fmt/tests/references/scenarios/composite_type_name/b.prisma new file mode 100644 index 000000000000..9864e134aaeb --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/composite_type_name/b.prisma @@ -0,0 +1,5 @@ +model User { + id String @id @map("_id") + authorId String + address Address +} diff --git a/prisma-fmt/tests/references/scenarios/composite_type_name/result.json b/prisma-fmt/tests/references/scenarios/composite_type_name/result.json new file mode 100644 index 000000000000..d02299ab411e --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/composite_type_name/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 5 + }, + "end": { + "line": 10, + "character": 12 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 3, + "character": 11 + }, + "end": { + "line": 3, + "character": 18 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/datasource_as_attribute/a.prisma b/prisma-fmt/tests/references/scenarios/datasource_as_attribute/a.prisma new file mode 100644 index 000000000000..a48d0d496a2f --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/datasource_as_attribute/a.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +enum Pet { + Cat + Dog + Redpanda +} diff --git a/prisma-fmt/tests/references/scenarios/datasource_as_attribute/b.prisma b/prisma-fmt/tests/references/scenarios/datasource_as_attribute/b.prisma new file mode 100644 index 000000000000..8406f54dbd58 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/datasource_as_attribute/b.prisma @@ -0,0 +1,4 @@ +model User { + id String @id @map("_id") @d<|>b.String + pet Pet +} diff --git a/prisma-fmt/tests/references/scenarios/datasource_as_attribute/result.json b/prisma-fmt/tests/references/scenarios/datasource_as_attribute/result.json new file mode 100644 index 000000000000..d9905f2e7ade --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/datasource_as_attribute/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 5, + "character": 11 + }, + "end": { + "line": 5, + "character": 13 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 1, + "character": 30 + }, + "end": { + "line": 1, + "character": 39 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/datasource_name/a.prisma b/prisma-fmt/tests/references/scenarios/datasource_name/a.prisma new file mode 100644 index 000000000000..201b7bd2c2cd --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/datasource_name/a.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource d<|>b { +provider = "mongodb" +url = env("DATABASE_URL") +} + +enum Pet { + Cat + Dog + Redpanda +} diff --git a/prisma-fmt/tests/references/scenarios/datasource_name/b.prisma b/prisma-fmt/tests/references/scenarios/datasource_name/b.prisma new file mode 100644 index 000000000000..16d0c16fe768 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/datasource_name/b.prisma @@ -0,0 +1,4 @@ +model User { + id String @id @map("_id") @db.String + pet Pet +} diff --git a/prisma-fmt/tests/references/scenarios/datasource_name/result.json b/prisma-fmt/tests/references/scenarios/datasource_name/result.json new file mode 100644 index 000000000000..d9905f2e7ade --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/datasource_name/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 5, + "character": 11 + }, + "end": { + "line": 5, + "character": 13 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 1, + "character": 30 + }, + "end": { + "line": 1, + "character": 39 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/enum_as_type/a.prisma b/prisma-fmt/tests/references/scenarios/enum_as_type/a.prisma new file mode 100644 index 000000000000..a48d0d496a2f --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/enum_as_type/a.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +enum Pet { + Cat + Dog + Redpanda +} diff --git a/prisma-fmt/tests/references/scenarios/enum_as_type/b.prisma b/prisma-fmt/tests/references/scenarios/enum_as_type/b.prisma new file mode 100644 index 000000000000..a8b01dd0dce0 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/enum_as_type/b.prisma @@ -0,0 +1,4 @@ +model User { + id String @id @map("_id") + pet P<|>et +} diff --git a/prisma-fmt/tests/references/scenarios/enum_as_type/result.json b/prisma-fmt/tests/references/scenarios/enum_as_type/result.json new file mode 100644 index 000000000000..8c2123cedda6 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/enum_as_type/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 5 + }, + "end": { + "line": 10, + "character": 8 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 2, + "character": 6 + }, + "end": { + "line": 2, + "character": 9 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/enum_name/a.prisma b/prisma-fmt/tests/references/scenarios/enum_name/a.prisma new file mode 100644 index 000000000000..d227fc11774b --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/enum_name/a.prisma @@ -0,0 +1,15 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +enum P<|>et { +Cat +Dog +Redpanda +} diff --git a/prisma-fmt/tests/references/scenarios/enum_name/b.prisma b/prisma-fmt/tests/references/scenarios/enum_name/b.prisma new file mode 100644 index 000000000000..0e2191c72b30 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/enum_name/b.prisma @@ -0,0 +1,4 @@ +model User { + id String @id @map("_id") + pet Pet +} diff --git a/prisma-fmt/tests/references/scenarios/enum_name/result.json b/prisma-fmt/tests/references/scenarios/enum_name/result.json new file mode 100644 index 000000000000..8c2123cedda6 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/enum_name/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 5 + }, + "end": { + "line": 10, + "character": 8 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 2, + "character": 6 + }, + "end": { + "line": 2, + "character": 9 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_as_type/a.prisma b/prisma-fmt/tests/references/scenarios/model_as_type/a.prisma new file mode 100644 index 000000000000..e050e2e32715 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_as_type/a.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +model UserTwo { + id String @id @map("_id") @db.String + + posts Post @relation(fields: [postId], references: [id]) + postId String +} diff --git a/prisma-fmt/tests/references/scenarios/model_as_type/b.prisma b/prisma-fmt/tests/references/scenarios/model_as_type/b.prisma new file mode 100644 index 000000000000..58f0d86dae09 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_as_type/b.prisma @@ -0,0 +1,5 @@ +model Post { + id String @id @map("_id") + authorId String + UserTwo User<|>Two[] +} \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_as_type/result.json b/prisma-fmt/tests/references/scenarios/model_as_type/result.json new file mode 100644 index 000000000000..3e31baf3b169 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_as_type/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 6 + }, + "end": { + "line": 10, + "character": 13 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 3, + "character": 11 + }, + "end": { + "line": 3, + "character": 18 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_name/a.prisma b/prisma-fmt/tests/references/scenarios/model_name/a.prisma new file mode 100644 index 000000000000..8e723bdc9e5c --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_name/a.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +model User<|>Two { +id String @id @map("_id") @db.String + +posts Post @relation(fields: [postId], references: [id]) +postId String +} diff --git a/prisma-fmt/tests/references/scenarios/model_name/b.prisma b/prisma-fmt/tests/references/scenarios/model_name/b.prisma new file mode 100644 index 000000000000..107a84502a96 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_name/b.prisma @@ -0,0 +1,5 @@ +model Post { + id String @id @map("_id") + authorId String + UserTwo UserTwo[] +} \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_name/result.json b/prisma-fmt/tests/references/scenarios/model_name/result.json new file mode 100644 index 000000000000..3e31baf3b169 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_name/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 6 + }, + "end": { + "line": 10, + "character": 13 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 3, + "character": 11 + }, + "end": { + "line": 3, + "character": 18 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_relation_fields/a.prisma b/prisma-fmt/tests/references/scenarios/model_relation_fields/a.prisma new file mode 100644 index 000000000000..835f01b07f01 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_fields/a.prisma @@ -0,0 +1,7 @@ +model Compound { + id String + name String + UserTwo UserTwo[] + + @@id([id, name]) +} diff --git a/prisma-fmt/tests/references/scenarios/model_relation_fields/b.prisma b/prisma-fmt/tests/references/scenarios/model_relation_fields/b.prisma new file mode 100644 index 000000000000..7308f18cd5b3 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_fields/b.prisma @@ -0,0 +1,7 @@ +model UserTwo { + id String @id @map("_id") + + compounds Compound @relation(fields: [comp<|>oundId, compoundName], references: [id, name]) + compoundId String + compoundName String +} diff --git a/prisma-fmt/tests/references/scenarios/model_relation_fields/config.prisma b/prisma-fmt/tests/references/scenarios/model_relation_fields/config.prisma new file mode 100644 index 000000000000..523204f6f2f5 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_fields/config.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} diff --git a/prisma-fmt/tests/references/scenarios/model_relation_fields/result.json b/prisma-fmt/tests/references/scenarios/model_relation_fields/result.json new file mode 100644 index 000000000000..fbbe001172cf --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_fields/result.json @@ -0,0 +1,15 @@ +[ + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 4, + "character": 4 + }, + "end": { + "line": 4, + "character": 14 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_relation_references/a.prisma b/prisma-fmt/tests/references/scenarios/model_relation_references/a.prisma new file mode 100644 index 000000000000..835f01b07f01 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_references/a.prisma @@ -0,0 +1,7 @@ +model Compound { + id String + name String + UserTwo UserTwo[] + + @@id([id, name]) +} diff --git a/prisma-fmt/tests/references/scenarios/model_relation_references/b.prisma b/prisma-fmt/tests/references/scenarios/model_relation_references/b.prisma new file mode 100644 index 000000000000..ff5c52bdc6d1 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_references/b.prisma @@ -0,0 +1,7 @@ +model UserTwo { + id String @id @map("_id") + + compounds Compound @relation(fields: [compoundId, compoundName], references: [id, n<|>ame]) + compoundId String + compoundName String +} diff --git a/prisma-fmt/tests/references/scenarios/model_relation_references/config.prisma b/prisma-fmt/tests/references/scenarios/model_relation_references/config.prisma new file mode 100644 index 000000000000..523204f6f2f5 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_references/config.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} diff --git a/prisma-fmt/tests/references/scenarios/model_relation_references/result.json b/prisma-fmt/tests/references/scenarios/model_relation_references/result.json new file mode 100644 index 000000000000..334857e0832c --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_relation_references/result.json @@ -0,0 +1,15 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 2, + "character": 4 + }, + "end": { + "line": 2, + "character": 8 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_unique_fields/result.json b/prisma-fmt/tests/references/scenarios/model_unique_fields/result.json new file mode 100644 index 000000000000..6908574aac86 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_unique_fields/result.json @@ -0,0 +1,15 @@ +[ + { + "uri": "file:///path/to/schema.prisma", + "range": { + "start": { + "line": 11, + "character": 4 + }, + "end": { + "line": 11, + "character": 8 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/model_unique_fields/schema.prisma b/prisma-fmt/tests/references/scenarios/model_unique_fields/schema.prisma new file mode 100644 index 000000000000..d0194d75b6e6 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/model_unique_fields/schema.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} + +model Compound { + id String + name String + + @@unique([id, n<|>ame]) +} + diff --git a/prisma-fmt/tests/references/scenarios/view_as_type/a.prisma b/prisma-fmt/tests/references/scenarios/view_as_type/a.prisma new file mode 100644 index 000000000000..ffd958d2799d --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_as_type/a.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +view UserTwo { + id String @id @map("_id") @db.String + + posts Post @relation(fields: [postId], references: [id]) + postId String +} \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_as_type/b.prisma b/prisma-fmt/tests/references/scenarios/view_as_type/b.prisma new file mode 100644 index 000000000000..58f0d86dae09 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_as_type/b.prisma @@ -0,0 +1,5 @@ +model Post { + id String @id @map("_id") + authorId String + UserTwo User<|>Two[] +} \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_as_type/result.json b/prisma-fmt/tests/references/scenarios/view_as_type/result.json new file mode 100644 index 000000000000..d02299ab411e --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_as_type/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 5 + }, + "end": { + "line": 10, + "character": 12 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 3, + "character": 11 + }, + "end": { + "line": 3, + "character": 18 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_index_fields/result.json b/prisma-fmt/tests/references/scenarios/view_index_fields/result.json new file mode 100644 index 000000000000..6908574aac86 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_index_fields/result.json @@ -0,0 +1,15 @@ +[ + { + "uri": "file:///path/to/schema.prisma", + "range": { + "start": { + "line": 11, + "character": 4 + }, + "end": { + "line": 11, + "character": 8 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_index_fields/schema.prisma b/prisma-fmt/tests/references/scenarios/view_index_fields/schema.prisma new file mode 100644 index 000000000000..df22e6509fba --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_index_fields/schema.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} + +model Compound { + id String + name String + + @@unique(fields: [id, n<|>ame]) +} + diff --git a/prisma-fmt/tests/references/scenarios/view_name/a.prisma b/prisma-fmt/tests/references/scenarios/view_name/a.prisma new file mode 100644 index 000000000000..dfb42c85b4b8 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_name/a.prisma @@ -0,0 +1,16 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +view User<|>Two { + id String @id @map("_id") @db.String + + posts Post @relation(fields: [postId], references: [id]) + postId String +} \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_name/b.prisma b/prisma-fmt/tests/references/scenarios/view_name/b.prisma new file mode 100644 index 000000000000..107a84502a96 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_name/b.prisma @@ -0,0 +1,5 @@ +model Post { + id String @id @map("_id") + authorId String + UserTwo UserTwo[] +} \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_name/result.json b/prisma-fmt/tests/references/scenarios/view_name/result.json new file mode 100644 index 000000000000..d02299ab411e --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_name/result.json @@ -0,0 +1,28 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 10, + "character": 5 + }, + "end": { + "line": 10, + "character": 12 + } + } + }, + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 3, + "character": 11 + }, + "end": { + "line": 3, + "character": 18 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_relation_fields/a.prisma b/prisma-fmt/tests/references/scenarios/view_relation_fields/a.prisma new file mode 100644 index 000000000000..8d0604d42728 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_fields/a.prisma @@ -0,0 +1,7 @@ +view Compound { + id String + name String + UserTwo UserTwo[] + + @@id([id, name]) +} diff --git a/prisma-fmt/tests/references/scenarios/view_relation_fields/b.prisma b/prisma-fmt/tests/references/scenarios/view_relation_fields/b.prisma new file mode 100644 index 000000000000..5d7d155b70bb --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_fields/b.prisma @@ -0,0 +1,7 @@ +view UserTwo { + id String @id @map("_id") + + compounds Compound @relation(fields: [comp<|>oundId, compoundName], references: [id, name]) + compoundId String + compoundName String +} diff --git a/prisma-fmt/tests/references/scenarios/view_relation_fields/config.prisma b/prisma-fmt/tests/references/scenarios/view_relation_fields/config.prisma new file mode 100644 index 000000000000..523204f6f2f5 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_fields/config.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} diff --git a/prisma-fmt/tests/references/scenarios/view_relation_fields/result.json b/prisma-fmt/tests/references/scenarios/view_relation_fields/result.json new file mode 100644 index 000000000000..fbbe001172cf --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_fields/result.json @@ -0,0 +1,15 @@ +[ + { + "uri": "file:///path/to/b.prisma", + "range": { + "start": { + "line": 4, + "character": 4 + }, + "end": { + "line": 4, + "character": 14 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/scenarios/view_relation_references/a.prisma b/prisma-fmt/tests/references/scenarios/view_relation_references/a.prisma new file mode 100644 index 000000000000..8d0604d42728 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_references/a.prisma @@ -0,0 +1,7 @@ +view Compound { + id String + name String + UserTwo UserTwo[] + + @@id([id, name]) +} diff --git a/prisma-fmt/tests/references/scenarios/view_relation_references/b.prisma b/prisma-fmt/tests/references/scenarios/view_relation_references/b.prisma new file mode 100644 index 000000000000..17e068b4d574 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_references/b.prisma @@ -0,0 +1,7 @@ +view UserTwo { + id String @id @map("_id") + + compounds Compound @relation(fields: [compoundId, compoundName], references: [id, n<|>ame]) + compoundId String + compoundName String +} diff --git a/prisma-fmt/tests/references/scenarios/view_relation_references/config.prisma b/prisma-fmt/tests/references/scenarios/view_relation_references/config.prisma new file mode 100644 index 000000000000..523204f6f2f5 --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_references/config.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} diff --git a/prisma-fmt/tests/references/scenarios/view_relation_references/result.json b/prisma-fmt/tests/references/scenarios/view_relation_references/result.json new file mode 100644 index 000000000000..334857e0832c --- /dev/null +++ b/prisma-fmt/tests/references/scenarios/view_relation_references/result.json @@ -0,0 +1,15 @@ +[ + { + "uri": "file:///path/to/a.prisma", + "range": { + "start": { + "line": 2, + "character": 4 + }, + "end": { + "line": 2, + "character": 8 + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/references/test_api.rs b/prisma-fmt/tests/references/test_api.rs new file mode 100644 index 000000000000..4fe7c0ffbc9d --- /dev/null +++ b/prisma-fmt/tests/references/test_api.rs @@ -0,0 +1,149 @@ +use crate::helpers::load_schema_files; +use once_cell::sync::Lazy; +use std::{fmt::Write as _, io::Write as _}; + +const CURSOR_MARKER: &str = "<|>"; +const SCENARIOS_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/references/scenarios"); +static UPDATE_EXPECT: Lazy = Lazy::new(|| std::env::var("UPDATE_EXPECT").is_ok()); + +pub(crate) fn test_scenario(scenario_name: &str) { + let mut path = String::with_capacity(SCENARIOS_PATH.len() + 12); + + let schema_files = { + write!(path, "{SCENARIOS_PATH}/{scenario_name}").unwrap(); + load_schema_files(&path) + }; + + path.clear(); + write!(path, "{SCENARIOS_PATH}/{scenario_name}/result.json").unwrap(); + let expected_result = std::fs::read_to_string(&path).unwrap_or_else(|_| String::new()); + + let (initiating_file_uri, cursor_position, schema_files) = take_cursor(schema_files); + + let params = lsp_types::ReferenceParams { + text_document_position: lsp_types::TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: initiating_file_uri.parse().unwrap(), + }, + position: cursor_position, + }, + work_done_progress_params: lsp_types::WorkDoneProgressParams { work_done_token: None }, + partial_result_params: lsp_types::PartialResultParams { + partial_result_token: None, + }, + context: lsp_types::ReferenceContext { + include_declaration: true, + }, + }; + + let result = prisma_fmt::references( + serde_json::to_string_pretty(&schema_files).unwrap(), + &serde_json::to_string_pretty(¶ms).unwrap(), + ); + // Prettify the JSON + let result = + serde_json::to_string_pretty(&serde_json::from_str::>(&result).unwrap()).unwrap(); + + if *UPDATE_EXPECT { + let mut file = std::fs::File::create(&path).unwrap(); // truncate + file.write_all(result.as_bytes()).unwrap(); + } else if expected_result != result { + let chunks = dissimilar::diff(&expected_result, &result); + panic!( + r#" +Snapshot comparison failed. Run the test again with UPDATE_EXPECT=1 in the environment to update the snapshot. + +===== EXPECTED ==== +{} +====== FOUND ====== +{} +======= DIFF ====== +{} +"#, + expected_result, + result, + format_chunks(chunks), + ); + } +} + +fn format_chunks(chunks: Vec) -> String { + let mut buf = String::new(); + for chunk in chunks { + let formatted = match chunk { + dissimilar::Chunk::Equal(text) => text.into(), + dissimilar::Chunk::Delete(text) => format!("\x1b[41m{text}\x1b[0m"), + dissimilar::Chunk::Insert(text) => format!("\x1b[42m{text}\x1b[0m"), + }; + buf.push_str(&formatted); + } + buf +} + +fn take_cursor(schema_files: Vec<(String, String)>) -> (String, lsp_types::Position, Vec<(String, String)>) { + let mut result = Vec::with_capacity(schema_files.len()); + let mut file_and_pos = None; + for (file_name, content) in schema_files { + if let Some((pos, without_cursor)) = take_cursor_one(&content) { + file_and_pos = Some((file_name.clone(), pos)); + result.push((file_name, without_cursor)); + } else { + result.push((file_name, content)); + } + } + + let (file_name, position) = file_and_pos.expect("Could not find a cursor in any of the schema files"); + + (file_name, position, result) +} + +fn take_cursor_one(schema: &str) -> Option<(lsp_types::Position, String)> { + let mut schema_without_cursor = String::with_capacity(schema.len() - 3); + let mut cursor_position = lsp_types::Position { character: 0, line: 0 }; + let mut cursor_found = false; + for line in schema.lines() { + if !cursor_found { + if let Some(pos) = line.find(CURSOR_MARKER) { + cursor_position.character = pos as u32; + cursor_found = true; + schema_without_cursor.push_str(&line[..pos]); + schema_without_cursor.push_str(&line[pos + 3..]); + schema_without_cursor.push('\n'); + } else { + schema_without_cursor.push_str(line); + schema_without_cursor.push('\n'); + cursor_position.line += 1; + } + } else { + schema_without_cursor.push_str(line); + schema_without_cursor.push('\n'); + } + } + + if !cursor_found { + return None; + } + // remove extra newline + schema_without_cursor.truncate(schema_without_cursor.len() - 1); + + Some((cursor_position, schema_without_cursor)) +} + +#[test] +fn take_cursor_works() { + let schema = r#" + model Test { + id Int @id @map(<|>) + } + "#; + let expected_schema = r#" + model Test { + id Int @id @map() + } + "#; + + let (pos, schema) = take_cursor_one(schema).unwrap(); + assert_eq!(pos.line, 2); + assert_eq!(pos.character, 28); + assert_eq!(schema, expected_schema); +} diff --git a/prisma-fmt/tests/references/tests.rs b/prisma-fmt/tests/references/tests.rs new file mode 100644 index 000000000000..c32a833f12be --- /dev/null +++ b/prisma-fmt/tests/references/tests.rs @@ -0,0 +1,31 @@ +use super::test_api::test_scenario; + +macro_rules! scenarios { + ($($scenario_name:ident)+) => { + $( + #[test] + fn $scenario_name() { + test_scenario(stringify!($scenario_name)) + } + )* + } +} + +scenarios! { + composite_type_as_type + composite_type_name + enum_as_type + enum_name + model_as_type + model_name + model_relation_fields + model_relation_references + model_unique_fields + view_as_type + view_index_fields + view_name + view_relation_fields + view_relation_references + datasource_as_attribute + datasource_name +} diff --git a/prisma-fmt/tests/references_tests.rs b/prisma-fmt/tests/references_tests.rs new file mode 100644 index 000000000000..7b6416981551 --- /dev/null +++ b/prisma-fmt/tests/references_tests.rs @@ -0,0 +1,2 @@ +mod helpers; +mod references; diff --git a/prisma-fmt/tests/text_document_completion/scenarios/default_map_mssql_in_arg_value/result.json b/prisma-fmt/tests/text_document_completion/scenarios/default_map_mssql_in_arg_value/result.json new file mode 100644 index 000000000000..cd41656e5303 --- /dev/null +++ b/prisma-fmt/tests/text_document_completion/scenarios/default_map_mssql_in_arg_value/result.json @@ -0,0 +1,4 @@ +{ + "isIncomplete": false, + "items": [] +} \ No newline at end of file diff --git a/prisma-fmt/tests/text_document_completion/scenarios/default_map_mssql_in_arg_value/schema.prisma b/prisma-fmt/tests/text_document_completion/scenarios/default_map_mssql_in_arg_value/schema.prisma new file mode 100644 index 000000000000..444e86c947ab --- /dev/null +++ b/prisma-fmt/tests/text_document_completion/scenarios/default_map_mssql_in_arg_value/schema.prisma @@ -0,0 +1,10 @@ +datasource db { + provider = "sqlserver" + url = env("DATABASE_URL") +} + +model User { + id String @id + + postId String @unique @default("defaul<|>t_value") +} diff --git a/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress/result.json b/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress/result.json index 0d1b8ac130e1..8b1a51e6b215 100644 --- a/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress/result.json +++ b/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress/result.json @@ -5,21 +5,6 @@ "label": "Cascade", "kind": 13, "detail": "Delete the child records when the parent record is deleted." - }, - { - "label": "NoAction", - "kind": 13, - "detail": "Prevent deleting a parent record as long as it is referenced." - }, - { - "label": "SetNull", - "kind": 13, - "detail": "Set the referencing fields to NULL when the referenced record is deleted." - }, - { - "label": "SetDefault", - "kind": 13, - "detail": "Set the referencing field's value to the default when the referenced record is deleted." } ] } \ No newline at end of file diff --git a/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress_2/result.json b/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress_2/result.json index 24180484bbd2..5609cdf1e9eb 100644 --- a/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress_2/result.json +++ b/prisma-fmt/tests/text_document_completion/scenarios/referential_actions_in_progress_2/result.json @@ -1,30 +1,10 @@ { "isIncomplete": false, "items": [ - { - "label": "Cascade", - "kind": 13, - "detail": "Delete the child records when the parent record is deleted." - }, { "label": "Restrict", "kind": 13, "detail": "Prevent deleting a parent record as long as it is referenced." - }, - { - "label": "NoAction", - "kind": 13, - "detail": "Prevent deleting a parent record as long as it is referenced." - }, - { - "label": "SetNull", - "kind": 13, - "detail": "Set the referencing fields to NULL when the referenced record is deleted." - }, - { - "label": "SetDefault", - "kind": 13, - "detail": "Set the referencing field's value to the default when the referenced record is deleted." } ] } \ No newline at end of file diff --git a/prisma-fmt/tests/text_document_completion/tests.rs b/prisma-fmt/tests/text_document_completion/tests.rs index c1332e6f9d41..1066c32fdfbb 100644 --- a/prisma-fmt/tests/text_document_completion/tests.rs +++ b/prisma-fmt/tests/text_document_completion/tests.rs @@ -15,6 +15,7 @@ scenarios! { argument_after_trailing_comma default_map_end_of_args_list default_map_mssql + default_map_mssql_in_arg_value default_map_mssql_multifile empty_schema extended_indexes_basic diff --git a/prisma-schema-wasm/src/lib.rs b/prisma-schema-wasm/src/lib.rs index c331170b2366..5cbb08067877 100644 --- a/prisma-schema-wasm/src/lib.rs +++ b/prisma-schema-wasm/src/lib.rs @@ -86,7 +86,7 @@ pub fn preview_features() -> String { } /// The API is modelled on an LSP [completion -/// request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#textDocument_completion). +/// request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#completion-request-leftwards_arrow_with_hook). /// Input and output are both JSON, the request being a `CompletionParams` object and the response /// being a `CompletionList` object. #[wasm_bindgen] @@ -96,7 +96,7 @@ pub fn text_document_completion(schema_files: String, params: String) -> String } /// This API is modelled on an LSP [code action -/// request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#textDocument_codeAction=). +/// request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#code-action-request-leftwards_arrow_with_hook). /// Input and output are both JSON, the request being a /// `CodeActionParams` object and the response being a list of /// `CodeActionOrCommand` objects. @@ -106,6 +106,17 @@ pub fn code_actions(schema: String, params: String) -> String { prisma_fmt::code_actions(schema, ¶ms) } +/// This API is modelled on an LSP [references +/// request](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#find-references-request-leftwards_arrow_with_hook). +/// Input and output are both JSON, the request being a +/// `CodeActionParams` object and the response being a list of +/// `CodeActionOrCommand` objects. +#[wasm_bindgen] +pub fn references(schema: String, params: String) -> String { + register_panic_hook(); + prisma_fmt::references(schema, ¶ms) +} + /// Trigger a panic inside the wasm module. This is only useful in development for testing panic /// handling. #[wasm_bindgen] diff --git a/psl/parser-database/src/walkers.rs b/psl/parser-database/src/walkers.rs index ee1058f9c2fc..dd8cf2504eae 100644 --- a/psl/parser-database/src/walkers.rs +++ b/psl/parser-database/src/walkers.rs @@ -75,7 +75,15 @@ impl crate::ParserDatabase { .flat_map(move |(file_id, _, _, ast)| ast.iter_tops().map(move |(top_id, top)| (file_id, top_id, top))) } - /// Intern any top by name. + /// Find the datasource by name. + pub fn find_source(&self, name: &str) -> Option<(FileId, ast::TopId)> { + self.interner + .lookup(name) + .and_then(|name_id| self.names.datasources.get(&name_id)) + .map(|(file_id, top_id)| (*file_id, *top_id)) + } + + /// Find any top by name. pub fn find_top<'db>(&'db self, name: &str) -> Option> { self.interner .lookup(name) diff --git a/psl/psl-core/src/builtin_connectors/mssql_datamodel_connector.rs b/psl/psl-core/src/builtin_connectors/mssql_datamodel_connector.rs index 6eb554787b84..d55b1429e23a 100644 --- a/psl/psl-core/src/builtin_connectors/mssql_datamodel_connector.rs +++ b/psl/psl-core/src/builtin_connectors/mssql_datamodel_connector.rs @@ -262,7 +262,10 @@ impl Connector for MsSqlDatamodelConnector { ) { if let ast::SchemaPosition::Model( _model_id, - ast::ModelPosition::Field(_, ast::FieldPosition::Attribute("default", _, None)), + ast::ModelPosition::Field( + _, + ast::FieldPosition::Attribute("default", _, ast::AttributePosition::Attribute), + ), ) = position { completions.items.push(CompletionItem { diff --git a/psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs b/psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs index 22badc3e0363..fa1559c33c2b 100644 --- a/psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs +++ b/psl/psl-core/src/builtin_connectors/postgres_datamodel_connector.rs @@ -495,7 +495,7 @@ impl Connector for PostgresDatamodelConnector { ast::ModelPosition::ModelAttribute( "index", attr_id, - ast::AttributePosition::FunctionArgument(field_name, "ops"), + ast::AttributePosition::FunctionArgument(field_name, "ops", _), ), ) => { // let's not care about composite field indices yet diff --git a/psl/schema-ast/src/ast/argument.rs b/psl/schema-ast/src/ast/argument.rs index 9272196780e9..a96f05cfa384 100644 --- a/psl/schema-ast/src/ast/argument.rs +++ b/psl/schema-ast/src/ast/argument.rs @@ -68,6 +68,13 @@ impl Argument { pub fn is_unnamed(&self) -> bool { self.name.is_none() } + + pub fn name(&self) -> Option<&str> { + match &self.name { + Some(ident) => Some(ident.name.as_str()), + None => None, + } + } } impl WithSpan for Argument { diff --git a/psl/schema-ast/src/ast/find_at_position/attribute.rs b/psl/schema-ast/src/ast/find_at_position/attribute.rs index 07dea044176c..fd1ea3ffb31d 100644 --- a/psl/schema-ast/src/ast/find_at_position/attribute.rs +++ b/psl/schema-ast/src/ast/find_at_position/attribute.rs @@ -9,50 +9,60 @@ pub enum AttributePosition<'ast> { Attribute, /// In an argument. (argument name) Argument(&'ast str), - /// In an function argument. (function name, argument name) - FunctionArgument(&'ast str, &'ast str), + /// In an argument's value. (argument name, value) + ArgumentValue(Option<&'ast str>, String), + /// In an function argument. (function name, argument name, argument value) + FunctionArgument(&'ast str, &'ast str, String), } 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); - } + 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(); - // 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; - } + 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, arg.value.to_string()); } - 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); + if arg.value.is_array() { + let arr = arg.value.as_array().unwrap(); + let expr = arr.0.iter().find(|expr| expr.span().contains(position)); + if let Some(expr) = expr { + return Self::ArgumentValue(arg.name(), expr.to_string()); } } + + return Self::ArgumentValue(arg.name(), arg.value.to_string()); + + // Self::ArgumentValue(arg_name, ()) + } + + if let Some(arg_name) = arg_name.flatten() { + return Self::Argument(arg_name); } Self::Attribute diff --git a/psl/schema-ast/src/ast/find_at_position/field.rs b/psl/schema-ast/src/ast/find_at_position/field.rs index 3c358f3c6da7..97995fad1678 100644 --- a/psl/schema-ast/src/ast/find_at_position/field.rs +++ b/psl/schema-ast/src/ast/find_at_position/field.rs @@ -1,6 +1,6 @@ use crate::ast::{self}; -use super::{WithName, WithSpan}; +use super::{AttributePosition, WithName, WithSpan}; /// In a scalar field. #[derive(Debug)] @@ -33,7 +33,8 @@ pub enum FieldPosition<'ast> { /// field Float /// } /// ``` - Attribute(&'ast str, usize, Option<&'ast str>), + // Attribute(&'ast str, usize, Option<&'ast str>), + Attribute(&'ast str, usize, AttributePosition<'ast>), } impl<'ast> FieldPosition<'ast> { @@ -50,32 +51,7 @@ impl<'ast> FieldPosition<'ast> { 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()); + return FieldPosition::Attribute(attr.name(), attr_idx, AttributePosition::new(attr, position)); } }