├── .clang-format ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.js ├── index.js ├── lib ├── base.ts ├── dart_libraries_for_browser_types.ts ├── declaration.ts ├── facade_converter.ts ├── json │ ├── conversions.ts │ ├── converted_syntax_kinds.ts │ ├── expression_with_type_arguments.ts │ ├── heritage_clause.ts │ ├── module_declarations │ │ ├── export_assignment.ts │ │ ├── export_declaration.ts │ │ ├── import_declaration.ts │ │ ├── index.ts │ │ └── module_declaration.ts │ ├── named_declarations │ │ ├── accessor_declarations.ts │ │ ├── call_signature_declaration.ts │ │ ├── class_declaration.ts │ │ ├── construct_signature_declaration.ts │ │ ├── constructor_declaration.ts │ │ ├── function_declaration.ts │ │ ├── index.ts │ │ ├── interface_declaration.ts │ │ ├── member_declaration.ts │ │ ├── named_declaration.ts │ │ ├── parameter_declaration.ts │ │ ├── signature_declaration.ts │ │ ├── type_alias_declaration.ts │ │ ├── type_parameter_declaration.ts │ │ └── variable_declaration.ts │ ├── node.ts │ ├── source_file.ts │ ├── types │ │ ├── function_type.ts │ │ ├── index.ts │ │ ├── keyword_type.ts │ │ ├── literal_type.ts │ │ ├── type.ts │ │ ├── type_literal.ts │ │ └── type_reference.ts │ └── variable_statement.ts ├── main.ts ├── merge.ts ├── mkdirp.ts ├── module.ts └── type.ts ├── package-lock.json ├── package.json ├── test ├── call_test.ts ├── declaration_test.ts ├── decorator_test.ts ├── expression_test.ts ├── facade_converter_test.ts ├── function_test.ts ├── js_interop_test.ts ├── json │ ├── declarations │ │ ├── class_test.ts │ │ ├── interface_test.ts │ │ └── variable_test.ts │ ├── json_test_support.ts │ ├── merge_test.ts │ └── module_test.ts ├── main_test.ts ├── module_test.ts ├── test_support.ts └── type_test.ts ├── tsconfig.json └── tslint.json /.clang-format: -------------------------------------------------------------------------------- 1 | Language: JavaScript 2 | BasedOnStyle: Google 3 | ColumnLimit: 100 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled JS 2 | build 3 | 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | .phutil_module_cache 13 | 14 | # Type definitions installed with tsd 15 | typings 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | node_modules 31 | 32 | # Users Environment Variables 33 | .lock-wscript 34 | 35 | # IDEs 36 | .idea/ 37 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | typings 2 | .idea 3 | .clang-format 4 | .travis.yml 5 | tsd.json 6 | build/test 7 | build/e2e 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "10" 5 | before_install: 6 | - 'if [ -n "$TSD_GITHUB_TOKEN" ]; then echo {\"token\": \"$TSD_GITHUB_TOKEN\"} > .tsdrc; fi' 7 | cache: 8 | directories: 9 | - node_modules 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ---- 2 | > **Warning** 3 | 4 | > # This repository is no longer being maintained and is archived. 5 | 6 | > For details on how to write interop interfaces using `package:js`, please visit the [package:js pub page](https://pub.dev/packages/js) or the [Dart documentation for JS interop](https://dart.dev/web/js-interop). The Dart team is working on improving JS interop in the future to make writing such interfaces easier. If there is interest in continuing this repository, feel free to contribute by forking. 7 | ---- 8 | 9 | Generates `package:js` JavaScript interop facades for arbitrary TypeScript libraries. 10 | 11 | ## Installation 12 | 13 | - [Install Node.js](https://docs.npmjs.com/getting-started/installing-node) 14 | - We depend on Node.js so that we can analyze TypeScript files using the [TypeScript Compiler API](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API) in the [TypeScript](https://www.npmjs.com/package/typescript) package. This ensures we parse `d.ts` consistently with other tools. 15 | - Execute `npm install -g dart_js_facade_gen` to install. 16 | 17 | ## Usage 18 | 19 | ### Basic 20 | `dart_js_facade_gen `
21 | Dart interop facade file is written to stdout. 22 | 23 | ### Advanced 24 | `dart_js_facade_gen --destination= --base-path= ...` 25 | 26 | #### Flags 27 | `--destination=`: Output generated code to destination-dir.
28 | `--base-path=`: Specify the directory that contains the input d.ts files.
29 | `--skip-formatting`: Skips running dart-format on the output. This is useful for large files (like dom.d.ts) since the node package version of dart-format is significantly slower than the version in the SDK.
30 | `--generate-html`: Generate facades for dart:html types rather than importing them.
31 | `--rename-conflicting-types`: Rename types to avoid conflicts in cases where a variable and a type have the exact same name, but it is not clear if they are related or not.
32 | `--explicit-static`: Disables default assumption that properties declared on the anonymous types of top level variable declarations are static.
33 | `--trust-js-types`: Emits @anonymous tags on classes that have neither constructors nor static members. This prevents the Dart Dev Compiler from checking whether or not objects are truly instances of those classes. This flag should be used if the input JS/TS library has structural types, or is otherwise claiming that types match in cases where the correct JS prototype is not there for DDC to check against. 34 | 35 | ### Example 36 | `dart_js_facade_gen --destination=/usr/foo/tmp/chartjs/lib --base-path=/usr/foo/git/DefinitelyTyped/chartjs /usr/foo/git/DefinitelyTyped/chartjs/chart.d.ts` 37 | 38 | ### Gulp tasks 39 | 40 | - `gulp watch` executes the unit tests in watch mode (use `gulp test.unit` for a single run), 41 | - `gulp test.check-format` checks the source code formatting using `clang-format`, 42 | - `gulp test` runs unit tests and checks the source code formatting. 43 | 44 | ### Publish 45 | 46 | - `npm run prepublish` 47 | - `npm publish` 48 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('source-map-support').install(); 2 | 3 | var clangFormat = require('clang-format'); 4 | var formatter = require('gulp-clang-format'); 5 | var gulp = require('gulp'); 6 | var gutil = require('gulp-util'); 7 | var merge = require('merge2'); 8 | var mocha = require('gulp-mocha'); 9 | var sourcemaps = require('gulp-sourcemaps'); 10 | var ts = require('gulp-typescript'); 11 | var typescript = require('typescript'); 12 | var tslint = require('gulp-tslint'); 13 | 14 | function checkFormat() { 15 | return gulp.src(['*.js', 'lib/**/*.ts', 'test/**/*.ts']) 16 | .pipe(formatter.checkFormat('file', clangFormat)) 17 | .on('warning', onError); 18 | } 19 | exports['test.check-format'] = checkFormat; 20 | 21 | function checkLint() { 22 | return gulp.src(['lib/**/*.ts', 'test/**/*.ts']) 23 | .pipe(tslint({formatter: 'verbose'})) 24 | .pipe(tslint.report()) 25 | .on('warning', onError); 26 | } 27 | exports['test.check-lint'] = checkLint; 28 | 29 | var hasError; 30 | var failOnError = true; 31 | 32 | var onError = function(err) { 33 | hasError = true; 34 | gutil.log(err.message); 35 | if (failOnError) { 36 | process.exit(1); 37 | } 38 | }; 39 | 40 | var tsProject = 41 | ts.createProject('tsconfig.json', {noEmit: false, declaration: true, typescript: typescript}); 42 | 43 | gulp.task('compile', () => { 44 | hasError = false; 45 | var tsResult = gulp.src(['lib/**/*.ts', 'node_modules/typescript/lib/typescript.d.ts']) 46 | .pipe(sourcemaps.init()) 47 | .pipe(tsProject()) 48 | .on('error', onError); 49 | return merge([ 50 | tsResult.dts.pipe(gulp.dest('build/definitions')), 51 | // Write external sourcemap next to the js file 52 | tsResult.js.pipe(sourcemaps.write('.')).pipe(gulp.dest('build/lib')), 53 | tsResult.js.pipe(gulp.dest('build/lib')), 54 | ]); 55 | }); 56 | 57 | gulp.task('test.compile', gulp.series('compile', function(done) { 58 | if (hasError) { 59 | done(); 60 | return; 61 | } 62 | return gulp.src(['test/**/*.ts', 'node_modules/dart-style/dart-style.d.ts'], {base: '.'}) 63 | .pipe(sourcemaps.init()) 64 | .pipe(tsProject()) 65 | .on('error', onError) 66 | .js.pipe(sourcemaps.write()) 67 | .pipe(gulp.dest('build/')); // '/test/' comes from base above. 68 | })); 69 | 70 | unitTests = gulp.series('test.compile', function(done) { 71 | if (hasError) { 72 | done(); 73 | return; 74 | } 75 | return gulp.src('build/test/**/*.js').pipe(mocha({ 76 | timeout: 4000, // Needed by the type-based tests :-( 77 | fullTrace: true, 78 | })); 79 | }); 80 | exports['test.unit'] = unitTests; 81 | 82 | gulp.task('test', gulp.series(checkFormat, checkLint, unitTests)); 83 | 84 | gulp.task('watch', gulp.series(unitTests, function() { 85 | failOnError = false; 86 | // Avoid watching generated .d.ts in the build (aka output) directory. 87 | return gulp.watch(['lib/**/*.ts', 'test/**/*.ts'], {ignoreInitial: true}, ['test.unit']); 88 | })); 89 | 90 | gulp.task('default', gulp.series('compile')); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const main = require('./build/lib/main.js'); 4 | 5 | var args = require('minimist')(process.argv.slice(2), { 6 | string: ['base-path'], 7 | boolean: [ 8 | 'semantic-diagnostics', 'skip-formatting', 'generate-html', 'rename-conflicting-types', 9 | 'explicit-static', 'trust-js-types', 'to-json' 10 | ], 11 | default: {'base-path': ''}, 12 | alias: { 13 | 'base-path': 'basePath', 14 | 'semantic-diagnostics': 'semanticDiagnostics', 15 | 'skip-formatting': 'skipFormatting', 16 | 'generate-html': 'generateHTML', 17 | 'rename-conflicting-types': 'renameConflictingTypes', 18 | 'explicit-static': 'explicitStatic', 19 | 'trust-js-types': 'trustJSTypes', 20 | 'to-json': 'toJSON' 21 | } 22 | }); 23 | try { 24 | var transpiler = new main.Transpiler(args); 25 | if (args.destination) console.error('Transpiling', args._, 'to', args.destination); 26 | transpiler.transpile(args._, args.destination); 27 | } catch (e) { 28 | if (e.name !== 'DartFacadeError') throw e; 29 | console.error(e.message); 30 | process.exit(1); 31 | } 32 | -------------------------------------------------------------------------------- /lib/base.ts: -------------------------------------------------------------------------------- 1 | import * as dartStyle from 'dart-style'; 2 | import * as path from 'path'; 3 | import * as ts from 'typescript'; 4 | 5 | import {OutputContext, Transpiler} from './main'; 6 | 7 | /** 8 | * Map from identifier name to resolved type. 9 | * Example: 'E' should map to a TypeNode for number when resolving a usage of MyArray 10 | * where MyArray is the alias type: 11 | * type MyArray = Array 12 | */ 13 | export type ResolvedTypeMap = Map; 14 | 15 | /*** 16 | * Options for how TypeScript types are represented as Dart types. 17 | */ 18 | export interface TypeDisplayOptions { 19 | /// We are displaying the type inside a comment so we don't have to restrict to valid Dart syntax. 20 | /// For example, we can display string literal type using the regular TypeScript syntax. 21 | /// 22 | /// Example: 23 | /// TypeScript type: number|string 24 | /// Dart inside comment: num|String 25 | /// Dart outside comment: dynamic /* num|string */ 26 | insideComment?: boolean; 27 | 28 | /// Dart has additional restrictions for what types are valid to emit inside a type argument. For 29 | /// example, "void" is not valid inside a type argument so Null has to be used instead. 30 | /// 31 | /// Example: 32 | /// TypeScript type: Foo 33 | /// Dart inside type argument: Foo 34 | /// Dart outside type argument: N/A 35 | /// TypeScript type: bar():void 36 | /// Dart inside type argument: N/A 37 | /// Dart outside type argument: void bar(); 38 | insideTypeArgument?: boolean; 39 | 40 | /// Indicates that we should not append an additional comment indicating what the true TypeScript 41 | /// type was for cases where Dart cannot express the type precisely. 42 | /// 43 | /// Example: 44 | /// TypeScript type: number|string 45 | /// Dart hide comment: dynamic 46 | /// Dart show comment: dynamic /*number|string*/ 47 | hideComment?: boolean; 48 | 49 | /** 50 | * Type arguments associated with the current type to display. 51 | * Arguments are emitted directly in normal cases but in the case of type aliases we have to 52 | * propagate and substitute type arguments. 53 | */ 54 | typeArguments?: ts.NodeArray; 55 | 56 | /** 57 | * Parameter declarations to substitute. This is required to support type aliases with type 58 | * arguments that are not representable in Dart. 59 | */ 60 | resolvedTypeArguments?: ResolvedTypeMap; 61 | } 62 | 63 | /** 64 | * Summary information on what is imported via a particular import. 65 | */ 66 | export class ImportSummary { 67 | showAll = false; 68 | shown: Set = new Set(); 69 | asPrefix: string; 70 | } 71 | 72 | export type Constructor = ts.ConstructorDeclaration|ts.ConstructSignatureDeclaration; 73 | export type ClassLike = ts.ClassLikeDeclaration|ts.InterfaceDeclaration; 74 | 75 | /** 76 | * Interface extending the true InterfaceDeclaration interface to add optional state we store on 77 | * interfaces to simplify conversion to Dart classes. 78 | */ 79 | export interface ExtendedInterfaceDeclaration extends ts.InterfaceDeclaration { 80 | /** 81 | * The type associated with this interface that we want to treat as the concrete location of this 82 | * interface to enable interfaces that act like constructors. Because Dart does not permit calling 83 | * objects like constructors we have to add this workaround. 84 | */ 85 | constructedType?: ts.InterfaceDeclaration|ts.TypeLiteralNode; 86 | } 87 | 88 | export function ident(n: ts.Node): string { 89 | if (ts.isIdentifier(n) || ts.isStringLiteralLike(n)) { 90 | return n.text; 91 | } 92 | if (ts.isQualifiedName(n)) { 93 | const leftName = ident(n.left); 94 | if (leftName) { 95 | return leftName + '.' + ident(n.right); 96 | } 97 | } 98 | return null; 99 | } 100 | 101 | export function isFunctionTypedefLikeInterface(ifDecl: ts.InterfaceDeclaration): boolean { 102 | return ifDecl.members && ifDecl.members.length === 1 && 103 | ts.isCallSignatureDeclaration(ifDecl.members[0]); 104 | } 105 | 106 | export function isExtendsClause(heritageClause: ts.HeritageClause) { 107 | return heritageClause.token === ts.SyntaxKind.ExtendsKeyword && 108 | !ts.isInterfaceDeclaration(heritageClause.parent); 109 | } 110 | 111 | export function isConstructor(n: ts.Node): n is Constructor { 112 | return ts.isConstructorDeclaration(n) || ts.isConstructSignatureDeclaration(n); 113 | } 114 | 115 | export function isStatic(n: ts.Node): boolean { 116 | let hasStatic = false; 117 | ts.forEachChild(n, (child) => { 118 | if (child.kind === ts.SyntaxKind.StaticKeyword) { 119 | hasStatic = true; 120 | } 121 | }); 122 | return hasStatic; 123 | } 124 | 125 | export function isReadonly(n: ts.Node): boolean { 126 | let hasReadonly = false; 127 | ts.forEachChild(n, (child) => { 128 | if (child.kind === ts.SyntaxKind.ReadonlyKeyword) { 129 | hasReadonly = true; 130 | } 131 | }); 132 | return hasReadonly; 133 | } 134 | 135 | export function isCallableType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { 136 | if (isFunctionType(type, tc)) return true; 137 | if (ts.isTypeReferenceNode(type)) { 138 | if (tc.getSignaturesOfType(tc.getTypeAtLocation(type), ts.SignatureKind.Call).length > 0) 139 | return true; 140 | } 141 | return false; 142 | } 143 | 144 | /** 145 | * Returns whether a type declaration is on we can generate a named Dart type for. 146 | * For unsupported alias types we need to manually substitute the expression 147 | * the alias corresponds to in call sites. 148 | */ 149 | export function supportedTypeDeclaration(decl: ts.Declaration): boolean { 150 | if (ts.isTypeAliasDeclaration(decl)) { 151 | let type = decl.type; 152 | return ts.isTypeLiteralNode(type) || ts.isFunctionTypeNode(type); 153 | } 154 | return true; 155 | } 156 | 157 | export function isFunctionType(type: ts.TypeNode, tc: ts.TypeChecker): boolean { 158 | if (ts.isFunctionTypeNode(type)) return true; 159 | if (ts.isTypeReferenceNode(type)) { 160 | let t = tc.getTypeAtLocation(type); 161 | if (t.symbol && t.symbol.flags & ts.SymbolFlags.Function) return true; 162 | } 163 | 164 | if (ts.isIntersectionTypeNode(type)) { 165 | let types = type.types; 166 | for (let i = 0; i < types.length; ++i) { 167 | if (isFunctionType(types[i], tc)) { 168 | return true; 169 | } 170 | } 171 | return false; 172 | } 173 | 174 | if (ts.isUnionTypeNode(type)) { 175 | let types = type.types; 176 | for (let i = 0; i < types.length; ++i) { 177 | if (!isFunctionType(types[i], tc)) { 178 | return false; 179 | } 180 | } 181 | return true; 182 | } 183 | // Warning: if the kind is a reference type and the reference is to an 184 | // interface that only has a call member we will not return that it is a 185 | // function type. 186 | if (ts.isTypeLiteralNode(type)) { 187 | let members = type.members; 188 | for (let i = 0; i < members.length; ++i) { 189 | if (ts.isCallSignatureDeclaration(members[i])) { 190 | return false; 191 | } 192 | } 193 | return true; 194 | } 195 | return false; 196 | } 197 | 198 | /** 199 | * Whether a parameter declaration is specifying information about the type of "this" passed to 200 | * the function instead of being a normal type parameter representable by a Dart type. 201 | */ 202 | export function isThisParameter(param: ts.ParameterDeclaration): boolean { 203 | return param.name && ts.isIdentifier(param.name) && param.name.text === 'this'; 204 | } 205 | 206 | /** 207 | * Dart does not have a concept of binding the type of the "this" parameter to a method. 208 | */ 209 | export function filterThisParameter(params: ts.NodeArray): 210 | ts.ParameterDeclaration[] { 211 | let ret: ts.ParameterDeclaration[] = []; 212 | for (let i = 0; i < params.length; i++) { 213 | let param = params[i]; 214 | if (!isThisParameter(param)) { 215 | ret.push(param); 216 | } 217 | } 218 | return ret; 219 | } 220 | 221 | export function isTypeNode(node: ts.Node): boolean { 222 | switch (node.kind) { 223 | case ts.SyntaxKind.IntersectionType: 224 | case ts.SyntaxKind.UnionType: 225 | case ts.SyntaxKind.ParenthesizedType: 226 | case ts.SyntaxKind.TypeReference: 227 | case ts.SyntaxKind.TypeLiteral: 228 | case ts.SyntaxKind.LastTypeNode: 229 | case ts.SyntaxKind.LiteralType: 230 | case ts.SyntaxKind.ArrayType: 231 | case ts.SyntaxKind.TypeOperator: 232 | case ts.SyntaxKind.IndexedAccessType: 233 | case ts.SyntaxKind.MappedType: 234 | case ts.SyntaxKind.TypePredicate: 235 | case ts.SyntaxKind.TypeQuery: 236 | case ts.SyntaxKind.TupleType: 237 | case ts.SyntaxKind.NumberKeyword: 238 | case ts.SyntaxKind.StringKeyword: 239 | case ts.SyntaxKind.VoidKeyword: 240 | case ts.SyntaxKind.NullKeyword: 241 | case ts.SyntaxKind.UndefinedKeyword: 242 | case ts.SyntaxKind.BooleanKeyword: 243 | case ts.SyntaxKind.AnyKeyword: 244 | case ts.SyntaxKind.NeverKeyword: 245 | case ts.SyntaxKind.FunctionType: 246 | case ts.SyntaxKind.ThisType: 247 | return true; 248 | default: 249 | return false; 250 | } 251 | } 252 | 253 | export function isPromise(type: ts.TypeNode): boolean { 254 | return type && ts.isTypeReferenceNode(type) && ident(type.typeName) === 'Promise'; 255 | } 256 | 257 | export function isCallable(decl: ClassLike): boolean { 258 | let members = decl.members as ReadonlyArray; 259 | return members.some((member) => { 260 | return member.kind === ts.SyntaxKind.CallSignature; 261 | }); 262 | } 263 | 264 | export function copyLocation(src: ts.Node, dest: ts.Node) { 265 | dest.pos = src.pos; 266 | dest.end = src.end; 267 | dest.parent = src.parent; 268 | } 269 | 270 | export function cloneNodeArray(src?: ts.NodeArray): ts.NodeArray| 271 | undefined { 272 | if (!src) { 273 | return undefined; 274 | } 275 | const clone = ts.createNodeArray(src.map(ts.getMutableClone)); 276 | copyNodeArrayLocation(src, clone); 277 | return clone; 278 | } 279 | 280 | export function copyNodeArrayLocation(src: ts.TextRange, dest: ts.NodeArray) { 281 | dest.pos = src.pos; 282 | dest.end = src.end; 283 | } 284 | 285 | export function getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { 286 | for (let parent = n; parent; parent = parent.parent) { 287 | if (parent.kind === kind) return parent; 288 | } 289 | return null; 290 | } 291 | 292 | export function getEnclosingClass(n: ts.Node): ClassLike { 293 | while (n) { 294 | if (ts.isClassDeclaration(n) || ts.isInterfaceDeclaration(n)) { 295 | return n; 296 | } 297 | n = n.parent; 298 | } 299 | return null; 300 | } 301 | 302 | export function isConstCall(node: ts.CallExpression): boolean { 303 | return node && ident(node.expression) === 'CONST_EXPR'; 304 | } 305 | 306 | export function isInsideConstExpr(node: ts.Node): boolean { 307 | return isConstCall(getAncestor(node, ts.SyntaxKind.CallExpression)); 308 | } 309 | 310 | export function getModuleBlock(moduleDecl: ts.ModuleDeclaration): ts.ModuleBlock { 311 | while (ts.isModuleDeclaration(moduleDecl.body)) { 312 | moduleDecl = moduleDecl.body; 313 | } 314 | if (ts.isModuleBlock(moduleDecl.body)) { 315 | return moduleDecl.body; 316 | } else { 317 | throw new Error('Module body must be a module block.'); 318 | } 319 | } 320 | 321 | /** 322 | * Determine the full module name including dots. 323 | * 324 | * e.g. returns 'foo.bar' for a declaration of namespace or module foo.bar 325 | */ 326 | export function getModuleName(moduleDecl: ts.ModuleDeclaration): string { 327 | let name = moduleDecl.name.text; 328 | while (ts.isModuleDeclaration(moduleDecl.body)) { 329 | moduleDecl = moduleDecl.body; 330 | name += '.' + moduleDecl.name.text; 331 | } 332 | return name; 333 | } 334 | 335 | export function formatType(s: string, comment: string, options: TypeDisplayOptions): string { 336 | if (!comment || options.hideComment) { 337 | return s; 338 | } else if (options.insideComment) { 339 | // When inside a comment we only need to emit the comment version which 340 | // is the syntax we would like to use if Dart supported all language 341 | // features we would like to use for interop. 342 | return comment; 343 | } else { 344 | let sb = s + '/*'; 345 | // Check if the comment is a valid type name in which case it is safe to use the Dart code 346 | // written in comments syntax. 347 | const stubToMakeTypeValidStatement = ' DUMMY_VARIABLE_NAME;'; 348 | comment = comment.trim(); 349 | let statement = comment + stubToMakeTypeValidStatement; 350 | let result = dartStyle.formatCode(statement); 351 | 352 | if (!result.error) { 353 | result.code = result.code.trim(); 354 | let expectedStubIndex = result.code.length - stubToMakeTypeValidStatement.length; 355 | if (result.code.lastIndexOf(stubToMakeTypeValidStatement) === expectedStubIndex) { 356 | comment = result.code.substring(0, expectedStubIndex).trim(); 357 | } 358 | } 359 | sb += comment; 360 | sb += '*/'; 361 | return sb; 362 | } 363 | } 364 | 365 | export class TranspilerBase { 366 | private idCounter = 0; 367 | constructor(protected transpiler: Transpiler) {} 368 | 369 | visit(n: ts.Node) { 370 | this.transpiler.visit(n); 371 | } 372 | pushContext(context: OutputContext) { 373 | this.transpiler.pushContext(context); 374 | } 375 | popContext() { 376 | this.transpiler.popContext(); 377 | } 378 | emit(s: string) { 379 | this.transpiler.emit(s); 380 | } 381 | emitNoSpace(s: string) { 382 | this.transpiler.emitNoSpace(s); 383 | } 384 | emitType(s: string, comment: string) { 385 | this.transpiler.emitType(s, comment); 386 | } 387 | maybeLineBreak() { 388 | return this.transpiler.maybeLineBreak(); 389 | } 390 | enterCodeComment() { 391 | return this.transpiler.enterCodeComment(); 392 | } 393 | exitCodeComment() { 394 | return this.transpiler.exitCodeComment(); 395 | } 396 | maybeWrapInCodeComment({shouldWrap = true, newLine = false}, emit: () => void): void { 397 | if (shouldWrap) { 398 | this.enterCodeComment(); 399 | } 400 | emit(); 401 | if (shouldWrap) { 402 | this.exitCodeComment(); 403 | } 404 | if (newLine) { 405 | this.emit('\n'); 406 | } 407 | } 408 | 409 | enterTypeArguments() { 410 | this.transpiler.enterTypeArgument(); 411 | } 412 | exitTypeArguments() { 413 | this.transpiler.exitTypeArgument(); 414 | } 415 | get insideTypeArgument() { 416 | return this.transpiler.insideTypeArgument; 417 | } 418 | 419 | get insideCodeComment() { 420 | return this.transpiler.insideCodeComment; 421 | } 422 | 423 | getImportSummary(libraryUri: string): ImportSummary { 424 | if (!this.transpiler.imports.has(libraryUri)) { 425 | let summary = new ImportSummary(); 426 | this.transpiler.imports.set(libraryUri, summary); 427 | return summary; 428 | } 429 | return this.transpiler.imports.get(libraryUri); 430 | } 431 | 432 | /** 433 | * Add an import. If an identifier is specified, only show that name. 434 | */ 435 | addImport(libraryUri: string, identifier?: string): ImportSummary { 436 | let summary = this.getImportSummary(libraryUri); 437 | if (identifier) { 438 | summary.shown.add(identifier); 439 | } else { 440 | summary.showAll = true; 441 | } 442 | return summary; 443 | } 444 | 445 | /** 446 | * Return resolved name possibly including a prefix for the identifier. 447 | */ 448 | resolveImportForSourceFile(sourceFile: ts.SourceFile, context: ts.SourceFile, identifier: string): 449 | string { 450 | if (sourceFile === context) { 451 | return identifier; 452 | } 453 | if (sourceFile.hasNoDefaultLib) { 454 | // We don't want to emit imports to default lib libraries as we replace with Dart equivalents 455 | // such as dart:html, etc. 456 | return identifier; 457 | } 458 | const relativePath = path.relative(path.dirname(context.fileName), sourceFile.fileName); 459 | const fileName = this.getDartFileName(relativePath); 460 | const identifierParts = identifier.split('.'); 461 | identifier = identifierParts[identifierParts.length - 1]; 462 | const summary = this.addImport(this.transpiler.getDartFileName(fileName), identifier); 463 | if (summary.asPrefix) { 464 | return summary.asPrefix + '.' + identifier; 465 | } 466 | return identifier; 467 | } 468 | 469 | 470 | reportError(n: ts.Node, message: string) { 471 | this.transpiler.reportError(n, message); 472 | } 473 | 474 | visitNode(n: ts.Node): boolean { 475 | throw new Error('not implemented'); 476 | } 477 | 478 | visitEach(nodes: ts.NodeArray) { 479 | nodes.forEach((n) => this.visit(n)); 480 | } 481 | 482 | visitEachIfPresent(nodes?: ts.NodeArray) { 483 | if (nodes) this.visitEach(nodes); 484 | } 485 | 486 | visitList(nodes: ts.NodeArray, separator?: string) { 487 | separator = separator || ','; 488 | for (let i = 0; i < nodes.length; i++) { 489 | this.visit(nodes[i]); 490 | if (i < nodes.length - 1) this.emitNoSpace(separator); 491 | } 492 | } 493 | 494 | /** 495 | * Returns whether any parameters were actually emitted. 496 | */ 497 | visitParameterList(nodes: ts.ParameterDeclaration[], namesOnly: boolean): boolean { 498 | let emittedParameters = false; 499 | for (let i = 0; i < nodes.length; ++i) { 500 | let param = nodes[i]; 501 | if (!this.insideCodeComment && isThisParameter(param)) { 502 | // Emit the this type in a comment as it could be of interest to Dart users who are 503 | // calling allowInteropCaptureThis to bind a Dart method. 504 | this.enterCodeComment(); 505 | this.visit(param.type); 506 | this.emit('this'); 507 | this.exitCodeComment(); 508 | continue; 509 | } 510 | if (emittedParameters) { 511 | this.emitNoSpace(','); 512 | } 513 | if (namesOnly) { 514 | this.emit(ident(param.name)); 515 | } else { 516 | this.visit(param); 517 | } 518 | emittedParameters = true; 519 | } 520 | return emittedParameters; 521 | } 522 | 523 | uniqueId(name: string): string { 524 | const id = this.idCounter++; 525 | return `_${name}\$\$js_facade_gen\$${id}`; 526 | } 527 | 528 | assert(c: ts.Node, condition: boolean, reason: string): void { 529 | if (!condition) { 530 | this.reportError(c, reason); 531 | throw new Error(reason); 532 | } 533 | } 534 | 535 | getAncestor(n: ts.Node, kind: ts.SyntaxKind): ts.Node { 536 | for (let parent = n; parent; parent = parent.parent) { 537 | if (parent.kind === kind) return parent; 538 | } 539 | return null; 540 | } 541 | 542 | hasAncestor(n: ts.Node, kind: ts.SyntaxKind): boolean { 543 | return !!getAncestor(n, kind); 544 | } 545 | 546 | hasAnnotation(decorators: ts.NodeArray, name: string): boolean { 547 | if (!decorators) return false; 548 | return decorators.some((d) => { 549 | let decName = ident(d.expression); 550 | if (decName === name) return true; 551 | if (!ts.isCallExpression(d.expression)) return false; 552 | let callExpr = d.expression; 553 | decName = ident(callExpr.expression); 554 | return decName === name; 555 | }); 556 | } 557 | 558 | hasNodeFlag(n: ts.Declaration, flag: ts.NodeFlags): boolean { 559 | return n && (ts.getCombinedNodeFlags(n) & flag) !== 0 || false; 560 | } 561 | 562 | hasModifierFlag(n: ts.Declaration, flag: ts.ModifierFlags): boolean { 563 | return n && (ts.getCombinedModifierFlags(n) & flag) !== 0 || false; 564 | } 565 | 566 | getRelativeFileName(fileName: string): string { 567 | return this.transpiler.getRelativeFileName(fileName); 568 | } 569 | 570 | getDartFileName(fileName?: string): string { 571 | return this.transpiler.getDartFileName(fileName); 572 | } 573 | 574 | maybeVisitTypeArguments(n: {typeArguments?: ts.NodeArray}) { 575 | if (n.typeArguments) { 576 | this.emitNoSpace('<'); 577 | this.enterTypeArguments(); 578 | this.visitList(n.typeArguments); 579 | this.exitTypeArguments(); 580 | this.emitNoSpace('>'); 581 | } 582 | } 583 | 584 | visitParameters(parameters: ts.NodeArray, {namesOnly = false}) { 585 | this.emitNoSpace('('); 586 | let firstInitParamIdx = 0; 587 | for (; firstInitParamIdx < parameters.length; firstInitParamIdx++) { 588 | // ObjectBindingPatterns are handled within the parameter visit. 589 | let isOpt = parameters[firstInitParamIdx].initializer || 590 | parameters[firstInitParamIdx].questionToken || 591 | parameters[firstInitParamIdx].dotDotDotToken; 592 | if (isOpt && !ts.isObjectBindingPattern(parameters[firstInitParamIdx].name)) { 593 | break; 594 | } 595 | } 596 | 597 | let hasValidParameters = false; 598 | if (firstInitParamIdx !== 0) { 599 | let requiredParams = parameters.slice(0, firstInitParamIdx); 600 | hasValidParameters = this.visitParameterList(requiredParams, namesOnly); 601 | } 602 | 603 | if (firstInitParamIdx !== parameters.length) { 604 | if (hasValidParameters) this.emitNoSpace(','); 605 | let positionalOptional = parameters.slice(firstInitParamIdx, parameters.length); 606 | if (!namesOnly) { 607 | this.emit('['); 608 | } 609 | this.visitParameterList(positionalOptional, namesOnly); 610 | if (!namesOnly) { 611 | this.emitNoSpace(']'); 612 | } 613 | } 614 | 615 | this.emitNoSpace(')'); 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /lib/json/conversions.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {ExportAssignment, ExportDeclaration, ImportDeclaration, ModuleDeclaration} from './module_declarations'; 4 | import {CallSignatureDeclaration, ClassDeclaration, ConstructorDeclaration, ConstructSignatureDeclaration, FunctionDeclaration, GetAccessorDeclaration, InterfaceDeclaration, MemberDeclaration, MethodDeclaration, ParameterDeclaration, PropertyDeclaration, SetAccessorDeclaration, TypeAliasDeclaration, TypeParameterDeclaration, VariableDeclaration} from './named_declarations'; 5 | import {Node} from './node'; 6 | import {SourceFile} from './source_file'; 7 | import {FunctionType, isKeywordTypeNode, KeywordType, LiteralType, Type, TypeLiteral, TypeReference} from './types'; 8 | import {VariableStatement} from './variable_statement'; 9 | 10 | /** 11 | * The conversion helpers may return undefined when the input TS node doesn't contain useful 12 | * information. This function is used to filter undefined out of arrays before serializing the 13 | * converted AST to JSON. 14 | */ 15 | export function filterUndefined(node: Node): boolean { 16 | return node !== undefined; 17 | } 18 | 19 | /** 20 | * Helper function that converts Names into strings. 21 | */ 22 | export function convertName(node: ts.DeclarationName|ts.EntityName|ts.BindingName| 23 | ts.QualifiedName): string { 24 | if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) { 25 | return node.text; 26 | } 27 | if (ts.isQualifiedName(node)) { 28 | const leftName = convertName(node.left); 29 | if (leftName) { 30 | return leftName + '.' + convertName(node.right); 31 | } 32 | } 33 | const error = new Error(`Unexpected Name kind: ${ts.SyntaxKind[node.kind]}`); 34 | error.name = 'DartFacadeError'; 35 | throw error; 36 | } 37 | 38 | /** 39 | * Helper function that converts Expressions into strings. 40 | */ 41 | export function convertExpression(node: ts.Expression): string { 42 | if (ts.isIdentifier(node)) { 43 | return node.text; 44 | } 45 | if (ts.isStringLiteralLike(node)) { 46 | return `'${node.text}'`; 47 | } 48 | const error = new Error(`Unexpected Expression kind: ${ts.SyntaxKind[node.kind]}`); 49 | error.name = 'DartFacadeError'; 50 | throw error; 51 | } 52 | 53 | /** 54 | * Takes in a ts.SourceFile and returns a new SourceFile object that is compatible with Dart and can 55 | * be serialized to JSON. 56 | */ 57 | export function convertAST(node: ts.SourceFile): SourceFile { 58 | return new SourceFile(node); 59 | } 60 | 61 | export function convertNode(node: ts.Node): Node|undefined { 62 | if (ts.isImportDeclaration(node)) { 63 | return new ImportDeclaration(node); 64 | } else if (ts.isExportDeclaration(node)) { 65 | return new ExportDeclaration(node); 66 | } else if (ts.isExportAssignment(node)) { 67 | return new ExportAssignment(node); 68 | } else if (ts.isModuleDeclaration(node)) { 69 | return new ModuleDeclaration(node); 70 | } else if (ts.isVariableDeclaration(node)) { 71 | return new VariableDeclaration(node); 72 | } else if (ts.isVariableStatement(node)) { 73 | return new VariableStatement(node); 74 | } else if (ts.isFunctionDeclaration(node)) { 75 | return new FunctionDeclaration(node); 76 | } else if (ts.isClassLike(node)) { 77 | return new ClassDeclaration(node); 78 | } else if (ts.isInterfaceDeclaration(node)) { 79 | return new InterfaceDeclaration(node); 80 | } else if (ts.isTypeAliasDeclaration(node)) { 81 | return new TypeAliasDeclaration(node); 82 | } else if (node.kind === ts.SyntaxKind.EndOfFileToken) { 83 | // no-op 84 | return undefined; 85 | } else { 86 | const error = new Error(`Unexpected Node kind ${ts.SyntaxKind[node.kind]}`); 87 | error.name = 'DartFacadeError'; 88 | throw error; 89 | } 90 | } 91 | 92 | export function convertTypeParameter(typeParam: ts.TypeParameterDeclaration): 93 | TypeParameterDeclaration { 94 | return new TypeParameterDeclaration(typeParam); 95 | } 96 | 97 | export function convertTypeNode(node: ts.TypeNode): Type { 98 | if (isKeywordTypeNode(node)) { 99 | return convertKeywordTypeNode(node); 100 | } else if (ts.isTypeReferenceNode(node)) { 101 | return new TypeReference(node); 102 | } else if (ts.isTypeLiteralNode(node)) { 103 | return new TypeLiteral(node); 104 | } else if (ts.isLiteralTypeNode(node)) { 105 | return undefined; 106 | } else if (ts.isFunctionTypeNode(node)) { 107 | return new FunctionType(node); 108 | } else { 109 | const error = new Error(`Unexpected TypeNode kind: ${ts.SyntaxKind[node.kind]}`); 110 | error.name = 'DartFacadeError'; 111 | throw error; 112 | } 113 | } 114 | 115 | export function convertKeywordTypeNode(node: ts.KeywordTypeNode): KeywordType { 116 | switch (node.kind) { 117 | case ts.SyntaxKind.AnyKeyword: 118 | return new KeywordType(node, 'any'); 119 | case ts.SyntaxKind.UnknownKeyword: 120 | return new KeywordType(node, 'unknown'); 121 | case ts.SyntaxKind.NumberKeyword: 122 | return new KeywordType(node, 'number'); 123 | case ts.SyntaxKind.BigIntKeyword: 124 | return new KeywordType(node, 'bigint'); 125 | case ts.SyntaxKind.ObjectKeyword: 126 | return new KeywordType(node, 'object'); 127 | case ts.SyntaxKind.BooleanKeyword: 128 | return new KeywordType(node, 'boolean'); 129 | case ts.SyntaxKind.StringKeyword: 130 | return new KeywordType(node, 'string'); 131 | case ts.SyntaxKind.SymbolKeyword: 132 | return new KeywordType(node, 'symbol'); 133 | case ts.SyntaxKind.ThisKeyword: 134 | return new KeywordType(node, 'this'); 135 | case ts.SyntaxKind.VoidKeyword: 136 | return new KeywordType(node, 'void'); 137 | case ts.SyntaxKind.UndefinedKeyword: 138 | return new KeywordType(node, 'undefined'); 139 | case ts.SyntaxKind.NullKeyword: 140 | return new KeywordType(node, 'null'); 141 | case ts.SyntaxKind.NeverKeyword: 142 | return new KeywordType(node, 'never'); 143 | default: 144 | const error = 145 | new Error(`Unexpected KeywordTypeNode kind: ${ts.SyntaxKind[(node as ts.Node).kind]}`); 146 | error.name = 'DartFacadeError'; 147 | throw error; 148 | } 149 | } 150 | 151 | export function convertLiteralTypeNode(node: ts.LiteralTypeNode): LiteralType { 152 | if (ts.isLiteralExpression(node)) { 153 | return new LiteralType(node, node.text); 154 | } else { 155 | switch (node.literal.kind) { 156 | case ts.SyntaxKind.TrueKeyword: 157 | return new LiteralType(node, 'true'); 158 | case ts.SyntaxKind.FalseKeyword: 159 | return new LiteralType(node, 'false'); 160 | default: 161 | const error = new Error(`Unexpected LiteralTypeNode kind: ${ts.SyntaxKind[(node.kind)]}`); 162 | error.name = 'DartFacadeError'; 163 | throw error; 164 | } 165 | } 166 | } 167 | 168 | export function convertMember(member: ts.ClassElement|ts.TypeElement): MemberDeclaration { 169 | if (ts.isSemicolonClassElement(member)) { 170 | return undefined; 171 | } 172 | if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) { 173 | return new PropertyDeclaration(member); 174 | } 175 | if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) { 176 | return new MethodDeclaration(member); 177 | } 178 | if (ts.isConstructorDeclaration(member)) { 179 | return new ConstructorDeclaration(member); 180 | } 181 | if (ts.isConstructSignatureDeclaration(member)) { 182 | return new ConstructSignatureDeclaration(member); 183 | } 184 | if (ts.isGetAccessor(member)) { 185 | return new GetAccessorDeclaration(member); 186 | } 187 | if (ts.isSetAccessor(member)) { 188 | return new SetAccessorDeclaration(member); 189 | } 190 | if (ts.isCallSignatureDeclaration(member)) { 191 | return new CallSignatureDeclaration(member); 192 | } 193 | const error = new Error(`Unexpected Member kind: ${ts.SyntaxKind[member.kind]}`); 194 | error.name = 'DartFacadeError'; 195 | throw error; 196 | } 197 | 198 | export function convertParameter(parameter: ts.ParameterDeclaration) { 199 | return new ParameterDeclaration(parameter); 200 | } 201 | -------------------------------------------------------------------------------- /lib/json/converted_syntax_kinds.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An enum containing the different types of AST nodes that will be present in the JSON AST. This 3 | * will be serialized and loaded into the emitter so that the SyntaxKinds are always consistent 4 | * between the TS and Dart halves of this tool. 5 | */ 6 | export enum ConvertedSyntaxKind { 7 | // TODO(derekx): This should only contain SyntaxKinds that we will use in the emitter. I have 8 | // removed some kinds that we certainly won't need, but there are still more that can be removed. 9 | Unknown = 0, 10 | NumericLiteral = 8, 11 | BigIntLiteral = 9, 12 | StringLiteral = 10, 13 | RegularExpressionLiteral = 13, 14 | NoSubstitutionTemplateLiteral = 14, 15 | TemplateHead = 15, 16 | TemplateMiddle = 16, 17 | TemplateTail = 17, 18 | Identifier = 75, 19 | ConstModifier = 80, 20 | ContinueKeyword = 81, 21 | DefaultModifier = 83, 22 | EnumKeyword = 87, 23 | ExportModifier = 88, 24 | FinallyKeyword = 91, 25 | ForKeyword = 92, 26 | FunctionKeyword = 93, 27 | ImportKeyword = 95, 28 | InKeyword = 96, 29 | InstanceOfKeyword = 97, 30 | NewKeyword = 98, 31 | ReturnKeyword = 100, 32 | SuperKeyword = 101, 33 | TypeOfKeyword = 107, 34 | WithKeyword = 111, 35 | PackageKeyword = 115, 36 | PrivateModifier = 116, 37 | ProtectedModifier = 117, 38 | PublicModifier = 118, 39 | StaticModifier = 119, 40 | YieldKeyword = 120, 41 | AbstractModifier = 121, 42 | AsKeyword = 122, 43 | AssertsKeyword = 123, 44 | KeywordType = 124, 45 | AsyncModifier = 125, 46 | AwaitKeyword = 126, 47 | ConstructorKeyword = 128, 48 | DeclareKeyword = 129, 49 | GetKeyword = 130, 50 | InferKeyword = 131, 51 | IsKeyword = 132, 52 | KeyOfKeyword = 133, 53 | ModuleKeyword = 134, 54 | NamespaceKeyword = 135, 55 | ReadonlyModifier = 137, 56 | RequireKeyword = 138, 57 | SetKeyword = 141, 58 | TypeKeyword = 144, 59 | UniqueKeyword = 146, 60 | FromKeyword = 148, 61 | GlobalKeyword = 149, 62 | OfKeyword = 151, 63 | QualifiedName = 152, 64 | ComputedPropertyName = 153, 65 | TypeParameter = 154, 66 | Parameter = 155, 67 | Decorator = 156, 68 | PropertyDeclaration = 158, 69 | MethodDeclaration = 160, 70 | Constructor = 161, 71 | GetAccessor = 162, 72 | SetAccessor = 163, 73 | CallSignature = 164, 74 | ConstructSignature = 165, 75 | IndexSignature = 166, 76 | TypePredicate = 167, 77 | TypeReference = 168, 78 | FunctionType = 169, 79 | ConstructorType = 170, 80 | TypeQuery = 171, 81 | TypeLiteral = 172, 82 | ArrayType = 173, 83 | TupleType = 174, 84 | OptionalType = 175, 85 | RestType = 176, 86 | UnionType = 177, 87 | IntersectionType = 178, 88 | ConditionalType = 179, 89 | InferType = 180, 90 | ParenthesizedType = 181, 91 | ThisType = 182, 92 | TypeOperator = 183, 93 | IndexedAccessType = 184, 94 | MappedType = 185, 95 | LiteralType = 186, 96 | ImportType = 187, 97 | ObjectBindingPattern = 188, 98 | ArrayBindingPattern = 189, 99 | BindingElement = 190, 100 | ArrayLiteralExpression = 191, 101 | ObjectLiteralExpression = 192, 102 | PropertyAccessExpression = 193, 103 | ElementAccessExpression = 194, 104 | CallExpression = 195, 105 | NewExpression = 196, 106 | TaggedTemplateExpression = 197, 107 | TypeAssertionExpression = 198, 108 | ParenthesizedExpression = 199, 109 | FunctionExpression = 200, 110 | ArrowFunction = 201, 111 | DeleteExpression = 202, 112 | TypeOfExpression = 203, 113 | VoidExpression = 204, 114 | AwaitExpression = 205, 115 | PrefixUnaryExpression = 206, 116 | PostfixUnaryExpression = 207, 117 | BinaryExpression = 208, 118 | ConditionalExpression = 209, 119 | TemplateExpression = 210, 120 | YieldExpression = 211, 121 | SpreadElement = 212, 122 | OmittedExpression = 214, 123 | ExpressionWithTypeArguments = 215, 124 | AsExpression = 216, 125 | NonNullExpression = 217, 126 | MetaProperty = 218, 127 | SyntheticExpression = 219, 128 | TemplateSpan = 220, 129 | SemicolonClassElement = 221, 130 | Block = 222, 131 | EmptyStatement = 223, 132 | VariableStatement = 224, 133 | VariableDeclaration = 241, 134 | FunctionDeclaration = 243, 135 | ClassDeclaration = 244, 136 | InterfaceDeclaration = 245, 137 | TypeAliasDeclaration = 246, 138 | EnumDeclaration = 247, 139 | ModuleDeclaration = 248, 140 | ModuleBlock = 249, 141 | NamespaceExportDeclaration = 251, 142 | ImportEqualsDeclaration = 252, 143 | ImportDeclaration = 253, 144 | ImportClause = 254, 145 | NamespaceImport = 255, 146 | NamedImports = 256, 147 | ImportSpecifier = 257, 148 | ExportAssignment = 258, 149 | ExportDeclaration = 259, 150 | NamedExports = 260, 151 | ExportSpecifier = 261, 152 | MissingDeclaration = 262, 153 | ExternalModuleReference = 263, 154 | CaseClause = 275, 155 | DefaultClause = 276, 156 | HeritageClause = 277, 157 | CatchClause = 278, 158 | PropertyAssignment = 279, 159 | ShorthandPropertyAssignment = 280, 160 | SpreadAssignment = 281, 161 | EnumMember = 282, 162 | SourceFile = 288, 163 | Bundle = 289, 164 | InputFiles = 291, 165 | // JSDocTypeExpression = 292, 166 | // JSDocAllType = 293, 167 | // JSDocUnknownType = 294, 168 | // JSDocNullableType = 295, 169 | // JSDocNonNullableType = 296, 170 | // JSDocOptionalType = 297, 171 | // JSDocFunctionType = 298, 172 | // JSDocVariadicType = 299, 173 | // JSDocNamepathType = 300, 174 | // JSDocComment = 301, 175 | // JSDocTypeLiteral = 302, 176 | // JSDocSignature = 303, 177 | // JSDocTag = 304, 178 | // JSDocAugmentsTag = 305, 179 | // JSDocAuthorTag = 306, 180 | // JSDocClassTag = 307, 181 | // JSDocCallbackTag = 308, 182 | // JSDocEnumTag = 309, 183 | // JSDocParameterTag = 310, 184 | // JSDocReturnTag = 311, 185 | // JSDocThisTag = 312, 186 | // JSDocTypeTag = 313, 187 | // JSDocTemplateTag = 314, 188 | // JSDocTypedefTag = 315, 189 | // JSDocPropertyTag = 316, 190 | SyntaxList = 317, 191 | NotEmittedStatement = 318, 192 | PartiallyEmittedExpression = 319, 193 | CommaListExpression = 320, 194 | MergeDeclarationMarker = 321, 195 | EndOfDeclarationMarker = 322, 196 | SyntheticReferenceExpression = 323, 197 | Count = 324, 198 | } 199 | 200 | export type ConvertedModifierKind = 201 | ConvertedSyntaxKind.AbstractModifier|ConvertedSyntaxKind.AsyncModifier| 202 | ConvertedSyntaxKind.ConstModifier|ConvertedSyntaxKind.DefaultModifier| 203 | ConvertedSyntaxKind.ExportModifier|ConvertedSyntaxKind.PublicModifier| 204 | ConvertedSyntaxKind.PrivateModifier|ConvertedSyntaxKind.ProtectedModifier| 205 | ConvertedSyntaxKind.ReadonlyModifier|ConvertedSyntaxKind.StaticModifier; 206 | 207 | export type ConvertedKeywordType = 'any'|'unknown'|'number'|'bigint'|'object'|'boolean'|'string'| 208 | 'symbol'|'this'|'void'|'undefined'|'null'|'never'; 209 | 210 | export type ConvertedNamedDeclarationKind = 211 | ConvertedSyntaxKind.ImportSpecifier|ConvertedSyntaxKind.ExportSpecifier| 212 | ConvertedSyntaxKind.VariableDeclaration| 213 | ConvertedSyntaxKind.FunctionDeclaration|ConvertedSyntaxKind.Parameter| 214 | ConvertedSyntaxKind.InterfaceDeclaration| 215 | ConvertedSyntaxKind.ClassDeclaration|ConvertedSyntaxKind.PropertyDeclaration| 216 | ConvertedSyntaxKind.MethodDeclaration| 217 | ConvertedSyntaxKind.TypeAliasDeclaration|ConvertedSyntaxKind.TypeParameter| 218 | ConvertedSignatureKind; 219 | 220 | export type ConvertedSignatureKind = 221 | ConvertedSyntaxKind.FunctionType|ConvertedSyntaxKind.FunctionDeclaration| 222 | ConvertedSyntaxKind.GetAccessor|ConvertedSyntaxKind.SetAccessor| 223 | ConvertedSyntaxKind.Constructor|ConvertedSyntaxKind.CallSignature| 224 | ConvertedSyntaxKind.ConstructSignature|ConvertedSyntaxKind.IndexSignature; 225 | -------------------------------------------------------------------------------- /lib/json/expression_with_type_arguments.ts: -------------------------------------------------------------------------------- 1 | import {ExpressionWithTypeArguments as tsExpressionWithTypeArguments} from 'typescript'; 2 | 3 | import {convertExpression, convertTypeNode, filterUndefined} from './conversions'; 4 | import {ConvertedSyntaxKind} from './converted_syntax_kinds'; 5 | import {Node} from './node'; 6 | import {Type} from './types'; 7 | 8 | export class ExpressionWithTypeArguments extends Node { 9 | private typeArguments?: Type[]; 10 | private expression: string; 11 | 12 | constructor(node: tsExpressionWithTypeArguments) { 13 | super(node, ConvertedSyntaxKind.ExpressionWithTypeArguments); 14 | 15 | if (node.typeArguments) { 16 | this.typeArguments = node.typeArguments.map(convertTypeNode).filter(filterUndefined); 17 | } 18 | this.expression = convertExpression(node.expression); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/json/heritage_clause.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {filterUndefined} from './conversions'; 4 | import {ConvertedSyntaxKind} from './converted_syntax_kinds'; 5 | import {ExpressionWithTypeArguments} from './expression_with_type_arguments'; 6 | import {Node} from './node'; 7 | 8 | 9 | export class HeritageClause extends Node { 10 | private keyword: string; 11 | private types: ExpressionWithTypeArguments[]; 12 | constructor(node: ts.HeritageClause) { 13 | super(node, ConvertedSyntaxKind.HeritageClause); 14 | 15 | if (node.token === ts.SyntaxKind.ExtendsKeyword) { 16 | this.keyword = 'extends'; 17 | } else if (node.token === ts.SyntaxKind.ImplementsKeyword) { 18 | this.keyword = 'implements'; 19 | } 20 | this.types = node.types 21 | .map((type: ts.ExpressionWithTypeArguments) => { 22 | return new ExpressionWithTypeArguments(type); 23 | }) 24 | .filter(filterUndefined); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/json/module_declarations/export_assignment.ts: -------------------------------------------------------------------------------- 1 | import {ExportAssignment as tsExportAssignment} from 'typescript'; 2 | import {convertExpression, convertName} from '../conversions'; 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | import {Node} from '../node'; 5 | 6 | /** 7 | * This is either an `export =` or an `export default` declaration. 8 | * Unless `isExportEquals` is set, this node was parsed as an `export default`. 9 | */ 10 | export class ExportAssignment extends Node { 11 | private name?: string; 12 | private isExportEquals = false; 13 | private expression: string; 14 | 15 | constructor(node: tsExportAssignment) { 16 | super(node, ConvertedSyntaxKind.ExportAssignment); 17 | 18 | if (node.name) { 19 | this.name = convertName(node.name); 20 | } 21 | if (node.isExportEquals) { 22 | this.isExportEquals = true; 23 | } 24 | this.expression = convertExpression(node.expression); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/json/module_declarations/export_declaration.ts: -------------------------------------------------------------------------------- 1 | import {ExportDeclaration as tsExportDeclaration, ExportSpecifier as tsExportSpecifier, NamedExports as tsNamedExports} from 'typescript'; 2 | 3 | import {convertExpression} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {NamedDeclaration} from '../named_declarations'; 6 | import {Node} from '../node'; 7 | 8 | export class ExportDeclaration extends Node { 9 | private exportClause?: NamedExports; 10 | private moduleSpecifier?: string; 11 | constructor(node: tsExportDeclaration) { 12 | super(node, ConvertedSyntaxKind.ExportDeclaration); 13 | 14 | if (node.exportClause) { 15 | this.exportClause = new NamedExports(node.exportClause as tsNamedExports); 16 | } 17 | if (node.moduleSpecifier) { 18 | this.moduleSpecifier = convertExpression(node.moduleSpecifier); 19 | } 20 | } 21 | } 22 | 23 | class NamedExports extends Node { 24 | elements: ExportSpecifier[]; 25 | constructor(node: tsNamedExports) { 26 | super(node, ConvertedSyntaxKind.NamedExports); 27 | this.elements = node.elements.map((element: tsExportSpecifier) => { 28 | return new ExportSpecifier(element); 29 | }); 30 | } 31 | } 32 | 33 | class ExportSpecifier extends NamedDeclaration { 34 | private propertyName?: string; 35 | 36 | constructor(node: tsExportSpecifier) { 37 | super(node, ConvertedSyntaxKind.ExportSpecifier); 38 | 39 | if (node.propertyName) { 40 | this.propertyName = node.propertyName.text; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/json/module_declarations/import_declaration.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {convertExpression, convertName} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {NamedDeclaration} from '../named_declarations'; 6 | import {Node} from '../node'; 7 | 8 | export class ImportDeclaration extends Node { 9 | private importClause?: ImportClause; 10 | private moduleSpecifier: string; 11 | constructor(node: ts.ImportDeclaration) { 12 | super(node, ConvertedSyntaxKind.ImportDeclaration); 13 | 14 | this.importClause = new ImportClause(node.importClause); 15 | this.moduleSpecifier = convertExpression(node.moduleSpecifier); 16 | } 17 | } 18 | 19 | class ImportClause extends Node { 20 | name?: string; 21 | namedBindings?: NamespaceImport|NamedImports; 22 | constructor(node: ts.ImportClause) { 23 | super(node, ConvertedSyntaxKind.ImportClause); 24 | if (node.name) { 25 | this.name = convertName(node.name); 26 | } 27 | if (ts.isNamespaceImport(node.namedBindings)) { 28 | this.namedBindings = new NamespaceImport(node.namedBindings); 29 | } else if (ts.isNamedImports(node.namedBindings)) { 30 | this.namedBindings = new NamedImports(node.namedBindings); 31 | } 32 | } 33 | } 34 | 35 | class NamespaceImport extends Node { 36 | private name: string; 37 | 38 | constructor(node: ts.NamespaceImport) { 39 | super(node, ConvertedSyntaxKind.NamespaceImport); 40 | this.name = convertName(node.name); 41 | } 42 | } 43 | 44 | class NamedImports extends Node { 45 | private name: string; 46 | private elements: ImportSpecifier[]; 47 | 48 | constructor(node: ts.NamedImports) { 49 | super(node, ConvertedSyntaxKind.NamedImports); 50 | 51 | this.elements = node.elements.map((element: ts.ImportSpecifier) => { 52 | return new ImportSpecifier(element); 53 | }); 54 | } 55 | } 56 | 57 | class ImportSpecifier extends NamedDeclaration { 58 | private propertyName?: string; 59 | 60 | constructor(node: ts.ImportSpecifier) { 61 | super(node, ConvertedSyntaxKind.ImportSpecifier); 62 | 63 | if (node.propertyName) { 64 | this.propertyName = node.propertyName.text; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/json/module_declarations/index.ts: -------------------------------------------------------------------------------- 1 | export {ExportAssignment} from './export_assignment'; 2 | export {ExportDeclaration} from './export_declaration'; 3 | export {ImportDeclaration} from './import_declaration'; 4 | export {ModuleDeclaration} from './module_declaration'; 5 | -------------------------------------------------------------------------------- /lib/json/module_declarations/module_declaration.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {convertName, convertNode, filterUndefined} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {Node} from '../node'; 6 | 7 | export class ModuleDeclaration extends Node { 8 | private name: string; 9 | private body?: ModuleBlock|ModuleDeclaration; 10 | constructor(node: ts.ModuleDeclaration) { 11 | super(node, ConvertedSyntaxKind.ModuleDeclaration); 12 | 13 | this.name = convertName(node.name); 14 | if (node.body && ts.isModuleBlock(node.body)) { 15 | this.body = new ModuleBlock(node.body); 16 | } else if (node.body && ts.isModuleDeclaration(node.body)) { 17 | this.body = new ModuleDeclaration(node.body); 18 | } 19 | } 20 | } 21 | 22 | class ModuleBlock extends Node { 23 | private statements: Node[]; 24 | constructor(node: ts.ModuleBlock) { 25 | super(node, ConvertedSyntaxKind.ModuleBlock); 26 | 27 | this.statements = node.statements.map(convertNode).filter(filterUndefined); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/json/named_declarations/accessor_declarations.ts: -------------------------------------------------------------------------------- 1 | import {GetAccessorDeclaration as tsGetAccessorDeclaration, SetAccessorDeclaration as tsSetAccessorDeclaration} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {SignatureDeclaration} from './signature_declaration'; 6 | 7 | export class GetAccessorDeclaration extends SignatureDeclaration { 8 | constructor(node: tsGetAccessorDeclaration) { 9 | super(node, ConvertedSyntaxKind.GetAccessor); 10 | } 11 | } 12 | 13 | export class SetAccessorDeclaration extends SignatureDeclaration { 14 | constructor(node: tsSetAccessorDeclaration) { 15 | super(node, ConvertedSyntaxKind.SetAccessor); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/json/named_declarations/call_signature_declaration.ts: -------------------------------------------------------------------------------- 1 | import {CallSignatureDeclaration as tsCallSignatureDeclaration} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {SignatureDeclaration} from './signature_declaration'; 6 | 7 | export class CallSignatureDeclaration extends SignatureDeclaration { 8 | constructor(node: tsCallSignatureDeclaration) { 9 | super(node, ConvertedSyntaxKind.CallSignature); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/json/named_declarations/class_declaration.ts: -------------------------------------------------------------------------------- 1 | import {ClassLikeDeclaration as tsClassLikeDeclaration, HeritageClause as tsHeritageClause} from 'typescript'; 2 | 3 | import {convertMember, convertTypeParameter, filterUndefined} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {HeritageClause} from '../heritage_clause'; 6 | 7 | import {MemberDeclaration} from './member_declaration'; 8 | import {NamedDeclaration} from './named_declaration'; 9 | import {TypeParameterDeclaration} from './type_parameter_declaration'; 10 | 11 | export class ClassDeclaration extends NamedDeclaration { 12 | private typeParameters?: TypeParameterDeclaration[]; 13 | private heritageClauses?: HeritageClause[]; 14 | private members: MemberDeclaration[]; 15 | constructor(node: tsClassLikeDeclaration) { 16 | super(node, ConvertedSyntaxKind.ClassDeclaration); 17 | 18 | if (node.typeParameters) { 19 | this.typeParameters = node.typeParameters.map(convertTypeParameter).filter(filterUndefined); 20 | } 21 | if (node.heritageClauses) { 22 | this.heritageClauses = node.heritageClauses 23 | .map((clause: tsHeritageClause) => { 24 | return new HeritageClause(clause); 25 | }) 26 | .filter(filterUndefined); 27 | } 28 | this.members = node.members.map(convertMember).filter(filterUndefined); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/json/named_declarations/construct_signature_declaration.ts: -------------------------------------------------------------------------------- 1 | import {ConstructSignatureDeclaration as tsConstructSignatureDeclaration} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {SignatureDeclaration} from './signature_declaration'; 6 | 7 | export class ConstructSignatureDeclaration extends SignatureDeclaration { 8 | constructor(node: tsConstructSignatureDeclaration) { 9 | super(node, ConvertedSyntaxKind.ConstructSignature); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/json/named_declarations/constructor_declaration.ts: -------------------------------------------------------------------------------- 1 | import {ConstructorDeclaration as tsConstructorDeclaration} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {SignatureDeclaration} from './signature_declaration'; 6 | 7 | export class ConstructorDeclaration extends SignatureDeclaration { 8 | constructor(node: tsConstructorDeclaration) { 9 | super(node, ConvertedSyntaxKind.Constructor); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/json/named_declarations/function_declaration.ts: -------------------------------------------------------------------------------- 1 | import {FunctionLikeDeclaration as tsFunctionLikeDeclaration} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {SignatureDeclaration} from './signature_declaration'; 6 | 7 | export class FunctionDeclaration extends SignatureDeclaration { 8 | constructor(node: tsFunctionLikeDeclaration) { 9 | super(node, ConvertedSyntaxKind.FunctionDeclaration); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/json/named_declarations/index.ts: -------------------------------------------------------------------------------- 1 | export {GetAccessorDeclaration, SetAccessorDeclaration} from './accessor_declarations'; 2 | export {CallSignatureDeclaration} from './call_signature_declaration'; 3 | export {ClassDeclaration} from './class_declaration'; 4 | export {ConstructSignatureDeclaration} from './construct_signature_declaration'; 5 | export {ConstructorDeclaration} from './constructor_declaration'; 6 | export {FunctionDeclaration} from './function_declaration'; 7 | export {InterfaceDeclaration} from './interface_declaration'; 8 | export {MemberDeclaration, MethodDeclaration, PropertyDeclaration} from './member_declaration'; 9 | export {NamedDeclaration} from './named_declaration'; 10 | export {ParameterDeclaration} from './parameter_declaration'; 11 | export {SignatureDeclaration} from './signature_declaration'; 12 | export {TypeAliasDeclaration} from './type_alias_declaration'; 13 | export {TypeParameterDeclaration} from './type_parameter_declaration'; 14 | export {VariableDeclaration} from './variable_declaration'; 15 | -------------------------------------------------------------------------------- /lib/json/named_declarations/interface_declaration.ts: -------------------------------------------------------------------------------- 1 | import {HeritageClause as tsHeritageClause, InterfaceDeclaration as tsInterfaceDeclaration} from 'typescript'; 2 | 3 | import {convertMember, convertTypeParameter, filterUndefined} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {HeritageClause} from '../heritage_clause'; 6 | 7 | import {MemberDeclaration} from './member_declaration'; 8 | import {NamedDeclaration} from './named_declaration'; 9 | import {TypeParameterDeclaration} from './type_parameter_declaration'; 10 | 11 | export class InterfaceDeclaration extends NamedDeclaration { 12 | private typeParameters: TypeParameterDeclaration[]; 13 | private heritageClauses?: HeritageClause[]; 14 | private members: MemberDeclaration[]; 15 | constructor(node: tsInterfaceDeclaration) { 16 | super(node, ConvertedSyntaxKind.InterfaceDeclaration); 17 | 18 | if (node.typeParameters) { 19 | this.typeParameters = node.typeParameters.map(convertTypeParameter).filter(filterUndefined); 20 | } 21 | if (node.heritageClauses) { 22 | this.heritageClauses = node.heritageClauses 23 | .map((clause: tsHeritageClause) => { 24 | return new HeritageClause(clause); 25 | }) 26 | .filter(filterUndefined); 27 | } 28 | this.members = node.members.map(convertMember).filter(filterUndefined); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/json/named_declarations/member_declaration.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {convertExpression, convertTypeNode, filterUndefined} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {Type} from '../types'; 6 | 7 | import {NamedDeclaration} from './named_declaration'; 8 | import {ParameterDeclaration} from './parameter_declaration'; 9 | 10 | export abstract class MemberDeclaration extends NamedDeclaration { 11 | constructor( 12 | node: ts.ClassElement|ts.TypeElement, 13 | kind: ConvertedSyntaxKind.PropertyDeclaration|ConvertedSyntaxKind.MethodDeclaration) { 14 | super(node, kind); 15 | } 16 | } 17 | 18 | export class PropertyDeclaration extends MemberDeclaration { 19 | private type: Type; 20 | private optional = false; 21 | private initializer?: string; 22 | constructor(node: ts.PropertyDeclaration|ts.PropertySignature) { 23 | super(node, ConvertedSyntaxKind.PropertyDeclaration); 24 | 25 | this.type = convertTypeNode(node.type); 26 | if (node.questionToken) { 27 | this.optional = true; 28 | } 29 | if (node.initializer) { 30 | this.initializer = convertExpression(node.initializer); 31 | } 32 | } 33 | } 34 | 35 | export class MethodDeclaration extends MemberDeclaration { 36 | private type: Type; 37 | private optional = false; 38 | private parameters: ParameterDeclaration[]; 39 | constructor(node: ts.MethodDeclaration|ts.MethodSignature) { 40 | super(node, ConvertedSyntaxKind.MethodDeclaration); 41 | 42 | this.type = convertTypeNode(node.type); 43 | if (node.questionToken) { 44 | this.optional = true; 45 | } 46 | this.parameters = 47 | node.parameters.map((param: ts.ParameterDeclaration) => new ParameterDeclaration(param)) 48 | .filter(filterUndefined); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/json/named_declarations/named_declaration.ts: -------------------------------------------------------------------------------- 1 | import {NamedDeclaration as tsNamedDeclaration} from 'typescript'; 2 | 3 | import {convertName} from '../conversions'; 4 | import {ConvertedNamedDeclarationKind} from '../converted_syntax_kinds'; 5 | import {Node} from '../node'; 6 | 7 | export abstract class NamedDeclaration extends Node { 8 | private name?: string; 9 | constructor(node: tsNamedDeclaration, kind: ConvertedNamedDeclarationKind) { 10 | super(node, kind); 11 | 12 | if (node.name) { 13 | this.name = convertName(node.name); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/json/named_declarations/parameter_declaration.ts: -------------------------------------------------------------------------------- 1 | import {ParameterDeclaration as tsParameterDeclaration} from 'typescript'; 2 | 3 | import {convertExpression, convertTypeNode} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {Type} from '../types'; 6 | 7 | import {NamedDeclaration} from './named_declaration'; 8 | 9 | export class ParameterDeclaration extends NamedDeclaration { 10 | private type: Type; 11 | private optional = false; 12 | private destructured = false; 13 | private initializer?: string; 14 | 15 | constructor(node: tsParameterDeclaration) { 16 | super(node, ConvertedSyntaxKind.Parameter); 17 | 18 | this.type = convertTypeNode(node.type); 19 | if (node.questionToken) { 20 | this.optional = true; 21 | } 22 | if (node.dotDotDotToken) { 23 | this.destructured = true; 24 | } 25 | if (node.initializer) { 26 | this.initializer = convertExpression(node.initializer); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/json/named_declarations/signature_declaration.ts: -------------------------------------------------------------------------------- 1 | import {SignatureDeclaration as tsSignatureDeclaration} from 'typescript'; 2 | 3 | import {convertParameter, convertTypeNode, convertTypeParameter, filterUndefined} from '../conversions'; 4 | import {ConvertedSignatureKind} from '../converted_syntax_kinds'; 5 | import {Type} from '../types'; 6 | 7 | import {NamedDeclaration} from './named_declaration'; 8 | import {ParameterDeclaration} from './parameter_declaration'; 9 | import {TypeParameterDeclaration} from './type_parameter_declaration'; 10 | 11 | export abstract class SignatureDeclaration extends NamedDeclaration { 12 | private typeParameters?: TypeParameterDeclaration[]; 13 | private parameters: ParameterDeclaration[]; 14 | private type?: Type; 15 | constructor(node: tsSignatureDeclaration, kind: ConvertedSignatureKind) { 16 | super(node, kind); 17 | 18 | if (node.typeParameters) { 19 | this.typeParameters = node.typeParameters.map(convertTypeParameter).filter(filterUndefined); 20 | } 21 | this.parameters = node.parameters.map(convertParameter).filter(filterUndefined); 22 | if (node.type) { 23 | this.type = convertTypeNode(node.type); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/json/named_declarations/type_alias_declaration.ts: -------------------------------------------------------------------------------- 1 | import {TypeAliasDeclaration as tsTypeAliasDeclaration} from 'typescript'; 2 | 3 | import {convertTypeNode, convertTypeParameter, filterUndefined} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {Type} from '../types'; 6 | 7 | import {NamedDeclaration} from './named_declaration'; 8 | import {TypeParameterDeclaration} from './type_parameter_declaration'; 9 | 10 | export class TypeAliasDeclaration extends NamedDeclaration { 11 | private type: Type; 12 | private typeParameters?: TypeParameterDeclaration[]; 13 | constructor(node: tsTypeAliasDeclaration) { 14 | super(node, ConvertedSyntaxKind.TypeAliasDeclaration); 15 | 16 | this.type = convertTypeNode(node.type); 17 | if (node.typeParameters) { 18 | this.typeParameters = node.typeParameters.map(convertTypeParameter).filter(filterUndefined); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/json/named_declarations/type_parameter_declaration.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {convertTypeNode} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {Type} from '../types'; 6 | 7 | import {NamedDeclaration} from './named_declaration'; 8 | 9 | 10 | export class TypeParameterDeclaration extends NamedDeclaration { 11 | private constraint?: Type; 12 | private default?: Type; 13 | 14 | constructor(node: ts.TypeParameterDeclaration) { 15 | super(node, ConvertedSyntaxKind.TypeParameter); 16 | 17 | if (node.constraint) { 18 | this.constraint = convertTypeNode(ts.getEffectiveConstraintOfTypeParameter(node)); 19 | } 20 | if (node.default) { 21 | this.default = convertTypeNode(node.default); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/json/named_declarations/variable_declaration.ts: -------------------------------------------------------------------------------- 1 | import {VariableDeclaration as tsVariableDeclaration} from 'typescript'; 2 | 3 | import {convertTypeNode} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {Type} from '../types'; 6 | 7 | import {NamedDeclaration} from './named_declaration'; 8 | 9 | export class VariableDeclaration extends NamedDeclaration { 10 | private type?: Type; 11 | constructor(node: tsVariableDeclaration) { 12 | super(node, ConvertedSyntaxKind.VariableDeclaration); 13 | 14 | if (node.type) { 15 | this.type = convertTypeNode(node.type); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/json/node.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {filterUndefined} from './conversions'; 4 | import {ConvertedModifierKind, ConvertedSyntaxKind} from './converted_syntax_kinds'; 5 | 6 | export abstract class Node { 7 | private decorators?: Decorator[]; 8 | private modifiers?: Modifier[]; 9 | constructor(node: ts.Node, private kind: ConvertedSyntaxKind) { 10 | if (Array.isArray(node.decorators)) { 11 | this.decorators = node.decorators.map(convertDecorator).filter(filterUndefined); 12 | } 13 | if (Array.isArray(node.modifiers)) { 14 | this.modifiers = node.modifiers.map(convertModifier).filter(filterUndefined); 15 | } 16 | } 17 | } 18 | 19 | class Decorator extends Node { 20 | constructor(node: ts.Decorator) { 21 | super(node, ConvertedSyntaxKind.Decorator); 22 | } 23 | } 24 | 25 | function convertDecorator(decorator: ts.Decorator): Decorator { 26 | return new Decorator(decorator); 27 | } 28 | 29 | class Modifier extends Node { 30 | constructor(node: ts.Modifier, kind: ConvertedModifierKind) { 31 | super(node, kind); 32 | } 33 | } 34 | 35 | function convertModifier(modifier: ts.Modifier): Modifier { 36 | switch (modifier.kind) { 37 | case ts.SyntaxKind.AbstractKeyword: 38 | return new Modifier(modifier, ConvertedSyntaxKind.AbstractModifier); 39 | case ts.SyntaxKind.AsyncKeyword: 40 | return new Modifier(modifier, ConvertedSyntaxKind.AsyncModifier); 41 | case ts.SyntaxKind.ConstKeyword: 42 | return new Modifier(modifier, ConvertedSyntaxKind.ConstModifier); 43 | case ts.SyntaxKind.ExportKeyword: 44 | return new Modifier(modifier, ConvertedSyntaxKind.ExportModifier); 45 | case ts.SyntaxKind.PublicKeyword: 46 | return new Modifier(modifier, ConvertedSyntaxKind.PublicModifier); 47 | case ts.SyntaxKind.PrivateKeyword: 48 | return new Modifier(modifier, ConvertedSyntaxKind.PrivateModifier); 49 | case ts.SyntaxKind.ProtectedKeyword: 50 | return new Modifier(modifier, ConvertedSyntaxKind.ProtectedModifier); 51 | case ts.SyntaxKind.ReadonlyKeyword: 52 | return new Modifier(modifier, ConvertedSyntaxKind.ReadonlyModifier); 53 | case ts.SyntaxKind.StaticKeyword: 54 | return new Modifier(modifier, ConvertedSyntaxKind.StaticModifier); 55 | case ts.SyntaxKind.DefaultKeyword: 56 | return new Modifier(modifier, ConvertedSyntaxKind.DefaultModifier); 57 | case ts.SyntaxKind.DeclareKeyword: 58 | // no-op, this modifier doesn't get used by the emitter 59 | return undefined; 60 | default: 61 | const error = 62 | new Error(`Unexpected Modifier kind: ${ts.SyntaxKind[(modifier as ts.Node).kind]}`); 63 | throw error; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/json/source_file.ts: -------------------------------------------------------------------------------- 1 | import {SourceFile as tsSourceFile} from 'typescript'; 2 | 3 | import {convertNode, filterUndefined} from './conversions'; 4 | import {ConvertedSyntaxKind} from './converted_syntax_kinds'; 5 | import {Node} from './node'; 6 | 7 | export class SourceFile extends Node { 8 | private fileName: string; 9 | private statements: Node[]; 10 | 11 | constructor(file: tsSourceFile) { 12 | super(file, ConvertedSyntaxKind.SourceFile); 13 | this.fileName = file.fileName; 14 | this.statements = file.statements.map(convertNode).filter(filterUndefined); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/json/types/function_type.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | import {SignatureDeclaration} from '../named_declarations'; 5 | 6 | import {Type} from './type'; 7 | 8 | export class FunctionType extends SignatureDeclaration implements Type { 9 | constructor(node: ts.FunctionTypeNode) { 10 | super(node, ConvertedSyntaxKind.FunctionType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/json/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type'; 2 | export * from './type_reference'; 3 | export * from './type_literal'; 4 | export * from './keyword_type'; 5 | export * from './literal_type'; 6 | export * from './function_type'; 7 | -------------------------------------------------------------------------------- /lib/json/types/keyword_type.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import {ConvertedKeywordType, ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {Type} from './type'; 6 | 7 | export class KeywordType extends Type { 8 | constructor(node: ts.KeywordTypeNode, private typeName: ConvertedKeywordType) { 9 | super(node, ConvertedSyntaxKind.KeywordType); 10 | } 11 | } 12 | 13 | /** 14 | * Returns whether or not a TypeNode is a KeywordTypeNode. 15 | */ 16 | export function isKeywordTypeNode(type: ts.TypeNode): type is ts.KeywordTypeNode { 17 | switch (type.kind) { 18 | case ts.SyntaxKind.AnyKeyword: 19 | case ts.SyntaxKind.UnknownKeyword: 20 | case ts.SyntaxKind.NumberKeyword: 21 | case ts.SyntaxKind.BigIntKeyword: 22 | case ts.SyntaxKind.ObjectKeyword: 23 | case ts.SyntaxKind.BooleanKeyword: 24 | case ts.SyntaxKind.StringKeyword: 25 | case ts.SyntaxKind.SymbolKeyword: 26 | case ts.SyntaxKind.ThisKeyword: 27 | case ts.SyntaxKind.VoidKeyword: 28 | case ts.SyntaxKind.UndefinedKeyword: 29 | case ts.SyntaxKind.NullKeyword: 30 | case ts.SyntaxKind.NeverKeyword: 31 | case ts.SyntaxKind.NumberKeyword: 32 | return true; 33 | default: 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/json/types/literal_type.ts: -------------------------------------------------------------------------------- 1 | import {LiteralTypeNode as tsLiteralTypeNode} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | 5 | import {Type} from './type'; 6 | 7 | export class LiteralType extends Type { 8 | constructor(node: tsLiteralTypeNode, private typeName: string) { 9 | super(node, ConvertedSyntaxKind.LiteralType); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/json/types/type.ts: -------------------------------------------------------------------------------- 1 | import {TypeNode as tsTypeNode} from 'typescript'; 2 | 3 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 4 | import {Node} from '../node'; 5 | 6 | export abstract class Type extends Node { 7 | constructor(node: tsTypeNode, kind: ConvertedSyntaxKind) { 8 | super(node, kind); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/json/types/type_literal.ts: -------------------------------------------------------------------------------- 1 | import {TypeLiteralNode as tsTypeLiteralNode} from 'typescript'; 2 | 3 | import {convertMember} from '../conversions'; 4 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 5 | import {MemberDeclaration} from '../named_declarations'; 6 | 7 | import {Type} from './type'; 8 | 9 | export class TypeLiteral extends Type { 10 | members: MemberDeclaration[]; 11 | constructor(node: tsTypeLiteralNode) { 12 | super(node, ConvertedSyntaxKind.TypeLiteral); 13 | 14 | this.members = node.members.map(convertMember); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/json/types/type_reference.ts: -------------------------------------------------------------------------------- 1 | import {TypeReferenceNode as tsTypeReferenceNode} from 'typescript'; 2 | 3 | import * as base from '../../base'; 4 | import {convertTypeNode, filterUndefined} from '../conversions'; 5 | import {ConvertedSyntaxKind} from '../converted_syntax_kinds'; 6 | 7 | import {Type} from './type'; 8 | 9 | export class TypeReference extends Type { 10 | typeName: string; 11 | typeArguments?: Type[]; 12 | constructor(node: tsTypeReferenceNode) { 13 | super(node, ConvertedSyntaxKind.TypeReference); 14 | 15 | this.typeName = base.ident(node.typeName); 16 | if (node.typeArguments) { 17 | this.typeArguments = node.typeArguments.map(convertTypeNode).filter(filterUndefined); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/json/variable_statement.ts: -------------------------------------------------------------------------------- 1 | import {NodeFlags as tsNodeFlags, VariableStatement as tsVariableStatement} from 'typescript'; 2 | 3 | import {convertNode, filterUndefined} from './conversions'; 4 | import {ConvertedSyntaxKind} from './converted_syntax_kinds'; 5 | import {Node} from './node'; 6 | 7 | export class VariableStatement extends Node { 8 | private keyword: 'var'|'let'|'const'; 9 | declarations: Node[]; 10 | constructor(node: tsVariableStatement) { 11 | super(node, ConvertedSyntaxKind.VariableStatement); 12 | 13 | const flags = node.declarationList.flags; 14 | if (flags & tsNodeFlags.Const) { 15 | this.keyword = 'const'; 16 | } else if (flags & tsNodeFlags.Let) { 17 | this.keyword = 'let'; 18 | } else { 19 | this.keyword = 'var'; 20 | } 21 | this.declarations = node.declarationList.declarations.map(convertNode).filter(filterUndefined); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/mkdirp.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | export default function mkdirP(p: string) { 5 | // Convert input path to absolute and then relative so that we always have relative path in the 6 | // end. This can be made simpler when path.isAbsolute is available in node v0.12. 7 | p = path.resolve(p); 8 | p = path.relative('', p); 9 | 10 | let pathToCreate = ''; 11 | p.split(path.sep).forEach(dirName => { 12 | pathToCreate = path.join(pathToCreate, dirName); 13 | if (!fs.existsSync(pathToCreate)) { 14 | fs.mkdirSync(pathToCreate); 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/module.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import * as base from './base'; 4 | import {FacadeConverter} from './facade_converter'; 5 | import {OutputContext, Transpiler} from './main'; 6 | 7 | export default class ModuleTranspiler extends base.TranspilerBase { 8 | constructor(tr: Transpiler, private fc: FacadeConverter, private moduleName: string) { 9 | super(tr); 10 | } 11 | 12 | visitNode(node: ts.Node): boolean { 13 | switch (node.kind) { 14 | case ts.SyntaxKind.SourceFile: 15 | this.pushContext(OutputContext.Import); 16 | let sourceFile = node as ts.SourceFile; 17 | let moduleName = this.moduleName; 18 | if (sourceFile.moduleName) { 19 | moduleName = sourceFile.moduleName; 20 | } 21 | sourceFile.statements.forEach((n: ts.Node) => { 22 | if (ts.isNamespaceExportDeclaration(n)) { 23 | moduleName = base.ident(n.name); 24 | } 25 | }); 26 | if (moduleName) { 27 | this.emit(`@JS("${moduleName}")`); 28 | } else { 29 | this.emit('@JS()'); 30 | } 31 | this.emit('library'); 32 | this.emit(this.getLibraryName()); 33 | this.emit(';'); 34 | this.popContext(); 35 | 36 | this.addImport('package:js/js.dart'); 37 | // The declaration transpiler is responsible for emitting the contents of the source file. 38 | return false; 39 | case ts.SyntaxKind.EndOfFileToken: 40 | ts.forEachChild(node, this.visit.bind(this)); 41 | break; 42 | case ts.SyntaxKind.ImportDeclaration: 43 | // Intentionally skip import clauses as we can do a better job generating imports from the 44 | // resolved entities referenced. This works better as there are import concepts in 45 | // TypeScript such as renaming imports that we cannot support in Dart. 46 | 47 | // TODO(jacobr): to reduce naming conflicts in complex cases we should check for 48 | // ImportDeclarations that have a NamespaceImport clause and use that prefix in the Dart 49 | // code we generating by setting the asPrefix the same as how we do for the 50 | // ImportEqualsDeclaration case. 51 | break; 52 | case ts.SyntaxKind.NamespaceImport: 53 | let nsImport = node; 54 | this.emit('as'); 55 | this.emit(base.ident(nsImport.name)); 56 | break; 57 | case ts.SyntaxKind.NamedImports: 58 | this.emit('show'); 59 | let used = this.filterImports((node).elements); 60 | if (used.length === 0) { 61 | this.reportError(node, 'internal error, used imports must not be empty'); 62 | } 63 | this.visitList(used); 64 | break; 65 | case ts.SyntaxKind.NamedExports: 66 | let exportElements = (node).elements; 67 | this.emit('show'); 68 | if (exportElements.length === 0) this.reportError(node, 'empty export list'); 69 | this.visitList((node).elements); 70 | break; 71 | case ts.SyntaxKind.ImportSpecifier: 72 | case ts.SyntaxKind.ExportSpecifier: 73 | let spec = node; 74 | if (spec.propertyName) { 75 | this.reportError(spec.propertyName, 'import/export renames are unsupported in Dart'); 76 | } 77 | this.fc.visitTypeName(spec.name); 78 | break; 79 | case ts.SyntaxKind.NamespaceExportDeclaration: 80 | // We handle this globally exporting all files in the packge with the specified global 81 | // module export location. 82 | break; 83 | case ts.SyntaxKind.ExportDeclaration: 84 | let exportDecl = node; 85 | this.emit('export'); 86 | if (exportDecl.moduleSpecifier) { 87 | this.emit( 88 | JSON.stringify(this.getExternalModuleReferenceExpr(exportDecl.moduleSpecifier))); 89 | } else { 90 | this.reportError(node, 're-exports must have a module URL (export x from "./y").'); 91 | } 92 | if (exportDecl.exportClause) this.visit(exportDecl.exportClause); 93 | this.emit(';\n'); 94 | break; 95 | case ts.SyntaxKind.ImportEqualsDeclaration: 96 | // We could ignore ImportEqualsDeclarations and but we track them to make the Dart output 97 | // look more visually similar to the input. 98 | let importEqDecl = node; 99 | let fileName = this.getExternalModuleReferenceExpr(importEqDecl.moduleReference); 100 | this.getImportSummary(fileName).asPrefix = base.ident(importEqDecl.name); 101 | break; 102 | 103 | default: 104 | return false; 105 | } 106 | return true; 107 | } 108 | 109 | private isIgnoredImport(e: ts.ImportSpecifier): boolean { 110 | // We need to hide import import specifiers that reference names that are not actually exported 111 | // by Dart. Currently this means suppressing unsupported type aliases. 112 | let s: ts.Symbol = this.fc.tc.getSymbolAtLocation(e.name); 113 | s = this.fc.tc.getAliasedSymbol(s); 114 | if (!s || !s.declarations) return false; 115 | let decl = s.declarations[0]; 116 | if (!decl) return false; 117 | return !base.supportedTypeDeclaration(decl); 118 | } 119 | 120 | private getExternalModuleReferenceExpr(expr: ts.Node): string { 121 | if (ts.isExternalModuleReference(expr)) { 122 | expr = expr.expression; 123 | } 124 | if (!ts.isStringLiteral(expr)) { 125 | this.reportError(expr, 'Unexpected module reference type:' + expr.kind); 126 | } 127 | let moduleName = expr; 128 | let text = moduleName.text; 129 | // TODO(jacobr): actually handle files in different directories. We assume for now that all 130 | // files in a library will be output to a single directory for codegen simplicity. 131 | let parts = text.split('/'); 132 | text = parts[parts.length - 1]; 133 | 134 | return text + '.dart'; 135 | } 136 | 137 | private filterImports(ns: ts.NodeArray) { 138 | let that = this; 139 | return ts.createNodeArray(ns.filter((e) => !that.isIgnoredImport(e))); 140 | } 141 | 142 | getLibraryName(jsFileName?: string) { 143 | let fileName = this.getDartFileName(jsFileName); 144 | let parts = fileName.split('/'); 145 | return parts.filter((p) => p.length > 0 && p !== '..') 146 | .map((p) => p.replace(/[^\w.]/g, '_')) 147 | .map((p) => p.replace(/\.dart$/, '')) 148 | .map((p) => FacadeConverter.DART_RESERVED_WORDS.has(p) ? '_' + p : p) 149 | .filter((p) => p.length > 0) 150 | .join('.'); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/type.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | import * as base from './base'; 4 | import {FacadeConverter, fixupIdentifierName} from './facade_converter'; 5 | import {Transpiler} from './main'; 6 | 7 | export default class TypeTranspiler extends base.TranspilerBase { 8 | constructor(tr: Transpiler, private fc: FacadeConverter) { 9 | super(tr); 10 | } 11 | 12 | visitNode(node: ts.Node): boolean { 13 | if (base.isTypeNode(node)) { 14 | this.emit(this.fc.generateDartTypeName(node)); 15 | return true; 16 | } 17 | if (ts.isTypeAssertion(node)) { 18 | let typeAssertExpr = node; 19 | if (this.isReifiedTypeLiteral(typeAssertExpr)) { 20 | this.visit(typeAssertExpr.expression); 21 | // type is handled by the container literal itself. 22 | } 23 | this.emit('('); 24 | this.visit(typeAssertExpr.expression); 25 | this.emit('as'); 26 | this.visit(typeAssertExpr.type); 27 | this.emit(')'); 28 | } else if (ts.isTypeParameterDeclaration(node)) { 29 | let typeParam = node; 30 | this.visit(typeParam.name); 31 | if (typeParam.constraint) { 32 | this.emit('extends'); 33 | this.visit(typeParam.constraint); 34 | } 35 | } else if (ts.isPropertyAccessExpression(node)) { 36 | this.visit(node.expression); 37 | this.emit('.'); 38 | this.fc.visitTypeName(node.name); 39 | } else if (ts.isQualifiedName(node)) { 40 | // TODO(jacobr): there is overlap between this case and 41 | // generateDartTypeName in facade_converter. 42 | let first = node; 43 | let match = this.fc.lookupCustomDartTypeName(first); 44 | if (match) { 45 | this.emitType(match.name, match.comment); 46 | } 47 | this.visit(first.left); 48 | this.emit('.'); 49 | this.visit(first.right); 50 | } else if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) { 51 | const text = fixupIdentifierName(node); 52 | this.emit(text); 53 | } else { 54 | return false; 55 | } 56 | return true; 57 | } 58 | 59 | isReifiedTypeLiteral(node: ts.TypeAssertion): boolean { 60 | if (node.expression.kind === ts.SyntaxKind.ArrayLiteralExpression && 61 | node.type.kind === ts.SyntaxKind.ArrayType) { 62 | return true; 63 | } else if ( 64 | node.expression.kind === ts.SyntaxKind.ObjectLiteralExpression && 65 | node.type.kind === ts.SyntaxKind.TypeLiteral) { 66 | return true; 67 | } 68 | return false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dart_js_facade_gen", 3 | "version": "0.0.7", 4 | "description": "Generate Dart package:js JS Interop faces for arbitrary TypeScript libraries.", 5 | "dependencies": { 6 | "@types/chai": "^4.2.3", 7 | "@types/fs-extra": "^8.0.0", 8 | "@types/minimist": "^1.2.0", 9 | "@types/mocha": "^5.2.7", 10 | "@types/node": "^12.7.8", 11 | "@types/source-map": "^0.5.7", 12 | "@types/source-map-support": "^0.5.0", 13 | "dart-style": "^1.3.2-dev", 14 | "minimist": "^1.2.0", 15 | "source-map": "^0.7.3", 16 | "source-map-support": "^0.5.13", 17 | "typescript": "^3.6.3" 18 | }, 19 | "devDependencies": { 20 | "chai": "^4.2.0", 21 | "clang-format": "1.2.4", 22 | "fs-extra": "^8.1.0", 23 | "gulp": "^4.0.2", 24 | "gulp-clang-format": "1.0.27", 25 | "gulp-mocha": "^7.0.1", 26 | "gulp-sourcemaps": "^2.6.5", 27 | "gulp-tslint": "8.1.4", 28 | "gulp-typescript": "^5.0.1", 29 | "gulp-util": "^3.0.8", 30 | "merge2": "^1.3.0", 31 | "npm-release": "^1.0.0", 32 | "temp": "^0.9.0", 33 | "tslint": "^5.20.0", 34 | "which": "^1.3.1" 35 | }, 36 | "preferGlobal": true, 37 | "bin": { 38 | "dart_js_facade_gen": "index.js" 39 | }, 40 | "scripts": { 41 | "prepare": "gulp compile", 42 | "test": "gulp test", 43 | "release": "npm-release" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/dart-lang/js_facade_gen.git" 48 | }, 49 | "keywords": [ 50 | "typescript", 51 | "dart" 52 | ], 53 | "contributors": [ 54 | "Dart Team (https://www.dartlang.org/)" 55 | ], 56 | "license": "Apache-2.0", 57 | "bugs": { 58 | "url": "https://github.com/dart-lang/js_facade_gen/issues" 59 | }, 60 | "homepage": "https://github.com/dart-lang/js_facade_gen" 61 | } 62 | -------------------------------------------------------------------------------- /test/call_test.ts: -------------------------------------------------------------------------------- 1 | import {expectTranslate} from './test_support'; 2 | 3 | describe('calls', () => { 4 | it('translates destructuring parameters', () => { 5 | expectTranslate('function x({p = null, d = false} = {}) {}').to.equal(`@JS() 6 | external x(Object p_d /*{p = null, d = false}*/);`); 7 | expectTranslate('function x({a=false}={a:true})').to.equal(`@JS() 8 | external x(Object a /*{a=false}*/);`); 9 | expectTranslate('function x({a=false}=true)').to.equal(`@JS() 10 | external x(Object a /*{a=false}*/);`); 11 | expectTranslate('class X { constructor() { super({p: 1}); } }').to.equal(`@JS() 12 | class X { 13 | // @Ignore 14 | X.fakeConstructor$(); 15 | external factory X(); 16 | }`); 17 | }); 18 | it('suppress calls with literal parameters', () => { 19 | expectTranslate('f(x, {a: 12, b: 4});').to.equal(''); 20 | expectTranslate('f({a: 12});').to.equal(''); 21 | expectTranslate('f({"a": 12});').to.equal(''); 22 | expectTranslate('new X(x, {a: 12, b: 4});').to.equal(''); 23 | expectTranslate('f(x, {});').to.equal(''); 24 | }); 25 | it('suppress calls', () => { 26 | expectTranslate('foo();').to.equal(''); 27 | expectTranslate('foo(1, 2);').to.equal(''); 28 | }); 29 | it('suppress new calls', () => { 30 | expectTranslate('new Foo();').to.equal(''); 31 | expectTranslate('new Foo(1, 2);').to.equal(''); 32 | expectTranslate('new Foo(1, 2);').to.equal(''); 33 | }); 34 | it('suppress "super()" constructor calls', () => { 35 | expectTranslate('class X { constructor() { super(1); } }').to.equal(`@JS() 36 | class X { 37 | // @Ignore 38 | X.fakeConstructor$(); 39 | external factory X(); 40 | }`); 41 | expectTranslate('class X { constructor() { if (y) super(1, 2); } }').to.equal(`@JS() 42 | class X { 43 | // @Ignore 44 | X.fakeConstructor$(); 45 | external factory X(); 46 | }`); 47 | expectTranslate('class X { constructor() { a(); super(1); b(); } }').to.equal(`@JS() 48 | class X { 49 | // @Ignore 50 | X.fakeConstructor$(); 51 | external factory X(); 52 | }`); 53 | }); 54 | it('ignore "super.x()" super method calls', () => { 55 | expectTranslate('class X { y() { super.z(1); } }').to.equal(`@JS() 56 | class X { 57 | // @Ignore 58 | X.fakeConstructor$(); 59 | external y(); 60 | }`); 61 | }); 62 | it('suppress new calls without arguments', () => { 63 | expectTranslate('new Foo;').to.equal(''); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/decorator_test.ts: -------------------------------------------------------------------------------- 1 | import {expectTranslate} from './test_support'; 2 | 3 | 4 | // We want to ignore decorators on JS interop for now. 5 | // These tests make sure we haven't accidentally left in historic code from 6 | // ts2dart that export decorators. 7 | describe('ignore decorators', () => { 8 | it('translates plain decorators', () => { 9 | expectTranslate('@A class X {}').to.equal(`@JS() 10 | class X { 11 | // @Ignore 12 | X.fakeConstructor$(); 13 | }`); 14 | }); 15 | it('ignore plain decorators applied to abstract classes', () => { 16 | expectTranslate('@A abstract class X {}').to.equal(`@JS() 17 | abstract class X { 18 | // @Ignore 19 | X.fakeConstructor$(); 20 | }`); 21 | }); 22 | it('translates arguments', () => { 23 | expectTranslate('@A(a, b) class X {}').to.equal(`@JS() 24 | class X { 25 | // @Ignore 26 | X.fakeConstructor$(); 27 | }`); 28 | }); 29 | it('translates const arguments', () => { 30 | expectTranslate('@A([1]) class X {}').to.equal(`@JS() 31 | class X { 32 | // @Ignore 33 | X.fakeConstructor$(); 34 | }`); 35 | expectTranslate('@A({"a": 1}) class X {}').to.equal(`@JS() 36 | class X { 37 | // @Ignore 38 | X.fakeConstructor$(); 39 | }`); 40 | expectTranslate('@A(new B()) class X {}').to.equal(`@JS() 41 | class X { 42 | // @Ignore 43 | X.fakeConstructor$(); 44 | }`); 45 | }); 46 | it('translates on functions', () => { 47 | expectTranslate('@A function f() {}').to.equal(`@JS() 48 | external f();`); 49 | }); 50 | it('translates on properties', () => { 51 | expectTranslate('class X { @A p; }').to.equal(`@JS() 52 | class X { 53 | // @Ignore 54 | X.fakeConstructor$(); 55 | external get p; 56 | external set p(v); 57 | }`); 58 | }); 59 | it('translates on parameters', () => { 60 | expectTranslate('function f (@A p) {}').to.equal(`@JS() 61 | external f(p);`); 62 | }); 63 | it('ignore special cases @CONST', () => { 64 | expectTranslate('@CONST class X {}').to.equal(`@JS() 65 | class X { 66 | // @Ignore 67 | X.fakeConstructor$(); 68 | }`); 69 | expectTranslate('@CONST() class X {}').to.equal(`@JS() 70 | class X { 71 | // @Ignore 72 | X.fakeConstructor$(); 73 | }`); 74 | expectTranslate(`@CONST class X { 75 | x: number; 76 | y; 77 | constructor() { super(3); this.x = 1; this.y = 2; } 78 | }`) 79 | .to.equal(`@JS() 80 | class X { 81 | // @Ignore 82 | X.fakeConstructor$(); 83 | external num get x; 84 | external set x(num v); 85 | external get y; 86 | external set y(v); 87 | external factory X(); 88 | }`); 89 | 90 | // @CONST constructors. 91 | expectTranslate('@CONST class X { constructor() {} }').to.equal(`@JS() 92 | class X { 93 | // @Ignore 94 | X.fakeConstructor$(); 95 | external factory X(); 96 | }`); 97 | // For backwards-compatibility for traceur inputs (not valid TS input) 98 | expectTranslate('class X { @CONST constructor() {} }').to.equal(`@JS() 99 | class X { 100 | // @Ignore 101 | X.fakeConstructor$(); 102 | external factory X(); 103 | }`); 104 | 105 | // @CONST properties. 106 | expectTranslate('class Foo { @CONST() static foo = 1; }').to.equal(`@JS() 107 | class Foo { 108 | // @Ignore 109 | Foo.fakeConstructor$(); 110 | external static get foo; 111 | external static set foo(v); 112 | }`); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/expression_test.ts: -------------------------------------------------------------------------------- 1 | import {expectTranslate} from './test_support'; 2 | 3 | function expectEmptyTranslates(cases: string[]) { 4 | for (const tsCode of cases) { 5 | expectTranslate(tsCode).to.equal(''); 6 | } 7 | } 8 | 9 | // TODO(jacobr): we don't really need to be specifying separate code for the 10 | // JS and Dart version for these tests as the code formatting is identical. 11 | describe('ignore expressions', () => { 12 | it('math', () => { 13 | expectEmptyTranslates([ 14 | '1 + 2', 15 | '1 - 2', 16 | '1 * 2', 17 | '1 / 2', 18 | '1 % 2', 19 | 'x++', 20 | 'x--', 21 | '++x', 22 | '--x', 23 | '-x', 24 | ]); 25 | }); 26 | it('assigns', () => { 27 | expectEmptyTranslates([ 28 | 'x += 1', 29 | 'x -= 1', 30 | 'x *= 1', 31 | 'x /= 1', 32 | 'x %= 1', 33 | 'x <<= 1', 34 | 'x >>= 1', 35 | 'x >>>= 1', 36 | 'x &= 1', 37 | 'x ^= 1', 38 | 'x |= 1', 39 | ]); 40 | }); 41 | it('compares', () => { 42 | expectEmptyTranslates([ 43 | '1 == 2', 44 | '1 != 2', 45 | '1 > 2', 46 | '1 < 2', 47 | '1 >= 2', 48 | '1 <= 2', 49 | ]); 50 | }); 51 | it('compares identity', () => { 52 | expectEmptyTranslates(['1 === 2', '1 !== 2']); 53 | }); 54 | it('bit fiddles', () => { 55 | expectEmptyTranslates( 56 | ['x & 2', '1 & 2', '1 | 2', '1 ^ 2', '~1', '1 << 2', '1 >> 2', '0x1 & 0x2', '1 >>> 2']); 57 | }); 58 | it('translates logic', () => { 59 | expectEmptyTranslates([ 60 | '1 && 2', 61 | '1 || 2', 62 | '!1', 63 | ]); 64 | }); 65 | it('translates ternary', () => { 66 | expectTranslate('var x = 1 ? 2 : 3').to.equal(`@JS() 67 | external get x; 68 | @JS() 69 | external set x(v);`); 70 | }); 71 | it('translates the comma operator', () => { 72 | expectTranslate('var x = [1, 2]').to.equal(`@JS() 73 | external get x; 74 | @JS() 75 | external set x(v);`); 76 | }); 77 | it('translates "in"', () => { 78 | expectTranslate('x in y').to.equal(''); 79 | }); 80 | it('translates "instanceof"', () => { 81 | expectTranslate('1 instanceof Foo').to.equal(''); 82 | }); 83 | it('translates "this"', () => { 84 | expectTranslate('this.x').to.equal(''); 85 | }); 86 | it('translates "delete"', () => { 87 | expectTranslate('delete x[y];').to.equal(''); 88 | }); 89 | it('translates "typeof"', () => { 90 | expectTranslate('typeof x;').to.equal(''); 91 | }); 92 | it('translates "void"', () => { 93 | expectTranslate('void x;').to.equal(''); 94 | }); 95 | it('translates parens', () => { 96 | expectTranslate('(1)').to.equal(''); 97 | }); 98 | 99 | it('translates property paths', () => { 100 | expectTranslate('foo.bar;').to.equal(''); 101 | expectTranslate('foo[bar];').to.equal(''); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/facade_converter_test.ts: -------------------------------------------------------------------------------- 1 | import {expectTranslate, FAKE_MAIN} from './test_support'; 2 | 3 | function getSources(str: string): Map { 4 | const srcs: Map = new Map(Object.entries({ 5 | 'other.ts': ` 6 | export class X { 7 | map(x: number): string { return String(x); } 8 | static get(m: any, k: string): number { return m[k]; } 9 | } 10 | `, 11 | })); 12 | srcs.set(FAKE_MAIN, str); 13 | return srcs; 14 | } 15 | 16 | const COMPILE_OPTS = { 17 | translateBuiltins: true, 18 | failFast: true, 19 | typingsRoot: 'some/path/to/typings/', 20 | }; 21 | 22 | function expectWithTypes(str: string) { 23 | return expectTranslate(getSources(str), COMPILE_OPTS); 24 | } 25 | 26 | describe('type based translation', () => { 27 | describe('Dart type substitution', () => { 28 | describe('finds registered substitutions', () => { 29 | it('for dart:html types by default', () => { 30 | expectWithTypes('const n: Node;').to.equal(`import "dart:html" show Node; 31 | 32 | @JS() 33 | external Node get n;`); 34 | 35 | expectWithTypes('const xhr: XMLHttpRequest;').to.equal(`import "dart:html" show HttpRequest; 36 | 37 | @JS() 38 | external HttpRequest get xhr;`); 39 | }); 40 | 41 | it('but does not import dart:html types when the flag is set', () => { 42 | const generateHTMLOpts = Object.assign({}, COMPILE_OPTS, {generateHTML: true}); 43 | expectTranslate('const n: Node', generateHTMLOpts).to.equal(`@JS() 44 | external Node get n;`); 45 | 46 | expectTranslate( 47 | `interface XMLHttpRequest { 48 | readonly readyState: number; 49 | readonly response: any; 50 | readonly responseText: string; 51 | readonly DONE: number; 52 | readonly HEADERS_RECEIVED: number; 53 | readonly LOADING: number; 54 | readonly OPENED: number; 55 | readonly UNSENT: number; 56 | } 57 | 58 | const xhr: XMLHttpRequest`, 59 | generateHTMLOpts) 60 | .to.equal(`@anonymous 61 | @JS() 62 | abstract class XMLHttpRequest { 63 | external num get readyState; 64 | external dynamic get response; 65 | external String get responseText; 66 | external num get DONE; 67 | external num get HEADERS_RECEIVED; 68 | external num get LOADING; 69 | external num get OPENED; 70 | external num get UNSENT; 71 | external factory XMLHttpRequest( 72 | {num readyState, 73 | dynamic response, 74 | String responseText, 75 | num DONE, 76 | num HEADERS_RECEIVED, 77 | num LOADING, 78 | num OPENED, 79 | num UNSENT}); 80 | } 81 | 82 | @JS() 83 | external XMLHttpRequest get xhr;`); 84 | }); 85 | 86 | it('finds other registered type substitutions', () => { 87 | expectWithTypes('const intArray: Uint8Array;') 88 | .to.equal(`import "dart:typed_data" show Uint8List; 89 | 90 | @JS() 91 | external Uint8List get intArray;`); 92 | expectWithTypes('const buff: ArrayBuffer;') 93 | .to.equal(`import "dart:typed_data" show ByteBuffer; 94 | 95 | @JS() 96 | external ByteBuffer get buff;`); 97 | 98 | expectWithTypes('const n: Number;').to.equal(`@JS() 99 | external num get n;`); 100 | 101 | expectWithTypes('const s: String;').to.equal(`@JS() 102 | external String get s;`); 103 | 104 | expectWithTypes('const s: string;').to.equal(`@JS() 105 | external String get s;`); 106 | 107 | expectWithTypes('const b: Boolean;').to.equal(`@JS() 108 | external bool get b;`); 109 | }); 110 | }); 111 | 112 | it('allows undeclared types', () => { 113 | expectWithTypes('const t: Thing;').to.equal(`@JS() 114 | external Thing get t;`); 115 | }); 116 | }); 117 | 118 | describe('skip top level calls', () => { 119 | it('console.log', () => { 120 | expectWithTypes(`console.log(1);`).to.equal(''); 121 | expectWithTypes(`console.log(1, 2);`).to.equal(''); 122 | }); 123 | }); 124 | 125 | describe('const', () => { 126 | it('simple', () => { 127 | expectWithTypes('const x = 1;').to.equal(`@JS() 128 | external get x;`); 129 | expectWithTypes('const x = [];').to.equal(`@JS() 130 | external get x;`); 131 | expectWithTypes(`class Person {} 132 | const x = new Person();`) 133 | .to.equal(`@JS() 134 | class Person { 135 | // @Ignore 136 | Person.fakeConstructor$(); 137 | } 138 | 139 | @JS() 140 | external get x;`); 141 | }); 142 | }); 143 | 144 | describe('readonly', () => { 145 | it('simple', () => { 146 | expectWithTypes(`export class Person { 147 | readonly x: number; 148 | readonly y: string; 149 | readonly z: boolean; 150 | }`).to.equal(`@JS() 151 | class Person { 152 | // @Ignore 153 | Person.fakeConstructor$(); 154 | external num get x; 155 | external String get y; 156 | external bool get z; 157 | }`); 158 | }); 159 | }); 160 | 161 | it('translates array façades', () => { 162 | expectWithTypes('function f() : string[] {}').to.equal(`@JS() 163 | external List f();`); 164 | 165 | expectWithTypes(`export interface DSVParsedArray extends Array { 166 | columns: Array; 167 | }`).to.equal(`@anonymous 168 | @JS() 169 | abstract class DSVParsedArray implements List { 170 | external List get columns; 171 | external set columns(List v); 172 | }`); 173 | }); 174 | 175 | 176 | it('translates readonly array façades', () => { 177 | expectWithTypes('declare const a : ReadonlyArray;').to.equal(`@JS() 178 | external List /*ReadonlyArray*/ get a;`); 179 | expectWithTypes('function f() : ReadonlyArray {}').to.equal(`@JS() 180 | external List /*ReadonlyArray*/ f();`); 181 | }); 182 | 183 | describe('error detection', () => { 184 | it('supports imports', () => { 185 | // In all tests, the main code has a fake location of FAKE_MAIN, which is declared 186 | // to be '/demo/some/main.ts' within test_support.ts. At the top of this file, the 'other' 187 | // module is declared to have a fake path of '/other.ts'. So, the correct import path for the 188 | // other module is '../../other.dart' 189 | expectWithTypes(` 190 | import {X} from "other"; 191 | declare let x:X; 192 | `).to.equal(`import "../../other.dart" show X; 193 | 194 | @JS() 195 | external X get x; 196 | @JS() 197 | external set x(X v);`); 198 | }); 199 | }); 200 | 201 | describe('special identifiers', () => { 202 | // For the Dart keyword list see 203 | // https://dart.dev/guides/language/language-tour#keywords 204 | it('always renames identifiers that are reserved keywords in Dart', () => { 205 | expectTranslate(`declare var rethrow: number;`).to.equal(`@JS() 206 | external num get JS$rethrow; 207 | @JS() 208 | external set JS$rethrow(num v);`); 209 | 210 | expectTranslate(`class X { while: string; }`).to.equal(`@JS() 211 | class X { 212 | // @Ignore 213 | X.fakeConstructor$(); 214 | external String get JS$while; 215 | external set JS$while(String v); 216 | }`); 217 | }); 218 | 219 | it('only renames built-in keywords when they are used as class or type names', () => { 220 | expectTranslate(`declare var abstract: number;`).to.equal(`@JS() 221 | external num get abstract; 222 | @JS() 223 | external set abstract(num v);`); 224 | 225 | expectTranslate(`declare function get(): void;`).to.equal(`@JS() 226 | external void get();`); 227 | 228 | expectTranslate(`interface X { abstract: string; }`).to.equal(`@anonymous 229 | @JS() 230 | abstract class X { 231 | external String get abstract; 232 | external set abstract(String v); 233 | external factory X({String abstract}); 234 | }`); 235 | 236 | expectTranslate(`interface X { get: number; }`).to.equal(`@anonymous 237 | @JS() 238 | abstract class X { 239 | external num get get; 240 | external set get(num v); 241 | external factory X({num get}); 242 | }`); 243 | 244 | expectTranslate(`interface abstract { a: number; }`).to.equal(`@anonymous 245 | @JS() 246 | abstract class JS$abstract { 247 | external num get a; 248 | external set a(num v); 249 | external factory JS$abstract({num a}); 250 | }`); 251 | 252 | expectTranslate(`class covariant { x: boolean; }`).to.equal(`@JS() 253 | class JS$covariant { 254 | // @Ignore 255 | JS$covariant.fakeConstructor$(); 256 | external bool get x; 257 | external set x(bool v); 258 | }`); 259 | }); 260 | 261 | it('preserves names that begin with two underscores', () => { 262 | expectWithTypes(`export function f(__a: number): boolean; 263 | export function f(__a: string): boolean;`) 264 | .to.equal(`/*external bool f(num JS$__a);*/ 265 | /*external bool f(String JS$__a);*/ 266 | @JS() 267 | external bool f(dynamic /*num|String*/ JS$__a);`); 268 | }); 269 | 270 | it('preserves names that begin with one underscore', () => { 271 | expectWithTypes(`export function f(_a: number): boolean; 272 | export function f(_a: string): boolean;`) 273 | .to.equal(`/*external bool f(num JS$_a);*/ 274 | /*external bool f(String JS$_a);*/ 275 | @JS() 276 | external bool f(dynamic /*num|String*/ JS$_a);`); 277 | }); 278 | }); 279 | }); 280 | -------------------------------------------------------------------------------- /test/function_test.ts: -------------------------------------------------------------------------------- 1 | import {expectTranslate} from './test_support'; 2 | 3 | describe('functions', () => { 4 | it('supports declarations', () => { 5 | expectTranslate('function x() {}').to.equal(`@JS() 6 | external x();`); 7 | }); 8 | it('hide param default values', () => { 9 | expectTranslate('function x(a = 42, b = 1) { return 42; }').to.equal(`@JS() 10 | external x([a, b]);`); 11 | expectTranslate('function x(p1, a = 42, b = 1, p2) { return 42; }').to.equal(`@JS() 12 | external x(p1, [a, b, p2]);`); 13 | }); 14 | it('translates optional parameters', () => { 15 | expectTranslate('function x(a?: number, b?: number) { return 42; }').to.equal(`@JS() 16 | external x([num a, num b]);`); 17 | expectTranslate('function x(p1, a?: number, b?: number, p2) { return 42; }').to.equal(`@JS() 18 | external x(p1, [num a, num b, p2]);`); 19 | }); 20 | it('supports empty returns', () => { 21 | expectTranslate('function x() { return; }').to.equal(`@JS() 22 | external x();`); 23 | }); 24 | 25 | it('supports type predicates', () => { 26 | expectTranslate('function isArrayBuffer(value?: any): value is ArrayBuffer;') 27 | .to.equal(`import "dart:typed_data" show ByteBuffer; 28 | 29 | @JS() 30 | external bool /*value is ByteBuffer*/ isArrayBuffer([dynamic value]);`); 31 | }); 32 | 33 | it('polyfill var args', () => { 34 | expectTranslate('function x(...a: number[]) { return 42; }').to.equal(`@JS() 35 | external x([num a1, num a2, num a3, num a4, num a5]);`); 36 | }); 37 | it('supports function parameters', () => { 38 | expectTranslate('function f(fn: (a: A, b: B) => C) {}').to.equal(`@JS() 39 | external f(C fn(A a, B b));`); 40 | }); 41 | it('supports recursive function parameters', () => { 42 | expectTranslate('function f(fn: (a: (b: B) => C) => D) {}').to.equal(`@JS() 43 | external f(D fn(C a(B b)));`); 44 | }); 45 | it('supports generic-typed function parameters', () => { 46 | expectTranslate('function f(fn: (a: T, b: U) => T) {}').to.equal(`@JS() 47 | external f/**/(dynamic /*T*/ fn(dynamic /*T*/ a, dynamic /*U*/ b));`); 48 | }); 49 | it('translates functions taking rest parameters to untyped Function', () => { 50 | expectTranslate('function f(fn: (...a: string[]) => number) {}').to.equal(`@JS() 51 | external f(Function /*(...a: string[]) => number*/ fn);`); 52 | }); 53 | }); 54 | 55 | /* TODO(jacobr): support named parameters. 56 | describe('named parameters', () => { 57 | it('supports named parameters', () => { 58 | expectTranslate('function x({a = "x", b}) { return a + b; 59 | }').to.equal(`x({a: "x", b}) { return a + b; 60 | }`); 61 | }); 62 | it('supports types on named parameters', () => { 63 | expectTranslate('function x({a = 1, b = 2}: {a: number, b: number} = {}) { 64 | return a + b; 65 | }').to.equal(`x({num a: 1, num b: 2}) { 66 | return a + b; 67 | }`); 68 | }); 69 | it('supports reference types on named parameters', () => { 70 | expectTranslate( 71 | 'interface Args { a: string; b: number }\n' + 72 | 'function x({a, b, c}: Args) { return a + b; }') 73 | .to.equal(`abstract class Args { 74 | String a; 75 | num b; 76 | } 77 | 78 | x({String a, num b, c}) { 79 | return a + b; 80 | }`); 81 | }); 82 | it('supports declared, untyped named parameters', () => { 83 | expectTranslate('function x({a, b}: {a: number, b}) { return a + b; 84 | }').to.equal(`x({num a, b}) 85 | { 86 | return a + b; 87 | }`); 88 | }); 89 | it('fails for non-property types on named parameters', () => { 90 | expectErroneousCode( 91 | 'interface X { a(a: number); }\n' + 92 | 'function x({a}: X) { return a + b; }') 93 | .to.throw('X.a used for named parameter definition must be a property'); 94 | }); 95 | }); 96 | */ 97 | 98 | describe('generic functions', () => { 99 | it('supports generic types', () => { 100 | expectTranslate('function sort(xs: T[]): T[] { return xs; }').to.equal(`@JS() 101 | external List sort/**/(List xs);`); 102 | }); 103 | it('replaces type usage sites, but not idents', () => { 104 | expectTranslate(`function wobble(u: U): T { }`).to.equal(`@JS() 105 | external dynamic /*T*/ wobble/**/(dynamic /*U*/ u);`); 106 | }); 107 | it('translates generic calls', () => { 108 | expectTranslate(`function wobble(foo: T): T { }`).to.equal(`@JS() 109 | external dynamic /*T*/ wobble/**/(dynamic /*T*/ foo);`); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/json/declarations/class_test.ts: -------------------------------------------------------------------------------- 1 | import {ConvertedSyntaxKind} from '../../../lib/json/converted_syntax_kinds'; 2 | 3 | import {expectTranslateJSON, prettyStringify} from '../json_test_support'; 4 | 5 | describe('classes', () => { 6 | it('supports class declarations', () => { 7 | expectTranslateJSON('declare class X {}').to.equal(prettyStringify({ 8 | kind: ConvertedSyntaxKind.SourceFile, 9 | fileName: 'demo/some/main.ts', 10 | statements: 11 | [{kind: ConvertedSyntaxKind.ClassDeclaration, modifiers: [], name: 'X', members: []}] 12 | })); 13 | }); 14 | 15 | it('supports abstract classes', () => { 16 | expectTranslateJSON('declare abstract class X {}').to.equal(prettyStringify({ 17 | kind: ConvertedSyntaxKind.SourceFile, 18 | fileName: 'demo/some/main.ts', 19 | statements: [{ 20 | kind: ConvertedSyntaxKind.ClassDeclaration, 21 | modifiers: [{kind: ConvertedSyntaxKind.AbstractModifier}], 22 | name: 'X', 23 | members: [] 24 | }] 25 | })); 26 | }); 27 | 28 | describe('heritage clauses', () => { 29 | it('supports implements', () => { 30 | expectTranslateJSON('declare class X implements Y {}').to.equal(prettyStringify({ 31 | kind: ConvertedSyntaxKind.SourceFile, 32 | fileName: 'demo/some/main.ts', 33 | statements: [{ 34 | kind: ConvertedSyntaxKind.ClassDeclaration, 35 | modifiers: [], 36 | name: 'X', 37 | heritageClauses: [{ 38 | kind: ConvertedSyntaxKind.HeritageClause, 39 | keyword: 'implements', 40 | types: [{kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Y'}] 41 | }], 42 | members: [] 43 | }] 44 | })); 45 | 46 | expectTranslateJSON('declare class X implements Y, Z {}').to.equal(prettyStringify({ 47 | kind: ConvertedSyntaxKind.SourceFile, 48 | fileName: 'demo/some/main.ts', 49 | statements: [{ 50 | kind: ConvertedSyntaxKind.ClassDeclaration, 51 | modifiers: [], 52 | name: 'X', 53 | heritageClauses: [{ 54 | kind: ConvertedSyntaxKind.HeritageClause, 55 | keyword: 'implements', 56 | types: [ 57 | {kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Y'}, 58 | {kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Z'} 59 | ] 60 | }], 61 | members: [] 62 | }] 63 | })); 64 | }); 65 | 66 | it('supports extends', () => { 67 | expectTranslateJSON('declare class X extends Y {}').to.equal(prettyStringify({ 68 | kind: ConvertedSyntaxKind.SourceFile, 69 | fileName: 'demo/some/main.ts', 70 | statements: [{ 71 | kind: ConvertedSyntaxKind.ClassDeclaration, 72 | modifiers: [], 73 | name: 'X', 74 | heritageClauses: [{ 75 | kind: ConvertedSyntaxKind.HeritageClause, 76 | keyword: 'extends', 77 | types: [{kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Y'}] 78 | }], 79 | members: [] 80 | }] 81 | })); 82 | }); 83 | 84 | it('supports classes with both heritage clauses', () => { 85 | expectTranslateJSON('declare class W extends X implements Y, Z {}').to.equal(prettyStringify({ 86 | kind: ConvertedSyntaxKind.SourceFile, 87 | fileName: 'demo/some/main.ts', 88 | statements: [{ 89 | kind: ConvertedSyntaxKind.ClassDeclaration, 90 | modifiers: [], 91 | name: 'W', 92 | heritageClauses: [ 93 | { 94 | kind: ConvertedSyntaxKind.HeritageClause, 95 | keyword: 'extends', 96 | types: [{kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'X'}] 97 | }, 98 | { 99 | kind: ConvertedSyntaxKind.HeritageClause, 100 | keyword: 'implements', 101 | types: [ 102 | {kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Y'}, 103 | {kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Z'} 104 | ] 105 | } 106 | ], 107 | members: [] 108 | }] 109 | })); 110 | }); 111 | }); 112 | 113 | describe('type parameters', () => { 114 | it('should handle basic cases', () => { 115 | expectTranslateJSON('declare class X {}').to.equal(prettyStringify({ 116 | kind: ConvertedSyntaxKind.SourceFile, 117 | fileName: 'demo/some/main.ts', 118 | statements: [{ 119 | kind: ConvertedSyntaxKind.ClassDeclaration, 120 | modifiers: [], 121 | name: 'X', 122 | typeParameters: [{kind: ConvertedSyntaxKind.TypeParameter, name: 'T'}], 123 | members: [] 124 | }] 125 | })); 126 | 127 | expectTranslateJSON('declare class X {}').to.equal(prettyStringify({ 128 | kind: ConvertedSyntaxKind.SourceFile, 129 | fileName: 'demo/some/main.ts', 130 | statements: [{ 131 | kind: ConvertedSyntaxKind.ClassDeclaration, 132 | modifiers: [], 133 | name: 'X', 134 | typeParameters: [ 135 | {kind: ConvertedSyntaxKind.TypeParameter, name: 'T1'}, 136 | {kind: ConvertedSyntaxKind.TypeParameter, name: 'T2'} 137 | ], 138 | members: [] 139 | }] 140 | })); 141 | }); 142 | 143 | it('should handle type parameters with constraints', () => { 144 | expectTranslateJSON('declare class X {}').to.equal(prettyStringify({ 145 | kind: ConvertedSyntaxKind.SourceFile, 146 | fileName: 'demo/some/main.ts', 147 | statements: [{ 148 | kind: ConvertedSyntaxKind.ClassDeclaration, 149 | modifiers: [], 150 | name: 'X', 151 | typeParameters: [{ 152 | kind: ConvertedSyntaxKind.TypeParameter, 153 | name: 'T', 154 | constraint: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 155 | }], 156 | members: [] 157 | }] 158 | })); 159 | 160 | expectTranslateJSON(`declare class X {} 161 | declare class Y extends X {} 162 | `).to.equal(prettyStringify({ 163 | kind: ConvertedSyntaxKind.SourceFile, 164 | fileName: 'demo/some/main.ts', 165 | statements: [ 166 | { 167 | kind: ConvertedSyntaxKind.ClassDeclaration, 168 | modifiers: [], 169 | name: 'X', 170 | typeParameters: [{ 171 | kind: ConvertedSyntaxKind.TypeParameter, 172 | name: 'T', 173 | constraint: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 174 | }], 175 | members: [] 176 | }, 177 | { 178 | kind: ConvertedSyntaxKind.ClassDeclaration, 179 | modifiers: [], 180 | name: 'Y', 181 | heritageClauses: [{ 182 | kind: ConvertedSyntaxKind.HeritageClause, 183 | keyword: 'extends', 184 | types: [{ 185 | kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, 186 | typeArguments: [{kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}], 187 | expression: 'X' 188 | }] 189 | }], 190 | members: [] 191 | } 192 | ] 193 | })); 194 | 195 | expectTranslateJSON('declare class X> {}') 196 | .to.equal(prettyStringify({ 197 | kind: ConvertedSyntaxKind.SourceFile, 198 | fileName: 'demo/some/main.ts', 199 | statements: [{ 200 | kind: ConvertedSyntaxKind.ClassDeclaration, 201 | modifiers: [], 202 | name: 'X', 203 | typeParameters: [ 204 | { 205 | kind: ConvertedSyntaxKind.TypeParameter, 206 | name: 'U', 207 | constraint: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 208 | }, 209 | { 210 | kind: ConvertedSyntaxKind.TypeParameter, 211 | name: 'T', 212 | constraint: { 213 | kind: ConvertedSyntaxKind.TypeReference, 214 | typeName: 'Promise', 215 | typeArguments: [{kind: ConvertedSyntaxKind.TypeReference, typeName: 'U'}] 216 | } 217 | } 218 | ], 219 | members: [] 220 | }] 221 | })); 222 | }); 223 | }); 224 | 225 | describe('members', () => { 226 | it('ignores the semicolon class element', () => { 227 | expectTranslateJSON('declare class X { ; ; ; }').to.equal(prettyStringify({ 228 | kind: ConvertedSyntaxKind.SourceFile, 229 | fileName: 'demo/some/main.ts', 230 | statements: 231 | [{kind: ConvertedSyntaxKind.ClassDeclaration, modifiers: [], name: 'X', members: []}] 232 | })); 233 | }); 234 | 235 | it('supports properties', () => { 236 | expectTranslateJSON('declare class X { a: number; b: string; }').to.equal(prettyStringify({ 237 | kind: ConvertedSyntaxKind.SourceFile, 238 | fileName: 'demo/some/main.ts', 239 | statements: [{ 240 | kind: ConvertedSyntaxKind.ClassDeclaration, 241 | modifiers: [], 242 | name: 'X', 243 | members: [ 244 | { 245 | kind: ConvertedSyntaxKind.PropertyDeclaration, 246 | name: 'a', 247 | optional: false, 248 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 249 | }, 250 | { 251 | kind: ConvertedSyntaxKind.PropertyDeclaration, 252 | name: 'b', 253 | optional: false, 254 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 255 | } 256 | ] 257 | }] 258 | })); 259 | }); 260 | 261 | it('supports methods', () => { 262 | expectTranslateJSON('declare class X { f(): void; }').to.equal(prettyStringify({ 263 | kind: ConvertedSyntaxKind.SourceFile, 264 | fileName: 'demo/some/main.ts', 265 | statements: [{ 266 | kind: ConvertedSyntaxKind.ClassDeclaration, 267 | modifiers: [], 268 | name: 'X', 269 | members: [{ 270 | kind: ConvertedSyntaxKind.MethodDeclaration, 271 | name: 'f', 272 | optional: false, 273 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'void'}, 274 | parameters: [] 275 | }] 276 | }] 277 | })); 278 | 279 | expectTranslateJSON('declare class X { f(a: number, b: string): boolean; }') 280 | .to.equal(prettyStringify({ 281 | kind: ConvertedSyntaxKind.SourceFile, 282 | fileName: 'demo/some/main.ts', 283 | statements: [{ 284 | kind: ConvertedSyntaxKind.ClassDeclaration, 285 | modifiers: [], 286 | name: 'X', 287 | members: [{ 288 | kind: ConvertedSyntaxKind.MethodDeclaration, 289 | name: 'f', 290 | optional: false, 291 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 292 | parameters: [ 293 | { 294 | kind: ConvertedSyntaxKind.Parameter, 295 | name: 'a', 296 | optional: false, 297 | destructured: false, 298 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 299 | }, 300 | { 301 | kind: ConvertedSyntaxKind.Parameter, 302 | name: 'b', 303 | optional: false, 304 | destructured: false, 305 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 306 | } 307 | ] 308 | }] 309 | }] 310 | })); 311 | }); 312 | 313 | it('supports abstract methods', () => { 314 | expectTranslateJSON('declare abstract class X { abstract f(): number; }') 315 | .to.equal(prettyStringify({ 316 | kind: ConvertedSyntaxKind.SourceFile, 317 | fileName: 'demo/some/main.ts', 318 | statements: [{ 319 | kind: ConvertedSyntaxKind.ClassDeclaration, 320 | modifiers: [{kind: ConvertedSyntaxKind.AbstractModifier}], 321 | name: 'X', 322 | members: [{ 323 | kind: ConvertedSyntaxKind.MethodDeclaration, 324 | modifiers: [{kind: ConvertedSyntaxKind.AbstractModifier}], 325 | name: 'f', 326 | optional: false, 327 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 328 | parameters: [] 329 | }] 330 | }] 331 | })); 332 | }); 333 | 334 | it('supports getters', () => { 335 | expectTranslateJSON('declare class X { get a(): number; }').to.equal(prettyStringify({ 336 | kind: ConvertedSyntaxKind.SourceFile, 337 | fileName: 'demo/some/main.ts', 338 | statements: [{ 339 | kind: ConvertedSyntaxKind.ClassDeclaration, 340 | modifiers: [], 341 | name: 'X', 342 | members: [{ 343 | kind: ConvertedSyntaxKind.GetAccessor, 344 | name: 'a', 345 | parameters: [], 346 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 347 | }] 348 | }] 349 | })); 350 | }); 351 | 352 | it('supports setters', () => { 353 | expectTranslateJSON('declare class X { set a(v: number); }').to.equal(prettyStringify({ 354 | kind: ConvertedSyntaxKind.SourceFile, 355 | fileName: 'demo/some/main.ts', 356 | statements: [{ 357 | kind: ConvertedSyntaxKind.ClassDeclaration, 358 | modifiers: [], 359 | name: 'X', 360 | members: [{ 361 | kind: ConvertedSyntaxKind.SetAccessor, 362 | name: 'a', 363 | parameters: [{ 364 | kind: ConvertedSyntaxKind.Parameter, 365 | name: 'v', 366 | optional: false, 367 | destructured: false, 368 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 369 | }], 370 | }] 371 | }] 372 | })); 373 | }); 374 | 375 | it('supports constructors', () => { 376 | expectTranslateJSON('declare class X { constructor(); }').to.equal(prettyStringify({ 377 | kind: ConvertedSyntaxKind.SourceFile, 378 | fileName: 'demo/some/main.ts', 379 | statements: [{ 380 | kind: ConvertedSyntaxKind.ClassDeclaration, 381 | modifiers: [], 382 | name: 'X', 383 | members: [{kind: ConvertedSyntaxKind.Constructor, parameters: []}] 384 | }] 385 | })); 386 | }); 387 | 388 | it('supports private', () => { 389 | expectTranslateJSON(` 390 | declare class X { 391 | private _a: number; 392 | private b(): boolean; 393 | }`).to.equal(prettyStringify({ 394 | kind: ConvertedSyntaxKind.SourceFile, 395 | fileName: 'demo/some/main.ts', 396 | statements: [{ 397 | kind: ConvertedSyntaxKind.ClassDeclaration, 398 | modifiers: [], 399 | name: 'X', 400 | members: [ 401 | { 402 | kind: ConvertedSyntaxKind.PropertyDeclaration, 403 | modifiers: [{kind: ConvertedSyntaxKind.PrivateModifier}], 404 | name: '_a', 405 | optional: false, 406 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 407 | }, 408 | { 409 | kind: ConvertedSyntaxKind.MethodDeclaration, 410 | modifiers: [{kind: ConvertedSyntaxKind.PrivateModifier}], 411 | name: 'b', 412 | optional: false, 413 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 414 | parameters: [] 415 | } 416 | ] 417 | }] 418 | })); 419 | }); 420 | 421 | it('supports protected', () => { 422 | expectTranslateJSON(` 423 | declare class X { 424 | protected a: number; 425 | protected b(): boolean; 426 | }`).to.equal(prettyStringify({ 427 | kind: ConvertedSyntaxKind.SourceFile, 428 | fileName: 'demo/some/main.ts', 429 | statements: [{ 430 | kind: ConvertedSyntaxKind.ClassDeclaration, 431 | modifiers: [], 432 | name: 'X', 433 | members: [ 434 | { 435 | kind: ConvertedSyntaxKind.PropertyDeclaration, 436 | modifiers: [{kind: ConvertedSyntaxKind.ProtectedModifier}], 437 | name: 'a', 438 | optional: false, 439 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 440 | }, 441 | { 442 | kind: ConvertedSyntaxKind.MethodDeclaration, 443 | modifiers: [{kind: ConvertedSyntaxKind.ProtectedModifier}], 444 | name: 'b', 445 | optional: false, 446 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 447 | parameters: [] 448 | } 449 | ] 450 | }] 451 | })); 452 | }); 453 | 454 | it('supports optional', () => { 455 | expectTranslateJSON('declare class X { a?: number }').to.equal(prettyStringify({ 456 | kind: ConvertedSyntaxKind.SourceFile, 457 | fileName: 'demo/some/main.ts', 458 | statements: [{ 459 | kind: ConvertedSyntaxKind.ClassDeclaration, 460 | modifiers: [], 461 | name: 'X', 462 | members: [{ 463 | kind: ConvertedSyntaxKind.PropertyDeclaration, 464 | name: 'a', 465 | optional: true, 466 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 467 | }] 468 | }] 469 | })); 470 | }); 471 | 472 | it('supports readonly', () => { 473 | expectTranslateJSON('declare class X { readonly a: number }').to.equal(prettyStringify({ 474 | kind: ConvertedSyntaxKind.SourceFile, 475 | fileName: 'demo/some/main.ts', 476 | statements: [{ 477 | kind: ConvertedSyntaxKind.ClassDeclaration, 478 | modifiers: [], 479 | name: 'X', 480 | members: [{ 481 | kind: ConvertedSyntaxKind.PropertyDeclaration, 482 | modifiers: [{kind: ConvertedSyntaxKind.ReadonlyModifier}], 483 | name: 'a', 484 | optional: false, 485 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 486 | }] 487 | }] 488 | })); 489 | }); 490 | 491 | it('supports static', () => { 492 | expectTranslateJSON('declare class X { static a: number; }').to.equal(prettyStringify({ 493 | kind: ConvertedSyntaxKind.SourceFile, 494 | fileName: 'demo/some/main.ts', 495 | statements: [{ 496 | kind: ConvertedSyntaxKind.ClassDeclaration, 497 | modifiers: [], 498 | name: 'X', 499 | members: [{ 500 | kind: ConvertedSyntaxKind.PropertyDeclaration, 501 | modifiers: [{kind: ConvertedSyntaxKind.StaticModifier}], 502 | name: 'a', 503 | optional: false, 504 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 505 | }] 506 | }] 507 | })); 508 | }); 509 | 510 | it('supports parameter properties', () => { 511 | expectTranslateJSON(` 512 | declare class X { 513 | constructor(public a: number, private _b: string = "hello") {} 514 | }`).to.equal(prettyStringify({ 515 | kind: ConvertedSyntaxKind.SourceFile, 516 | fileName: 'demo/some/main.ts', 517 | statements: [{ 518 | kind: ConvertedSyntaxKind.ClassDeclaration, 519 | modifiers: [], 520 | name: 'X', 521 | members: [{ 522 | kind: ConvertedSyntaxKind.Constructor, 523 | parameters: [ 524 | { 525 | kind: ConvertedSyntaxKind.Parameter, 526 | modifiers: [{kind: ConvertedSyntaxKind.PublicModifier}], 527 | name: 'a', 528 | optional: false, 529 | destructured: false, 530 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 531 | }, 532 | { 533 | kind: ConvertedSyntaxKind.Parameter, 534 | modifiers: [{kind: ConvertedSyntaxKind.PrivateModifier}], 535 | name: '_b', 536 | optional: false, 537 | destructured: false, 538 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 539 | initializer: '\'hello\'' 540 | } 541 | ] 542 | }] 543 | }] 544 | })); 545 | }); 546 | }); 547 | }); 548 | -------------------------------------------------------------------------------- /test/json/declarations/interface_test.ts: -------------------------------------------------------------------------------- 1 | import {ConvertedSyntaxKind} from '../../../lib/json/converted_syntax_kinds'; 2 | 3 | import {expectTranslateJSON, prettyStringify} from '../json_test_support'; 4 | 5 | describe('interfaces', () => { 6 | it('supports interface declarations', () => { 7 | expectTranslateJSON('declare interface X {}').to.equal(prettyStringify({ 8 | kind: ConvertedSyntaxKind.SourceFile, 9 | fileName: 'demo/some/main.ts', 10 | statements: 11 | [{kind: ConvertedSyntaxKind.InterfaceDeclaration, modifiers: [], name: 'X', members: []}] 12 | })); 13 | }); 14 | 15 | describe('heritage clauses', () => { 16 | it('supports extends', () => { 17 | expectTranslateJSON('declare interface X extends Y, Z {}').to.equal(prettyStringify({ 18 | kind: ConvertedSyntaxKind.SourceFile, 19 | fileName: 'demo/some/main.ts', 20 | statements: [{ 21 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 22 | modifiers: [], 23 | name: 'X', 24 | heritageClauses: [{ 25 | kind: ConvertedSyntaxKind.HeritageClause, 26 | keyword: 'extends', 27 | types: [ 28 | {kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Y'}, 29 | {kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, expression: 'Z'} 30 | ] 31 | }], 32 | members: [] 33 | }] 34 | })); 35 | }); 36 | }); 37 | 38 | describe('type parameters', () => { 39 | it('should handle basic cases', () => { 40 | expectTranslateJSON('declare interface X {}').to.equal(prettyStringify({ 41 | kind: ConvertedSyntaxKind.SourceFile, 42 | fileName: 'demo/some/main.ts', 43 | statements: [{ 44 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 45 | modifiers: [], 46 | name: 'X', 47 | typeParameters: [{kind: ConvertedSyntaxKind.TypeParameter, name: 'T'}], 48 | members: [] 49 | }] 50 | })); 51 | 52 | expectTranslateJSON('declare interface X {}').to.equal(prettyStringify({ 53 | kind: ConvertedSyntaxKind.SourceFile, 54 | fileName: 'demo/some/main.ts', 55 | statements: [{ 56 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 57 | modifiers: [], 58 | name: 'X', 59 | typeParameters: [ 60 | {kind: ConvertedSyntaxKind.TypeParameter, name: 'T1'}, 61 | {kind: ConvertedSyntaxKind.TypeParameter, name: 'T2'} 62 | ], 63 | members: [] 64 | }] 65 | })); 66 | }); 67 | 68 | it('should handle type parameters with constraints', () => { 69 | expectTranslateJSON('declare interface X {}').to.equal(prettyStringify({ 70 | kind: ConvertedSyntaxKind.SourceFile, 71 | fileName: 'demo/some/main.ts', 72 | statements: [{ 73 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 74 | modifiers: [], 75 | name: 'X', 76 | typeParameters: [{ 77 | kind: ConvertedSyntaxKind.TypeParameter, 78 | name: 'T', 79 | constraint: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 80 | }], 81 | members: [] 82 | }] 83 | })); 84 | 85 | expectTranslateJSON(`declare interface X {} 86 | declare interface Y extends X {} 87 | `).to.equal(prettyStringify({ 88 | kind: ConvertedSyntaxKind.SourceFile, 89 | fileName: 'demo/some/main.ts', 90 | statements: [ 91 | { 92 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 93 | modifiers: [], 94 | name: 'X', 95 | typeParameters: [{ 96 | kind: ConvertedSyntaxKind.TypeParameter, 97 | name: 'T', 98 | constraint: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 99 | }], 100 | members: [] 101 | }, 102 | { 103 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 104 | modifiers: [], 105 | name: 'Y', 106 | heritageClauses: [{ 107 | kind: ConvertedSyntaxKind.HeritageClause, 108 | keyword: 'extends', 109 | types: [{ 110 | kind: ConvertedSyntaxKind.ExpressionWithTypeArguments, 111 | typeArguments: [{kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}], 112 | expression: 'X' 113 | }] 114 | }], 115 | members: [] 116 | } 117 | ] 118 | })); 119 | 120 | expectTranslateJSON('declare interface X> {}') 121 | .to.equal(prettyStringify({ 122 | kind: ConvertedSyntaxKind.SourceFile, 123 | fileName: 'demo/some/main.ts', 124 | statements: [{ 125 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 126 | modifiers: [], 127 | name: 'X', 128 | typeParameters: [ 129 | { 130 | kind: ConvertedSyntaxKind.TypeParameter, 131 | name: 'U', 132 | constraint: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 133 | }, 134 | { 135 | kind: ConvertedSyntaxKind.TypeParameter, 136 | name: 'T', 137 | constraint: { 138 | kind: ConvertedSyntaxKind.TypeReference, 139 | typeName: 'Promise', 140 | typeArguments: [{kind: ConvertedSyntaxKind.TypeReference, typeName: 'U'}] 141 | } 142 | } 143 | ], 144 | members: [] 145 | }] 146 | })); 147 | }); 148 | }); 149 | 150 | describe('members', () => { 151 | it('supports properties', () => { 152 | expectTranslateJSON('declare interface X { a: number; b: string; }') 153 | .to.equal(prettyStringify({ 154 | kind: ConvertedSyntaxKind.SourceFile, 155 | fileName: 'demo/some/main.ts', 156 | statements: [{ 157 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 158 | modifiers: [], 159 | name: 'X', 160 | members: [ 161 | { 162 | kind: ConvertedSyntaxKind.PropertyDeclaration, 163 | name: 'a', 164 | optional: false, 165 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 166 | }, 167 | { 168 | kind: ConvertedSyntaxKind.PropertyDeclaration, 169 | name: 'b', 170 | optional: false, 171 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 172 | } 173 | ] 174 | }] 175 | })); 176 | }); 177 | 178 | it('supports methods', () => { 179 | expectTranslateJSON('declare interface X { f(): void; }').to.equal(prettyStringify({ 180 | kind: ConvertedSyntaxKind.SourceFile, 181 | fileName: 'demo/some/main.ts', 182 | statements: [{ 183 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 184 | modifiers: [], 185 | name: 'X', 186 | members: [{ 187 | kind: ConvertedSyntaxKind.MethodDeclaration, 188 | name: 'f', 189 | optional: false, 190 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'void'}, 191 | parameters: [] 192 | }] 193 | }] 194 | })); 195 | 196 | expectTranslateJSON('declare interface X { f(a: number, b: string): boolean; }') 197 | .to.equal(prettyStringify({ 198 | kind: ConvertedSyntaxKind.SourceFile, 199 | fileName: 'demo/some/main.ts', 200 | statements: [{ 201 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 202 | modifiers: [], 203 | name: 'X', 204 | members: [{ 205 | kind: ConvertedSyntaxKind.MethodDeclaration, 206 | name: 'f', 207 | optional: false, 208 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 209 | parameters: [ 210 | { 211 | kind: ConvertedSyntaxKind.Parameter, 212 | name: 'a', 213 | optional: false, 214 | destructured: false, 215 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 216 | }, 217 | { 218 | kind: ConvertedSyntaxKind.Parameter, 219 | name: 'b', 220 | optional: false, 221 | destructured: false, 222 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 223 | } 224 | ] 225 | }] 226 | }] 227 | })); 228 | }); 229 | 230 | it('supports abstract methods', () => { 231 | expectTranslateJSON('declare abstract interface X { abstract f(): number; }') 232 | .to.equal(prettyStringify({ 233 | kind: ConvertedSyntaxKind.SourceFile, 234 | fileName: 'demo/some/main.ts', 235 | statements: [{ 236 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 237 | modifiers: [{kind: ConvertedSyntaxKind.AbstractModifier}], 238 | name: 'X', 239 | members: [{ 240 | kind: ConvertedSyntaxKind.MethodDeclaration, 241 | modifiers: [{kind: ConvertedSyntaxKind.AbstractModifier}], 242 | name: 'f', 243 | optional: false, 244 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 245 | parameters: [] 246 | }] 247 | }] 248 | })); 249 | }); 250 | 251 | it('supports optional', () => { 252 | expectTranslateJSON('declare interface X { a?: number }').to.equal(prettyStringify({ 253 | kind: ConvertedSyntaxKind.SourceFile, 254 | fileName: 'demo/some/main.ts', 255 | statements: [{ 256 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 257 | modifiers: [], 258 | name: 'X', 259 | members: [{ 260 | kind: ConvertedSyntaxKind.PropertyDeclaration, 261 | name: 'a', 262 | optional: true, 263 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 264 | }] 265 | }] 266 | })); 267 | }); 268 | 269 | it('supports readonly', () => { 270 | expectTranslateJSON('declare interface X { readonly a: number }').to.equal(prettyStringify({ 271 | kind: ConvertedSyntaxKind.SourceFile, 272 | fileName: 'demo/some/main.ts', 273 | statements: [{ 274 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 275 | modifiers: [], 276 | name: 'X', 277 | members: [{ 278 | kind: ConvertedSyntaxKind.PropertyDeclaration, 279 | modifiers: [{kind: ConvertedSyntaxKind.ReadonlyModifier}], 280 | name: 'a', 281 | optional: false, 282 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 283 | }] 284 | }] 285 | })); 286 | }); 287 | 288 | it('supports call signatures', () => { 289 | expectTranslateJSON('declare interface FnDef { (y: number): string; }') 290 | .to.equal(prettyStringify({ 291 | kind: ConvertedSyntaxKind.SourceFile, 292 | fileName: 'demo/some/main.ts', 293 | statements: [ 294 | { 295 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 296 | modifiers: [], 297 | name: 'FnDef', 298 | members: [{ 299 | kind: ConvertedSyntaxKind.CallSignature, 300 | parameters: [{ 301 | kind: ConvertedSyntaxKind.Parameter, 302 | name: 'y', 303 | optional: false, 304 | destructured: false, 305 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 306 | }], 307 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 308 | }] 309 | }, 310 | ] 311 | })); 312 | }); 313 | 314 | it('supports construct signatures', () => { 315 | expectTranslateJSON(` 316 | declare interface X { 317 | new(a: number, b: string): XType; 318 | } 319 | `).to.equal(prettyStringify({ 320 | kind: ConvertedSyntaxKind.SourceFile, 321 | fileName: 'demo/some/main.ts', 322 | statements: [ 323 | { 324 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 325 | modifiers: [], 326 | name: 'X', 327 | members: [{ 328 | kind: ConvertedSyntaxKind.ConstructSignature, 329 | parameters: [ 330 | { 331 | kind: ConvertedSyntaxKind.Parameter, 332 | name: 'a', 333 | optional: false, 334 | destructured: false, 335 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 336 | }, 337 | { 338 | kind: ConvertedSyntaxKind.Parameter, 339 | name: 'b', 340 | optional: false, 341 | destructured: false, 342 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 343 | } 344 | ], 345 | type: {kind: ConvertedSyntaxKind.TypeReference, typeName: 'XType'} 346 | }] 347 | }, 348 | ] 349 | })); 350 | }); 351 | }); 352 | }); 353 | -------------------------------------------------------------------------------- /test/json/declarations/variable_test.ts: -------------------------------------------------------------------------------- 1 | import {ConvertedSyntaxKind} from '../../../lib/json/converted_syntax_kinds'; 2 | 3 | import {expectTranslateJSON, prettyStringify} from '../json_test_support'; 4 | 5 | describe('variables', () => { 6 | describe('variable declarations', () => { 7 | it('supports declare var', () => { 8 | expectTranslateJSON('declare var a: number;').to.equal(prettyStringify({ 9 | kind: ConvertedSyntaxKind.SourceFile, 10 | fileName: 'demo/some/main.ts', 11 | statements: [{ 12 | kind: ConvertedSyntaxKind.VariableStatement, 13 | modifiers: [], 14 | keyword: 'var', 15 | declarations: [{ 16 | kind: ConvertedSyntaxKind.VariableDeclaration, 17 | name: 'a', 18 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 19 | }] 20 | }] 21 | })); 22 | }); 23 | 24 | it('supports declare let', () => { 25 | expectTranslateJSON('declare let a: number;').to.equal(prettyStringify({ 26 | kind: ConvertedSyntaxKind.SourceFile, 27 | fileName: 'demo/some/main.ts', 28 | statements: [{ 29 | kind: ConvertedSyntaxKind.VariableStatement, 30 | modifiers: [], 31 | keyword: 'let', 32 | declarations: [{ 33 | kind: ConvertedSyntaxKind.VariableDeclaration, 34 | name: 'a', 35 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 36 | }] 37 | }] 38 | })); 39 | }); 40 | 41 | it('supports declare const', () => { 42 | expectTranslateJSON('declare const a: number;').to.equal(prettyStringify({ 43 | kind: ConvertedSyntaxKind.SourceFile, 44 | fileName: 'demo/some/main.ts', 45 | statements: [{ 46 | kind: ConvertedSyntaxKind.VariableStatement, 47 | modifiers: [], 48 | keyword: 'const', 49 | declarations: [{ 50 | kind: ConvertedSyntaxKind.VariableDeclaration, 51 | name: 'a', 52 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 53 | }] 54 | }] 55 | })); 56 | }); 57 | 58 | it('supports lines with multiple declarations', () => { 59 | expectTranslateJSON('declare const a: number, b: string, c: boolean;') 60 | .to.equal(prettyStringify({ 61 | kind: ConvertedSyntaxKind.SourceFile, 62 | fileName: 'demo/some/main.ts', 63 | statements: [{ 64 | kind: ConvertedSyntaxKind.VariableStatement, 65 | modifiers: [], 66 | keyword: 'const', 67 | declarations: [ 68 | { 69 | kind: ConvertedSyntaxKind.VariableDeclaration, 70 | name: 'a', 71 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 72 | }, 73 | { 74 | kind: ConvertedSyntaxKind.VariableDeclaration, 75 | name: 'b', 76 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 77 | }, 78 | { 79 | kind: ConvertedSyntaxKind.VariableDeclaration, 80 | name: 'c', 81 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'} 82 | } 83 | ] 84 | }] 85 | })); 86 | }); 87 | 88 | it('supports untyped variables', () => { 89 | expectTranslateJSON('declare let a, b;').to.equal(prettyStringify({ 90 | kind: ConvertedSyntaxKind.SourceFile, 91 | fileName: 'demo/some/main.ts', 92 | statements: [{ 93 | kind: ConvertedSyntaxKind.VariableStatement, 94 | modifiers: [], 95 | keyword: 'let', 96 | declarations: [ 97 | {kind: ConvertedSyntaxKind.VariableDeclaration, name: 'a'}, 98 | {kind: ConvertedSyntaxKind.VariableDeclaration, name: 'b'} 99 | ] 100 | }] 101 | })); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/json/json_test_support.ts: -------------------------------------------------------------------------------- 1 | import {ConvertedSyntaxKind} from '../../lib/json/converted_syntax_kinds'; 2 | import {TranspilerOptions} from '../../lib/main'; 3 | import {expectTranslate, FAKE_MAIN} from '../test_support'; 4 | 5 | function getSources(str: string): Map { 6 | const srcs: Map = new Map(Object.entries({ 7 | 'demo/some/other.d.ts': ` 8 | export class X { 9 | toString(x: number): string; 10 | } 11 | export interface Y { 12 | f(a: number): number; 13 | g(a: string): boolean; 14 | }` 15 | })); 16 | srcs.set(FAKE_MAIN, str); 17 | return srcs; 18 | } 19 | 20 | const OPTS: TranspilerOptions = { 21 | failFast: true, 22 | toJSON: true, 23 | }; 24 | 25 | export function prettyStringify(object: object) { 26 | return JSON.stringify(object, undefined, 2); 27 | } 28 | 29 | export function expectTranslateJSON(str: string, options: TranspilerOptions = OPTS) { 30 | return expectTranslate(str, options); 31 | } 32 | 33 | export function expectWithExports(str: string) { 34 | return expectTranslate(getSources(str), OPTS); 35 | } 36 | 37 | export function expectKeywordType(typeName: string): 38 | {kind: ConvertedSyntaxKind.KeywordType, typeName: string} { 39 | return {kind: ConvertedSyntaxKind.KeywordType, typeName}; 40 | } 41 | -------------------------------------------------------------------------------- /test/json/merge_test.ts: -------------------------------------------------------------------------------- 1 | import {ConvertedSyntaxKind} from '../../lib/json/converted_syntax_kinds'; 2 | 3 | import {expectTranslateJSON, prettyStringify} from './json_test_support'; 4 | 5 | describe('merging variables with types', () => { 6 | describe('upgrading variables whose types contain construct signatures', () => { 7 | it('supports type literals with construct signatures', () => { 8 | expectTranslateJSON(` 9 | declare interface XType { 10 | a: number; 11 | b: string; 12 | c(): boolean; 13 | } 14 | 15 | declare var X: { new(a: number, b: string): XType }; 16 | `).to.equal(prettyStringify({ 17 | kind: ConvertedSyntaxKind.SourceFile, 18 | fileName: 'demo/some/main.ts', 19 | statements: [ 20 | { 21 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 22 | modifiers: [], 23 | name: 'XType', 24 | members: [ 25 | { 26 | kind: ConvertedSyntaxKind.PropertyDeclaration, 27 | name: 'a', 28 | optional: false, 29 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 30 | }, 31 | { 32 | kind: ConvertedSyntaxKind.PropertyDeclaration, 33 | name: 'b', 34 | optional: false, 35 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 36 | }, 37 | { 38 | kind: ConvertedSyntaxKind.MethodDeclaration, 39 | name: 'c', 40 | optional: false, 41 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 42 | parameters: [] 43 | } 44 | ] 45 | }, 46 | { 47 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 48 | modifiers: [], 49 | name: 'X', 50 | members: [ 51 | { 52 | kind: ConvertedSyntaxKind.PropertyDeclaration, 53 | name: 'a', 54 | optional: false, 55 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 56 | }, 57 | { 58 | kind: ConvertedSyntaxKind.PropertyDeclaration, 59 | name: 'b', 60 | optional: false, 61 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 62 | }, 63 | { 64 | kind: ConvertedSyntaxKind.MethodDeclaration, 65 | name: 'c', 66 | optional: false, 67 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 68 | parameters: [] 69 | }, 70 | { 71 | kind: ConvertedSyntaxKind.ConstructSignature, 72 | name: 'X', 73 | parameters: [ 74 | { 75 | kind: ConvertedSyntaxKind.Parameter, 76 | name: 'a', 77 | optional: false, 78 | destructured: false, 79 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 80 | }, 81 | { 82 | kind: ConvertedSyntaxKind.Parameter, 83 | name: 'b', 84 | optional: false, 85 | destructured: false, 86 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 87 | } 88 | ], 89 | type: {kind: ConvertedSyntaxKind.TypeReference, typeName: 'XType'} 90 | } 91 | ] 92 | }, 93 | ] 94 | })); 95 | }); 96 | 97 | it('supports interfaces with construct signatures', () => { 98 | expectTranslateJSON(` 99 | declare interface XType { 100 | a: number; 101 | b: string; 102 | c(): boolean; 103 | } 104 | 105 | declare interface X { 106 | new(a: number, b: string): XType; 107 | } 108 | declare var X: X; 109 | `).to.equal(prettyStringify({ 110 | kind: ConvertedSyntaxKind.SourceFile, 111 | fileName: 'demo/some/main.ts', 112 | statements: [ 113 | { 114 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 115 | modifiers: [], 116 | name: 'XType', 117 | members: [ 118 | { 119 | kind: ConvertedSyntaxKind.PropertyDeclaration, 120 | name: 'a', 121 | optional: false, 122 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 123 | }, 124 | { 125 | kind: ConvertedSyntaxKind.PropertyDeclaration, 126 | name: 'b', 127 | optional: false, 128 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 129 | }, 130 | { 131 | kind: ConvertedSyntaxKind.MethodDeclaration, 132 | name: 'c', 133 | optional: false, 134 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 135 | parameters: [] 136 | } 137 | ] 138 | }, 139 | { 140 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 141 | modifiers: [], 142 | name: 'X', 143 | members: [ 144 | { 145 | kind: ConvertedSyntaxKind.PropertyDeclaration, 146 | name: 'a', 147 | optional: false, 148 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 149 | }, 150 | { 151 | kind: ConvertedSyntaxKind.PropertyDeclaration, 152 | name: 'b', 153 | optional: false, 154 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 155 | }, 156 | { 157 | kind: ConvertedSyntaxKind.MethodDeclaration, 158 | name: 'c', 159 | optional: false, 160 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 161 | parameters: [] 162 | }, 163 | { 164 | kind: ConvertedSyntaxKind.ConstructSignature, 165 | name: 'X', 166 | parameters: [ 167 | { 168 | kind: ConvertedSyntaxKind.Parameter, 169 | name: 'a', 170 | optional: false, 171 | destructured: false, 172 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 173 | }, 174 | { 175 | kind: ConvertedSyntaxKind.Parameter, 176 | name: 'b', 177 | optional: false, 178 | destructured: false, 179 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 180 | } 181 | ], 182 | type: {kind: ConvertedSyntaxKind.TypeReference, typeName: 'XType'} 183 | } 184 | ] 185 | }, 186 | ] 187 | })); 188 | }); 189 | 190 | it('makes members of the type static in the upgraded class by default', () => { 191 | expectTranslateJSON(` 192 | declare interface XType { 193 | n: number; 194 | } 195 | 196 | declare var X: { 197 | new(n: number): XType 198 | m: string; 199 | }; 200 | `).to.equal(prettyStringify({ 201 | kind: ConvertedSyntaxKind.SourceFile, 202 | fileName: 'demo/some/main.ts', 203 | statements: [ 204 | { 205 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 206 | modifiers: [], 207 | name: 'XType', 208 | members: [ 209 | { 210 | kind: ConvertedSyntaxKind.PropertyDeclaration, 211 | name: 'n', 212 | optional: false, 213 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 214 | }, 215 | ] 216 | }, 217 | { 218 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 219 | modifiers: [], 220 | name: 'X', 221 | members: [ 222 | { 223 | kind: ConvertedSyntaxKind.PropertyDeclaration, 224 | name: 'n', 225 | optional: false, 226 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 227 | }, 228 | { 229 | kind: ConvertedSyntaxKind.ConstructSignature, 230 | name: 'X', 231 | parameters: [{ 232 | kind: ConvertedSyntaxKind.Parameter, 233 | name: 'n', 234 | optional: false, 235 | destructured: false, 236 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 237 | }], 238 | type: {kind: ConvertedSyntaxKind.TypeReference, typeName: 'XType'} 239 | }, 240 | { 241 | kind: ConvertedSyntaxKind.PropertyDeclaration, 242 | modifiers: [{kind: ConvertedSyntaxKind.StaticModifier}], 243 | name: 'm', 244 | optional: false, 245 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 246 | }, 247 | ] 248 | }, 249 | ] 250 | })); 251 | }); 252 | 253 | it('does not make members of the type static when the --explicit-static flag is set', () => { 254 | const explicitStaticOpts = {toJSON: true, failFast: true, explicitStatic: true}; 255 | expectTranslateJSON( 256 | ` 257 | declare interface XType { 258 | n: number; 259 | } 260 | 261 | declare var X: { 262 | new(n: number): XType 263 | m: string; 264 | }; 265 | `, 266 | explicitStaticOpts, 267 | ) 268 | .to.equal(prettyStringify({ 269 | kind: ConvertedSyntaxKind.SourceFile, 270 | fileName: 'demo/some/main.ts', 271 | statements: [ 272 | { 273 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 274 | modifiers: [], 275 | name: 'XType', 276 | members: [ 277 | { 278 | kind: ConvertedSyntaxKind.PropertyDeclaration, 279 | name: 'n', 280 | optional: false, 281 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 282 | }, 283 | ] 284 | }, 285 | { 286 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 287 | modifiers: [], 288 | name: 'X', 289 | members: [ 290 | { 291 | kind: ConvertedSyntaxKind.PropertyDeclaration, 292 | name: 'n', 293 | optional: false, 294 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 295 | }, 296 | { 297 | kind: ConvertedSyntaxKind.ConstructSignature, 298 | name: 'X', 299 | parameters: [{ 300 | kind: ConvertedSyntaxKind.Parameter, 301 | name: 'n', 302 | optional: false, 303 | destructured: false, 304 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 305 | }], 306 | type: {kind: ConvertedSyntaxKind.TypeReference, typeName: 'XType'} 307 | }, 308 | { 309 | kind: ConvertedSyntaxKind.PropertyDeclaration, 310 | name: 'm', 311 | optional: false, 312 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 313 | }, 314 | ] 315 | }, 316 | ] 317 | })); 318 | }); 319 | }); 320 | 321 | describe('merging variables with types based on their names', () => { 322 | it('merges variables with types if they have the exact same name by default', () => { 323 | expectTranslateJSON(` 324 | declare interface X { 325 | a: number; 326 | b: string; 327 | c(): boolean; 328 | } 329 | 330 | declare var X: { d: number; } 331 | `).to.equal(prettyStringify({ 332 | kind: ConvertedSyntaxKind.SourceFile, 333 | fileName: 'demo/some/main.ts', 334 | statements: [ 335 | { 336 | kind: ConvertedSyntaxKind.InterfaceDeclaration, 337 | modifiers: [], 338 | name: 'X', 339 | members: [ 340 | { 341 | kind: ConvertedSyntaxKind.PropertyDeclaration, 342 | name: 'a', 343 | optional: false, 344 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 345 | }, 346 | { 347 | kind: ConvertedSyntaxKind.PropertyDeclaration, 348 | name: 'b', 349 | optional: false, 350 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 351 | }, 352 | { 353 | kind: ConvertedSyntaxKind.MethodDeclaration, 354 | name: 'c', 355 | optional: false, 356 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'boolean'}, 357 | parameters: [] 358 | }, 359 | { 360 | kind: ConvertedSyntaxKind.PropertyDeclaration, 361 | modifiers: [{kind: ConvertedSyntaxKind.StaticModifier}], 362 | name: 'd', 363 | optional: false, 364 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 365 | } 366 | ] 367 | }, 368 | // TODO(derekx): Small fix to make the JSON cleaner: in mergeVariablesIntoClasses we 369 | // should detect when deleting a VariableDeclartion makes the parent 370 | // VariableDeclarationList empty, and delete the list node as well. 371 | { 372 | kind: ConvertedSyntaxKind.VariableStatement, 373 | modifiers: [], 374 | keyword: 'var', 375 | declarations: [] 376 | } 377 | ] 378 | })); 379 | }); 380 | 381 | // TODO(derekx): The following test should pass, but currently the type of var x is incorrect. 382 | // It is currently a TypeReference to 'X', but it should be to 'XType'. 383 | // it('renames types that conflict with unrelated variables when --rename-conflicting-types 384 | // is set', 385 | // () => { 386 | // const renameTypesOpts = {toJSON: true, failFast: true, renameConflictingTypes: 387 | // true}; expectTranslateJSON( 388 | // ` 389 | // declare interface X { 390 | // a: number; 391 | // b: string; 392 | // } 393 | 394 | // declare var X: { y: number; } 395 | 396 | // declare var x: X; 397 | // `, 398 | // renameTypesOpts, 399 | // ) 400 | // .to.equal(prettyStringify({ 401 | // kind: ConvertedSyntaxKind.SourceFile, 402 | // fileName: 'demo/some/main.ts', 403 | // statements: [ 404 | // { 405 | // kind: ConvertedSyntaxKind.InterfaceDeclaration, 406 | // modifiers: [], 407 | // name: 'XType', 408 | // members: [ 409 | // { 410 | // kind: ConvertedSyntaxKind.PropertyDeclaration, 411 | // name: 'a', 412 | // optional: false, 413 | // type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'}, 414 | // }, 415 | // { 416 | // kind: ConvertedSyntaxKind.PropertyDeclaration, 417 | // name: 'b', 418 | // optional: false, 419 | // type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'}, 420 | // }, 421 | // ] 422 | // }, 423 | // { 424 | // kind: ConvertedSyntaxKind.VariableStatement, 425 | // keyword: 'var', 426 | // declarations: [{ 427 | // kind: ConvertedSyntaxKind.VariableDeclaration, 428 | // name: 'X', 429 | // type: { 430 | // kind: ConvertedSyntaxKind.TypeLiteral, 431 | // members: [{ 432 | // kind: ConvertedSyntaxKind.PropertyDeclaration, 433 | // name: 'y', 434 | // type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 435 | // }] 436 | // } 437 | // }] 438 | // }, 439 | // { 440 | // kind: ConvertedSyntaxKind.VariableStatement, 441 | // keyword: 'var', 442 | // declarations: [{ 443 | // kind: ConvertedSyntaxKind.VariableDeclaration, 444 | // name: 'x', 445 | // type: {kind: ConvertedSyntaxKind.TypeReference, typeName: 'XType'} 446 | // }] 447 | // } 448 | // ] 449 | // })); 450 | // }); 451 | }); 452 | }); 453 | -------------------------------------------------------------------------------- /test/json/module_test.ts: -------------------------------------------------------------------------------- 1 | import {ConvertedSyntaxKind} from '../../lib/json/converted_syntax_kinds'; 2 | 3 | import {expectTranslateJSON, expectWithExports, prettyStringify} from './json_test_support'; 4 | 5 | describe('imports and exports', () => { 6 | it('converts export declarations', () => { 7 | expectTranslateJSON(`var x: string = 'abc'; 8 | var y: number = 123; 9 | 10 | export {x, y};`) 11 | .to.equal(prettyStringify({ 12 | kind: ConvertedSyntaxKind.SourceFile, 13 | fileName: 'demo/some/main.ts', 14 | statements: [ 15 | { 16 | kind: ConvertedSyntaxKind.VariableStatement, 17 | keyword: 'var', 18 | declarations: [{ 19 | kind: ConvertedSyntaxKind.VariableDeclaration, 20 | name: 'x', 21 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 22 | }] 23 | }, 24 | { 25 | kind: ConvertedSyntaxKind.VariableStatement, 26 | keyword: 'var', 27 | declarations: [{ 28 | kind: ConvertedSyntaxKind.VariableDeclaration, 29 | name: 'y', 30 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 31 | }] 32 | }, 33 | { 34 | kind: ConvertedSyntaxKind.ExportDeclaration, 35 | exportClause: { 36 | kind: ConvertedSyntaxKind.NamedExports, 37 | elements: [ 38 | {kind: ConvertedSyntaxKind.ExportSpecifier, name: 'x'}, 39 | {kind: ConvertedSyntaxKind.ExportSpecifier, name: 'y'} 40 | ] 41 | } 42 | } 43 | ] 44 | })); 45 | }); 46 | 47 | it('converts the export modifier', () => { 48 | expectTranslateJSON(`export var x: string = 'abc'; 49 | export var y: number = 123; 50 | `).to.equal(prettyStringify({ 51 | kind: ConvertedSyntaxKind.SourceFile, 52 | fileName: 'demo/some/main.ts', 53 | statements: [ 54 | { 55 | kind: ConvertedSyntaxKind.VariableStatement, 56 | modifiers: [{kind: ConvertedSyntaxKind.ExportModifier}], 57 | keyword: 'var', 58 | declarations: [{ 59 | kind: ConvertedSyntaxKind.VariableDeclaration, 60 | name: 'x', 61 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'string'} 62 | }] 63 | }, 64 | { 65 | kind: ConvertedSyntaxKind.VariableStatement, 66 | modifiers: [{kind: ConvertedSyntaxKind.ExportModifier}], 67 | keyword: 'var', 68 | declarations: [{ 69 | kind: ConvertedSyntaxKind.VariableDeclaration, 70 | name: 'y', 71 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 72 | }] 73 | } 74 | ] 75 | })); 76 | }); 77 | 78 | it('converts import declarations', () => { 79 | expectWithExports(`import {X, Y} from './other'`).to.equal(prettyStringify({ 80 | kind: ConvertedSyntaxKind.SourceFile, 81 | fileName: 'demo/some/main.ts', 82 | statements: [{ 83 | kind: ConvertedSyntaxKind.ImportDeclaration, 84 | importClause: { 85 | kind: ConvertedSyntaxKind.ImportClause, 86 | namedBindings: { 87 | kind: ConvertedSyntaxKind.NamedImports, 88 | elements: [ 89 | {kind: ConvertedSyntaxKind.ImportSpecifier, name: 'X'}, 90 | {kind: ConvertedSyntaxKind.ImportSpecifier, name: 'Y'} 91 | ] 92 | } 93 | }, 94 | moduleSpecifier: `'./other'` 95 | }] 96 | })); 97 | }); 98 | }); 99 | 100 | describe('module declarations', () => { 101 | it('converts module declarations', () => { 102 | expectWithExports('declare module m1 { export var x: number; }').to.equal(prettyStringify({ 103 | kind: ConvertedSyntaxKind.SourceFile, 104 | fileName: 'demo/some/main.ts', 105 | statements: [{ 106 | kind: ConvertedSyntaxKind.ModuleDeclaration, 107 | modifiers: [], 108 | name: 'm1', 109 | body: { 110 | kind: ConvertedSyntaxKind.ModuleBlock, 111 | statements: [{ 112 | kind: ConvertedSyntaxKind.VariableStatement, 113 | modifiers: [{kind: ConvertedSyntaxKind.ExportModifier}], 114 | keyword: 'var', 115 | declarations: [{ 116 | kind: ConvertedSyntaxKind.VariableDeclaration, 117 | name: 'x', 118 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 119 | }] 120 | }] 121 | } 122 | }] 123 | })); 124 | }); 125 | 126 | it('converts nested module declarations', () => { 127 | expectWithExports(` 128 | declare module m1.a { 129 | export var x: number; 130 | }`).to.equal(prettyStringify({ 131 | kind: ConvertedSyntaxKind.SourceFile, 132 | fileName: 'demo/some/main.ts', 133 | statements: [{ 134 | kind: ConvertedSyntaxKind.ModuleDeclaration, 135 | modifiers: [], 136 | name: 'm1', 137 | body: { 138 | kind: ConvertedSyntaxKind.ModuleDeclaration, 139 | name: 'a', 140 | body: { 141 | kind: ConvertedSyntaxKind.ModuleBlock, 142 | statements: [{ 143 | kind: ConvertedSyntaxKind.VariableStatement, 144 | modifiers: [{kind: ConvertedSyntaxKind.ExportModifier}], 145 | keyword: 'var', 146 | declarations: [{ 147 | kind: ConvertedSyntaxKind.VariableDeclaration, 148 | name: 'x', 149 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 150 | }] 151 | }] 152 | } 153 | } 154 | }] 155 | })); 156 | 157 | expectWithExports(` 158 | declare module m1 { 159 | module a { 160 | export var x: number; 161 | } 162 | }`).to.equal(prettyStringify({ 163 | kind: ConvertedSyntaxKind.SourceFile, 164 | fileName: 'demo/some/main.ts', 165 | statements: [{ 166 | kind: ConvertedSyntaxKind.ModuleDeclaration, 167 | modifiers: [], 168 | name: 'm1', 169 | body: { 170 | kind: ConvertedSyntaxKind.ModuleBlock, 171 | statements: [{ 172 | kind: ConvertedSyntaxKind.ModuleDeclaration, 173 | name: 'a', 174 | body: { 175 | kind: ConvertedSyntaxKind.ModuleBlock, 176 | statements: [{ 177 | kind: ConvertedSyntaxKind.VariableStatement, 178 | modifiers: [{kind: ConvertedSyntaxKind.ExportModifier}], 179 | keyword: 'var', 180 | declarations: [{ 181 | kind: ConvertedSyntaxKind.VariableDeclaration, 182 | name: 'x', 183 | type: {kind: ConvertedSyntaxKind.KeywordType, typeName: 'number'} 184 | }] 185 | }] 186 | } 187 | }] 188 | } 189 | }] 190 | })); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /test/main_test.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | import main = require('../lib/main'); 3 | 4 | import {expectTranslate} from './test_support'; 5 | 6 | describe('main transpiler functionality', () => { 7 | describe( 8 | 'comments', () => { 9 | it('keeps leading comments', 10 | () => { 11 | expectTranslate(`/* A */ var a; 12 | /* B */ var b;`).to.equal(`/// A 13 | @JS() 14 | external get a; 15 | @JS() 16 | external set a(v); 17 | 18 | /// B 19 | @JS() 20 | external get b; 21 | @JS() 22 | external set b(v);`); 23 | expectTranslate(`// A 24 | var a; 25 | /// B 26 | var b;`).to.equal(`/// A 27 | @JS() 28 | external get a; 29 | @JS() 30 | external set a(v); 31 | 32 | /// B 33 | @JS() 34 | external get b; 35 | @JS() 36 | external set b(v);`); 37 | }); 38 | it('keeps ctor comments', () => { 39 | expectTranslate('/** A */ class A {\n /** ctor */ constructor() {}}').to.equal(`/// A 40 | @JS() 41 | class A { 42 | // @Ignore 43 | A.fakeConstructor$(); 44 | 45 | /// ctor 46 | external factory A(); 47 | }`); 48 | }); 49 | it('translates links to dart doc format', () => { 50 | expectTranslate('/** {@link this/place} */ var a').to.equal(`/// [this/place] 51 | @JS() 52 | external get a; 53 | @JS() 54 | external set a(v);`); 55 | expectTranslate('/* {@link 1} {@link 2} */ var a').to.equal(`/// [1] [2] 56 | @JS() 57 | external get a; 58 | @JS() 59 | external set a(v);`); 60 | }); 61 | it('removes @module doc tags', () => { 62 | expectTranslate(`/** @module 63 | * This is a module for doing X. 64 | */`).to.equal(`/// This is a module for doing X.`); 65 | }); 66 | it('removes @description doc tags', () => { 67 | expectTranslate(`/** @description 68 | * This is a module for doing X. 69 | */`).to.equal(`/// This is a module for doing X.`); 70 | }); 71 | it('removes @depracted doc tags', () => { 72 | expectTranslate(`/** 73 | * Use SomethingElse instead. 74 | * @deprecated 75 | */`).to.equal(`/// Use SomethingElse instead.`); 76 | }); 77 | it('removes @param doc tags', () => { 78 | expectTranslate(`/** 79 | * Method to do blah. 80 | * @param doc Document. 81 | */`).to.equal(`/// Method to do blah.`); 82 | }); 83 | it('removes @return doc tags', () => { 84 | expectTranslate(`/** 85 | * Method to do blah. 86 | * @return {String} 87 | */`).to.equal(`/// Method to do blah.`); 88 | }); 89 | it('removes @throws doc tags', () => { 90 | expectTranslate(`/** 91 | * Method to do blah. 92 | * @throws ArgumentException If arguments are wrong 93 | */`).to.equal(`/// Method to do blah.`); 94 | }); 95 | it('multiple line comment', () => { 96 | expectTranslate(`/** 97 | * Method to do blah. 98 | * Bla bla bla. 99 | * Foo bar. 100 | */`).to.equal(`/// Method to do blah. 101 | /// Bla bla bla. 102 | /// Foo bar.`); 103 | }); 104 | it('multiple line comment', () => { 105 | expectTranslate(`class Foo { 106 | /** 107 | * Method to do blah. 108 | * Bla bla bla. 109 | * Foo bar. 110 | */ 111 | bar(); 112 | }`).to.equal(`@JS() 113 | class Foo { 114 | // @Ignore 115 | Foo.fakeConstructor$(); 116 | 117 | /// Method to do blah. 118 | /// Bla bla bla. 119 | /// Foo bar. 120 | external bar(); 121 | }`); 122 | 123 | expectTranslate(`class Foo { 124 | // Baz. 125 | // Bla bla bla. 126 | // Foo bar. 127 | 128 | // Bla. 129 | bar(); 130 | }`).to.equal(`@JS() 131 | class Foo { 132 | // @Ignore 133 | Foo.fakeConstructor$(); 134 | 135 | /// Baz. 136 | /// Bla bla bla. 137 | /// Foo bar. 138 | 139 | /// Bla. 140 | external bar(); 141 | }`); 142 | }); 143 | }); 144 | 145 | describe('output paths', () => { 146 | it('writes within the path', () => { 147 | let transpiler = new main.Transpiler({basePath: '/a'}); 148 | chai.expect(transpiler.getOutputPath('/a/b/c.js', '/x')).to.equal('/x/b/c.dart'); 149 | chai.expect(transpiler.getOutputPath('b/c.js', '/x')).to.equal('/x/b/c.dart'); 150 | chai.expect(transpiler.getOutputPath('b/c.js', 'x')).to.equal('x/b/c.dart'); 151 | chai.expect(() => transpiler.getOutputPath('/outside/b/c.js', '/x')) 152 | .to.throw(/must be located under base/); 153 | }); 154 | it('defaults to writing to the same location', () => { 155 | let transpiler = new main.Transpiler({basePath: undefined}); 156 | chai.expect(transpiler.getOutputPath('/a/b/c.js', '/e')).to.equal('/a/b/c.dart'); 157 | chai.expect(transpiler.getOutputPath('b/c.js', '')).to.equal('b/c.dart'); 158 | }); 159 | it('translates .es6, .ts, and .js', () => { 160 | let transpiler = new main.Transpiler({basePath: undefined}); 161 | ['a.js', 'a.ts', 'a.es6'].forEach((n) => { 162 | chai.expect(transpiler.getOutputPath(n, '')).to.equal('a.dart'); 163 | }); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/module_test.ts: -------------------------------------------------------------------------------- 1 | import chai = require('chai'); 2 | import main = require('../lib/main'); 3 | import ModuleTranspiler from '../lib/module'; 4 | import {FacadeConverter} from '../lib/facade_converter'; 5 | 6 | import {expectTranslate, expectErroneousCode, translateSources} from './test_support'; 7 | 8 | describe('imports', () => { 9 | it('ignore import equals statements', () => { 10 | expectTranslate('import x = require("y");').to.equal('import "y.dart" as x;'); 11 | }); 12 | it('ignore import from statements', () => { 13 | expectTranslate('import {x,y} from "z";').to.equal(''); 14 | }); 15 | it('ignore import star', () => { 16 | expectTranslate('import * as foo from "z";').to.equal(''); 17 | }); 18 | it('ignore renamed imports', () => { 19 | expectTranslate('import {Foo as Bar} from "baz";').to.equal(''); 20 | }); 21 | it('empty import spec generates safe Dart code', () => { 22 | expectTranslate('import {} from "baz";').to.equal(''); 23 | }); 24 | }); 25 | 26 | describe('exports', () => { 27 | // Dart exports are implicit, everything non-private is exported by the library. 28 | it('allows variable exports', () => { 29 | expectTranslate('export var x = 12;').to.equal(`@JS() 30 | external get x; 31 | @JS() 32 | external set x(v);`); 33 | }); 34 | it('allows class exports', () => { 35 | expectTranslate('export class X {}').to.equal(`@JS() 36 | class X { 37 | // @Ignore 38 | X.fakeConstructor$(); 39 | }`); 40 | }); 41 | it('allows export declarations', () => { 42 | expectTranslate('export * from "X";').to.equal('export "X.dart";'); 43 | }); 44 | it('allows export declarations', () => { 45 | expectTranslate('export * from "./X";').to.equal('export "X.dart";'); 46 | }); 47 | it('allows named export declarations', () => { 48 | expectTranslate('export {a, b} from "X";').to.equal('export "X.dart" show a, b;'); 49 | }); 50 | it('ignores named export declarations', () => { 51 | expectTranslate(`declare module '../some_other_module' { 52 | interface Foo { } 53 | }`) 54 | .to.equal( 55 | '// Library augmentation not allowed by Dart. Ignoring augmentation of ../some_other_module'); 56 | }); 57 | 58 | it('fails for renamed exports', () => { 59 | expectErroneousCode('export {Foo as Bar} from "baz";') 60 | .to.throw(/import\/export renames are unsupported in Dart/); 61 | }); 62 | it('fails for exports without URLs', () => { 63 | expectErroneousCode('export {a as b};').to.throw('re-exports must have a module URL'); 64 | }); 65 | it('fails for empty export specs', () => { 66 | expectErroneousCode('export {} from "baz";').to.throw(/empty export list/); 67 | }); 68 | }); 69 | 70 | describe('module name', () => { 71 | let transpiler: main.Transpiler; 72 | let modTranspiler: ModuleTranspiler; 73 | beforeEach(() => { 74 | transpiler = new main.Transpiler({failFast: true, moduleName: 'sample_module', basePath: '/a'}); 75 | modTranspiler = 76 | new ModuleTranspiler(transpiler, new FacadeConverter(transpiler, ''), 'sample_module'); 77 | }); 78 | it('adds module name', () => { 79 | let results = translateSources( 80 | new Map(Object.entries({'/a/b/c.ts': 'var x;'})), 81 | {failFast: true, moduleName: 'sample_module', basePath: '/a'}); 82 | chai.expect(results.get('/a/b/c.ts')).to.equal(`@JS("sample_module") 83 | library b.c; 84 | 85 | import "package:js/js.dart"; 86 | 87 | @JS() 88 | external get x; 89 | @JS() 90 | external set x(v); 91 | `); 92 | }); 93 | it('leaves relative paths alone', () => { 94 | chai.expect(modTranspiler.getLibraryName('a/b')).to.equal('a.b'); 95 | }); 96 | it('handles reserved words', () => { 97 | chai.expect(modTranspiler.getLibraryName('/a/for/in/do/x')).to.equal('_for._in._do.x'); 98 | }); 99 | it('handles built-in and limited keywords', () => { 100 | chai.expect(modTranspiler.getLibraryName('/a/as/if/sync/x')).to.equal('as._if.sync.x'); 101 | }); 102 | it('handles file extensions', () => { 103 | chai.expect(modTranspiler.getLibraryName('a/x.ts')).to.equal('a.x'); 104 | chai.expect(modTranspiler.getLibraryName('a/x.js')).to.equal('a.x'); 105 | }); 106 | it('handles non word characters', () => { 107 | chai.expect(modTranspiler.getLibraryName('a/%x.ts')).to.equal('a._x'); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/test_support.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | import * as fs from 'fs'; 3 | import * as ts from 'typescript'; 4 | 5 | import * as main from '../lib/main'; 6 | 7 | export type Input = string|Map; 8 | 9 | export function expectTranslate(tsCode: Input, options: main.TranspilerOptions = {}) { 10 | const result = translateSource(tsCode, options); 11 | return chai.expect(result); 12 | } 13 | 14 | export function expectErroneousCode(tsCode: Input, options: main.TranspilerOptions = {}) { 15 | options.failFast = false; // Collect *all* errors. 16 | return chai.expect(() => translateSource(tsCode, options)); 17 | } 18 | 19 | const defaultLibFileName = ts.getDefaultLibFileName(main.COMPILER_OPTIONS); 20 | // Used to cache library files so that they don't need to be re-parsed for each test. 21 | const libSourceFiles: Map = new Map(); 22 | 23 | export function parseAndNormalizeFiles( 24 | nameToContent: Map, transpiler: main.Transpiler): ts.Program { 25 | const compilerOptions = transpiler.getCompilerOptions(); 26 | const sourceFileMap: Map = new Map(); 27 | 28 | let compilerHost = ts.createCompilerHost(compilerOptions); 29 | compilerHost.getSourceFile = (sourceName) => { 30 | let sourcePath = sourceName; 31 | if (sourcePath === defaultLibFileName) { 32 | sourcePath = ts.getDefaultLibFilePath(compilerOptions); 33 | } else if (sourceFileMap.has(sourceName)) { 34 | return sourceFileMap.get(sourceName); 35 | } else if (nameToContent.has(sourcePath)) { 36 | return ts.createSourceFile( 37 | sourcePath, nameToContent.get(sourcePath), compilerOptions.target, true); 38 | } else if (!fs.existsSync(sourcePath)) { 39 | return undefined; 40 | } 41 | 42 | if (!libSourceFiles.has(sourcePath)) { 43 | const contents = fs.readFileSync(sourcePath, 'utf-8'); 44 | // Cache to avoid excessive test times. 45 | libSourceFiles.set( 46 | sourcePath, ts.createSourceFile(sourcePath, contents, compilerOptions.target, true)); 47 | } 48 | return libSourceFiles.get(sourcePath); 49 | }; 50 | compilerHost.fileExists = (sourceName) => { 51 | return nameToContent.has(sourceName); 52 | }; 53 | compilerHost.readFile = () => { 54 | throw new Error('unexpected call to readFile'); 55 | }; 56 | compilerHost.useCaseSensitiveFileNames = () => false; 57 | compilerHost.getCanonicalFileName = (fileName) => `../${fileName}`; 58 | compilerHost.getCurrentDirectory = () => 'fakeDir'; 59 | compilerHost.resolveModuleNames = main.getModuleResolver(compilerHost); 60 | 61 | // Create a program from inputs. 62 | const entryPoints = new Set(nameToContent.keys()); 63 | 64 | transpiler.normalizeSourceFiles(entryPoints, sourceFileMap, compilerHost); 65 | 66 | // Create a new program after performing source file transformations. 67 | const updatedProgram = ts.createProgram(Array.from(entryPoints), compilerOptions, compilerHost); 68 | return updatedProgram; 69 | } 70 | 71 | export const FAKE_MAIN = 'demo/some/main.ts'; 72 | 73 | export function translateSources( 74 | contents: Input, options: main.TranspilerOptions = {}): Map { 75 | // Default to quick stack traces. 76 | if (!options.hasOwnProperty('failFast')) { 77 | options.failFast = true; 78 | } 79 | 80 | let namesToContent: Map; 81 | if (typeof contents === 'string') { 82 | namesToContent = new Map(); 83 | namesToContent.set(FAKE_MAIN, contents); 84 | } else { 85 | namesToContent = contents; 86 | } 87 | options.enforceUnderscoreConventions = true; 88 | const transpiler = new main.Transpiler(options); 89 | const program = parseAndNormalizeFiles(namesToContent, transpiler); 90 | return transpiler.translateProgram(program, Array.from(namesToContent.keys())); 91 | } 92 | 93 | export function translateSource(contents: Input, options: main.TranspilerOptions = {}): string { 94 | const results = translateSources(contents, options); 95 | // Return the main outcome, from 'main.ts'. 96 | let result = results.get(FAKE_MAIN); 97 | // Strip out the package:js import as it clutters the output. 98 | result = result.replace(/import "package:js\/js.dart";\s+/g, ''); 99 | result = result.replace(/^@JS\("?[^)]*"?\)\s+library [^;]+;\s+/g, ''); 100 | return result.trim(); 101 | } 102 | -------------------------------------------------------------------------------- /test/type_test.ts: -------------------------------------------------------------------------------- 1 | import {expectTranslate} from './test_support'; 2 | 3 | describe('types', () => { 4 | it('supports qualified names', () => { 5 | expectTranslate('var x: foo.Bar;').to.equal(`@JS() 6 | external foo.Bar get x; 7 | @JS() 8 | external set x(foo.Bar v);`); 9 | }); 10 | 11 | it('supports null types', () => { 12 | expectTranslate('export function attr(name: string, value: null);').to.equal(`@JS() 13 | external attr(String name, Null value);`); 14 | 15 | expectTranslate(`export function style(name: string, priority?: 'regular' | 'important');`) 16 | .to.equal(`@JS() 17 | external style(String name, [String /*'regular'|'important'*/ priority]);`); 18 | 19 | expectTranslate(`export function style(name: string, priority?: null | 'important');`) 20 | .to.equal(`@JS() 21 | external style(String name, [String /*Null|'important'*/ priority]);`); 22 | expectTranslate('var foo: null;').to.equal(`@JS() 23 | external Null get foo; 24 | @JS() 25 | external set foo(Null v);`); 26 | }); 27 | it('supports this return type', () => { 28 | expectTranslate('export interface Foo { bar() : this; }').to.equal(`@anonymous 29 | @JS() 30 | abstract class Foo { 31 | external Foo bar(); 32 | }`); 33 | }); 34 | it('supports true and false return types', () => { 35 | expectTranslate('export function f(): true;').to.equal(`@JS() 36 | external bool /*true*/ f();`); 37 | expectTranslate('export function g(): false;').to.equal(`@JS() 38 | external bool /*false*/ g();`); 39 | }); 40 | 41 | it('comment type literals', () => { 42 | expectTranslate('var x: {x: string, y: number};').to.equal(`@JS() 43 | external dynamic /*{x: string, y: number}*/ get x; 44 | @JS() 45 | external set x(dynamic /*{x: string, y: number}*/ v);`); 46 | }); 47 | it('do not translates string index signatures to dartisms', () => { 48 | // We wish these could be just Map but sadly can't support 49 | // that yet. 50 | expectTranslate('var x: {[k: string]: any[]};').to.equal(`@JS() 51 | external dynamic /*JSMap of >*/ get x; 52 | @JS() 53 | external set x(dynamic /*JSMap of >*/ v);`); 54 | expectTranslate('var x: {[k: number]: number};').to.equal(`@JS() 55 | external dynamic /*JSMap of */ get x; 56 | @JS() 57 | external set x(dynamic /*JSMap of */ v);`); 58 | }); 59 | it('drops type literals with index signatures and other properties', () => { 60 | expectTranslate('var x: {a: number, [k: string]: number};').to.equal(`@JS() 61 | external dynamic /*{a: number, [k: string]: number}*/ get x; 62 | @JS() 63 | external set x(dynamic /*{a: number, [k: string]: number}*/ v);`); 64 | }); 65 | 66 | it('should support union types', () => { 67 | expectTranslate('function foo() : number | number[];').to.equal(`@JS() 68 | external dynamic /*num|List*/ foo();`); 69 | expectTranslate('var x: number|Array;').to.equal(`@JS() 70 | external dynamic /*num|List*/ get x; 71 | @JS() 72 | external set x(dynamic /*num|List*/ v);`); 73 | expectTranslate('function x(): number|Array<{[k: string]: any}> {};').to.equal(`@JS() 74 | external dynamic /*num|List>*/ x();`); 75 | }); 76 | 77 | it('should support intersection types', () => { 78 | expectTranslate(` 79 | interface Foo { a: number, b: string } 80 | interface Bar { b: string } 81 | 82 | function foo() : Foo & Bar; 83 | `).to.equal(`@anonymous 84 | @JS() 85 | abstract class Foo { 86 | external num get a; 87 | external set a(num v); 88 | external String get b; 89 | external set b(String v); 90 | external factory Foo({num a, String b}); 91 | } 92 | 93 | @anonymous 94 | @JS() 95 | abstract class Bar { 96 | external String get b; 97 | external set b(String v); 98 | external factory Bar({String b}); 99 | } 100 | 101 | @JS() 102 | external Foo /*Foo&Bar*/ foo();`); 103 | }); 104 | 105 | it('should support parenthesized types', () => { 106 | expectTranslate('function foo() : (number | number[]);').to.equal(`@JS() 107 | external dynamic /*num|List*/ foo();`); 108 | expectTranslate('var x: (number|Array);').to.equal(`@JS() 109 | external dynamic /*num|List*/ get x; 110 | @JS() 111 | external set x(dynamic /*num|List*/ v);`); 112 | expectTranslate('function x(): number|(Array<{[k: string]: any}>) {};').to.equal(`@JS() 113 | external dynamic /*num|List>*/ x();`); 114 | }); 115 | 116 | it('should support array types', () => { 117 | expectTranslate('var x: string[] = [];').to.equal(`@JS() 118 | external List get x; 119 | @JS() 120 | external set x(List v);`); 121 | }); 122 | it('should support function types', () => { 123 | expectTranslate('var x: (a: string) => string;').to.equal(`@JS() 124 | external String Function(String) get x; 125 | @JS() 126 | external set x(String Function(String) v);`); 127 | 128 | expectTranslate('declare var a: Function').to.equal(`@JS() 129 | external Function get a; 130 | @JS() 131 | external set a(Function v);`); 132 | }); 133 | 134 | describe('TypeScript utility types and other mapped types', () => { 135 | it('emits X in place of Partial since all Dart types are currently nullable', () => { 136 | expectTranslate('interface X { a: number; } declare const x: Partial;') 137 | .to.equal(`@anonymous 138 | @JS() 139 | abstract class X { 140 | external num get a; 141 | external set a(num v); 142 | external factory X({num a}); 143 | } 144 | 145 | @JS() 146 | external X /*Partial*/ get x;`); 147 | }); 148 | 149 | it('treats other mapped types as dynamic', () => { 150 | expectTranslate(`interface Todo { 151 | task: string; 152 | } 153 | 154 | type ReadonlyTodo = { 155 | readonly[P in keyof Todo]: Todo[P]; 156 | } 157 | 158 | declare const todo: ReadonlyTodo;`) 159 | .to.equal(`@anonymous 160 | @JS() 161 | abstract class Todo { 162 | external String get task; 163 | external set task(String v); 164 | external factory Todo({String task}); 165 | } 166 | 167 | /* 168 | Warning: Mapped types are not supported in Dart. Uses of this type will be replaced by dynamic. 169 | type ReadonlyTodo = { 170 | readonly[P in keyof Todo]: Todo[P]; 171 | } 172 | */ 173 | @JS() 174 | external dynamic /*ReadonlyTodo*/ get todo;`); 175 | }); 176 | }); 177 | 178 | it('should support conditional types', () => { 179 | expectTranslate(` 180 | type TypeName = T extends string ? "string" : 181 | T extends string ? "string" : 182 | T extends number ? "number" : 183 | T extends boolean ? "boolean" : 184 | T extends undefined ? "undefined" : 185 | T extends Function ? "function" : 186 | "object"; 187 | 188 | declare var x: TypeName; 189 | declare var y: TypeName; 190 | declare var z: TypeName;`) 191 | .to.equal( 192 | `/*Warning: Conditional types are not supported in Dart. Uses of this type will be replaced by dynamic. 193 | type TypeName = T extends string ? "string" : 194 | T extends string ? "string" : 195 | T extends number ? "number" : 196 | T extends boolean ? "boolean" : 197 | T extends undefined ? "undefined" : 198 | T extends Function ? "function" : 199 | "object"; 200 | */ 201 | @JS() 202 | external dynamic /*TypeName*/ get x; 203 | @JS() 204 | external set x(dynamic /*TypeName*/ v); 205 | @JS() 206 | external dynamic /*TypeName*/ get y; 207 | @JS() 208 | external set y(dynamic /*TypeName*/ v); 209 | @JS() 210 | external dynamic /*TypeName*/ get z; 211 | @JS() 212 | external set z(dynamic /*TypeName*/ v);`); 213 | }); 214 | }); 215 | 216 | describe('type arguments', () => { 217 | it('should support declaration', () => { 218 | expectTranslate('class X { a: A; }').to.equal(`@JS() 219 | class X { 220 | // @Ignore 221 | X.fakeConstructor$(); 222 | external A get a; 223 | external set a(A v); 224 | }`); 225 | }); 226 | it('should support nested extends', () => { 227 | expectTranslate('class X> { }').to.equal(`@JS() 228 | class X> { 229 | // @Ignore 230 | X.fakeConstructor$(); 231 | }`); 232 | }); 233 | it('should multiple extends', () => { 234 | expectTranslate('class X { }').to.equal(`@JS() 235 | class X { 236 | // @Ignore 237 | X.fakeConstructor$(); 238 | }`); 239 | }); 240 | it('should support use', () => { 241 | expectTranslate('class X extends Y { }').to.equal(`@JS() 242 | class X extends Y { 243 | // @Ignore 244 | X.fakeConstructor$() : super.fakeConstructor$(); 245 | }`); 246 | }); 247 | it('should handle and generic arguments', () => { 248 | expectTranslate('var x: X;').to.equal(`@JS() 249 | external X get x; 250 | @JS() 251 | external set x(X v);`); 252 | expectTranslate('class X extends Y { }').to.equal(`@JS() 253 | class X extends Y { 254 | // @Ignore 255 | X.fakeConstructor$() : super.fakeConstructor$(); 256 | }`); 257 | expectTranslate('class X extends Y { }').to.equal(`@JS() 258 | class X extends Y { 259 | // @Ignore 260 | X.fakeConstructor$() : super.fakeConstructor$(); 261 | }`); 262 | expectTranslate('var z : Y;').to.equal(`@JS() 263 | external Y get z; 264 | @JS() 265 | external set z(Y v);`); 266 | expectTranslate('var z : Y;').to.equal(`@JS() 267 | external Y get z; 268 | @JS() 269 | external set z(Y v);`); 270 | }); 271 | 272 | it('should create class for type alias literals', () => { 273 | expectTranslate(`/** 274 | * Event Parameters. 275 | */ 276 | export type EventParameters = { 277 | bubbles: boolean; 278 | /** 279 | * Is cancelable. 280 | */ 281 | cancelable: boolean; 282 | }; 283 | 284 | export function dispatch(parameters: EventParameters): void;`) 285 | .to.equal(`/// Event Parameters. 286 | @anonymous 287 | @JS() 288 | abstract class EventParameters { 289 | external bool get bubbles; 290 | external set bubbles(bool v); 291 | 292 | /// Is cancelable. 293 | external bool get cancelable; 294 | external set cancelable(bool v); 295 | external factory EventParameters({bool bubbles, bool cancelable}); 296 | } 297 | 298 | @JS() 299 | external void dispatch(EventParameters parameters);`); 300 | 301 | expectTranslate(`/** 302 | * Event Parameters. 303 | */ 304 | export type EventParameters = { 305 | bubbles: T; 306 | /** 307 | * Is cancelable. 308 | */ 309 | cancelable: T; 310 | }; 311 | 312 | export function dispatch(parameters: EventParameters): void;`) 313 | .to.equal(`/// Event Parameters. 314 | @anonymous 315 | @JS() 316 | abstract class EventParameters { 317 | external T get bubbles; 318 | external set bubbles(T v); 319 | 320 | /// Is cancelable. 321 | external T get cancelable; 322 | external set cancelable(T v); 323 | external factory EventParameters({T bubbles, T cancelable}); 324 | } 325 | 326 | @JS() 327 | external void dispatch(EventParameters parameters);`); 328 | }); 329 | 330 | it('should create typedef for type alias function literals', () => { 331 | expectTranslate(` 332 | export type ValueFn = (this: T, a: A, b: B) => A; 333 | 334 | export type SimpleValueFn = (a: A, b: B) => A; 335 | 336 | export function dispatch(callback: ValueFn): void; 337 | export function dispatchSimple(callback: SimpleValueFn): void;`) 338 | .to.equal(`import "dart:html" show Element; 339 | 340 | typedef A ValueFn(/*T this*/ A a, B b); 341 | typedef A SimpleValueFn(A a, B b); 342 | @JS() 343 | external void dispatch(ValueFn callback); 344 | @JS() 345 | external void dispatchSimple(SimpleValueFn callback);`); 346 | }); 347 | 348 | it('should handle generic parameters on non dart compatible type aliases', () => { 349 | expectTranslate(` 350 | export type Triangle = [G, G, G]; 351 | export type ListOfLists = [G[]]; 352 | 353 | export function triangles(): Triangle[]; 354 | `).to.equal(`/*export type Triangle = [G, G, G];*/ 355 | /*export type ListOfLists = [G[]];*/ 356 | @JS() 357 | external List /*Tuple of */ > triangles/**/();`); 358 | }); 359 | 360 | it('supports the keyof operator and the indexed access operator', () => { 361 | expectTranslate(`export interface A { 362 | a: number; 363 | } 364 | export function f(first: K, second: A[K]): boolean;`) 365 | .to.equal(`@anonymous 366 | @JS() 367 | abstract class A { 368 | external num get a; 369 | external set a(num v); 370 | external factory A({num a}); 371 | } 372 | 373 | @JS() 374 | external bool f/**/( 375 | dynamic /*K*/ first, dynamic /*A[K]*/ second);`); 376 | }); 377 | }); 378 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "noEmit": true, 6 | "noEmitOnError": true, 7 | "noImplicitAny": true, 8 | "allowUnreachableCode": false, 9 | "target": "ES2015", 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "build", 15 | "test/e2e" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "eofline": true, 5 | "forin": true, 6 | "jsdoc-format": true, 7 | "label-position": true, 8 | "no-arg": true, 9 | "no-conditional-assignment": true, 10 | "no-construct": true, 11 | "no-debugger": true, 12 | "no-empty": true, 13 | "no-inferrable-types": true, 14 | "no-internal-module": true, 15 | "no-shadowed-variable": [true, {"temporalDeadZone": false}], 16 | "no-string-literal": true, 17 | "no-switch-case-fall-through": true, 18 | "no-unused-expression": true, 19 | "no-var-keyword": true, 20 | "radix": true, 21 | "semicolon": [ 22 | true, 23 | "always" 24 | ], 25 | "switch-default": true, 26 | "triple-equals": [ 27 | true, 28 | "allow-null-check" 29 | ], 30 | "variable-name": [ 31 | true, 32 | "check-format", 33 | "ban-keywords" 34 | ] 35 | } 36 | } 37 | --------------------------------------------------------------------------------