diff options
author | Jake Petroules <jake.petroules@petroules.com> | 2014-04-01 11:52:08 -0400 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@digia.com> | 2014-04-25 17:52:56 +0200 |
commit | 1dab72cfbf45ec6a47697d92fcad9f1b77a3e205 (patch) | |
tree | 39067ed2190262d25b92a6d0294c0cf2e2e43957 | |
parent | 7b81ad2adb32fe5d5b233bbc2bda8948224d778a (diff) |
Add support for building and running TypeScript apps through Node.js.
Change-Id: I13f4d1e7d994cc5c52a0a0d80e1db1de0c710376
Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
-rw-r--r-- | doc/reference/modules/typescript-module.qdoc | 210 | ||||
-rw-r--r-- | share/qbs/modules/typescript/TypeScriptModule.qbs | 267 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/typescript/animals.ts | 21 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/typescript/extra.js | 3 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/typescript/foo.ts | 5 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/typescript/main.ts | 19 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata/typescript/typescript.qbs | 34 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.cpp | 19 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackbox.h | 1 |
9 files changed, 579 insertions, 0 deletions
diff --git a/doc/reference/modules/typescript-module.qdoc b/doc/reference/modules/typescript-module.qdoc new file mode 100644 index 000000000..b7a969a93 --- /dev/null +++ b/doc/reference/modules/typescript-module.qdoc @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Petroules Corporation. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Build Suite. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +/*! + \contentspage index.html + \page typescript-module.html + \ingroup list-of-modules + + \title Module typescript + \brief Provides TypeScript support. + + The \c typescript module contains properties and rules for building + \l{http://www.typescriptlang.org}{TypeScript} applications and may be used in combination with + the \l {Module nodejs} {nodejs} module to run TypeScript applications directly from \QBS. + + \section1 General Properties + + + \section2 warningLevel + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Allowed Values:} \li \c{"normal"}, \c{"pedantic"} + \row \li \b{Default:} \li \c{"normal"} + \endtable + + Severity of warnings to emit. The higher the level, the more warnings will be shown. + \c{pedantic} causes the TypeScript to emit warnings on expressions and declarations with an + implied 'any' type. + + \section2 targetVersion + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Allowed Values:} \li \c{"ES3"}, \c{"ES5"} + \row \li \b{Default:} \li \c{undefined} + \endtable + + ECMAScript target version for generated JavaScript code. \c{undefined} uses the TypeScript + compiler default, which is currently \c{"ES3"}. + + \section2 moduleLoader + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Allowed Values:} \li \c{"commonjs"}, \c{"amd"} + \row \li \b{Default:} \li \c{undefined} + \endtable + + If TypeScript modules are being used, the JavaScript module loading mechanism to use in the + generated JavaScript code. \c{undefined} indicates modules are not being used. + + \section2 stripComments + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{!qbs.debugInformation} + \endtable + + Whether to remove comments from the generated JavaScript files. + + \section2 generateDeclarations + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{false} + \endtable + + Whether to generate corresponding .d.ts files during compilation; these are TypeScript's + equivalent of header files. + + \section2 generateSourceMaps + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{qbs.debugInformation} + \endtable + + Whether to generate corresponding .map files during compilation. + + \section2 compilerFlags + + \table + \row \li \b{Type:} \li \c{stringList} + \row \li \b{Default:} \li undefined + \endtable + + Additional flags for the TypeScript compiler. + + \section2 singleFile + + \table + \row \li \b{Type:} \li \c{bool} + \row \li \b{Default:} \li \c{false} + \endtable + + Whether to compile all TypeScript source files to a single JavaScript output file. The default + is to compile each TypeScript file to a corresponding JavaScript file. This property is + incompatible with \c{moduleLoader}. + + \section2 version + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li \c{undefined} + \endtable + + The TypeScript version. Consists of four numbers separated by dots, for instance "1.0.0.0". + + \section2 versionMajor + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[0]} + \endtable + + The TypeScript major version. + + \section2 versionMinor + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[1]} + \endtable + + The TypeScript minor version. + + \section2 versionParts + + \table + \row \li \b{Type:} \li \c{list} + \row \li \b{Default:} \li \c{empty} + \endtable + + The TypeScript version as a list. For instance, TypeScript version 1.0 would correspond to a + value of \c[1, 0, 0, 0]. + + \section2 versionPatch + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[2]} + \endtable + + The TypeScript patch level. + + \section2 versionBuild + + \table + \row \li \b{Type:} \li \c{int} + \row \li \b{Default:} \li \c{versionParts[3]} + \endtable + + The fourth TypeScript version number component. + + \section2 toolchainInstallPath + + \table + \row \li \b{Type:} \li \c{path} + \row \li \b{Default:} \li \c{undefined} + \endtable + + TypeScript installation directory. This should not normally need to be changed provided that + \c{tsc} is already available by searching the PATH environment variable. + + \section2 compilerName + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li \c{"tsc"} + \endtable + + Name of the compiler binary. This should not normally need to be changed. + + \section2 compilerPath + + \table + \row \li \b{Type:} \li \c{string} + \row \li \b{Default:} \li \c{compilerName} + \endtable + + Directory where the compiler binary is located. This should not normally need to be changed. +*/ diff --git a/share/qbs/modules/typescript/TypeScriptModule.qbs b/share/qbs/modules/typescript/TypeScriptModule.qbs new file mode 100644 index 000000000..531e5c30b --- /dev/null +++ b/share/qbs/modules/typescript/TypeScriptModule.qbs @@ -0,0 +1,267 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Process + +Module { + Depends { name: "nodejs" } + + additionalProductTypes: ["compiled_typescript"] + + property path toolchainInstallPath + property string version: { + var p = new Process(); + p.exec(compilerPath, ["--version"]); + var match = p.readStdOut().match(/^Version ([0-9]+(\.[0-9]+){1,3})\n$/); + if (match !== null) + return match[1]; + } + + property var versionParts: version ? version.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int versionMajor: versionParts[0] + property int versionMinor: versionParts[1] + property int versionPatch: versionParts[2] + property int versionBuild: versionParts[3] + + property string compilerName: "tsc" + property string compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + + property string warningLevel: "normal" + PropertyOptions { + name: "warningLevel" + description: "pedantic to warn on expressions and declarations with an implied 'any' type" + allowedValues: ["normal", "pedantic"] + } + + property string targetVersion + PropertyOptions { + name: "targetVersion" + description: "ECMAScript target version" + allowedValues: ["ES3", "ES5"] + } + + property string moduleLoader + PropertyOptions { + name: "moduleLoader" + allowedValues: ["commonjs", "amd"] + } + + property bool stripComments: !qbs.debugInformation + PropertyOptions { + name: "stripComments" + description: "whether to remove comments from the generated output" + } + + property bool generateDeclarations: false + PropertyOptions { + name: "generateDeclarations" + description: "whether to generate corresponding .d.ts files during compilation" + } + + // In release mode, nodejs can/should default-enable minification and obfuscation, + // making the source maps useless, so these default settings work out fine + property bool generateSourceMaps: qbs.debugInformation + PropertyOptions { + name: "generateSourceMaps" + description: "whether to generate corresponding .map files during compilation" + } + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional flags for the TypeScript compiler" + } + + property bool singleFile: false + PropertyOptions { + name: "singleFile" + description: "whether to compile all source files to a single output file" + } + + validate: { + var validator = new ModUtils.PropertyValidator("typescript"); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addVersionValidator("version", version, 4, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + validator.addRangeValidator("versionBuild", versionBuild, 0); + validator.validate(); + } + + setupBuildEnvironment: { + if (toolchainInstallPath) { + var v = new ModUtils.EnvironmentVariable("PATH", qbs.pathListSeparator, qbs.hostOS.contains("windows")); + v.prepend(toolchainInstallPath); + v.set(); + } + } + + // TypeScript declaration files + FileTagger { + patterns: ["*.d.ts"] + fileTags: ["typescript_declaration"] + } + + // TypeScript source files + FileTagger { + patterns: ["*.ts"] + fileTags: ["typescript"] + } + + Rule { + id: typescriptCompiler + multiplex: true + inputs: ["typescript"] + usings: ["typescript_declaration"] + + outputArtifacts: { + var artifacts = []; + + if (product.moduleProperty("typescript", "singleFile")) { + var jsTags = ["js", "compiled_typescript"]; + + // We could check + // if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath) + // but since we're compiling to a single file there's no need to state it explicitly + jsTags.push("application_js"); + + var filePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName); + + artifacts.push({fileTags: jsTags, + filePath: FileInfo.joinPaths(".obj", product.targetName, "typescript", filePath + ".js")}); + artifacts.push({condition: product.moduleProperty("typescript", "generateDeclarations"), // ### QBS-412 + fileTags: ["typescript_declaration"], + filePath: filePath + ".d.ts"}); + artifacts.push({condition: product.moduleProperty("typescript", "generateSourceMaps"), // ### QBS-412 + fileTags: ["source_map"], + filePath: filePath + ".js.map"}); + } else { + for (var i = 0; i < inputs.typescript.length; ++i) { + var jsTags = ["js", "compiled_typescript"]; + if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath) + jsTags.push("application_js"); + + var filePath = FileInfo.joinPaths(product.destinationDirectory, FileInfo.baseName(inputs.typescript[i].filePath)); + + artifacts.push({fileTags: jsTags, + filePath: FileInfo.joinPaths(".obj", product.targetName, "typescript", filePath + ".js")}); + artifacts.push({condition: product.moduleProperty("typescript", "generateDeclarations"), // ### QBS-412 + fileTags: ["typescript_declaration"], + filePath: filePath + ".d.ts"}); + artifacts.push({condition: product.moduleProperty("typescript", "generateSourceMaps"), // ### QBS-412 + fileTags: ["source_map"], + filePath: filePath + ".js.map"}); + } + } + + return artifacts; + } + + outputFileTags: { + var fileTags = ["js", "compiled_typescript"]; + if (product.moduleProperty("nodejs", "applicationFile")) + fileTags.push("application_js"); + if (product.moduleProperty("typescript", "generateDeclarations")) + fileTags.push("typescript_declaration"); + if (product.moduleProperty("typescript", "generateSourceMaps")) + fileTags.push("source_map"); + return fileTags; + } + + prepare: { + var i; + var args = []; + + var primaryOutput = outputs.compiled_typescript[0]; + var outDir = FileInfo.joinPaths(product.buildDirectory, product.destinationDirectory); + + if (ModUtils.moduleProperty(product, "warningLevel") === "pedantic") { + args.push("--noImplicitAny"); + } + + var targetVersion = ModUtils.moduleProperty(product, "targetVersion"); + if (targetVersion) { + args.push("--target"); + args.push(targetVersion); + } + + var moduleLoader = ModUtils.moduleProperty(product, "moduleLoader"); + if (moduleLoader) { + if (ModUtils.moduleProperty(product, "singleFile")) { + throw("typescript.singleFile cannot be true when typescript.moduleLoader is set"); + } + + args.push("--module"); + args.push(moduleLoader); + } + + if (ModUtils.moduleProperty(product, "stripComments")) { + args.push("--removeComments"); + } + + if (ModUtils.moduleProperty(product, "generateDeclarations")) { + args.push("--declaration"); + } + + if (ModUtils.moduleProperty(product, "generateSourceMaps")) { + args.push("--sourcemap"); + } + + // User-supplied flags + var flags = ModUtils.moduleProperty(product, "compilerFlags"); + for (i in flags) { + args.push(flags[i]); + } + + args.push("--outDir"); + args.push(outDir); + + if (ModUtils.moduleProperty(product, "singleFile")) { + args.push("--out"); + args.push(primaryOutput.filePath); + } + + if (inputs.typescript_declaration) { + for (i = 0; i < inputs.typescript_declaration.length; ++i) { + args.push(inputs.typescript_declaration[i].filePath); + } + } + + for (i = 0; i < inputs.typescript.length; ++i) { + args.push(inputs.typescript[i].filePath); + } + + var cmd, cmds = []; + + cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); + cmd.description = "compiling " + (ModUtils.moduleProperty(product, "singleFile") + ? FileInfo.fileName(primaryOutput.filePath) + : inputs.typescript.map(function(obj) { return FileInfo.fileName(obj.filePath) }).join(", ")); + cmd.highlight = "compiler"; + cmd.workingDirectory = outDir; + cmds.push(cmd); + + // Move all the compiled TypeScript files to the proper intermediate directory + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.outDir = outDir; + cmd.sourceCode = function() { + for (i = 0; i < outputs.compiled_typescript.length; ++i) { + var fp = outputs.compiled_typescript[i].filePath; + var originalFilePath = FileInfo.joinPaths(outDir, FileInfo.fileName(fp)); + File.copy(originalFilePath, fp); + File.remove(originalFilePath); + } + }; + cmds.push(cmd); + + return cmds; + } + } +} diff --git a/tests/auto/blackbox/testdata/typescript/animals.ts b/tests/auto/blackbox/testdata/typescript/animals.ts new file mode 100644 index 000000000..a33ae5c11 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/animals.ts @@ -0,0 +1,21 @@ +export interface Mammal { + speak(): string; +} + +export class Cat implements Mammal { + public speak() { + return "Meow"; // a cat says meow + } +} + +export class Dog implements Mammal { + public speak() { + return "Woof"; // a dog says woof + } +} + +export class Human implements Mammal { + public speak() { + return "Hello"; + } +} diff --git a/tests/auto/blackbox/testdata/typescript/extra.js b/tests/auto/blackbox/testdata/typescript/extra.js new file mode 100644 index 000000000..5500e4688 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/extra.js @@ -0,0 +1,3 @@ +if (console) { + console.log("This doesn't do anything useful!"); +} diff --git a/tests/auto/blackbox/testdata/typescript/foo.ts b/tests/auto/blackbox/testdata/typescript/foo.ts new file mode 100644 index 000000000..3554d317a --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/foo.ts @@ -0,0 +1,5 @@ +export class Greeter { + public getGreeting(): string { + return "guten Tag!"; + } +} diff --git a/tests/auto/blackbox/testdata/typescript/main.ts b/tests/auto/blackbox/testdata/typescript/main.ts new file mode 100644 index 000000000..c41eebea5 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/main.ts @@ -0,0 +1,19 @@ +import Animals = require("animals"); +import Foo = require("foo"); + +function main() { + var mammals: Animals.Mammal[] = []; + mammals.push(new Animals.Human()); + mammals.push(new Animals.Dog()); + mammals.push(new Animals.Cat()); + + // Make everyone speak + for (var i = 0; i < mammals.length; ++i) { + console.log(mammals[i].speak()); + } + + var greeting: string = (new Foo.Greeter()).getGreeting(); + console.log(greeting); +} + +main(); diff --git a/tests/auto/blackbox/testdata/typescript/typescript.qbs b/tests/auto/blackbox/testdata/typescript/typescript.qbs new file mode 100644 index 000000000..8407d1203 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/typescript.qbs @@ -0,0 +1,34 @@ +import qbs + +Project { + NodeJSApplication { + Depends { name: "typescript" } + Depends { name: "lib" } + + typescript.warningLevel: ["pedantic"] + typescript.generateDeclarations: true + typescript.moduleLoader: "commonjs" + nodejs.applicationFile: "main.ts" + + name: "animals" + + files: [ + "animals.ts", + "extra.js", + "main.ts" + ] + } + + Product { + Depends { name: "typescript" } + + typescript.generateDeclarations: true + typescript.moduleLoader: "commonjs" + + name: "lib" + + files: [ + "foo.ts" + ] + } +} diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index c980896db..dab91a2e0 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -2020,4 +2020,23 @@ void TestBlackbox::testNodeJs() QVERIFY(QFile::exists(buildDir + "/hello.js")); } +void TestBlackbox::testTypeScript() +{ + if (!haveNodeJs()) { + SKIP_TEST("node.js is not installed"); + return; + } + + QDir::setCurrent(testDataDir + QLatin1String("/typescript")); + + QbsRunParameters params; + params.command = QLatin1String("run"); + params.arguments = QStringList() << "-p" << "animals"; + QCOMPARE(runQbs(params), 0); + + QVERIFY(QFile::exists(buildDir + "/animals.js")); + QVERIFY(QFile::exists(buildDir + "/extra.js")); + QVERIFY(QFile::exists(buildDir + "/main.js")); +} + QTEST_MAIN(TestBlackbox) diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 900b9d13e..6bc2e2fe3 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -170,6 +170,7 @@ private slots: void testEmbedInfoPlist(); void testWiX(); void testNodeJs(); + void testTypeScript(); private: QByteArray m_qbsStderr; |