├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── ClassSpec.ts ├── EnumSpec.ts ├── FunctionSpec.ts ├── IdentifierSpec.ts ├── IndexSpec.ts ├── ModuleSpec.ts ├── Parser.ts ├── SignatureSpec.ts ├── TypeSpec.ts └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | package-lock.json 4 | *.lock 5 | *.log.* 6 | *.log 7 | *.tgz 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | package-lock.json 4 | .travis.yml 5 | *.lock 6 | *.log.* 7 | *.log 8 | *.tgz 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "5.6" 5 | - "4.3" 6 | - "0.12" 7 | - "0.10" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 BusFaster Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | readts 2 | ====== 3 | 4 | [![build status](https://travis-ci.org/charto/readts.svg?branch=master)](http://travis-ci.org/charto/readts) 5 | [![npm version](https://img.shields.io/npm/v/readts.svg)](https://www.npmjs.com/package/readts) 6 | 7 | This is a TypeScript exported class, function, type and documentation parser. 8 | It outputs everything needed to automatically generate documentation and better understand a project's public API. 9 | Information is extracted using TypeScript's [Compiler API](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API). 10 | 11 | Usage 12 | ----- 13 | 14 | ```typescript 15 | import * as readts from 'readts'; 16 | 17 | var parser = new readts.Parser(); 18 | 19 | // Read configuration used in the project we want to analyze. 20 | var config = parser.parseConfig('tsconfig.json'); 21 | 22 | // Modify configuration as needed, for example to avoid writing compiler output to disk. 23 | config.options.noEmit = true; 24 | 25 | // Parse the project. 26 | var tree = parser.parse(config); 27 | ``` 28 | 29 | The output is a list of [`ModuleSpec`](#api-ModuleSpec) objects, each with exported classes and interfaces ([`ClassSpec`](#api-ClassSpec)), 30 | functions ([`FunctionSpec`](#api-FunctionSpec)) and variables ([`IdentifierSpec`](#api-IdentifierSpec)). 31 | Types of variables and function parameters are parsed to [`TypeSpec`](#api-TypeSpec) objects, 32 | pointing to correct [`ClassSpec`](#api-ClassSpec) objects if applicable. 33 | This allows cross-linking types to their definitions in generated documentation. 34 | 35 | See [charto/docts](https://github.com/charto/docts) for a documentation generator using this parser. 36 | 37 | API 38 | === 39 | Docs generated using [`docts`](https://github.com/charto/docts) 40 | > 41 | > 42 | > ### Class [`ClassSpec`](#api-ClassSpec) 43 | > Class or interface and its members. 44 | > Source code: [`<>`](http://github.com/charto/readts/blob/62754ee/src/ClassSpec.ts#L11-L74) 45 | > 46 | > Methods: 47 | > > **new( )** [ClassSpec](#api-ClassSpec) [`<>`](http://github.com/charto/readts/blob/62754ee/src/ClassSpec.ts#L14-L21) 48 | > >  ▪ spec SymbolSpec 49 | > 50 | > Properties: 51 | > > **.name** string 52 | > >  Class name. 53 | > > **.pos** [SourcePos](#api-SourcePos) 54 | 55 | > > **.construct** [FunctionSpec](#api-FunctionSpec) 56 | > >  Constructor function. 57 | > > **.index** [IndexSpec](#api-IndexSpec) 58 | > >  Index signature. 59 | > > **.methodList** [FunctionSpec](#api-FunctionSpec)[] 60 | > >  Public methods. 61 | > > **.propertyList** [IdentifierSpec](#api-IdentifierSpec)[] 62 | > >  Public properties. 63 | > > **.extendList** [ClassSpec](#api-ClassSpec)[] 64 | > >  Class extends 65 | > > **.exports** [ModuleSpec](#api-ModuleSpec) 66 | > >  Class exports 67 | > > **.doc** string 68 | > >  JSDoc comment. 69 | > 70 | > 71 | > ### Class [`EnumSpec`](#api-EnumSpec) 72 | > Enum and its members. 73 | > Source code: [`<>`](http://github.com/charto/readts/blob/ce93baf/src/EnumSpec.ts#L11-L37) 74 | > 75 | > Methods: 76 | > > **new( )** [EnumSpec](#api-EnumSpec) [`<>`](http://github.com/charto/readts/blob/ce93baf/src/EnumSpec.ts#L14-L20) 77 | > >  ▪ spec SymbolSpec 78 | > > **.addMember( )** void [`<>`](http://github.com/charto/readts/blob/ce93baf/src/EnumSpec.ts#L22-L26) 79 | > >  ▪ spec [IdentifierSpec](#api-IdentifierSpec) 80 | > 81 | > Properties: 82 | > > **.name** string 83 | > >  Class name. 84 | > > **.pos** [SourcePos](#api-SourcePos) 85 | 86 | > > **.memberList** [IdentifierSpec](#api-IdentifierSpec)[] 87 | > >  Public properties. 88 | > > **.doc** string 89 | > >  JSDoc comment. 90 | > 91 | > 92 | > ### Interface [`FormatHooks`](#api-FormatHooks) 93 | > Hooks to change how parts of type definitions are converted to strings. 94 | > Source code: [`<>`](http://github.com/charto/readts/blob/ce93baf/src/TypeSpec.ts#L11-L20) 95 | > 96 | > Properties: 97 | > > **.unknown**? FormatHook 98 | > > **.ref**? FormatHook 99 | > > **.array**? FormatHook 100 | > > **.union**? FormatHook 101 | > > **.intersection**? FormatHook 102 | > > **.generic**? FormatHook 103 | > 104 | > 105 | > ### Class [`FunctionSpec`](#api-FunctionSpec) 106 | > Function or method with any number of overloaded signatures. 107 | > Source code: [`<>`](http://github.com/charto/readts/blob/c857e04/src/FunctionSpec.ts#L11-L30) 108 | > 109 | > Methods: 110 | > > **new( )** [FunctionSpec](#api-FunctionSpec) [`<>`](http://github.com/charto/readts/blob/c857e04/src/FunctionSpec.ts#L14-L18) 111 | > >  ▪ spec SymbolSpec 112 | > 113 | > Properties: 114 | > > **.name** string 115 | > >  Function name. 116 | > > **.signatureList** [SignatureSpec](#api-SignatureSpec)[] 117 | > >  List of signatures, one for each overload. 118 | > 119 | > 120 | > ### Class [`IdentifierSpec`](#api-IdentifierSpec) 121 | > Property, function / method parameter or variable. 122 | > Source code: [`<>`](http://github.com/charto/readts/blob/ce93baf/src/IdentifierSpec.ts#L11-L34) 123 | > 124 | > Properties: 125 | > > **.name** string 126 | > >  Identifier name. 127 | > > **.pos** [SourcePos](#api-SourcePos) 128 | > > **.type** [TypeSpec](#api-TypeSpec) 129 | > >  Type definition. 130 | > > **.value**? any 131 | > >  Literal type value 132 | > > **.optional** boolean 133 | > >  Interface members and function / method parameters may be optional. 134 | > > **.doc** string 135 | > >  JSDoc comment. 136 | > 137 | > 138 | > ### Class [`IndexSpec`](#api-IndexSpec) 139 | > Index signature. 140 | > Source code: [`<>`](http://github.com/charto/readts/blob/62754ee/src/IndexSpec.ts#L9-L21) 141 | > 142 | > Properties: 143 | > > **.signature** [TypeSpec](#api-TypeSpec) 144 | > >  Singature type. 145 | > > **.value** [TypeSpec](#api-TypeSpec) 146 | > >  Value type 147 | > 148 | > 149 | > ### Class [`ModuleSpec`](#api-ModuleSpec) 150 | > Module or source file. 151 | > Source code: [`<>`](http://github.com/charto/readts/blob/ce93baf/src/ModuleSpec.ts#L11-L58) 152 | > 153 | > Methods: 154 | > > **.isEmpty( )** boolean [`<>`](http://github.com/charto/readts/blob/ce93baf/src/ModuleSpec.ts#L38-L46) 155 | > >  Test if nothing is exported. 156 | > 157 | > Properties: 158 | > > **.enumList** [EnumSpec](#api-EnumSpec)[] 159 | > >  Definitions of exported enums. 160 | > > **.classList** [ClassSpec](#api-ClassSpec)[] 161 | > >  Definitions of exported classes. 162 | > > **.interfaceList** [ClassSpec](#api-ClassSpec)[] 163 | > >  Definitions of exported interfaces. 164 | > > **.functionList** [FunctionSpec](#api-FunctionSpec)[] 165 | > >  Definitions of exported functions. 166 | > > **.variableList** [IdentifierSpec](#api-IdentifierSpec)[] 167 | > >  Definitions of exported variables. 168 | > 169 | > 170 | > ### Class [`Parser`](#api-Parser) 171 | > Main parser class with public methods, also holding its internal state. 172 | > Source code: [`<>`](http://github.com/charto/readts/blob/f0d51fd/src/Parser.ts#L36-L402) 173 | > 174 | > Methods: 175 | > > **.parseConfig( )** ParsedCommandLine [`<>`](http://github.com/charto/readts/blob/f0d51fd/src/Parser.ts#L39-L44) 176 | > >  Parse a tsconfig.json file using TypeScript services API. 177 | > >  ▪ tsconfigPath string 178 | > > **.parse( )** [ModuleSpec](#api-ModuleSpec)[] [`<>`](http://github.com/charto/readts/blob/f0d51fd/src/Parser.ts#L48-L75) 179 | > >  Parse a TypeScript project using TypeScript services API and configuration. 180 | > >  ▪ config ParsedCommandLine 181 | > >  ▫ nameFilter? (pathName: string) => boolean 182 | > >  ▫ extension? string 183 | > 184 | > 185 | > ### Interface [`RefSpec`](#api-RefSpec) 186 | > Source code: [`<>`](http://github.com/charto/readts/blob/f0d51fd/src/Parser.ts#L25-L32) 187 | > 188 | > Properties: 189 | > > **.name**? string 190 | > > **.symbol**? Symbol 191 | > > **.class**? [ClassSpec](#api-ClassSpec) 192 | > > **.enum**? [EnumSpec](#api-EnumSpec) 193 | > 194 | > 195 | > ### Class [`SignatureSpec`](#api-SignatureSpec) 196 | > Function or method signature defining input and output types. 197 | > Source code: [`<>`](http://github.com/charto/readts/blob/c857e04/src/SignatureSpec.ts#L11-L33) 198 | > 199 | > Methods: 200 | > > **new( )** [SignatureSpec](#api-SignatureSpec) [`<>`](http://github.com/charto/readts/blob/c857e04/src/SignatureSpec.ts#L14-L18) 201 | > >  ▪ pos [SourcePos](#api-SourcePos) 202 | > >  ▪ returnType [TypeSpec](#api-TypeSpec) 203 | > >  ▪ doc string 204 | > 205 | > Properties: 206 | > > **.pos** [SourcePos](#api-SourcePos) 207 | > > **.paramList** [IdentifierSpec](#api-IdentifierSpec)[] 208 | > >  List of parameters. 209 | > > **.returnType** [TypeSpec](#api-TypeSpec) 210 | > >  Return type definition. 211 | > > **.doc** string 212 | > >  JSDoc comment. 213 | > 214 | > 215 | > ### Interface [`SourcePos`](#api-SourcePos) 216 | > Source code: [`<>`](http://github.com/charto/readts/blob/f0d51fd/src/Parser.ts#L8-L12) 217 | > 218 | > Properties: 219 | > > **.sourcePath** string 220 | > > **.firstLine** number 221 | > > **.lastLine** number 222 | > 223 | > 224 | > ### Class [`TypeSpec`](#api-TypeSpec) 225 | > Type definition. 226 | > Source code: [`<>`](http://github.com/charto/readts/blob/ce93baf/src/TypeSpec.ts#L24-L163) 227 | > 228 | > Methods: 229 | > > **.format( )** string [`<>`](http://github.com/charto/readts/blob/ce93baf/src/TypeSpec.ts#L106-L147) 230 | > >  Convert to string, with optional hooks replacing default formatting code. 231 | > >  ▫ hooks? [FormatHooks](#api-FormatHooks) 232 | > >  ▫ needParens? boolean 233 | > 234 | > Properties: 235 | > > **.name** string 236 | > >  Name of the type, only present if not composed of other type or class etc. 237 | > > **.value**? string | number 238 | > >  Value of the type, only present if literal type 239 | > > **.ref** [RefSpec](#api-RefSpec) 240 | > >  Definition of what the type points to, if available. 241 | > > **.unionOf** [TypeSpec](#api-TypeSpec)[] 242 | > >  If the type is a union, list of the possible types. 243 | > > **.intersectionOf** [TypeSpec](#api-TypeSpec)[] 244 | > >  If the type is an intersection, list of the possible types. 245 | > > **.arrayOf** [TypeSpec](#api-TypeSpec) 246 | > >  If the type is an array, its element type. 247 | > > **.argumentList** [TypeSpec](#api-TypeSpec)[] 248 | > >  Arguments of a generic type. 249 | 250 | License 251 | ======= 252 | 253 | [The MIT License](https://raw.githubusercontent.com/charto/readts/master/LICENSE) 254 | 255 | Copyright (c) 2016 BusFaster Ltd 256 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "readts", 3 | "version": "0.2.0", 4 | "description": "TypeScript exported definitions parser", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "tsc": "tsc", 9 | "docts": "docts", 10 | "prepublish": "tsc", 11 | "test": "node dist/index.js" 12 | }, 13 | "author": "Juha Järvi", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/charto/readts.git" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/charto/readts/issues" 21 | }, 22 | "homepage": "https://github.com/charto/readts#readme", 23 | "devDependencies": { 24 | "@types/node": "^8.9.4", 25 | "docts": "~0.2.0" 26 | }, 27 | "dependencies": { 28 | "typescript": "^2.7.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ClassSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | import { SymbolSpec } from './Parser'; 8 | 9 | /** Class or interface and its members. */ 10 | 11 | export class ClassSpec { 12 | /** @ignore internal use. */ 13 | 14 | constructor(spec: SymbolSpec) { 15 | this.name = spec.name; 16 | this.pos = spec.pos; 17 | this.symbol = spec.symbol; 18 | this.exports = new readts.ModuleSpec(); 19 | 20 | if(spec.doc) this.doc = spec.doc; 21 | } 22 | 23 | /** Add constructor signature. @ignore internal use. */ 24 | 25 | addConstructor(spec: readts.SignatureSpec) { 26 | if(!this.construct) this.construct = new readts.FunctionSpec(null); 27 | 28 | this.construct.addSignature(spec); 29 | } 30 | 31 | /** Add method. @ignore internal use. */ 32 | 33 | addMethod(spec: readts.FunctionSpec) { 34 | if(!this.methodList) this.methodList = []; 35 | 36 | this.methodList.push(spec); 37 | } 38 | 39 | /** Add property. @ignore internal use. */ 40 | 41 | addProperty(spec: readts.IdentifierSpec) { 42 | if(!this.propertyList) this.propertyList = []; 43 | 44 | this.propertyList.push(spec); 45 | } 46 | 47 | /** Add extend. @ignore internal use. */ 48 | 49 | addExtend(spec: readts.ClassSpec) { 50 | if(!this.extendList) this.extendList = []; 51 | 52 | this.extendList.push(spec); 53 | } 54 | 55 | /** Class name. */ 56 | name: string; 57 | pos: readts.SourcePos; 58 | /** Symbol from TypeScript services. @ignore internal use. */ 59 | symbol: ts.Symbol; 60 | /** Constructor function. */ 61 | construct: readts.FunctionSpec; 62 | /** Index signature. */ 63 | index: readts.IndexSpec; 64 | /** Public methods. */ 65 | methodList: readts.FunctionSpec[]; 66 | /** Public properties. */ 67 | propertyList: readts.IdentifierSpec[]; 68 | /** Class extends */ 69 | extendList: readts.ClassSpec[]; 70 | /** Class exports */ 71 | exports: readts.ModuleSpec; 72 | /** JSDoc comment. */ 73 | doc: string; 74 | } 75 | -------------------------------------------------------------------------------- /src/EnumSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2018 Arttu Liimola. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | import { SymbolSpec } from './Parser'; 8 | 9 | /** Enum and its members. */ 10 | 11 | export class EnumSpec { 12 | /** @ignore internal use. */ 13 | 14 | constructor(spec: SymbolSpec) { 15 | this.name = spec.name; 16 | this.pos = spec.pos; 17 | this.symbol = spec.symbol; 18 | 19 | if(spec.doc) this.doc = spec.doc; 20 | } 21 | 22 | addMember(spec: readts.IdentifierSpec) { 23 | if(!this.memberList) this.memberList = []; 24 | 25 | this.memberList.push(spec); 26 | } 27 | 28 | /** Class name. */ 29 | name: string; 30 | pos: readts.SourcePos; 31 | /** Symbol from TypeScript services. @ignore internal use. */ 32 | symbol: ts.Symbol; 33 | /** Public properties. */ 34 | memberList: readts.IdentifierSpec[]; 35 | /** JSDoc comment. */ 36 | doc: string; 37 | } 38 | -------------------------------------------------------------------------------- /src/FunctionSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | import { SymbolSpec } from './Parser'; 8 | 9 | /** Function or method with any number of overloaded signatures. */ 10 | 11 | export class FunctionSpec { 12 | /** @ignore internal use. */ 13 | 14 | constructor(spec: SymbolSpec) { 15 | if(spec) { 16 | this.name = spec.name; 17 | } 18 | } 19 | 20 | /** Add a new signature. @ignore internal use. */ 21 | 22 | addSignature(spec: readts.SignatureSpec) { 23 | this.signatureList.push(spec); 24 | } 25 | 26 | /** Function name. */ 27 | name: string; 28 | /** List of signatures, one for each overload. */ 29 | signatureList: readts.SignatureSpec[] = []; 30 | } 31 | -------------------------------------------------------------------------------- /src/IdentifierSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | import { SymbolSpec } from './Parser'; 8 | 9 | /** Property, function / method parameter or variable. */ 10 | 11 | export class IdentifierSpec { 12 | /** @ignore internal use. */ 13 | 14 | constructor(spec: SymbolSpec, type: readts.TypeSpec, optional: boolean) { 15 | this.name = spec.name; 16 | this.type = type; 17 | this.value = type.value; 18 | this.optional = optional; 19 | this.pos = spec.pos; 20 | if(spec.doc) this.doc = spec.doc; 21 | } 22 | 23 | /** Identifier name. */ 24 | name: string; 25 | pos: readts.SourcePos; 26 | /** Type definition. */ 27 | type: readts.TypeSpec; 28 | /** Literal type value */ 29 | value?: any; 30 | /** Interface members and function / method parameters may be optional. */ 31 | optional: boolean; 32 | /** JSDoc comment. */ 33 | doc: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/IndexSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2018 Arttu Liimola. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | /** Index signature. */ 8 | 9 | export class IndexSpec { 10 | /** @ignore internal use. */ 11 | 12 | constructor(signature: readts.TypeSpec, value: readts.TypeSpec) { 13 | this.signature = signature; 14 | this.value = value; 15 | } 16 | 17 | /** Singature type. */ 18 | signature: readts.TypeSpec; 19 | /** Value type */ 20 | value: readts.TypeSpec; 21 | } 22 | -------------------------------------------------------------------------------- /src/ModuleSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | import { SymbolSpec } from './Parser'; 8 | 9 | /** Module or source file. */ 10 | 11 | export class ModuleSpec { 12 | /** Add an exported enum. @ignore internal use. */ 13 | 14 | addEnum(spec: readts.EnumSpec) { 15 | this.enumList.push(spec); 16 | } 17 | 18 | /** Add an exported class. @ignore internal use. */ 19 | 20 | addClass(spec: readts.ClassSpec) { 21 | this.classList.push(spec); 22 | } 23 | 24 | /** Add an exported interface. @ignore internal use. */ 25 | 26 | addInterface(spec: readts.ClassSpec) { 27 | this.interfaceList.push(spec); 28 | } 29 | 30 | /** Add an exported function. @ignore internal use. */ 31 | 32 | addFunction(spec: readts.FunctionSpec) { 33 | this.functionList.push(spec); 34 | } 35 | 36 | /** Test if nothing is exported. */ 37 | 38 | isEmpty() { 39 | return( 40 | !this.enumList.length && 41 | !this.classList.length && 42 | !this.interfaceList.length && 43 | !this.functionList.length && 44 | !this.variableList.length 45 | ); 46 | } 47 | 48 | /** Definitions of exported enums. */ 49 | enumList: readts.EnumSpec[] = []; 50 | /** Definitions of exported classes. */ 51 | classList: readts.ClassSpec[] = []; 52 | /** Definitions of exported interfaces. */ 53 | interfaceList: readts.ClassSpec[] = []; 54 | /** Definitions of exported functions. */ 55 | functionList: readts.FunctionSpec[] = []; 56 | /** Definitions of exported variables. */ 57 | variableList: readts.IdentifierSpec[] = []; 58 | } 59 | -------------------------------------------------------------------------------- /src/Parser.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as path from 'path'; 5 | import * as ts from 'typescript'; 6 | import * as readts from './index'; 7 | 8 | export interface SourcePos { 9 | sourcePath: string; 10 | firstLine: number; 11 | lastLine: number; 12 | } 13 | 14 | /** @ignore internal use. */ 15 | 16 | export interface SymbolSpec { 17 | name: string; 18 | symbol: ts.Symbol; 19 | declaration: ts.Declaration; 20 | type: ts.Type; 21 | pos: SourcePos; 22 | doc: string; 23 | } 24 | 25 | export interface RefSpec { 26 | [key: string]: any; 27 | 28 | name?: string; 29 | symbol?: ts.Symbol; 30 | class?: readts.ClassSpec; 31 | enum?: readts.EnumSpec; 32 | } 33 | 34 | /** Main parser class with public methods, also holding its internal state. */ 35 | 36 | export class Parser { 37 | /** Parse a tsconfig.json file using TypeScript services API. */ 38 | 39 | parseConfig(tsconfigPath: string) { 40 | var configJson = ts.parseConfigFileTextToJson(tsconfigPath, ts.sys.readFile(tsconfigPath)).config; 41 | var config = ts.parseJsonConfigFileContent(configJson, ts.sys, tsconfigPath.replace(/[^/]+$/, ''), {}, tsconfigPath); 42 | 43 | return(config); 44 | } 45 | 46 | /** Parse a TypeScript project using TypeScript services API and configuration. */ 47 | 48 | parse( 49 | config: ts.ParsedCommandLine, 50 | nameFilter?: (pathName: string) => boolean, 51 | extension?: string 52 | ): readts.ModuleSpec[] { 53 | var sourceNum = 0; 54 | 55 | this.program = ts.createProgram(config.fileNames, config.options); 56 | this.checker = this.program.getTypeChecker(); 57 | this.moduleList = []; 58 | this.symbolTbl = {}; 59 | 60 | for(var source of this.program.getSourceFiles()) { 61 | // Skip contents of the default library. 62 | if(sourceNum++ == 0) continue; 63 | 64 | // Call optional filter to check if file should be parsed. 65 | if( 66 | !nameFilter || 67 | !extension || 68 | nameFilter(this.getOwnEmitOutputFilePath(source, this.program, extension)) 69 | ) { 70 | this.parseSource(source); 71 | } 72 | } 73 | 74 | return(this.moduleList); 75 | } 76 | 77 | /** Convert an otherwise unrecognized type to string. @ignore internal use. */ 78 | 79 | typeToString(type: ts.Type) { 80 | return(this.checker.typeToString(type)); 81 | } 82 | 83 | /** Get or change reference for a symbol. @ignore internal use. */ 84 | 85 | getRef(symbol: ts.Symbol, ref?: RefSpec) { 86 | var name = symbol.getName(); 87 | var symbolList = this.symbolTbl[name]; 88 | 89 | if(!symbolList) { 90 | symbolList = []; 91 | this.symbolTbl[name] = symbolList; 92 | } else { 93 | for(var match of symbolList) { 94 | if(symbol == match.symbol) { 95 | if(ref) for(var key of Object.keys(ref)) match[key] = ref[key]; 96 | 97 | return(match); 98 | } 99 | } 100 | } 101 | 102 | if(!ref) ref = {}; 103 | 104 | ref.name = name; 105 | ref.symbol = symbol; 106 | symbolList.push(ref); 107 | 108 | return(ref); 109 | } 110 | 111 | private parseType(type: ts.Type) { 112 | var spec = new readts.TypeSpec(type, this); 113 | 114 | return(spec); 115 | } 116 | 117 | private parseSource(source: ts.SourceFile) { 118 | var symbol = this.checker.getSymbolAtLocation(source); 119 | if(!symbol) return; 120 | 121 | var exportTbl = symbol.exports; 122 | 123 | for(var name of this.getKeys(exportTbl).sort()) { 124 | var spec = this.parseSymbol(exportTbl.get(name)); 125 | 126 | // Resolve aliases. 127 | while(1) { 128 | var symbolFlags = spec.symbol.getFlags(); 129 | 130 | if(symbolFlags & ts.SymbolFlags.Alias) { 131 | spec = this.parseSymbol(this.checker.getAliasedSymbol(spec.symbol)); 132 | } else break; 133 | } 134 | 135 | if(spec.declaration) { 136 | var module = new readts.ModuleSpec(); 137 | 138 | this.parseDeclaration(spec, module); 139 | 140 | this.moduleList.push(module); 141 | } 142 | } 143 | } 144 | 145 | /** Extract declared function, class or interface from a symbol. */ 146 | 147 | private parseDeclaration(spec: SymbolSpec, moduleSpec: readts.ModuleSpec) { 148 | var node = spec.declaration as ts.Node; 149 | 150 | switch(node.kind) { 151 | case ts.SyntaxKind.FunctionDeclaration: 152 | if(spec) { 153 | var functionSpec = this.parseFunction(spec); 154 | if(functionSpec) moduleSpec.addFunction(functionSpec); 155 | } 156 | break; 157 | case ts.SyntaxKind.EnumDeclaration: 158 | if (spec) { 159 | var enumSpec = this.parseEnum(spec); 160 | if(enumSpec) moduleSpec.addEnum(enumSpec); 161 | } 162 | break; 163 | case ts.SyntaxKind.ClassDeclaration: 164 | case ts.SyntaxKind.InterfaceDeclaration: 165 | if(spec) { 166 | var classSpec = this.parseClass(spec); 167 | if(classSpec) { 168 | if(node.kind == ts.SyntaxKind.InterfaceDeclaration) { 169 | moduleSpec.addInterface(classSpec); 170 | } else moduleSpec.addClass(classSpec); 171 | } 172 | } 173 | break; 174 | } 175 | } 176 | 177 | private parseComment(symbol: ts.Symbol | ts.Signature) { 178 | return(ts.displayPartsToString(symbol.getDocumentationComment(this.checker)).trim()); 179 | } 180 | 181 | private parsePos(node: ts.Declaration): SourcePos { 182 | var source = node.getSourceFile(); 183 | 184 | return({ 185 | sourcePath: source.fileName, 186 | firstLine: ts.getLineAndCharacterOfPosition(source, node.getStart()).line + 1, 187 | lastLine: ts.getLineAndCharacterOfPosition(source, node.getEnd()).line + 1 188 | }); 189 | } 190 | 191 | private parseSymbol(symbol: ts.Symbol) { 192 | var declaration = symbol.valueDeclaration; 193 | var type: ts.Type = null; 194 | var pos: SourcePos = null; 195 | 196 | // Interfaces have no value declaration. 197 | if(!declaration) declaration = symbol.getDeclarations()[0]; 198 | 199 | // On merged declaration with enums, valueDeclaration is ModuleDeclaration. 200 | if(declaration.kind == ts.SyntaxKind.ModuleDeclaration) { 201 | const first = symbol.getDeclarations()[0]; 202 | 203 | if(first.kind == ts.SyntaxKind.InterfaceDeclaration) 204 | declaration = first; 205 | } 206 | 207 | if(declaration) { 208 | pos = this.parsePos(declaration); 209 | type = this.checker.getTypeOfSymbolAtLocation(symbol, declaration); 210 | } 211 | 212 | var spec: SymbolSpec = { 213 | symbol: symbol, 214 | declaration: declaration, 215 | type: type, 216 | name: symbol.getName(), 217 | pos: pos, 218 | doc: this.parseComment(symbol) 219 | }; 220 | 221 | return(spec); 222 | } 223 | 224 | private parseEnum(spec: SymbolSpec) { 225 | var enumSpec = new readts.EnumSpec(spec); 226 | 227 | this.getRef(spec.symbol, { enum: enumSpec }); 228 | 229 | if(spec.symbol.getFlags() & ts.SymbolFlags.HasExports) { 230 | var exportTbl = spec.symbol.exports; 231 | 232 | for(let key of this.getKeys(exportTbl)) { 233 | const symbol = exportTbl.get(key); 234 | 235 | const spec = this.parseSymbol(exportTbl.get(key)); 236 | 237 | if(!spec) continue; 238 | 239 | if(spec.symbol.getFlags() & ts.SymbolFlags.EnumMember) { 240 | enumSpec.addMember(this.parseIdentifier(spec, false)); 241 | } 242 | } 243 | } 244 | 245 | return(enumSpec); 246 | } 247 | 248 | private parseClass(spec: SymbolSpec) { 249 | var classSpec = new readts.ClassSpec(spec); 250 | 251 | this.getRef(spec.symbol, { class: classSpec }); 252 | 253 | // Interfaces have no value type. 254 | if(spec.type) { 255 | for(var signature of spec.type.getConstructSignatures()) { 256 | classSpec.addConstructor(this.parseSignature(signature)); 257 | } 258 | } 259 | 260 | var memberTbl = spec.symbol.members; 261 | 262 | for(let key of this.getKeys(memberTbl)) { 263 | const spec = this.parseSymbol(memberTbl.get(key)); 264 | 265 | if(!spec) continue; 266 | 267 | if(spec.declaration) { 268 | if(ts.getCombinedModifierFlags(spec.declaration) & ts.ModifierFlags.Private) continue; 269 | } 270 | 271 | const symbolFlags = spec.symbol.getFlags(); 272 | 273 | if(symbolFlags & ts.SymbolFlags.Method) { 274 | classSpec.addMethod(this.parseFunction(spec)); 275 | } else if(symbolFlags & ts.SymbolFlags.Property) { 276 | classSpec.addProperty(this.parseIdentifier(spec, !!(symbolFlags & ts.SymbolFlags.Optional))); 277 | } else if(ts.isIndexSignatureDeclaration(spec.declaration)) { 278 | classSpec.index = this.parseIndex(spec); 279 | } 280 | } 281 | 282 | const heritageClauses = (spec.declaration).heritageClauses; 283 | 284 | if(heritageClauses) { 285 | for(let heritageClause of heritageClauses) { 286 | for(let type of heritageClause.types) { 287 | const symbol = this.checker.getSymbolAtLocation(type.expression); 288 | 289 | if(symbol && symbol.declarations.length) { 290 | const ref = this.getRef(symbol); 291 | 292 | if(ref.class) { 293 | classSpec.addExtend(ref.class); 294 | } 295 | } 296 | } 297 | } 298 | } 299 | 300 | if(spec.symbol.getFlags() & ts.SymbolFlags.HasExports) { 301 | var exportTbl = spec.symbol.exports; 302 | 303 | for(let key of this.getKeys(exportTbl)) { 304 | const symbol = exportTbl.get(key); 305 | 306 | if(!(symbol.getFlags() & ts.SymbolFlags.ClassMember)) { 307 | const spec = this.parseSymbol(exportTbl.get(key)); 308 | 309 | if(!spec) continue; 310 | 311 | this.parseDeclaration(spec, classSpec.exports); 312 | } 313 | } 314 | } 315 | 316 | return(classSpec); 317 | } 318 | 319 | private parseFunction(spec: SymbolSpec) { 320 | var funcSpec = new readts.FunctionSpec(spec); 321 | 322 | for(var signature of spec.type.getCallSignatures()) { 323 | funcSpec.addSignature(this.parseSignature(signature)); 324 | } 325 | 326 | return(funcSpec); 327 | } 328 | 329 | private parseIndex(spec: SymbolSpec) { 330 | var declaration = spec.declaration as ts.IndexSignatureDeclaration; 331 | var parameter = declaration.parameters[0] as ts.ParameterDeclaration; 332 | 333 | // ParameterDeclaration does have symbol, even though it is not on interfaces. 334 | var singatureType = this.parseType(this.checker.getTypeOfSymbolAtLocation((parameter).symbol, parameter.type)); 335 | var valueType = this.parseType(this.checker.getTypeAtLocation(declaration.type)); 336 | 337 | var indexSpec = new readts.IndexSpec(singatureType, valueType); 338 | return(indexSpec); 339 | } 340 | 341 | /** Parse property, function / method parameter or variable. */ 342 | 343 | private parseIdentifier(spec: SymbolSpec, optional: boolean) { 344 | var varSpec = new readts.IdentifierSpec(spec, this.parseType(spec.type), optional); 345 | return(varSpec); 346 | } 347 | 348 | /** Parse function / method signature. */ 349 | 350 | private parseSignature(signature: ts.Signature) { 351 | var pos: SourcePos; 352 | var declaration = signature.getDeclaration(); 353 | 354 | if(declaration) pos = this.parsePos(declaration); 355 | 356 | var signatureSpec = new readts.SignatureSpec( 357 | pos, 358 | this.parseType(signature.getReturnType()), 359 | this.parseComment(signature) 360 | ); 361 | 362 | for(var param of signature.parameters) { 363 | var spec = this.parseSymbol(param); 364 | if(spec) signatureSpec.addParam(this.parseIdentifier(spec, this.checker.isOptionalParameter(spec.declaration as ts.ParameterDeclaration))); 365 | } 366 | 367 | return(signatureSpec); 368 | } 369 | 370 | private getKeys(map: ts.ReadonlyUnderscoreEscapedMap): ts.__String[] { 371 | const keys: ts.__String[] = []; 372 | map.forEach((_, key) => keys.push(key)); 373 | return(keys); 374 | } 375 | 376 | private getOwnEmitOutputFilePath(sourceFile: ts.SourceFile, program: ts.Program, extension: string): string { 377 | var getCanonicalFileName = (ts as any).createGetCanonicalFileName(ts.sys.useCaseSensitiveFileNames); 378 | var host = { 379 | getCanonicalFileName, 380 | getCommonSourceDirectory: (program as any).getCommonSourceDirectory, 381 | getCompilerOptions: program.getCompilerOptions, 382 | getCurrentDirectory: program.getCurrentDirectory, 383 | }; 384 | var outPath = (ts as any).getOwnEmitOutputFilePath(sourceFile, host, extension); 385 | return(path.resolve(program.getCurrentDirectory(), outPath)); 386 | } 387 | 388 | private static isNodeExported(node: ts.Node) { 389 | return( 390 | !!(ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) || 391 | (node.parent && node.parent.kind == ts.SyntaxKind.SourceFile) 392 | ); 393 | } 394 | 395 | /** TypeScript services API object. */ 396 | private program: ts.Program; 397 | /** TypeScript services type checker. */ 398 | private checker: ts.TypeChecker; 399 | /** List of modules found while parsing. */ 400 | private moduleList: readts.ModuleSpec[]; 401 | private symbolTbl: { [name: string]: RefSpec[] }; 402 | } 403 | -------------------------------------------------------------------------------- /src/SignatureSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | import { SymbolSpec } from './Parser'; 8 | 9 | /** Function or method signature defining input and output types. */ 10 | 11 | export class SignatureSpec { 12 | /** @ignore internal use. */ 13 | 14 | constructor(pos: readts.SourcePos, returnType: readts.TypeSpec, doc: string) { 15 | this.pos = pos; 16 | this.returnType = returnType; 17 | if(doc) this.doc = doc; 18 | } 19 | 20 | /** Add a new parameter and type. @ignore internal use. */ 21 | 22 | addParam(spec: readts.IdentifierSpec) { 23 | this.paramList.push(spec); 24 | } 25 | 26 | pos: readts.SourcePos; 27 | /** List of parameters. */ 28 | paramList: readts.IdentifierSpec[] = []; 29 | /** Return type definition. */ 30 | returnType: readts.TypeSpec; 31 | /** JSDoc comment. */ 32 | doc: string; 33 | } 34 | -------------------------------------------------------------------------------- /src/TypeSpec.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import * as ts from 'typescript'; 5 | import * as readts from './index'; 6 | 7 | export type FormatHook = (spec: TypeSpec, output?: string, hooks?: FormatHooks) => string; 8 | 9 | /** Hooks to change how parts of type definitions are converted to strings. */ 10 | 11 | export interface FormatHooks { 12 | [name: string]: FormatHook; 13 | 14 | unknown?: FormatHook; 15 | ref?: FormatHook; 16 | array?: FormatHook; 17 | union?: FormatHook; 18 | intersection?: FormatHook; 19 | generic?: FormatHook; 20 | } 21 | 22 | /** Type definition. */ 23 | 24 | export class TypeSpec { 25 | /** Parse a type from TypeScript services. @ignore internal use. */ 26 | 27 | constructor(type: ts.Type, parser: readts.Parser) { 28 | var tf = ts.TypeFlags; 29 | var oFlags = ts.ObjectFlags; 30 | 31 | // console.log(Object.keys(tf).map((name: string) => type.flags & tf[name] ? name : null).filter((name) => !!name).join(' | ')); 32 | 33 | if(type.flags & tf.EnumLiteral){ 34 | if(type.flags & tf.Union) { 35 | this.ref = parser.getRef(type.symbol); 36 | } else { 37 | this.parseEnum(type); 38 | } 39 | } else if(this.isSimpleType(type)) { 40 | this.name = parser.typeToString(type); 41 | } else if (this.isTypeReference(type)) { 42 | this.parseReference(type, parser); 43 | } else if (this.isClassLikeType(type)) { 44 | this.parseClass(type, parser); 45 | } else if (this.isObjectType(type) && (type.objectFlags & oFlags.Tuple)) { 46 | } else if (type.flags & tf.Union) { 47 | this.unionOf = this.parseList((type as ts.UnionOrIntersectionType).types, parser); 48 | } else if (type.flags & tf.Intersection) { 49 | this.intersectionOf = this.parseList((type as ts.UnionOrIntersectionType).types, parser); 50 | } 51 | } 52 | 53 | private parseEnum(type: ts.Type) { 54 | this.name = type.flags & ts.TypeFlags.NumberLiteral ? 'number' : 'string'; 55 | this.value = (type).value; 56 | } 57 | 58 | private parseClass(type: ts.Type, parser: readts.Parser) { 59 | this.ref = parser.getRef(type.symbol); 60 | } 61 | 62 | private parseReference(type: ts.TypeReference, parser: readts.Parser) { 63 | // Hack to recognize arrays, TypeScript services doesn't seem to export 64 | // the array symbol it uses internally to detect array types 65 | // so just check the name. 66 | if(type.target.symbol.getName() == 'Array' && type.typeArguments) { 67 | this.arrayOf = new TypeSpec(type.typeArguments[0], parser); 68 | } else { 69 | this.parseClass(type, parser); 70 | if(type.typeArguments) this.argumentList = this.parseList(type.typeArguments, parser); 71 | } 72 | } 73 | 74 | private parseList(typeList: ts.Type[], parser: readts.Parser) { 75 | return(typeList.map((type: ts.Type) => new TypeSpec(type, parser))); 76 | } 77 | 78 | private isSimpleType(type: ts.Type): boolean { 79 | if (!!(type.flags & ((ts.TypeFlags as any).Intrinsic | ts.TypeFlags.StringLiteral))) { 80 | return true; 81 | } 82 | if ((type as any).isThisType) { 83 | return true; 84 | } 85 | if (this.isObjectType(type) && !!(type.objectFlags & ts.ObjectFlags.Anonymous)) { 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | private isClassLikeType(type: ts.Type): boolean { 92 | return !!(type.flags & (ts.TypeFlags.Enum | ts.TypeFlags.TypeParameter)) 93 | || (this.isObjectType(type) && !!(type.objectFlags & ts.ObjectFlags.ClassOrInterface)); 94 | } 95 | 96 | private isObjectType(type: ts.Type): type is ts.ObjectType { 97 | return !!(type.flags & ts.TypeFlags.Object); 98 | } 99 | 100 | private isTypeReference(type: ts.Type): type is ts.TypeReference { 101 | return this.isObjectType(type) && !!(type.objectFlags & ts.ObjectFlags.Reference); 102 | } 103 | 104 | /** Convert to string, with optional hooks replacing default formatting code. */ 105 | 106 | format(hooks?: FormatHooks, needParens?: boolean): string { 107 | var output: string; 108 | var hook: FormatHook; 109 | 110 | if(!hooks) hooks = {}; 111 | 112 | if(this.name) { 113 | hook = hooks.unknown; 114 | output = this.name; 115 | } 116 | 117 | if(this.ref) { 118 | output = this.ref.name; 119 | if(hooks.ref) output = hooks.ref(this, output, hooks); 120 | 121 | if(this.argumentList) { 122 | hook = hooks.generic; 123 | output += '<' + this.argumentList.map((spec: TypeSpec) => spec.format(hooks, false)).join(', ') + '>'; 124 | } 125 | } 126 | 127 | if(this.arrayOf) { 128 | hook = hooks.array; 129 | output = this.arrayOf.format(hooks, true) + '[]'; 130 | } 131 | 132 | if(output) return(hook ? hook(this, output, hooks) : output); 133 | 134 | if(this.unionOf) { 135 | hook = hooks.union; 136 | output = this.unionOf.map((spec: TypeSpec) => spec.format(hooks, true)).join(' | '); 137 | } 138 | 139 | if(this.intersectionOf) { 140 | hook = hooks.intersection; 141 | output = this.intersectionOf.map((spec: TypeSpec) => spec.format(hooks, true)).join(' & '); 142 | } 143 | 144 | if(needParens) output = '(' + output + ')'; 145 | 146 | return(hook ? hook(this, output, hooks) : output); 147 | } 148 | 149 | /** Name of the type, only present if not composed of other type or class etc. */ 150 | name: string; 151 | /** Value of the type, only present if literal type */ 152 | value?: string | number; 153 | /** Definition of what the type points to, if available. */ 154 | ref: readts.RefSpec; 155 | /** If the type is a union, list of the possible types. */ 156 | unionOf: TypeSpec[]; 157 | /** If the type is an intersection, list of the possible types. */ 158 | intersectionOf: TypeSpec[]; 159 | /** If the type is an array, its element type. */ 160 | arrayOf: TypeSpec; 161 | /** Arguments of a generic type. */ 162 | argumentList: TypeSpec[]; 163 | } 164 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // This file is part of readts, copyright (c) 2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | export { IdentifierSpec } from './IdentifierSpec'; 5 | export { SignatureSpec } from './SignatureSpec'; 6 | export { FunctionSpec } from './FunctionSpec'; 7 | export { EnumSpec } from './EnumSpec'; 8 | export { ClassSpec } from './ClassSpec'; 9 | export { ModuleSpec } from './ModuleSpec'; 10 | export { IndexSpec } from './IndexSpec'; 11 | export { TypeSpec, FormatHook, FormatHooks } from './TypeSpec'; 12 | export { Parser, SourcePos, RefSpec } from './Parser'; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "experimentalDecorators": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noImplicitAny": true, 9 | "noImplicitThis": true, 10 | "outDir": "dist", 11 | "removeComments": false, 12 | "target": "es5" 13 | }, 14 | "files": [ 15 | "src/index.ts" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------