diff --git a/src/ClassSpec.ts b/src/ClassSpec.ts index eb7bf4b..c7d1930 100644 --- a/src/ClassSpec.ts +++ b/src/ClassSpec.ts @@ -5,8 +5,9 @@ import * as ts from 'typescript'; import * as readts from './readts'; export class ClassSpec { - constructor(name: string, doc: string) { + constructor(name: string, symbol: ts.Symbol, doc: string) { this.name = name; + this.symbol = symbol; if(doc) this.doc = doc; } @@ -29,6 +30,7 @@ export class ClassSpec { } name: string; + symbol: ts.Symbol; construct: readts.FunctionSpec; methodList: readts.FunctionSpec[]; propertyList: readts.IdentifierSpec[]; diff --git a/src/IdentifierSpec.ts b/src/IdentifierSpec.ts index 3c44062..0d16fbf 100644 --- a/src/IdentifierSpec.ts +++ b/src/IdentifierSpec.ts @@ -2,11 +2,12 @@ // Released under the MIT license, see LICENSE. import * as ts from 'typescript'; +import * as readts from './readts'; /** Property, function / method parameter or variable. */ export class IdentifierSpec { - constructor(name: string, type: string, doc: string) { + constructor(name: string, type: readts.TypeSpec, doc: string) { this.name = name; this.type = type; if(doc) this.doc = doc; @@ -15,7 +16,7 @@ export class IdentifierSpec { /** Identifier name. */ name: string; /** Type in TypeScript syntax. */ - type: string; + type: readts.TypeSpec; /** Interface members and function / method parameters may be optional. */ optional: boolean; /** JSDoc comment. */ diff --git a/src/Parser.ts b/src/Parser.ts index 1336c96..67ff8f3 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -26,6 +26,8 @@ export class Parser { this.program = ts.createProgram(config.fileNames, config.options); this.checker = this.program.getTypeChecker(); + this.moduleList = []; + this.symbolTbl = {}; for(var source of this.program.getSourceFiles()) { // Skip contents of default library. @@ -37,10 +39,30 @@ export class Parser { return(this.moduleList); } - private formatType(type: ts.Type) { + typeToString(type: ts.Type) { return(this.checker.typeToString(type)); } + private addSymbol(name: string, data: any) { + if(!this.symbolTbl[name]) this.symbolTbl[name] = []; + + this.symbolTbl[name].push(data); + } + + getSymbol(symbol: ts.Symbol) { + for(var match of this.symbolTbl[symbol.getName()] || []) { + if(symbol == match.symbol) return(match); + } + + return(null); + } + + private formatType(type: ts.Type) { + var spec = new readts.TypeSpec(type, this); + + return(spec); + } + private parseModule(node: ts.Node) { var spec = new readts.ModuleSpec(); @@ -100,7 +122,9 @@ export class Parser { } private parseClass(spec: SymbolSpec) { - var classSpec = new readts.ClassSpec(spec.name, spec.doc); + var classSpec = new readts.ClassSpec(spec.name, spec.symbol, spec.doc); + + this.addSymbol(spec.name, classSpec); for(var signature of spec.type.getConstructSignatures()) { classSpec.addConstructor(this.parseSignature(signature)); @@ -167,7 +191,8 @@ export class Parser { ); } - program: ts.Program; - checker: ts.TypeChecker; - moduleList: readts.ModuleSpec[] = []; + private program: ts.Program; + private checker: ts.TypeChecker; + private moduleList: readts.ModuleSpec[]; + private symbolTbl: { [name: string]: any[] }; } diff --git a/src/SignatureSpec.ts b/src/SignatureSpec.ts index 24193b2..bc5b5f5 100644 --- a/src/SignatureSpec.ts +++ b/src/SignatureSpec.ts @@ -5,7 +5,7 @@ import * as ts from 'typescript'; import * as readts from './readts'; export class SignatureSpec { - constructor(returnType: string, doc: string) { + constructor(returnType: readts.TypeSpec, doc: string) { this.returnType = returnType; if(doc) this.doc = doc; } @@ -17,7 +17,7 @@ export class SignatureSpec { /** List of parameters. */ paramList: readts.IdentifierSpec[] = []; /** Return type in TypeScript syntax. */ - returnType: string; + returnType: readts.TypeSpec; /** JSDoc comment. */ doc: string; } diff --git a/src/TypeSpec.ts b/src/TypeSpec.ts new file mode 100644 index 0000000..6de4b8e --- /dev/null +++ b/src/TypeSpec.ts @@ -0,0 +1,68 @@ +// This file is part of readts, copyright (c) 2016 BusFaster Ltd. +// Released under the MIT license, see LICENSE. + +import * as ts from 'typescript'; +import * as readts from './readts'; + +export interface FormatHooks { + [name: string]: (spec: TypeSpec, hooks: FormatHooks) => string; + + class?: (spec: TypeSpec, hooks: FormatHooks) => string; + array?: (spec: TypeSpec, hooks: FormatHooks) => string; + union?: (spec: TypeSpec, hooks: FormatHooks) => string; +} + +export class TypeSpec { + constructor(type: ts.Type, parser: readts.Parser) { + var tf = ts.TypeFlags; + + // console.log(Object.keys(tf).map((name: string) => type.flags & tf[name] ? name : null).filter((name) => !!name).join(' | ')); + + if(type.flags & ((tf as any).Intrinsic | tf.ThisType | tf.Anonymous | tf.StringLiteral)) { + this.name = parser.typeToString(type); + } else if(type.flags & tf.Reference) { + this.parseReference(type as ts.TypeReference, parser); + } else if (type.flags & (tf.Class | tf.Interface | tf.Enum | tf.TypeParameter)) { + this.parseClass(type, parser); + } else if (type.flags & tf.Tuple) { + } else if (type.flags & tf.UnionOrIntersection) { + this.parseUnion(type as ts.UnionOrIntersectionType, parser); + } + } + + parseClass(type: ts.Type, parser: readts.Parser) { + var spec = parser.getSymbol(type.symbol); + + if(spec) this.class = spec; + else this.name = parser.typeToString(type); + } + + parseReference(type: ts.TypeReference, parser: readts.Parser) { + if(type.target.symbol.getName() == 'Array' && type.typeArguments) { + this.arrayOf = new TypeSpec(type.typeArguments[0], parser); + } else this.parseClass(type, parser); + } + + parseUnion(type: ts.UnionOrIntersectionType, parser: readts.Parser) { + this.unionOf = type.types.map((type: ts.Type) => new TypeSpec(type, parser)); + } + + format(hooks?: FormatHooks, needParens?: boolean): string { + if(this.name) return(this.name); + if(this.class) return(hooks && hooks.class ? hooks.class(this, hooks) : this.class.name); + if(this.arrayOf) return(hooks && hooks.array ? hooks.array(this, hooks) : this.arrayOf.format(hooks, true) + '[]'); + + var output: string; + + if(this.unionOf) output = hooks && hooks.union ? hooks.union(this, hooks) : this.unionOf.map((spec: TypeSpec) => spec.format(hooks, true)).join(' | '); + + if(needParens) output = '(' + output + ')'; + + return(output); + } + + name: string; + class: readts.ClassSpec; + unionOf: TypeSpec[]; + arrayOf: TypeSpec; +} diff --git a/src/readts.ts b/src/readts.ts index c1aaccb..ac253c1 100644 --- a/src/readts.ts +++ b/src/readts.ts @@ -6,4 +6,5 @@ export {SignatureSpec} from './SignatureSpec'; export {FunctionSpec} from './FunctionSpec'; export {ClassSpec} from './ClassSpec'; export {ModuleSpec} from './ModuleSpec'; +export {TypeSpec, FormatHooks} from './TypeSpec'; export {Parser} from './Parser';