UNPKG

@ts-ast-parser/core

Version:

Reflects a simplified version of the TypeScript AST for generating documentation

241 lines 8.27 kB
import { importFactory, declarationFactories, exportFactories } from '../factories/index.js'; import { tryAddProperty } from '../utils/try-add-property.js'; import { CommentNode } from './comment-node.js'; import { is } from '../utils/is.js'; import ts from 'typescript'; /** * Represents a reflected module as a collection of imports, exports and * class/interface/function/type-alias/enum declarations */ export class ModuleNode { constructor(node, context) { Object.defineProperty(this, "_declarations", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "_exports", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "_imports", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "_node", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_context", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_jsDoc", { enumerable: true, configurable: true, writable: true, value: void 0 }); this._node = node; this._context = context; this._jsDoc = new CommentNode(this._node); this._visitNode(node); this._removeNonPublicDeclarations(); } /** * The original TypeScript node * * @returns The TypeScript AST node associated with this module */ getTSNode() { return this._node; } /** * The path to the source file for this module relative to the current * working directory * * @returns The relative path where the module is located */ getSourcePath() { return this._context.getSystem().normalizePath(this._node.fileName); } /** * The path where the JS file will be output by the TypeScript Compiler * * @returns The relative path where the TypeScript compiler would emit the JS module. If * the source file is a JS file, it returns the source path. */ getOutputPath() { const sourcePath = this._node.fileName; const system = this._context.getSystem(); // If the source file was already JS, just return that if (sourcePath.endsWith('js')) { return system.normalizePath(sourcePath); } // Use the TS API to determine where the associated JS will be output based // on tsconfig settings. const absolutePath = system.getAbsolutePath(sourcePath); const outputPath = ts.getOutputFileNames(this._context.getCommandLine(), absolutePath, false)[0]; return system.normalizePath(outputPath ?? ''); } /** * The context includes useful APIs that are shared across * all the reflected symbols. * * Some APIs include the parsed configuration options, the * system interface, the type checker * * @returns The analyser context */ getContext() { return this._context; } /** * All the import declarations found in the source file * * @returns The reflected import declarations */ getImports() { return this._imports; } /** * All the export declarations found in the source file * * @returns The reflected export declarations */ getExports() { return this._exports; } /** * All the class/interface/function/type-alias/enum declarations found * in the source file * * @returns The reflected declaration nodes */ getDeclarations() { return this._declarations; } /** * Reflects the module-level JSDoc comment * * @returns The module-level JSDoc comment blocks for a given source file. */ getJSDoc() { return this._jsDoc; } /** * Finds a declaration based on it's kind * * @param kind - The declaration kind * @returns All declaration nodes found */ getDeclarationByKind(kind) { return this.getDeclarations().filter(decl => decl.getKind() === kind); } /** * Finds a declaration based on it's name * * @param name - The declaration name * @returns The matched declaration found if any */ getDeclarationByName(name) { return this.getDeclarations().find(decl => decl.getName() === name) ?? null; } /** * Returns all the declarations that have the tag `@category` * with the specified name * * @param category - The category name * @returns All declaration nodes found */ getDeclarationsByCategory(category) { return this.getDeclarations().filter(decl => { return decl.getJSDoc()?.getTag('category')?.text === category; }); } /** * Serializes the reflected node * * @returns The reflected node as a serializable object */ serialize() { const result = { sourcePath: this.getSourcePath(), outputPath: this.getOutputPath(), imports: this.getImports().map(imp => imp.serialize()), declarations: this.getDeclarations().map(dec => dec.serialize()), exports: this.getExports().map(exp => exp.serialize()), }; tryAddProperty(result, 'jsDoc', this.getJSDoc().serialize()); return result; } _visitNode(rootNode) { let declarationFound = false; if (importFactory.isNode(rootNode)) { this._add(importFactory.create(rootNode, this._context)); } for (const factory of declarationFactories) { if (factory.isNode(rootNode)) { this._add(factory.create(rootNode, this._context)); declarationFound = true; } } for (const factory of exportFactories) { if (factory.isNode(rootNode)) { this._add(factory.create(rootNode, this._context)); } } if (!declarationFound) { ts.forEachChild(rootNode, node => this._visitNode(node)); } } _add(reflectedNodes) { for (const reflectedNode of reflectedNodes) { if (is.ImportNode(reflectedNode)) { this._imports.push(reflectedNode); } // We don't want to add duplicate declarations when there are functions with overloads if (is.DeclarationNode(reflectedNode) && !this._hasDeclaration(reflectedNode)) { this._declarations.push(reflectedNode); } // We don't want to add duplicate exports when there are functions with overloads if (is.ExportNode(reflectedNode) && !this._hasExport(reflectedNode)) { this._exports.push(reflectedNode); } } } _hasDeclaration(declaration) { return this._declarations.some(decl => decl.getName() === declaration.getName()); } _hasExport(exp) { if (is.ReExportNode(exp)) { return this._exports.some(e => is.ReExportNode(e) && e.getModule() === exp.getModule()); } return this._exports.some(e => e.getName() === exp.getName() && e.getKind() === exp.getKind()); } _removeNonPublicDeclarations() { this._declarations = this._declarations.filter(decl => { // If the export has an "AS" keyword, we need to use the "originalName" const index = this._exports.findIndex(exp => exp.getOriginalName() === decl.getName()); const isIgnored = !!decl.getJSDoc()?.isIgnored(); if (index === -1) { return false; } // Remove also the declaration from the exports array if (isIgnored) { this._exports.splice(index, 1); } return !isIgnored; }); } } //# sourceMappingURL=module-node.js.map