UNPKG

@compodoc/compodoc

Version:

The missing documentation tool for your Angular application

356 lines (323 loc) 11.6 kB
// @ts-nocheck import * as _ from '../../../../../utils/collection.util'; import { ts, SyntaxKind } from "ts-morph"; import { TsPrinterUtil } from "../../../../../utils/ts-printer.util"; import ImportsUtil from "../../../../../utils/imports.util"; enum AngularProviderConfigProperties { Useclass = "useClass", UseValue = "useValue", UseFactory = "useFactory", UseExisting = "useExisting", } export class SymbolHelper { private readonly unknown = "???"; public parseDeepIndentifier( name: string, srcFile?: ts.SourceFile, ): IParseDeepIdentifierResult { let result = { name: "", type: "", }; if (typeof name === "undefined") { return result; } let nsModule = name.split("."); let type = this.getType(name); if (nsModule.length > 1) { result.ns = nsModule[0]; result.name = name; result.type = type; return result; } if (typeof srcFile !== "undefined") { result.file = ImportsUtil.getFileNameOfImport(name, srcFile); } result.name = name; result.type = type; return result; } public getType(name: string): string { let type; if (name.toLowerCase().indexOf("component") !== -1) { type = "component"; } else if (name.toLowerCase().indexOf("pipe") !== -1) { type = "pipe"; } else if (name.toLowerCase().indexOf("controller") !== -1) { type = "controller"; } else if (name.toLowerCase().indexOf("module") !== -1) { type = "module"; } else if (name.toLowerCase().indexOf("directive") !== -1) { type = "directive"; } else if ( name.toLowerCase().indexOf("injectable") !== -1 || name.toLowerCase().indexOf("service") !== -1 ) { type = "injectable"; } return type; } /** * Output * RouterModule.forRoot 179 */ public buildIdentifierName( node: ts.Identifier | ts.PropertyAccessExpression | ts.SpreadElement, name, ) { if (ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) { return `${node.text}.${name}`; } name = name ? `.${name}` : ""; let nodeName = this.unknown; if (node.name) { nodeName = node.name.text; } else if (node.text) { nodeName = node.text; } else if (node.expression) { if (node.expression.text) { nodeName = node.expression.text; } else if (node.expression.elements) { if (ts.isArrayLiteralExpression(node.expression)) { nodeName = node.expression.elements .map((el) => el.text) .join(", "); nodeName = `[${nodeName}]`; } } } if (ts.isSpreadElement(node)) { return `...${nodeName}`; } return `${this.buildIdentifierName(node.expression, nodeName)}${name}`; } /** * parse expressions such as: * { provide: APP_BASE_HREF, useValue: '/' } * { provide: 'Date', useFactory: (d1, d2) => new Date(), deps: ['d1', 'd2'] } */ public parseProviderConfiguration( node: ts.ObjectLiteralExpression, ): string { if (node.kind && node.kind === SyntaxKind.ObjectLiteralExpression) { const provideProperty = node.properties.find( (props) => props.name.getText() === "provide", ); if (!provideProperty) { throw new Error( "provide property not found in provider object config", ); } const providerObjectProps = Object.values( AngularProviderConfigProperties, ); for (let i = 0; i < providerObjectProps.length; i++) { const providerProp = providerObjectProps[i]; const prop = node.properties.find( (props) => props.name.getText() === providerProp, ); if (prop) { return prop.getLastToken().getText(); } } } return new TsPrinterUtil().print(node); } /** * Kind * 181 CallExpression => "RouterModule.forRoot(args)" * 71 Identifier => "RouterModule" "TodoStore" * 9 StringLiteral => "./app.component.css" "./tab.scss" */ public parseSymbolElements( node: | ts.CallExpression | ts.Identifier | ts.StringLiteral | ts.PropertyAccessExpression | ts.SpreadElement, ): string { // parse expressions such as: AngularFireModule.initializeApp(firebaseConfig) // if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { if ( (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) || (ts.isNewExpression(node) && ts.isElementAccessExpression(node.expression)) ) { let className = this.buildIdentifierName(node.expression); // function arguments could be really complex. There are so // many use cases that we can't handle. Just print "args" to indicate // that we have arguments. let functionArgs = node.arguments.length > 0 ? "args" : ""; let text = `${className}(${functionArgs})`; return text; } else if (ts.isPropertyAccessExpression(node)) { // parse expressions such as: Shared.Module return this.buildIdentifierName(node); } else if (ts.isIdentifier(node)) { // parse expressions such as: MyComponent if (node.text) { return node.text; } if (node.escapedText) { return node.escapedText; } } else if (ts.isSpreadElement(node)) { // parse expressions such as: ...MYARRAY // Resolve MYARRAY in imports or local file variables after full scan, just return the name of the variable if (node.expression && node.expression.text) { return node.expression.text; } // parse expressions such as: ...fromComponents.components if ( node.expression && ts.isPropertyAccessExpression(node.expression) ) { return node.expression.name.text; } } return node.text ? node.text : this.parseProviderConfiguration(node); } /** * Kind * 177 ArrayLiteralExpression * 122 BooleanKeyword * 9 StringLiteral */ private parseSymbols( node: ts.ObjectLiteralElement, srcFile: ts.SourceFile, decoratorType: string, ): Array<string | boolean> { let localNode = node; if ( ts.isShorthandPropertyAssignment(localNode) && decoratorType !== "template" ) { localNode = ImportsUtil.findValueInImportOrLocalVariables( node.name.text, srcFile, decoratorType, ); } if ( ts.isShorthandPropertyAssignment(localNode) && decoratorType === "template" ) { const data = ImportsUtil.findValueInImportOrLocalVariables( node.name.text, srcFile, decoratorType, ); return [data]; } // Handle: @NgModule({ providers: PROVIDERS }) where PROVIDERS is an identifier // referencing a local const or imported variable if (localNode.initializer && ts.isIdentifier(localNode.initializer)) { const resolved = ImportsUtil.findValueInImportOrLocalVariables( localNode.initializer.text, srcFile, decoratorType, ); if ( resolved && !Array.isArray(resolved) && (resolved as any).initializer ) { localNode = resolved as any; } } if ( localNode.initializer && ts.isArrayLiteralExpression(localNode.initializer) ) { return localNode.initializer.elements.map((x) => this.parseSymbolElements(x), ); } else if ( (localNode.initializer && ts.isStringLiteral(localNode.initializer)) || (localNode.initializer && ts.isTemplateLiteral(localNode.initializer)) || (localNode.initializer && ts.isPropertyAssignment(localNode) && localNode.initializer.text && !ts.isIdentifier(localNode.initializer)) ) { return [localNode.initializer.text]; } else if ( localNode.initializer && localNode.initializer.kind && (localNode.initializer.kind === SyntaxKind.TrueKeyword || localNode.initializer.kind === SyntaxKind.FalseKeyword) ) { return [ localNode.initializer.kind === SyntaxKind.TrueKeyword ? true : false, ]; } else if ( localNode.initializer && ts.isPropertyAccessExpression(localNode.initializer) ) { let identifier = this.parseSymbolElements(localNode.initializer); if (decoratorType === "templateUrl") { const resolvedTemplateUrl = ImportsUtil.findPropertyValueInImportOrLocalVariables( identifier, srcFile, ); if (resolvedTemplateUrl) { return [resolvedTemplateUrl]; } } return [identifier]; } else if ( localNode.initializer && localNode.initializer.elements && localNode.initializer.elements.length > 0 ) { // Node replaced by ts-simple-ast & kind = 265 return localNode.initializer.elements.map((x) => this.parseSymbolElements(x), ); } } public getSymbolDeps( props: ReadonlyArray<ts.ObjectLiteralElementLike>, decoratorType: string, srcFile: ts.SourceFile, multiLine?: boolean, ): Array<string> { if (props.length === 0) { return []; } let i = 0, len = props.length, filteredProps = []; for (i; i < len; i++) { if (props[i].name && props[i].name.text === decoratorType) { filteredProps.push(props[i]); } } return ( filteredProps .map((x) => this.parseSymbols(x, srcFile, decoratorType)) .pop() || [] ); } public getSymbolDepsRaw( props: ReadonlyArray<ts.ObjectLiteralElementLike>, type: string, multiLine?: boolean, ): Array<ts.ObjectLiteralElementLike> { return props.filter((node) => node.name.getText() === type); } } export interface IParseDeepIdentifierResult { ns?: any; name: string; file?: string; type: string | undefined; }