diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/Post.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/Post.prisma index 78de5db220..5696e75f20 100644 --- a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/Post.prisma +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/Post.prisma @@ -1,6 +1,6 @@ /// This is a blog post model Post { - id String @id @default(uuid()) + id String @id @default(uuid()) @map("_id") title String content String authorId String diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/User.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/User.prisma index 030c4d5bb1..e39bdfdadd 100644 --- a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/User.prisma +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/User.prisma @@ -1,7 +1,11 @@ /// This is the user of the platform model User { - id String @id @default(uuid()) + id String @id @default(uuid()) @map("_id") name String email String posts Post[] + + address Address + + favouriteAnimal FavouriteAnimal } diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/address.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/address.prisma new file mode 100644 index 0000000000..ff9f6dc7d6 --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/address.prisma @@ -0,0 +1,6 @@ +/// Petrichor V +type Address { + /// ISO 3166-2 standard + country String + POBox Int +} diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/animal.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/animal.prisma new file mode 100644 index 0000000000..605f11f3a2 --- /dev/null +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/animal.prisma @@ -0,0 +1,6 @@ +/// My favourite is the red panda, could you tell? +enum FavouriteAnimal { + RedPanda + Cat + Dog +} diff --git a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/config.prisma b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/config.prisma index 97fbd437ae..a329d451dc 100644 --- a/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/config.prisma +++ b/packages/language-server/src/__test__/__fixtures__/multi-file/user-posts/config.prisma @@ -1,5 +1,5 @@ datasource db { - provider = "postgresql" + provider = "mongodb" url = env("DATABASE_URL") } diff --git a/packages/language-server/src/__test__/hover.test.ts b/packages/language-server/src/__test__/hover.test.ts new file mode 100644 index 0000000000..f872f39e4c --- /dev/null +++ b/packages/language-server/src/__test__/hover.test.ts @@ -0,0 +1,135 @@ +import { test, expect, describe } from 'vitest' +import { handleHoverRequest } from '../lib/MessageHandler' +import { getMultifileHelper } from './MultifileHelper' + +describe('hover', () => { + test('model doc from field', async () => { + const helper = await getMultifileHelper('user-posts') + const user = helper.file('User.prisma') + + const response = handleHoverRequest(helper.schema, user.textDocument, { + textDocument: { + uri: user.uri, + }, + position: user.lineContaining('posts Post[]').characterAfter('Po'), + }) + + expect(response).toMatchInlineSnapshot(` + { + "contents": { + "kind": "markdown", + "value": "\`\`\`prisma + model Post { + ... + author User @relation(name: "PostToUser", fields: [authorId], references: [id]) + } + \`\`\` + ___ + one-to-many + ___ + This is a blog post", + }, + } + `) + }) + + test('enum doc from field', async () => { + const helper = await getMultifileHelper('user-posts') + const user = helper.file('User.prisma') + + const response = handleHoverRequest(helper.schema, user.textDocument, { + textDocument: { + uri: user.uri, + }, + position: user.lineContaining('favouriteAnimal FavouriteAnimal').characterAfter('Favo'), + }) + + expect(response).toMatchInlineSnapshot(` + { + "contents": { + "kind": "markdown", + "value": "\`\`\`prisma + enum FavouriteAnimal {} + \`\`\` + ___ + My favourite is the red panda, could you tell?", + }, + } + `) + }) + + test('composite doc from field', async () => { + const helper = await getMultifileHelper('user-posts') + const user = helper.file('User.prisma') + + const response = handleHoverRequest(helper.schema, user.textDocument, { + textDocument: { + uri: user.uri, + }, + position: user.lineContaining('address Address').characterAfter('Addr'), + }) + + expect(response).toMatchInlineSnapshot(` + { + "contents": { + "kind": "markdown", + "value": "\`\`\`prisma + type Address {} + \`\`\` + ___ + Petrichor V", + }, + } + `) + }) + + test('doc from block name', async () => { + const helper = await getMultifileHelper('user-posts') + const user = helper.file('animal.prisma') + + const response = handleHoverRequest(helper.schema, user.textDocument, { + textDocument: { + uri: user.uri, + }, + position: user.lineContaining('enum FavouriteAnimal {').characterAfter('Fav'), + }) + + expect(response).toMatchInlineSnapshot(` + { + "contents": { + "kind": "markdown", + "value": "\`\`\`prisma + enum FavouriteAnimal {} + \`\`\` + ___ + My favourite is the red panda, could you tell?", + }, + } + `) + }) + + test('doc from field name', async () => { + const helper = await getMultifileHelper('user-posts') + const user = helper.file('address.prisma') + + const response = handleHoverRequest(helper.schema, user.textDocument, { + textDocument: { + uri: user.uri, + }, + position: user.lineContaining('country String').characterAfter('cou'), + }) + + expect(response).toMatchInlineSnapshot(` + { + "contents": { + "kind": "markdown", + "value": "\`\`\`prisma + country + \`\`\` + ___ + ISO 3166-2 standard", + }, + } + `) + }) +}) diff --git a/packages/language-server/src/__test__/hover/multi-file.test.ts b/packages/language-server/src/__test__/hover/multi-file.test.ts deleted file mode 100644 index 4a7d8a2cc3..0000000000 --- a/packages/language-server/src/__test__/hover/multi-file.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { test, expect } from 'vitest' -import { handleHoverRequest } from '../../lib/MessageHandler' -import { getMultifileHelper } from '../MultifileHelper' - -test('basic doc', async () => { - const helper = await getMultifileHelper('user-posts') - const user = helper.file('User.prisma') - - const response = handleHoverRequest(helper.schema, user.textDocument, { - textDocument: { - uri: user.uri, - }, - position: user.lineContaining('posts Post[]').characterAfter('Po'), - }) - - expect(response).toMatchInlineSnapshot(` - { - "contents": "This is a blog post", - } - `) -}) diff --git a/packages/language-server/src/__test__/hover/single-file.test.ts b/packages/language-server/src/__test__/hover/single-file.test.ts deleted file mode 100644 index 19c39697dc..0000000000 --- a/packages/language-server/src/__test__/hover/single-file.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { expect, describe, test } from 'vitest' -import { Position, Hover } from 'vscode-languageserver' -import { handleHoverRequest } from '../../lib/MessageHandler' -import { PrismaSchema } from '../../lib/Schema' -import { getTextDocument } from '../helper' - -function assertHover(position: Position, expected: Hover, fixturePath: string): void { - const textDocument = getTextDocument(fixturePath) - - const params = { - textDocument, - position: position, - } - const hoverResult: Hover | undefined = handleHoverRequest(PrismaSchema.singleFile(textDocument), textDocument, params) - - expect(hoverResult).not.toBeUndefined() - expect(hoverResult?.contents).toStrictEqual(expected.contents) - expect(hoverResult?.range).toStrictEqual(expected.range) -} - -describe('Hover of /// documentation comments', () => { - const fixturePath = './hover/postgresql.prisma' - - test('Model', () => { - assertHover( - { - character: 15, - line: 24, - }, - { - contents: "Post including an author, it's content\n\nand whether it was published", - }, - fixturePath, - ) - }) - test('Enum', () => { - assertHover( - { - character: 15, - line: 25, - }, - { - contents: 'This is an enum specifying the UserName.', - }, - fixturePath, - ) - }) -}) diff --git a/packages/language-server/src/__test__/jump-to-definition/multi-file.test.ts b/packages/language-server/src/__test__/jump-to-definition/multi-file.test.ts index af387e9610..99e3aa60ef 100644 --- a/packages/language-server/src/__test__/jump-to-definition/multi-file.test.ts +++ b/packages/language-server/src/__test__/jump-to-definition/multi-file.test.ts @@ -19,7 +19,7 @@ test('basic doc', async () => { "targetRange": { "end": { "character": 1, - "line": 6, + "line": 10, }, "start": { "character": 0, diff --git a/packages/language-server/src/__test__/rename/multi-file.test.ts b/packages/language-server/src/__test__/rename/multi-file.test.ts index e3ac75c897..76ff24e861 100644 --- a/packages/language-server/src/__test__/rename/multi-file.test.ts +++ b/packages/language-server/src/__test__/rename/multi-file.test.ts @@ -52,11 +52,11 @@ test('rename model', async () => { "range": { "end": { "character": 1, - "line": 6, + "line": 10, }, "start": { "character": 0, - "line": 6, + "line": 10, }, }, }, @@ -69,7 +69,7 @@ test('rename model', async () => { { "file:///user-posts/Post.prisma": "/// This is a blog post model Post { - id String @id @default(uuid()) + id String @id @default(uuid()) @map("_id") title String content String authorId String @@ -78,10 +78,14 @@ test('rename model', async () => { ", "file:///user-posts/User.prisma": "/// This is the user of the platform model Account { - id String @id @default(uuid()) + id String @id @default(uuid()) @map("_id") name String email String posts Post[] + + address Address + + favouriteAnimal FavouriteAnimal @@map("User") } ", @@ -133,19 +137,6 @@ test('rename field', async () => { }, }, }, - { - "newText": " @map("id")", - "range": { - "end": { - "character": 36, - "line": 2, - }, - "start": { - "character": 36, - "line": 2, - }, - }, - }, ], }, } @@ -155,7 +146,7 @@ test('rename field', async () => { { "file:///user-posts/Post.prisma": "/// This is a blog post model Post { - id String @id @default(uuid()) + id String @id @default(uuid()) @map("_id") title String content String authorId String @@ -164,10 +155,14 @@ test('rename field', async () => { ", "file:///user-posts/User.prisma": "/// This is the user of the platform model User { - primaryKey String @id @default(uuid() @map("id")) + primaryKey String @id @default(uuid()) @map("_id") name String email String posts Post[] + + address Address + + favouriteAnimal FavouriteAnimal } ", } diff --git a/packages/language-server/src/lib/MessageHandler.ts b/packages/language-server/src/lib/MessageHandler.ts index 9c192840b1..93eabc5743 100644 --- a/packages/language-server/src/lib/MessageHandler.ts +++ b/packages/language-server/src/lib/MessageHandler.ts @@ -56,6 +56,7 @@ import { prismaSchemaWasmCompletions, localCompletions } from './completions' import { PrismaSchema, SchemaDocument } from './Schema' import { DiagnosticMap } from './DiagnosticMap' import references from './prisma-schema-wasm/references' +import hover from './prisma-schema-wasm/hover' export function handleDiagnosticsRequest( schema: PrismaSchema, @@ -190,27 +191,9 @@ export function handleHoverRequest( schema: PrismaSchema, initiatingDocument: TextDocument, params: HoverParams, + onError?: (errorMessage: string) => void, ): Hover | undefined { - const position = params.position - - const word = getWordAtPosition(initiatingDocument, position) - - if (word === '') { - return - } - - const block = getDatamodelBlock(word, schema) - if (!block) { - return - } - - const blockDocumentation = getDocumentationForBlock(block) - - if (blockDocumentation.length !== 0) { - return { - contents: blockDocumentation.join('\n\n'), - } - } + return hover(schema, initiatingDocument, params, onError) } /** diff --git a/packages/language-server/src/lib/prisma-schema-wasm/hover.ts b/packages/language-server/src/lib/prisma-schema-wasm/hover.ts new file mode 100644 index 0000000000..7e8ecf0092 --- /dev/null +++ b/packages/language-server/src/lib/prisma-schema-wasm/hover.ts @@ -0,0 +1,31 @@ +import { Hover, HoverParams } from 'vscode-languageserver' +import { prismaSchemaWasm } from '.' +import { handleFormatPanic, handleWasmError } from './internals' +import { PrismaSchema } from '../Schema' +import { TextDocument } from 'vscode-languageserver-textdocument' + +export default function hover( + schema: PrismaSchema, + initiatingDocument: TextDocument, + params: HoverParams, + onError?: (errorMessage: string) => void, +): Hover | undefined { + try { + if (process.env.FORCE_PANIC_PRISMA_SCHEMA) { + handleFormatPanic(() => { + console.debug('Triggering a Rust panic...') + prismaSchemaWasm.debug_panic() + }) + } + + const result = prismaSchemaWasm.hover(JSON.stringify(schema), JSON.stringify(params)) + + return JSON.parse(result) as Hover | undefined + } catch (e) { + const err = e as Error + + handleWasmError(err, 'hover', onError) + + return undefined + } +} diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 302cdc6837..fd40392400 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -213,7 +213,7 @@ export function startServer(options?: LSOptions): void { const doc = getDocument(params.textDocument.uri) if (doc) { const schema = await PrismaSchema.load(doc, documents) - return MessageHandler.handleHoverRequest(schema, doc, params) + return MessageHandler.handleHoverRequest(schema, doc, params, showErrorToast) } }) diff --git a/packages/vscode/src/__test__/language-server/hover.test.ts b/packages/vscode/src/__test__/language-server/hover.test.ts index fff014e3c6..a7dd381fc3 100644 --- a/packages/vscode/src/__test__/language-server/hover.test.ts +++ b/packages/vscode/src/__test__/language-server/hover.test.ts @@ -18,29 +18,10 @@ async function testHover(docUri: vscode.Uri, position: vscode.Position, expected suite('Should show /// documentation comments for', () => { const docUri = getDocUri('hover/schema.prisma') + const expectedHover = `\`\`\`prisma\nmodel Post {\n\t...\n\tauthor User? @relation(name: "PostToUser", fields: [authorId], references: [id])\n}\n\`\`\`\n___\none-to-many\n___\nPost including an author and content.` + test('model', async () => { await activate(docUri) - await testHover(docUri, new vscode.Position(23, 10), 'Post including an author and content.') + await testHover(docUri, new vscode.Position(23, 10), expectedHover) }) }) - -// TODO do we still need this? -// TODO uncomment once https://github.com/prisma/prisma/issues/2546 is resolved! -/* -suite('Should show // comments for', () => { - const docUri = getDocUri('hover.prisma') - - test('model', async() => { - await testHover( - docUri, - new vscode.Position(14, 15), - 'Documentation for this model.') - }) - test('enum', async () => { - await testHover( - docUri, - new vscode.Position(25, 9), - 'This is a test enum.' - ) - }) -}) */