From ca5d0299c53275368c83f8f5b8f067c46f0372da Mon Sep 17 00:00:00 2001 From: Sophie <29753584+Druue@users.noreply.github.com> Date: Thu, 4 Jul 2024 18:37:22 +0200 Subject: [PATCH] feat(LS): references (#1775) * companion pr to https://github.com/prisma/prisma-engines/pull/4934 * test references in LT --------- Co-authored-by: Serhii Tatarintsev --- .../multi-file/references/config.prisma | 9 + .../multi-file/references/enums.prisma | 6 + .../multi-file/references/models.prisma | 29 ++ .../multi-file/references/types.prisma | 12 + .../multi-file/references/views.prisma | 16 + .../src/__test__/references.test.ts | 283 ++++++++++++++++++ .../language-server/src/lib/MessageHandler.ts | 11 + .../src/lib/prisma-schema-wasm/references.ts | 29 ++ packages/language-server/src/server.ts | 12 + 9 files changed, 407 insertions(+) create mode 100644 packages/language-server/src/__test__/__fixtures__/multi-file/references/config.prisma create mode 100644 packages/language-server/src/__test__/__fixtures__/multi-file/references/enums.prisma create mode 100644 packages/language-server/src/__test__/__fixtures__/multi-file/references/models.prisma create mode 100644 packages/language-server/src/__test__/__fixtures__/multi-file/references/types.prisma create mode 100644 packages/language-server/src/__test__/__fixtures__/multi-file/references/views.prisma create mode 100644 packages/language-server/src/__test__/references.test.ts create mode 100644 packages/language-server/src/lib/prisma-schema-wasm/references.ts diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/references/config.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/references/config.prisma new file mode 100644 index 0000000000..6b19290cd2 --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/references/config.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder", "views"] +} + +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/references/enums.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/references/enums.prisma new file mode 100644 index 0000000000..a957299d4e --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/references/enums.prisma @@ -0,0 +1,6 @@ +/// enum doc +enum Pet { + /// Cat doc + Cat + RedPanda +} diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/references/models.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/references/models.prisma new file mode 100644 index 0000000000..078db3b097 --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/references/models.prisma @@ -0,0 +1,29 @@ +model User { + id String @id @map("_id") + + posts Post[] + /// field doc + pet Pet + address Address + + indexedField String + + @@index([indexedField]) +} + +/// Documentation +model Post { + id String @id @map("_id") + authorId String + author User @relation(fields: [authorId], references: [id]) + blah UserTwo[] +} + +model B { + /// field doc + id String @id @map("_id") + + bId String? + bees B[] @relation(name: "bees") + B B? @relation(name: "bees", fields: [bId], references: [id], onDelete: NoAction, onUpdate: NoAction) +} diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/references/types.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/references/types.prisma new file mode 100644 index 0000000000..383e38be6f --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/references/types.prisma @@ -0,0 +1,12 @@ +/// Address doc +type Address { + street String + city String + /// poBox doc + poBox POBox +} + +/// Nested ct doc +type POBox { + name String +} diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/references/views.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/references/views.prisma new file mode 100644 index 0000000000..94331912e4 --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/references/views.prisma @@ -0,0 +1,16 @@ +view UserTwo { + id String @id @map("_id") + + postId String + posts Post @relation(fields: [postId], references: [id]) + + address Address + + uniqueField String + otherUniqueField String + + @@unique(fields: [uniqueField]) + @@unique([otherUniqueField]) + @@unique([address.poBox.name]) + @@map("hi") +} diff --git a/packages/language-server/src/__test__/references.test.ts b/packages/language-server/src/__test__/references.test.ts new file mode 100644 index 0000000000..98a86479de --- /dev/null +++ b/packages/language-server/src/__test__/references.test.ts @@ -0,0 +1,283 @@ +import { describe, expect, test } from 'vitest' +import { Position, ReferenceParams } from 'vscode-languageserver' +import { handleReferencesRequest } from '../lib/MessageHandler' +import { PrismaSchema } from '../lib/Schema' +import { getMultifileHelper } from './MultifileHelper' + +const getReferences = async (uri: string, schema: PrismaSchema, position: Position) => { + const params: ReferenceParams = { + textDocument: { uri: uri }, + position, + context: { includeDeclaration: true }, + } + + return handleReferencesRequest(schema, params) +} +describe('References', async () => { + const helper = await getMultifileHelper('references') + + test('of a composite type block name', async () => { + const file = helper.file('types.prisma') + + const references = await getReferences( + file.uri, + helper.schema, + file.lineContaining('type Address').characterAfter('Addr'), + ) + + expect(references).not.toBeUndefined() + expect(references).toMatchInlineSnapshot(` + [ + { + "range": { + "end": { + "character": 12, + "line": 1, + }, + "start": { + "character": 5, + "line": 1, + }, + }, + "uri": "file:///references/types.prisma", + }, + { + "range": { + "end": { + "character": 19, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "uri": "file:///references/models.prisma", + }, + { + "range": { + "end": { + "character": 19, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "uri": "file:///references/views.prisma", + }, + ] + `) + }) + + test('of a composite type as a field type', async () => { + const file = helper.file('models.prisma') + + const references = await getReferences( + file.uri, + helper.schema, + file.lineContaining('address Address').characterAfter('Addr'), + ) + + expect(references).not.toBeUndefined() + expect(references).toMatchInlineSnapshot(` + [ + { + "range": { + "end": { + "character": 12, + "line": 1, + }, + "start": { + "character": 5, + "line": 1, + }, + }, + "uri": "file:///references/types.prisma", + }, + { + "range": { + "end": { + "character": 19, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "uri": "file:///references/models.prisma", + }, + { + "range": { + "end": { + "character": 19, + "line": 6, + }, + "start": { + "character": 12, + "line": 6, + }, + }, + "uri": "file:///references/views.prisma", + }, + ] + `) + }) + + test('of a model block name', async () => { + const file = helper.file('models.prisma') + + const references = await getReferences( + file.uri, + helper.schema, + file.lineContaining('model Post').characterAfter('Po'), + ) + + expect(references).not.toBeUndefined() + expect(references).toMatchInlineSnapshot(` + [ + { + "range": { + "end": { + "character": 10, + "line": 14, + }, + "start": { + "character": 6, + "line": 14, + }, + }, + "uri": "file:///references/models.prisma", + }, + { + "range": { + "end": { + "character": 16, + "line": 3, + }, + "start": { + "character": 12, + "line": 3, + }, + }, + "uri": "file:///references/models.prisma", + }, + { + "range": { + "end": { + "character": 15, + "line": 4, + }, + "start": { + "character": 11, + "line": 4, + }, + }, + "uri": "file:///references/views.prisma", + }, + ] + `) + }) + + test('of a model relation as a field type', async () => { + const file = helper.file('models.prisma') + + const references = await getReferences( + file.uri, + helper.schema, + file.lineContaining('author User').characterAfter('Us'), + ) + + expect(references).not.toBeUndefined() + expect(references).toMatchInlineSnapshot(` + [ + { + "range": { + "end": { + "character": 10, + "line": 0, + }, + "start": { + "character": 6, + "line": 0, + }, + }, + "uri": "file:///references/models.prisma", + }, + { + "range": { + "end": { + "character": 17, + "line": 17, + }, + "start": { + "character": 13, + "line": 17, + }, + }, + "uri": "file:///references/models.prisma", + }, + ] + `) + }) + + test('of a field from relation fields', async () => { + const file = helper.file('models.prisma') + + const references = await getReferences( + file.uri, + helper.schema, + file.lineContaining('author User').characterAfter('fields: [auth'), + ) + + expect(references).not.toBeUndefined() + expect(references).toMatchInlineSnapshot(` + [ + { + "range": { + "end": { + "character": 12, + "line": 16, + }, + "start": { + "character": 4, + "line": 16, + }, + }, + "uri": "file:///references/models.prisma", + }, + ] + `) + }) + + test('of a field from relation fields', async () => { + const file = helper.file('models.prisma') + + const references = await getReferences( + file.uri, + helper.schema, + file.lineContaining('author User').characterAfter('references: [i'), + ) + + expect(references).not.toBeUndefined() + expect(references).toMatchInlineSnapshot(` + [ + { + "range": { + "end": { + "character": 6, + "line": 1, + }, + "start": { + "character": 4, + "line": 1, + }, + }, + "uri": "file:///references/models.prisma", + }, + ] + `) + }) +}) diff --git a/packages/language-server/src/lib/MessageHandler.ts b/packages/language-server/src/lib/MessageHandler.ts index cc995e1b07..9c192840b1 100644 --- a/packages/language-server/src/lib/MessageHandler.ts +++ b/packages/language-server/src/lib/MessageHandler.ts @@ -17,6 +17,8 @@ import { DocumentSymbol, SymbolKind, LocationLink, + ReferenceParams, + Location, } from 'vscode-languageserver' import type { TextDocument } from 'vscode-languageserver-textdocument' @@ -53,6 +55,7 @@ import { import { prismaSchemaWasmCompletions, localCompletions } from './completions' import { PrismaSchema, SchemaDocument } from './Schema' import { DiagnosticMap } from './DiagnosticMap' +import references from './prisma-schema-wasm/references' export function handleDiagnosticsRequest( schema: PrismaSchema, @@ -223,6 +226,14 @@ export function handleCompletionRequest( return prismaSchemaWasmCompletions(schema, params, onError) || localCompletions(schema, document, params, onError) } +export function handleReferencesRequest( + schema: PrismaSchema, + params: ReferenceParams, + onError?: (errorMessage: string) => void, +): Location[] | undefined { + return references(schema, params, onError) +} + export function handleRenameRequest( schema: PrismaSchema, initiatingDocument: TextDocument, diff --git a/packages/language-server/src/lib/prisma-schema-wasm/references.ts b/packages/language-server/src/lib/prisma-schema-wasm/references.ts new file mode 100644 index 0000000000..285be12993 --- /dev/null +++ b/packages/language-server/src/lib/prisma-schema-wasm/references.ts @@ -0,0 +1,29 @@ +import { Location, ReferenceParams } from 'vscode-languageserver' +import { PrismaSchema } from '../Schema' +import { handleFormatPanic, handleWasmError } from './internals' +import { prismaSchemaWasm } from '.' + +export default function references( + schema: PrismaSchema, + params: ReferenceParams, + onError?: (errorMessage: string) => void, +): Location[] | undefined { + try { + if (process.env.FORCE_PANIC_PRISMA_SCHEMA) { + handleFormatPanic(() => { + console.debug('Triggering a Rust panic...') + prismaSchemaWasm.debug_panic() + }) + } + + const response = prismaSchemaWasm.references(JSON.stringify(schema), JSON.stringify(params)) + + return JSON.parse(response) as Location[] + } catch (e) { + const err = e as Error + + handleWasmError(err, 'references', onError) + + return undefined + } +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index c7e3f8b3a1..302cdc6837 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -13,6 +13,7 @@ import { DidChangeConfigurationNotification, Connection, DocumentSymbolParams, + ReferenceParams, } from 'vscode-languageserver' import { createConnection, IPCMessageReader, IPCMessageWriter } from 'vscode-languageserver/node' import { TextDocument } from 'vscode-languageserver-textdocument' @@ -81,6 +82,7 @@ export function startServer(options?: LSOptions): void { hoverProvider: true, renameProvider: true, documentSymbolProvider: true, + referencesProvider: true, }, } @@ -183,6 +185,16 @@ export function startServer(options?: LSOptions): void { } }) + connection.onReferences(async (params: ReferenceParams) => { + const doc = getDocument(params.textDocument.uri) + + if (doc) { + const schema = await PrismaSchema.load(doc, documents) + + return MessageHandler.handleReferencesRequest(schema, params, showErrorToast) + } + }) + // This handler resolves additional information for the item selected in the completion list. connection.onCompletionResolve((completionItem: CompletionItem) => { return MessageHandler.handleCompletionResolveRequest(completionItem)