src/directives.ts (79 lines of code) (raw):
import * as ts from 'typescript';
import { JsiiDiagnostic } from './jsii-diagnostic';
/**
* TSDoc-style directives that can be attached to a symbol.
*/
export class Directives {
/**
* Obtains the `Directives` for a given TypeScript AST node.
*
* @param node the node for which directives are requested.
* @param onDiagnostic a callback invoked whenever a diagnostic message is
* emitted when parsing directives.
*/
public static of(node: ts.Node, onDiagnostic: (diag: JsiiDiagnostic) => void): Directives {
const found = Directives.#CACHE.get(node);
if (found != null) {
return found;
}
const directives = new Directives(node, onDiagnostic);
Directives.#CACHE.set(node, directives);
return directives;
}
static readonly #CACHE = new WeakMap<ts.Node, Directives>();
/** Whether the node has the `@internal` JSDoc tag. */
public readonly tsInternal?: ts.JSDocTag;
/** Whether the node has the `@jsii ignore` directive set. */
public readonly ignore?: ts.JSDocComment | ts.JSDocTag;
/** Whether the node has the `@jsii struct` directive set. */
public readonly struct?: ts.JSDocComment | ts.JSDocTag;
private constructor(node: ts.Node, onDiagnostic: (diag: JsiiDiagnostic) => void) {
for (const tag of ts.getJSDocTags(node)) {
switch (tag.tagName.text) {
case 'internal':
this.tsInternal ??= tag;
break;
case 'jsii':
const comments = getComments(tag);
if (comments.length === 0) {
onDiagnostic(JsiiDiagnostic.JSII_2000_MISSING_DIRECTIVE_ARGUMENT.create(tag));
continue;
}
for (const { text, jsdocNode } of comments) {
switch (text) {
case 'ignore':
this.ignore ??= jsdocNode;
break;
default:
onDiagnostic(JsiiDiagnostic.JSII_2999_UNKNOWN_DIRECTIVE.create(jsdocNode, text));
break;
}
}
break;
default: // Ignore
}
}
}
}
function getComments(tag: ts.JSDocTag): Comment[] {
if (tag.comment == null) {
return [];
}
if (typeof tag.comment === 'string') {
const text = tag.comment.trim();
return text
? text.split(/[\n,]/).flatMap((line) => {
line = line.trim();
return line ? [{ text: line, jsdocNode: tag }] : [];
})
: [];
}
// Possible per the type signature in the compiler, however not sure in which conditions.
return tag.comment.flatMap((jsdocNode): Comment[] => {
let text: string;
switch (jsdocNode.kind) {
case ts.SyntaxKind.JSDocText:
text = jsdocNode.text;
break;
case ts.SyntaxKind.JSDocLink:
case ts.SyntaxKind.JSDocLinkCode:
case ts.SyntaxKind.JSDocLinkPlain:
text = jsdocNode.name
? `${jsdocNode.name.getText(jsdocNode.name.getSourceFile())}: ${jsdocNode.text}`
: jsdocNode.text;
break;
}
text = text.trim();
return text ? [{ text, jsdocNode }] : [];
});
}
interface Comment {
readonly text: string;
readonly jsdocNode: ts.JSDocComment | ts.JSDocTag;
}