Skip to content

Commit

Permalink
Datasource Spans & Completions (#3703)
Browse files Browse the repository at this point in the history
* Added datasource completions from LT to prisma-fmt

* Completions for DS now available at start of line

* ds completions no longer available outside of the closing curly-brace

* Updated the rest of the grammar and parsing logic
This follows the changes for config blocks

Co-authored-by: Tom Houlé <13155277+tomhoule@users.noreply.github.com>

* Updated reformatter to handle inner_contents
Update test expects

Co-authored-by: Tom Houlé <13155277+tomhoule@users.noreply.github.com>

* fix prisma-fmt tests to reflect span changes

* added tests for default ds completions and multischema

* Added: sub-completions for xyz_url
new datasource positions

* Update: config values now Optional
Added: new diagnostic for props without value

Co-authored-by: Tom Houlé <13155277+tomhoule@users.noreply.github.com>

* Added tests for url argument completions

* Updated get_config test snapshots

* Added support for params to pretty_doc

* Added XYZ_URL completion for `env()`
Added tests

* Updated completion test snapshots due to params

* Remove engines tag from completion labels

* removed commented line

* update `env` doc to mention db pull

* extensions completion

* validation to only offer completions for what's not already there

* `add_quotes` for envar name completions

* Updated Tests:
env -> env() per what was in LT
XYZ_URL kind -> 21, per what was in LT
db gen > db pull
completions no longer re-show-up
formatting

* missed change in merge

* no fancy features allowed

* revert direct_url span back to just the arg

* Clippy complaining about unused inner_span for models and views
- this will need to be re-added later when models and views are updated

* formatting

* Update ds and connector to check prop definitions

* cleanup unused

* Added connector specific completion capabilities.
Moved extensions and schemas (only postgres).

Co-authored-by: Julius de Bruijn <bruijn@prisma.io>

* Add schemas completions to other connectors
- mssql, mysql, cockroachdb
Moved connector completions to builtin-connectors crate

* rename push_completions -> datamodel_completions
formatting

---------

Co-authored-by: Tom Houlé <13155277+tomhoule@users.noreply.github.com>
Co-authored-by: Julius de Bruijn <bruijn@prisma.io>
  • Loading branch information
3 people authored Mar 16, 2023
1 parent 1c2b092 commit 3b9f029
Show file tree
Hide file tree
Showing 56 changed files with 1,208 additions and 291 deletions.
2 changes: 1 addition & 1 deletion prisma-fmt/src/get_dmmf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ mod tests {
});

let expected = expect![[
r#"{"error_code":"P1012","message":"\u001b[1;91merror\u001b[0m: \u001b[1mThe `referentialIntegrity` and `relationMode` attributes cannot be used together. Please use only `relationMode` instead.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m relationMode = \"prisma\"\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91mreferentialIntegrity = \"foreignKeys\"\u001b[0m\n\u001b[1;94m 7 | \u001b[0m }\n\u001b[1;94m | \u001b[0m\n\nValidation Error Count: 1"}"#
r#"{"error_code":"P1012","message":"\u001b[1;91merror\u001b[0m: \u001b[1mThe `referentialIntegrity` and `relationMode` attributes cannot be used together. Please use only `relationMode` instead.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m relationMode = \"prisma\"\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91mreferentialIntegrity = \"foreignKeys\"\u001b[0m\n\u001b[1;94m | \u001b[0m\n\nValidation Error Count: 1"}"#
]];
let response = get_dmmf(&request.to_string()).unwrap_err();
expected.assert_eq(&response);
Expand Down
67 changes: 66 additions & 1 deletion prisma-fmt/src/text_document_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use std::sync::Arc;

use crate::position_to_offset;

mod datasource;

