Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: auto completions (relation stop, name, more docs) #840

Merged
merged 8 commits into from Aug 19, 2021
97 changes: 54 additions & 43 deletions packages/language-server/src/completion/completionUtil.ts
Expand Up @@ -44,7 +44,9 @@ function convertAttributesToCompletionItems(
itemKind: CompletionItemKind,
insertTextFunc: (label: string) => string,
): CompletionItem[] {
// https://code.visualstudio.com/api/references/vscode-api#CompletionItem
const result: CompletionItem[] = []

for (const item of completionItems) {
const docComment = [
'```prisma',
Expand Down Expand Up @@ -81,64 +83,73 @@ export const allowedBlockTypes: CompletionItem[] = convertToCompletionItems(
CompletionItemKind.Class,
)

export const supportedDataSourceFields: CompletionItem[] = convertToCompletionItems(
completions.dataSourceFields,
CompletionItemKind.Field,
)
export const supportedDataSourceFields: CompletionItem[] =
convertToCompletionItems(
completions.dataSourceFields,
CompletionItemKind.Field,
)

export const supportedGeneratorFields: CompletionItem[] = convertToCompletionItems(
completions.generatorFields,
CompletionItemKind.Field,
)
export const supportedGeneratorFields: CompletionItem[] =
convertToCompletionItems(
completions.generatorFields,
CompletionItemKind.Field,
)

export const blockAttributes: CompletionItem[] = convertAttributesToCompletionItems(
completions.blockAttributes,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)
export const blockAttributes: CompletionItem[] =
convertAttributesToCompletionItems(
completions.blockAttributes,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)

export const fieldAttributes: CompletionItem[] = convertAttributesToCompletionItems(
completions.fieldAttributes,
CompletionItemKind.Property,
(label: string) => label.replace('()', '($0)').replace('""', '"$0"'),
)
export const fieldAttributes: CompletionItem[] =
convertAttributesToCompletionItems(
completions.fieldAttributes,
CompletionItemKind.Property,
(label: string) => label.replace('()', '($0)').replace('""', '"$0"'),
)

export const relationArguments: CompletionItem[] = convertAttributesToCompletionItems(
completions.relationArguments,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)
export const relationArguments: CompletionItem[] =
convertAttributesToCompletionItems(
completions.relationArguments,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)

export const dataSourceUrlArguments: CompletionItem[] = convertAttributesToCompletionItems(
completions.datasourceUrlArguments,
CompletionItemKind.Property,
(label: string) => label.replace('()', '($0)').replace('""', '"$0"'),
)
export const dataSourceUrlArguments: CompletionItem[] =
convertAttributesToCompletionItems(
completions.datasourceUrlArguments,
CompletionItemKind.Property,
(label: string) => label.replace('()', '($0)').replace('""', '"$0"'),
)

export const dataSourceProviders: CompletionItem[] = convertToCompletionItems(
completions.datasourceProviders,
CompletionItemKind.Constant,
)

export const dataSourceProviderArguments: CompletionItem[] = convertToCompletionItems(
completions.datasourceProviderArguments,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)
export const dataSourceProviderArguments: CompletionItem[] =
convertToCompletionItems(
completions.datasourceProviderArguments,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)

export const generatorProviders: CompletionItem[] = convertToCompletionItems(
completions.generatorProviders,
CompletionItemKind.Constant,
)

export const generatorProviderArguments: CompletionItem[] = convertToCompletionItems(
completions.generatorProviderArguments,
CompletionItemKind.Property,
(label: string) => label.replace('""', '"$0"'),
)
export const generatorProviderArguments: CompletionItem[] =
convertToCompletionItems(
completions.generatorProviderArguments,
CompletionItemKind.Property,
(label: string) => label.replace('""', '"$0"'),
)

export const previewFeaturesArguments: CompletionItem[] = convertToCompletionItems(
completions.previewFeaturesArguments,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)
export const previewFeaturesArguments: CompletionItem[] =
convertToCompletionItems(
completions.previewFeaturesArguments,
CompletionItemKind.Property,
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)
62 changes: 55 additions & 7 deletions packages/language-server/src/completion/completions.json
Expand Up @@ -240,19 +240,67 @@
},
{
"label": "onDelete: ",
"fullSignature": "onDelete: ",
"documentation": "Specifies the action to perform when a referenced entry in the referenced model is being deleted.",
"params": []
"fullSignature": "onDelete: Cascade | Restrict | NoAction | SetNull | SetDefault",
"documentation": "Specifies the action to perform when a referenced entry in the referenced model is being deleted, [learn more](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions).",
"params": [
{
"label": "Cascade",
"documentation": "Deleting a referenced record will trigger the deletion of referencing record."
},
{
"label": "Restrict",
"documentation": "Prevents the deletion if any referencing records exist."
},
{
"label": "NoAction",
"documentation": "Is similar to Restrict, the difference between the two is dependant on the database being used, [learn more](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions)."
},
{
"label": "SetNull",
"documentation": "The scalar field of the referenced object will be set to NULL."
},
{
"label": "SetDefault",
"documentation": "The scalar field of the referencing object will be set to the fields default value."
}
]
},
{
"label": "onUpdate: ",
"fullSignature": "onUpdate: ",
"documentation": "Specifies the action to perform when a referenced field in the referenced model is being updated to a new value.",
"params": []
"fullSignature": "onUpdate: Cascade | Restrict | NoAction | SetNull | SetDefault",
"documentation": "Specifies the action to perform when a referenced field in the referenced model is being updated to a new value, [learn more](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions).",
"params": [
{
"label": "Cascade",
"documentation": "Updates the relation scalar fields if the referenced scalar fields of the dependant record are updated."
},
{
"label": "Restrict",
"documentation": "Prevents the identifier of a referenced record from being changed."
},
{
"label": "NoAction",
"documentation": "Is similar to Restrict, the difference between the two is dependant on the database being used, [learn more](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions)."
},
{
"label": "SetNull",
"documentation": "When updating the identifier of a referenced object, the scalar fields of the referencing objects will be set to NULL."
},
{
"label": "SetDefault",
"documentation": "The scalar field of the referencing object will be set to the fields default value."
}
]
},
{
"label": "\"\"",
"fullSignature": "\"\"",
"fullSignature": "String",
"documentation": "Defines the name of the relationship. In an m-n-relation, it also determines the name of the underlying relation table.",
"params": []
},
{
"label": "name: ",
"fullSignature": "name: String",
"documentation": "Defines the name of the relationship. In an m-n-relation, it also determines the name of the underlying relation table.",
"params": []
}
Expand Down
61 changes: 46 additions & 15 deletions packages/language-server/src/completion/completions.ts
Expand Up @@ -933,25 +933,56 @@ function getSuggestionsForRelationDirective(
isIncomplete: false,
}
}

if (stringTilPosition.endsWith(',')) {
const referencesExist = wordsBeforePosition.some((a) =>
a.includes('references'),
// Check which attributes are already present
// so we can filter them out from the suggestions
const attributesFound: Set<string> = new Set()

for (const word of wordsBeforePosition) {
if (word.includes('references')) {
attributesFound.add('references')
}
if (word.includes('fields')) {
attributesFound.add('fields')
}
if (word.includes('onUpdate')) {
attributesFound.add('onUpdate')
}
if (word.includes('onDelete')) {
attributesFound.add('onDelete')
}
if (word.includes('name') || /".*"/.exec(word)) {
attributesFound.add('name')
attributesFound.add('""')
}
}

const filteredSuggestions: CompletionItem[] = suggestions.reduce(
Jolg42 marked this conversation as resolved.
Show resolved Hide resolved
(accumulator: CompletionItem[] & unknown[], sugg) => {
let suggestionMatch = false
for (const attribute of attributesFound) {
if (sugg.label.includes(attribute)) {
suggestionMatch = true
}
}

if (!suggestionMatch) {
accumulator.push(sugg)
}

return accumulator
},
[],
)
const fieldsExist = wordsBeforePosition.some((a) => a.includes('fields'))
if (referencesExist && fieldsExist) {

if (filteredSuggestions.length === 0) {
Jolg42 marked this conversation as resolved.
Show resolved Hide resolved
return
}
if (referencesExist) {
return {
items: suggestions.filter((sugg) => !sugg.label.includes('references')),
isIncomplete: false,
}
}
if (fieldsExist) {
return {
items: suggestions.filter((sugg) => !sugg.label.includes('fields')),
isIncomplete: false,
}

return {
items: filteredSuggestions,
isIncomplete: false,
}
}
}
Expand Down
37 changes: 29 additions & 8 deletions packages/language-server/src/test/completion.test.ts
Expand Up @@ -454,24 +454,26 @@ suite('Completions', () => {
label: 'fields: []',
kind: CompletionItemKind.Property,
}

const referencesProperty = {
label: 'references: []',
kind: CompletionItemKind.Property,
}
const onDeleteProperty = {
label: 'onDelete: ',
kind: CompletionItemKind.Property
kind: CompletionItemKind.Property,
}
const onUpdateProperty = {
label: 'onUpdate: ',
kind: CompletionItemKind.Property
kind: CompletionItemKind.Property,
}

const nameProperty = {
const nameQuotesProperty = {
label: '""',
kind: CompletionItemKind.Property,
}
const nameProperty = {
label: 'name: ',
kind: CompletionItemKind.Property,
}

test('Diagnoses field and block attribute suggestions', () => {
assertCompletion(
Expand Down Expand Up @@ -655,7 +657,14 @@ suite('Completions', () => {
{ line: 12, character: 26 },
{
isIncomplete: false,
items: [referencesProperty, fieldsProperty, onDeleteProperty, onUpdateProperty, nameProperty],
items: [
referencesProperty,
fieldsProperty,
onDeleteProperty,
onUpdateProperty,
nameQuotesProperty,
nameProperty,
],
},
)
assertCompletion(
Expand All @@ -675,15 +684,27 @@ suite('Completions', () => {
{ line: 30, character: 44 },
{
isIncomplete: false,
items: [fieldsProperty, onDeleteProperty, onUpdateProperty, nameProperty],
items: [
fieldsProperty,
onDeleteProperty,
onUpdateProperty,
nameQuotesProperty,
nameProperty,
],
},
)
assertCompletion(
relationDirectiveUri,
{ line: 39, character: 45 },
{
isIncomplete: false,
items: [referencesProperty, onDeleteProperty, onUpdateProperty, nameProperty],
items: [
referencesProperty,
onDeleteProperty,
onUpdateProperty,
nameQuotesProperty,
nameProperty,
],
},
)
assertCompletion(
Expand Down