diff --git a/index.d.ts b/index.d.ts index 503bdc1..66f8289 100644 --- a/index.d.ts +++ b/index.d.ts @@ -4,6 +4,7 @@ import ts = require('typescript'); declare function tss(code: string, options: ts.CompilerOptions): string; declare module tss { class TypeScriptSimple { + private doSemanticChecks; private service; private outputs; private options; @@ -11,16 +12,23 @@ declare module tss { /** * @param {ts.CompilerOptions=} options TypeScript compile options (some options are ignored) */ - constructor(options?: ts.CompilerOptions); + constructor(options?: ts.CompilerOptions, doSemanticChecks?: boolean); /** * @param {string} code TypeScript source code to compile - * @return {string} + * @param {string} only needed if you plan to use sourceMaps. Provide the complete filePath relevant to you + * @return {string} The JavaScript with inline sourceMaps if sourceMaps were enabled */ - compile(code: string): string; + compile(code: string, filename?: string): string; private createService(); private getTypeScriptBinDir(); private getDefaultLibFilename(options); - private toJavaScript(service); + /** + * converts {"version":3,"file":"file.js","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC"} + * to {"version":3,"sources":["foo/test.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC","file":"foo/test.ts","sourcesContent":["var x = 'test';"]} + * derived from : https://github.com/thlorenz/convert-source-map + */ + private getInlineSourceMap(mapText, filename); + private toJavaScript(service, filename?); private formatDiagnostics(diagnostics); } } diff --git a/index.js b/index.js index a9ace09..fe88d0c 100644 --- a/index.js +++ b/index.js @@ -19,8 +19,10 @@ var tss; /** * @param {ts.CompilerOptions=} options TypeScript compile options (some options are ignored) */ - function TypeScriptSimple(options) { + function TypeScriptSimple(options, doSemanticChecks) { if (options === void 0) { options = {}; } + if (doSemanticChecks === void 0) { doSemanticChecks = true; } + this.doSemanticChecks = doSemanticChecks; this.service = null; this.outputs = {}; this.files = {}; @@ -34,16 +36,18 @@ var tss; } /** * @param {string} code TypeScript source code to compile - * @return {string} + * @param {string} only needed if you plan to use sourceMaps. Provide the complete filePath relevant to you + * @return {string} The JavaScript with inline sourceMaps if sourceMaps were enabled */ - TypeScriptSimple.prototype.compile = function (code) { + TypeScriptSimple.prototype.compile = function (code, filename) { + if (filename === void 0) { filename = FILENAME_TS; } if (!this.service) { this.service = this.createService(); } var file = this.files[FILENAME_TS]; file.text = code; file.version++; - return this.toJavaScript(this.service); + return this.toJavaScript(this.service, filename); }; TypeScriptSimple.prototype.createService = function () { var _this = this; @@ -84,15 +88,49 @@ var tss; return 'lib.d.ts'; } }; - TypeScriptSimple.prototype.toJavaScript = function (service) { + /** + * converts {"version":3,"file":"file.js","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC"} + * to {"version":3,"sources":["foo/test.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC","file":"foo/test.ts","sourcesContent":["var x = 'test';"]} + * derived from : https://github.com/thlorenz/convert-source-map + */ + TypeScriptSimple.prototype.getInlineSourceMap = function (mapText, filename) { + var sourceMap = JSON.parse(mapText); + sourceMap.file = filename; + sourceMap.sources = [filename]; + sourceMap.sourcesContent = [this.files[FILENAME_TS].text]; + delete sourceMap.sourceRoot; + return JSON.stringify(sourceMap); + }; + TypeScriptSimple.prototype.toJavaScript = function (service, filename) { + if (filename === void 0) { filename = FILENAME_TS; } var output = service.getEmitOutput(FILENAME_TS); - if (output.emitOutputStatus === 0 /* Succeeded */) { - var filename = FILENAME_TS.replace(/ts$/, 'js'); - var file = output.outputFiles.filter(function (file) { return file.name === filename; })[0]; + // Meaning of succeeded is driven by whether we need to check for semantic errors or not + var succeeded = output.emitOutputStatus === 0 /* Succeeded */; + if (!this.doSemanticChecks) { + // We have an output. It implies syntactic success + if (!succeeded) + succeeded = !!output.outputFiles.length; + } + if (succeeded) { + var outputFilename = FILENAME_TS.replace(/ts$/, 'js'); + var file = output.outputFiles.filter(function (file) { return file.name === outputFilename; })[0]; // Fixed in v1.5 https://github.com/Microsoft/TypeScript/issues/1653 - return file.text.replace(/\r\n/g, os.EOL); + var text = file.text.replace(/\r\n/g, os.EOL); + // If we have sourceMaps convert them to inline sourceMaps + if (this.options.sourceMap) { + var sourceMapFilename = FILENAME_TS.replace(/ts$/, 'js.map'); + var sourceMapFile = output.outputFiles.filter(function (file) { return file.name === sourceMapFilename; })[0]; + // Transform sourcemap + var sourceMapText = sourceMapFile.text; + sourceMapText = this.getInlineSourceMap(sourceMapText, filename); + var base64SourceMapText = new Buffer(sourceMapText).toString('base64'); + text = text.replace('//# sourceMappingURL=' + sourceMapFilename, '//# sourceMappingURL=data:application/json;base64,' + base64SourceMapText); + } + return text; } - var allDiagnostics = service.getCompilerOptionsDiagnostics().concat(service.getSyntacticDiagnostics(FILENAME_TS)).concat(service.getSemanticDiagnostics(FILENAME_TS)); + var allDiagnostics = service.getCompilerOptionsDiagnostics().concat(service.getSyntacticDiagnostics(FILENAME_TS)); + if (this.doSemanticChecks) + allDiagnostics = allDiagnostics.concat(service.getSemanticDiagnostics(FILENAME_TS)); throw new Error(this.formatDiagnostics(allDiagnostics)); }; TypeScriptSimple.prototype.formatDiagnostics = function (diagnostics) { diff --git a/index.ts b/index.ts index 88a5657..3bf5d20 100644 --- a/index.ts +++ b/index.ts @@ -22,12 +22,12 @@ module tss { private service: ts.LanguageService = null; private outputs: ts.Map = {}; private options: ts.CompilerOptions; - private files: ts.Map<{version: number; text: string;}> = {}; + private files: ts.Map<{ version: number; text: string; }> = {}; /** * @param {ts.CompilerOptions=} options TypeScript compile options (some options are ignored) */ - constructor(options: ts.CompilerOptions = {}) { + constructor(options: ts.CompilerOptions = {}, private doSemanticChecks = true) { if (options.target == null) { options.target = ts.ScriptTarget.ES5; } @@ -36,12 +36,13 @@ module tss { } this.options = options; } - + /** * @param {string} code TypeScript source code to compile - * @return {string} + * @param {string} only needed if you plan to use sourceMaps. Provide the complete filePath relevant to you + * @return {string} The JavaScript with inline sourceMaps if sourceMaps were enabled */ - compile(code: string): string { + compile(code: string, filename = FILENAME_TS): string { if (!this.service) { this.service = this.createService(); } @@ -50,7 +51,7 @@ module tss { file.text = code; file.version++; - return this.toJavaScript(this.service); + return this.toJavaScript(this.service, filename); } private createService(): ts.LanguageService { @@ -95,18 +96,58 @@ module tss { } } - private toJavaScript(service: ts.LanguageService): string { + /** + * converts {"version":3,"file":"file.js","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC"} + * to {"version":3,"sources":["foo/test.ts"],"names":[],"mappings":"AAAA,IAAI,CAAC,GAAG,MAAM,CAAC","file":"foo/test.ts","sourcesContent":["var x = 'test';"]} + * derived from : https://github.com/thlorenz/convert-source-map + */ + private getInlineSourceMap(mapText: string, filename: string): string { + var sourceMap = JSON.parse(mapText); + sourceMap.file = filename; + sourceMap.sources = [filename]; + sourceMap.sourcesContent = [this.files[FILENAME_TS].text]; + delete sourceMap.sourceRoot; + return JSON.stringify(sourceMap); + } + + private toJavaScript(service: ts.LanguageService, filename = FILENAME_TS): string { var output = service.getEmitOutput(FILENAME_TS); - if (output.emitOutputStatus === ts.EmitReturnStatus.Succeeded) { - var filename = FILENAME_TS.replace(/ts$/, 'js'); - var file = output.outputFiles.filter((file) => file.name === filename)[0]; + + // Meaning of succeeded is driven by whether we need to check for semantic errors or not + var succeeded = output.emitOutputStatus === ts.EmitReturnStatus.Succeeded; + if (!this.doSemanticChecks) { + // We have an output. It implies syntactic success + if (!succeeded) succeeded = !!output.outputFiles.length; + } + + if (succeeded) { + var outputFilename = FILENAME_TS.replace(/ts$/, 'js'); + var file = output.outputFiles.filter((file) => file.name === outputFilename)[0]; // Fixed in v1.5 https://github.com/Microsoft/TypeScript/issues/1653 - return file.text.replace(/\r\n/g, os.EOL); + var text = file.text.replace(/\r\n/g, os.EOL); + + // If we have sourceMaps convert them to inline sourceMaps + if (this.options.sourceMap) { + var sourceMapFilename = FILENAME_TS.replace(/ts$/, 'js.map'); + var sourceMapFile = output.outputFiles.filter((file) => file.name === sourceMapFilename)[0]; + + // Transform sourcemap + var sourceMapText = sourceMapFile.text; + sourceMapText = this.getInlineSourceMap(sourceMapText, filename); + + var base64SourceMapText = new Buffer(sourceMapText).toString('base64'); + text = text.replace('//# sourceMappingURL=' + sourceMapFilename, '//# sourceMappingURL=data:application/json;base64,' + base64SourceMapText); + } + + return text; } var allDiagnostics = service.getCompilerOptionsDiagnostics() - .concat(service.getSyntacticDiagnostics(FILENAME_TS)) - .concat(service.getSemanticDiagnostics(FILENAME_TS)); + .concat(service.getSyntacticDiagnostics(FILENAME_TS)); + + if (this.doSemanticChecks) + allDiagnostics = allDiagnostics.concat(service.getSemanticDiagnostics(FILENAME_TS)); + throw new Error(this.formatDiagnostics(allDiagnostics)); } diff --git a/test/test.js b/test/test.js index f5a5eb8..1d77fd4 100644 --- a/test/test.js +++ b/test/test.js @@ -1,6 +1,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); +var eol = require('os').EOL; var ts = require('typescript'); var tss = require('../'); @@ -10,27 +11,27 @@ describe('typescript-update', function() { context('default target (ES5)', function() { it('compiles correct code', function() { var src = "var x: number = 1;"; - var expected = 'var x = 1;\n'; + var expected = 'var x = 1;' + eol; assert.equal(tss(src), expected); }); it('does not replace CRLF literal', function() { var src = "var x: string = '\\r\\n';"; - var expected = "var x = '\\r\\n';\n"; + var expected = "var x = '\\r\\n';" + eol; assert.equal(tss(src), expected); }); it('compiles many times', function() { var src = "var x: number = 1;"; - var expected = 'var x = 1;\n'; + var expected = 'var x = 1;' + eol; assert.equal(tss(src), expected); src = "var y: number = 2;"; - expected = 'var y = 2;\n'; + expected = 'var y = 2;' + eol; assert.equal(tss(src), expected); src = "var z: number = 3;"; - expected = 'var z = 3;\n'; + expected = 'var z = 3;' + eol; assert.equal(tss(src), expected); }); @@ -64,14 +65,50 @@ describe('typescript-update', function() { it('compiles ES6 "let"', function() { var src = "let x: number = 1;"; - var expected = 'let x = 1;\n'; + var expected = 'let x = 1;' + eol; assert.equal(tss.compile(src), expected); }); it('compiles ES6 Promise', function() { - var src = "var x = new Promise(function (resolve, reject) {\n});"; - var expected = src + '\n'; + var src = "var x = new Promise(function (resolve, reject) {" + eol + "});"; + var expected = src + eol; + assert.equal(tss.compile(src), expected); + }); + }); + + context('semantic vs. syntactic errors', function() { + var tss; + beforeEach(function() { + tss = new TypeScriptSimple({target: ts.ScriptTarget.ES5}, false); + }); + + it('semantic errors are ignored', function() { + var src = "var x: number = 'some string';"; + var expected = "var x = 'some string';" + eol; assert.equal(tss.compile(src), expected); }); + + it('syntactic errors are not ignored', function() { + var src = "var x = 123 123;"; + assert.throws(function() { + tss.compile(src); + }, /^Error: L1: ',' expected./); + }); + }); + + context('sourceMaps', function() { + var tss; + beforeEach(function() { + tss = new TypeScriptSimple({target: ts.ScriptTarget.ES5, sourceMap: true}, false); + }); + + it('sourceMap:true should result in inline sourceMaps', function() { + var src = 'var x = "test";'; + var srcFile = 'foo/test.ts'; + var expected = + 'var x = "test";' + eol + + '//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9vL3Rlc3QudHMiLCJzb3VyY2VzIjpbImZvby90ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLElBQUksQ0FBQyxHQUFHLE1BQU0sQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbInZhciB4ID0gXCJ0ZXN0XCI7Il19'; + assert.equal(tss.compile(src, srcFile), expected); + }); }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..18529a2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "version": "1.4.1", + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "declaration": true, + "noImplicitAny": true, + "removeComments": false, + "noLib": false + }, + "filesGlob": [ + "./**/*.ts", + "!./node_modules/**/*.ts" + ], + "files": [ + "./index.d.ts", + "./index.ts", + "./typings/bundle.d.ts", + "./typings/node/node.d.ts" + ] +} \ No newline at end of file