pub(crate) fn empty_completion_list() -> CompletionList {
CompletionList {
is_incomplete: true,
Expand Down Expand Up @@ -119,7 +121,70 @@ fn push_ast_completions(ctx: CompletionContext<'_>, completion_list: &mut Comple
push_namespaces(ctx, completion_list);
}

position => ctx.connector().push_completions(ctx.db, position, completion_list),
ast::SchemaPosition::DataSource(_source_id, ast::SourcePosition::Source) => {
if !ds_has_prop(ctx, "provider") {
datasource::provider_completion(completion_list);
}

if !ds_has_prop(ctx, "url") {
datasource::url_completion(completion_list);
}

if !ds_has_prop(ctx, "shadowDatabaseUrl") {
datasource::shadow_db_completion(completion_list);
}

if !ds_has_prop(ctx, "directUrl") {
datasource::direct_url_completion(completion_list);
}

if !ds_has_prop(ctx, "relationMode") {
datasource::relation_mode_completion(completion_list);
}

if let Some(config) = ctx.config {
ctx.connector().datasource_completions(config, completion_list);
}
}

ast::SchemaPosition::DataSource(
_source_id,
ast::SourcePosition::Property("url", ast::PropertyPosition::FunctionValue("env")),
) => datasource::url_env_db_completion(completion_list, "url", ctx),

ast::SchemaPosition::DataSource(
_source_id,
ast::SourcePosition::Property("directUrl", ast::PropertyPosition::FunctionValue("env")),
) => datasource::url_env_db_completion(completion_list, "directUrl", ctx),

ast::SchemaPosition::DataSource(
_source_id,
ast::SourcePosition::Property("shadowDatabaseUrl", ast::PropertyPosition::FunctionValue("env")),
) => datasource::url_env_db_completion(completion_list, "shadowDatabaseUrl", ctx),

ast::SchemaPosition::DataSource(_source_id, ast::SourcePosition::Property("url", _))
| ast::SchemaPosition::DataSource(_source_id, ast::SourcePosition::Property("directUrl", _))
| ast::SchemaPosition::DataSource(_source_id, ast::SourcePosition::Property("shadowDatabaseUrl", _)) => {
datasource::url_env_completion(completion_list);
datasource::url_quotes_completion(completion_list);
}

position => ctx.connector().datamodel_completions(ctx.db, position, completion_list),
}
}

fn ds_has_prop(ctx: CompletionContext<'_>, prop: &str) -> bool {
if let Some(ds) = ctx.datasource() {
match prop {
"relationMode" => ds.relation_mode_defined(),
"directurl" => ds.direct_url_defined(),
"shadowDatabaseUrl" => ds.shadow_url_defined(),
"url" => ds.url_defined(),
"provider" => ds.provider_defined(),
_ => false,
}
} else {
false
}
}

Expand Down
160 changes: 160 additions & 0 deletions prisma-fmt/src/text_document_completion/datasource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use std::collections::HashMap;

use lsp_types::{
CompletionItem, CompletionItemKind, CompletionList, Documentation, InsertTextFormat, MarkupContent, MarkupKind,
};
use psl::datamodel_connector::format_completion_docs;

use super::{add_quotes, CompletionContext};

pub(super) fn relation_mode_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: "relationMode".to_owned(),
insert_text: Some(r#"relationmode = $0"#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::FIELD),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#"relationMode = "foreignKeys" | "prisma""#,
r#"Set the global relation mode for all relations. Values can be either "foreignKeys" (Default), or "prisma". [Learn more](https://pris.ly/d/relation-mode)"#,
None,
),
})),
..Default::default()
})
}

pub(super) fn direct_url_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: "directUrl".to_owned(),
insert_text: Some(r#"directUrl = $0"#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::FIELD),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#"directUrl = "String" | env("ENVIRONMENT_VARIABLE")"#,
r#"Connection URL for direct connection to the database. [Learn more](https://pris.ly/d/data-proxy-cli)."#,
None,
)
})),
..Default::default()
})
}

pub(super) fn shadow_db_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: "shadowDatabaseUrl".to_owned(),
insert_text: Some(r#"shadowDatabaseUrl = $0"#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::FIELD),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#"shadowDatabaseUrl = "String" | env("ENVIRONMENT_VARIABLE")"#,
r#"Connection URL including authentication info to use for Migrate's [shadow database](https://pris.ly/d/migrate-shadow)."#,
None,
),
})),
..Default::default()
})
}

