From 599843d42ec8993014bd56758791a2c8f08b5065 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 12 Nov 2023 10:48:49 +0100 Subject: [PATCH] feat: customize rendering of `@param`, `@result`, and `@typeParam` tags --- .../safe-ds-lang/src/helpers/stringUtils.ts | 10 +++++ .../safe-ds-documentation-provider.ts | 25 ++++++++++-- .../tests/helpers/stringUtils.test.ts | 29 +++++++++++++- .../safe-ds-documentation-provider.test.ts | 39 ++++++++++++++++--- .../tests/language/lsp/formatting/creator.ts | 11 +----- 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/packages/safe-ds-lang/src/helpers/stringUtils.ts b/packages/safe-ds-lang/src/helpers/stringUtils.ts index a2bc03da9..660a4dcd1 100644 --- a/packages/safe-ds-lang/src/helpers/stringUtils.ts +++ b/packages/safe-ds-lang/src/helpers/stringUtils.ts @@ -1,3 +1,13 @@ +/** + * Normalizes line breaks to `\n`. + * + * @param text The text to normalize. + * @return The normalized text. + */ +export const normalizeLineBreaks = (text: string | undefined): string => { + return text?.replace(/\r\n?/gu, '\n') ?? ''; +}; + /** * Based on the given count, returns the singular or plural form of the given word. */ diff --git a/packages/safe-ds-lang/src/language/documentation/safe-ds-documentation-provider.ts b/packages/safe-ds-lang/src/language/documentation/safe-ds-documentation-provider.ts index cf18886f1..27c1b774c 100644 --- a/packages/safe-ds-lang/src/language/documentation/safe-ds-documentation-provider.ts +++ b/packages/safe-ds-lang/src/language/documentation/safe-ds-documentation-provider.ts @@ -5,6 +5,7 @@ import { JSDocComment, JSDocDocumentationProvider, JSDocRenderOptions, + type JSDocTag, parseJSDoc, } from 'langium'; import { @@ -17,6 +18,10 @@ import { SdsTypeParameter, } from '../generated/ast.js'; +const PARAM_TAG = 'param'; +const RESULT_TAG = 'result'; +const TYPE_PARAM_TAG = 'typeParam'; + export class SafeDsDocumentationProvider extends JSDocDocumentationProvider { override getDocumentation(node: AstNode): string | undefined { if (isSdsParameter(node) || isSdsResult(node) || isSdsTypeParameter(node)) { @@ -39,6 +44,16 @@ export class SafeDsDocumentationProvider extends JSDocDocumentationProvider { } } + protected override documentationTagRenderer(node: AstNode, tag: JSDocTag): string | undefined { + if (tag.name === PARAM_TAG || tag.name === RESULT_TAG || tag.name === TYPE_PARAM_TAG) { + const contentMd = tag.content.toMarkdown(); + const [paramName, description] = contentMd.split(/\s(.*)/su); + return `**@${tag.name}** *${paramName}* — ${(description ?? '').trim()}`; + } else { + return super.documentationTagRenderer(node, tag); + } + } + private getJSDocComment(node: AstNode): JSDocComment | undefined { const comment = this.commentProvider.getComment(node); if (comment && isJSDoc(comment)) { @@ -70,11 +85,11 @@ export class SafeDsDocumentationProvider extends JSDocDocumentationProvider { private getTagName(node: SdsParameter | SdsResult | SdsTypeParameter): string { if (isSdsParameter(node)) { - return 'param'; + return PARAM_TAG; } else if (isSdsResult(node)) { - return 'result'; + return RESULT_TAG; } else { - return 'typeParam'; + return TYPE_PARAM_TAG; } } @@ -83,6 +98,10 @@ export class SafeDsDocumentationProvider extends JSDocDocumentationProvider { renderLink: (link, display) => { return this.documentationLinkRenderer(node, link, display); }, + tag: 'bold', + renderTag: (tag: JSDocTag) => { + return this.documentationTagRenderer(node, tag); + }, }; } } diff --git a/packages/safe-ds-lang/tests/helpers/stringUtils.test.ts b/packages/safe-ds-lang/tests/helpers/stringUtils.test.ts index 3139058bd..9a5a2b9ec 100644 --- a/packages/safe-ds-lang/tests/helpers/stringUtils.test.ts +++ b/packages/safe-ds-lang/tests/helpers/stringUtils.test.ts @@ -1,5 +1,32 @@ import { describe, expect, it } from 'vitest'; -import { pluralize } from '../../src/helpers/stringUtils.js'; +import { normalizeLineBreaks, pluralize } from '../../src/helpers/stringUtils.js'; + +describe('normalizeLineBreaks', () => { + it.each([ + { + text: undefined, + expected: '', + }, + { + text: '', + expected: '', + }, + { + text: 'foo\nbar', + expected: 'foo\nbar', + }, + { + text: 'foo\rbar', + expected: 'foo\nbar', + }, + { + text: 'foo\r\nbar', + expected: 'foo\nbar', + }, + ])(`should normalize line breaks (%#)`, ({ text, expected }) => { + expect(normalizeLineBreaks(text)).toBe(expected); + }); +}); describe('pluralize', () => { it.each([ diff --git a/packages/safe-ds-lang/tests/language/documentation/safe-ds-documentation-provider.test.ts b/packages/safe-ds-lang/tests/language/documentation/safe-ds-documentation-provider.test.ts index e670cb239..7547285a2 100644 --- a/packages/safe-ds-lang/tests/language/documentation/safe-ds-documentation-provider.test.ts +++ b/packages/safe-ds-lang/tests/language/documentation/safe-ds-documentation-provider.test.ts @@ -1,8 +1,7 @@ -import { afterEach, describe, expect, it } from 'vitest'; -import { createSafeDsServices } from '../../../src/language/safe-ds-module.js'; -import { AstNode, EmptyFileSystem } from 'langium'; +import { AstNode, EmptyFileSystem, expandToString } from 'langium'; import { clearDocuments } from 'langium/test'; -import { getNodeOfType } from '../../helpers/nodeFinder.js'; +import { afterEach, describe, expect, it } from 'vitest'; +import { normalizeLineBreaks } from '../../../src/helpers/stringUtils.js'; import { isSdsAnnotation, isSdsFunction, @@ -10,6 +9,8 @@ import { isSdsResult, isSdsTypeParameter, } from '../../../src/language/generated/ast.js'; +import { createSafeDsServices } from '../../../src/language/index.js'; +import { getNodeOfType } from '../../helpers/nodeFinder.js'; const services = createSafeDsServices(EmptyFileSystem).SafeDs; const documentationProvider = services.documentation.DocumentationProvider; @@ -166,11 +167,39 @@ describe('SafeDsDocumentationProvider', () => { predicate: isSdsTypeParameter, expectedDocumentation: undefined, }, + { + testName: 'custom tag rendering', + code: ` + /** + * ${testDocumentation} + * + * @param param ${testDocumentation} + * @result result ${testDocumentation} + * @typeParam T ${testDocumentation} + * @since 1.0.0 + */ + fun myFunction(param: String) -> result: String + `, + predicate: isSdsFunction, + expectedDocumentation: expandToString` + Lorem ipsum. + + **@param** *param* — Lorem ipsum. + + **@result** *result* — Lorem ipsum. + + **@typeParam** *T* — Lorem ipsum. + + **@since** — 1.0.0 + `, + }, ]; it.each(testCases)('$testName', async ({ code, predicate, expectedDocumentation }) => { const node = await getNodeOfType(services, code, predicate); - expect(documentationProvider.getDocumentation(node)).toStrictEqual(expectedDocumentation); + const normalizedActual = normalizeLineBreaks(documentationProvider.getDocumentation(node)); + const normalizedExpected = normalizeLineBreaks(expectedDocumentation); + expect(normalizedActual).toStrictEqual(normalizedExpected); }); it('should resolve links', async () => { diff --git a/packages/safe-ds-lang/tests/language/lsp/formatting/creator.ts b/packages/safe-ds-lang/tests/language/lsp/formatting/creator.ts index a0b596c55..cf73b93fb 100644 --- a/packages/safe-ds-lang/tests/language/lsp/formatting/creator.ts +++ b/packages/safe-ds-lang/tests/language/lsp/formatting/creator.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import { EmptyFileSystem, URI } from 'langium'; import { Diagnostic } from 'vscode-languageserver'; +import { normalizeLineBreaks } from '../../../../src/helpers/stringUtils.js'; import { createSafeDsServices } from '../../../../src/language/index.js'; import { getSyntaxErrors } from '../../../helpers/diagnostics.js'; import { TestDescription, TestDescriptionError } from '../../../helpers/testDescription.js'; @@ -64,16 +65,6 @@ const invalidTest = (error: TestDescriptionError): FormattingTest => { }; }; -/** - * Normalizes line breaks to `\n`. - * - * @param code The code to normalize. - * @return The normalized code. - */ -const normalizeLineBreaks = (code: string): string => { - return code.replace(/\r\n?/gu, '\n'); -}; - /** * A description of a formatting test. */