├── .gitattributes ├── CHANGES.txt ├── LICENSE.txt ├── Makefile ├── README.md ├── bin ├── harness.js ├── readline.js ├── tss └── tss.js ├── ftplugin └── typescript_tss.vim ├── harness.ts ├── package.json ├── tests ├── README.md ├── concat-map.script ├── empty.ts ├── issue-10.script ├── issue-11.script ├── issue-12.script ├── issue-13.script ├── issue-15-import.ts ├── issue-15.script ├── issue-15.ts ├── issue-17-import.ts ├── issue-17.script ├── issue-17.ts ├── issue-52.script ├── issue-9.script ├── partial-update.script ├── script.js ├── script.out ├── test.script ├── test.ts ├── tsconfig.json └── update-nocheck-completion-chain.script ├── tss.ts └── typings └── node └── node.d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 0.7.0: 2 | 3 | - get it to compile and run with TypeScript 1.8.10 4 | 5 | - findConfigFile needs additional parameter 6 | 7 | 0.6.0: 8 | 9 | - get it to compile and run with TypeScript 1.7.5 10 | 11 | - modified module resolution: 12 | no need to reference typescript.d.ts explicitly 13 | dependency chasing may involve package.json files 14 | 15 | - readConfigFile needs additional parameter 16 | 17 | - parseConfigFile renamed to parseJsonConfigFileContent 18 | 19 | 0.5.0: 20 | 21 | - get it to compile and run with TypeScript 1.6.3 22 | 23 | - changed location of typescript.d.ts and typescript.js 24 | 25 | - package.json: use standardized licence field 26 | 27 | - Makefile; add NODE variable 28 | eliminate mentions of lib.d.ts 29 | don't try to bump package version on every build 30 | 31 | - eliminate defaultLibs.d.ts (unused) 32 | 33 | - tss.ts: getScriptIsOpen is gone 34 | parseConfigFile needs an extra parameter 35 | fix calculation of endPos in update command 36 | 37 | - tests: use relative imports 38 | factor out NODE_MODULES path 39 | update test results 40 | 41 | 0.4.7: 42 | 43 | - get it to compile and run with TypeScript 1.5-beta 44 | 45 | 0.4.6: 46 | 47 | - split into vim plugin (typescript-tools.vim) and commandline server (typescript-tools) 48 | 49 | 0.3.1 to 0.4.5: 50 | 51 | - get it to compile and run with github version of TypeScript (1.5-alpha), see #39 52 | 53 | - 'type' command renamed to 'quickInfo' (follows LS API naming) 54 | (old version is still accepted, but deprecated) 55 | 56 | - 'navigateToItems' command is new (:TSSnavigateTo in Vim) 57 | 58 | - 'navigationBarItems' command is new (:TSSnavigation in Vim) 59 | 60 | - 'structure' command (:TSSstructure in Vim) is gone (try 'navigationBarItems' instead) 61 | 62 | - 'signature' command is new 63 | 64 | - 'references' command: add 'lineText' field (#45, :TSSreferences in Vim) 65 | 66 | - 'completions' command: boolean no longer required 67 | 68 | - support tsconfig.json file and commandline options (#44,#49) 69 | 70 | - improve dependency chasing: 71 | - don't build a Program before creating language service 72 | - support loading new files after language service has started 73 | 74 | - Vim: add type to preview info for :TSStype and Omni-Completion (helps with #46) 75 | 76 | - fix #35 (error handling and "tss not running" message) 77 | 78 | - fix script lookup existence check (#47) 79 | 80 | - accept multiple root files (#49) 81 | 82 | - follow LS API change in 'showErrors' ('text' field can be a message chain, #52) 83 | 84 | - API fix 'showErrors': global errors have no file-related fields 85 | 86 | 0.3.0 to 0.3.1: 87 | 88 | - update TS to v0.9.1-2505-g18875cc (develop branch) 89 | 90 | - fix #32 (EOLs in bin) 91 | 92 | - fix #33 (-1 return in TSScompleteFunc) 93 | 94 | 0.2.2-1 to 0.3.0: 95 | 96 | - update TS to v0.9.1-2130-g0c53b50 (develop branch, about TS v1.0 release) 97 | 98 | - fix tss command help 99 | 100 | - fix #23 (bin/tss executable bit) 101 | 102 | - primitive types have no completion details #29 103 | 104 | - tss.vim: 105 | - change `TSSstructure` to popup nagivation menu 106 | - add `TSSfile`: quickly open project files, with filename completion 107 | - add `TSSfilesMenu`: quickly open project files, with popup navigation menu 108 | - add sample keymap 109 | - guard against tss not running/no python 110 | - only use shell on windows; fixes #18 111 | - improve TSSstatus();"tss not running" handling #29 112 | 113 | 0.2.1 to 0.2.2-1: NOTE: package name changed! 114 | 115 | - same code as 0.2.1, packaged slightly differently: 116 | 117 | - package name changed from `tss` to `typescript-tools` 118 | (matches repo name, is descriptive and easier to find 119 | in package lists) 120 | 121 | NOTE: remember to uninstall previous version (named `tss`) 122 | 123 | - Vim plugin reorganised as a filetype plugin 124 | (so you can just add the repo path to Vim's runtime path 125 | and enable filetype plugins) 126 | 127 | 0.2.0 to 0.2.1: 128 | 129 | - store project files in dependency-first order 130 | (avoids some spurious type errors) 131 | 132 | - updated TS to v0.9.1-376-gad18600 133 | 134 | - patch for #15 (TS issue 1651) 135 | 136 | - several fixes in file `update` handling 137 | 138 | - `update`: show syntax/semantics error count (works around TS issue 1592), 139 | distinguish updated from added files (highlights file addition accidents); 140 | for speed in combination with completions, use `update nocheck`, with fallback 141 | to full `update` when `completions` fails (cf #13,#14) 142 | 143 | - fixes in `completions`, `definitions`, and `showErrors` 144 | 145 | - add `--version` flag, use 'git describe --tags' for versioning 146 | 147 | - improved tests and commandline debugging; add basic Makefile 148 | 149 | - improved file path resolution and normalization 150 | 151 | - `showErrors`: provide category of diagnostic (error/warning) 152 | and phase which generated it (resolution/syntax/semantics) 153 | 154 | - add `structure` command (getScriptLexicalStructure) 155 | 156 | - tss.vim: rudimentary support for 157 | - showing file structure `TSSstructure` 158 | - listing project files `TSSfiles` 159 | - browsing MDN docs for EcmaScript global object properties&methods 160 | 161 | 0.1.0 to 0.2.0: 162 | 163 | - partial rewrite, including PROTOCOL CHANGES 164 | (outlined below, see README.md for details of current protocol) 165 | 166 | - tss v0.2 supports/uses TypeScript v0.9 167 | 168 | - `symbol` command is gone 169 | (use `type` command instead, properties `fullSymbolName` and `type`) 170 | 171 | - commands now return `{line,character}` records, whereever positions are concerned 172 | 173 | - `completions-brief` command omits `type`/`docComment` details 174 | 175 | - `completions` commands prefilter suggestions, based on identifier prefix before cursor 176 | 177 | - `files` command returns list of files in current project 178 | 179 | - `references` command returns list of reference locations (`getReferencesAtPosition`) 180 | 181 | - improved feature test coverage 182 | 183 | - tss.vim: 184 | - add TSSreferences command 185 | - fix preview window handling 186 | - improved tracing and log file handling 187 | - set omnifunc only for .ts files 188 | - improved update handling 189 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | 39 | You must cause any modified files to carry prominent notices stating that You changed the files; and 40 | 41 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 42 | 43 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE=node 2 | TYPESCRIPT=node_modules/typescript 3 | TSC=$(NODE) $(TYPESCRIPT)/bin/tsc 4 | TEST_SCRIPTS=tests/*.script 5 | TEST_SOURCES=tests/*.ts 6 | VERSION=$(shell git describe --tags) 7 | 8 | build: bin/tss.js tests/script.diff tests/script.results 9 | 10 | bin/tss.js: tss.ts harness.ts $(TYPESCRIPT)/lib/typescript.d.ts 11 | $(TSC) tss.ts -target ES5 -m commonjs --noEmitOnError -outDir bin 2>&1 | tee build.log 12 | test ! -s build.log 13 | 14 | tests/script.out2: $(TEST_SCRIPTS) $(TEST_SOURCES) tests/tsconfig.json tests/script.js tests/script.out bin/tss.js 15 | cd tests;\ 16 | $(NODE) script.js >script.out2; 17 | 18 | tests/script.diff: tests/script.out tests/script.out2 19 | cd tests;\ 20 | diff --strip-trailing-cr script.out* 2>&1 | tee script.diff 21 | 22 | tests/script.results: tests/script.diff 23 | grep '^[0-9]' tests/script.diff >script.results;\ 24 | grep -n '//.*script' tests/script.out >>script.results;\ 25 | sort -n script.results 26 | 27 | package.json: .git/refs/heads/master 28 | $(NODE) -e "var pj=require('./package.json'); \ 29 | pj.version='$(VERSION)';\ 30 | require('fs').writeFileSync('package.json',JSON.stringify(pj,null,' '));" 31 | 32 | eols: 33 | find . -wholename './.git' -prune -o -wholename './node_modules' -prune -o -exec file \{\} \; | grep CRLF 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | (if you are upgrading from an older version, see CHANGES.txt) 3 | 4 | ## typescript-tools 5 | 6 | typescript-tools (v0.7) provides access to the TypeScript Language Services (v1.8) via a simple commandline server (tss). This makes it easy to build editor plugins supporting TypeScript. Several editor plugins are available. If you build plugins for other editors/IDEs based on typescript-tools, please let me know. 7 | 8 | - Vim plugin: https://github.com/clausreinke/typescript-tools.vim 9 | - Emacs plugin: https://github.com/aki2o/emacs-tss 10 | - Sublime plugins: https://github.com/Railk/T3S, https://github.com/Phaiax/ArcticTypescript 11 | 12 | For reporting bugs in typescript-tools itself (server or vim plugin), please use our issue tracker. If you want to announce an editor plugin based on typescript-tools, just file a documentation bug;-) 13 | 14 | ### Installation 15 | 16 | npm installation goes somewhat like this - either clone and install: 17 | 18 | ``` 19 | # install git and node/npm, then 20 | $ git clone git://github.com/clausreinke/typescript-tools.git 21 | $ cd typescript-tools/ 22 | $ npm install -g 23 | ``` 24 | 25 | or let npm do the cloning: 26 | 27 | ``` 28 | # install git and node/npm, then 29 | $ npm install -g clausreinke/typescript-tools 30 | ``` 31 | 32 | From-source compilation should not be necessary, as a pre-compiled `bin/tss.js` is included, as well as a `bin/lib.d.ts`. But if you want to rebuild and test tss, you can run `make` in `typescript-tools`. 33 | 34 | The installation should give you a global `tss` command, which you can use directly, as in this sample session (note that the absolute paths will differ in your installation): 35 | 36 | ``` 37 | $ tss tests/test.ts 38 | "loaded c:/javascript/typescript/github/typescript-tools/tests/test.ts, TSS listening.." 39 | 40 | type 4 2 tests/test.ts 41 | {"kind":"var","kindModifiers":"","textSpan":{"start":38,"length":1},"documentation":[],"type":"(var) x: {\n a: number;\n b: number;\n}","docComment":""} 42 | 43 | definition 4 2 tests/test.ts 44 | {"def":{"fileName":"c:/javascript/typescript/github/typescript-tools/tests/test.ts","textSpan":{"start":4,"length":13},"kind":"var","name":"x","containerName":""},"file":"c:/javascript/typescript/github/typescript-tools/tests/test.ts","min":{"line":1,"character":5},"lim":{"line":1,"character":18}} 45 | 46 | completions 4 4 tests/test.ts 47 | {"isMemberCompletion":true,"entries":[{"name":"a","kind":"property","kindModifiers":"","type":"(property) a: number","docComment":""},{"name":"b","kind":"property","kindModifiers":"","type":"(property) b: number","docComment":""}]} 48 | 49 | quit 50 | "TSS closing" 51 | ``` 52 | 53 | If you want to use tss from Vim/Emacs/Sublime, see the plugin links above. If you want to use this from other editors/IDEs, you will need to write some code, to communicate with `tss` as an asynchronous subprocess (please let me know how it goes, especially if you release a working plugin). 54 | 55 | TypeScript tools currently available: 56 | 57 | ## tss.ts: TypeScript Services Server 58 | 59 | Simple commandline interface (commands in, info out) to TypeScript Services. Currently supported commands (with indication of purpose and output format) include: 60 | 61 | ``` 62 | quickInfo 63 | // get type information and documentation 64 | 65 | { type: string 66 | , docComment: string 67 | } 68 | 69 | definition 70 | // get location of definition 71 | 72 | { file: string 73 | , min: { line: number, character: number } 74 | , lim: { line: number, character: number } 75 | } 76 | 77 | completions 78 | // get completions 79 | 80 | { entries: [{name: string, type?: string, docComment?: string}, ...] 81 | } 82 | 83 | completions-brief 84 | // get completions without type/docComment details 85 | 86 | { entries: [{name: string}, ...] 87 | } 88 | 89 | references 90 | // get references 91 | 92 | [{ file: string 93 | , lineText: string 94 | , min: { line: number, character: number } 95 | , lim: { line: number, character: number } 96 | }] 97 | 98 | navigationBarItems 99 | // get list of items to navigate to in 100 | 101 | [{ info: string 102 | , min: { line: number, character: number } 103 | , lim: { line: number, character: number } 104 | , childItems: ..recursive.. 105 | }] 106 | 107 | navigateToItems 108 | // get list of matching items to navigate to in project 109 | // where matching is modulo case, prefix, infix, camelCase 110 | // (exposes LS API details occasionally subject to change) 111 | 112 | [{ name: string 113 | , kind: string 114 | , kindModifiers: string 115 | , matchKind: string 116 | , isCaseSensitive: boolean 117 | , fileName: string 118 | , containerName: string 119 | , containerKind: string 120 | , min: { line: number, character: number } 121 | , lim: { line: number, character: number } 122 | }] 123 | 124 | update (nocheck)? // followed by linecount lines of source text 125 | // provide current source, if there are unsaved changes 126 | 127 | "updated , (/) errors" 128 | 129 | or (probably not a good idea to use this) 130 | 131 | "added , (/) errors" 132 | 133 | reload 134 | // reload current project (chasing dependencies from ) 135 | 136 | "reloaded , TSS listening.." 137 | 138 | files 139 | // list files in current project 140 | 141 | [,...] 142 | 143 | showErrors 144 | // show compilation errors for current project 145 | // (NOTE: global errors have no file/start/end fields) 146 | 147 | [{file?: string 148 | ,start?: {line: number, character: number} 149 | ,end?: {line: number, character: number} 150 | ,text: string 151 | ,phase: string 152 | ,category: string 153 | } 154 | , ... 155 | ] 156 | 157 | quit 158 | // quit tss 159 | 160 | "TSS closing" 161 | ``` 162 | 163 | Start `tss` with project root file - may take several seconds to load all 164 | dependencies for larger projects; then enter commands and get JSON info or 165 | error messages. 166 | 167 | ### configuration: tsconfig.json or commandline options 168 | 169 | tss can now be configured the same way as tsc, either via commandline options or 170 | via tsconfig.json files (since about TSv1.5). In both cases, only options that affect 171 | the language service have any effect. As a simple example, loading sources with 172 | external modules generates errors 173 | 174 | ``` 175 | $ echo showErrors | bin/tss tests/issue-17.ts 176 | "loaded c:/javascript/typescript/github/typescript-tools/tests/issue-17.ts, TSS 177 | listening.." 178 | showErrors 179 | [{"file":"c:/javascript/typescript/github/typescript-tools/tests/issue-17-import 180 | .ts","start":{"line":1,"character":14},"end":{"line":1,"character":18},"text":"C 181 | annot compile external modules unless the '--module' flag is provided.","code":1 182 | 148,"phase":"Syntax","category":"Error"}] 183 | ``` 184 | 185 | unless a module system is selected: 186 | 187 | ``` 188 | $ echo showErrors | bin/tss --module commonjs tests/issue-17.ts 189 | "loaded c:/javascript/typescript/github/typescript-tools/tests/issue-17.ts, TSS 190 | listening.." 191 | showErrors 192 | [] 193 | 194 | $ echo showErrors | bin/tss --project tests/ tests/issue-17.ts 195 | "loaded c:/javascript/typescript/github/typescript-tools/tests/issue-17.ts, TSS 196 | listening.." 197 | showErrors 198 | [] 199 | 200 | $ cat tests/tsconfig.json 201 | {"compilerOptions": {"target":"ES5","module":"commonjs"} } 202 | 203 | ``` 204 | -------------------------------------------------------------------------------- /bin/harness.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft, Claus Reinke. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. 3 | // See LICENSE.txt in the project root for complete license information. 4 | "use strict"; 5 | /// 6 | var ts = require("typescript"); 7 | // TODO: avoid pre-computing line starts, can tss use SourceFiles instead? 8 | /* @internal, from scanner.ts */ 9 | function computeLineStarts(text) { 10 | var result = new Array(); 11 | var pos = 0; 12 | var lineStart = 0; 13 | while (pos < text.length) { 14 | var ch = text.charCodeAt(pos++); 15 | switch (ch) { 16 | case 0x0D: 17 | if (text.charCodeAt(pos) === 0x0A) { 18 | pos++; 19 | } 20 | case 0x0A: 21 | result.push(lineStart); 22 | lineStart = pos; 23 | break; 24 | default: 25 | if (ch > 0x7F && ts.isLineBreak(ch)) { 26 | result.push(lineStart); 27 | lineStart = pos; 28 | } 29 | break; 30 | } 31 | } 32 | result.push(lineStart); 33 | return result; 34 | } 35 | var ScriptInfo = (function () { 36 | function ScriptInfo(fileName, content, isOpen) { 37 | if (isOpen === void 0) { isOpen = true; } 38 | this.fileName = fileName; 39 | this.content = content; 40 | this.isOpen = isOpen; 41 | this.version = 1; 42 | this.editRanges = []; 43 | this.setContent(content); 44 | } 45 | ScriptInfo.prototype.setContent = function (content) { 46 | this.content = content; 47 | }; 48 | ScriptInfo.prototype.updateContent = function (content) { 49 | var old_length = this.content.length; 50 | this.setContent(content); 51 | this.editRanges.push({ 52 | length: content.length, 53 | textChangeRange: 54 | // NOTE: no shortcut for "update everything" (null only works in some places, #10) 55 | ts.createTextChangeRange(ts.createTextSpan(0, old_length), content.length) 56 | }); 57 | this.version++; 58 | }; 59 | ScriptInfo.prototype.editContent = function (minChar, limChar, newText) { 60 | // Apply edits 61 | var prefix = this.content.substring(0, minChar); 62 | var middle = newText; 63 | var suffix = this.content.substring(limChar); 64 | this.setContent(prefix + middle + suffix); 65 | // Store edit range + new length of script 66 | this.editRanges.push({ 67 | length: this.content.length, 68 | textChangeRange: ts.createTextChangeRange(ts.createTextSpanFromBounds(minChar, limChar), newText.length) 69 | }); 70 | // Update version # 71 | this.version++; 72 | }; 73 | ScriptInfo.prototype.getTextChangeRangeBetweenVersions = function (startVersion, endVersion) { 74 | if (startVersion === endVersion) { 75 | // No edits! 76 | return ts.unchangedTextChangeRange; 77 | } 78 | var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); 79 | var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); 80 | var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); 81 | return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(function (e) { return e.textChangeRange; })); 82 | }; 83 | return ScriptInfo; 84 | }()); 85 | exports.ScriptInfo = ScriptInfo; 86 | var ScriptSnapshot = (function () { 87 | function ScriptSnapshot(scriptInfo) { 88 | this.scriptInfo = scriptInfo; 89 | this.lineMap = null; 90 | this.textSnapshot = scriptInfo.content; 91 | this.version = scriptInfo.version; 92 | } 93 | ScriptSnapshot.prototype.getText = function (start, end) { 94 | return this.textSnapshot.substring(start, end); 95 | }; 96 | ScriptSnapshot.prototype.getLength = function () { 97 | return this.textSnapshot.length; 98 | }; 99 | ScriptSnapshot.prototype.getLineStartPositions = function () { 100 | if (this.lineMap === null) { 101 | this.lineMap = computeLineStarts(this.textSnapshot); 102 | } 103 | return this.lineMap; 104 | }; 105 | ScriptSnapshot.prototype.getChangeRange = function (oldSnapshot) { 106 | return undefined; 107 | }; 108 | return ScriptSnapshot; 109 | }()); 110 | exports.ScriptSnapshot = ScriptSnapshot; 111 | -------------------------------------------------------------------------------- /bin/readline.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // Inspiration for this code comes from Salvatore Sanfilippo's linenoise. 23 | // https://github.com/antirez/linenoise 24 | // Reference: 25 | // * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 26 | // * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 27 | 28 | var kHistorySize = 30; 29 | var kBufSize = 10 * 1024; 30 | 31 | var util = require('util'); 32 | var inherits = require('util').inherits; 33 | var EventEmitter = require('events').EventEmitter; 34 | 35 | 36 | exports.createInterface = function(input, output, completer, terminal) { 37 | var rl; 38 | if (arguments.length === 1) { 39 | rl = new Interface(input); 40 | } else { 41 | rl = new Interface(input, output, completer, terminal); 42 | } 43 | return rl; 44 | }; 45 | 46 | 47 | function Interface(input, output, completer, terminal) { 48 | if (!(this instanceof Interface)) { 49 | return new Interface(input, output, completer, terminal); 50 | } 51 | 52 | this._sawReturn = false; 53 | 54 | EventEmitter.call(this); 55 | 56 | if (arguments.length === 1) { 57 | // an options object was given 58 | output = input.output; 59 | completer = input.completer; 60 | terminal = input.terminal; 61 | input = input.input; 62 | } 63 | 64 | completer = completer || function() { return []; }; 65 | 66 | if (typeof completer !== 'function') { 67 | throw new TypeError('Argument \'completer\' must be a function'); 68 | } 69 | 70 | // backwards compat; check the isTTY prop of the output stream 71 | // when `terminal` was not specified 72 | if (typeof terminal == 'undefined') { 73 | terminal = !!output.isTTY; 74 | } 75 | 76 | var self = this; 77 | 78 | this.output = output; 79 | this.input = input; 80 | 81 | // Check arity, 2 - for async, 1 for sync 82 | this.completer = completer.length === 2 ? completer : function(v, callback) { 83 | callback(null, completer(v)); 84 | }; 85 | 86 | this.setPrompt('> '); 87 | 88 | this.terminal = !!terminal; 89 | 90 | function ondata(data) { 91 | self._normalWrite(data); 92 | } 93 | 94 | function onend() { 95 | self.close(); 96 | } 97 | 98 | function onkeypress(s, key) { 99 | self._ttyWrite(s, key); 100 | } 101 | 102 | function onresize() { 103 | self._refreshLine(); 104 | } 105 | 106 | if (!this.terminal) { 107 | input.on('data', ondata); 108 | input.on('end', onend); 109 | self.once('close', function() { 110 | input.removeListener('data', ondata); 111 | input.removeListener('end', onend); 112 | }); 113 | var StringDecoder = require('string_decoder').StringDecoder; // lazy load 114 | this._decoder = new StringDecoder('utf8'); 115 | 116 | } else { 117 | 118 | exports.emitKeypressEvents(input); 119 | 120 | // input usually refers to stdin 121 | input.on('keypress', onkeypress); 122 | 123 | // Current line 124 | this.line = ''; 125 | 126 | this._setRawMode(true); 127 | this.terminal = true; 128 | 129 | // Cursor position on the line. 130 | this.cursor = 0; 131 | 132 | this.history = []; 133 | this.historyIndex = -1; 134 | 135 | output.on('resize', onresize); 136 | self.once('close', function() { 137 | input.removeListener('keypress', onkeypress); 138 | output.removeListener('resize', onresize); 139 | }); 140 | } 141 | 142 | input.resume(); 143 | } 144 | 145 | inherits(Interface, EventEmitter); 146 | 147 | Interface.prototype.__defineGetter__('columns', function() { 148 | return this.output.columns || Infinity; 149 | }); 150 | 151 | Interface.prototype.setPrompt = function(prompt, length) { 152 | this._prompt = prompt; 153 | if (length) { 154 | this._promptLength = length; 155 | } else { 156 | var lines = prompt.split(/[\r\n]/); 157 | var lastLine = lines[lines.length - 1]; 158 | this._promptLength = lastLine.length; 159 | } 160 | }; 161 | 162 | 163 | Interface.prototype._setRawMode = function(mode) { 164 | if (typeof this.input.setRawMode === 'function') { 165 | return this.input.setRawMode(mode); 166 | } 167 | }; 168 | 169 | 170 | Interface.prototype.prompt = function(preserveCursor) { 171 | if (this.paused) this.resume(); 172 | if (this.terminal) { 173 | if (!preserveCursor) this.cursor = 0; 174 | this._refreshLine(); 175 | } else { 176 | this.output.write(this._prompt); 177 | } 178 | }; 179 | 180 | 181 | Interface.prototype.question = function(query, cb) { 182 | if (typeof cb === 'function') { 183 | if (this._questionCallback) { 184 | this.prompt(); 185 | } else { 186 | this._oldPrompt = this._prompt; 187 | this.setPrompt(query); 188 | this._questionCallback = cb; 189 | this.prompt(); 190 | } 191 | } 192 | }; 193 | 194 | 195 | Interface.prototype._onLine = function(line) { 196 | if (this._questionCallback) { 197 | var cb = this._questionCallback; 198 | this._questionCallback = null; 199 | this.setPrompt(this._oldPrompt); 200 | cb(line); 201 | } else { 202 | this.emit('line', line); 203 | } 204 | }; 205 | 206 | 207 | Interface.prototype._addHistory = function() { 208 | if (this.line.length === 0) return ''; 209 | 210 | if (this.history.length === 0 || this.history[0] !== this.line) { 211 | this.history.unshift(this.line); 212 | 213 | // Only store so many 214 | if (this.history.length > kHistorySize) this.history.pop(); 215 | } 216 | 217 | this.historyIndex = -1; 218 | return this.history[0]; 219 | }; 220 | 221 | 222 | Interface.prototype._refreshLine = function() { 223 | var columns = this.columns; 224 | 225 | // line length 226 | var line = this._prompt + this.line; 227 | var lineLength = line.length; 228 | var lineCols = lineLength % columns; 229 | var lineRows = (lineLength - lineCols) / columns; 230 | 231 | // cursor position 232 | var cursorPos = this._getCursorPos(); 233 | 234 | // first move to the bottom of the current line, based on cursor pos 235 | var prevRows = this.prevRows || 0; 236 | if (prevRows > 0) { 237 | exports.moveCursor(this.output, 0, -prevRows); 238 | } 239 | 240 | // Cursor to left edge. 241 | exports.cursorTo(this.output, 0); 242 | // erase data 243 | exports.clearScreenDown(this.output); 244 | 245 | // Write the prompt and the current buffer content. 246 | this.output.write(line); 247 | 248 | // Force terminal to allocate a new line 249 | if (lineCols === 0) { 250 | this.output.write(' '); 251 | } 252 | 253 | // Move cursor to original position. 254 | exports.cursorTo(this.output, cursorPos.cols); 255 | 256 | var diff = lineRows - cursorPos.rows; 257 | if (diff > 0) { 258 | exports.moveCursor(this.output, 0, -diff); 259 | } 260 | 261 | this.prevRows = cursorPos.rows; 262 | }; 263 | 264 | 265 | Interface.prototype.close = function() { 266 | if (this.closed) return; 267 | if (this.terminal) { 268 | this._setRawMode(false); 269 | } 270 | this.pause(); 271 | this.closed = true; 272 | this.emit('close'); 273 | }; 274 | 275 | 276 | Interface.prototype.pause = function() { 277 | if (this.paused) return; 278 | this.input.pause(); 279 | this.paused = true; 280 | this.emit('pause'); 281 | }; 282 | 283 | 284 | Interface.prototype.resume = function() { 285 | if (!this.paused) return; 286 | this.input.resume(); 287 | this.paused = false; 288 | this.emit('resume'); 289 | }; 290 | 291 | 292 | Interface.prototype.write = function(d, key) { 293 | if (this.paused) this.resume(); 294 | this.terminal ? this._ttyWrite(d, key) : this._normalWrite(d); 295 | }; 296 | 297 | // \r\n, \n, or \r followed by something other than \n 298 | var lineEnding = /\r?\n|\r(?!\n)/; 299 | Interface.prototype._normalWrite = function(b) { 300 | if (b === undefined) { 301 | return; 302 | } 303 | var string = this._decoder.write(b); 304 | if (this._sawReturn) { 305 | string = string.replace(/^\n/, ''); 306 | this._sawReturn = false; 307 | } 308 | 309 | if (this._line_buffer) { 310 | string = this._line_buffer + string; 311 | this._line_buffer = null; 312 | } 313 | if (lineEnding.test(string)) { 314 | this._sawReturn = /\r$/.test(string); 315 | 316 | // got one or more newlines; process into "line" events 317 | var lines = string.split(lineEnding); 318 | // either '' or (concievably) the unfinished portion of the next line 319 | string = lines.pop(); 320 | this._line_buffer = string; 321 | lines.forEach(function(line) { 322 | this._onLine(line); 323 | }, this); 324 | } else if (string) { 325 | // no newlines this time, save what we have for next time 326 | this._line_buffer = string; 327 | } 328 | }; 329 | 330 | Interface.prototype._insertString = function(c) { 331 | //BUG: Problem when adding tabs with following content. 332 | // Perhaps the bug is in _refreshLine(). Not sure. 333 | // A hack would be to insert spaces instead of literal '\t'. 334 | if (this.cursor < this.line.length) { 335 | var beg = this.line.slice(0, this.cursor); 336 | var end = this.line.slice(this.cursor, this.line.length); 337 | this.line = beg + c + end; 338 | this.cursor += c.length; 339 | this._refreshLine(); 340 | } else { 341 | this.line += c; 342 | this.cursor += c.length; 343 | 344 | if (this._getCursorPos().cols === 0) { 345 | this._refreshLine(); 346 | } else { 347 | this.output.write(c); 348 | } 349 | 350 | // a hack to get the line refreshed if it's needed 351 | this._moveCursor(0); 352 | } 353 | }; 354 | 355 | Interface.prototype._tabComplete = function() { 356 | var self = this; 357 | 358 | self.pause(); 359 | self.completer(self.line.slice(0, self.cursor), function(err, rv) { 360 | self.resume(); 361 | 362 | if (err) { 363 | // XXX Log it somewhere? 364 | return; 365 | } 366 | 367 | var completions = rv[0], 368 | completeOn = rv[1]; // the text that was completed 369 | if (completions && completions.length) { 370 | // Apply/show completions. 371 | if (completions.length === 1) { 372 | self._insertString(completions[0].slice(completeOn.length)); 373 | } else { 374 | self.output.write('\r\n'); 375 | var width = completions.reduce(function(a, b) { 376 | return a.length > b.length ? a : b; 377 | }).length + 2; // 2 space padding 378 | var maxColumns = Math.floor(self.columns / width) || 1; 379 | var group = [], c; 380 | for (var i = 0, compLen = completions.length; i < compLen; i++) { 381 | c = completions[i]; 382 | if (c === '') { 383 | handleGroup(self, group, width, maxColumns); 384 | group = []; 385 | } else { 386 | group.push(c); 387 | } 388 | } 389 | handleGroup(self, group, width, maxColumns); 390 | 391 | // If there is a common prefix to all matches, then apply that 392 | // portion. 393 | var f = completions.filter(function(e) { if (e) return e; }); 394 | var prefix = commonPrefix(f); 395 | if (prefix.length > completeOn.length) { 396 | self._insertString(prefix.slice(completeOn.length)); 397 | } 398 | 399 | } 400 | self._refreshLine(); 401 | } 402 | }); 403 | }; 404 | 405 | // this = Interface instance 406 | function handleGroup(self, group, width, maxColumns) { 407 | if (group.length == 0) { 408 | return; 409 | } 410 | var minRows = Math.ceil(group.length / maxColumns); 411 | for (var row = 0; row < minRows; row++) { 412 | for (var col = 0; col < maxColumns; col++) { 413 | var idx = row * maxColumns + col; 414 | if (idx >= group.length) { 415 | break; 416 | } 417 | var item = group[idx]; 418 | self.output.write(item); 419 | if (col < maxColumns - 1) { 420 | for (var s = 0, itemLen = item.length; s < width - itemLen; 421 | s++) { 422 | self.output.write(' '); 423 | } 424 | } 425 | } 426 | self.output.write('\r\n'); 427 | } 428 | self.output.write('\r\n'); 429 | } 430 | 431 | function commonPrefix(strings) { 432 | if (!strings || strings.length == 0) { 433 | return ''; 434 | } 435 | var sorted = strings.slice().sort(); 436 | var min = sorted[0]; 437 | var max = sorted[sorted.length - 1]; 438 | for (var i = 0, len = min.length; i < len; i++) { 439 | if (min[i] != max[i]) { 440 | return min.slice(0, i); 441 | } 442 | } 443 | return min; 444 | } 445 | 446 | 447 | Interface.prototype._wordLeft = function() { 448 | if (this.cursor > 0) { 449 | var leading = this.line.slice(0, this.cursor); 450 | var match = leading.match(/([^\w\s]+|\w+|)\s*$/); 451 | this._moveCursor(-match[0].length); 452 | } 453 | }; 454 | 455 | 456 | Interface.prototype._wordRight = function() { 457 | if (this.cursor < this.line.length) { 458 | var trailing = this.line.slice(this.cursor); 459 | var match = trailing.match(/^(\s+|\W+|\w+)\s*/); 460 | this._moveCursor(match[0].length); 461 | } 462 | }; 463 | 464 | 465 | Interface.prototype._deleteLeft = function() { 466 | if (this.cursor > 0 && this.line.length > 0) { 467 | this.line = this.line.slice(0, this.cursor - 1) + 468 | this.line.slice(this.cursor, this.line.length); 469 | 470 | this.cursor--; 471 | this._refreshLine(); 472 | } 473 | }; 474 | 475 | 476 | Interface.prototype._deleteRight = function() { 477 | this.line = this.line.slice(0, this.cursor) + 478 | this.line.slice(this.cursor + 1, this.line.length); 479 | this._refreshLine(); 480 | }; 481 | 482 | 483 | Interface.prototype._deleteWordLeft = function() { 484 | if (this.cursor > 0) { 485 | var leading = this.line.slice(0, this.cursor); 486 | var match = leading.match(/([^\w\s]+|\w+|)\s*$/); 487 | leading = leading.slice(0, leading.length - match[0].length); 488 | this.line = leading + this.line.slice(this.cursor, this.line.length); 489 | this.cursor = leading.length; 490 | this._refreshLine(); 491 | } 492 | }; 493 | 494 | 495 | Interface.prototype._deleteWordRight = function() { 496 | if (this.cursor < this.line.length) { 497 | var trailing = this.line.slice(this.cursor); 498 | var match = trailing.match(/^(\s+|\W+|\w+)\s*/); 499 | this.line = this.line.slice(0, this.cursor) + 500 | trailing.slice(match[0].length); 501 | this._refreshLine(); 502 | } 503 | }; 504 | 505 | 506 | Interface.prototype._deleteLineLeft = function() { 507 | this.line = this.line.slice(this.cursor); 508 | this.cursor = 0; 509 | this._refreshLine(); 510 | }; 511 | 512 | 513 | Interface.prototype._deleteLineRight = function() { 514 | this.line = this.line.slice(0, this.cursor); 515 | this._refreshLine(); 516 | }; 517 | 518 | 519 | Interface.prototype.clearLine = function() { 520 | this._moveCursor(+Infinity); 521 | this.output.write('\r\n'); 522 | this.line = ''; 523 | this.cursor = 0; 524 | this.prevRows = 0; 525 | }; 526 | 527 | 528 | Interface.prototype._line = function() { 529 | var line = this._addHistory(); 530 | this.clearLine(); 531 | this._onLine(line); 532 | }; 533 | 534 | 535 | Interface.prototype._historyNext = function() { 536 | if (this.historyIndex > 0) { 537 | this.historyIndex--; 538 | this.line = this.history[this.historyIndex]; 539 | this.cursor = this.line.length; // set cursor to end of line. 540 | this._refreshLine(); 541 | 542 | } else if (this.historyIndex === 0) { 543 | this.historyIndex = -1; 544 | this.cursor = 0; 545 | this.line = ''; 546 | this._refreshLine(); 547 | } 548 | }; 549 | 550 | 551 | Interface.prototype._historyPrev = function() { 552 | if (this.historyIndex + 1 < this.history.length) { 553 | this.historyIndex++; 554 | this.line = this.history[this.historyIndex]; 555 | this.cursor = this.line.length; // set cursor to end of line. 556 | 557 | this._refreshLine(); 558 | } 559 | }; 560 | 561 | 562 | // Returns current cursor's position and line 563 | Interface.prototype._getCursorPos = function() { 564 | var columns = this.columns; 565 | var cursorPos = this.cursor + this._promptLength; 566 | var cols = cursorPos % columns; 567 | var rows = (cursorPos - cols) / columns; 568 | return {cols: cols, rows: rows}; 569 | }; 570 | 571 | 572 | // This function moves cursor dx places to the right 573 | // (-dx for left) and refreshes the line if it is needed 574 | Interface.prototype._moveCursor = function(dx) { 575 | var oldcursor = this.cursor; 576 | var oldPos = this._getCursorPos(); 577 | this.cursor += dx; 578 | 579 | // bounds check 580 | if (this.cursor < 0) this.cursor = 0; 581 | if (this.cursor > this.line.length) this.cursor = this.line.length; 582 | 583 | var newPos = this._getCursorPos(); 584 | 585 | // check if cursors are in the same line 586 | if (oldPos.rows === newPos.rows) { 587 | exports.moveCursor(this.output, this.cursor - oldcursor, 0); 588 | this.prevRows = newPos.rows; 589 | } else { 590 | this._refreshLine(); 591 | } 592 | }; 593 | 594 | 595 | // handle a write from the tty 596 | Interface.prototype._ttyWrite = function(s, key) { 597 | key = key || {}; 598 | 599 | // Ignore escape key - Fixes #2876 600 | if (key.name == 'escape') return; 601 | 602 | if (key.ctrl && key.shift) { 603 | /* Control and shift pressed */ 604 | switch (key.name) { 605 | case 'backspace': 606 | this._deleteLineLeft(); 607 | break; 608 | 609 | case 'delete': 610 | this._deleteLineRight(); 611 | break; 612 | } 613 | 614 | } else if (key.ctrl) { 615 | /* Control key pressed */ 616 | 617 | switch (key.name) { 618 | case 'c': 619 | if (this.listeners('SIGINT').length) { 620 | this.emit('SIGINT'); 621 | } else { 622 | // This readline instance is finished 623 | this.close(); 624 | } 625 | break; 626 | 627 | case 'h': // delete left 628 | this._deleteLeft(); 629 | break; 630 | 631 | case 'd': // delete right or EOF 632 | if (this.cursor === 0 && this.line.length === 0) { 633 | // This readline instance is finished 634 | this.close(); 635 | } else if (this.cursor < this.line.length) { 636 | this._deleteRight(); 637 | } 638 | break; 639 | 640 | case 'u': // delete the whole line 641 | this.cursor = 0; 642 | this.line = ''; 643 | this._refreshLine(); 644 | break; 645 | 646 | case 'k': // delete from current to end of line 647 | this._deleteLineRight(); 648 | break; 649 | 650 | case 'a': // go to the start of the line 651 | this._moveCursor(-Infinity); 652 | break; 653 | 654 | case 'e': // go to the end of the line 655 | this._moveCursor(+Infinity); 656 | break; 657 | 658 | case 'b': // back one character 659 | this._moveCursor(-1); 660 | break; 661 | 662 | case 'f': // forward one character 663 | this._moveCursor(+1); 664 | break; 665 | 666 | case 'n': // next history item 667 | this._historyNext(); 668 | break; 669 | 670 | case 'p': // previous history item 671 | this._historyPrev(); 672 | break; 673 | 674 | case 'z': 675 | if (process.platform == 'win32') break; 676 | if (this.listeners('SIGTSTP').length) { 677 | this.emit('SIGTSTP'); 678 | } else { 679 | process.once('SIGCONT', (function(self) { 680 | return function() { 681 | // Don't raise events if stream has already been abandoned. 682 | if (!self.paused) { 683 | // Stream must be paused and resumed after SIGCONT to catch 684 | // SIGINT, SIGTSTP, and EOF. 685 | self.pause(); 686 | self.emit('SIGCONT'); 687 | } 688 | // explictly re-enable "raw mode" and move the cursor to 689 | // the correct position. 690 | // See https://github.com/joyent/node/issues/3295. 691 | self._setRawMode(true); 692 | self._refreshLine(); 693 | }; 694 | })(this)); 695 | this._setRawMode(false); 696 | process.kill(process.pid, 'SIGTSTP'); 697 | } 698 | break; 699 | 700 | case 'w': // delete backwards to a word boundary 701 | case 'backspace': 702 | this._deleteWordLeft(); 703 | break; 704 | 705 | case 'delete': // delete forward to a word boundary 706 | this._deleteWordRight(); 707 | break; 708 | 709 | case 'backspace': 710 | this._deleteWordLeft(); 711 | break; 712 | 713 | case 'left': 714 | this._wordLeft(); 715 | break; 716 | 717 | case 'right': 718 | this._wordRight(); 719 | break; 720 | } 721 | 722 | } else if (key.meta) { 723 | /* Meta key pressed */ 724 | 725 | switch (key.name) { 726 | case 'b': // backward word 727 | this._wordLeft(); 728 | break; 729 | 730 | case 'f': // forward word 731 | this._wordRight(); 732 | break; 733 | 734 | case 'd': // delete forward word 735 | case 'delete': 736 | this._deleteWordRight(); 737 | break; 738 | 739 | case 'backspace': // delete backwards to a word boundary 740 | this._deleteWordLeft(); 741 | break; 742 | } 743 | 744 | } else { 745 | /* No modifier keys used */ 746 | 747 | // \r bookkeeping is only relevant if a \n comes right after. 748 | if (this._sawReturn && key.name !== 'enter') 749 | this._sawReturn = false; 750 | 751 | switch (key.name) { 752 | case 'return': // carriage return, i.e. \r 753 | this._sawReturn = true; 754 | this._line(); 755 | break; 756 | 757 | case 'enter': 758 | if (this._sawReturn) 759 | this._sawReturn = false; 760 | else 761 | this._line(); 762 | break; 763 | 764 | case 'backspace': 765 | this._deleteLeft(); 766 | break; 767 | 768 | case 'delete': 769 | this._deleteRight(); 770 | break; 771 | 772 | case 'tab': // tab completion 773 | this._tabComplete(); 774 | break; 775 | 776 | case 'left': 777 | this._moveCursor(-1); 778 | break; 779 | 780 | case 'right': 781 | this._moveCursor(+1); 782 | break; 783 | 784 | case 'home': 785 | this._moveCursor(-Infinity); 786 | break; 787 | 788 | case 'end': 789 | this._moveCursor(+Infinity); 790 | break; 791 | 792 | case 'up': 793 | this._historyPrev(); 794 | break; 795 | 796 | case 'down': 797 | this._historyNext(); 798 | break; 799 | 800 | default: 801 | if (Buffer.isBuffer(s)) 802 | s = s.toString('utf-8'); 803 | 804 | if (s) { 805 | var lines = s.split(/\r\n|\n|\r/); 806 | for (var i = 0, len = lines.length; i < len; i++) { 807 | if (i > 0) { 808 | this._line(); 809 | } 810 | this._insertString(lines[i]); 811 | } 812 | } 813 | } 814 | } 815 | }; 816 | 817 | 818 | exports.Interface = Interface; 819 | 820 | 821 | 822 | /** 823 | * accepts a readable Stream instance and makes it emit "keypress" events 824 | */ 825 | 826 | function emitKeypressEvents(stream) { 827 | if (stream._keypressDecoder) return; 828 | var StringDecoder = require('string_decoder').StringDecoder; // lazy load 829 | stream._keypressDecoder = new StringDecoder('utf8'); 830 | 831 | function onData(b) { 832 | if (stream.listeners('keypress').length > 0) { 833 | var r = stream._keypressDecoder.write(b); 834 | if (r) emitKey(stream, r); 835 | } else { 836 | // Nobody's watching anyway 837 | stream.removeListener('data', onData); 838 | stream.on('newListener', onNewListener); 839 | } 840 | } 841 | 842 | function onNewListener(event) { 843 | if (event == 'keypress') { 844 | stream.on('data', onData); 845 | stream.removeListener('newListener', onNewListener); 846 | } 847 | } 848 | 849 | if (stream.listeners('keypress').length > 0) { 850 | stream.on('data', onData); 851 | } else { 852 | stream.on('newListener', onNewListener); 853 | } 854 | } 855 | exports.emitKeypressEvents = emitKeypressEvents; 856 | 857 | /* 858 | Some patterns seen in terminal key escape codes, derived from combos seen 859 | at http://www.midnight-commander.org/browser/lib/tty/key.c 860 | 861 | ESC letter 862 | ESC [ letter 863 | ESC [ modifier letter 864 | ESC [ 1 ; modifier letter 865 | ESC [ num char 866 | ESC [ num ; modifier char 867 | ESC O letter 868 | ESC O modifier letter 869 | ESC O 1 ; modifier letter 870 | ESC N letter 871 | ESC [ [ num ; modifier char 872 | ESC [ [ 1 ; modifier letter 873 | ESC ESC [ num char 874 | ESC ESC O letter 875 | 876 | - char is usually ~ but $ and ^ also happen with rxvt 877 | - modifier is 1 + 878 | (shift * 1) + 879 | (left_alt * 2) + 880 | (ctrl * 4) + 881 | (right_alt * 8) 882 | - two leading ESCs apparently mean the same as one leading ESC 883 | */ 884 | 885 | // Regexes used for ansi escape code splitting 886 | var metaKeyCodeRe = /^(?:\x1b)([a-zA-Z0-9])$/; 887 | var functionKeyCodeRe = 888 | /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/; 889 | 890 | function emitKey(stream, s) { 891 | var ch, 892 | key = { 893 | name: undefined, 894 | ctrl: false, 895 | meta: false, 896 | shift: false 897 | }, 898 | parts; 899 | 900 | if (Buffer.isBuffer(s)) { 901 | if (s[0] > 127 && s[1] === undefined) { 902 | s[0] -= 128; 903 | s = '\x1b' + s.toString(stream.encoding || 'utf-8'); 904 | } else { 905 | s = s.toString(stream.encoding || 'utf-8'); 906 | } 907 | } 908 | 909 | key.sequence = s; 910 | 911 | if (s === '\r') { 912 | // carriage return 913 | key.name = 'return'; 914 | 915 | } else if (s === '\n') { 916 | // enter, should have been called linefeed 917 | key.name = 'enter'; 918 | 919 | } else if (s === '\t') { 920 | // tab 921 | key.name = 'tab'; 922 | 923 | } else if (s === '\b' || s === '\x7f' || 924 | s === '\x1b\x7f' || s === '\x1b\b') { 925 | // backspace or ctrl+h 926 | key.name = 'backspace'; 927 | key.meta = (s.charAt(0) === '\x1b'); 928 | 929 | } else if (s === '\x1b' || s === '\x1b\x1b') { 930 | // escape key 931 | key.name = 'escape'; 932 | key.meta = (s.length === 2); 933 | 934 | } else if (s === ' ' || s === '\x1b ') { 935 | key.name = 'space'; 936 | key.meta = (s.length === 2); 937 | 938 | } else if (s <= '\x1a') { 939 | // ctrl+letter 940 | key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1); 941 | key.ctrl = true; 942 | 943 | } else if (s.length === 1 && s >= 'a' && s <= 'z') { 944 | // lowercase letter 945 | key.name = s; 946 | 947 | } else if (s.length === 1 && s >= 'A' && s <= 'Z') { 948 | // shift+letter 949 | key.name = s.toLowerCase(); 950 | key.shift = true; 951 | 952 | } else if (parts = metaKeyCodeRe.exec(s)) { 953 | // meta+character key 954 | key.name = parts[1].toLowerCase(); 955 | key.meta = true; 956 | key.shift = /^[A-Z]$/.test(parts[1]); 957 | 958 | } else if (parts = functionKeyCodeRe.exec(s)) { 959 | // ansi escape sequence 960 | 961 | // reassemble the key code leaving out leading \x1b's, 962 | // the modifier key bitflag and any meaningless "1;" sequence 963 | var code = (parts[1] || '') + (parts[2] || '') + 964 | (parts[4] || '') + (parts[6] || ''), 965 | modifier = (parts[3] || parts[5] || 1) - 1; 966 | 967 | // Parse the key modifier 968 | key.ctrl = !!(modifier & 4); 969 | key.meta = !!(modifier & 10); 970 | key.shift = !!(modifier & 1); 971 | key.code = code; 972 | 973 | // Parse the key itself 974 | switch (code) { 975 | /* xterm/gnome ESC O letter */ 976 | case 'OP': key.name = 'f1'; break; 977 | case 'OQ': key.name = 'f2'; break; 978 | case 'OR': key.name = 'f3'; break; 979 | case 'OS': key.name = 'f4'; break; 980 | 981 | /* xterm/rxvt ESC [ number ~ */ 982 | case '[11~': key.name = 'f1'; break; 983 | case '[12~': key.name = 'f2'; break; 984 | case '[13~': key.name = 'f3'; break; 985 | case '[14~': key.name = 'f4'; break; 986 | 987 | /* from Cygwin and used in libuv */ 988 | case '[[A': key.name = 'f1'; break; 989 | case '[[B': key.name = 'f2'; break; 990 | case '[[C': key.name = 'f3'; break; 991 | case '[[D': key.name = 'f4'; break; 992 | case '[[E': key.name = 'f5'; break; 993 | 994 | /* common */ 995 | case '[15~': key.name = 'f5'; break; 996 | case '[17~': key.name = 'f6'; break; 997 | case '[18~': key.name = 'f7'; break; 998 | case '[19~': key.name = 'f8'; break; 999 | case '[20~': key.name = 'f9'; break; 1000 | case '[21~': key.name = 'f10'; break; 1001 | case '[23~': key.name = 'f11'; break; 1002 | case '[24~': key.name = 'f12'; break; 1003 | 1004 | /* xterm ESC [ letter */ 1005 | case '[A': key.name = 'up'; break; 1006 | case '[B': key.name = 'down'; break; 1007 | case '[C': key.name = 'right'; break; 1008 | case '[D': key.name = 'left'; break; 1009 | case '[E': key.name = 'clear'; break; 1010 | case '[F': key.name = 'end'; break; 1011 | case '[H': key.name = 'home'; break; 1012 | 1013 | /* xterm/gnome ESC O letter */ 1014 | case 'OA': key.name = 'up'; break; 1015 | case 'OB': key.name = 'down'; break; 1016 | case 'OC': key.name = 'right'; break; 1017 | case 'OD': key.name = 'left'; break; 1018 | case 'OE': key.name = 'clear'; break; 1019 | case 'OF': key.name = 'end'; break; 1020 | case 'OH': key.name = 'home'; break; 1021 | 1022 | /* xterm/rxvt ESC [ number ~ */ 1023 | case '[1~': key.name = 'home'; break; 1024 | case '[2~': key.name = 'insert'; break; 1025 | case '[3~': key.name = 'delete'; break; 1026 | case '[4~': key.name = 'end'; break; 1027 | case '[5~': key.name = 'pageup'; break; 1028 | case '[6~': key.name = 'pagedown'; break; 1029 | 1030 | /* putty */ 1031 | case '[[5~': key.name = 'pageup'; break; 1032 | case '[[6~': key.name = 'pagedown'; break; 1033 | 1034 | /* rxvt */ 1035 | case '[7~': key.name = 'home'; break; 1036 | case '[8~': key.name = 'end'; break; 1037 | 1038 | /* rxvt keys with modifiers */ 1039 | case '[a': key.name = 'up'; key.shift = true; break; 1040 | case '[b': key.name = 'down'; key.shift = true; break; 1041 | case '[c': key.name = 'right'; key.shift = true; break; 1042 | case '[d': key.name = 'left'; key.shift = true; break; 1043 | case '[e': key.name = 'clear'; key.shift = true; break; 1044 | 1045 | case '[2$': key.name = 'insert'; key.shift = true; break; 1046 | case '[3$': key.name = 'delete'; key.shift = true; break; 1047 | case '[5$': key.name = 'pageup'; key.shift = true; break; 1048 | case '[6$': key.name = 'pagedown'; key.shift = true; break; 1049 | case '[7$': key.name = 'home'; key.shift = true; break; 1050 | case '[8$': key.name = 'end'; key.shift = true; break; 1051 | 1052 | case 'Oa': key.name = 'up'; key.ctrl = true; break; 1053 | case 'Ob': key.name = 'down'; key.ctrl = true; break; 1054 | case 'Oc': key.name = 'right'; key.ctrl = true; break; 1055 | case 'Od': key.name = 'left'; key.ctrl = true; break; 1056 | case 'Oe': key.name = 'clear'; key.ctrl = true; break; 1057 | 1058 | case '[2^': key.name = 'insert'; key.ctrl = true; break; 1059 | case '[3^': key.name = 'delete'; key.ctrl = true; break; 1060 | case '[5^': key.name = 'pageup'; key.ctrl = true; break; 1061 | case '[6^': key.name = 'pagedown'; key.ctrl = true; break; 1062 | case '[7^': key.name = 'home'; key.ctrl = true; break; 1063 | case '[8^': key.name = 'end'; key.ctrl = true; break; 1064 | 1065 | /* misc. */ 1066 | case '[Z': key.name = 'tab'; key.shift = true; break; 1067 | default: key.name = 'undefined'; break; 1068 | 1069 | } 1070 | } else if (s.length > 1 && s[0] !== '\x1b') { 1071 | // Got a longer-than-one string of characters. 1072 | // Probably a paste, since it wasn't a control sequence. 1073 | Array.prototype.forEach.call(s, function(c) { 1074 | emitKey(stream, c); 1075 | }); 1076 | return; 1077 | } 1078 | 1079 | // Don't emit a key if no name was found 1080 | if (key.name === undefined) { 1081 | key = undefined; 1082 | } 1083 | 1084 | if (s.length === 1) { 1085 | ch = s; 1086 | } 1087 | 1088 | if (key || ch) { 1089 | stream.emit('keypress', ch, key); 1090 | } 1091 | } 1092 | 1093 | 1094 | /** 1095 | * moves the cursor to the x and y coordinate on the given stream 1096 | */ 1097 | 1098 | function cursorTo(stream, x, y) { 1099 | if (typeof x !== 'number' && typeof y !== 'number') 1100 | return; 1101 | 1102 | if (typeof x !== 'number') 1103 | throw new Error("Can't set cursor row without also setting it's column"); 1104 | 1105 | if (typeof y !== 'number') { 1106 | stream.write('\x1b[' + (x + 1) + 'G'); 1107 | } else { 1108 | stream.write('\x1b[' + (y + 1) + ';' + (x + 1) + 'H'); 1109 | } 1110 | } 1111 | exports.cursorTo = cursorTo; 1112 | 1113 | 1114 | /** 1115 | * moves the cursor relative to its current location 1116 | */ 1117 | 1118 | function moveCursor(stream, dx, dy) { 1119 | if (dx < 0) { 1120 | stream.write('\x1b[' + (-dx) + 'D'); 1121 | } else if (dx > 0) { 1122 | stream.write('\x1b[' + dx + 'C'); 1123 | } 1124 | 1125 | if (dy < 0) { 1126 | stream.write('\x1b[' + (-dy) + 'A'); 1127 | } else if (dy > 0) { 1128 | stream.write('\x1b[' + dy + 'B'); 1129 | } 1130 | } 1131 | exports.moveCursor = moveCursor; 1132 | 1133 | 1134 | /** 1135 | * clears the current line the cursor is on: 1136 | * -1 for left of the cursor 1137 | * +1 for right of the cursor 1138 | * 0 for the entire line 1139 | */ 1140 | 1141 | function clearLine(stream, dir) { 1142 | if (dir < 0) { 1143 | // to the beginning 1144 | stream.write('\x1b[1K'); 1145 | } else if (dir > 0) { 1146 | // to the end 1147 | stream.write('\x1b[0K'); 1148 | } else { 1149 | // entire line 1150 | stream.write('\x1b[2K'); 1151 | } 1152 | } 1153 | exports.clearLine = clearLine; 1154 | 1155 | 1156 | /** 1157 | * clears the screen from the current position of the cursor down 1158 | */ 1159 | 1160 | function clearScreenDown(stream) { 1161 | stream.write('\x1b[0J'); 1162 | } 1163 | exports.clearScreenDown = clearScreenDown; 1164 | -------------------------------------------------------------------------------- /bin/tss: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./tss.js') 3 | -------------------------------------------------------------------------------- /bin/tss.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Claus Reinke. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. 3 | // See LICENSE.txt in the project root for complete license information. 4 | "use strict"; 5 | /// 6 | var ts = require("typescript"); 7 | var harness = require("./harness"); 8 | var path = require("path"); 9 | function resolvePath(rpath) { 10 | return switchToForwardSlashes(path.resolve(rpath)); 11 | } 12 | function switchToForwardSlashes(path) { 13 | return path.replace(/\\/g, "/"); 14 | } 15 | // bypass import, we don't want to drop out of the global module; 16 | // use fixed readline (https://github.com/joyent/node/issues/3305), 17 | // fixed version should be in nodejs from about v0.9.9/v0.8.19? 18 | var readline = require("./readline"); 19 | var EOL = require("os").EOL; 20 | /** holds list of fileNames, ScriptInfos and ScriptSnapshots for LS host */ 21 | var FileCache = (function () { 22 | function FileCache() { 23 | this.fileNames = []; 24 | this.snapshots = {}; 25 | this.fileNameToScript = {}; 26 | } 27 | FileCache.prototype.getFileNames = function () { return this.fileNames; }; 28 | /** 29 | * @param fileName resolved name of possibly cached file 30 | */ 31 | FileCache.prototype.getScriptInfo = function (fileName) { 32 | if (!this.fileNameToScript[fileName]) { 33 | this.fetchFile(fileName); 34 | } 35 | return this.fileNameToScript[fileName]; 36 | }; 37 | /** 38 | * @param fileName resolved name of possibly cached file 39 | */ 40 | FileCache.prototype.getScriptSnapshot = function (fileName) { 41 | // console.log("getScriptSnapshot",fileName); 42 | if (!this.snapshots[fileName]) { 43 | this.fetchFile(fileName); 44 | } 45 | return this.snapshots[fileName]; 46 | }; 47 | /** 48 | * @param fileName resolved file name 49 | * @param text file contents 50 | * @param isDefaultLib should fileName be listed first? 51 | */ 52 | FileCache.prototype.addFile = function (fileName, text, isDefaultLib) { 53 | if (isDefaultLib === void 0) { isDefaultLib = false; } 54 | if (isDefaultLib) { 55 | this.fileNames.push(fileName); 56 | } 57 | else { 58 | this.fileNames.unshift(fileName); 59 | } 60 | this.fileNameToScript[fileName] = new harness.ScriptInfo(fileName, text); 61 | this.snapshots[fileName] = new harness.ScriptSnapshot(this.getScriptInfo(fileName)); 62 | }; 63 | /** 64 | * @param fileName resolved file name 65 | */ 66 | FileCache.prototype.fetchFile = function (fileName) { 67 | // console.log("fetchFile:",fileName); 68 | if (ts.sys.fileExists(fileName)) { 69 | this.addFile(fileName, ts.sys.readFile(fileName)); 70 | } 71 | else { 72 | } 73 | }; 74 | /** 75 | * @param fileName resolved name of cached file 76 | * @param line 1 based index 77 | * @param col 1 based index 78 | */ 79 | FileCache.prototype.lineColToPosition = function (fileName, line, col) { 80 | var script = this.getScriptInfo(fileName); 81 | return ts.getPositionOfLineAndCharacter(this.ls.getSourceFile(fileName), line - 1, col - 1); 82 | }; 83 | /** 84 | * @param fileName resolved name of cached file 85 | * @returns {line,character} 1 based indices 86 | */ 87 | FileCache.prototype.positionToLineCol = function (fileName, position) { 88 | var script = this.getScriptInfo(fileName); 89 | var lineChar = ts.getLineAndCharacterOfPosition(this.ls.getSourceFile(fileName), position); 90 | return { line: lineChar.line + 1, character: lineChar.character + 1 }; 91 | }; 92 | /** 93 | * @param fileName resolved name of cached file 94 | * @param line 1 based index 95 | */ 96 | FileCache.prototype.getLineText = function (fileName, line) { 97 | var source = this.ls.getSourceFile(fileName); 98 | var lineStart = ts.getPositionOfLineAndCharacter(source, line - 1, 0); 99 | var lineEnd = ts.getPositionOfLineAndCharacter(source, line, 0) - 1; 100 | var lineText = source.text.substring(lineStart, lineEnd); 101 | return lineText; 102 | }; 103 | /** 104 | * @param fileName resolved name of possibly cached file 105 | * @param content new file contents 106 | */ 107 | FileCache.prototype.updateScript = function (fileName, content) { 108 | var script = this.getScriptInfo(fileName); 109 | if (script) { 110 | script.updateContent(content); 111 | this.snapshots[fileName] = new harness.ScriptSnapshot(script); 112 | } 113 | else { 114 | this.addFile(fileName, content); 115 | } 116 | }; 117 | /** 118 | * @param fileName resolved name of cached file 119 | * @param minChar first char of edit range 120 | * @param limChar first char after edit range 121 | * @param newText new file contents 122 | */ 123 | FileCache.prototype.editScript = function (fileName, minChar, limChar, newText) { 124 | var script = this.getScriptInfo(fileName); 125 | if (script) { 126 | script.editContent(minChar, limChar, newText); 127 | this.snapshots[fileName] = new harness.ScriptSnapshot(script); 128 | return; 129 | } 130 | throw new Error("No script with name '" + fileName + "'"); 131 | }; 132 | return FileCache; 133 | }()); 134 | /** TypeScript Services Server, 135 | an interactive commandline tool 136 | for getting info on .ts projects */ 137 | var TSS = (function () { 138 | function TSS(prettyJSON) { 139 | if (prettyJSON === void 0) { prettyJSON = false; } 140 | this.prettyJSON = prettyJSON; 141 | } // NOTE: call setup 142 | /** collect syntactic and semantic diagnostics for all project files */ 143 | TSS.prototype.getErrors = function () { 144 | var _this = this; 145 | var addPhase = function (phase) { return function (d) { d.phase = phase; return d; }; }; 146 | var errors = []; 147 | this.fileCache.getFileNames().map(function (file) { 148 | if (!file.match(/^.*(\.ts)$/)) 149 | return; // exclude package.json 150 | var syntactic = _this.ls.getSyntacticDiagnostics(file); 151 | var semantic = _this.ls.getSemanticDiagnostics(file); 152 | // this.ls.languageService.getEmitOutput(file).diagnostics; 153 | errors = errors.concat(syntactic.map(addPhase("Syntax")), semantic.map(addPhase("Semantics"))); 154 | }); 155 | return errors; 156 | }; 157 | /** flatten messageChain into string|string[] */ 158 | TSS.prototype.messageChain = function (message) { 159 | if (typeof message === "string") { 160 | return [message]; 161 | } 162 | else { 163 | return [message.messageText].concat(message.next ? this.messageChain(message.next) : []); 164 | } 165 | }; 166 | /** load file and dependencies, prepare language service for queries */ 167 | TSS.prototype.setup = function (files, options) { 168 | var _this = this; 169 | this.fileCache = new FileCache(); 170 | this.rootFiles = files.map(function (file) { return resolvePath(file); }); 171 | this.compilerOptions = options; 172 | this.compilerHost = ts.createCompilerHost(options); 173 | //TODO: diagnostics 174 | // prime fileCache with root files and defaultLib 175 | var seenNoDefaultLib = options.noLib; 176 | this.rootFiles.forEach(function (file) { 177 | var source = _this.compilerHost.getSourceFile(file, options.target); 178 | if (source) { 179 | seenNoDefaultLib = seenNoDefaultLib || source.hasNoDefaultLib; 180 | _this.fileCache.addFile(file, source.text); 181 | } 182 | else { 183 | throw ("tss cannot find file: " + file); 184 | } 185 | }); 186 | if (!seenNoDefaultLib) { 187 | var defaultLibFileName = this.compilerHost.getDefaultLibFileName(options); 188 | var source = this.compilerHost.getSourceFile(defaultLibFileName, options.target); 189 | this.fileCache.addFile(defaultLibFileName, source.text); 190 | } 191 | // Get a language service 192 | // internally builds programs from root files, 193 | // chases dependencies (references and imports), ... 194 | // (NOTE: files are processed on demand, loaded via lsHost, cached in fileCache) 195 | this.lsHost = { 196 | getCompilationSettings: function () { return _this.compilerOptions; }, 197 | getScriptFileNames: function () { return _this.fileCache.getFileNames(); }, 198 | getScriptVersion: function (fileName) { return _this.fileCache.getScriptInfo(fileName).version.toString(); }, 199 | //getScriptIsOpen : (fileName: string)=>this.fileCache.getScriptInfo(fileName).isOpen, 200 | getScriptSnapshot: function (fileName) { return _this.fileCache.getScriptSnapshot(fileName); }, 201 | getCurrentDirectory: function () { return ts.sys.getCurrentDirectory(); }, 202 | getDefaultLibFileName: function (options) { return ts.getDefaultLibFileName(options); }, 203 | log: function (message) { return undefined; }, 204 | trace: function (message) { return undefined; }, 205 | error: function (message) { return console.error(message); } // ?? 206 | }; 207 | this.ls = ts.createLanguageService(this.lsHost, ts.createDocumentRegistry()); 208 | this.fileCache.ls = this.ls; 209 | }; 210 | /** output value/object as JSON, excluding irrelevant properties, 211 | * with optional pretty-printing controlled by this.prettyJSON 212 | * @param info thing to output 213 | * @param excludes Array of property keys to exclude 214 | */ 215 | TSS.prototype.output = function (info, excludes) { 216 | if (excludes === void 0) { excludes = ["displayParts"]; } 217 | var replacer = function (k, v) { return excludes.indexOf(k) !== -1 ? undefined : v; }; 218 | if (info) { 219 | console.log(JSON.stringify(info, replacer, this.prettyJSON ? " " : undefined).trim()); 220 | } 221 | else { 222 | console.log(JSON.stringify(info, replacer)); 223 | } 224 | }; 225 | TSS.prototype.outputJSON = function (json) { 226 | console.log(json.trim()); 227 | }; 228 | /** recursively prepare navigationBarItems for JSON output */ 229 | TSS.prototype.handleNavBarItem = function (file, item) { 230 | var _this = this; 231 | // TODO: under which circumstances can item.spans.length be other than 1? 232 | return { info: [item.kindModifiers, item.kind, item.text].filter(function (s) { return s !== ""; }).join(" "), 233 | kindModifiers: item.kindModifiers, 234 | kind: item.kind, 235 | text: item.text, 236 | min: this.fileCache.positionToLineCol(file, item.spans[0].start), 237 | lim: this.fileCache.positionToLineCol(file, item.spans[0].start + item.spans[0].length), 238 | childItems: item.childItems.map(function (item) { return _this.handleNavBarItem(file, item); }) 239 | }; 240 | }; 241 | /** commandline server main routine: commands in, JSON info out */ 242 | TSS.prototype.listen = function () { 243 | var _this = this; 244 | var line; 245 | var col; 246 | var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); 247 | var cmd, pos, file, script, added, range, check, def, refs, locs, info, source, brief, member, navbarItems, pattern; 248 | var collecting = 0, on_collected_callback, lines = []; 249 | var commands = {}; 250 | function match(cmd, regexp) { 251 | commands[regexp.source] = true; 252 | return cmd.match(regexp); 253 | } 254 | rl.on('line', function (input) { 255 | var m; 256 | try { 257 | cmd = input.trim(); 258 | if (collecting > 0) { 259 | lines.push(input); 260 | collecting--; 261 | if (collecting === 0) { 262 | on_collected_callback(); 263 | } 264 | } 265 | else if (m = match(cmd, /^signature (\d+) (\d+) (.*)$/)) { 266 | (function () { 267 | line = parseInt(m[1]); 268 | col = parseInt(m[2]); 269 | file = resolvePath(m[3]); 270 | pos = _this.fileCache.lineColToPosition(file, line, col); 271 | info = _this.ls.getSignatureHelpItems(file, pos); 272 | var param = function (p) { return ({ name: p.name, 273 | isOptional: p.isOptional, 274 | type: ts.displayPartsToString(p.displayParts) || "", 275 | docComment: ts.displayPartsToString(p.documentation) || "" 276 | }); }; 277 | info && (info.items = info.items 278 | .map(function (item) { return ({ prefix: ts.displayPartsToString(item.prefixDisplayParts) || "", 279 | separator: ts.displayPartsToString(item.separatorDisplayParts) || "", 280 | suffix: ts.displayPartsToString(item.suffixDisplayParts) || "", 281 | parameters: item.parameters.map(param), 282 | docComment: ts.displayPartsToString(item.documentation) || "" 283 | }); })); 284 | _this.output(info); 285 | })(); 286 | } 287 | else if (m = match(cmd, /^(type|quickInfo) (\d+) (\d+) (.*)$/)) { 288 | line = parseInt(m[2]); 289 | col = parseInt(m[3]); 290 | file = resolvePath(m[4]); 291 | pos = _this.fileCache.lineColToPosition(file, line, col); 292 | info = (_this.ls.getQuickInfoAtPosition(file, pos) || {}); 293 | info.type = ((info && ts.displayPartsToString(info.displayParts)) || ""); 294 | info.docComment = ((info && ts.displayPartsToString(info.documentation)) || ""); 295 | _this.output(info); 296 | } 297 | else if (m = match(cmd, /^definition (\d+) (\d+) (.*)$/)) { 298 | line = parseInt(m[1]); 299 | col = parseInt(m[2]); 300 | file = resolvePath(m[3]); 301 | pos = _this.fileCache.lineColToPosition(file, line, col); 302 | locs = _this.ls.getDefinitionAtPosition(file, pos); // NOTE: multiple definitions 303 | info = locs && locs.map(function (def) { return ({ 304 | def: def, 305 | file: def && def.fileName, 306 | min: def && _this.fileCache.positionToLineCol(def.fileName, def.textSpan.start), 307 | lim: def && _this.fileCache.positionToLineCol(def.fileName, ts.textSpanEnd(def.textSpan)) 308 | }); }); 309 | // TODO: what about multiple definitions? 310 | _this.output((locs && info[0]) || null); 311 | } 312 | else if (m = match(cmd, /^(references|occurrences) (\d+) (\d+) (.*)$/)) { 313 | line = parseInt(m[2]); 314 | col = parseInt(m[3]); 315 | file = resolvePath(m[4]); 316 | pos = _this.fileCache.lineColToPosition(file, line, col); 317 | switch (m[1]) { 318 | case "references": 319 | refs = _this.ls.getReferencesAtPosition(file, pos); 320 | break; 321 | case "occurrences": 322 | refs = _this.ls.getOccurrencesAtPosition(file, pos); 323 | break; 324 | default: 325 | throw "cannot happen"; 326 | } 327 | info = (refs || []).map(function (ref) { 328 | var start, end, fileName, lineText; 329 | if (ref) { 330 | start = _this.fileCache.positionToLineCol(ref.fileName, ref.textSpan.start); 331 | end = _this.fileCache.positionToLineCol(ref.fileName, ts.textSpanEnd(ref.textSpan)); 332 | fileName = resolvePath(ref.fileName); 333 | lineText = _this.fileCache.getLineText(fileName, start.line); 334 | } 335 | return { 336 | ref: ref, 337 | file: ref && ref.fileName, 338 | lineText: lineText, 339 | min: start, 340 | lim: end 341 | }; 342 | }); 343 | _this.output(info); 344 | } 345 | else if (m = match(cmd, /^navigationBarItems (.*)$/)) { 346 | file = resolvePath(m[1]); 347 | _this.output(_this.ls.getNavigationBarItems(file) 348 | .map(function (item) { return _this.handleNavBarItem(file, item); })); 349 | } 350 | else if (m = match(cmd, /^navigateToItems (.*)$/)) { 351 | pattern = m[1]; 352 | info = _this.ls.getNavigateToItems(pattern) 353 | .map(function (item) { 354 | item['min'] = _this.fileCache.positionToLineCol(item.fileName, item.textSpan.start); 355 | item['lim'] = _this.fileCache.positionToLineCol(item.fileName, item.textSpan.start 356 | + item.textSpan.length); 357 | return item; 358 | }); 359 | _this.output(info); 360 | } 361 | else if (m = match(cmd, /^completions(-brief)?( true| false)? (\d+) (\d+) (.*)$/)) { 362 | brief = m[1]; 363 | line = parseInt(m[3]); 364 | col = parseInt(m[4]); 365 | file = resolvePath(m[5]); 366 | pos = _this.fileCache.lineColToPosition(file, line, col); 367 | info = _this.ls.getCompletionsAtPosition(file, pos) || null; 368 | if (info) { 369 | // fill in completion entry details, unless briefness requested 370 | !brief && (info.entries = info.entries.map(function (e) { 371 | var d = _this.ls.getCompletionEntryDetails(file, pos, e.name); 372 | if (d) { 373 | d["type"] = ts.displayPartsToString(d.displayParts); 374 | d["docComment"] = ts.displayPartsToString(d.documentation); 375 | return d; 376 | } 377 | else { 378 | return e; 379 | } 380 | })); 381 | // NOTE: details null for primitive type symbols, see TS #1592 382 | (function () { 383 | var languageVersion = _this.compilerOptions.target; 384 | var source = _this.fileCache.getScriptInfo(file).content; 385 | var startPos = pos; 386 | var idPart = function (p) { return /[0-9a-zA-Z_$]/.test(source[p]) 387 | || ts.isIdentifierPart(source.charCodeAt(p), languageVersion); }; 388 | var idStart = function (p) { return /[a-zA-Z_$]/.test(source[p]) 389 | || ts.isIdentifierStart(source.charCodeAt(p), languageVersion); }; 390 | while ((--startPos >= 0) && idPart(startPos)) 391 | ; 392 | if ((++startPos < pos) && idStart(startPos)) { 393 | var prefix = source.slice(startPos, pos); 394 | info["prefix"] = prefix; 395 | var len = prefix.length; 396 | info.entries = info.entries.filter(function (e) { return e.name.substr(0, len) === prefix; }); 397 | } 398 | })(); 399 | } 400 | _this.output(info, ["displayParts", "documentation", "sortText"]); 401 | } 402 | else if (m = match(cmd, /^update( nocheck)? (\d+)( (\d+)-(\d+))? (.*)$/)) { 403 | file = resolvePath(m[6]); 404 | source = _this.ls.getSourceFile(file); 405 | script = _this.fileCache.getScriptInfo(file); 406 | added = !script; 407 | range = !!m[3]; 408 | check = !m[1]; 409 | if (!added || !range) { 410 | collecting = parseInt(m[2]); 411 | on_collected_callback = function () { 412 | if (!range) { 413 | _this.fileCache.updateScript(file, lines.join(EOL)); 414 | } 415 | else { 416 | var startLine = parseInt(m[4]); 417 | var endLine = parseInt(m[5]); 418 | var maxLines = source.getLineStarts().length; 419 | var startPos = startLine <= maxLines 420 | ? (startLine < 1 ? 0 : _this.fileCache.lineColToPosition(file, startLine, 1)) 421 | : script.content.length; 422 | var endPos = endLine < maxLines 423 | ? (endLine < 1 ? 0 : _this.fileCache.lineColToPosition(file, endLine + 1, 1) - 1) //??CHECK 424 | : script.content.length; 425 | _this.fileCache.editScript(file, startPos, endPos, lines.join(EOL)); 426 | } 427 | var syn, sem; 428 | if (check) { 429 | syn = _this.ls.getSyntacticDiagnostics(file).length; 430 | sem = _this.ls.getSemanticDiagnostics(file).length; 431 | } 432 | on_collected_callback = undefined; 433 | lines = []; 434 | _this.outputJSON((added ? '"added ' : '"updated ') 435 | + (range ? 'lines' + m[3] + ' in ' : '') 436 | + file + (check ? ', (' + syn + '/' + sem + ') errors' : '') + '"'); 437 | }; 438 | } 439 | else { 440 | _this.outputJSON('"cannot update line range in new file"'); 441 | } 442 | } 443 | else if (m = match(cmd, /^showErrors$/)) { 444 | info = _this.ls.getProgram().getGlobalDiagnostics() 445 | .concat(_this.getErrors()) 446 | .map(function (d) { 447 | if (d.file) { 448 | var file = resolvePath(d.file.fileName); 449 | var lc = _this.fileCache.positionToLineCol(file, d.start); 450 | var len = _this.fileCache.getScriptInfo(file).content.length; 451 | var end = Math.min(len, d.start + d.length); 452 | // NOTE: clamped to end of file (#11) 453 | var lc2 = _this.fileCache.positionToLineCol(file, end); 454 | return { 455 | file: file, 456 | start: { line: lc.line, character: lc.character }, 457 | end: { line: lc2.line, character: lc2.character }, 458 | text: _this.messageChain(d.messageText).join(EOL), 459 | code: d.code, 460 | phase: d["phase"], 461 | category: ts.DiagnosticCategory[d.category] 462 | }; 463 | } 464 | else { 465 | return { 466 | text: _this.messageChain(d.messageText).join(EOL), 467 | code: d.code, 468 | phase: d["phase"], 469 | category: ts.DiagnosticCategory[d.category] 470 | }; 471 | } 472 | }); 473 | _this.output(info); 474 | } 475 | else if (m = match(cmd, /^files$/)) { 476 | info = _this.lsHost.getScriptFileNames(); // TODO: files are pre-resolved 477 | _this.output(info); 478 | } 479 | else if (m = match(cmd, /^lastError(Dump)?$/)) { 480 | if (_this.lastError) 481 | if (m[1]) 482 | console.log(JSON.parse(_this.lastError).stack); 483 | else 484 | _this.outputJSON(_this.lastError); 485 | else 486 | _this.outputJSON('"no last error"'); 487 | } 488 | else if (m = match(cmd, /^dump (\S+) (.*)$/)) { 489 | (function () { 490 | var dump = m[1]; 491 | var file = resolvePath(m[2]); 492 | var sourceText = _this.fileCache.getScriptInfo(file).content; 493 | if (dump === "-") { 494 | console.log('dumping ' + file); 495 | console.log(sourceText); 496 | } 497 | else { 498 | ts.sys.writeFile(dump, sourceText, false); 499 | _this.outputJSON('"dumped ' + file + ' to ' + dump + '"'); 500 | } 501 | })(); 502 | } 503 | else if (m = match(cmd, /^reload$/)) { 504 | // TODO: keep updated (in-memory-only) files? 505 | _this.setup(_this.rootFiles, _this.compilerOptions); 506 | _this.outputJSON(_this.listeningMessage('reloaded')); 507 | } 508 | else if (m = match(cmd, /^quit$/)) { 509 | rl.close(); 510 | } 511 | else if (m = match(cmd, /^prettyJSON (true|false)$/)) { 512 | _this.prettyJSON = m[1] === 'true'; 513 | _this.outputJSON('"pretty JSON: ' + _this.prettyJSON + '"'); 514 | } 515 | else if (m = match(cmd, /^help$/)) { 516 | console.log(Object.keys(commands).join(EOL)); 517 | } 518 | else { 519 | _this.outputJSON('"TSS command syntax error: ' + cmd + '"'); 520 | } 521 | } 522 | catch (e) { 523 | _this.lastError = (JSON.stringify({ msg: e.toString(), stack: e.stack })).trim(); 524 | _this.outputJSON('"TSS command processing error: ' + e + '"'); 525 | } 526 | }).on('close', function () { 527 | _this.outputJSON('"TSS closing"'); 528 | }); 529 | this.outputJSON(this.listeningMessage('loaded')); 530 | }; 531 | TSS.prototype.listeningMessage = function (prefix) { 532 | var count = this.rootFiles.length - 1; 533 | var more = count > 0 ? ' (+' + count + ' more)' : ''; 534 | return '"' + prefix + ' ' + this.rootFiles[0] + more + ', TSS listening.."'; 535 | }; 536 | return TSS; 537 | }()); 538 | function extend(o1, o2) { 539 | var o = {}; 540 | for (var p in o1) { 541 | o[p] = o1[p]; 542 | } 543 | for (var p in o2) { 544 | if (!(p in o)) 545 | o[p] = o2[p]; 546 | } 547 | return o; 548 | } 549 | var fileNames; 550 | var configFile, configObject, configObjectParsed; 551 | // NOTE: partial options support only 552 | var commandLine = ts.parseCommandLine(ts.sys.args); 553 | if (commandLine.options.version) { 554 | console.log(require("../package.json").version); 555 | process.exit(0); 556 | } 557 | if (commandLine.fileNames.length > 0) { 558 | fileNames = commandLine.fileNames; 559 | } 560 | else if (commandLine.options.project) { 561 | configFile = path.join(commandLine.options.project, "tsconfig.json"); 562 | } 563 | else { 564 | configFile = ts.findConfigFile(path.normalize(ts.sys.getCurrentDirectory()), ts.sys.fileExists); 565 | } 566 | var options; 567 | if (configFile) { 568 | configObject = ts.readConfigFile(configFile, ts.sys.readFile); 569 | if (!configObject) { 570 | console.error("can't read tsconfig.json at", configFile); 571 | process.exit(1); 572 | } 573 | configObjectParsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configFile)); 574 | if (configObjectParsed.errors.length > 0) { 575 | console.error(configObjectParsed.errors); 576 | process.exit(1); 577 | } 578 | fileNames = configObjectParsed.fileNames; 579 | options = extend(commandLine.options, configObjectParsed.options); 580 | } 581 | else { 582 | options = extend(commandLine.options, ts.getDefaultCompilerOptions()); 583 | } 584 | if (!fileNames) { 585 | console.error("can't find project root"); 586 | console.error("please specify root source file"); 587 | console.error(" or --project directory (containing a tsconfig.json)"); 588 | process.exit(1); 589 | } 590 | var tss = new TSS(); 591 | try { 592 | tss.setup(fileNames, options); 593 | tss.listen(); 594 | } 595 | catch (e) { 596 | console.error(e.toString()); 597 | process.exit(1); 598 | } 599 | -------------------------------------------------------------------------------- /ftplugin/typescript_tss.vim: -------------------------------------------------------------------------------- 1 | echohl WarningMsg 2 | echomsg "typescript-tools vim plugin has moved to separate repo:" 3 | echomsg "https://github.com/clausreinke/typescript-tools.vim" 4 | echomsg "(you are using a deprecated version)" 5 | call input("hit return to continue") 6 | echohl None 7 | 8 | if exists("g:TSSloaded") 9 | finish 10 | endif 11 | if !has("python") 12 | echoerr "typescript_tss.vim needs python interface" 13 | finish 14 | else 15 | 16 | python < :TSSdef 62 | map ] :TSSdefsplit 63 | map ]] :TSSdeftab 64 | map ? :TSSdefpreview 65 | map _? :TSStype 66 | " :TSSsymbol 67 | map _?? :TSSbrowse 68 | " :TSSreferences 69 | map n :TSSnavigation 70 | map t :TSSnavigateTo 71 | map u :TSSupdate 72 | map e :TSSshowErrors 73 | map p :TSSfilesMenu 74 | map f :TSSfile 75 | " :TSSfiles 76 | map r :TSSreload 77 | " :TSSstart 78 | " :TSSstarthere 79 | " :TSSstatus 80 | " :TSSend 81 | endfunction 82 | 83 | " TODO: doesn't work anymore - replace with something useful, or drop 84 | " browse url for ES5 global property/method under cursor 85 | command! TSSbrowse echo TSSbrowse() 86 | function! TSSbrowse() 87 | let info = TSScmd("type",{}) 88 | if type(info)!=type({}) || !has_key(info,"fullSymbolName") 89 | return info 90 | endif 91 | let patterns = ["\\(Object\\)\\.\\(\\k*\\)" 92 | \,"\\(Function\\)\\.\\(\\k*\\)" 93 | \,"\\(String\\)\\.\\(\\k*\\)" 94 | \,"\\(Boolean\\)\\.\\(\\k*\\)" 95 | \,"\\(Number\\)\\.\\(\\k*\\)" 96 | \,"\\(Array\\)<.*>\\.\\(\\k*\\)" 97 | \,"\\(Date\\)\\.\\(\\k*\\)" 98 | \,"\\(RegExp\\)\\.\\(\\k*\\)" 99 | \,"\\(Error\\)\\.\\(\\k*\\)" 100 | \,"\\(Math\\)\\.\\(\\k*\\)" 101 | \,"\\(JSON\\)\\.\\(\\k*\\)" 102 | \] 103 | for p in patterns 104 | let m = matchlist(info.fullSymbolName,p) 105 | if m!=[] 106 | py webbrowser.open(TSS_MDN+vim.eval("m[1].'/'.m[2]")) 107 | return m[1].'.'.m[2] 108 | endif 109 | endfor 110 | return "no url found" 111 | endfunction 112 | 113 | " echo symbol/type of item under cursor 114 | " (also show JSDoc in preview window, if known) 115 | command! TSSsymbol echo TSSsymbol("") 116 | function! TSSsymbol(rawcmd) 117 | if a:rawcmd=="" 118 | let info = TSScmd("type",{}) 119 | else 120 | let info = TSScmd(a:rawcmd,{'rawcmd':1}) 121 | endif 122 | if type(info)!=type({}) || !has_key(info,"type") 123 | if a:rawcmd=="" 124 | echoerr 'no useable type information' 125 | return info 126 | else " called from ballooneval 127 | return "" 128 | endif 129 | endif 130 | return info.type 131 | endfunction 132 | 133 | command! TSStype echo TSStype() 134 | function! TSStype() 135 | let info = TSScmd("type",{}) 136 | if type(info)!=type({}) || !has_key(info,"type") 137 | echoerr 'no useable type information' 138 | return info 139 | endif 140 | if has_key(info,"docComment") && info.docComment!="" 141 | pclose 142 | new +setlocal\ previewwindow|setlocal\ buftype=nofile|setlocal\ noswapfile 143 | exe "normal z" . &previewheight . "\" 144 | call append(0,[info.type]+split(info.docComment,"\n")) 145 | wincmd p 146 | endif 147 | return info.type 148 | endfunction 149 | 150 | " for use as balloonexpr, symbol under mouse pointer 151 | " set balloonexpr=TSSballoon() 152 | " set ballooneval 153 | function! TSSballoon() 154 | let file = expand("#".v:beval_bufnr.":p") 155 | " case-insensitive.. 156 | if count(g:TSSfiles,file,1)!=0 157 | return TSSsymbol("type ".v:beval_lnum." ".v:beval_col." ".file) 158 | else 159 | return '' 160 | endif 161 | endfunction 162 | 163 | " jump to or show definition of item under cursor 164 | command! TSSdef call TSSdef("edit") 165 | command! TSSdefpreview call TSSdef("pedit") 166 | command! TSSdefsplit call TSSdef("split") 167 | command! TSSdeftab call TSSdef("tabe") 168 | function! TSSdef(cmd) 169 | let info = TSScmd("definition",{}) 170 | if type(info)!=type({}) || info.file=='null' || type(info.min)!=type({}) 171 | \ || type(info.min.line)!=type(0) || type(info.min.character)!=type(0) 172 | if type(info)==type("") && info=='null' 173 | echoerr 'no useable definition information' 174 | else 175 | echoerr string(info) 176 | endif 177 | return info 178 | endif 179 | if a:cmd=="pedit" 180 | exe a:cmd.'+'.info.min.line.' '.info.file 181 | else 182 | exe a:cmd.' '.info.file 183 | call cursor(info.min.line,info.min.character) 184 | endif 185 | return info 186 | endfunction 187 | 188 | " update TSS with current file source, record state of updates 189 | " NOTE: this will be hard to get right: 190 | " disk vs buffer, update vs reload, dependencies, ... 191 | command! -nargs=? TSSupdate echo TSSupdate() 192 | function! TSSupdate(completion) 193 | let nocheck = ((a:completion=="completionStart")||(a:completion=="nocheck")) ? " nocheck" : "" 194 | let file = expand("%:p") 195 | let cur = undotree().seq_cur 196 | let updated = has_key(g:TSSupdates,file) 197 | if (!updated && &modified) || (updated && (cur!=g:TSSupdates[file])) || a:completion=~"completion" 198 | let g:TSSupdates[file] = cur 199 | let info = TSScmd("update".nocheck." ".line('$')." ".file,{'rawcmd':1,'lines':getline(1,line('$'))}) 200 | return (a:completion!="" ? "" : info) 201 | else 202 | return "" 203 | endif 204 | endfunction 205 | 206 | " dump TSS internal file source 207 | command! -nargs=1 TSSdump echo TSScmd("dump ".." ".expand("%:p"),{'rawcmd':1}) 208 | 209 | " completions (omnifunc will be set for all *.ts files) 210 | function! TSScompleteFunc(findstart,base) 211 | " echomsg a:findstart."|".a:base 212 | let col = col(".") 213 | let line = getline(".") 214 | 215 | " search backwards for start of identifier (iskeyword pattern) 216 | let start = col 217 | while start>0 && line[start-2] =~ "\\k" 218 | let start -= 1 219 | endwhile 220 | 221 | if a:findstart 222 | if TSSstatus()!="None" 223 | py vim.command('echoerr "'+TSS_NOT_RUNNING_MSG+'"') 224 | endif 225 | 226 | " force updates for completed fragments, while still in insert mode 227 | " bypass error checking (cf #13,#14) 228 | TSSupdate completionStart 229 | 230 | "return line[start-1] =~ "\\k" ? start-1 : -1 231 | return start-1 232 | else 233 | " check if preceded by dot (won't see dot on previous line!) 234 | let member = (start>1 && line[start-2]==".") ? 'true' : 'false' 235 | echomsg start.":".member 236 | 237 | " cf #13,#14 238 | let info = TSScmd("completions ".member,{'col':start}) 239 | if type(info)==type("") && info=~"TSS command processing error" 240 | " force updates for completed fragments, while still in insert mode 241 | " try update again, this time with error checking (to trigger semantic analysis) 242 | call TSSupdate("completion") 243 | unlet info 244 | let info = TSScmd("completions ".member,{'col':start}) 245 | endif 246 | 247 | let result = [] 248 | if type(info)==type({}) 249 | for entry in info.entries 250 | if entry['name'] =~ '^'.a:base 251 | let typish = get(entry,'type',get(entry,'kind','')) 252 | call add(result, {'word': entry['name'], 'menu': typish 253 | \,'info': entry['name']." ".typish."\n".get(entry,'docComment','') }) 254 | endif 255 | endfor 256 | endif 257 | return result 258 | endif 259 | endfunction 260 | aug TSS 261 | au! 262 | au BufNewFile,BufRead *.ts setlocal omnifunc=TSScompleteFunc 263 | aug END 264 | doau TSS BufRead 265 | 266 | " open project file, with filename completion 267 | command! -complete=customlist,TSSfile -nargs=1 TSSfile edit 268 | function! TSSfile(A,L,P) 269 | return filter(copy(g:TSSfiles),'v:val=~"'.a:A.'"') 270 | endfunction 271 | 272 | function! TSSgroupPaths(pl) 273 | let ps = {} 274 | for p in a:pl 275 | let pfrags = split(p,'/') 276 | let prefix = ps 277 | for f in pfrags 278 | if !has_key(prefix,f) 279 | let prefix[f] = {} 280 | endif 281 | let prefix = prefix[f] 282 | endfor 283 | endfor 284 | return ps 285 | endfunction 286 | 287 | function! TSSpathMenu(prefix,path,pt) 288 | if empty(a:pt) 289 | exe a:prefix.' :edit '.a:path.'' 290 | elseif len(a:pt)==1 291 | let key = keys(a:pt)[0] 292 | call TSSpathMenu(a:prefix.(a:path==''?'.':'/').substitute(key,'\.','\\.','g'),(a:path==''?'':a:path.'/').key,a:pt[key]) 293 | else 294 | for key in sort(keys(a:pt)) 295 | call TSSpathMenu(a:prefix.'.'.substitute(key,'\.','\\.','g'),(a:path==''?'':a:path.'/').key,a:pt[key]) 296 | endfor 297 | endif 298 | endfunction 299 | 300 | " navigate to project file via popup menu 301 | command! TSSfilesMenu echo TSSfilesMenu('show') 302 | function! TSSfilesMenu(action) 303 | let files = a:action=~'fetch' ? TSScmd("files",{'rawcmd':1}) : g:TSSfiles 304 | if type(files)==type([]) 305 | let g:TSSfiles = files 306 | if a:action=~'show' 307 | silent! unmenu ]TSSfiles 308 | call TSSpathMenu('menu ]TSSfiles','',TSSgroupPaths(g:TSSfiles)) 309 | popup ]TSSfiles 310 | endif 311 | endif 312 | popup ]TSSfiles 313 | endfunction 314 | 315 | " show project file list in preview window 316 | command! TSSfiles echo TSSfiles('show') 317 | function! TSSfiles(action) 318 | let files = a:action=~'fetch' ? TSScmd("files",{'rawcmd':1}) : g:TSSfiles 319 | if type(files)==type([]) 320 | let g:TSSfiles = files 321 | if a:action=~'show' 322 | pclose 323 | new +setlocal\ previewwindow|setlocal\ buftype=nofile|setlocal\ noswapfile 324 | exe "normal z" . &previewheight . "\" 325 | call append(0,files) 326 | " TODO: group by prefix paths 327 | endif 328 | endif 329 | endfunction 330 | 331 | " reload project sources 332 | command! TSSreload echo TSSreload() 333 | function! TSSreload() 334 | let unsaved = [] 335 | for f in g:TSSfiles 336 | if bufloaded(f) && getbufvar(f,"&modified") 337 | let unsaved += [f] 338 | endif 339 | endfor 340 | if unsaved!=[] 341 | echoerr "there are buffers with unsaved changes:" 342 | for f in unsaved 343 | echomsg f 344 | endfor 345 | return "TSSreload cancelled" 346 | endif 347 | let msg = TSScmd("reload",{'rawcmd':1}) 348 | call TSSfiles('fetch') 349 | let g:TSSupdates = {} 350 | if g:TSSshowErrors 351 | TSSshowErrors 352 | endif 353 | return msg 354 | endfunction 355 | 356 | " create quickfix list from TSS errors 357 | command! TSSshowErrors call TSSshowErrors() 358 | function! TSSshowErrors() 359 | 360 | TSSupdate nocheck 361 | 362 | let info = TSScmd("showErrors",{'rawcmd':1}) 363 | if type(info)==type([]) 364 | let qflist = [] 365 | for i in info 366 | let chain = split(i.text,'\(\r\)\?\n') 367 | for msg in chain 368 | let qflist = add(qflist,{ 'lnum': i['start']['line'] 369 | \ , 'col': i['start']['character'] 370 | \ , 'filename': i['file'] 371 | \ , 'text': msg }) 372 | endfor 373 | endfor 374 | call setqflist(qflist) 375 | if len(qflist)!=0 376 | copen 377 | endif 378 | else 379 | echoerr info 380 | endif 381 | endfunction 382 | 383 | " create location list for references 384 | command! TSSreferences call TSSreferences() 385 | function! TSSreferences() 386 | let info = TSScmd("references",{}) 387 | if type(info)==type([]) 388 | for i in info 389 | let i['lnum'] = i['min']['line'] 390 | let i['col'] = i['min']['character'] 391 | let i['filename'] = i['file'] 392 | let i['text'] = i['lineText'] 393 | endfor 394 | call setloclist(0,info) 395 | if len(info)!=0 396 | lopen 397 | endif 398 | else 399 | echoerr info 400 | endif 401 | endfunction 402 | 403 | " recursively build menu at prefix, from navigationBarItems 404 | function! TSSnavigationMenu(prefix,items) 405 | for item in a:items 406 | let prefix = a:prefix.'.'.substitute(item['info'],' ','\\\ ','g') 407 | let cmd = prefix.(!empty(item['childItems'])?'.\.':'') 408 | \ .' :call cursor('.item.min.line.','.item.min.character.')' 409 | exe cmd 410 | call TSSnavigationMenu(prefix,item['childItems']) 411 | endfor 412 | endfunction 413 | 414 | " create and open navigation menu for file navigation bar items 415 | command! TSSnavigation call TSSnavigation() 416 | function! TSSnavigation() 417 | let info = TSScmd("navigationBarItems ".expand("%:p"),{'rawcmd':1}) 418 | if type(info)==type([]) 419 | silent! unmenu ]TSSnavigation 420 | call TSSnavigationMenu('menu ]TSSnavigation',info) 421 | " TODO: mark directly before call cursor (bco tear-off menus) 422 | normal m' 423 | popup ]TSSnavigation 424 | else 425 | echoerr info 426 | endif 427 | endfunction 428 | 429 | " navigate to items in project 430 | " 1. narrow down symbols via completion, modulo case/prefix/infix/camelCase 431 | command! -complete=customlist,TSSnavigateToItems -nargs=1 TSSnavigateTo 432 | \ call TSSnavigateTo() 433 | function! TSSnavigateToItems(A,L,P) 434 | let items = TSScmd('navigateToItems '.a:A,{'rawcmd':1}) 435 | let results = [] 436 | silent! unmenu ]TSSnavigateTo 437 | for item in items 438 | let results += [item.name] 439 | endfor 440 | return results 441 | endfunction 442 | 443 | " 2. offer remaining exact (modulo case) matches as a menu 444 | function! TSSnavigateTo(item) 445 | let items = TSScmd('navigateToItems '.a:item,{'rawcmd':1}) 446 | silent! unmenu ]TSSnavigateTo 447 | silent! tunmenu ]TSSnavigateTo 448 | for item in items 449 | if item.matchKind!="exact" 450 | continue 451 | endif 452 | let entry = (item.kind!=""?item.kind."\\ ":"").item.name 453 | if item.containerName!="" 454 | let entry = entry."\\ (".(item.containerKind!=""?item.containerKind."\\ ":"") 455 | \ .item.containerName.")" 456 | endif 457 | exe "menu ]TSSnavigateTo.".entry 458 | \ ." :call TSSgoto('".item.fileName."',".item.min.line.",".item.min.character.")" 459 | endfor 460 | popup ]TSSnavigateTo 461 | endfunction 462 | 463 | function! TSSgoto(file,line,col) 464 | exe "edit ".a:file 465 | call cursor(a:line,a:col) 466 | endfunction 467 | 468 | "TODO: guard tss usage in later functions 469 | " start typescript service process asynchronously, via python 470 | " NOTE: one reason for shell=True is to avoid popup console window; 471 | command! -nargs=* TSSstart call TSSstart() 472 | command! TSSstarthere call TSSstart(expand("%")) 473 | function! TSSstart(...) 474 | echomsg "starting TSS..." 475 | python <,{}) 520 | function! TSScmd(cmd,opts) 521 | python <": exit status) 571 | command! TSSstatus echo TSSstatus() 572 | function! TSSstatus() 573 | python < 6 | 7 | import ts = require("typescript"); 8 | 9 | // TODO: avoid pre-computing line starts, can tss use SourceFiles instead? 10 | /* @internal, from scanner.ts */ 11 | function computeLineStarts(text: string): number[] { 12 | let result: number[] = new Array(); 13 | let pos = 0; 14 | let lineStart = 0; 15 | while (pos < text.length) { 16 | let ch = text.charCodeAt(pos++); 17 | switch (ch) { 18 | case 0x0D: 19 | if (text.charCodeAt(pos) === 0x0A) { 20 | pos++; 21 | } 22 | case 0x0A: 23 | result.push(lineStart); 24 | lineStart = pos; 25 | break; 26 | default: 27 | if (ch > 0x7F && ts.isLineBreak(ch)) { 28 | result.push(lineStart); 29 | lineStart = pos; 30 | } 31 | break; 32 | } 33 | } 34 | result.push(lineStart); 35 | return result; 36 | } 37 | 38 | export class ScriptInfo { 39 | public version: number = 1; 40 | public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = []; 41 | 42 | constructor(public fileName: string, public content: string, public isOpen = true) { 43 | this.setContent(content); 44 | } 45 | 46 | private setContent(content: string): void { 47 | this.content = content; 48 | } 49 | 50 | public updateContent(content: string): void { 51 | var old_length = this.content.length; 52 | this.setContent(content); 53 | this.editRanges.push({ 54 | length: content.length, 55 | textChangeRange: 56 | // NOTE: no shortcut for "update everything" (null only works in some places, #10) 57 | ts.createTextChangeRange(ts.createTextSpan(0,old_length),content.length) 58 | }); 59 | this.version++; 60 | } 61 | 62 | public editContent(minChar: number, limChar: number, newText: string): void { 63 | // Apply edits 64 | var prefix = this.content.substring(0, minChar); 65 | var middle = newText; 66 | var suffix = this.content.substring(limChar); 67 | this.setContent(prefix + middle + suffix); 68 | 69 | // Store edit range + new length of script 70 | this.editRanges.push({ 71 | length: this.content.length, 72 | textChangeRange: 73 | ts.createTextChangeRange( ts.createTextSpanFromBounds(minChar, limChar) 74 | , newText.length) 75 | }); 76 | 77 | // Update version # 78 | this.version++; 79 | } 80 | 81 | public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { 82 | if (startVersion === endVersion) { 83 | // No edits! 84 | return ts.unchangedTextChangeRange; 85 | } 86 | 87 | var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); 88 | var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); 89 | 90 | var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); 91 | return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); 92 | } 93 | } 94 | 95 | export class ScriptSnapshot implements ts.IScriptSnapshot { 96 | private lineMap: number[] = null; 97 | private textSnapshot: string; 98 | private version: number; 99 | 100 | constructor(private scriptInfo: ScriptInfo) { 101 | this.textSnapshot = scriptInfo.content; 102 | this.version = scriptInfo.version; 103 | } 104 | 105 | public getText(start: number, end: number): string { 106 | return this.textSnapshot.substring(start, end); 107 | } 108 | 109 | public getLength(): number { 110 | return this.textSnapshot.length; 111 | } 112 | 113 | private getLineStartPositions(): number[] { 114 | if (this.lineMap === null) { 115 | this.lineMap = computeLineStarts(this.textSnapshot); 116 | } 117 | 118 | return this.lineMap; 119 | } 120 | 121 | public getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange { 122 | return undefined; 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-tools", 3 | "version": "v0.7.0", 4 | "description": "TypeScript Services commandline server", 5 | "bin": { 6 | "tss": "bin/tss" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/clausreinke/typescript-tools.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/clausreinke/typescript-tools/issues" 14 | }, 15 | "keywords": [ 16 | "TypeScript" 17 | ], 18 | "preferGlobal": true, 19 | "author": "Claus Reinke", 20 | "license": "Apache-2.0", 21 | "dependencies": { 22 | "typescript": "1.8" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | Minimal semi-automated testing (results may differ if you are using a different `lib.d.ts`). 2 | ``` 3 | $ node script.js >script.out2 4 | 5 | $ diff --strip-trailing-cr script.out* 6 | ``` 7 | -------------------------------------------------------------------------------- /tests/concat-map.script: -------------------------------------------------------------------------------- 1 | update 4 empty.ts 2 | var x = [].concat([{a:1}],[{a:2}]) 3 | .map(b => b.a); 4 | 5 | console.log(x); 6 | showErrors 7 | -------------------------------------------------------------------------------- /tests/empty.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clausreinke/typescript-tools/6c07eab06fe9ac3afdf3259b13061aa7dfeb7020/tests/empty.ts -------------------------------------------------------------------------------- /tests/issue-10.script: -------------------------------------------------------------------------------- 1 | update 1 PREFIX/empty.ts 2 | var i = 20; 3 | update 1 PREFIX/empty.ts 4 | var i = 10; 5 | definition 1 5 PREFIX/empty.ts 6 | -------------------------------------------------------------------------------- /tests/issue-11.script: -------------------------------------------------------------------------------- 1 | update 1 PREFIX/empty.ts 2 | foo = 'bar 3 | showErrors 4 | -------------------------------------------------------------------------------- /tests/issue-12.script: -------------------------------------------------------------------------------- 1 | update 1 PREFIX/empty.ts 2 | var t 3 | completions false 1 6 PREFIX/empty.ts 4 | -------------------------------------------------------------------------------- /tests/issue-13.script: -------------------------------------------------------------------------------- 1 | update 4 PREFIX/empty.ts 2 | // semantics check after update avoids #13 3 | function foo(bar){ 4 | b 5 | } 6 | completions false 3 4 PREFIX/empty.ts 7 | update nocheck 5 empty.ts 8 | // without the check, completion fails 9 | // once TS issue 1592 gets fixed, completion should succeed and this test should fail 10 | function foo(bar) { 11 | b 12 | } 13 | completions false 4 4 empty.ts 14 | -------------------------------------------------------------------------------- /tests/issue-15-import.ts: -------------------------------------------------------------------------------- 1 | export class Class { public a; public b; init() {}} 2 | -------------------------------------------------------------------------------- /tests/issue-15.script: -------------------------------------------------------------------------------- 1 | update nocheck 30 issue-15.ts 2 | /* 3 | declare module 'issue-15-import' { 4 | export class Background { public a; public b; init() } 5 | } 6 | */ 7 | 8 | import Background = require('./issue-15-import'); 9 | 10 | class Test { 11 | 12 | public test_var; 13 | 14 | constructor(){ } 15 | 16 | public test_method(){ } 17 | 18 | } 19 | 20 | function main(){ 21 | 22 | var xx = new Background.noSuchExport(); 23 | var bg = new Background.Class(); 24 | xx.noSuchProperty(); 25 | bg.noSuchProperty(); 26 | 27 | var test = new Test() 28 | test.test_method(); 29 | 30 | } 31 | 32 | type 23 3 issue-15.ts 33 | type 24 3 issue-15.ts 34 | completions true 21 27 issue-15.ts 35 | completions true 22 27 issue-15.ts 36 | completions true 23 6 issue-15.ts 37 | completions true 24 6 issue-15.ts 38 | completions true 27 8 issue-15.ts 39 | -------------------------------------------------------------------------------- /tests/issue-15.ts: -------------------------------------------------------------------------------- 1 | /* 2 | declare module 'issue-15-import' { 3 | export class Background { public a; public b; init() } 4 | } 5 | */ 6 | 7 | import Background = require('./issue-15-import'); 8 | 9 | class Test { 10 | 11 | public test_var; 12 | 13 | constructor(){ } 14 | 15 | public test_method(){ } 16 | 17 | } 18 | 19 | function main(){ 20 | 21 | var xx = new Background.noSuchExport(); 22 | var bg = new Background.Class(); 23 | xx.noSuchProperty(); 24 | bg.noSuchProperty(); 25 | 26 | var test = new Test() 27 | test.test_method(); 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /tests/issue-17-import.ts: -------------------------------------------------------------------------------- 1 | export class Test{ 2 | 3 | constructor(){ 4 | 5 | } 6 | 7 | public start(){ 8 | return this; 9 | } 10 | 11 | public stop(){ 12 | return this; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tests/issue-17.script: -------------------------------------------------------------------------------- 1 | files 2 | references 7 12 issue-17-import.ts 3 | references 7 12 issue-17-import.ts 4 | files 5 | lastErrorDump 6 | -------------------------------------------------------------------------------- /tests/issue-17.ts: -------------------------------------------------------------------------------- 1 | import Second = require("./issue-17-import"); 2 | 3 | var second = new Second.Test() 4 | second.start(); 5 | second.stop(); 6 | 7 | -------------------------------------------------------------------------------- /tests/issue-52.script: -------------------------------------------------------------------------------- 1 | update 3 PREFIX/empty.ts 2 | var U = {"wer": 33, "ttz": {"werwer": 77, "oo": [1,2,3]}} 3 | 4 | U.ttz = 3; 5 | showErrors 6 | -------------------------------------------------------------------------------- /tests/issue-9.script: -------------------------------------------------------------------------------- 1 | update 2 PREFIX/empty.ts 2 | for(;;) 3 | 4 | showErrors 5 | update 2 PREFIX/empty.ts 6 | for(;;) 7 | ; 8 | showErrors 9 | update 2 PREFIX/empty.ts 10 | for(;;) 11 | 12 | showErrors 13 | -------------------------------------------------------------------------------- /tests/partial-update.script: -------------------------------------------------------------------------------- 1 | update 3 empty.ts 2 | function foo(bar) { 3 | bar 4 | } 5 | dump - empty.ts 6 | update 2 2-2 empty.ts 7 | "hi"; 8 | "ho"; 9 | dump - empty.ts 10 | update 1 4-4 empty.ts 11 | } // EOF 12 | dump - empty.ts 13 | update 1 3-3 empty.ts 14 | bozo; 15 | dump - empty.ts 16 | showErrors 17 | update 2 5-5 empty.ts 18 | 19 | // postmortem 20 | dump - empty.ts 21 | update 2 0-0 empty.ts 22 | // header 23 | 24 | dump - empty.ts 25 | -------------------------------------------------------------------------------- /tests/script.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require("fs"); 3 | var exec = require("child_process").exec; 4 | 5 | var tss_path = "../bin/tss.js"; 6 | 7 | var PREFIX = __dirname.replace(/\\/g,"/"); 8 | var NODE_MODULES = require.resolve('typescript').replace(/\/typescript\/.*$/,''); 9 | 10 | var tests = [], log = {}, done = {}; 11 | var filter = process.argv[2] && new RegExp(process.argv[2]); 12 | 13 | function test(scriptName,fileName,options) { 14 | if (filter && !filter.test(scriptName)) return; 15 | var script = fs.readFileSync(scriptName,"utf8") 16 | .replace(/PREFIX/g,PREFIX) 17 | .replace(/NODE_MODULES/g,NODE_MODULES); 18 | 19 | var cmd = "node "+tss_path+(options?" "+options:"")+" "+fileName; 20 | tests.push(scriptName); 21 | log[scriptName] = ["// "+scriptName,cmd]; 22 | 23 | var tss = exec(cmd 24 | ,{maxBuffer:Infinity} 25 | ,function(error, stdout, stderr) { 26 | log[scriptName].push("// stdout"); 27 | log[scriptName].push(stdout.replace(new RegExp(PREFIX,"g"),"PREFIX") 28 | .replace(new RegExp(NODE_MODULES,"g"),"NODE_MODULES") 29 | .replace(/(,"|,{)/g,'\n $1') 30 | .replace(/\\r\\n/g,'\\n')); 31 | log[scriptName].push("// stderr"); 32 | log[scriptName].push(stderr); 33 | if (error) log[scriptName].push("// error: "+error); 34 | 35 | done[scriptName] = true; 36 | collectResults(); 37 | }); 38 | tss.stdin.write(script); 39 | tss.stdin.end(); 40 | } 41 | function collectResults() { 42 | while (done[tests[0]]) { 43 | console.log( log[tests[0]].join('\n') ) 44 | tests.shift(); 45 | } 46 | } 47 | test("test.script","test.ts"); 48 | test("issue-9.script","empty.ts"); 49 | test("issue-10.script","empty.ts"); 50 | test("issue-11.script","empty.ts"); 51 | test("issue-12.script","empty.ts"); 52 | test("issue-13.script","empty.ts"); 53 | test("partial-update.script","empty.ts"); 54 | test("update-nocheck-completion-chain.script","empty.ts"); 55 | test("issue-15.script","issue-15.ts","-m commonjs"); 56 | test("issue-17.script","issue-17.ts","-m commonjs"); 57 | test("concat-map.script","empty.ts"); 58 | test("issue-52.script","empty.ts"); 59 | -------------------------------------------------------------------------------- /tests/script.out: -------------------------------------------------------------------------------- 1 | // test.script 2 | node ../bin/tss.js test.ts 3 | // stdout 4 | "loaded PREFIX/test.ts, TSS listening.." 5 | [{"file":"PREFIX/test.ts" 6 | ,"start":{"line":3 7 | ,"character":5} 8 | ,"end":{"line":3 9 | ,"character":6} 10 | ,"text":"Identifier expected." 11 | ,"code":1003 12 | ,"phase":"Syntax" 13 | ,"category":"Error"} 14 | ,{"file":"PREFIX/test.ts" 15 | ,"start":{"line":4 16 | ,"character":5} 17 | ,"end":{"line":4 18 | ,"character":6} 19 | ,"text":"Identifier expected." 20 | ,"code":1003 21 | ,"phase":"Syntax" 22 | ,"category":"Error"}] 23 | {"kind":"var" 24 | ,"kindModifiers":"" 25 | ,"textSpan":{"start":32 26 | ,"length":1} 27 | ,"documentation":[] 28 | ,"type":"var s: string" 29 | ,"docComment":""} 30 | {"kind":"var" 31 | ,"kindModifiers":"" 32 | ,"textSpan":{"start":38 33 | ,"length":1} 34 | ,"documentation":[] 35 | ,"type":"var x: {\n a: number;\n b: number;\n}" 36 | ,"docComment":""} 37 | {"def":{"fileName":"PREFIX/test.ts" 38 | ,"textSpan":{"start":23 39 | ,"length":6} 40 | ,"kind":"var" 41 | ,"name":"s" 42 | ,"containerName":""} 43 | ,"file":"PREFIX/test.ts" 44 | ,"min":{"line":2 45 | ,"character":5} 46 | ,"lim":{"line":2 47 | ,"character":11}} 48 | {"def":{"fileName":"PREFIX/test.ts" 49 | ,"textSpan":{"start":4 50 | ,"length":13} 51 | ,"kind":"var" 52 | ,"name":"x" 53 | ,"containerName":""} 54 | ,"file":"PREFIX/test.ts" 55 | ,"min":{"line":1 56 | ,"character":5} 57 | ,"lim":{"line":1 58 | ,"character":18}} 59 | {"isMemberCompletion":true 60 | ,"isNewIdentifierLocation":false 61 | ,"entries":[{"name":"toString" 62 | ,"kindModifiers":"declare" 63 | ,"kind":"method" 64 | ,"type":"(method) String.toString(): string" 65 | ,"docComment":"Returns a string representation of a string. "} 66 | ,{"name":"charAt" 67 | ,"kindModifiers":"declare" 68 | ,"kind":"method" 69 | ,"type":"(method) String.charAt(pos: number): string" 70 | ,"docComment":"Returns the character at the specified index."} 71 | ,{"name":"charCodeAt" 72 | ,"kindModifiers":"declare" 73 | ,"kind":"method" 74 | ,"type":"(method) String.charCodeAt(index: number): number" 75 | ,"docComment":"Returns the Unicode value of the character at the specified location."} 76 | ,{"name":"concat" 77 | ,"kindModifiers":"declare" 78 | ,"kind":"method" 79 | ,"type":"(method) String.concat(...strings: string[]): string" 80 | ,"docComment":"Returns a string that contains the concatenation of two or more strings."} 81 | ,{"name":"indexOf" 82 | ,"kindModifiers":"declare" 83 | ,"kind":"method" 84 | ,"type":"(method) String.indexOf(searchString: string, position?: number): number" 85 | ,"docComment":"Returns the position of the first occurrence of a substring. "} 86 | ,{"name":"lastIndexOf" 87 | ,"kindModifiers":"declare" 88 | ,"kind":"method" 89 | ,"type":"(method) String.lastIndexOf(searchString: string, position?: number): number" 90 | ,"docComment":"Returns the last occurrence of a substring in the string."} 91 | ,{"name":"localeCompare" 92 | ,"kindModifiers":"declare" 93 | ,"kind":"method" 94 | ,"type":"(method) String.localeCompare(that: string): number (+2 overloads)" 95 | ,"docComment":"Determines whether two strings are equivalent in the current locale."} 96 | ,{"name":"match" 97 | ,"kindModifiers":"declare" 98 | ,"kind":"method" 99 | ,"type":"(method) String.match(regexp: string): RegExpMatchArray (+1 overload)" 100 | ,"docComment":"Matches a string with a regular expression, and returns an array containing the results of that search."} 101 | ,{"name":"replace" 102 | ,"kindModifiers":"declare" 103 | ,"kind":"method" 104 | ,"type":"(method) String.replace(searchValue: string, replaceValue: string): string (+3 overloads)" 105 | ,"docComment":"Replaces text in a string, using a regular expression or search string."} 106 | ,{"name":"search" 107 | ,"kindModifiers":"declare" 108 | ,"kind":"method" 109 | ,"type":"(method) String.search(regexp: string): number (+1 overload)" 110 | ,"docComment":"Finds the first substring match in a regular expression search."} 111 | ,{"name":"slice" 112 | ,"kindModifiers":"declare" 113 | ,"kind":"method" 114 | ,"type":"(method) String.slice(start?: number, end?: number): string" 115 | ,"docComment":"Returns a section of a string."} 116 | ,{"name":"split" 117 | ,"kindModifiers":"declare" 118 | ,"kind":"method" 119 | ,"type":"(method) String.split(separator: string, limit?: number): string[] (+1 overload)" 120 | ,"docComment":"Split a string into substrings using the specified separator and return them as an array."} 121 | ,{"name":"substring" 122 | ,"kindModifiers":"declare" 123 | ,"kind":"method" 124 | ,"type":"(method) String.substring(start: number, end?: number): string" 125 | ,"docComment":"Returns the substring at the specified location within a String object. "} 126 | ,{"name":"toLowerCase" 127 | ,"kindModifiers":"declare" 128 | ,"kind":"method" 129 | ,"type":"(method) String.toLowerCase(): string" 130 | ,"docComment":"Converts all the alphabetic characters in a string to lowercase. "} 131 | ,{"name":"toLocaleLowerCase" 132 | ,"kindModifiers":"declare" 133 | ,"kind":"method" 134 | ,"type":"(method) String.toLocaleLowerCase(): string" 135 | ,"docComment":"Converts all alphabetic characters to lowercase, taking into account the host environment's current locale. "} 136 | ,{"name":"toUpperCase" 137 | ,"kindModifiers":"declare" 138 | ,"kind":"method" 139 | ,"type":"(method) String.toUpperCase(): string" 140 | ,"docComment":"Converts all the alphabetic characters in a string to uppercase. "} 141 | ,{"name":"toLocaleUpperCase" 142 | ,"kindModifiers":"declare" 143 | ,"kind":"method" 144 | ,"type":"(method) String.toLocaleUpperCase(): string" 145 | ,"docComment":"Returns a string where all alphabetic characters have been converted to uppercase, taking into account the host environment's current locale. "} 146 | ,{"name":"trim" 147 | ,"kindModifiers":"declare" 148 | ,"kind":"method" 149 | ,"type":"(method) String.trim(): string" 150 | ,"docComment":"Removes the leading and trailing white space and line terminator characters from a string. "} 151 | ,{"name":"length" 152 | ,"kindModifiers":"declare" 153 | ,"kind":"property" 154 | ,"type":"(property) String.length: number" 155 | ,"docComment":"Returns the length of a String object. "} 156 | ,{"name":"substr" 157 | ,"kindModifiers":"declare" 158 | ,"kind":"method" 159 | ,"type":"(method) String.substr(from: number, length?: number): string" 160 | ,"docComment":"Gets a substring beginning at the specified location and having the specified length."} 161 | ,{"name":"valueOf" 162 | ,"kindModifiers":"declare" 163 | ,"kind":"method" 164 | ,"type":"(method) String.valueOf(): string" 165 | ,"docComment":"Returns the primitive value of the specified object. "}]} 166 | {"isMemberCompletion":true 167 | ,"isNewIdentifierLocation":false 168 | ,"entries":[{"name":"a" 169 | ,"kindModifiers":"" 170 | ,"kind":"property" 171 | ,"type":"(property) a: number" 172 | ,"docComment":""} 173 | ,{"name":"b" 174 | ,"kindModifiers":"" 175 | ,"kind":"property" 176 | ,"type":"(property) b: number" 177 | ,"docComment":""}]} 178 | "updated PREFIX/test.ts, (2/1) errors" 179 | dumping PREFIX/test.ts 180 | var x = {a:1,b:2}; 181 | var s = ""; 182 | var a = []; 183 | {s.to } 184 | {x. } 185 | {a. } 186 | [{"file":"PREFIX/test.ts" 187 | ,"start":{"line":5 188 | ,"character":5} 189 | ,"end":{"line":5 190 | ,"character":6} 191 | ,"text":"Identifier expected." 192 | ,"code":1003 193 | ,"phase":"Syntax" 194 | ,"category":"Error"} 195 | ,{"file":"PREFIX/test.ts" 196 | ,"start":{"line":6 197 | ,"character":5} 198 | ,"end":{"line":6 199 | ,"character":6} 200 | ,"text":"Identifier expected." 201 | ,"code":1003 202 | ,"phase":"Syntax" 203 | ,"category":"Error"} 204 | ,{"file":"PREFIX/test.ts" 205 | ,"start":{"line":4 206 | ,"character":4} 207 | ,"end":{"line":4 208 | ,"character":6} 209 | ,"text":"Property 'to' does not exist on type 'string'." 210 | ,"code":2339 211 | ,"phase":"Semantics" 212 | ,"category":"Error"}] 213 | {"isMemberCompletion":true 214 | ,"isNewIdentifierLocation":false 215 | ,"entries":[{"name":"toString" 216 | ,"kindModifiers":"declare" 217 | ,"kind":"method" 218 | ,"type":"(method) String.toString(): string" 219 | ,"docComment":"Returns a string representation of a string. "} 220 | ,{"name":"charAt" 221 | ,"kindModifiers":"declare" 222 | ,"kind":"method" 223 | ,"type":"(method) String.charAt(pos: number): string" 224 | ,"docComment":"Returns the character at the specified index."} 225 | ,{"name":"charCodeAt" 226 | ,"kindModifiers":"declare" 227 | ,"kind":"method" 228 | ,"type":"(method) String.charCodeAt(index: number): number" 229 | ,"docComment":"Returns the Unicode value of the character at the specified location."} 230 | ,{"name":"concat" 231 | ,"kindModifiers":"declare" 232 | ,"kind":"method" 233 | ,"type":"(method) String.concat(...strings: string[]): string" 234 | ,"docComment":"Returns a string that contains the concatenation of two or more strings."} 235 | ,{"name":"indexOf" 236 | ,"kindModifiers":"declare" 237 | ,"kind":"method" 238 | ,"type":"(method) String.indexOf(searchString: string, position?: number): number" 239 | ,"docComment":"Returns the position of the first occurrence of a substring. "} 240 | ,{"name":"lastIndexOf" 241 | ,"kindModifiers":"declare" 242 | ,"kind":"method" 243 | ,"type":"(method) String.lastIndexOf(searchString: string, position?: number): number" 244 | ,"docComment":"Returns the last occurrence of a substring in the string."} 245 | ,{"name":"localeCompare" 246 | ,"kindModifiers":"declare" 247 | ,"kind":"method" 248 | ,"type":"(method) String.localeCompare(that: string): number (+2 overloads)" 249 | ,"docComment":"Determines whether two strings are equivalent in the current locale."} 250 | ,{"name":"match" 251 | ,"kindModifiers":"declare" 252 | ,"kind":"method" 253 | ,"type":"(method) String.match(regexp: string): RegExpMatchArray (+1 overload)" 254 | ,"docComment":"Matches a string with a regular expression, and returns an array containing the results of that search."} 255 | ,{"name":"replace" 256 | ,"kindModifiers":"declare" 257 | ,"kind":"method" 258 | ,"type":"(method) String.replace(searchValue: string, replaceValue: string): string (+3 overloads)" 259 | ,"docComment":"Replaces text in a string, using a regular expression or search string."} 260 | ,{"name":"search" 261 | ,"kindModifiers":"declare" 262 | ,"kind":"method" 263 | ,"type":"(method) String.search(regexp: string): number (+1 overload)" 264 | ,"docComment":"Finds the first substring match in a regular expression search."} 265 | ,{"name":"slice" 266 | ,"kindModifiers":"declare" 267 | ,"kind":"method" 268 | ,"type":"(method) String.slice(start?: number, end?: number): string" 269 | ,"docComment":"Returns a section of a string."} 270 | ,{"name":"split" 271 | ,"kindModifiers":"declare" 272 | ,"kind":"method" 273 | ,"type":"(method) String.split(separator: string, limit?: number): string[] (+1 overload)" 274 | ,"docComment":"Split a string into substrings using the specified separator and return them as an array."} 275 | ,{"name":"substring" 276 | ,"kindModifiers":"declare" 277 | ,"kind":"method" 278 | ,"type":"(method) String.substring(start: number, end?: number): string" 279 | ,"docComment":"Returns the substring at the specified location within a String object. "} 280 | ,{"name":"toLowerCase" 281 | ,"kindModifiers":"declare" 282 | ,"kind":"method" 283 | ,"type":"(method) String.toLowerCase(): string" 284 | ,"docComment":"Converts all the alphabetic characters in a string to lowercase. "} 285 | ,{"name":"toLocaleLowerCase" 286 | ,"kindModifiers":"declare" 287 | ,"kind":"method" 288 | ,"type":"(method) String.toLocaleLowerCase(): string" 289 | ,"docComment":"Converts all alphabetic characters to lowercase, taking into account the host environment's current locale. "} 290 | ,{"name":"toUpperCase" 291 | ,"kindModifiers":"declare" 292 | ,"kind":"method" 293 | ,"type":"(method) String.toUpperCase(): string" 294 | ,"docComment":"Converts all the alphabetic characters in a string to uppercase. "} 295 | ,{"name":"toLocaleUpperCase" 296 | ,"kindModifiers":"declare" 297 | ,"kind":"method" 298 | ,"type":"(method) String.toLocaleUpperCase(): string" 299 | ,"docComment":"Returns a string where all alphabetic characters have been converted to uppercase, taking into account the host environment's current locale. "} 300 | ,{"name":"trim" 301 | ,"kindModifiers":"declare" 302 | ,"kind":"method" 303 | ,"type":"(method) String.trim(): string" 304 | ,"docComment":"Removes the leading and trailing white space and line terminator characters from a string. "} 305 | ,{"name":"length" 306 | ,"kindModifiers":"declare" 307 | ,"kind":"property" 308 | ,"type":"(property) String.length: number" 309 | ,"docComment":"Returns the length of a String object. "} 310 | ,{"name":"substr" 311 | ,"kindModifiers":"declare" 312 | ,"kind":"method" 313 | ,"type":"(method) String.substr(from: number, length?: number): string" 314 | ,"docComment":"Gets a substring beginning at the specified location and having the specified length."} 315 | ,{"name":"valueOf" 316 | ,"kindModifiers":"declare" 317 | ,"kind":"method" 318 | ,"type":"(method) String.valueOf(): string" 319 | ,"docComment":"Returns the primitive value of the specified object. "}]} 320 | {"isMemberCompletion":true 321 | ,"isNewIdentifierLocation":false 322 | ,"entries":[{"name":"a" 323 | ,"kindModifiers":"" 324 | ,"kind":"property" 325 | ,"type":"(property) a: number" 326 | ,"docComment":""} 327 | ,{"name":"b" 328 | ,"kindModifiers":"" 329 | ,"kind":"property" 330 | ,"type":"(property) b: number" 331 | ,"docComment":""}]} 332 | {"isMemberCompletion":true 333 | ,"isNewIdentifierLocation":false 334 | ,"entries":[{"name":"length" 335 | ,"kindModifiers":"declare" 336 | ,"kind":"property" 337 | ,"type":"(property) Array.length: number" 338 | ,"docComment":"Gets or sets the length of the array. This is a number one higher than the highest element defined in an array."} 339 | ,{"name":"toString" 340 | ,"kindModifiers":"declare" 341 | ,"kind":"method" 342 | ,"type":"(method) Array.toString(): string" 343 | ,"docComment":"Returns a string representation of an array."} 344 | ,{"name":"toLocaleString" 345 | ,"kindModifiers":"declare" 346 | ,"kind":"method" 347 | ,"type":"(method) Array.toLocaleString(): string" 348 | ,"docComment":""} 349 | ,{"name":"push" 350 | ,"kindModifiers":"declare" 351 | ,"kind":"method" 352 | ,"type":"(method) Array.push(...items: any[]): number" 353 | ,"docComment":"Appends new elements to an array, and returns the new length of the array."} 354 | ,{"name":"pop" 355 | ,"kindModifiers":"declare" 356 | ,"kind":"method" 357 | ,"type":"(method) Array.pop(): any" 358 | ,"docComment":"Removes the last element from an array and returns it."} 359 | ,{"name":"concat" 360 | ,"kindModifiers":"declare" 361 | ,"kind":"method" 362 | ,"type":"(method) Array.concat(...items: U[]): any[] (+1 overload)" 363 | ,"docComment":"Combines two or more arrays."} 364 | ,{"name":"join" 365 | ,"kindModifiers":"declare" 366 | ,"kind":"method" 367 | ,"type":"(method) Array.join(separator?: string): string" 368 | ,"docComment":"Adds all the elements of an array separated by the specified separator string."} 369 | ,{"name":"reverse" 370 | ,"kindModifiers":"declare" 371 | ,"kind":"method" 372 | ,"type":"(method) Array.reverse(): any[]" 373 | ,"docComment":"Reverses the elements in an Array. "} 374 | ,{"name":"shift" 375 | ,"kindModifiers":"declare" 376 | ,"kind":"method" 377 | ,"type":"(method) Array.shift(): any" 378 | ,"docComment":"Removes the first element from an array and returns it."} 379 | ,{"name":"slice" 380 | ,"kindModifiers":"declare" 381 | ,"kind":"method" 382 | ,"type":"(method) Array.slice(start?: number, end?: number): any[]" 383 | ,"docComment":"Returns a section of an array."} 384 | ,{"name":"sort" 385 | ,"kindModifiers":"declare" 386 | ,"kind":"method" 387 | ,"type":"(method) Array.sort(compareFn?: (a: any, b: any) => number): any[]" 388 | ,"docComment":"Sorts an array."} 389 | ,{"name":"splice" 390 | ,"kindModifiers":"declare" 391 | ,"kind":"method" 392 | ,"type":"(method) Array.splice(start: number): any[] (+1 overload)" 393 | ,"docComment":"Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements."} 394 | ,{"name":"unshift" 395 | ,"kindModifiers":"declare" 396 | ,"kind":"method" 397 | ,"type":"(method) Array.unshift(...items: any[]): number" 398 | ,"docComment":"Inserts new elements at the start of an array."} 399 | ,{"name":"indexOf" 400 | ,"kindModifiers":"declare" 401 | ,"kind":"method" 402 | ,"type":"(method) Array.indexOf(searchElement: any, fromIndex?: number): number" 403 | ,"docComment":"Returns the index of the first occurrence of a value in an array."} 404 | ,{"name":"lastIndexOf" 405 | ,"kindModifiers":"declare" 406 | ,"kind":"method" 407 | ,"type":"(method) Array.lastIndexOf(searchElement: any, fromIndex?: number): number" 408 | ,"docComment":"Returns the index of the last occurrence of a specified value in an array."} 409 | ,{"name":"every" 410 | ,"kindModifiers":"declare" 411 | ,"kind":"method" 412 | ,"type":"(method) Array.every(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean" 413 | ,"docComment":"Determines whether all the members of an array satisfy the specified test."} 414 | ,{"name":"some" 415 | ,"kindModifiers":"declare" 416 | ,"kind":"method" 417 | ,"type":"(method) Array.some(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): boolean" 418 | ,"docComment":"Determines whether the specified callback function returns true for any element of an array."} 419 | ,{"name":"forEach" 420 | ,"kindModifiers":"declare" 421 | ,"kind":"method" 422 | ,"type":"(method) Array.forEach(callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any): void" 423 | ,"docComment":"Performs the specified action for each element in an array."} 424 | ,{"name":"map" 425 | ,"kindModifiers":"declare" 426 | ,"kind":"method" 427 | ,"type":"(method) Array.map(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any): U[]" 428 | ,"docComment":"Calls a defined callback function on each element of an array, and returns an array that contains the results."} 429 | ,{"name":"filter" 430 | ,"kindModifiers":"declare" 431 | ,"kind":"method" 432 | ,"type":"(method) Array.filter(callbackfn: (value: any, index: number, array: any[]) => boolean, thisArg?: any): any[]" 433 | ,"docComment":"Returns the elements of an array that meet the condition specified in a callback function. "} 434 | ,{"name":"reduce" 435 | ,"kindModifiers":"declare" 436 | ,"kind":"method" 437 | ,"type":"(method) Array.reduce(callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue?: any): any (+1 overload)" 438 | ,"docComment":"Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function."} 439 | ,{"name":"reduceRight" 440 | ,"kindModifiers":"declare" 441 | ,"kind":"method" 442 | ,"type":"(method) Array.reduceRight(callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue?: any): any (+1 overload)" 443 | ,"docComment":"Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function."}]} 444 | {"isMemberCompletion":true 445 | ,"isNewIdentifierLocation":false 446 | ,"entries":[{"name":"length" 447 | ,"kind":"property" 448 | ,"kindModifiers":"declare"} 449 | ,{"name":"toString" 450 | ,"kind":"method" 451 | ,"kindModifiers":"declare"} 452 | ,{"name":"toLocaleString" 453 | ,"kind":"method" 454 | ,"kindModifiers":"declare"} 455 | ,{"name":"push" 456 | ,"kind":"method" 457 | ,"kindModifiers":"declare"} 458 | ,{"name":"pop" 459 | ,"kind":"method" 460 | ,"kindModifiers":"declare"} 461 | ,{"name":"concat" 462 | ,"kind":"method" 463 | ,"kindModifiers":"declare"} 464 | ,{"name":"join" 465 | ,"kind":"method" 466 | ,"kindModifiers":"declare"} 467 | ,{"name":"reverse" 468 | ,"kind":"method" 469 | ,"kindModifiers":"declare"} 470 | ,{"name":"shift" 471 | ,"kind":"method" 472 | ,"kindModifiers":"declare"} 473 | ,{"name":"slice" 474 | ,"kind":"method" 475 | ,"kindModifiers":"declare"} 476 | ,{"name":"sort" 477 | ,"kind":"method" 478 | ,"kindModifiers":"declare"} 479 | ,{"name":"splice" 480 | ,"kind":"method" 481 | ,"kindModifiers":"declare"} 482 | ,{"name":"unshift" 483 | ,"kind":"method" 484 | ,"kindModifiers":"declare"} 485 | ,{"name":"indexOf" 486 | ,"kind":"method" 487 | ,"kindModifiers":"declare"} 488 | ,{"name":"lastIndexOf" 489 | ,"kind":"method" 490 | ,"kindModifiers":"declare"} 491 | ,{"name":"every" 492 | ,"kind":"method" 493 | ,"kindModifiers":"declare"} 494 | ,{"name":"some" 495 | ,"kind":"method" 496 | ,"kindModifiers":"declare"} 497 | ,{"name":"forEach" 498 | ,"kind":"method" 499 | ,"kindModifiers":"declare"} 500 | ,{"name":"map" 501 | ,"kind":"method" 502 | ,"kindModifiers":"declare"} 503 | ,{"name":"filter" 504 | ,"kind":"method" 505 | ,"kindModifiers":"declare"} 506 | ,{"name":"reduce" 507 | ,"kind":"method" 508 | ,"kindModifiers":"declare"} 509 | ,{"name":"reduceRight" 510 | ,"kind":"method" 511 | ,"kindModifiers":"declare"}]} 512 | {"isMemberCompletion":true 513 | ,"isNewIdentifierLocation":false 514 | ,"entries":[{"name":"toString" 515 | ,"kindModifiers":"declare" 516 | ,"kind":"method" 517 | ,"type":"(method) String.toString(): string" 518 | ,"docComment":"Returns a string representation of a string. "} 519 | ,{"name":"toLowerCase" 520 | ,"kindModifiers":"declare" 521 | ,"kind":"method" 522 | ,"type":"(method) String.toLowerCase(): string" 523 | ,"docComment":"Converts all the alphabetic characters in a string to lowercase. "} 524 | ,{"name":"toLocaleLowerCase" 525 | ,"kindModifiers":"declare" 526 | ,"kind":"method" 527 | ,"type":"(method) String.toLocaleLowerCase(): string" 528 | ,"docComment":"Converts all alphabetic characters to lowercase, taking into account the host environment's current locale. "} 529 | ,{"name":"toUpperCase" 530 | ,"kindModifiers":"declare" 531 | ,"kind":"method" 532 | ,"type":"(method) String.toUpperCase(): string" 533 | ,"docComment":"Converts all the alphabetic characters in a string to uppercase. "} 534 | ,{"name":"toLocaleUpperCase" 535 | ,"kindModifiers":"declare" 536 | ,"kind":"method" 537 | ,"type":"(method) String.toLocaleUpperCase(): string" 538 | ,"docComment":"Returns a string where all alphabetic characters have been converted to uppercase, taking into account the host environment's current locale. "}] 539 | ,"prefix":"to"} 540 | "TSS closing" 541 | 542 | // stderr 543 | 544 | // issue-9.script 545 | node ../bin/tss.js empty.ts 546 | // stdout 547 | "loaded PREFIX/empty.ts, TSS listening.." 548 | "updated PREFIX/empty.ts, (1/0) errors" 549 | [{"file":"PREFIX/empty.ts" 550 | ,"start":{"line":2 551 | ,"character":1} 552 | ,"end":{"line":2 553 | ,"character":1} 554 | ,"text":"Expression expected." 555 | ,"code":1109 556 | ,"phase":"Syntax" 557 | ,"category":"Error"}] 558 | "updated PREFIX/empty.ts, (0/0) errors" 559 | [] 560 | "updated PREFIX/empty.ts, (1/0) errors" 561 | [{"file":"PREFIX/empty.ts" 562 | ,"start":{"line":2 563 | ,"character":1} 564 | ,"end":{"line":2 565 | ,"character":1} 566 | ,"text":"Expression expected." 567 | ,"code":1109 568 | ,"phase":"Syntax" 569 | ,"category":"Error"}] 570 | "TSS closing" 571 | 572 | // stderr 573 | 574 | // issue-10.script 575 | node ../bin/tss.js empty.ts 576 | // stdout 577 | "loaded PREFIX/empty.ts, TSS listening.." 578 | "updated PREFIX/empty.ts, (0/0) errors" 579 | "updated PREFIX/empty.ts, (0/0) errors" 580 | {"def":{"fileName":"PREFIX/empty.ts" 581 | ,"textSpan":{"start":4 582 | ,"length":6} 583 | ,"kind":"var" 584 | ,"name":"i" 585 | ,"containerName":""} 586 | ,"file":"PREFIX/empty.ts" 587 | ,"min":{"line":1 588 | ,"character":5} 589 | ,"lim":{"line":1 590 | ,"character":11}} 591 | "TSS closing" 592 | 593 | // stderr 594 | 595 | // issue-11.script 596 | node ../bin/tss.js empty.ts 597 | // stdout 598 | "loaded PREFIX/empty.ts, TSS listening.." 599 | "updated PREFIX/empty.ts, (1/1) errors" 600 | [{"file":"PREFIX/empty.ts" 601 | ,"start":{"line":1 602 | ,"character":11} 603 | ,"end":{"line":1 604 | ,"character":11} 605 | ,"text":"Unterminated string literal." 606 | ,"code":1002 607 | ,"phase":"Syntax" 608 | ,"category":"Error"} 609 | ,{"file":"PREFIX/empty.ts" 610 | ,"start":{"line":1 611 | ,"character":1} 612 | ,"end":{"line":1 613 | ,"character":4} 614 | ,"text":"Cannot find name 'foo'." 615 | ,"code":2304 616 | ,"phase":"Semantics" 617 | ,"category":"Error"}] 618 | "TSS closing" 619 | 620 | // stderr 621 | 622 | // issue-12.script 623 | node ../bin/tss.js empty.ts 624 | // stdout 625 | "loaded PREFIX/empty.ts, TSS listening.." 626 | "updated PREFIX/empty.ts, (0/0) errors" 627 | null 628 | "TSS closing" 629 | 630 | // stderr 631 | 632 | // issue-13.script 633 | node ../bin/tss.js empty.ts 634 | // stdout 635 | "loaded PREFIX/empty.ts, TSS listening.." 636 | "updated PREFIX/empty.ts, (0/1) errors" 637 | {"isMemberCompletion":false 638 | ,"isNewIdentifierLocation":false 639 | ,"entries":[{"name":"bar" 640 | ,"kindModifiers":"" 641 | ,"kind":"parameter" 642 | ,"type":"(parameter) bar: any" 643 | ,"docComment":""} 644 | ,{"name":"blur" 645 | ,"kindModifiers":"declare" 646 | ,"kind":"function" 647 | ,"type":"function blur(): void" 648 | ,"docComment":""} 649 | ,{"name":"btoa" 650 | ,"kindModifiers":"declare" 651 | ,"kind":"function" 652 | ,"type":"function btoa(rawString: string): string" 653 | ,"docComment":""} 654 | ,{"name":"break" 655 | ,"kind":"keyword" 656 | ,"kindModifiers":"" 657 | ,"type":"break" 658 | ,"docComment":""} 659 | ,{"name":"boolean" 660 | ,"kind":"keyword" 661 | ,"kindModifiers":"" 662 | ,"type":"boolean" 663 | ,"docComment":""}] 664 | ,"prefix":"b"} 665 | "updated PREFIX/empty.ts" 666 | {"isMemberCompletion":false 667 | ,"isNewIdentifierLocation":false 668 | ,"entries":[{"name":"bar" 669 | ,"kindModifiers":"" 670 | ,"kind":"parameter" 671 | ,"type":"(parameter) bar: any" 672 | ,"docComment":""} 673 | ,{"name":"blur" 674 | ,"kindModifiers":"declare" 675 | ,"kind":"function" 676 | ,"type":"function blur(): void" 677 | ,"docComment":""} 678 | ,{"name":"btoa" 679 | ,"kindModifiers":"declare" 680 | ,"kind":"function" 681 | ,"type":"function btoa(rawString: string): string" 682 | ,"docComment":""} 683 | ,{"name":"break" 684 | ,"kind":"keyword" 685 | ,"kindModifiers":"" 686 | ,"type":"break" 687 | ,"docComment":""} 688 | ,{"name":"boolean" 689 | ,"kind":"keyword" 690 | ,"kindModifiers":"" 691 | ,"type":"boolean" 692 | ,"docComment":""}] 693 | ,"prefix":"b"} 694 | "TSS closing" 695 | 696 | // stderr 697 | 698 | // partial-update.script 699 | node ../bin/tss.js empty.ts 700 | // stdout 701 | "loaded PREFIX/empty.ts, TSS listening.." 702 | "updated PREFIX/empty.ts, (0/0) errors" 703 | dumping PREFIX/empty.ts 704 | function foo(bar) { 705 | bar 706 | } 707 | "updated lines 2-2 in PREFIX/empty.ts, (0/0) errors" 708 | dumping PREFIX/empty.ts 709 | function foo(bar) { 710 | "hi"; 711 | "ho"; 712 | } 713 | "updated lines 4-4 in PREFIX/empty.ts, (0/0) errors" 714 | dumping PREFIX/empty.ts 715 | function foo(bar) { 716 | "hi"; 717 | "ho"; 718 | } // EOF 719 | "updated lines 3-3 in PREFIX/empty.ts, (0/1) errors" 720 | dumping PREFIX/empty.ts 721 | function foo(bar) { 722 | "hi"; 723 | bozo; 724 | } // EOF 725 | [{"file":"PREFIX/empty.ts" 726 | ,"start":{"line":3 727 | ,"character":1} 728 | ,"end":{"line":3 729 | ,"character":5} 730 | ,"text":"Cannot find name 'bozo'." 731 | ,"code":2304 732 | ,"phase":"Semantics" 733 | ,"category":"Error"}] 734 | "updated lines 5-5 in PREFIX/empty.ts, (0/1) errors" 735 | dumping PREFIX/empty.ts 736 | function foo(bar) { 737 | "hi"; 738 | bozo; 739 | } // EOF 740 | // postmortem 741 | "updated lines 0-0 in PREFIX/empty.ts, (0/1) errors" 742 | dumping PREFIX/empty.ts 743 | // header 744 | function foo(bar) { 745 | "hi"; 746 | bozo; 747 | } // EOF 748 | // postmortem 749 | "TSS closing" 750 | 751 | // stderr 752 | 753 | // update-nocheck-completion-chain.script 754 | node ../bin/tss.js empty.ts 755 | // stdout 756 | "loaded PREFIX/empty.ts, TSS listening.." 757 | "updated PREFIX/empty.ts" 758 | {"isMemberCompletion":true 759 | ,"isNewIdentifierLocation":false 760 | ,"entries":[{"name":"forEach" 761 | ,"kindModifiers":"declare" 762 | ,"kind":"method" 763 | ,"type":"(method) Array.forEach(callbackfn: (value: undefined, index: number, array: undefined[]) => void, thisArg?: any): void" 764 | ,"docComment":"Performs the specified action for each element in an array."} 765 | ,{"name":"filter" 766 | ,"kindModifiers":"declare" 767 | ,"kind":"method" 768 | ,"type":"(method) Array.filter(callbackfn: (value: undefined, index: number, array: undefined[]) => boolean, thisArg?: any): undefined[]" 769 | ,"docComment":"Returns the elements of an array that meet the condition specified in a callback function. "}] 770 | ,"prefix":"f"} 771 | "updated PREFIX/empty.ts" 772 | {"isMemberCompletion":true 773 | ,"isNewIdentifierLocation":false 774 | ,"entries":[{"name":"map" 775 | ,"kindModifiers":"declare" 776 | ,"kind":"method" 777 | ,"type":"(method) Array.map(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any): U[]" 778 | ,"docComment":"Calls a defined callback function on each element of an array, and returns an array that contains the results."}] 779 | ,"prefix":"m"} 780 | "updated PREFIX/empty.ts" 781 | {"isMemberCompletion":true 782 | ,"isNewIdentifierLocation":false 783 | ,"entries":[{"name":"forEach" 784 | ,"kindModifiers":"declare" 785 | ,"kind":"method" 786 | ,"type":"(method) Array.forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void" 787 | ,"docComment":"Performs the specified action for each element in an array."}] 788 | ,"prefix":"fo"} 789 | "updated PREFIX/empty.ts" 790 | {"isMemberCompletion":true 791 | ,"isNewIdentifierLocation":false 792 | ,"entries":[{"name":"toLowerCase" 793 | ,"kindModifiers":"declare" 794 | ,"kind":"method" 795 | ,"type":"(method) String.toLowerCase(): string" 796 | ,"docComment":"Converts all the alphabetic characters in a string to lowercase. "} 797 | ,{"name":"toLocaleLowerCase" 798 | ,"kindModifiers":"declare" 799 | ,"kind":"method" 800 | ,"type":"(method) String.toLocaleLowerCase(): string" 801 | ,"docComment":"Converts all alphabetic characters to lowercase, taking into account the host environment's current locale. "} 802 | ,{"name":"toLocaleUpperCase" 803 | ,"kindModifiers":"declare" 804 | ,"kind":"method" 805 | ,"type":"(method) String.toLocaleUpperCase(): string" 806 | ,"docComment":"Returns a string where all alphabetic characters have been converted to uppercase, taking into account the host environment's current locale. "}] 807 | ,"prefix":"toL"} 808 | "updated PREFIX/empty.ts" 809 | {"isMemberCompletion":false 810 | ,"isNewIdentifierLocation":false 811 | ,"entries":[{"name":"bar" 812 | ,"kindModifiers":"" 813 | ,"kind":"parameter" 814 | ,"type":"(parameter) bar: any" 815 | ,"docComment":""}] 816 | ,"prefix":"ba"} 817 | "TSS closing" 818 | 819 | // stderr 820 | 821 | // issue-15.script 822 | node ../bin/tss.js -m commonjs issue-15.ts 823 | // stdout 824 | "loaded PREFIX/issue-15.ts, TSS listening.." 825 | "updated PREFIX/issue-15.ts" 826 | {"kind":"local var" 827 | ,"kindModifiers":"" 828 | ,"textSpan":{"start":333 829 | ,"length":2} 830 | ,"documentation":[] 831 | ,"type":"(local var) xx: any" 832 | ,"docComment":""} 833 | {"kind":"local var" 834 | ,"kindModifiers":"" 835 | ,"textSpan":{"start":356 836 | ,"length":2} 837 | ,"documentation":[] 838 | ,"type":"(local var) bg: Background.Class" 839 | ,"docComment":""} 840 | {"isMemberCompletion":true 841 | ,"isNewIdentifierLocation":false 842 | ,"entries":[{"name":"Class" 843 | ,"kindModifiers":"export" 844 | ,"kind":"constructor" 845 | ,"type":"constructor Background.Class(): Background.Class" 846 | ,"docComment":""}]} 847 | {"isMemberCompletion":true 848 | ,"isNewIdentifierLocation":false 849 | ,"entries":[{"name":"Class" 850 | ,"kindModifiers":"export" 851 | ,"kind":"constructor" 852 | ,"type":"constructor Background.Class(): Background.Class" 853 | ,"docComment":""}]} 854 | null 855 | {"isMemberCompletion":true 856 | ,"isNewIdentifierLocation":false 857 | ,"entries":[{"name":"a" 858 | ,"kindModifiers":"public" 859 | ,"kind":"property" 860 | ,"type":"(property) Background.Class.a: any" 861 | ,"docComment":""} 862 | ,{"name":"b" 863 | ,"kindModifiers":"public" 864 | ,"kind":"property" 865 | ,"type":"(property) Background.Class.b: any" 866 | ,"docComment":""} 867 | ,{"name":"init" 868 | ,"kindModifiers":"" 869 | ,"kind":"method" 870 | ,"type":"(method) Background.Class.init(): void" 871 | ,"docComment":""}]} 872 | {"isMemberCompletion":true 873 | ,"isNewIdentifierLocation":false 874 | ,"entries":[{"name":"test_var" 875 | ,"kindModifiers":"public" 876 | ,"kind":"property" 877 | ,"type":"(property) Test.test_var: any" 878 | ,"docComment":""} 879 | ,{"name":"test_method" 880 | ,"kindModifiers":"public" 881 | ,"kind":"method" 882 | ,"type":"(method) Test.test_method(): void" 883 | ,"docComment":""}]} 884 | "TSS closing" 885 | 886 | // stderr 887 | 888 | // issue-17.script 889 | node ../bin/tss.js -m commonjs issue-17.ts 890 | // stdout 891 | "loaded PREFIX/issue-17.ts, TSS listening.." 892 | ["NODE_MODULES/typescript/lib/lib.d.ts" 893 | ,"PREFIX/issue-17.ts"] 894 | [{"ref":{"fileName":"PREFIX/issue-17-import.ts" 895 | ,"textSpan":{"start":58 896 | ,"length":5} 897 | ,"isWriteAccess":true} 898 | ,"file":"PREFIX/issue-17-import.ts" 899 | ,"lineText":" public start(){" 900 | ,"min":{"line":7 901 | ,"character":12} 902 | ,"lim":{"line":7 903 | ,"character":17}} 904 | ,{"ref":{"fileName":"PREFIX/issue-17.ts" 905 | ,"textSpan":{"start":85 906 | ,"length":5} 907 | ,"isWriteAccess":false} 908 | ,"file":"PREFIX/issue-17.ts" 909 | ,"lineText":"second.start();" 910 | ,"min":{"line":4 911 | ,"character":8} 912 | ,"lim":{"line":4 913 | ,"character":13}}] 914 | [{"ref":{"fileName":"PREFIX/issue-17-import.ts" 915 | ,"textSpan":{"start":58 916 | ,"length":5} 917 | ,"isWriteAccess":true} 918 | ,"file":"PREFIX/issue-17-import.ts" 919 | ,"lineText":" public start(){" 920 | ,"min":{"line":7 921 | ,"character":12} 922 | ,"lim":{"line":7 923 | ,"character":17}} 924 | ,{"ref":{"fileName":"PREFIX/issue-17.ts" 925 | ,"textSpan":{"start":85 926 | ,"length":5} 927 | ,"isWriteAccess":false} 928 | ,"file":"PREFIX/issue-17.ts" 929 | ,"lineText":"second.start();" 930 | ,"min":{"line":4 931 | ,"character":8} 932 | ,"lim":{"line":4 933 | ,"character":13}}] 934 | ["PREFIX/issue-17-import.ts" 935 | ,"NODE_MODULES/typescript/lib/lib.d.ts" 936 | ,"PREFIX/issue-17.ts"] 937 | "no last error" 938 | "TSS closing" 939 | 940 | // stderr 941 | 942 | // concat-map.script 943 | node ../bin/tss.js empty.ts 944 | // stdout 945 | "loaded PREFIX/empty.ts, TSS listening.." 946 | "updated PREFIX/empty.ts, (0/0) errors" 947 | [] 948 | "TSS closing" 949 | 950 | // stderr 951 | 952 | // issue-52.script 953 | node ../bin/tss.js empty.ts 954 | // stdout 955 | "loaded PREFIX/empty.ts, TSS listening.." 956 | "updated PREFIX/empty.ts, (0/1) errors" 957 | [{"file":"PREFIX/empty.ts" 958 | ,"start":{"line":3 959 | ,"character":1} 960 | ,"end":{"line":3 961 | ,"character":6} 962 | ,"text":"Type 'number' is not assignable to type '{ \"werwer\": number; \"oo\": number[]; }'." 963 | ,"code":2322 964 | ,"phase":"Semantics" 965 | ,"category":"Error"}] 966 | "TSS closing" 967 | 968 | // stderr 969 | 970 | -------------------------------------------------------------------------------- /tests/test.script: -------------------------------------------------------------------------------- 1 | showErrors 2 | type 3 2 PREFIX/test.ts 3 | type 4 2 PREFIX/test.ts 4 | definition 3 2 PREFIX/test.ts 5 | definition 4 2 PREFIX/test.ts 6 | completions true 3 4 PREFIX/test.ts 7 | completions true 4 4 PREFIX/test.ts 8 | update 6 PREFIX/test.ts 9 | var x = {a:1,b:2}; 10 | var s = ""; 11 | var a = []; 12 | {s.to } 13 | {x. } 14 | {a. } 15 | dump - PREFIX/test.ts 16 | showErrors 17 | completions true 4 4 PREFIX/test.ts 18 | completions true 5 4 PREFIX/test.ts 19 | completions true 6 4 PREFIX/test.ts 20 | completions-brief true 6 4 PREFIX/test.ts 21 | completions true 4 6 PREFIX/test.ts 22 | -------------------------------------------------------------------------------- /tests/test.ts: -------------------------------------------------------------------------------- 1 | var x = {a:1,b:2}; 2 | var s = ""; 3 | {s. } 4 | {x. } 5 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | {"compilerOptions": {"target":"ES5","module":"commonjs"} } 2 | -------------------------------------------------------------------------------- /tests/update-nocheck-completion-chain.script: -------------------------------------------------------------------------------- 1 | update nocheck 3 empty.ts 2 | function foo(bar) { 3 | [].f 4 | } 5 | completions true 2 5 empty.ts 6 | update nocheck 4 empty.ts 7 | function foo(bar) { 8 | [].filter(x=>true) 9 | .m 10 | } 11 | completions true 3 5 empty.ts 12 | update nocheck 5 empty.ts 13 | function foo(bar) { 14 | [].filter(x=>true) 15 | .map(_=>'o') 16 | .fo 17 | } 18 | completions true 4 6 empty.ts 19 | update nocheck 5 empty.ts 20 | function foo(bar) { 21 | [].filter(x=>true) 22 | .map(_=>'o') 23 | .forEach(s=>s.toL) 24 | } 25 | completions true 4 20 empty.ts 26 | update nocheck 5 empty.ts 27 | function foo(bar) { 28 | [].filter(x=>true) 29 | .map(_=>'o') 30 | .forEach(s=>s.toLowerCase()+ba) 31 | } 32 | completions false 4 33 empty.ts 33 | -------------------------------------------------------------------------------- /tss.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Claus Reinke. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. 3 | // See LICENSE.txt in the project root for complete license information. 4 | 5 | /// 6 | 7 | import ts = require("typescript"); 8 | import harness = require("./harness"); 9 | import path = require("path"); 10 | 11 | function resolvePath(rpath) { 12 | return switchToForwardSlashes(path.resolve(rpath)); 13 | } 14 | 15 | function switchToForwardSlashes(path: string) { 16 | return path.replace(/\\/g, "/"); 17 | } 18 | 19 | // some approximated subsets.. 20 | interface ReadlineHandlers { 21 | on(event: string, listener: (event:string)=>void) : ReadlineHandlers; 22 | close() : void; 23 | } 24 | interface Readline { 25 | createInterface(options:any) : ReadlineHandlers; 26 | } 27 | 28 | // bypass import, we don't want to drop out of the global module; 29 | // use fixed readline (https://github.com/joyent/node/issues/3305), 30 | // fixed version should be in nodejs from about v0.9.9/v0.8.19? 31 | var readline:Readline = require("./readline"); 32 | 33 | var EOL = require("os").EOL; 34 | 35 | /** holds list of fileNames, ScriptInfos and ScriptSnapshots for LS host */ 36 | class FileCache { 37 | public ls: ts.LanguageService; 38 | public fileNames: string[] = []; 39 | public snapshots:ts.Map = {}; 40 | public fileNameToScript:ts.Map = {}; 41 | 42 | public getFileNames() { return this.fileNames; } 43 | 44 | /** 45 | * @param fileName resolved name of possibly cached file 46 | */ 47 | public getScriptInfo(fileName) { 48 | if (!this.fileNameToScript[fileName]) { 49 | this.fetchFile(fileName); 50 | } 51 | return this.fileNameToScript[fileName]; 52 | } 53 | 54 | /** 55 | * @param fileName resolved name of possibly cached file 56 | */ 57 | public getScriptSnapshot(fileName) { 58 | // console.log("getScriptSnapshot",fileName); 59 | if (!this.snapshots[fileName]) { 60 | this.fetchFile(fileName); 61 | } 62 | return this.snapshots[fileName]; 63 | } 64 | 65 | /** 66 | * @param fileName resolved file name 67 | * @param text file contents 68 | * @param isDefaultLib should fileName be listed first? 69 | */ 70 | public addFile(fileName,text,isDefaultLib=false) { 71 | if (isDefaultLib) { 72 | this.fileNames.push(fileName); 73 | } else { 74 | this.fileNames.unshift(fileName); 75 | } 76 | this.fileNameToScript[fileName] = new harness.ScriptInfo(fileName,text); 77 | this.snapshots[fileName] = new harness.ScriptSnapshot(this.getScriptInfo(fileName)); 78 | } 79 | 80 | /** 81 | * @param fileName resolved file name 82 | */ 83 | public fetchFile(fileName) { 84 | // console.log("fetchFile:",fileName); 85 | if (ts.sys.fileExists(fileName)) { 86 | this.addFile(fileName,ts.sys.readFile(fileName)); 87 | } else { 88 | // console.error ("tss: cannot fetch file: "+fileName); 89 | } 90 | } 91 | 92 | /** 93 | * @param fileName resolved name of cached file 94 | * @param line 1 based index 95 | * @param col 1 based index 96 | */ 97 | public lineColToPosition(fileName: string, line: number, col: number): number { 98 | var script: harness.ScriptInfo = this.getScriptInfo(fileName); 99 | 100 | return ts.getPositionOfLineAndCharacter(this.ls.getSourceFile(fileName),line-1, col-1); 101 | } 102 | 103 | /** 104 | * @param fileName resolved name of cached file 105 | * @returns {line,character} 1 based indices 106 | */ 107 | public positionToLineCol(fileName: string, position: number): ts.LineAndCharacter { 108 | var script: harness.ScriptInfo = this.getScriptInfo(fileName); 109 | 110 | var lineChar = ts.getLineAndCharacterOfPosition(this.ls.getSourceFile(fileName),position); 111 | 112 | return {line: lineChar.line+1, character: lineChar.character+1 }; 113 | } 114 | 115 | /** 116 | * @param fileName resolved name of cached file 117 | * @param line 1 based index 118 | */ 119 | public getLineText(fileName,line) { 120 | var source = this.ls.getSourceFile(fileName); 121 | var lineStart = ts.getPositionOfLineAndCharacter(source,line-1,0) 122 | var lineEnd = ts.getPositionOfLineAndCharacter(source,line,0)-1; 123 | var lineText = source.text.substring(lineStart,lineEnd); 124 | return lineText; 125 | } 126 | 127 | /** 128 | * @param fileName resolved name of possibly cached file 129 | * @param content new file contents 130 | */ 131 | public updateScript(fileName: string, content: string) { 132 | var script = this.getScriptInfo(fileName); 133 | if (script) { 134 | script.updateContent(content); 135 | this.snapshots[fileName] = new harness.ScriptSnapshot(script); 136 | } else { 137 | this.addFile(fileName,content); 138 | } 139 | } 140 | 141 | /** 142 | * @param fileName resolved name of cached file 143 | * @param minChar first char of edit range 144 | * @param limChar first char after edit range 145 | * @param newText new file contents 146 | */ 147 | public editScript(fileName: string, minChar: number, limChar: number, newText: string) { 148 | var script = this.getScriptInfo(fileName); 149 | if (script) { 150 | script.editContent(minChar, limChar, newText); 151 | this.snapshots[fileName] = new harness.ScriptSnapshot(script); 152 | return; 153 | } 154 | throw new Error("No script with name '" + fileName + "'"); 155 | } 156 | } 157 | 158 | /** TypeScript Services Server, 159 | an interactive commandline tool 160 | for getting info on .ts projects */ 161 | class TSS { 162 | public compilerOptions: ts.CompilerOptions; 163 | public compilerHost: ts.CompilerHost; 164 | public lsHost : ts.LanguageServiceHost; 165 | public ls : ts.LanguageService; 166 | public rootFiles : string[]; 167 | public lastError; 168 | 169 | constructor (public prettyJSON: boolean = false) { } // NOTE: call setup 170 | 171 | private fileCache: FileCache; 172 | 173 | /** collect syntactic and semantic diagnostics for all project files */ 174 | public getErrors(): ts.Diagnostic[] { 175 | 176 | var addPhase = phase => d => {d.phase = phase; return d}; 177 | var errors = []; 178 | this.fileCache.getFileNames().map( file=>{ 179 | if (!file.match(/^.*(\.ts)$/)) return; // exclude package.json 180 | var syntactic = this.ls.getSyntacticDiagnostics(file); 181 | var semantic = this.ls.getSemanticDiagnostics(file); 182 | // this.ls.languageService.getEmitOutput(file).diagnostics; 183 | errors = errors.concat(syntactic.map(addPhase("Syntax")) 184 | ,semantic.map(addPhase("Semantics"))); 185 | }); 186 | return errors; 187 | 188 | } 189 | 190 | /** flatten messageChain into string|string[] */ 191 | private messageChain(message:string|ts.DiagnosticMessageChain) { 192 | if (typeof message==="string") { 193 | return [message]; 194 | } else { 195 | return [message.messageText].concat(message.next?this.messageChain(message.next):[]); 196 | } 197 | } 198 | 199 | /** load file and dependencies, prepare language service for queries */ 200 | public setup(files,options) { 201 | this.fileCache = new FileCache(); 202 | 203 | this.rootFiles = files.map(file=>resolvePath(file)); 204 | 205 | this.compilerOptions = options; 206 | this.compilerHost = ts.createCompilerHost(options); 207 | 208 | //TODO: diagnostics 209 | 210 | // prime fileCache with root files and defaultLib 211 | var seenNoDefaultLib = options.noLib; 212 | this.rootFiles.forEach(file=>{ 213 | var source = this.compilerHost.getSourceFile(file,options.target); 214 | if (source) { 215 | seenNoDefaultLib = seenNoDefaultLib || source.hasNoDefaultLib; 216 | this.fileCache.addFile(file,source.text); 217 | } else { 218 | throw ("tss cannot find file: "+file); 219 | } 220 | }); 221 | if (!seenNoDefaultLib) { 222 | var defaultLibFileName = this.compilerHost.getDefaultLibFileName(options); 223 | var source = this.compilerHost.getSourceFile(defaultLibFileName,options.target); 224 | this.fileCache.addFile(defaultLibFileName,source.text); 225 | } 226 | 227 | // Get a language service 228 | // internally builds programs from root files, 229 | // chases dependencies (references and imports), ... 230 | // (NOTE: files are processed on demand, loaded via lsHost, cached in fileCache) 231 | this.lsHost = { 232 | getCompilationSettings : ()=>this.compilerOptions, 233 | getScriptFileNames : ()=>this.fileCache.getFileNames(), 234 | getScriptVersion : (fileName: string)=>this.fileCache.getScriptInfo(fileName).version.toString(), 235 | //getScriptIsOpen : (fileName: string)=>this.fileCache.getScriptInfo(fileName).isOpen, 236 | getScriptSnapshot : (fileName: string)=>this.fileCache.getScriptSnapshot(fileName), 237 | getCurrentDirectory : ()=>ts.sys.getCurrentDirectory(), 238 | getDefaultLibFileName : 239 | (options: ts.CompilerOptions)=>ts.getDefaultLibFileName(options), 240 | log : (message)=>undefined, // ?? 241 | trace : (message)=>undefined, // ?? 242 | error : (message)=>console.error(message) // ?? 243 | }; 244 | this.ls = ts.createLanguageService(this.lsHost,ts.createDocumentRegistry()); 245 | this.fileCache.ls = this.ls; 246 | } 247 | 248 | /** output value/object as JSON, excluding irrelevant properties, 249 | * with optional pretty-printing controlled by this.prettyJSON 250 | * @param info thing to output 251 | * @param excludes Array of property keys to exclude 252 | */ 253 | private output(info,excludes=["displayParts"]) { 254 | var replacer = (k,v)=>excludes.indexOf(k)!==-1?undefined:v; 255 | if (info) { 256 | console.log(JSON.stringify(info,replacer,this.prettyJSON?" ":undefined).trim()); 257 | } else { 258 | console.log(JSON.stringify(info,replacer)); 259 | } 260 | } 261 | 262 | private outputJSON(json) { 263 | console.log(json.trim()); 264 | } 265 | 266 | /** recursively prepare navigationBarItems for JSON output */ 267 | private handleNavBarItem(file:string,item:ts.NavigationBarItem) { 268 | // TODO: under which circumstances can item.spans.length be other than 1? 269 | return { info: [item.kindModifiers,item.kind,item.text].filter(s=>s!=="").join(" ") 270 | , kindModifiers : item.kindModifiers 271 | , kind: item.kind 272 | , text: item.text 273 | , min: this.fileCache.positionToLineCol(file,item.spans[0].start) 274 | , lim: this.fileCache.positionToLineCol(file,item.spans[0].start+item.spans[0].length) 275 | , childItems: item.childItems.map(item=>this.handleNavBarItem(file,item)) 276 | }; 277 | } 278 | 279 | /** commandline server main routine: commands in, JSON info out */ 280 | public listen() { 281 | var line: number; 282 | var col: number; 283 | 284 | var rl = readline.createInterface({input:process.stdin,output:process.stdout}); 285 | 286 | var cmd:string, pos:number, file:string, script, added:boolean, range:boolean, check:boolean 287 | , def, refs:ts.ReferenceEntry[], locs:ts.DefinitionInfo[], info, source:ts.SourceFile 288 | , brief, member:boolean, navbarItems:ts.NavigationBarItem[], pattern:string; 289 | 290 | var collecting = 0, on_collected_callback:()=>void, lines:string[] = []; 291 | 292 | var commands = {}; 293 | function match(cmd,regexp) { 294 | commands[regexp.source] = true; 295 | return cmd.match(regexp); 296 | } 297 | 298 | rl.on('line', input => { // most commands are one-liners 299 | var m:string[]; 300 | try { 301 | 302 | cmd = input.trim(); 303 | 304 | if (collecting>0) { // multiline input, eg, source 305 | 306 | lines.push(input) 307 | collecting--; 308 | 309 | if (collecting===0) { 310 | on_collected_callback(); 311 | } 312 | 313 | } else if (m = match(cmd,/^signature (\d+) (\d+) (.*)$/)) { // only within call parameters? 314 | 315 | (()=>{ 316 | line = parseInt(m[1]); 317 | col = parseInt(m[2]); 318 | file = resolvePath(m[3]); 319 | 320 | pos = this.fileCache.lineColToPosition(file,line,col); 321 | 322 | info = this.ls.getSignatureHelpItems(file,pos); 323 | 324 | var param = p=>({name:p.name 325 | ,isOptional:p.isOptional 326 | ,type:ts.displayPartsToString(p.displayParts)||"" 327 | ,docComment:ts.displayPartsToString(p.documentation)||"" 328 | }); 329 | 330 | info && (info.items = info.items 331 | .map(item=>({prefix: ts.displayPartsToString(item.prefixDisplayParts)||"" 332 | ,separator: ts.displayPartsToString(item.separatorDisplayParts)||"" 333 | ,suffix: ts.displayPartsToString(item.suffixDisplayParts)||"" 334 | ,parameters: item.parameters.map(param) 335 | ,docComment: ts.displayPartsToString(item.documentation)||"" 336 | })) 337 | ); 338 | 339 | this.output(info); 340 | })(); 341 | 342 | } else if (m = match(cmd,/^(type|quickInfo) (\d+) (\d+) (.*)$/)) { // "type" deprecated 343 | 344 | line = parseInt(m[2]); 345 | col = parseInt(m[3]); 346 | file = resolvePath(m[4]); 347 | 348 | pos = this.fileCache.lineColToPosition(file,line,col); 349 | 350 | info = (this.ls.getQuickInfoAtPosition(file, pos)||{}); 351 | info.type = ((info&&ts.displayPartsToString(info.displayParts))||""); 352 | info.docComment = ((info&&ts.displayPartsToString(info.documentation))||""); 353 | 354 | this.output(info); 355 | 356 | } else if (m = match(cmd,/^definition (\d+) (\d+) (.*)$/)) { 357 | 358 | line = parseInt(m[1]); 359 | col = parseInt(m[2]); 360 | file = resolvePath(m[3]); 361 | 362 | pos = this.fileCache.lineColToPosition(file,line,col); 363 | locs = this.ls.getDefinitionAtPosition(file, pos); // NOTE: multiple definitions 364 | 365 | info = locs && locs.map( def => ({ 366 | def : def, 367 | file : def && def.fileName, 368 | min : def && this.fileCache.positionToLineCol(def.fileName,def.textSpan.start), 369 | lim : def && this.fileCache.positionToLineCol(def.fileName,ts.textSpanEnd(def.textSpan)) 370 | })); 371 | 372 | // TODO: what about multiple definitions? 373 | this.output((locs && info[0])||null); 374 | 375 | } else if (m = match(cmd,/^(references|occurrences) (\d+) (\d+) (.*)$/)) { 376 | 377 | line = parseInt(m[2]); 378 | col = parseInt(m[3]); 379 | file = resolvePath(m[4]); 380 | 381 | pos = this.fileCache.lineColToPosition(file,line,col); 382 | switch (m[1]) { 383 | case "references": 384 | refs = this.ls.getReferencesAtPosition(file, pos); 385 | break; 386 | case "occurrences": 387 | refs = this.ls.getOccurrencesAtPosition(file, pos); 388 | break; 389 | default: 390 | throw "cannot happen"; 391 | } 392 | 393 | info = (refs || []).map( ref => { 394 | var start, end, fileName, lineText; 395 | if (ref) { 396 | start = this.fileCache.positionToLineCol(ref.fileName,ref.textSpan.start); 397 | end = this.fileCache.positionToLineCol(ref.fileName,ts.textSpanEnd(ref.textSpan)); 398 | fileName = resolvePath(ref.fileName); 399 | lineText = this.fileCache.getLineText(fileName,start.line); 400 | } 401 | return { 402 | ref : ref, 403 | file : ref && ref.fileName, 404 | lineText : lineText, 405 | min : start, 406 | lim : end 407 | }} ); 408 | 409 | this.output(info); 410 | 411 | } else if (m = match(cmd,/^navigationBarItems (.*)$/)) { 412 | 413 | file = resolvePath(m[1]); 414 | 415 | this.output(this.ls.getNavigationBarItems(file) 416 | .map(item=>this.handleNavBarItem(file,item))); 417 | 418 | } else if (m = match(cmd,/^navigateToItems (.*)$/)) { 419 | 420 | pattern = m[1]; 421 | 422 | info = this.ls.getNavigateToItems(pattern) 423 | .map(item=>{ 424 | item['min'] = this.fileCache.positionToLineCol(item.fileName 425 | ,item.textSpan.start); 426 | item['lim'] = this.fileCache.positionToLineCol(item.fileName 427 | ,item.textSpan.start 428 | +item.textSpan.length); 429 | return item; 430 | }); 431 | 432 | this.output(info); 433 | 434 | } else if (m = match(cmd,/^completions(-brief)?( true| false)? (\d+) (\d+) (.*)$/)) { 435 | 436 | brief = m[1]; 437 | line = parseInt(m[3]); 438 | col = parseInt(m[4]); 439 | file = resolvePath(m[5]); 440 | 441 | pos = this.fileCache.lineColToPosition(file,line,col); 442 | 443 | info = this.ls.getCompletionsAtPosition(file, pos) || null; 444 | 445 | if (info) { 446 | // fill in completion entry details, unless briefness requested 447 | !brief && (info.entries = info.entries.map( e =>{ 448 | var d = this.ls.getCompletionEntryDetails(file,pos,e.name); 449 | if (d) { 450 | d["type"] =ts.displayPartsToString(d.displayParts); 451 | d["docComment"]=ts.displayPartsToString(d.documentation); 452 | return d; 453 | } else { 454 | return e; 455 | }} )); 456 | // NOTE: details null for primitive type symbols, see TS #1592 457 | 458 | (()=>{ // filter entries by prefix, determined by pos 459 | var languageVersion = this.compilerOptions.target; 460 | var source = this.fileCache.getScriptInfo(file).content; 461 | var startPos = pos; 462 | var idPart = p => /[0-9a-zA-Z_$]/.test(source[p]) 463 | || ts.isIdentifierPart(source.charCodeAt(p),languageVersion); 464 | var idStart = p => /[a-zA-Z_$]/.test(source[p]) 465 | || ts.isIdentifierStart(source.charCodeAt(p),languageVersion); 466 | while ((--startPos>=0) && idPart(startPos) ); 467 | if ((++startPos < pos) && idStart(startPos)) { 468 | var prefix = source.slice(startPos,pos); 469 | info["prefix"] = prefix; 470 | var len = prefix.length; 471 | info.entries = info.entries.filter( e => e.name.substr(0,len)===prefix ); 472 | } 473 | })(); 474 | } 475 | 476 | this.output(info,["displayParts","documentation","sortText"]); 477 | 478 | } else if (m = match(cmd,/^update( nocheck)? (\d+)( (\d+)-(\d+))? (.*)$/)) { // send non-saved source 479 | 480 | file = resolvePath(m[6]); 481 | source = this.ls.getSourceFile(file); 482 | script = this.fileCache.getScriptInfo(file); 483 | added = !script; 484 | range = !!m[3] 485 | check = !m[1] 486 | 487 | if (!added || !range) { 488 | collecting = parseInt(m[2]); 489 | on_collected_callback = () => { 490 | 491 | if (!range) { 492 | this.fileCache.updateScript(file,lines.join(EOL)); 493 | } else { 494 | var startLine = parseInt(m[4]); 495 | var endLine = parseInt(m[5]); 496 | var maxLines = source.getLineStarts().length; 497 | var startPos = startLine<=maxLines 498 | ? (startLine<1 ? 0 : this.fileCache.lineColToPosition(file,startLine,1)) 499 | : script.content.length; 500 | var endPos = endLine { 527 | if (d.file) { 528 | 529 | var file = resolvePath(d.file.fileName); 530 | var lc = this.fileCache.positionToLineCol(file,d.start); 531 | var len = this.fileCache.getScriptInfo(file).content.length; 532 | var end = Math.min(len,d.start+d.length); 533 | // NOTE: clamped to end of file (#11) 534 | var lc2 = this.fileCache.positionToLineCol(file,end); 535 | return { 536 | file: file, 537 | start: {line: lc.line, character: lc.character}, 538 | end: {line: lc2.line, character: lc2.character}, 539 | text: this.messageChain(d.messageText).join(EOL), 540 | code: d.code, 541 | phase: d["phase"], 542 | category: ts.DiagnosticCategory[d.category] 543 | }; 544 | 545 | } else { // global diagnostics have no file 546 | 547 | return { 548 | text: this.messageChain(d.messageText).join(EOL), 549 | code: d.code, 550 | phase: d["phase"], 551 | category: ts.DiagnosticCategory[d.category] 552 | }; 553 | 554 | } 555 | } 556 | ); 557 | 558 | this.output(info); 559 | 560 | } else if (m = match(cmd,/^files$/)) { // list files in project 561 | 562 | info = this.lsHost.getScriptFileNames(); // TODO: files are pre-resolved 563 | 564 | this.output(info); 565 | 566 | } else if (m = match(cmd,/^lastError(Dump)?$/)) { // debugging only 567 | 568 | if (this.lastError) 569 | if (m[1]) // commandline use 570 | console.log(JSON.parse(this.lastError).stack); 571 | else 572 | this.outputJSON(this.lastError); 573 | else 574 | this.outputJSON('"no last error"'); 575 | 576 | } else if (m = match(cmd,/^dump (\S+) (.*)$/)) { // debugging only 577 | 578 | (()=>{ 579 | var dump = m[1]; 580 | var file = resolvePath(m[2]); 581 | 582 | var sourceText = this.fileCache.getScriptInfo(file).content; 583 | if (dump==="-") { // to console 584 | console.log('dumping '+file); 585 | console.log(sourceText); 586 | } else { // to file 587 | ts.sys.writeFile(dump,sourceText,false); 588 | 589 | this.outputJSON('"dumped '+file+' to '+dump+'"'); 590 | } 591 | })(); 592 | 593 | } else if (m = match(cmd,/^reload$/)) { // reload current project 594 | 595 | // TODO: keep updated (in-memory-only) files? 596 | this.setup(this.rootFiles,this.compilerOptions); 597 | this.outputJSON(this.listeningMessage('reloaded')); 598 | 599 | } else if (m = match(cmd,/^quit$/)) { 600 | 601 | rl.close(); 602 | 603 | } else if (m = match(cmd,/^prettyJSON (true|false)$/)) { // useful for debugging 604 | 605 | this.prettyJSON = m[1]==='true'; 606 | 607 | this.outputJSON('"pretty JSON: '+this.prettyJSON+'"'); 608 | 609 | } else if (m = match(cmd,/^help$/)) { 610 | 611 | console.log(Object.keys(commands).join(EOL)); 612 | 613 | } else { 614 | 615 | this.outputJSON('"TSS command syntax error: '+cmd+'"'); 616 | 617 | } 618 | 619 | } catch(e) { 620 | 621 | this.lastError = (JSON.stringify({msg:e.toString(),stack:e.stack})).trim(); 622 | this.outputJSON('"TSS command processing error: '+e+'"'); 623 | 624 | } 625 | 626 | }).on('close', () => { 627 | 628 | this.outputJSON('"TSS closing"'); 629 | 630 | }); 631 | 632 | this.outputJSON(this.listeningMessage('loaded')); 633 | 634 | } 635 | 636 | private listeningMessage(prefix) { 637 | var count = this.rootFiles.length-1; 638 | var more = count>0 ? ' (+'+count+' more)' : ''; 639 | return '"'+prefix+' '+this.rootFiles[0]+more+', TSS listening.."'; 640 | } 641 | } 642 | 643 | function extend(o1,o2) { 644 | var o = {}; 645 | for(var p in o1) { o[p] = o1[p] } 646 | for(var p in o2) { if(!(p in o)) o[p] = o2[p] } 647 | return o; 648 | } 649 | 650 | var fileNames; 651 | var configFile, configObject, configObjectParsed; 652 | 653 | // NOTE: partial options support only 654 | var commandLine = ts.parseCommandLine(ts.sys.args); 655 | 656 | if (commandLine.options.version) { 657 | console.log(require("../package.json").version); 658 | process.exit(0); 659 | } 660 | 661 | if (commandLine.fileNames.length>0) { 662 | 663 | fileNames = commandLine.fileNames; 664 | 665 | } else if (commandLine.options.project) { 666 | 667 | configFile = path.join(commandLine.options.project,"tsconfig.json"); 668 | 669 | } else { 670 | 671 | configFile = ts.findConfigFile(path.normalize(ts.sys.getCurrentDirectory()),ts.sys.fileExists); 672 | 673 | } 674 | 675 | var options; 676 | 677 | if (configFile) { 678 | 679 | configObject = ts.readConfigFile(configFile,ts.sys.readFile); 680 | 681 | if (!configObject) { 682 | console.error("can't read tsconfig.json at",configFile); 683 | process.exit(1); 684 | } 685 | 686 | configObjectParsed = ts.parseJsonConfigFileContent(configObject,ts.sys,path.dirname(configFile)); 687 | 688 | if (configObjectParsed.errors.length>0) { 689 | console.error(configObjectParsed.errors); 690 | process.exit(1); 691 | } 692 | 693 | fileNames = configObjectParsed.fileNames; 694 | options = extend(commandLine.options,configObjectParsed.options); 695 | 696 | } else { 697 | 698 | options = extend(commandLine.options,ts.getDefaultCompilerOptions()); 699 | 700 | } 701 | 702 | if (!fileNames) { 703 | console.error("can't find project root"); 704 | console.error("please specify root source file"); 705 | console.error(" or --project directory (containing a tsconfig.json)"); 706 | process.exit(1); 707 | } 708 | 709 | var tss = new TSS(); 710 | try { 711 | tss.setup(fileNames,options); 712 | tss.listen(); 713 | } catch (e) { 714 | console.error(e.toString()); 715 | process.exit(1); 716 | } 717 | --------------------------------------------------------------------------------