Skip to content

Commit

Permalink
fix: completion for referentialActions (Cascade,...) (#847)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jolg42 committed Aug 23, 2021
1 parent 2c60ff0 commit a5d892c
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 24 deletions.
16 changes: 16 additions & 0 deletions packages/language-server/src/completion/completionUtil.ts
Expand Up @@ -123,6 +123,22 @@ export const relationArguments: CompletionItem[] =
(label: string) => label.replace('[]', '[$0]').replace('""', '"$0"'),
)

export const relationOnDeleteArguments: CompletionItem[] =
convertToCompletionItems(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
completions.relationArguments.find((item) => item.label === 'onDelete: ')!
.params,
CompletionItemKind.Enum,
)

export const relationOnUpdateArguments: CompletionItem[] =
convertToCompletionItems(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
completions.relationArguments.find((item) => item.label === 'onUpdate: ')!
.params,
CompletionItemKind.Enum,
)

export const dataSourceUrlArguments: CompletionItem[] =
convertAttributesToCompletionItems(
completions.datasourceUrlArguments,
Expand Down
82 changes: 60 additions & 22 deletions packages/language-server/src/completion/completions.ts
Expand Up @@ -14,6 +14,8 @@ import {
supportedDataSourceFields,
supportedGeneratorFields,
relationArguments,
relationOnDeleteArguments,
relationOnUpdateArguments,
dataSourceUrlArguments,
dataSourceProviders,
dataSourceProviderArguments,
Expand All @@ -24,7 +26,7 @@ import {
import { klona } from 'klona'
import { extractModelName } from '../rename/renameUtil'
import previewFeatures from '../prisma-fmt/previewFeatures'
import referentialActions from '../prisma-fmt/referentialActions'
// import referentialActions from '../prisma-fmt/referentialActions'
import nativeTypeConstructors, {
NativeTypeConstructors,
} from '../prisma-fmt/nativeTypes'
Expand Down Expand Up @@ -778,16 +780,6 @@ function isInsideFieldsOrReferences(
return false
}

function definingReferentialAction(
wordsBeforePosition: Array<string>,
): boolean {
const lastWord = wordsBeforePosition[wordsBeforePosition.length - 2]
return (
lastWord != undefined &&
(lastWord.includes('onDelete') || lastWord.includes('onUpdate'))
)
}

function getFieldsFromCurrentBlock(
lines: Array<string>,
block: Block,
Expand Down Expand Up @@ -865,40 +857,86 @@ function getFieldType(line: string): string | undefined {
return undefined
}

// function definingReferentialAction(
// wordsBeforePosition: Array<string>,
// ): boolean {
// const lastWord = wordsBeforePosition[wordsBeforePosition.length - 2]
// return (
// lastWord != undefined &&
// (lastWord.includes('onDelete') || lastWord.includes('onUpdate'))
// )
// }

// @relation
function getSuggestionsForRelationDirective(
wordsBeforePosition: string[],
currentLineUntrimmed: string,
lines: string[],
document: TextDocument,
document: TextDocument, // eslint-disable-line @typescript-eslint/no-unused-vars
block: Block,
position: Position,
binPath: string,
binPath: string, // eslint-disable-line @typescript-eslint/no-unused-vars
): CompletionList | undefined {
// create deep copy
const suggestions: CompletionItem[] = klona(relationArguments)
const wordBeforePosition = wordsBeforePosition[wordsBeforePosition.length - 1]
const firstWordBeforePosition =
wordsBeforePosition[wordsBeforePosition.length - 1]
const secondWordBeforePosition =
wordsBeforePosition[wordsBeforePosition.length - 2]
const wordBeforePosition =
firstWordBeforePosition === ''
? secondWordBeforePosition
: firstWordBeforePosition
const stringTilPosition = currentLineUntrimmed
.slice(0, position.character)
.trim()

// If we are right after @relation(
if (wordBeforePosition.includes('@relation')) {
// This is basically hardcoding the suggestions
// because prisma-format referential-actions returns an empty array [] most of the time
// Main issue is that "Restrict" should be excluded if on SQL Server
//
// Note: needs to be before @relation condition because
// `@relation(onUpdate: |)` means wordBeforePosition = '@relation(onUpdate:'
if (wordBeforePosition.includes('onDelete:')) {
return {
items: suggestions,
items: relationOnDeleteArguments,
isIncomplete: false,
}
}
if (wordBeforePosition.includes('onUpdate:')) {
return {
items: relationOnUpdateArguments,
isIncomplete: false,
}
}
if (definingReferentialAction(wordsBeforePosition)) {
const suggestions: CompletionItem[] = referentialActions(
binPath,
document.getText(),
).map((action) => CompletionItem.create(action))

// If we are right after @relation(
if (wordBeforePosition.includes('@relation')) {
return {
items: suggestions,
isIncomplete: false,
}
}

// Doesn't really work because prisma-fmt returns nothing when the schema is "invalid"
// but that also means that the schema is considered invalid when trying to autocomplete...
//
// if lastWord = onUpdate or onDelete
// then get suggestions by passing `referential-actions` arg to `prisma-fmt`
// if (definingReferentialAction(wordsBeforePosition)) {
// const suggestionsForReferentialActions: CompletionItem[] = referentialActions(
// binPath,
// document.getText(),
// ).map((action) => {
// return CompletionItem.create(action)
// })

// return {
// items: suggestionsForReferentialActions,
// isIncomplete: false,
// }
// }

if (
isInsideFieldsOrReferences(
currentLineUntrimmed,
Expand Down
68 changes: 66 additions & 2 deletions packages/language-server/src/test/completion.test.ts
Expand Up @@ -33,12 +33,28 @@ function assertCompletion(
)

assert.ok(completionResult !== undefined)
assert.deepStrictEqual(completionResult.isIncomplete, expected.isIncomplete)
assert.deepStrictEqual(completionResult.items.length, expected.items.length)

assert.deepStrictEqual(
completionResult.isIncomplete,
expected.isIncomplete,
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Expected isIncomplete to be ${expected.isIncomplete} suggestions and got ${completionResult.isIncomplete}`,
)

assert.deepStrictEqual(
completionResult.items.length,
expected.items.length,
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Expected ${expected.items.length} suggestions and got ${
completionResult.items.length
}: ${JSON.stringify(completionResult.items, undefined, 2)}`,
)

assert.deepStrictEqual(
completionResult.items.map((items) => items.label),
expected.items.map((items) => items.label),
)

assert.deepStrictEqual(
completionResult.items.map((item) => item.kind),
expected.items.map((item) => item.kind),
Expand Down Expand Up @@ -749,6 +765,54 @@ suite('Completions', () => {
},
)
})
test('@relation(onDelete: |)', () => {
assertCompletion(
relationDirectiveUri,
{ line: 66, character: 36 },
{
isIncomplete: false,
items: [
{ label: 'Cascade', kind: CompletionItemKind.Enum },
{ label: 'Restrict', kind: CompletionItemKind.Enum },
{ label: 'NoAction', kind: CompletionItemKind.Enum },
{ label: 'SetNull', kind: CompletionItemKind.Enum },
{ label: 'SetDefault', kind: CompletionItemKind.Enum },
],
},
)
})
test('@relation(onUpdate: |)', () => {
assertCompletion(
relationDirectiveUri,
{ line: 75, character: 36 },
{
isIncomplete: false,
items: [
{ label: 'Cascade', kind: CompletionItemKind.Enum },
{ label: 'Restrict', kind: CompletionItemKind.Enum },
{ label: 'NoAction', kind: CompletionItemKind.Enum },
{ label: 'SetNull', kind: CompletionItemKind.Enum },
{ label: 'SetDefault', kind: CompletionItemKind.Enum },
],
},
)
})
test('@relation(fields: [orderId], references: [id], onDelete: |)', () => {
assertCompletion(
relationDirectiveUri,
{ line: 84, character: 73 },
{
isIncomplete: false,
items: [
{ label: 'Cascade', kind: CompletionItemKind.Enum },
{ label: 'Restrict', kind: CompletionItemKind.Enum },
{ label: 'NoAction', kind: CompletionItemKind.Enum },
{ label: 'SetNull', kind: CompletionItemKind.Enum },
{ label: 'SetDefault', kind: CompletionItemKind.Enum },
],
},
)
})
})
})
})

0 comments on commit a5d892c

Please sign in to comment.