aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Petroules <jake.petroules@petroules.com>2014-04-01 11:52:08 -0400
committerJoerg Bornemann <joerg.bornemann@digia.com>2014-04-25 17:52:56 +0200
commit1dab72cfbf45ec6a47697d92fcad9f1b77a3e205 (patch)
tree39067ed2190262d25b92a6d0294c0cf2e2e43957
parent7b81ad2adb32fe5d5b233bbc2bda8948224d778a (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.qdoc210
-rw-r--r--share/qbs/modules/typescript/TypeScriptModule.qbs267
-rw-r--r--tests/auto/blackbox/testdata/typescript/animals.ts21
-rw-r--r--tests/auto/blackbox/testdata/typescript/extra.js3
-rw-r--r--tests/auto/blackbox/testdata/typescript/foo.ts5
-rw-r--r--tests/auto/blackbox/testdata/typescript/main.ts19
-rw-r--r--tests/auto/blackbox/testdata/typescript/typescript.qbs34
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp19
-rw-r--r--tests/auto/blackbox/tst_blackbox.h1
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;