From 1db4420f46a4c743d7db21f868c67be281898546 Mon Sep 17 00:00:00 2001 From: Sophie <29753584+Druue@users.noreply.github.com> Date: Mon, 12 Dec 2022 08:56:12 +0100 Subject: [PATCH] Suggest @map when not already present in line (#1320) * Fixes: #1079 Added filter to remove `@map` when already present in line Added test to ensure that `@map` is not suggested when already present in line Co-authored-by: Jan Piotrowski --- .../src/__test__/completion.test.ts | 15 +++++- .../src/completion/completionUtils.ts | 49 +++++++++++++++++-- .../src/completion/completions.ts | 41 +++++----------- 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/packages/language-server/src/__test__/completion.test.ts b/packages/language-server/src/__test__/completion.test.ts index 5f51d856c8..aec0dc2aea 100644 --- a/packages/language-server/src/__test__/completion.test.ts +++ b/packages/language-server/src/__test__/completion.test.ts @@ -2043,7 +2043,7 @@ suite('Completions', function () { provider: 'postgresql', schema: /* Prisma */ ` model Post { - id Int @id @default() + id Int @id @default() @map("foobar") email String? @unique name String | }`, @@ -2059,6 +2059,19 @@ suite('Completions', function () { ], }, }) + assertCompletion({ + schema: /* Prisma */ ` + model Post { + id Int @id @default() + email String? @unique + name String @map("_name")| + } + `, + expected: { + isIncomplete: false, + items: [fieldAttributeUnique, fieldAttributeDefault, fieldAttributeRelation, fieldAttributeIgnore], + }, + }) }) const enumUserTypeExpectedItems = [ diff --git a/packages/language-server/src/completion/completionUtils.ts b/packages/language-server/src/completion/completionUtils.ts index a4a9b5d230..29749107aa 100644 --- a/packages/language-server/src/completion/completionUtils.ts +++ b/packages/language-server/src/completion/completionUtils.ts @@ -11,7 +11,7 @@ import { import * as completions from './completions.json' import type { PreviewFeatures } from '../previewFeatures' import nativeTypeConstructors, { NativeTypeConstructors } from '../prisma-fmt/nativeTypes' -import { Block, getValuesInsideSquareBrackets, isInsideAttribute } from '../util' +import { Block, BlockType, getValuesInsideSquareBrackets, isInsideAttribute } from '../util' type JSONSimpleCompletionItems = { label: string @@ -377,8 +377,8 @@ export function toCompletionItems(allowedTypes: string[], kind: CompletionItemKi * Removes all block attribute suggestions that are invalid in this context. * E.g. `@@id()` when already used should not be in the suggestions. */ -export function removeInvalidAttributeSuggestions( - supportedAttributes: CompletionItem[], +export function filterSuggestionsForBlock( + suggestions: CompletionItem[], block: Block, lines: string[], ): CompletionItem[] { @@ -398,11 +398,50 @@ export function removeInvalidAttributeSuggestions( if (!item.startsWith('//')) { // TODO we should also remove the other suggestions if used (default()...) if (item.includes('@id')) { - supportedAttributes = supportedAttributes.filter((attribute) => !attribute.label.includes('id')) + suggestions = suggestions.filter((attribute) => !attribute.label.includes('id')) } } } - return supportedAttributes + return suggestions +} + +/** + * Removes all line attribute suggestions that are invalid in this context. + * E.g. `@map()` when already used should not be in the suggestions. + */ +export function filterSuggestionsForLine( + suggestions: CompletionItem[], + currentLine: string, + fieldType: string, + fieldBlockType?: BlockType, +) { + if (fieldBlockType === 'type') { + // @default & @relation are invalid on field referencing a composite type + // we filter them out + suggestions = suggestions.filter((sugg) => sugg.label !== '@default' && sugg.label !== '@relation') + } + + // Tom: I think we allow ids on basically everything except relation fields + // so it doesn't need to be restricted to Int and String. + // These are terrible, terrible ideas of course, but you can have id DateTime @id or id Float @id. + // TODO: decide if we want to only suggest things that make most sense or everything that is technically possible. + const isAtIdAllowed = fieldType === 'Int' || fieldType === 'String' || fieldBlockType === 'enum' + if (!isAtIdAllowed) { + // id not allowed + suggestions = suggestions.filter((suggestion) => suggestion.label !== '@id') + } + + const isUpdatedAtAllowed = fieldType === 'DateTime' + if (!isUpdatedAtAllowed) { + // updatedAt not allowed + suggestions = suggestions.filter((suggestion) => suggestion.label !== '@updatedAt') + } + + if (currentLine.includes('@map')) { + suggestions = suggestions.filter((suggestion) => suggestion.label !== '@map') + } + + return suggestions } /** diff --git a/packages/language-server/src/completion/completions.ts b/packages/language-server/src/completion/completions.ts index 519dde5ba7..1053bfed96 100644 --- a/packages/language-server/src/completion/completions.ts +++ b/packages/language-server/src/completion/completions.ts @@ -28,11 +28,12 @@ import { sortLengthProperties, filterSortLengthBasedOnInput, toCompletionItems, - removeInvalidAttributeSuggestions, + filterSuggestionsForBlock, removeInvalidFieldSuggestions, getNativeTypes, handlePreviewFeatures, relationModeValues, + filterSuggestionsForLine, } from './completionUtils' import listAllAvailablePreviewFeatures from '../prisma-fmt/listAllAvailablePreviewFeatures' import { @@ -60,7 +61,7 @@ function getSuggestionForModelBlockAttribute(block: Block, lines: string[]): Com return [] } // create deep copy - const suggestions: CompletionItem[] = removeInvalidAttributeSuggestions(klona(blockAttributes), block, lines) + const suggestions: CompletionItem[] = filterSuggestionsForBlock(klona(blockAttributes), block, lines) // We can filter on the datasource const datasourceProvider = getFirstDatasourceProvider(lines) @@ -137,6 +138,13 @@ export function getSuggestionForFieldAttribute( if (block.type !== 'model') { return } + + const fieldType = getFieldType(currentLine) + // If we don't find a field type (e.g. String, Int...), return no suggestion + if (!fieldType) { + return + } + let suggestions: CompletionItem[] = [] // Because @.? @@ -175,36 +183,11 @@ export function getSuggestionForFieldAttribute( suggestions.push(...fieldAttributes) - const fieldType = getFieldType(currentLine) - // If we don't find a field type (e.g. String, Int...), return no suggestion - if (!fieldType) { - return - } - const modelOrTypeOrEnum = getModelOrTypeOrEnumBlock(fieldType, lines) - if (modelOrTypeOrEnum?.type === 'type') { - // @default & @relation are invalid on field referencing a composite type - // we filter them out - suggestions = suggestions.filter((sugg) => sugg.label !== '@default' && sugg.label !== '@relation') - } - - // Tom: I think we allow ids on basically everything except relation fields - // so it doesn't need to be restricted to Int and String. - // These are terrible, terrible ideas of course, but you can have id DateTime @id or id Float @id. - // TODO: decide if we want to only suggest things that make most sense or everything that is technically possible. - const isAtIdAllowed = fieldType === 'Int' || fieldType === 'String' || modelOrTypeOrEnum?.type === 'enum' - if (!isAtIdAllowed) { - // id not allowed - suggestions = suggestions.filter((sugg) => sugg.label !== '@id') - } - const isUpdatedAtAllowed = fieldType === 'DateTime' - if (!isUpdatedAtAllowed) { - // updatedAt not allowed - suggestions = suggestions.filter((sugg) => sugg.label !== '@updatedAt') - } + suggestions = filterSuggestionsForLine(suggestions, currentLine, fieldType, modelOrTypeOrEnum?.type) - suggestions = removeInvalidAttributeSuggestions(suggestions, block, lines) + suggestions = filterSuggestionsForBlock(suggestions, block, lines) return { items: suggestions,