Skip to content

Commit

Permalink
Preparatory refactor for split schema support (#1713)
Browse files Browse the repository at this point in the history
* [WIP]: Preparatory refactor for split schema support

* Update packages/language-server/src/lib/validations.ts

Co-authored-by: Sophie <29753584+Druue@users.noreply.github.com>

* Fix completions

* Fix renames

* Fix jump-to-definition and cleanup

* Update packages/language-server/src/lib/completions/types.ts

Co-authored-by: Sophie <29753584+Druue@users.noreply.github.com>

* Cleanup

* Line object

* Apply suggestions from code review

Co-authored-by: Sophie <29753584+Druue@users.noreply.github.com>

* Flip condition in attributes

---------

Co-authored-by: Sophie <29753584+Druue@users.noreply.github.com>
  • Loading branch information
SevInf and Druue committed May 3, 2024
1 parent b814ab1 commit 0e2441c
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 336 deletions.
91 changes: 44 additions & 47 deletions packages/language-server/src/lib/MessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import type { TextDocument } from 'vscode-languageserver-textdocument'
import format from './prisma-schema-wasm/format'
import lint from './prisma-schema-wasm/lint'

import { convertDocumentTextToTrimmedLineArray } from './ast'

import { quickFix } from './code-actions'
import {
insertBasicRename,
Expand All @@ -39,6 +37,8 @@ import {
printLogMessage,
isRelationField,
isDatamodelBlockName,
EditsMap,
mergeEditMaps,
} from './code-actions/rename'
import { validateExperimentalFeatures, validateIgnoredBlocks } from './validations'
import {
Expand All @@ -51,6 +51,7 @@ import {
getDatamodelBlock,
} from './ast'
import { prismaSchemaWasmCompletions, localCompletions } from './completions'
import { PrismaSchema, SchemaDocument } from './Schema'

export function handleDiagnosticsRequest(
document: TextDocument,
Expand Down Expand Up @@ -101,8 +102,8 @@ export function handleDiagnosticsRequest(

validateExperimentalFeatures(document, diagnostics)

const lines = convertDocumentTextToTrimmedLineArray(document)
validateIgnoredBlocks(lines, diagnostics)
const schema = PrismaSchema.singleFile(document)
validateIgnoredBlocks(schema, diagnostics)

return diagnostics
}
Expand All @@ -111,37 +112,37 @@ export function handleDiagnosticsRequest(
* @todo Use official schema.prisma parser. This is a workaround!
*/
export function handleDefinitionRequest(document: TextDocument, params: DeclarationParams): LocationLink[] | undefined {
const textDocument = params.textDocument
const position = params.position

const lines = convertDocumentTextToTrimmedLineArray(document)
const schema = PrismaSchema.singleFile(document)
const word = getWordAtPosition(document, position)

if (word === '') {
return
}

// get start position of block
const results: number[] = lines
.map((line, index) => {
const results = schema
.linesAsArray()
.map(({ document, lineIndex, text }) => {
if (
(line.includes('model') && line.includes(word)) ||
(line.includes('type') && line.includes(word)) ||
(line.includes('enum') && line.includes(word))
(text.includes('model') && text.includes(word)) ||
(text.includes('type') && text.includes(word)) ||
(text.includes('enum') && text.includes(word))
) {
return index
return [document, lineIndex]
}
})
.filter((index) => index !== undefined) as number[]
.filter((result) => result !== undefined) as [SchemaDocument, number][]

if (results.length === 0) {
return
}

const foundBlocks: Block[] = results
.map((result) => {
const block = getBlockAtPosition(result, lines)
if (block && block.name === word && block.range.start.line === result) {
.map(([document, lineNo]) => {
const block = getBlockAtPosition(document.uri, lineNo, schema)
if (block && block.name === word && block.range.start.line === lineNo) {
return block
}
})
Expand All @@ -157,7 +158,7 @@ export function handleDefinitionRequest(document: TextDocument, params: Declarat

return [
{
targetUri: textDocument.uri,
targetUri: foundBlocks[0].definingDocument.uri,
targetRange: foundBlocks[0].range,
targetSelectionRange: foundBlocks[0].nameRange,
},
Expand All @@ -179,14 +180,14 @@ export function handleDocumentFormatting(
export function handleHoverRequest(document: TextDocument, params: HoverParams): Hover | undefined {
const position = params.position

const lines = convertDocumentTextToTrimmedLineArray(document)
const schema = PrismaSchema.singleFile(document)
const word = getWordAtPosition(document, position)

if (word === '') {
return
}

const block = getDatamodelBlock(word, lines)
const block = getDatamodelBlock(word, schema)
if (!block) {
return
}
Expand Down Expand Up @@ -222,25 +223,27 @@ export function handleCompletionRequest(
}

export function handleRenameRequest(params: RenameParams, document: TextDocument): WorkspaceEdit | undefined {
const lines: string[] = convertDocumentTextToTrimmedLineArray(document)
const schema = PrismaSchema.singleFile(document)
const schemaLines = schema.linesAsArray()
const position = params.position
const currentLine: string = lines[position.line]
const block = getBlockAtPosition(position.line, lines)
const block = getBlockAtPosition(document.uri, position.line, schema)
if (!block) {
return
return undefined
}

const isDatamodelBlockRename = isDatamodelBlockName(position, block, lines, document)
const currentLine = block.definingDocument.lines[params.position.line].text

const isDatamodelBlockRename = isDatamodelBlockName(position, block, schema, document)

const isMappable = ['model', 'enum', 'view'].includes(block.type)
const needsMap = !isDatamodelBlockRename ? true : isMappable

const isEnumValueRename: boolean = isEnumValue(currentLine, params.position, block, document)
const isValidFieldRename: boolean = isValidFieldName(currentLine, params.position, block, document)
const isRelationFieldRename: boolean = isValidFieldRename && isRelationField(currentLine, lines)
const isRelationFieldRename: boolean = isValidFieldRename && isRelationField(currentLine, schema)

if (isDatamodelBlockRename || isEnumValueRename || isValidFieldRename) {
const edits: TextEdit[] = []
const edits: EditsMap[] = []
const currentName = extractCurrentName(
currentLine,
isDatamodelBlockRename,
Expand All @@ -252,16 +255,18 @@ export function handleRenameRequest(params: RenameParams, document: TextDocument

let lineNumberOfDefinition = position.line
let blockOfDefinition = block
let lineOfDefinition = currentLine
let lineOfDefinitionContent = currentLine
if (isDatamodelBlockRename) {
// get definition of model or enum
const matchBlockBeginning = new RegExp(`\\s*(${block.type})\\s+(${currentName})\\s*({)`, 'g')
lineNumberOfDefinition = lines.findIndex((l) => matchBlockBeginning.test(l))
if (lineNumberOfDefinition === -1) {
const lineOfDefinition = schemaLines.find((line) => matchBlockBeginning.test(line.text))
if (!lineOfDefinition) {
return
}
lineOfDefinition = lines[lineNumberOfDefinition]
const definitionBlockAtPosition = getBlockAtPosition(lineNumberOfDefinition, lines)
const { document: definitionDoc, lineIndex, text } = lineOfDefinition
lineNumberOfDefinition = lineIndex
lineOfDefinitionContent = text
const definitionBlockAtPosition = getBlockAtPosition(definitionDoc.uri, lineNumberOfDefinition, schema)
if (!definitionBlockAtPosition) {
return
}
Expand All @@ -274,7 +279,7 @@ export function handleRenameRequest(params: RenameParams, document: TextDocument
// check if map exists already
if (
!isRelationFieldRename &&
!mapExistsAlready(lineOfDefinition, lines, blockOfDefinition, isDatamodelBlockRename) &&
!mapExistsAlready(lineOfDefinitionContent, schema, blockOfDefinition, isDatamodelBlockRename) &&
needsMap
) {
// add map attribute
Expand All @@ -283,19 +288,12 @@ export function handleRenameRequest(params: RenameParams, document: TextDocument

// rename references
if (isDatamodelBlockRename) {
edits.push(...renameReferencesForModelName(currentName, params.newName, document, lines))
edits.push(renameReferencesForModelName(currentName, params.newName, schema))
} else if (isEnumValueRename) {
edits.push(...renameReferencesForEnumValue(currentName, params.newName, document, lines, blockOfDefinition.name))
edits.push(renameReferencesForEnumValue(currentName, params.newName, schema, blockOfDefinition.name))
} else if (isValidFieldRename) {
edits.push(
...renameReferencesForFieldName(
currentName,
params.newName,
document,
lines,
blockOfDefinition,
isRelationFieldRename,
),
renameReferencesForFieldName(currentName, params.newName, schema, blockOfDefinition, isRelationFieldRename),
)
}

Expand All @@ -307,10 +305,9 @@ export function handleRenameRequest(params: RenameParams, document: TextDocument
isEnumValueRename,
block.type,
)

return {
changes: {
[document.uri]: edits,
},
changes: mergeEditMaps(edits),
}
}

Expand Down Expand Up @@ -338,8 +335,8 @@ export function handleCodeActions(
}

export function handleDocumentSymbol(params: DocumentSymbolParams, document: TextDocument): DocumentSymbol[] {
const lines: string[] = convertDocumentTextToTrimmedLineArray(document)
return Array.from(getBlocks(lines), (block) => ({
const schema = PrismaSchema.singleFile(document)
return Array.from(getBlocks(schema), (block) => ({
kind: {
model: SymbolKind.Class,
enum: SymbolKind.Enum,
Expand Down
71 changes: 71 additions & 0 deletions packages/language-server/src/lib/Schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { TextDocument } from 'vscode-languageserver-textdocument'

export type Line = {
readonly document: SchemaDocument
readonly lineIndex: number
readonly text: string
readonly untrimmedText: string
}
export class SchemaDocument {
readonly lines: Line[]

static fromTextDocument(textDocument: TextDocument): SchemaDocument {
return new SchemaDocument(textDocument.uri, textDocument.getText())
}

constructor(
readonly uri: string,
readonly content: string,
) {
this.lines = content.split(/\r?\n/).map((untrimmedText, lineIndex) => ({
document: this,
lineIndex,
untrimmedText,
text: untrimmedText.trim(),
}))
}

getLineContent(lineIndex: number): string {
return this.lines[lineIndex].text
}
}

type FindRegexpResult = {
match: RegExpExecArray
documentUri: string
}

export class PrismaSchema {
static singleFile(textDocument: TextDocument) {
return new PrismaSchema([SchemaDocument.fromTextDocument(textDocument)])
}

constructor(private readonly documents: SchemaDocument[]) {}

*iterLines(): Generator<Line, void, void> {
for (const doc of this.documents) {
for (const line of doc.lines) {
yield line
}
}
}

linesAsArray(): Line[] {
return Array.from(this.iterLines())
}

findDocByUri(fileUri: string): SchemaDocument | undefined {
return this.documents.find((doc) => doc.uri === fileUri)
}

findWithRegex(regexp: RegExp): FindRegexpResult | undefined {
for (const doc of this.documents) {
regexp.lastIndex = 0
const match = regexp.exec(doc.content)
if (match) {
return { match, documentUri: doc.uri }
}
}
return undefined
}
}

0 comments on commit 0e2441c

Please sign in to comment.