UNPKG

@compodoc/compodoc

Version:

The missing documentation tool for your Angular application

277 lines (250 loc) 9.84 kB
import * as _ from 'lodash'; import { ts, SyntaxKind } from 'ts-morph'; import { TsPrinterUtil } from '../../../../../utils/ts-printer.util'; import ImportsUtil from '../../../../../utils/imports.util'; 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'; } 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) { // Search for provide: HTTP_INTERCEPTORS // and if true, return type: 'interceptor' + name let interceptorName, hasInterceptor; if (node.properties) { if (node.properties.length > 0) { _.forEach(node.properties, property => { if (property.kind && property.kind === SyntaxKind.PropertyAssignment) { if (property.name.text === 'provide') { if (property.initializer.text === 'HTTP_INTERCEPTORS') { hasInterceptor = true; } } if ( property.name.text === 'useClass' || property.name.text === 'useExisting' ) { interceptorName = property.initializer.text; } } }); } } if (hasInterceptor) { return interceptorName; } else { return new TsPrinterUtil().print(node); } } else { 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; } } 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]; } 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) ) { 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); 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.text === type); } } export interface IParseDeepIdentifierResult { ns?: any; name: string; file?: string; type: string | undefined; }