import * as _ from 'lodash';
import * as util from 'util';
import * as path from 'path';
import { ts, SyntaxKind } from 'ts-simple-ast';
import { getNamesCompareFn, mergeTagsAndArgs, markedtags } from '../../../../../utils/utils';
import { kindToType } from '../../../../../utils/kind-to-type';
import { JsdocParserUtil } from '../../../../../utils/jsdoc-parser.util';
import { isIgnore } from '../../../../../utils';
import AngularVersionUtil from '../../../../..//utils/angular-version.util';
import BasicTypeUtil from '../../../../../utils/basic-type.util';
import { StringifyObjectLiteralExpression } from '../../../../../utils/object-literal-expression.util';
import DependenciesEngine from '../../../../engines/dependencies.engine';
import Configuration from '../../../../configuration';
const crypto = require('crypto');
const marked = require('marked');
export class ClassHelper {
private jsdocParserUtil = new JsdocParserUtil();
constructor(private typeChecker: ts.TypeChecker) {}
/**
* HELPERS
*/
public stringifyDefaultValue(node: ts.Node): string {
/**
* Copyright https://github.com/ng-bootstrap/ng-bootstrap
*/
if (node.getText()) {
return node.getText();
} else if (node.kind === SyntaxKind.FalseKeyword) {
return 'false';
} else if (node.kind === SyntaxKind.TrueKeyword) {
return 'true';
}
}
private getDecoratorOfType(node, decoratorType) {
let decorators = node.decorators || [];
for (let i = 0; i < decorators.length; i++) {
if (decorators[i].expression.expression) {
if (decorators[i].expression.expression.text === decoratorType) {
return decorators[i];
}
}
}
return undefined;
}
private formatDecorators(decorators) {
let _decorators = [];
_.forEach(decorators, (decorator: any) => {
if (decorator.expression) {
if (decorator.expression.text) {
_decorators.push({ name: decorator.expression.text });
}
if (decorator.expression.expression) {
let info: any = { name: decorator.expression.expression.text };
if (decorator.expression.arguments) {
info.stringifiedArguments = this.stringifyArguments(
decorator.expression.arguments
);
}
_decorators.push(info);
}
}
});
return _decorators;
}
private handleFunction(arg): string {
if (arg.function.length === 0) {
return `${arg.name}${this.getOptionalString(arg)}: () => void`;
}
let argums = arg.function.map(argu => {
let _result = DependenciesEngine.find(argu.type);
if (_result) {
if (_result.source === 'internal') {
let path = _result.data.type;
if (_result.data.type === 'class') {
path = 'classe';
}
return `${argu.name}${this.getOptionalString(arg)}: ${argu.type}`;
} else {
let path = AngularVersionUtil.getApiLink(
_result.data,
Configuration.mainData.angularVersion
);
return `${argu.name}${this.getOptionalString(
arg
)}: ${argu.type}`;
}
} else if (BasicTypeUtil.isKnownType(argu.type)) {
let path = BasicTypeUtil.getTypeUrl(argu.type);
return `${argu.name}${this.getOptionalString(
arg
)}: ${argu.type}`;
} else {
if (argu.name && argu.type) {
return `${argu.name}${this.getOptionalString(arg)}: ${argu.type}`;
} else {
if (argu.name) {
return `${argu.name.text}`;
} else {
return '';
}
}
}
});
return `${arg.name}${this.getOptionalString(arg)}: (${argums}) => void`;
}
private getOptionalString(arg): string {
return arg.optional ? '?' : '';
}
private stringifyArguments(args) {
let stringifyArgs = [];
stringifyArgs = args
.map(arg => {
let _result = DependenciesEngine.find(arg.type);
if (_result) {
if (_result.source === 'internal') {
let path = _result.data.type;
if (_result.data.type === 'class') {
path = 'classe';
}
return `${arg.name}${this.getOptionalString(arg)}: ${arg.type}`;
} else {
let path = AngularVersionUtil.getApiLink(
_result.data,
Configuration.mainData.angularVersion
);
return `${arg.name}${this.getOptionalString(
arg
)}: ${arg.type}`;
}
} else if (arg.dotDotDotToken) {
return `...${arg.name}: ${arg.type}`;
} else if (arg.function) {
return this.handleFunction(arg);
} else if (arg.expression && arg.name) {
return arg.expression.text + '.' + arg.name.text;
} else if (arg.expression && arg.kind === SyntaxKind.NewExpression) {
return 'new ' + arg.expression.text + '()';
} else if (arg.kind && arg.kind === SyntaxKind.StringLiteral) {
return `'` + arg.text + `'`;
} else if (arg.kind && arg.kind === SyntaxKind.ObjectLiteralExpression) {
return StringifyObjectLiteralExpression(arg);
} else if (BasicTypeUtil.isKnownType(arg.type)) {
let path = BasicTypeUtil.getTypeUrl(arg.type);
return `${arg.name}${this.getOptionalString(
arg
)}: ${arg.type}`;
} else {
if (arg.type) {
let finalStringifiedArgument = '';
let separator = ':';
if (arg.name) {
finalStringifiedArgument += arg.name;
}
if (
arg.kind === SyntaxKind.AsExpression &&
arg.expression &&
arg.expression.text
) {
finalStringifiedArgument += arg.expression.text;
separator = ' as';
}
if (arg.optional) {
finalStringifiedArgument += this.getOptionalString(arg);
}
if (arg.type) {
finalStringifiedArgument += separator + ' ' + this.visitType(arg.type);
}
return finalStringifiedArgument;
} else if (arg.text) {
return `${arg.text}`;
} else {
return `${arg.name}${this.getOptionalString(arg)}`;
}
}
})
.join(', ');
return stringifyArgs;
}
private getPosition(node: ts.Node, sourceFile: ts.SourceFile): ts.LineAndCharacter {
let position: ts.LineAndCharacter;
if (node.name && node.name.end) {
position = ts.getLineAndCharacterOfPosition(sourceFile, node.name.end);
} else {
position = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
}
return position;
}
private addAccessor(accessors, nodeAccessor, sourceFile) {
let nodeName = '';
if (nodeAccessor.name) {
nodeName = nodeAccessor.name.text;
let jsdoctags = this.jsdocParserUtil.getJSDocs(nodeAccessor);
if (!accessors[nodeName]) {
accessors[nodeName] = {
name: nodeName,
setSignature: undefined,
getSignature: undefined
};
}
if (nodeAccessor.kind === SyntaxKind.SetAccessor) {
let setSignature = {
name: nodeName,
type: 'void',
args: nodeAccessor.parameters.map(param => {
return {
name: param.name.text,
type: param.type ? kindToType(param.type.kind) : ''
};
}),
returnType: nodeAccessor.type ? this.visitType(nodeAccessor.type) : 'void',
line: this.getPosition(nodeAccessor, sourceFile).line + 1
};
if (nodeAccessor.jsDoc && nodeAccessor.jsDoc.length >= 1) {
let comment = nodeAccessor.jsDoc[0].comment;
if (typeof comment !== 'undefined') {
setSignature.description = marked(comment);
}
}
if (jsdoctags && jsdoctags.length >= 1) {
if (jsdoctags[0].tags) {
setSignature.jsdoctags = markedtags(jsdoctags[0].tags);
}
}
if (setSignature.jsdoctags && setSignature.jsdoctags.length > 0) {
setSignature.jsdoctags = mergeTagsAndArgs(
setSignature.args,
setSignature.jsdoctags
);
} else if (setSignature.args && setSignature.args.length > 0) {
setSignature.jsdoctags = mergeTagsAndArgs(setSignature.args);
}
accessors[nodeName].setSignature = setSignature;
}
if (nodeAccessor.kind === SyntaxKind.GetAccessor) {
let getSignature = {
name: nodeName,
type: nodeAccessor.type ? kindToType(nodeAccessor.type.kind) : '',
returnType: nodeAccessor.type ? this.visitType(nodeAccessor.type) : '',
line: this.getPosition(nodeAccessor, sourceFile).line + 1
};
if (nodeAccessor.jsDoc && nodeAccessor.jsDoc.length >= 1) {
let comment = nodeAccessor.jsDoc[0].comment;
if (typeof comment !== 'undefined') {
getSignature.description = marked(comment);
}
}
if (jsdoctags && jsdoctags.length >= 1) {
if (jsdoctags[0].tags) {
getSignature.jsdoctags = markedtags(jsdoctags[0].tags);
}
}
accessors[nodeName].getSignature = getSignature;
}
}
}
private isDirectiveDecorator(decorator: ts.Decorator): boolean {
if (decorator.expression.expression) {
let decoratorIdentifierText = decorator.expression.expression.text;
return (
decoratorIdentifierText === 'Directive' || decoratorIdentifierText === 'Component'
);
} else {
return false;
}
}
private isServiceDecorator(decorator) {
return decorator.expression.expression
? decorator.expression.expression.text === 'Injectable'
: false;
}
private isPrivate(member): boolean {
/**
* Copyright https://github.com/ng-bootstrap/ng-bootstrap
*/
if (member.modifiers) {
const isPrivate: boolean = member.modifiers.some(
modifier => modifier.kind === SyntaxKind.PrivateKeyword
);
if (isPrivate) {
return true;
}
}
return this.isHiddenMember(member);
}
private isProtected(member): boolean {
if (member.modifiers) {
const isProtected: boolean = member.modifiers.some(
modifier => modifier.kind === SyntaxKind.ProtectedKeyword
);
if (isProtected) {
return true;
}
}
return this.isHiddenMember(member);
}
private isInternal(member): boolean {
/**
* Copyright https://github.com/ng-bootstrap/ng-bootstrap
*/
const internalTags: string[] = ['internal'];
if (member.jsDoc) {
for (const doc of member.jsDoc) {
if (doc.tags) {
for (const tag of doc.tags) {
if (internalTags.indexOf(tag.tagName.text) > -1) {
return true;
}
}
}
}
}
return false;
}
private isPublic(member): boolean {
if (member.modifiers) {
const isPublic: boolean = member.modifiers.some(
modifier => modifier.kind === SyntaxKind.PublicKeyword
);
if (isPublic) {
return true;
}
}
return this.isHiddenMember(member);
}
private isHiddenMember(member): boolean {
/**
* Copyright https://github.com/ng-bootstrap/ng-bootstrap
*/
const internalTags: string[] = ['hidden'];
if (member.jsDoc) {
for (const doc of member.jsDoc) {
if (doc.tags) {
for (const tag of doc.tags) {
if (internalTags.indexOf(tag.tagName.text) > -1) {
return true;
}
}
}
}
}
return false;
}
private isPipeDecorator(decorator) {
return decorator.expression.expression
? decorator.expression.expression.text === 'Pipe'
: false;
}
private isModuleDecorator(decorator) {
return decorator.expression.expression
? decorator.expression.expression.text === 'NgModule'
: false;
}
/**
* VISITERS
*/
public visitClassDeclaration(
fileName: string,
classDeclaration: ts.ClassDeclaration | ts.InterfaceDeclaration,
sourceFile?: ts.SourceFile
): any {
let symbol = this.typeChecker.getSymbolAtLocation(classDeclaration.name);
let rawdescription = '';
let description = '';
if (symbol) {
rawdescription = this.jsdocParserUtil.getMainCommentOfNode(classDeclaration);
description = marked(this.jsdocParserUtil.getMainCommentOfNode(classDeclaration));
if (symbol.valueDeclaration && isIgnore(symbol.valueDeclaration)) {
return [{ ignore: true }];
}
if (symbol.declarations && symbol.declarations.length > 0) {
if (isIgnore(symbol.declarations[0])) {
return [{ ignore: true }];
}
}
}
let className = classDeclaration.name.text;
let members;
let implementsElements = [];
let extendsElement;
let jsdoctags = [];
if (typeof ts.getClassImplementsHeritageClauseElements !== 'undefined') {
let implementedTypes = ts.getClassImplementsHeritageClauseElements(classDeclaration);
if (implementedTypes) {
let i = 0;
let len = implementedTypes.length;
for (i; i < len; i++) {
if (implementedTypes[i].expression) {
implementsElements.push(implementedTypes[i].expression.text);
}
}
}
}
if (typeof ts.getClassExtendsHeritageClauseElement !== 'undefined') {
let extendsTypes = ts.getClassExtendsHeritageClauseElement(classDeclaration);
if (extendsTypes) {
if (extendsTypes.expression) {
extendsElement = extendsTypes.expression.text;
}
}
}
if (symbol) {
if (symbol.valueDeclaration) {
jsdoctags = this.jsdocParserUtil.getJSDocs(symbol.valueDeclaration);
}
}
members = this.visitMembers(classDeclaration.members, sourceFile);
if (classDeclaration.decorators) {
for (let i = 0; i < classDeclaration.decorators.length; i++) {
if (this.isDirectiveDecorator(classDeclaration.decorators[i])) {
return {
description,
rawdescription: rawdescription,
inputs: members.inputs,
outputs: members.outputs,
hostBindings: members.hostBindings,
hostListeners: members.hostListeners,
properties: members.properties,
methods: members.methods,
indexSignatures: members.indexSignatures,
kind: members.kind,
constructor: members.constructor,
jsdoctags: jsdoctags,
extends: extendsElement,
implements: implementsElements,
accessors: members.accessors
};
} else if (this.isServiceDecorator(classDeclaration.decorators[i])) {
return [
{
fileName,
className,
description,
rawdescription: rawdescription,
methods: members.methods,
indexSignatures: members.indexSignatures,
properties: members.properties,
kind: members.kind,
constructor: members.constructor,
jsdoctags: jsdoctags,
extends: extendsElement,
implements: implementsElements,
accessors: members.accessors
}
];
} else if (this.isPipeDecorator(classDeclaration.decorators[i])) {
return [
{
fileName,
className,
description,
rawdescription: rawdescription,
jsdoctags: jsdoctags,
properties: members.properties,
methods: members.methods
}
];
} else if (this.isModuleDecorator(classDeclaration.decorators[i])) {
return [
{
fileName,
className,
description,
rawdescription: rawdescription,
jsdoctags: jsdoctags,
methods: members.methods
}
];
}
// Did not find an angular decorator in first iteration, try the next
}
return [
{
description,
rawdescription: rawdescription,
methods: members.methods,
indexSignatures: members.indexSignatures,
properties: members.properties,
kind: members.kind,
constructor: members.constructor,
jsdoctags: jsdoctags,
extends: extendsElement,
implements: implementsElements,
accessors: members.accessors
}
];
} else if (description) {
return [
{
description,
rawdescription: rawdescription,
inputs: members.inputs,
outputs: members.outputs,
hostBindings: members.hostBindings,
hostListeners: members.hostListeners,
methods: members.methods,
indexSignatures: members.indexSignatures,
properties: members.properties,
kind: members.kind,
constructor: members.constructor,
jsdoctags: jsdoctags,
extends: extendsElement,
implements: implementsElements,
accessors: members.accessors
}
];
} else {
return [
{
methods: members.methods,
inputs: members.inputs,
outputs: members.outputs,
hostBindings: members.hostBindings,
hostListeners: members.hostListeners,
indexSignatures: members.indexSignatures,
properties: members.properties,
kind: members.kind,
constructor: members.constructor,
jsdoctags: jsdoctags,
extends: extendsElement,
implements: implementsElements,
accessors: members.accessors
}
];
}
return [];
}
private visitMembers(members, sourceFile) {
/**
* Copyright https://github.com/ng-bootstrap/ng-bootstrap
*/
let inputs = [];
let outputs = [];
let hostBindings = [];
let hostListeners = [];
let methods = [];
let properties = [];
let indexSignatures = [];
let kind;
let inputDecorator;
let hostBinding;
let hostListener;
let constructor;
let outDecorator;
let accessors = {};
let result = {};
for (let i = 0; i < members.length; i++) {
// Allows typescript guess type when using ts.is*
let member = members[i];
inputDecorator = this.getDecoratorOfType(member, 'Input');
outDecorator = this.getDecoratorOfType(member, 'Output');
hostBinding = this.getDecoratorOfType(member, 'HostBinding');
hostListener = this.getDecoratorOfType(member, 'HostListener');
kind = member.kind;
if (isIgnore(member)) {
continue;
}
if (inputDecorator) {
inputs.push(this.visitInputAndHostBinding(member, inputDecorator, sourceFile));
if (ts.isSetAccessorDeclaration(member)) {
this.addAccessor(accessors, members[i], sourceFile);
}
} else if (outDecorator) {
outputs.push(this.visitOutput(member, outDecorator, sourceFile));
} else if (hostBinding) {
hostBindings.push(this.visitInputAndHostBinding(member, hostBinding, sourceFile));
} else if (hostListener) {
hostListeners.push(this.visitHostListener(member, hostListener, sourceFile));
} else if (!this.isHiddenMember(member)) {
if (!(this.isPrivate(member) && Configuration.mainData.disablePrivate)) {
if (!(this.isInternal(member) && Configuration.mainData.disableInternal)) {
if (
!(this.isProtected(member) && Configuration.mainData.disableProtected)
) {
if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
methods.push(this.visitMethodDeclaration(member, sourceFile));
} else if (
ts.isPropertyDeclaration(member) ||
ts.isPropertySignature(member)
) {
properties.push(this.visitProperty(member, sourceFile));
} else if (ts.isCallSignatureDeclaration(member)) {
properties.push(this.visitCallDeclaration(member, sourceFile));
} else if (
ts.isGetAccessorDeclaration(member) ||
ts.isSetAccessorDeclaration(member)
) {
this.addAccessor(accessors, members[i], sourceFile);
} else if (ts.isIndexSignatureDeclaration(member)) {
indexSignatures.push(
this.visitIndexDeclaration(member, sourceFile)
);
} else if (ts.isConstructorDeclaration(member)) {
let _constructorProperties = this.visitConstructorProperties(
member,
sourceFile
);
let j = 0;
let len = _constructorProperties.length;
for (j; j < len; j++) {
properties.push(_constructorProperties[j]);
}
constructor = this.visitConstructorDeclaration(member, sourceFile);
}
}
}
}
}
}
inputs.sort(getNamesCompareFn());
outputs.sort(getNamesCompareFn());
hostBindings.sort(getNamesCompareFn());
hostListeners.sort(getNamesCompareFn());
properties.sort(getNamesCompareFn());
methods.sort(getNamesCompareFn());
indexSignatures.sort(getNamesCompareFn());
result = {
inputs,
outputs,
hostBindings,
hostListeners,
methods,
properties,
indexSignatures,
kind,
constructor
};
if (Object.keys(accessors).length) {
result['accessors'] = accessors;
}
return result;
}
private visitTypeName(typeName: ts.Identifier) {
if (typeName.text) {
return typeName.text;
}
return `${this.visitTypeName(typeName.left)}.${this.visitTypeName(typeName.right)}`;
}
public visitType(node): string {
let _return = 'void';
if (!node) {
return _return;
}
if (node.typeName) {
_return = this.visitTypeName(node.typeName);
} else if (node.type) {
if (node.type.kind) {
_return = kindToType(node.type.kind);
}
if (node.type.typeName) {
_return = this.visitTypeName(node.type.typeName);
}
if (node.type.typeArguments) {
_return += '<';
const typeArguments = [];
for (const argument of node.type.typeArguments) {
typeArguments.push(this.visitType(argument));
}
_return += typeArguments.join(' | ');
_return += '>';
}
if (node.type.elementType) {
const _firstPart = this.visitType(node.type.elementType);
_return = _firstPart + kindToType(node.type.kind);
if (node.type.elementType.kind === SyntaxKind.ParenthesizedType) {
_return = '(' + _firstPart + ')' + kindToType(node.type.kind);
}
}
if (node.type.types && ts.isUnionTypeNode(node.type)) {
_return = '';
let i = 0;
let len = node.type.types.length;
for (i; i < len; i++) {
let type = node.type.types[i];
if (type.elementType) {
const _firstPart = this.visitType(type.elementType);
if (type.elementType.kind === SyntaxKind.ParenthesizedType) {
_return += '(' + _firstPart + ')' + kindToType(type.kind);
} else {
_return += _firstPart + kindToType(type.kind);
}
} else {
_return += kindToType(type.kind);
if (ts.isLiteralTypeNode(type) && type.literal) {
_return += '"' + type.literal.text + '"';
}
if (type.typeName) {
_return += this.visitTypeName(type.typeName);
}
if (type.typeArguments) {
_return += '<';
const typeArguments = [];
for (const argument of type.typeArguments) {
typeArguments.push(this.visitType(argument));
}
_return += typeArguments.join(' | ');
_return += '>';
}
}
if (i < len - 1) {
_return += ' | ';
}
}
}
if (node.type.elementTypes) {
let elementTypes = node.type.elementTypes;
let i = 0;
let len = elementTypes.length;
if (len > 0) {
_return = '[';
for (i; i < len; i++) {
let type = elementTypes[i];
_return += kindToType(type.kind);
if (ts.isLiteralTypeNode(type) && type.literal) {
_return += '"' + type.literal.text + '"';
}
if (type.typeName) {
_return += this.visitTypeName(type.typeName);
}
if (i < len - 1) {
_return += ', ';
}
}
_return += ']';
}
}
} else if (node.elementType) {
_return = kindToType(node.elementType.kind) + kindToType(node.kind);
if (node.elementType.typeName) {
_return = this.visitTypeName(node.elementType.typeName) + kindToType(node.kind);
}
} else if (node.types && ts.isUnionTypeNode(node)) {
_return = '';
let i = 0;
let len = node.types.length;
for (i; i < len; i++) {
let type = node.types[i];
_return += kindToType(type.kind);
if (ts.isLiteralTypeNode(type) && type.literal) {
_return += '"' + type.literal.text + '"';
}
if (type.typeName) {
_return += this.visitTypeName(type.typeName);
}
if (i < len - 1) {
_return += ' | ';
}
}
} else if (node.dotDotDotToken) {
_return = 'any[]';
} else {
_return = kindToType(node.kind);
if (
_return === '' &&
node.initializer &&
node.initializer.kind &&
(node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.Parameter)
) {
_return = kindToType(node.initializer.kind);
}
if (node.kind === SyntaxKind.TypeParameter) {
_return = node.name.text;
}
if (node.kind === SyntaxKind.LiteralType) {
_return = node.literal.text;
}
}
if (node.typeArguments && node.typeArguments.length > 0) {
_return += '<';
let i = 0,
len = node.typeArguments.length;
for (i; i < len; i++) {
let argument = node.typeArguments[i];
_return += this.visitType(argument);
if (i >= 0 && i < len - 1) {
_return += ', ';
}
}
_return += '>';
}
return _return;
}
private visitCallDeclaration(method: ts.CallSignatureDeclaration, sourceFile: ts.SourceFile) {
let sourceCode = sourceFile.getText();
let hash = crypto
.createHash('md5')
.update(sourceCode)
.digest('hex');
let result: any = {
id: 'call-declaration-' + hash,
args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [],
returnType: this.visitType(method.type),
line: this.getPosition(method, sourceFile).line + 1
};
if (method.jsDoc) {
result.description = marked(marked(this.jsdocParserUtil.getMainCommentOfNode(method)));
}
let jsdoctags = this.jsdocParserUtil.getJSDocs(method);
if (jsdoctags && jsdoctags.length >= 1) {
if (jsdoctags[0].tags) {
result.jsdoctags = markedtags(jsdoctags[0].tags);
}
}
return result;
}
private visitIndexDeclaration(
method: ts.IndexSignatureDeclaration,
sourceFile?: ts.SourceFile
) {
let sourceCode = sourceFile.getText();
let hash = crypto
.createHash('md5')
.update(sourceCode)
.digest('hex');
let result = {
id: 'index-declaration-' + hash,
args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [],
returnType: this.visitType(method.type),
line: this.getPosition(method, sourceFile).line + 1
};
if (method.jsDoc) {
result.description = marked(this.jsdocParserUtil.getMainCommentOfNode(method));
}
return result;
}
private visitConstructorDeclaration(
method: ts.ConstructorDeclaration,
sourceFile?: ts.SourceFile
) {
/**
* Copyright https://github.com/ng-bootstrap/ng-bootstrap
*/
let result: any = {
name: 'constructor',
description: '',
args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [],
line: this.getPosition(method, sourceFile).line + 1
};
let jsdoctags = this.jsdocParserUtil.getJSDocs(method);
if (method.jsDoc) {
result.description = marked(this.jsdocParserUtil.getMainCommentOfNode(method));
}
if (method.modifiers) {
if (method.modifiers.length > 0) {
let kinds = method.modifiers.map(modifier => {
return modifier.kind;
});
if (
_.indexOf(kinds, SyntaxKind.PublicKeyword) !== -1 &&
_.indexOf(kinds, SyntaxKind.StaticKeyword) !== -1
) {
kinds = kinds.filter(kind => kind !== SyntaxKind.PublicKeyword);
}
result.modifierKind = kinds;
}
}
if (jsdoctags && jsdoctags.length >= 1) {
if (jsdoctags[0].tags) {
result.jsdoctags = markedtags(jsdoctags[0].tags);
}
}
if (result.jsdoctags && result.jsdoctags.length > 0) {
result.jsdoctags = mergeTagsAndArgs(result.args, result.jsdoctags);
} else if (result.args.length > 0) {
result.jsdoctags = mergeTagsAndArgs(result.args);
}
return result;
}
private visitProperty(property: ts.PropertyDeclaration, sourceFile) {
let result: any = {
name: property.name.text,
defaultValue: property.initializer
? this.stringifyDefaultValue(property.initializer)
: undefined,
type: this.visitType(property),
optional: typeof property.questionToken !== 'undefined',
description: '',
line: this.getPosition(property, sourceFile).line + 1
};
let jsdoctags;
if (property.initializer && !result.type) {
result.type = this.visitInitializer(property.initializer);
}
if (property.initializer && property.initializer.kind === SyntaxKind.ArrowFunction) {
result.defaultValue = '() => {...}';
}
if (typeof result.name === 'undefined' && typeof property.name.expression !== 'undefined') {
result.name = property.name.expression.text;
}
if (property.jsDoc) {
jsdoctags = this.jsdocParserUtil.getJSDocs(property);
result.description = marked(this.jsdocParserUtil.getMainCommentOfNode(property));
}
if (property.decorators) {
result.decorators = this.formatDecorators(property.decorators);
}
if (property.modifiers) {
if (property.modifiers.length > 0) {
let kinds = property.modifiers.map(modifier => {
return modifier.kind;
});
if (
_.indexOf(kinds, SyntaxKind.PublicKeyword) !== -1 &&
_.indexOf(kinds, SyntaxKind.StaticKeyword) !== -1
) {
kinds = kinds.filter(kind => kind !== SyntaxKind.PublicKeyword);
}
result.modifierKind = kinds;
}
}
if (jsdoctags && jsdoctags.length >= 1) {
if (jsdoctags[0].tags) {
result.jsdoctags = markedtags(jsdoctags[0].tags);
}
}
return result;
}
private visitInitializer(initializer): string {
const text: string = initializer.getText();
if (ts.isNewExpression(initializer)) {
return text.split('new ')[1].split('(')[0];
} else {
if (text.startsWith("'") || text.startsWith('"') || text.startsWith('`')) {
return 'string';
} else if (text.match(/^[0-9]*$/)) {
return 'number';
} else if (text === 'true' || text === 'false') {
return 'boolean';
} else {
return text;
}
}
}
private visitConstructorProperties(constr, sourceFile) {
if (constr.parameters) {
let _parameters = [];
let i = 0;
let len = constr.parameters.length;
for (i; i < len; i++) {
if (this.isPublic(constr.parameters[i])) {
_parameters.push(this.visitProperty(constr.parameters[i], sourceFile));
}
}
/**
* Merge JSDoc tags description from constructor with parameters
*/
if (constr.jsDoc) {
if (constr.jsDoc.length > 0) {
let constrTags = constr.jsDoc[0].tags;
if (constrTags && constrTags.length > 0) {
constrTags.forEach(tag => {
_parameters.forEach(param => {
if (
tag.tagName &&
tag.tagName.escapedText &&
tag.tagName.escapedText === 'param'
) {
if (
tag.name &&
tag.name.escapedText &&
tag.name.escapedText === param.name
) {
param.description = tag.comment;
}
}
});
});
}
}
}
return _parameters;
} else {
return [];
}
}
private visitInputAndHostBinding(property, inDecorator, sourceFile?) {
let inArgs = inDecorator.expression.arguments;
let _return: any = {};
_return.name = inArgs.length > 0 ? inArgs[0].text : property.name.text;
_return.defaultValue = property.initializer
? this.stringifyDefaultValue(property.initializer)
: undefined;
if (!_return.description) {
if (property.jsDoc) {
if (property.jsDoc.length > 0) {
if (typeof property.jsDoc[0].comment !== 'undefined') {
_return.description = marked(property.jsDoc[0].comment);
}
}
}
}
_return.line = this.getPosition(property, sourceFile).line + 1;
if (property.type) {
_return.type = this.visitType(property);
} else {
// handle NewExpression
if (property.initializer) {
_return.type = this.visitInitializer(property.initializer);
}
}
if (property.kind === SyntaxKind.SetAccessor) {
// For setter accessor, find type in first parameter
if (property.parameters && property.parameters.length === 1) {
_return.type = this.visitProperty(property.parameters[0], sourceFile).type;
}
}
return _return;
}
private visitMethodDeclaration(method: ts.MethodDeclaration, sourceFile: ts.SourceFile) {
let result: any = {
name: method.name.text,
args: method.parameters ? method.parameters.map(prop => this.visitArgument(prop)) : [],
optional: typeof method.questionToken !== 'undefined',
returnType: this.visitType(method.type),
typeParameters: [],
line: this.getPosition(method, sourceFile).line + 1
};
let jsdoctags = this.jsdocParserUtil.getJSDocs(method);
if (typeof method.type === 'undefined') {
// Try to get inferred type
if (method.symbol) {
let symbol: ts.Symbol = method.symbol;
if (symbol.valueDeclaration) {
let symbolType = this.typeChecker.getTypeOfSymbolAtLocation(
symbol,
symbol.valueDeclaration
);
if (symbolType) {
try {
const signature = this.typeChecker.getSignatureFromDeclaration(method);
const returnType = signature.getReturnType();
result.returnType = this.typeChecker.typeToString(returnType);
// tslint:disable-next-line:no-empty
} catch (error) {}
}
}
}
}
if (method.typeParameters && method.typeParameters.length > 0) {
result.typeParameters = method.typeParameters.map(typeParameter =>
this.visitType(typeParameter)
);
}
if (method.jsDoc) {
result.description = marked(this.jsdocParserUtil.getMainCommentOfNode(method));
}
if (method.decorators) {
result.decorators = this.formatDecorators(method.decorators);
}
if (method.modifiers) {
if (method.modifiers.length > 0) {
let kinds = method.modifiers.map(modifier => {
return modifier.kind;
});
if (
_.indexOf(kinds, SyntaxKind.PublicKeyword) !== -1 &&
_.indexOf(kinds, SyntaxKind.StaticKeyword) !== -1
) {
kinds = kinds.filter(kind => kind !== SyntaxKind.PublicKeyword);
}
result.modifierKind = kinds;
}
}
if (jsdoctags && jsdoctags.length >= 1) {
if (jsdoctags[0].tags) {
result.jsdoctags = markedtags(jsdoctags[0].tags);
}
}
if (result.jsdoctags && result.jsdoctags.length > 0) {
result.jsdoctags = mergeTagsAndArgs(result.args, result.jsdoctags);
} else if (result.args.length > 0) {
result.jsdoctags = mergeTagsAndArgs(result.args);
}
return result;
}
private visitOutput(
property: ts.PropertyDeclaration,
outDecorator: ts.Decorator,
sourceFile?: ts.SourceFile
) {
let inArgs = outDecorator.expression.arguments;
let _return: any = {
name: inArgs.length > 0 ? inArgs[0].text : property.name.text,
defaultValue: property.initializer
? this.stringifyDefaultValue(property.initializer)
: undefined
};
if (property.jsDoc) {
_return.description = marked(
marked(this.jsdocParserUtil.getMainCommentOfNode(property))
);
}
if (!_return.description) {
if (property.jsDoc && property.jsDoc.length > 0) {
if (typeof property.jsDoc[0].comment !== 'undefined') {
_return.description = marked(property.jsDoc[0].comment);
}
}
}
_return.line = this.getPosition(property, sourceFile).line + 1;
if (property.type) {
_return.type = this.visitType(property);
} else {
// handle NewExpression
if (property.initializer) {
_return.type = this.visitInitializer(property.initializer);
}
}
return _return;
}
private visitArgument(arg: ts.ParameterDeclaration) {
let _result: any = { name: arg.name.text, type: this.visitType(arg) };
if (arg.dotDotDotToken) {
_result.dotDotDotToken = true;
}
if (arg.questionToken) {
_result.optional = true;
}
if (arg.type) {
if (arg.type.kind) {
if (ts.isFunctionTypeNode(arg.type)) {
_result.function = arg.type.parameters
? arg.type.parameters.map(prop => this.visitArgument(prop))
: [];
}
}
}
if (arg.initializer) {
_result.defaultValue = this.stringifyDefaultValue(arg.initializer);
}
return _result;
}
private visitHostListener(property, hostListenerDecorator, sourceFile?) {
let inArgs = hostListenerDecorator.expression.arguments;
let _return: any = {};
_return.name = inArgs.length > 0 ? inArgs[0].text : property.name.text;
_return.args = property.parameters
? property.parameters.map(prop => this.visitArgument(prop))
: [];
_return.argsDecorator =
inArgs.length > 1
? inArgs[1].elements.map(prop => {
return prop.text;
})
: [];
if (property.jsDoc) {
_return.description = marked(this.jsdocParserUtil.getMainCommentOfNode(property));
}
if (!_return.description) {
if (property.jsDoc) {
if (property.jsDoc.length > 0) {
if (typeof property.jsDoc[0].comment !== 'undefined') {
_return.description = marked(property.jsDoc[0].comment);
}
}
}
}
_return.line = this.getPosition(property, sourceFile).line + 1;
return _return;
}
}