langium
Version:
A language engineering tool for the Language Server Protocol
94 lines (82 loc) • 3.89 kB
text/typescript
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { LangiumCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription } from '../syntax-tree.js';
import type { IndexManager } from '../workspace/index-manager.js';
import type { CommentProvider } from './comment-provider.js';
import type { JSDocTag } from './jsdoc.js';
import { getDocument } from '../utils/ast-utils.js';
import { isJSDoc, parseJSDoc } from './jsdoc.js';
/**
* Provides documentation for AST nodes.
*/
export interface DocumentationProvider {
/**
* Returns a markdown documentation string for the specified AST node.
*
* The default implementation `JSDocDocumentationProvider` will inspect the comment associated with the specified node.
*/
getDocumentation(node: AstNode): string | undefined;
}
export class JSDocDocumentationProvider implements DocumentationProvider {
protected readonly indexManager: IndexManager;
protected readonly commentProvider: CommentProvider;
constructor(services: LangiumCoreServices) {
this.indexManager = services.shared.workspace.IndexManager;
this.commentProvider = services.documentation.CommentProvider;
}
getDocumentation(node: AstNode): string | undefined {
const comment = this.commentProvider.getComment(node);
if (comment && isJSDoc(comment)) {
const parsedJSDoc = parseJSDoc(comment);
return parsedJSDoc.toMarkdown({
renderLink: (link, display) => {
return this.documentationLinkRenderer(node, link, display);
},
renderTag: (tag) => {
return this.documentationTagRenderer(node, tag);
}
});
}
return undefined;
}
protected documentationLinkRenderer(node: AstNode, name: string, display: string): string | undefined {
const description = this.findNameInLocalSymbols(node, name) ?? this.findNameInGlobalScope(node, name);
if (description && description.nameSegment) {
const line = description.nameSegment.range.start.line + 1;
const character = description.nameSegment.range.start.character + 1;
const uri = description.documentUri.with({ fragment: `L${line},${character}` });
return `[${display}](${uri.toString()})`;
} else {
return undefined;
}
}
protected documentationTagRenderer(_node: AstNode, _tag: JSDocTag): string | undefined {
// Fall back to the default tag rendering
return undefined;
}
protected findNameInLocalSymbols(node: AstNode, name: string): AstNodeDescription | undefined {
const document = getDocument(node);
const precomputed = document.localSymbols;
if (!precomputed) {
return undefined;
}
let currentNode: AstNode | undefined = node;
do {
const allDescriptions = precomputed.getStream(currentNode);
const description = allDescriptions.find(e => e.name === name);
if (description) {
return description;
}
currentNode = currentNode.$container;
} while (currentNode);
return undefined;
}
protected findNameInGlobalScope(node: AstNode, name: string): AstNodeDescription | undefined {
const description = this.indexManager.allElements().find(e => e.name === name);
return description;
}
}