pub(super) fn url_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: "url".to_owned(),
insert_text: Some(r#"url = $0"#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::FIELD),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#"url = "String" | env("ENVIRONMENT_VARIABLE")"#,
r#"Connection URL including authentication info. Each datasource provider documents the URL syntax. Most providers use the syntax provided by the database. [Learn more](https://pris.ly/d/connection-strings)."#,
None,
),
})),
..Default::default()
})
}

pub(super) fn provider_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: "provider".to_owned(),
insert_text: Some(r#"provider = $0"#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::FIELD),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#"provider = "foo""#,
r#"Describes which datasource connector to use. Can be one of the following datasource providers: `postgresql`, `mysql`, `sqlserver`, `sqlite`, `mongodb` or `cockroachdb`."#,
None,
),
})),
..Default::default()
})
}

pub(super) fn url_env_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: "env()".to_owned(),
insert_text: Some(r#"env($0)"#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::PROPERTY),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#"env(_ environmentVariable: string)"#,
r#"Specifies a datasource via an environment variable. When running a Prisma CLI command that needs the database connection URL (e.g. `prisma db pull`), you need to make sure that the `DATABASE_URL` environment variable is set. One way to do so is by creating a `.env` file. Note that the file must be in the same directory as your schema.prisma file to automatically be picked up by the Prisma CLI.""#,
Some(HashMap::from([(
"environmentVariable",
"The environment variable in which the database connection URL is stored.",
)])),
),
})),
..Default::default()
})
}

pub(super) fn url_quotes_completion(completion_list: &mut CompletionList) {
completion_list.items.push(CompletionItem {
label: r#""""#.to_owned(),
insert_text: Some(r#""$0""#.to_owned()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
kind: Some(CompletionItemKind::PROPERTY),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format_completion_docs(
r#""connectionString""#,
r#"Connection URL including authentication info. Each datasource provider documents the URL syntax. Most providers use the syntax provided by the database. [Learn more](https://pris.ly/d/prisma-schema)."#,
None,
),
})),
..Default::default()
})
}

pub(super) fn url_env_db_completion(completion_list: &mut CompletionList, kind: &str, ctx: CompletionContext<'_>) {
let text = match kind {
"url" => "DATABASE_URL",
"directUrl" => "DIRECT_URL",
"shadowDatabaseUrl" => "SHADOW_DATABASE_URL",
_ => unreachable!(),
};

let insert_text = if add_quotes(ctx.params, ctx.db.source()) {
format!(r#""{text}""#)
} else {
text.to_owned()
};

completion_list.items.push(CompletionItem {
label: text.to_owned(),
insert_text: Some(insert_text),
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
kind: Some(CompletionItemKind::CONSTANT),
..Default::default()
})
}
3 changes: 2 additions & 1 deletion prisma-fmt/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@ mod tests {
});

let expected = expect![[
r#"{"error_code":"P1012","message":"\u001b[1;91merror\u001b[0m: \u001b[1mThe `referentialIntegrity` and `relationMode` attributes cannot be used together. Please use only `relationMode` instead.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m relationMode = \"prisma\"\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91mreferentialIntegrity = \"foreignKeys\"\u001b[0m\n\u001b[1;94m 7 | \u001b[0m }\n\u001b[1;94m | \u001b[0m\n\nValidation Error Count: 1"}"#
r#"{"error_code":"P1012","message":"\u001b[1;91merror\u001b[0m: \u001b[1mThe `referentialIntegrity` and `relationMode` attributes cannot be used together. Please use only `relationMode` instead.\u001b[0m\n \u001b[1;94m-->\u001b[0m \u001b[4mschema.prisma:6\u001b[0m\n\u001b[1;94m | \u001b[0m\n\u001b[1;94m 5 | \u001b[0m relationMode = \"prisma\"\n\u001b[1;94m 6 | \u001b[0m \u001b[1;91mreferentialIntegrity = \"foreignKeys\"\u001b[0m\n\u001b[1;94m | \u001b[0m\n\nValidation Error Count: 1"}"#
]];

