+25
@@ -0,0 +1,25 @@
|
||||
/******************************************************************************
|
||||
* 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 { GrammarConfig } from '../languages/grammar-config.js';
|
||||
import type { LangiumCoreServices } from '../services.js';
|
||||
import type { AstNode } from '../syntax-tree.js';
|
||||
/**
|
||||
* Provides comments for AST nodes.
|
||||
*/
|
||||
export interface CommentProvider {
|
||||
/**
|
||||
* Returns the comment associated with the specified AST node.
|
||||
* @param node The AST node to get the comment for.
|
||||
* @returns The comment associated with the specified AST node or `undefined` if there is no comment.
|
||||
*/
|
||||
getComment(node: AstNode): string | undefined;
|
||||
}
|
||||
export declare class DefaultCommentProvider implements CommentProvider {
|
||||
protected readonly grammarConfig: () => GrammarConfig;
|
||||
constructor(services: LangiumCoreServices);
|
||||
getComment(node: AstNode): string | undefined;
|
||||
}
|
||||
//# sourceMappingURL=comment-provider.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"comment-provider.d.ts","sourceRoot":"","sources":["../../src/documentation/comment-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CACjD;AAED,qBAAa,sBAAuB,YAAW,eAAe;IAC1D,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,aAAa,CAAC;gBAC1C,QAAQ,EAAE,mBAAmB;IAGzC,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;CAMhD"}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/******************************************************************************
|
||||
* 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 { isAstNodeWithComment } from '../serializer/json-serializer.js';
|
||||
import { findCommentNode } from '../utils/cst-utils.js';
|
||||
export class DefaultCommentProvider {
|
||||
constructor(services) {
|
||||
this.grammarConfig = () => services.parser.GrammarConfig;
|
||||
}
|
||||
getComment(node) {
|
||||
if (isAstNodeWithComment(node)) {
|
||||
return node.$comment;
|
||||
}
|
||||
return findCommentNode(node.$cstNode, this.grammarConfig().multilineCommentRules)?.text;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=comment-provider.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"comment-provider.js","sourceRoot":"","sources":["../../src/documentation/comment-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAGxE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAcxD,MAAM,OAAO,sBAAsB;IAE/B,YAAY,QAA6B;QACrC,IAAI,CAAC,aAAa,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC;IAC7D,CAAC;IACD,UAAU,CAAC,IAAa;QACpB,IAAG,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC;QACzB,CAAC;QACD,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC;IAC5F,CAAC;CACJ"}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/******************************************************************************
|
||||
* 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';
|
||||
/**
|
||||
* 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 declare class JSDocDocumentationProvider implements DocumentationProvider {
|
||||
protected readonly indexManager: IndexManager;
|
||||
protected readonly commentProvider: CommentProvider;
|
||||
constructor(services: LangiumCoreServices);
|
||||
getDocumentation(node: AstNode): string | undefined;
|
||||
protected documentationLinkRenderer(node: AstNode, name: string, display: string): string | undefined;
|
||||
protected documentationTagRenderer(_node: AstNode, _tag: JSDocTag): string | undefined;
|
||||
protected findNameInLocalSymbols(node: AstNode, name: string): AstNodeDescription | undefined;
|
||||
protected findNameInGlobalScope(node: AstNode, name: string): AstNodeDescription | undefined;
|
||||
}
|
||||
//# sourceMappingURL=documentation-provider.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"documentation-provider.d.ts","sourceRoot":"","sources":["../../src/documentation/documentation-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CACvD;AAED,qBAAa,0BAA2B,YAAW,qBAAqB;IAEpE,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAC9C,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;gBAExC,QAAQ,EAAE,mBAAmB;IAKzC,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;IAgBnD,SAAS,CAAC,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAYrG,SAAS,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS;IAKtF,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAmB7F,SAAS,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;CAI/F"}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/******************************************************************************
|
||||
* 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 { getDocument } from '../utils/ast-utils.js';
|
||||
import { isJSDoc, parseJSDoc } from './jsdoc.js';
|
||||
export class JSDocDocumentationProvider {
|
||||
constructor(services) {
|
||||
this.indexManager = services.shared.workspace.IndexManager;
|
||||
this.commentProvider = services.documentation.CommentProvider;
|
||||
}
|
||||
getDocumentation(node) {
|
||||
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;
|
||||
}
|
||||
documentationLinkRenderer(node, name, display) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
documentationTagRenderer(_node, _tag) {
|
||||
// Fall back to the default tag rendering
|
||||
return undefined;
|
||||
}
|
||||
findNameInLocalSymbols(node, name) {
|
||||
const document = getDocument(node);
|
||||
const precomputed = document.localSymbols;
|
||||
if (!precomputed) {
|
||||
return undefined;
|
||||
}
|
||||
let currentNode = 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;
|
||||
}
|
||||
findNameInGlobalScope(node, name) {
|
||||
const description = this.indexManager.allElements().find(e => e.name === name);
|
||||
return description;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=documentation-provider.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"documentation-provider.js","sourceRoot":"","sources":["../../src/documentation/documentation-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAOhF,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAcjD,MAAM,OAAO,0BAA0B;IAKnC,YAAY,QAA6B;QACrC,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC;QAC3D,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAC;IAClE,CAAC;IAED,gBAAgB,CAAC,IAAa;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;YACxC,OAAO,WAAW,CAAC,UAAU,CAAC;gBAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;oBAC1B,OAAO,IAAI,CAAC,yBAAyB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;gBAC/D,CAAC;gBACD,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;oBACf,OAAO,IAAI,CAAC,wBAAwB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBACpD,CAAC;aACJ,CAAC,CAAC;QACP,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAES,yBAAyB,CAAC,IAAa,EAAE,IAAY,EAAE,OAAe;QAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACtG,IAAI,WAAW,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;YAC1D,MAAM,SAAS,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;YACpE,MAAM,GAAG,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,IAAI,IAAI,SAAS,EAAE,EAAE,CAAC,CAAC;YAChF,OAAO,IAAI,OAAO,KAAK,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC;QAC7C,CAAC;aAAM,CAAC;YACJ,OAAO,SAAS,CAAC;QACrB,CAAC;IACL,CAAC;IAES,wBAAwB,CAAC,KAAc,EAAE,IAAc;QAC7D,yCAAyC;QACzC,OAAO,SAAS,CAAC;IACrB,CAAC;IAES,sBAAsB,CAAC,IAAa,EAAE,IAAY;QACxD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,WAAW,GAAG,QAAQ,CAAC,YAAY,CAAC;QAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,WAAW,GAAwB,IAAI,CAAC;QAC5C,GAAG,CAAC;YACA,MAAM,eAAe,GAAG,WAAW,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC3D,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAC/D,IAAI,WAAW,EAAE,CAAC;gBACd,OAAO,WAAW,CAAC;YACvB,CAAC;YACD,WAAW,GAAG,WAAW,CAAC,UAAU,CAAC;QACzC,CAAC,QAAQ,WAAW,EAAE;QAEtB,OAAO,SAAS,CAAC;IACrB,CAAC;IAES,qBAAqB,CAAC,IAAa,EAAE,IAAY;QACvD,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC/E,OAAO,WAAW,CAAC;IACvB,CAAC;CACJ"}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
export * from './comment-provider.js';
|
||||
export * from './documentation-provider.js';
|
||||
export * from './jsdoc.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/documentation/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,YAAY,CAAC"}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
/******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
export * from './comment-provider.js';
|
||||
export * from './documentation-provider.js';
|
||||
export * from './jsdoc.js';
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/documentation/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,YAAY,CAAC"}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/******************************************************************************
|
||||
* 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 { Position, Range } from 'vscode-languageserver-types';
|
||||
import type { CstNode } from '../syntax-tree.js';
|
||||
export interface JSDocComment extends JSDocValue {
|
||||
readonly elements: JSDocElement[];
|
||||
getTag(name: string): JSDocTag | undefined;
|
||||
getTags(name: string): JSDocTag[];
|
||||
}
|
||||
export type JSDocElement = JSDocParagraph | JSDocTag;
|
||||
export type JSDocInline = JSDocTag | JSDocLine;
|
||||
export interface JSDocValue {
|
||||
/**
|
||||
* Represents the range that this JSDoc element occupies.
|
||||
* If the JSDoc was parsed from a `CstNode`, the range will represent the location in the source document.
|
||||
*/
|
||||
readonly range: Range;
|
||||
/**
|
||||
* Renders this JSDoc element to a plain text representation.
|
||||
*/
|
||||
toString(): string;
|
||||
/**
|
||||
* Renders this JSDoc element to a markdown representation.
|
||||
*
|
||||
* @param options Rendering options to customize the markdown result.
|
||||
*/
|
||||
toMarkdown(options?: JSDocRenderOptions): string;
|
||||
}
|
||||
export interface JSDocParagraph extends JSDocValue {
|
||||
readonly inlines: JSDocInline[];
|
||||
}
|
||||
export interface JSDocLine extends JSDocValue {
|
||||
readonly text: string;
|
||||
}
|
||||
export interface JSDocTag extends JSDocValue {
|
||||
readonly name: string;
|
||||
readonly content: JSDocParagraph;
|
||||
readonly inline: boolean;
|
||||
}
|
||||
export interface JSDocParseOptions {
|
||||
/**
|
||||
* The start symbol of your comment format. Defaults to `/**`.
|
||||
*/
|
||||
readonly start?: RegExp | string;
|
||||
/**
|
||||
* The symbol that start a line of your comment format. Defaults to `*`.
|
||||
*/
|
||||
readonly line?: RegExp | string;
|
||||
/**
|
||||
* The end symbol of your comment format. Defaults to `*\/`.
|
||||
*/
|
||||
readonly end?: RegExp | string;
|
||||
}
|
||||
export interface JSDocRenderOptions {
|
||||
/**
|
||||
* Determines the style for rendering tags. Defaults to `italic`.
|
||||
*/
|
||||
tag?: 'plain' | 'italic' | 'bold' | 'bold-italic';
|
||||
/**
|
||||
* Determines the default for rendering `@link` tags. Defaults to `plain`.
|
||||
*/
|
||||
link?: 'code' | 'plain';
|
||||
/**
|
||||
* Custom tag rendering function.
|
||||
* Return a markdown formatted tag or `undefined` to fall back to the default rendering.
|
||||
*/
|
||||
renderTag?(tag: JSDocTag): string | undefined;
|
||||
/**
|
||||
* Custom link rendering function. Accepts a link target and a display value for the link.
|
||||
* Return a markdown formatted link with the format `[$display]($link)` or `undefined` if the link is not a valid target.
|
||||
*/
|
||||
renderLink?(link: string, display: string): string | undefined;
|
||||
}
|
||||
/**
|
||||
* Parses a JSDoc from a `CstNode` containing a comment.
|
||||
*
|
||||
* @param node A `CstNode` from a parsed Langium document.
|
||||
* @param options Parsing options specialized to your language. See {@link JSDocParseOptions}.
|
||||
*/
|
||||
export declare function parseJSDoc(node: CstNode, options?: JSDocParseOptions): JSDocComment;
|
||||
/**
|
||||
* Parses a JSDoc from a string comment.
|
||||
*
|
||||
* @param content A string containing the source of the JSDoc comment.
|
||||
* @param start The start position the comment occupies in the source document.
|
||||
* @param options Parsing options specialized to your language. See {@link JSDocParseOptions}.
|
||||
*/
|
||||
export declare function parseJSDoc(content: string, start?: Position, options?: JSDocParseOptions): JSDocComment;
|
||||
export declare function isJSDoc(node: CstNode | string, options?: JSDocParseOptions): boolean;
|
||||
//# sourceMappingURL=jsdoc.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"jsdoc.d.ts","sourceRoot":"","sources":["../../src/documentation/jsdoc.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC5C,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;IACjC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC1C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAA;CACpC;AAED,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,QAAQ,CAAC;AAErD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/C,MAAM,WAAW,UAAU;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB;;OAEG;IACH,QAAQ,IAAI,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAA;CACnD;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAC9C,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,SAAU,SAAQ,UAAU;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,QAAS,SAAQ,UAAU;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAC9B;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B;;OAEG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IAC/B;;OAEG;IACH,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAA;IACjD;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;IAC7C;;;OAGG;IACH,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,YAAY,CAAC;AACrF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,YAAY,CAAC;AA+BzG,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAapF"}
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
/******************************************************************************
|
||||
* 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 { Position, Range } from 'vscode-languageserver-types';
|
||||
import { NEWLINE_REGEXP, escapeRegExp } from '../utils/regexp-utils.js';
|
||||
import { URI } from '../utils/uri-utils.js';
|
||||
export function parseJSDoc(node, start, options) {
|
||||
let opts;
|
||||
let position;
|
||||
if (typeof node === 'string') {
|
||||
position = start;
|
||||
opts = options;
|
||||
}
|
||||
else {
|
||||
position = node.range.start;
|
||||
opts = start;
|
||||
}
|
||||
if (!position) {
|
||||
position = Position.create(0, 0);
|
||||
}
|
||||
const lines = getLines(node);
|
||||
const normalizedOptions = normalizeOptions(opts);
|
||||
const tokens = tokenize({
|
||||
lines,
|
||||
position,
|
||||
options: normalizedOptions
|
||||
});
|
||||
return parseJSDocComment({
|
||||
index: 0,
|
||||
tokens,
|
||||
position
|
||||
});
|
||||
}
|
||||
export function isJSDoc(node, options) {
|
||||
const normalizedOptions = normalizeOptions(options);
|
||||
const lines = getLines(node);
|
||||
if (lines.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const first = lines[0];
|
||||
const last = lines[lines.length - 1];
|
||||
const firstRegex = normalizedOptions.start;
|
||||
const lastRegex = normalizedOptions.end;
|
||||
return Boolean(firstRegex?.exec(first)) && Boolean(lastRegex?.exec(last));
|
||||
}
|
||||
function getLines(node) {
|
||||
let content = '';
|
||||
if (typeof node === 'string') {
|
||||
content = node;
|
||||
}
|
||||
else {
|
||||
content = node.text;
|
||||
}
|
||||
const lines = content.split(NEWLINE_REGEXP);
|
||||
return lines;
|
||||
}
|
||||
const tagRegex = /\s*(@([\p{L}][\p{L}\p{N}]*)?)/uy;
|
||||
const inlineTagRegex = /\{(@[\p{L}][\p{L}\p{N}]*)(\s*)([^\r\n}]+)?\}/gu;
|
||||
function tokenize(context) {
|
||||
const tokens = [];
|
||||
let currentLine = context.position.line;
|
||||
let currentCharacter = context.position.character;
|
||||
for (let i = 0; i < context.lines.length; i++) {
|
||||
const first = i === 0;
|
||||
const last = i === context.lines.length - 1;
|
||||
let line = context.lines[i];
|
||||
let index = 0;
|
||||
if (first && context.options.start) {
|
||||
const match = context.options.start?.exec(line);
|
||||
if (match) {
|
||||
index = match.index + match[0].length;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const match = context.options.line?.exec(line);
|
||||
if (match) {
|
||||
index = match.index + match[0].length;
|
||||
}
|
||||
}
|
||||
if (last) {
|
||||
const match = context.options.end?.exec(line);
|
||||
if (match) {
|
||||
line = line.substring(0, match.index);
|
||||
}
|
||||
}
|
||||
line = line.substring(0, lastCharacter(line));
|
||||
const whitespaceEnd = skipWhitespace(line, index);
|
||||
if (whitespaceEnd >= line.length) {
|
||||
// Only create a break token when we already have previous tokens
|
||||
if (tokens.length > 0) {
|
||||
const position = Position.create(currentLine, currentCharacter);
|
||||
tokens.push({
|
||||
type: 'break',
|
||||
content: '',
|
||||
range: Range.create(position, position)
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
tagRegex.lastIndex = index;
|
||||
const tagMatch = tagRegex.exec(line);
|
||||
if (tagMatch) {
|
||||
const fullMatch = tagMatch[0];
|
||||
const value = tagMatch[1];
|
||||
const start = Position.create(currentLine, currentCharacter + index);
|
||||
const end = Position.create(currentLine, currentCharacter + index + fullMatch.length);
|
||||
tokens.push({
|
||||
type: 'tag',
|
||||
content: value,
|
||||
range: Range.create(start, end)
|
||||
});
|
||||
index += fullMatch.length;
|
||||
index = skipWhitespace(line, index);
|
||||
}
|
||||
if (index < line.length) {
|
||||
const rest = line.substring(index);
|
||||
const inlineTagMatches = Array.from(rest.matchAll(inlineTagRegex));
|
||||
tokens.push(...buildInlineTokens(inlineTagMatches, rest, currentLine, currentCharacter + index));
|
||||
}
|
||||
}
|
||||
currentLine++;
|
||||
currentCharacter = 0;
|
||||
}
|
||||
// Remove last break token if there is one
|
||||
if (tokens.length > 0 && tokens[tokens.length - 1].type === 'break') {
|
||||
return tokens.slice(0, -1);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
function buildInlineTokens(tags, line, lineIndex, characterIndex) {
|
||||
const tokens = [];
|
||||
if (tags.length === 0) {
|
||||
const start = Position.create(lineIndex, characterIndex);
|
||||
const end = Position.create(lineIndex, characterIndex + line.length);
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
content: line,
|
||||
range: Range.create(start, end)
|
||||
});
|
||||
}
|
||||
else {
|
||||
let lastIndex = 0;
|
||||
for (const match of tags) {
|
||||
const matchIndex = match.index;
|
||||
const startContent = line.substring(lastIndex, matchIndex);
|
||||
if (startContent.length > 0) {
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
content: line.substring(lastIndex, matchIndex),
|
||||
range: Range.create(Position.create(lineIndex, lastIndex + characterIndex), Position.create(lineIndex, matchIndex + characterIndex))
|
||||
});
|
||||
}
|
||||
let offset = startContent.length + 1;
|
||||
const tagName = match[1];
|
||||
tokens.push({
|
||||
type: 'inline-tag',
|
||||
content: tagName,
|
||||
range: Range.create(Position.create(lineIndex, lastIndex + offset + characterIndex), Position.create(lineIndex, lastIndex + offset + tagName.length + characterIndex))
|
||||
});
|
||||
offset += tagName.length;
|
||||
if (match.length === 4) {
|
||||
offset += match[2].length;
|
||||
const value = match[3];
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
content: value,
|
||||
range: Range.create(Position.create(lineIndex, lastIndex + offset + characterIndex), Position.create(lineIndex, lastIndex + offset + value.length + characterIndex))
|
||||
});
|
||||
}
|
||||
else {
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
content: '',
|
||||
range: Range.create(Position.create(lineIndex, lastIndex + offset + characterIndex), Position.create(lineIndex, lastIndex + offset + characterIndex))
|
||||
});
|
||||
}
|
||||
lastIndex = matchIndex + match[0].length;
|
||||
}
|
||||
const endContent = line.substring(lastIndex);
|
||||
if (endContent.length > 0) {
|
||||
tokens.push({
|
||||
type: 'text',
|
||||
content: endContent,
|
||||
range: Range.create(Position.create(lineIndex, lastIndex + characterIndex), Position.create(lineIndex, lastIndex + characterIndex + endContent.length))
|
||||
});
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
const nonWhitespaceRegex = /\S/;
|
||||
const whitespaceEndRegex = /\s*$/;
|
||||
function skipWhitespace(line, index) {
|
||||
const match = line.substring(index).match(nonWhitespaceRegex);
|
||||
if (match) {
|
||||
return index + match.index;
|
||||
}
|
||||
else {
|
||||
return line.length;
|
||||
}
|
||||
}
|
||||
function lastCharacter(line) {
|
||||
const match = line.match(whitespaceEndRegex);
|
||||
if (match && typeof match.index === 'number') {
|
||||
return match.index;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// Parsing
|
||||
function parseJSDocComment(context) {
|
||||
const startPosition = Position.create(context.position.line, context.position.character);
|
||||
if (context.tokens.length === 0) {
|
||||
return new JSDocCommentImpl([], Range.create(startPosition, startPosition));
|
||||
}
|
||||
const elements = [];
|
||||
while (context.index < context.tokens.length) {
|
||||
const element = parseJSDocElement(context, elements[elements.length - 1]);
|
||||
if (element) {
|
||||
elements.push(element);
|
||||
}
|
||||
}
|
||||
const start = elements[0]?.range.start ?? startPosition;
|
||||
const end = elements[elements.length - 1]?.range.end ?? startPosition;
|
||||
return new JSDocCommentImpl(elements, Range.create(start, end));
|
||||
}
|
||||
function parseJSDocElement(context, last) {
|
||||
const next = context.tokens[context.index];
|
||||
if (next.type === 'tag') {
|
||||
return parseJSDocTag(context, false);
|
||||
}
|
||||
else if (next.type === 'text' || next.type === 'inline-tag') {
|
||||
return parseJSDocText(context);
|
||||
}
|
||||
else {
|
||||
appendEmptyLine(next, last);
|
||||
context.index++;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
function appendEmptyLine(token, element) {
|
||||
if (element) {
|
||||
const line = new JSDocLineImpl('', token.range);
|
||||
if ('inlines' in element) {
|
||||
element.inlines.push(line);
|
||||
}
|
||||
else {
|
||||
element.content.inlines.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
function parseJSDocText(context) {
|
||||
let token = context.tokens[context.index];
|
||||
const firstToken = token;
|
||||
let lastToken = token;
|
||||
const lines = [];
|
||||
while (token && token.type !== 'break' && token.type !== 'tag') {
|
||||
lines.push(parseJSDocInline(context));
|
||||
lastToken = token;
|
||||
token = context.tokens[context.index];
|
||||
}
|
||||
return new JSDocTextImpl(lines, Range.create(firstToken.range.start, lastToken.range.end));
|
||||
}
|
||||
function parseJSDocInline(context) {
|
||||
const token = context.tokens[context.index];
|
||||
if (token.type === 'inline-tag') {
|
||||
return parseJSDocTag(context, true);
|
||||
}
|
||||
else {
|
||||
return parseJSDocLine(context);
|
||||
}
|
||||
}
|
||||
function parseJSDocTag(context, inline) {
|
||||
const tagToken = context.tokens[context.index++];
|
||||
const name = tagToken.content.substring(1);
|
||||
const nextToken = context.tokens[context.index];
|
||||
if (nextToken?.type === 'text') {
|
||||
if (inline) {
|
||||
const docLine = parseJSDocLine(context);
|
||||
return new JSDocTagImpl(name, new JSDocTextImpl([docLine], docLine.range), inline, Range.create(tagToken.range.start, docLine.range.end));
|
||||
}
|
||||
else {
|
||||
const textDoc = parseJSDocText(context);
|
||||
return new JSDocTagImpl(name, textDoc, inline, Range.create(tagToken.range.start, textDoc.range.end));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const range = tagToken.range;
|
||||
return new JSDocTagImpl(name, new JSDocTextImpl([], range), inline, range);
|
||||
}
|
||||
}
|
||||
function parseJSDocLine(context) {
|
||||
const token = context.tokens[context.index++];
|
||||
return new JSDocLineImpl(token.content, token.range);
|
||||
}
|
||||
function normalizeOptions(options) {
|
||||
if (!options) {
|
||||
return normalizeOptions({
|
||||
start: '/**',
|
||||
end: '*/',
|
||||
line: '*'
|
||||
});
|
||||
}
|
||||
const { start, end, line } = options;
|
||||
return {
|
||||
start: normalizeOption(start, true),
|
||||
end: normalizeOption(end, false),
|
||||
line: normalizeOption(line, true)
|
||||
};
|
||||
}
|
||||
function normalizeOption(option, start) {
|
||||
if (typeof option === 'string' || typeof option === 'object') {
|
||||
const escaped = typeof option === 'string' ? escapeRegExp(option) : option.source;
|
||||
if (start) {
|
||||
return new RegExp(`^\\s*${escaped}`);
|
||||
}
|
||||
else {
|
||||
return new RegExp(`\\s*${escaped}\\s*$`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
class JSDocCommentImpl {
|
||||
constructor(elements, range) {
|
||||
this.elements = elements;
|
||||
this.range = range;
|
||||
}
|
||||
getTag(name) {
|
||||
return this.getAllTags().find(e => e.name === name);
|
||||
}
|
||||
getTags(name) {
|
||||
return this.getAllTags().filter(e => e.name === name);
|
||||
}
|
||||
getAllTags() {
|
||||
return this.elements.filter(e => 'name' in e);
|
||||
}
|
||||
toString() {
|
||||
let value = '';
|
||||
for (const element of this.elements) {
|
||||
if (value.length === 0) {
|
||||
value = element.toString();
|
||||
}
|
||||
else {
|
||||
const text = element.toString();
|
||||
value += fillNewlines(value) + text;
|
||||
}
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
toMarkdown(options) {
|
||||
let value = '';
|
||||
for (const element of this.elements) {
|
||||
if (value.length === 0) {
|
||||
value = element.toMarkdown(options);
|
||||
}
|
||||
else {
|
||||
const text = element.toMarkdown(options);
|
||||
value += fillNewlines(value) + text;
|
||||
}
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
class JSDocTagImpl {
|
||||
constructor(name, content, inline, range) {
|
||||
this.name = name;
|
||||
this.content = content;
|
||||
this.inline = inline;
|
||||
this.range = range;
|
||||
}
|
||||
toString() {
|
||||
let text = `@${this.name}`;
|
||||
const content = this.content.toString();
|
||||
if (this.content.inlines.length === 1) {
|
||||
text = `${text} ${content}`;
|
||||
}
|
||||
else if (this.content.inlines.length > 1) {
|
||||
text = `${text}\n${content}`;
|
||||
}
|
||||
if (this.inline) {
|
||||
// Inline tags are surrounded by curly braces
|
||||
return `{${text}}`;
|
||||
}
|
||||
else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
toMarkdown(options) {
|
||||
return options?.renderTag?.(this) ?? this.toMarkdownDefault(options);
|
||||
}
|
||||
toMarkdownDefault(options) {
|
||||
const content = this.content.toMarkdown(options);
|
||||
if (this.inline) {
|
||||
const rendered = renderInlineTag(this.name, content, options ?? {});
|
||||
if (typeof rendered === 'string') {
|
||||
return rendered;
|
||||
}
|
||||
}
|
||||
let marker = '';
|
||||
if (options?.tag === 'italic' || options?.tag === undefined) {
|
||||
marker = '*';
|
||||
}
|
||||
else if (options?.tag === 'bold') {
|
||||
marker = '**';
|
||||
}
|
||||
else if (options?.tag === 'bold-italic') {
|
||||
marker = '***';
|
||||
}
|
||||
let text = `${marker}@${this.name}${marker}`;
|
||||
if (this.content.inlines.length === 1) {
|
||||
text = `${text} — ${content}`;
|
||||
}
|
||||
else if (this.content.inlines.length > 1) {
|
||||
text = `${text}\n${content}`;
|
||||
}
|
||||
if (this.inline) {
|
||||
// Inline tags are surrounded by curly braces
|
||||
return `{${text}}`;
|
||||
}
|
||||
else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
function renderInlineTag(tag, content, options) {
|
||||
if (tag === 'linkplain' || tag === 'linkcode' || tag === 'link') {
|
||||
const index = content.indexOf(' ');
|
||||
let display = content;
|
||||
if (index > 0) {
|
||||
const displayStart = skipWhitespace(content, index);
|
||||
display = content.substring(displayStart);
|
||||
content = content.substring(0, index);
|
||||
}
|
||||
if (tag === 'linkcode' || (tag === 'link' && options.link === 'code')) {
|
||||
// Surround the display value in a markdown inline code block
|
||||
display = `\`${display}\``;
|
||||
}
|
||||
const renderedLink = options.renderLink?.(content, display) ?? renderLinkDefault(content, display);
|
||||
return renderedLink;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function renderLinkDefault(content, display) {
|
||||
try {
|
||||
URI.parse(content, true);
|
||||
return `[${display}](${content})`;
|
||||
}
|
||||
catch {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
class JSDocTextImpl {
|
||||
constructor(lines, range) {
|
||||
this.inlines = lines;
|
||||
this.range = range;
|
||||
}
|
||||
toString() {
|
||||
let text = '';
|
||||
for (let i = 0; i < this.inlines.length; i++) {
|
||||
const inline = this.inlines[i];
|
||||
const next = this.inlines[i + 1];
|
||||
text += inline.toString();
|
||||
if (next && next.range.start.line > inline.range.start.line) {
|
||||
text += '\n';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
toMarkdown(options) {
|
||||
let text = '';
|
||||
for (let i = 0; i < this.inlines.length; i++) {
|
||||
const inline = this.inlines[i];
|
||||
const next = this.inlines[i + 1];
|
||||
text += inline.toMarkdown(options);
|
||||
if (next && next.range.start.line > inline.range.start.line) {
|
||||
text += '\n';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
class JSDocLineImpl {
|
||||
constructor(text, range) {
|
||||
this.text = text;
|
||||
this.range = range;
|
||||
}
|
||||
toString() {
|
||||
return this.text;
|
||||
}
|
||||
toMarkdown() {
|
||||
return this.text;
|
||||
}
|
||||
}
|
||||
function fillNewlines(text) {
|
||||
if (text.endsWith('\n')) {
|
||||
return '\n';
|
||||
}
|
||||
else {
|
||||
return '\n\n';
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=jsdoc.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user