@prisma/language-server
Version:
Prisma Language Server
439 lines • 17.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDatamodelBlockName = void 0;
exports.isRelationField = isRelationField;
exports.isValidFieldName = isValidFieldName;
exports.isEnumValue = isEnumValue;
exports.printLogMessage = printLogMessage;
exports.renameReferencesForFieldName = renameReferencesForFieldName;
exports.renameReferencesForEnumValue = renameReferencesForEnumValue;
exports.renameReferencesForModelName = renameReferencesForModelName;
exports.insertBasicRename = insertBasicRename;
exports.mapExistsAlready = mapExistsAlready;
exports.insertMapAttribute = insertMapAttribute;
exports.extractCurrentName = extractCurrentName;
exports.appendEdit = appendEdit;
exports.appendEdits = appendEdits;
exports.mergeEditMaps = mergeEditMaps;
const ast_1 = require("../ast");
const constants_1 = require("../constants");
function getType(currentLine) {
const wordsInLine = currentLine.split(/\s+/);
if (wordsInLine.length < 2) {
return '';
}
return wordsInLine[1].replace('?', '').replace('[]', '');
}
function isRelationField(currentLine, schema) {
const relationNames = (0, ast_1.getAllRelationNames)(schema, constants_1.relationNamesRegexFilter);
const type = getType(currentLine);
if (type == '') {
return false;
}
return relationNames.includes(type);
}
function extractFirstWord(line) {
return line.replace(/ .*/, '');
}
function isValidFieldName(currentLine, position, block, document) {
if (block.type === 'datasource' ||
block.type === 'generator' ||
block.type === 'enum' ||
position.line == block.range.start.line ||
position.line == block.range.end.line) {
return false;
}
if (currentLine.startsWith('@')) {
return false;
}
// check if position is inside first word
const currentLineUntrimmed = (0, ast_1.getCurrentLine)(document, position.line);
const firstWord = extractFirstWord(currentLine);
const indexOfFirstWord = currentLineUntrimmed.indexOf(firstWord);
const isFieldName = indexOfFirstWord <= position.character && indexOfFirstWord + firstWord.length >= position.character;
if (!isFieldName) {
return false;
}
// remove type modifiers
const type = getType(currentLine);
return type !== '' && type !== undefined;
}
const isDatamodelBlockName = (position, block, schema, document) => {
// TODO figure out how to use DatamodelBlockType
if (!['model', 'view', 'type', 'enum'].includes(block.type)) {
return false;
}
if (position.line !== block.range.start.line) {
return renameDatamodelBlockWhereUsedAsType(block, schema, document, position);
}
switch (block.type) {
case 'model':
return position.character > 5;
case 'enum':
case 'view':
case 'type':
return position.character > 4;
default:
return false;
}
};
exports.isDatamodelBlockName = isDatamodelBlockName;
function renameDatamodelBlockWhereUsedAsType(block, schema, document, position) {
// TODO type?
if (block.type !== 'model') {
return false;
}
const allRelationNames = (0, ast_1.getAllRelationNames)(schema, constants_1.relationNamesRegexFilter);
const currentName = (0, ast_1.getWordAtPosition)(document, position);
const isRelation = allRelationNames.includes(currentName);
if (!isRelation) {
return false;
}
const indexOfRelation = schema
.linesAsArray()
.findIndex((line) => line.text.startsWith(block.type) && line.text.includes(currentName));
return indexOfRelation !== -1;
}
function isEnumValue(line, position, block, document) {
return (block.type === 'enum' &&
position.line !== block.range.start.line &&
!line.startsWith('@@') &&
!(0, ast_1.getWordAtPosition)(document, position).startsWith('@'));
}
function printLogMessage(currentName, newName, isBlockRename, isFieldRename, isEnumValueRename, blockType) {
const message = `'${currentName}' was renamed to '${newName}'`;
let typeOfRename = '';
if (isBlockRename) {
typeOfRename = `${blockType} `;
}
else if (isFieldRename) {
typeOfRename = 'Field ';
}
else if (isEnumValueRename) {
typeOfRename = 'Enum value ';
}
console.log(typeOfRename + message);
}
function insertInlineRename(currentName, line) {
const character = lastNewLineCharacter(line.untrimmedText);
return {
[line.document.uri]: [
{
range: {
start: {
line: line.lineIndex,
character,
},
end: {
line: line.lineIndex,
character,
},
},
newText: ` @map("${currentName}")`,
},
],
};
}
function lastNewLineCharacter(lineText) {
const i = lineText.length - 1;
if (lineText[i] === '\n' && lineText[i - 1] === '\r') {
return i - 1;
}
else if (lineText[i] === '\n') {
return i;
}
else {
return 0;
}
}
function insertMapBlockAttribute(oldName, block) {
return {
[block.definingDocument.uri]: [
{
range: {
start: {
line: block.range.end.line,
character: 0,
},
end: block.range.end,
},
newText: `\t@@map("${oldName}")\n}`,
},
],
};
}
function positionIsNotInsideSearchedBlocks(line, searchedBlocks) {
if (searchedBlocks.length === 0) {
return true;
}
return !searchedBlocks.some((block) => line >= block.range.start.line && line <= block.range.end.line);
}
/**
* Renames references in any '@@index', '@@id' and '@@unique' attributes in the same model.
* Renames references in any referenced fields inside a '@relation' attribute in the same model (fields: []).
* Renames references inside a '@relation' attribute in other model blocks (references: []).
*/
function renameReferencesForFieldName(currentName, newName, schema, block, isRelationFieldRename) {
const edits = {};
const searchStringsSameBlock = ['@@index', '@@id', '@@unique'];
const relationAttribute = '@relation';
// search in same model first
let reachedStartLine = false;
for (const { document, lineIndex, text, untrimmedText } of schema.iterLines()) {
if (lineIndex === block.range.start.line + 1) {
reachedStartLine = true;
}
if (!reachedStartLine) {
continue;
}
if (lineIndex === block.range.end.line) {
break;
}
if (text.includes(relationAttribute) && text.includes(currentName) && !isRelationFieldRename) {
// search for fields references
const indexOfFieldsStart = untrimmedText.indexOf('fields:');
const indexOfFieldEnd = untrimmedText.slice(indexOfFieldsStart).indexOf(']') + indexOfFieldsStart;
const fields = untrimmedText.slice(indexOfFieldsStart, indexOfFieldEnd + 1);
const indexOfFoundValue = fields.indexOf(currentName);
const fieldValues = (0, ast_1.getValuesInsideSquareBrackets)(fields);
if (indexOfFoundValue !== -1 && fieldValues.includes(currentName)) {
// found a referenced field
appendEdit(edits, document.uri, {
range: {
start: {
line: lineIndex,
character: indexOfFieldsStart + indexOfFoundValue,
},
end: {
line: lineIndex,
character: indexOfFieldsStart + indexOfFoundValue + currentName.length,
},
},
newText: newName,
});
}
}
// search for references in index, id and unique block attributes
if (searchStringsSameBlock.some((s) => text.includes(s)) && text.includes(currentName)) {
const valuesInsideBracket = (0, ast_1.getValuesInsideSquareBrackets)(untrimmedText);
if (valuesInsideBracket.includes(currentName)) {
const indexOfCurrentValue = untrimmedText.indexOf(currentName);
appendEdit(edits, document.uri, {
range: {
start: {
line: lineIndex,
character: indexOfCurrentValue,
},
end: {
line: lineIndex,
character: indexOfCurrentValue + currentName.length,
},
},
newText: newName,
});
}
}
}
// search for references in other model blocks
for (const { document, lineIndex, text, untrimmedText } of schema.iterLines()) {
if (text.includes(block.name) && text.includes(currentName) && text.includes(relationAttribute)) {
// get the index of the second word
const indexOfReferences = untrimmedText.indexOf('references:');
const indexOfReferencesEnd = untrimmedText.slice(indexOfReferences).indexOf(']') + indexOfReferences;
const references = untrimmedText.slice(indexOfReferences, indexOfReferencesEnd + 1);
const indexOfFoundValue = references.indexOf(currentName);
const referenceValues = (0, ast_1.getValuesInsideSquareBrackets)(references);
if (indexOfFoundValue !== -1 && referenceValues.includes(currentName)) {
appendEdit(edits, document.uri, {
range: {
start: {
line: lineIndex,
character: indexOfReferences + indexOfFoundValue,
},
end: {
line: lineIndex,
character: indexOfReferences + indexOfFoundValue + currentName.length,
},
},
newText: newName,
});
}
}
}
return edits;
}
/**
* Renames references where the current enum value is used as a default value in other model blocks.
*/
function renameReferencesForEnumValue(currentValue, newName, schema, enumName) {
const edits = {};
const searchString = `@default(${currentValue})`;
for (const { document, lineIndex, text, untrimmedText } of schema.iterLines()) {
if (text.includes(searchString) && text.includes(enumName)) {
// get the index of the second word
const indexOfCurrentName = untrimmedText.indexOf(searchString);
appendEdit(edits, document.uri, {
range: {
start: {
line: lineIndex,
character: indexOfCurrentName,
},
end: {
line: lineIndex,
character: indexOfCurrentName + searchString.length,
},
},
newText: `@default(${newName})`,
});
}
}
return edits;
}
/**
* Renames references where the model name is used as a relation type in the same and other model blocks.
*/
function renameReferencesForModelName(currentName, newName, schema) {
const searchedBlocks = [];
const edits = {};
for (const { document, lineIndex, text } of schema.iterLines()) {
// check if inside model
if (text.includes(currentName) && positionIsNotInsideSearchedBlocks(lineIndex, searchedBlocks)) {
const block = (0, ast_1.getBlockAtPosition)(document.uri, lineIndex, schema);
// TODO type here
if (block && block.type == 'model') {
searchedBlocks.push(block);
// search for field types in current block
const fieldTypes = (0, ast_1.getFieldTypesFromCurrentBlock)(schema, block);
for (const fieldType of fieldTypes.fieldTypes.keys()) {
if (fieldType.replace('?', '').replace('[]', '') === currentName) {
// replace here
const foundFieldTypes = fieldTypes.fieldTypes.get(fieldType);
if (!foundFieldTypes?.locations) {
return edits;
}
for (const { lineIndex, document: foundDocument } of foundFieldTypes.locations) {
const currentLineUntrimmed = foundDocument.lines[lineIndex].untrimmedText;
const wordsInLine = foundDocument.lines[lineIndex].text.split(/\s+/);
// get the index of the second word
const indexOfFirstWord = currentLineUntrimmed.indexOf(wordsInLine[0]);
const indexOfCurrentName = currentLineUntrimmed.indexOf(currentName, indexOfFirstWord + wordsInLine[0].length);
appendEdit(edits, document.uri, {
range: {
start: {
line: lineIndex,
character: indexOfCurrentName,
},
end: {
line: lineIndex,
character: indexOfCurrentName + currentName.length,
},
},
newText: newName,
});
}
}
}
}
}
}
return edits;
}
function mapFieldAttributeExistsAlready(line) {
return line.includes('@map(');
}
function mapBlockAttributeExistsAlready(block, schema) {
let reachedStartLine = false;
for (const { lineIndex, text } of schema.iterLines()) {
if (lineIndex === block.range.start.line + 1) {
reachedStartLine = true;
}
if (!reachedStartLine) {
continue;
}
if (lineIndex === block.range.end.line) {
break;
}
if (text.startsWith('@@map(')) {
return true;
}
}
return false;
}
function insertBasicRename(newName, currentName, document, line) {
const currentLineUntrimmed = (0, ast_1.getCurrentLine)(document, line);
const indexOfCurrentName = currentLineUntrimmed.indexOf(currentName);
return {
[document.uri]: [
{
range: {
start: {
line: line,
character: indexOfCurrentName,
},
end: {
line: line,
character: indexOfCurrentName + currentName.length,
},
},
newText: newName,
},
],
};
}
function mapExistsAlready(currentLine, schema, block, isDatamodelBlockRename) {
if (isDatamodelBlockRename) {
return mapBlockAttributeExistsAlready(block, schema);
}
else {
return mapFieldAttributeExistsAlready(currentLine);
}
}
function insertMapAttribute(currentName, position, block, isDatamodelBlockRename) {
if (isDatamodelBlockRename) {
return insertMapBlockAttribute(currentName, block);
}
else {
const line = block.definingDocument.lines[position.line];
return insertInlineRename(currentName, line);
}
}
function extractCurrentName(line, isBlockRename, isEnumValueRename, isFieldRename, document, position) {
if (isBlockRename) {
const currentLineUntrimmed = (0, ast_1.getCurrentLine)(document, position.line);
const currentLineTillPosition = currentLineUntrimmed
.slice(0, position.character + currentLineUntrimmed.slice(position.character).search(/\W/))
.trim();
const wordsBeforePosition = currentLineTillPosition.split(/\s+/);
if (wordsBeforePosition.length < 2) {
return '';
}
return wordsBeforePosition[1];
}
if (isEnumValueRename || isFieldRename) {
return extractFirstWord(line);
}
return '';
}
function appendEdit(edits, documentUri, edit) {
let docEdits = edits[documentUri];
if (!docEdits) {
docEdits = [];
edits[documentUri] = docEdits;
}
docEdits.push(edit);
}
function appendEdits(edits, documentUri, editsToAppend) {
for (const edit of editsToAppend) {
appendEdit(edits, documentUri, edit);
}
}
function mergeEditMaps(maps) {
const result = {};
for (const map of maps) {
for (const [documentUri, edits] of Object.entries(map)) {
appendEdits(result, documentUri, edits);
}
}
return result;
}
//# sourceMappingURL=rename.js.map