UNPKG

@prisma/language-server

Version:
523 lines • 24.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.prismaSchemaWasmCompletions = prismaSchemaWasmCompletions; exports.localCompletions = localCompletions; const vscode_languageserver_1 = require("vscode-languageserver"); const textDocumentCompletion_1 = __importDefault(require("../prisma-schema-wasm/textDocumentCompletion")); const ast_1 = require("../ast"); const attributes_1 = require("./attributes"); const blocks_1 = require("./blocks"); const internals_1 = require("./internals"); const types_1 = require("./types"); const datasource_1 = require("./datasource"); const generator_1 = require("./generator"); const arguments_1 = require("./arguments"); const functions_1 = require("./functions"); function getDefaultValues({ currentLine, schema, wordsBeforePosition, }) { const suggestions = []; const datasourceProvider = (0, ast_1.getFirstDatasourceProvider)(schema); // Completions for sequence(|) if (datasourceProvider === 'cockroachdb') { if (wordsBeforePosition.some((a) => a.includes('sequence('))) { const sequenceProperties = ['virtual', 'minValue', 'maxValue', 'cache', 'increment', 'start']; // No suggestions if virtual is present if (currentLine.includes('virtual')) { return suggestions; } if (!sequenceProperties.some((it) => currentLine.includes(it))) { (0, arguments_1.virtualSequenceDefaultCompletion)(suggestions); } if (!currentLine.includes('minValue')) { (0, arguments_1.minValueSequenceDefaultCompletion)(suggestions); } if (!currentLine.includes('maxValue')) { (0, arguments_1.maxValueSequenceDefaultCompletion)(suggestions); } if (!currentLine.includes('cache')) { (0, arguments_1.cacheSequenceDefaultCompletion)(suggestions); } if (!currentLine.includes('increment')) { (0, arguments_1.incrementSequenceDefaultCompletion)(suggestions); } if (!currentLine.includes('start')) { (0, arguments_1.startSequenceDefaultCompletion)(suggestions); } return suggestions; } } // MongoDB only if (datasourceProvider === 'mongodb') { (0, functions_1.autoDefaultCompletion)(suggestions); } else { (0, functions_1.dbgeneratedDefaultCompletion)(suggestions); } const fieldType = (0, ast_1.getFieldType)(currentLine); // If we don't find a field type (e.g. String, Int...), return no suggestion if (!fieldType) { return []; } switch (fieldType) { case 'BigInt': case 'Int': if (datasourceProvider === 'cockroachdb') { (0, functions_1.sequenceDefaultCompletion)(suggestions); if (fieldType === 'Int') { // @default(autoincrement()) is only supported on BigInt fields for cockroachdb. break; } } (0, functions_1.autoincrementDefaultCompletion)(suggestions); break; case 'DateTime': (0, functions_1.nowDefaultCompletion)(suggestions); break; case 'String': (0, functions_1.uuidDefaultCompletion)(suggestions); (0, functions_1.cuidDefaultCompletion)(suggestions); (0, functions_1.ulidDefaultCompletion)(suggestions); (0, functions_1.nanoidDefaultCompletion)(suggestions); break; case 'Boolean': (0, arguments_1.booleanDefaultCompletions)(suggestions); break; } const isScalarList = fieldType.endsWith('[]'); if (isScalarList) { (0, arguments_1.scalarListDefaultCompletion)(suggestions); } const dataBlock = (0, ast_1.getDatamodelBlock)(fieldType, schema); if (dataBlock && dataBlock.type === 'enum') { // get fields from enum block for suggestions const values = (0, ast_1.getFieldsFromCurrentBlock)(schema, dataBlock); values.forEach((v) => suggestions.push({ label: v, kind: vscode_languageserver_1.CompletionItemKind.Value })); } return suggestions; } function getSuggestionsForAttribute({ attribute, wordsBeforePosition, untrimmedCurrentLine, schema, block, position, }) { const firstWordBeforePosition = wordsBeforePosition[wordsBeforePosition.length - 1]; const secondWordBeforePosition = wordsBeforePosition[wordsBeforePosition.length - 2]; const wordBeforePosition = firstWordBeforePosition === '' ? secondWordBeforePosition : firstWordBeforePosition; let suggestions = []; // We can filter on the datasource const datasourceProvider = (0, ast_1.getFirstDatasourceProvider)(schema); // We can filter on the previewFeatures enabled const previewFeatures = (0, ast_1.getAllPreviewFeaturesFromGenerators)(schema); if (attribute === '@relation') { if (datasourceProvider === 'mongodb') { suggestions = arguments_1.relationArguments.filter((arg) => arg.label !== 'map' && arg.label !== 'onDelete' && arg.label !== 'onUpdate'); } else { suggestions = arguments_1.relationArguments; } // If we are right after @relation( if (wordBeforePosition.includes('@relation')) { return { items: suggestions, isIncomplete: false, }; } // TODO check fields with [] shortcut if ((0, ast_1.isInsideGivenProperty)(untrimmedCurrentLine, wordsBeforePosition, 'fields', position)) { return { items: (0, internals_1.toCompletionItems)((0, ast_1.getFieldsFromCurrentBlock)(schema, block, position), vscode_languageserver_1.CompletionItemKind.Field), isIncomplete: false, }; } if ((0, ast_1.isInsideGivenProperty)(untrimmedCurrentLine, wordsBeforePosition, 'references', position)) { // Get the name by potentially removing ? and [] from Foo? or Foo[] const referencedModelName = wordsBeforePosition[1].replace('?', '').replace('[]', ''); const referencedBlock = (0, ast_1.getDatamodelBlock)(referencedModelName, schema); // referenced model does not exist // TODO type? if (!referencedBlock || referencedBlock.type !== 'model') { return; } return { items: (0, internals_1.toCompletionItems)((0, ast_1.getFieldsFromCurrentBlock)(schema, referencedBlock), vscode_languageserver_1.CompletionItemKind.Field), isIncomplete: false, }; } } else { // @id, @unique // @@id, @@unique, @@index, @@fulltext // 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. let attribute = undefined; if (wordsBeforePosition.some((a) => a.includes('@@id'))) { attribute = '@@id'; } else if (wordsBeforePosition.some((a) => a.includes('@id'))) { attribute = '@id'; } else if (wordsBeforePosition.some((a) => a.includes('@@unique'))) { attribute = '@@unique'; } else if (wordsBeforePosition.some((a) => a.includes('@unique'))) { attribute = '@unique'; } else if (wordsBeforePosition.some((a) => a.includes('@@index'))) { attribute = '@@index'; } else if (wordsBeforePosition.some((a) => a.includes('@@fulltext'))) { attribute = '@@fulltext'; } /** * inside [] * suggest composite types for MongoDB * suggest fields and extendedIndexes arguments (sort / length) * * Examples * field attribute: slug String @unique(sort: Desc, length: 42) @db.VarChar(3000) * block attribute: @@id([title(length: 100, sort: Desc), abstract(length: 10)]) */ if (attribute && attribute !== '@@fulltext' && (0, ast_1.isInsideAttribute)(untrimmedCurrentLine, position, '[]')) { if ((0, ast_1.isInsideFieldArgument)(untrimmedCurrentLine, position)) { // extendedIndexes const items = []; // https://www.notion.so/prismaio/Proposal-More-PostgreSQL-index-types-GiST-GIN-SP-GiST-and-BRIN-e27ef762ee4846a9a282eec1a5129270 if (datasourceProvider === 'postgresql' && attribute === '@@index') { (0, arguments_1.opsIndexFulltextCompletion)(items); } items.push(...(0, arguments_1.filterSortLengthBasedOnInput)(attribute, previewFeatures, datasourceProvider, wordBeforePosition, arguments_1.sortLengthProperties)); return { items, isIncomplete: false, }; } const fieldsFromLine = (0, ast_1.getValuesInsideSquareBrackets)(untrimmedCurrentLine); /* * MongoDB composite type fields, see https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#composite-type-unique-constraints * Examples * @@unique([address.|]) or @@unique(fields: [address.|]) * @@index([address.|]) or @@index(fields: [address.|]) */ if (datasourceProvider === 'mongodb' && fieldsFromLine && firstWordBeforePosition.endsWith('.')) { const getFieldName = (text) => { const [_, __, value] = new RegExp(/(.*\[)?(.+)/).exec(text) || []; let name = value; // Example for `@@index([email,address.|])` when there is no space between fields if (name?.includes(',')) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion name = name.split(',').pop(); } // Remove . to only get the name if (name?.endsWith('.')) { name = name.slice(0, -1); } return name; }; const currentFieldName = getFieldName(firstWordBeforePosition); if (!currentFieldName) { return { isIncomplete: false, items: [], }; } const currentCompositeAsArray = currentFieldName.split('.'); const fieldTypesFromCurrentBlock = (0, ast_1.getFieldTypesFromCurrentBlock)(schema, block); const fields = (0, ast_1.getCompositeTypeFieldsRecursively)(schema, currentCompositeAsArray, fieldTypesFromCurrentBlock); return { items: (0, internals_1.toCompletionItems)(fields, vscode_languageserver_1.CompletionItemKind.Field), isIncomplete: false, }; } let fieldsFromCurrentBlock = (0, ast_1.getFieldsFromCurrentBlock)(schema, block, position); if (fieldsFromLine.length > 0) { // If we are in a composite type, exit here, to not pollute results with first level fields if (firstWordBeforePosition.includes('.')) { return { isIncomplete: false, items: [], }; } // Remove items already used fieldsFromCurrentBlock = fieldsFromCurrentBlock.filter((s) => !fieldsFromLine.includes(s)); // Return fields // `onCompletionResolve` will take care of filtering the partial matches if (firstWordBeforePosition !== '' && !firstWordBeforePosition.endsWith(',') && !firstWordBeforePosition.endsWith(', ')) { return { items: (0, internals_1.toCompletionItems)(fieldsFromCurrentBlock, vscode_languageserver_1.CompletionItemKind.Field), isIncomplete: false, }; } } return { items: (0, internals_1.toCompletionItems)(fieldsFromCurrentBlock, vscode_languageserver_1.CompletionItemKind.Field), isIncomplete: false, }; } // "@@" block attributes let blockAtrributeArguments = []; if (attribute === '@@unique') { blockAtrributeArguments = (0, arguments_1.getCompletionsForBlockAttributeArgs)({ blockAttributeWithParams: '@@unique', wordBeforePosition, datasourceProvider, previewFeatures, }); } else if (attribute === '@@id') { blockAtrributeArguments = (0, arguments_1.getCompletionsForBlockAttributeArgs)({ blockAttributeWithParams: '@@id', wordBeforePosition, datasourceProvider, previewFeatures, }); } else if (attribute === '@@index') { blockAtrributeArguments = (0, arguments_1.getCompletionsForBlockAttributeArgs)({ blockAttributeWithParams: '@@index', wordBeforePosition, datasourceProvider, previewFeatures, }); } else if (attribute === '@@fulltext') { blockAtrributeArguments = (0, arguments_1.getCompletionsForBlockAttributeArgs)({ blockAttributeWithParams: '@@fulltext', wordBeforePosition, datasourceProvider, previewFeatures, }); } if (blockAtrributeArguments.length) { suggestions = blockAtrributeArguments; } else { // "@" field attributes let fieldAtrributeArguments = []; if (attribute === '@unique') { fieldAtrributeArguments = (0, arguments_1.getCompletionsForFieldAttributeArgs)('@unique', previewFeatures, datasourceProvider, wordBeforePosition); } else if (attribute === '@id') { fieldAtrributeArguments = (0, arguments_1.getCompletionsForFieldAttributeArgs)('@id', previewFeatures, datasourceProvider, wordBeforePosition); } suggestions = fieldAtrributeArguments; } } // Check which attributes are already present // so we can filter them out from the suggestions const attributesFound = 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('map')) { attributesFound.add('map'); } if (word.includes('name') || /".*"/.exec(word)) { attributesFound.add('name'); attributesFound.add('""'); } if (word.includes('type')) { attributesFound.add('type'); } } // now filter them out of the suggestions as they are already present const filteredSuggestions = suggestions.reduce((accumulator, sugg) => { let suggestionMatch = false; for (const attribute of attributesFound) { if (sugg.label.includes(attribute)) { suggestionMatch = true; } } if (!suggestionMatch) { accumulator.push(sugg); } return accumulator; }, []); // nothing to present any more, return if (filteredSuggestions.length === 0) { return; } return { items: filteredSuggestions, isIncomplete: false, }; } function getSuggestionsForInsideRoundBrackets(untrimmedCurrentLine, schema, position, block) { const wordsBeforePosition = untrimmedCurrentLine.slice(0, position.character).trimLeft().split(/\s+/); if (wordsBeforePosition.some((a) => a.includes('@default'))) { return { items: getDefaultValues({ currentLine: block.definingDocument.getLineContent(position.line), schema, wordsBeforePosition, }), isIncomplete: false, }; } else if (wordsBeforePosition.some((a) => a.includes('@relation'))) { return getSuggestionsForAttribute({ attribute: '@relation', wordsBeforePosition, untrimmedCurrentLine, schema, // document, block, position, }); } else if ( // matches // @id, @unique // @@id, @@unique, @@index, @@fulltext wordsBeforePosition.some((a) => a.includes('@unique') || a.includes('@id') || a.includes('@@index') || a.includes('@@fulltext'))) { return getSuggestionsForAttribute({ wordsBeforePosition, untrimmedCurrentLine, schema, // document, block, position, }); } else { return { items: (0, internals_1.toCompletionItems)([], vscode_languageserver_1.CompletionItemKind.Field), isIncomplete: false, }; } } // Suggest fields for a BlockType function getSuggestionForSupportedFields(blockType, currentLine, currentLineUntrimmed, position, schema, onError) { const isInsideQuotation = (0, internals_1.isInsideQuotationMark)(currentLineUntrimmed, position); // We can filter on the datasource const datasourceProvider = (0, ast_1.getFirstDatasourceProvider)(schema); // We can filter on the previewFeatures enabled // const previewFeatures = getAllPreviewFeaturesFromGenerators(lines) switch (blockType) { case 'generator': return (0, generator_1.generatorSuggestions)(currentLine, currentLineUntrimmed, position, isInsideQuotation, onError); case 'datasource': return (0, datasource_1.dataSourceSuggestions)(currentLine, isInsideQuotation, datasourceProvider); default: return undefined; } } /** * gets suggestions for block type */ function getSuggestionForFirstInsideBlock(blockType, schema, position, block) { let suggestions = []; switch (blockType) { case 'generator': suggestions = (0, generator_1.getSuggestionForGeneratorField)(block, schema, position); break; case 'model': case 'view': suggestions = (0, attributes_1.getSuggestionForBlockAttribute)(block, schema); break; case 'type': // No suggestions break; } return { items: suggestions, isIncomplete: false, }; } function prismaSchemaWasmCompletions(schema, params, onError) { const completionList = (0, textDocumentCompletion_1.default)(schema, params, (errorMessage) => { if (onError) { onError(errorMessage); } }); if (completionList.items.length === 0) { return undefined; } else { return completionList; } } function localCompletions(schema, initiatingDocument, params, onError) { const context = params.context; const position = params.position; const currentLineUntrimmed = (0, ast_1.getCurrentLine)(initiatingDocument, position.line); const currentLineTillPosition = currentLineUntrimmed.slice(0, position.character - 1).trim(); const wordsBeforePosition = currentLineTillPosition.split(/\s+/); const symbolBeforePosition = (0, ast_1.getSymbolBeforePosition)(initiatingDocument, position); const symbolBeforePositionIsWhiteSpace = symbolBeforePosition.search(/\s/) !== -1; const positionIsAfterArray = wordsBeforePosition.length >= 3 && !currentLineTillPosition.includes('[') && symbolBeforePositionIsWhiteSpace; // datasource, generator, model, type or enum const foundBlock = (0, ast_1.getBlockAtPosition)(initiatingDocument.uri, position.line, schema); if (!foundBlock) { if (wordsBeforePosition.length > 1 || (wordsBeforePosition.length === 1 && symbolBeforePositionIsWhiteSpace)) { return; } return (0, blocks_1.getSuggestionForBlockTypes)(schema); } if ((0, ast_1.isFirstInsideBlock)(position, foundBlock.definingDocument.lines[position.line].untrimmedText)) { return getSuggestionForFirstInsideBlock(foundBlock.type, schema, position, foundBlock); } // Completion was triggered by a triggerCharacter // triggerCharacters defined in src/server.ts if (context?.triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter) { switch (context.triggerCharacter) { case '@': if (!(0, ast_1.positionIsAfterFieldAndType)(position, initiatingDocument, wordsBeforePosition)) { return; } return (0, attributes_1.getSuggestionForFieldAttribute)(foundBlock, (0, ast_1.getCurrentLine)(initiatingDocument, position.line), schema, wordsBeforePosition, onError); case '"': return getSuggestionForSupportedFields(foundBlock.type, foundBlock.definingDocument.getLineContent(position.line), currentLineUntrimmed, position, schema, onError); case '.': // check if inside attribute // Useful to complete composite types if (['model', 'view'].includes(foundBlock.type) && (0, ast_1.isInsideAttribute)(currentLineUntrimmed, position, '()')) { return getSuggestionsForInsideRoundBrackets(currentLineUntrimmed, schema, position, foundBlock); } else { return (0, types_1.getSuggestionForNativeTypes)(foundBlock, schema, wordsBeforePosition, onError); } } } switch (foundBlock.type) { case 'model': case 'view': case 'type': // check if inside attribute if ((0, ast_1.isInsideAttribute)(currentLineUntrimmed, position, '()')) { return getSuggestionsForInsideRoundBrackets(currentLineUntrimmed, schema, position, foundBlock); } // check if field type if (!(0, ast_1.positionIsAfterFieldAndType)(position, initiatingDocument, wordsBeforePosition)) { return (0, types_1.getSuggestionsForFieldTypes)(schema, position, currentLineUntrimmed); } return (0, attributes_1.getSuggestionForFieldAttribute)(foundBlock, foundBlock.definingDocument.getLineContent(position.line), schema, wordsBeforePosition, onError); case 'datasource': case 'generator': if (wordsBeforePosition.length === 1 && symbolBeforePositionIsWhiteSpace) { return (0, internals_1.suggestEqualSymbol)(foundBlock.type); } if (currentLineTillPosition.includes('=') && !currentLineTillPosition.includes(']') && !positionIsAfterArray && symbolBeforePosition !== ',') { return getSuggestionForSupportedFields(foundBlock.type, foundBlock.definingDocument.getLineContent(position.line), currentLineUntrimmed, position, schema, onError); } break; case 'enum': break; } } //# sourceMappingURL=index.js.map