@ts-ast-parser/core
Version:
Reflects a simplified version of the TypeScript AST for generating documentation
241 lines • 8.27 kB
JavaScript
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