let response = validate(&request.to_string()).unwrap_err();
expected.assert_eq(&response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"character": 4
},
"end": {
"line": 4,
"character": 0
"line": 3,
"character": 35
}
},
"severity": 2,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"isIncomplete": false,
"items": [
{
"label": "provider",
"kind": 5,
"documentation": {
"kind": "markdown",
"value": "```prisma\nprovider = \"foo\"\n```\n___\nDescribes which datasource connector to use. Can be one of the following datasource providers: `postgresql`, `mysql`, `sqlserver`, `sqlite`, `mongodb` or `cockroachdb`.\n\n"
},
"insertText": "provider = $0",
"insertTextFormat": 2
},
{
"label": "url",
"kind": 5,
"documentation": {
"kind": "markdown",
"value": "```prisma\nurl = \"String\" | env(\"ENVIRONMENT_VARIABLE\")\n```\n___\nConnection URL including authentication info. Each datasource provider documents the URL syntax. Most providers use the syntax provided by the database. [Learn more](https://pris.ly/d/connection-strings).\n\n"
},
"insertText": "url = $0",
"insertTextFormat": 2
},
{
"label": "shadowDatabaseUrl",
"kind": 5,
"documentation": {
"kind": "markdown",
"value": "```prisma\nshadowDatabaseUrl = \"String\" | env(\"ENVIRONMENT_VARIABLE\")\n```\n___\nConnection URL including authentication info to use for Migrate's [shadow database](https://pris.ly/d/migrate-shadow).\n\n"
},
"insertText": "shadowDatabaseUrl = $0",
"insertTextFormat": 2
},
{
"label": "directUrl",
"kind": 5,
"documentation": {
"kind": "markdown",
"value": "```prisma\ndirectUrl = \"String\" | env(\"ENVIRONMENT_VARIABLE\")\n```\n___\nConnection URL for direct connection to the database. [Learn more](https://pris.ly/d/data-proxy-cli).\n\n"
},
"insertText": "directUrl = $0",
"insertTextFormat": 2
},
{
"label": "relationMode",
"kind": 5,
"documentation": {
"kind": "markdown",
"value": "```prisma\nrelationMode = \"foreignKeys\" | \"prisma\"\n```\n___\nSet the global relation mode for all relations. Values can be either \"foreignKeys\" (Default), or \"prisma\". [Learn more](https://pris.ly/d/relation-mode)\n\n"
},
"insertText": "relationmode = $0",
"insertTextFormat": 2
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
generator client {
provider = "prisma-client-js"
}

datasource db {
<|>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"isIncomplete": false,
"items": [
{
"label": "env()",
"kind": 10,
"documentation": {
"kind": "markdown",
"value": "```prisma\nenv(_ environmentVariable: string)\n```\n___\nSpecifies a datasource via an environment variable. When running a Prisma CLI command that needs the database connection URL (e.g. `prisma db pull`), you need to make sure that the `DATABASE_URL` environment variable is set. One way to do so is by creating a `.env` file. Note that the file must be in the same directory as your schema.prisma file to automatically be picked up by the Prisma CLI.\"\n\n_@param_ environmentVariable The environment variable in which the database connection URL is stored."
},
"insertText": "env($0)",
"insertTextFormat": 2
},
{
"label": "\"\"",
"kind": 10,
"documentation": {
"kind": "markdown",
"value": "```prisma\n\"connectionString\"\n```\n___\nConnection URL including authentication info. Each datasource provider documents the URL syntax. Most providers use the syntax provided by the database. [Learn more](https://pris.ly/d/prisma-schema).\n\n"
},
"insertText": "\"$0\"",
"insertTextFormat": 2
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["multischema"]
}

datasource db {
provider = "postgresql"
url = ""
directUrl = <|>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"isIncomplete": false,
"items": [
{
"label": "DIRECT_URL",
"kind": 21,
"insertText": "DIRECT_URL",
"insertTextFormat": 1
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["multischema"]
}

datasource db {
provider = "postgresql"
url = env("")
directUrl = env("<|>")
}
Loading

0 comments on commit 3b9f029

Please sign in to comment.