@prisma/language-server
Version:
Prisma Language Server
324 lines (294 loc) • 10.9 kB
text/typescript
import { CompletionItem, CompletionItemKind, InsertTextFormat, InsertTextMode } from 'vscode-languageserver'
import { convertAttributesToCompletionItems, convertToCompletionItems } from './internals'
import * as completions from './completions.json'
import { PreviewFeatures } from '../types'
export const sortLengthProperties: CompletionItem[] = convertToCompletionItems(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
completions.fieldAttributes
.find((item) => item.label === '@unique')!
.params.filter((item) => item.label === 'length' || item.label === 'sort'),
CompletionItemKind.Property,
)
export const relationArguments: CompletionItem[] = convertAttributesToCompletionItems(
completions.relationArguments,
CompletionItemKind.Property,
)
const sqlServerClusteredValuesCompletionItems: CompletionItem[] = [
{
label: 'true',
kind: CompletionItemKind.Value,
insertTextFormat: InsertTextFormat.PlainText,
documentation: {
kind: 'markdown',
value: 'CLUSTERED',
},
},
{
label: 'false',
kind: CompletionItemKind.Value,
insertTextFormat: InsertTextFormat.PlainText,
documentation: {
kind: 'markdown',
value: 'NONCLUSTERED',
},
},
]
/**
* ```prisma
* model A {
* id Int @id
* field Int @unique(sort: |)
* otherField Int
*
* \@@unique(fields: [otherField(sort: |)])
* \@@index(fields: [id(sort: |)])
* }
* ```
* And then specifically Sql Server, we also return:
* ```prisma
* model A {
* id Int @id(sort: |)
*
* \@@id(fields: [id(sort: |)])
* }
* ```
*/
const sortValuesCompletionItems: CompletionItem[] = [
{
label: 'Asc',
kind: CompletionItemKind.Enum,
insertTextFormat: InsertTextFormat.PlainText,
documentation: {
kind: 'markdown',
value: 'Ascending',
},
},
{
label: 'Desc',
kind: CompletionItemKind.Enum,
insertTextFormat: InsertTextFormat.PlainText,
documentation: {
kind: 'markdown',
value: 'Descending',
},
},
]
const clusteredCompletion = (items: CompletionItem[]) =>
items.push({
label: 'clustered',
insertText: 'clustered: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation:
'An index, unique constraint or primary key can be created as clustered or non-clustered; altering the storage and retrieve behavior of the index.',
})
const typeIndexCompletion = (items: CompletionItem[]) =>
items.push({
label: 'type',
kind: CompletionItemKind.Property,
insertText: 'type: $0',
insertTextFormat: InsertTextFormat.Snippet,
insertTextMode: InsertTextMode.adjustIndentation,
documentation: {
kind: 'markdown',
value: 'Defines the access type of indexes: BTree (default) or Hash.',
},
})
export const opsIndexFulltextCompletion = (items: CompletionItem[]) =>
items.push({
label: 'ops',
insertText: 'ops: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation: 'Specify the operator class for an indexed field.',
})
//#region COCKROACHDB ONLY
export const virtualSequenceDefaultCompletion = (items: CompletionItem[]) =>
items.push({
label: 'virtual',
insertText: 'virtual',
kind: CompletionItemKind.Property,
documentation:
'Virtual sequences are sequences that do not generate monotonically increasing values and instead produce values like those generated by the built-in function unique_rowid(). They are intended for use in combination with SERIAL-typed columns.',
})
export const minValueSequenceDefaultCompletion = (items: CompletionItem[]) =>
items.push({
label: 'minValue',
insertText: 'minValue: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation: 'The new minimum value of the sequence.',
})
export const maxValueSequenceDefaultCompletion = (items: CompletionItem[]) =>
items.push({
label: 'maxValue',
insertText: 'maxValue: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation: 'The new maximum value of the sequence.',
})
export const cacheSequenceDefaultCompletion = (items: CompletionItem[]) =>
items.push({
label: 'cache',
insertText: 'cache: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation:
'The number of sequence values to cache in memory for reuse in the session. A cache size of 1 means that there is no cache, and cache sizes of less than 1 are not valid.',
})
export const incrementSequenceDefaultCompletion = (items: CompletionItem[]) =>
items.push({
label: 'increment',
insertText: 'increment: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation:
'The new value by which the sequence is incremented. A negative number creates a descending sequence. A positive number creates an ascending sequence.',
})
export const startSequenceDefaultCompletion = (items: CompletionItem[]) =>
items.push({
label: 'start',
insertText: 'start: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation:
'The value the sequence starts at if you RESTART or if the sequence hits the MAXVALUE and CYCLE is set.',
})
//#endregion
export const scalarListDefaultCompletion = (items: CompletionItem[]) =>
items.unshift({
label: '[]',
insertText: '[$0]',
insertTextFormat: InsertTextFormat.Snippet,
documentation: 'Set a default value on the list field',
kind: CompletionItemKind.Value,
})
export const booleanDefaultCompletions = (items: CompletionItem[]) =>
items.push({ label: 'true', kind: CompletionItemKind.Value }, { label: 'false', kind: CompletionItemKind.Value })
export function filterSortLengthBasedOnInput(
attribute: '@@unique' | '@unique' | '@@id' | '@id' | '@@index',
previewFeatures: PreviewFeatures[] | undefined,
datasourceProvider: string | undefined,
wordBeforePosition: string,
items: CompletionItem[],
): CompletionItem[] {
/*
* 1 - Autocomplete values
*/
// Auto completion for sort: Desc | Asc
// includes because `@unique(sort: |)` means wordBeforePosition = '@unique(sort:'
if (wordBeforePosition.includes('sort:')) {
return sortValuesCompletionItems
} else {
/*
* 2 - Autocomplete properties
*/
// The length argument is available on MySQL only on the
// @id, @@id, @unique, @@unique and @@index fields.
// The sort argument is available for all databases on the
// @unique, @@unique and @@index fields.
// Additionally, SQL Server also allows it on @id and @@id.
// Which translates too
// - `length` argument for `@id`, `@@id`, `@unique`, `@@unique` and `@@index` (MySQL only)
// - Note that on the `@@` the argument is on available a field - not on the top level attribute
// - `sort` argument for `@unique`, `@@unique` and `@@index` (Additionally `@id` and `@@id` for SQL Server)
if (datasourceProvider === 'mysql') {
if (['@unique', '@@unique', '@@index'].includes(attribute)) {
return items
} else {
// filter sort out
return items.filter((arg) => arg.label !== 'sort')
}
} else if (datasourceProvider === 'sqlserver') {
if (['@unique', '@@unique', '@@index', '@id', '@@id'].includes(attribute)) {
// only filter length out
return items.filter((arg) => arg.label !== 'length')
} else {
// filter length and sort out
return items.filter((arg) => arg.label !== 'length' && arg.label !== 'sort')
}
} else {
if (['@unique', '@@unique', '@@index'].includes(attribute)) {
// only filter length out
return items.filter((arg) => arg.label !== 'length')
} else {
// filter length and sort out
return items.filter((arg) => arg.label !== 'length' && arg.label !== 'sort')
}
}
}
}
export function getCompletionsForFieldAttributeArgs(
fieldAttributeWithParams: '@unique' | '@id',
previewFeatures: PreviewFeatures[] | undefined,
datasourceProvider: string | undefined,
wordBeforePosition: string,
): CompletionItem[] {
const items = convertToCompletionItems(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
completions.fieldAttributes.find((item) => item.label.includes(fieldAttributeWithParams))!.params,
CompletionItemKind.Property,
)
const completionItems = filterSortLengthBasedOnInput(
fieldAttributeWithParams,
previewFeatures,
datasourceProvider,
wordBeforePosition,
items,
)
if (datasourceProvider === 'sqlserver') {
// Auto completion for SQL Server only, clustered: true | false
if (wordBeforePosition.includes('clustered:')) {
return sqlServerClusteredValuesCompletionItems
}
// add clustered propery to completion items
completionItems.push({
label: 'clustered',
insertText: 'clustered: $0',
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Property,
documentation:
'An index, unique constraint or primary key can be created as clustered or non-clustered; altering the storage and retrieve behavior of the index.',
})
}
return completionItems
}
export function getCompletionsForBlockAttributeArgs({
blockAttributeWithParams,
wordBeforePosition,
datasourceProvider,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
previewFeatures,
}: {
blockAttributeWithParams: '@@unique' | '@@id' | '@@index' | '@@fulltext'
wordBeforePosition: string
datasourceProvider: string | undefined
previewFeatures: PreviewFeatures[] | undefined
}): CompletionItem[] {
const items = convertToCompletionItems(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
completions.blockAttributes.find((item) => item.label.includes(blockAttributeWithParams))!.params,
CompletionItemKind.Property,
)
// SQL Server only, suggest clustered
if (datasourceProvider === 'sqlserver' && blockAttributeWithParams !== '@@fulltext') {
// Auto completion for SQL Server only, clustered: true | false
if (wordBeforePosition.includes('clustered:')) {
return sqlServerClusteredValuesCompletionItems
} else {
// add clustered to suggestions
clusteredCompletion(items)
}
}
// PostgreSQL only, suggest type
else if (
blockAttributeWithParams === '@@index' &&
datasourceProvider &&
['postgresql', 'postgres'].includes(datasourceProvider)
) {
// TODO (Joël) figure out if we need to add cockroachdb provider here
// The type argument is only available for PostgreSQL on @@index
typeIndexCompletion(items)
}
return items
}