-
Notifications
You must be signed in to change notification settings - Fork 61
/
documentation-provider.ts
85 lines (75 loc) · 3.44 KB
/
documentation-provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/******************************************************************************
* 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 { GrammarConfig } from '../grammar/grammar-config';
import { LangiumServices } from '../services';
import { AstNode, AstNodeDescription, isLeafCstNode } from '../syntax-tree';
import { getDocument } from '../utils/ast-util';
import { findCommentNode } from '../utils/cst-util';
import { IndexManager } from '../workspace/index-manager';
import { isJSDoc, parseJSDoc } from './jsdoc';
/**
* 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 grammarConfig: GrammarConfig;
constructor(services: LangiumServices) {
this.indexManager = services.shared.workspace.IndexManager;
this.grammarConfig = services.parser.GrammarConfig;
}
getDocumentation(node: AstNode): string | undefined {
const lastNode = findCommentNode(node.$cstNode, this.grammarConfig.multilineCommentRules);
if (isLeafCstNode(lastNode) && isJSDoc(lastNode)) {
const parsedJSDoc = parseJSDoc(lastNode);
return parsedJSDoc.toMarkdown({
renderLink: (link, display) => {
return this.documentationLinkRenderer(node, link, display);
}
});
}
return undefined;
}
protected documentationLinkRenderer(node: AstNode, name: string, display: string): string | undefined {
const description = this.findNameInPrecomputedScopes(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 findNameInPrecomputedScopes(node: AstNode, name: string): AstNodeDescription | undefined {
const document = getDocument(node);
const precomputed = document.precomputedScopes;
if (!precomputed) {
return undefined;
}
let currentNode: AstNode | undefined = node;
do {
const allDescriptions = precomputed.get(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;
}
}