自分用メモ
ここでは、TypeScriptのソースをダウンロードしてきて、
cd src/compiler
tsc tsc.ts
でコンパイルして出力されるJavascriptのソースを読んでいます。
やりたい事
自分の書いているJavaScriptのコードから、TypeScriptCompilerを呼び出してコンパイルしたい。
参照しているファイルとかは考えないで、1ファイルだけコンパイルする関数を書くのが最終的な目標。
結果だけみたい人は「まとめ」まで飛べばオッケー。
TypeScriptCompilerのインスタンスの作成
まずは、メソッドBatchCompiler.prototype.compileでTypeScriptCompilerのインスタンスが作成されている行を見てみる。
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, localizedDiagnosticMessages);
引数の
・logger
・this.compilationSettings
・localizedDiagnosticMessages
が何を渡しているのか探してみる。
logger
まず、loggerから見ていくと、次の行がみつかる。
var logger = this.compilationSettings.gatherDiagnostics ? new DiagnosticsLogger(this.ioHost) : new TypeScript.NullLogger();
最悪、TypeScriptNullLoggerというのを使えば良さそうだ。
一応、NullLoggerの定義をみておく。
var NullLogger = (function () {
function NullLogger() {
}
NullLogger.prototype.information = function () {
return false;
};
NullLogger.prototype.debug = function () {
return false;
};
NullLogger.prototype.warning = function () {
return false;
};
NullLogger.prototype.error = function () {
return false;
};
NullLogger.prototype.fatal = function () {
return false;
};
NullLogger.prototype.log = function (s) {
};
return NullLogger;
})();
TypeScript.NullLogger = NullLogger;
これ、何もしないというか、ログ吐かないのか…。
シグネチャは
information = function ()
debug = function ()
warning = function ()
error = function ()
fatal = function ()
log = function (s)
実際に仕事するのは、log(s)かな…。
this.compilationSettings
BatchCompilerの宣言のところに、次のようなところがある。
var BatchCompiler = (function () {
function BatchCompiler(ioHost) {
this.ioHost = ioHost;
this.resolvedEnvironment = null;
this.hasResolveErrors = false;
this.compilerVersion = "0.9.0.1";
this.printedVersion = false;
this.errorReporter = null;
this.compilationSettings = new TypeScript.CompilationSettings();
this.compilationEnvironment = new TypeScript.CompilationEnvironment(this.compilationSettings, this.ioHost);
this.errorReporter = new ErrorReporter(this.ioHost, this.compilationEnvironment);
}
this.compilationSettingsもここで作成されているが、その他あとで出てきそうな変数があるので、とりあえず、全部のっけておく。
TypeScript.CompilationSettingsというクラスがあるらしいことだけ覚えておこう…。
localizedDiagnosticMessages
localizedDiagnosticMessagesについては、次の行が参考になりそう。
if (typeof localizedDiagnosticMessages === "undefined") {
localizedDiagnosticMessages = null;
}
localizedDiagnosticMessagesの型が"undefined"だったら、nullを代入している。
つまり、よくわからなかったら、nullを渡しておけば良さそう。
コンパイル
実際のコンパイルの手順をみていく。
for (var iCode = 0; iCode < this.resolvedEnvironment.code.length; iCode++) {
var code = this.resolvedEnvironment.code[iCode];
if (!this.compilationSettings.resolve) {
try {
code.fileInformation = this.ioHost.readFile(code.path);
} catch (e) {
(略)
}
if (this.compilationSettings.generateDeclarationFiles) {
(略)
}
}
if (code.fileInformation != null) {
compiler.addSourceUnit(
code.path,
TypeScript.ScriptSnapshot.fromString(code.fileInformation.contents()),
code.fileInformation.byteOrderMark(),
0,
false,
code.referencedFiles
);
(略)
}
}
前後のソースも含めてざっと眺めた感じでは、関連するソースファイルをすべて読み込んでから、
コンパイルしているような気がする。
で、上記のコードでは、
this.resolvedEnvironment.code
にソースファイルの情報が含まれている…んじゃないかな。
前述のBatchCompilerの宣言部分では、
this.resolvedEnvironment = null;
nullが代入されていたので、それがいつソースファイルの情報を含むようになるのか調べてみよう。
BatchCompiler.prototype.batchCompile
調べていくと、メソッドBatchCompiler.prototype.batchCompileの中で
var onWatchedFileChange = function () {
_this.compilationEnvironment.code = sourceFiles;
_this.errorReporter.reset();
_this.resolvedEnvironment = _this.compilationSettings.resolve
? _this.resolve() : _this.compilationEnvironment;
のように代入されている。
みてのとおり、_this.resolve()の返り値か、_this.compilationEnvironmentが代入されている。
どっちか、読みやすいほうを参考にすれば良さそう。
compilationEnvironment
compilationEnvironmentは、前に出た
var BatchCompiler = (function () {
function BatchCompiler(ioHost) {
this.ioHost = ioHost;
…
this.compilationSettings = new TypeScript.CompilationSettings();
this.compilationEnvironment = new TypeScript.CompilationEnvironment(this.compilationSettings, this.ioHost);
…
}
のところで、代入されているのかな。ここでは、
new TypeScript.CompilationEnvironment(this.compilationSettings, this.ioHost);
が代入されていて、引数this.compilationSettings、this.ioHostともすぐ上で値が代入されている。
となると、クラスCompilationSettingsとCompilationEnvironmentを調べる必要があるかな…。
CompilationSettings
precompile.jsに以下の記述がある。
var CompilationSettings = (function () {
function CompilationSettings() {
this.propagateConstants = false;
this.minWhitespace = false;
this.emitComments = false;
this.watch = false;
this.exec = false;
this.resolve = true;
this.disallowBool = false;
this.allowAutomaticSemicolonInsertion = true;
this.allowModuleKeywordInExternalModuleReference = true;
this.useDefaultLib = true;
this.codeGenTarget = TypeScript.LanguageVersion.EcmaScript3;
this.moduleGenTarget = TypeScript.ModuleGenTarget.Synchronous;
this.outputOption = "";
this.mapSourceFiles = false;
this.emitFullSourceMapPath = false;
this.generateDeclarationFiles = false;
this.useCaseSensitiveFileResolution = false;
this.gatherDiagnostics = false;
this.updateTC = false;
this.implicitAny = false;
}
いろいろ書いてあるけど、このまま使えそうな気がする。
CompilationEnvironment
referenceResolutin.jsに以下の記述がある。
var CompilationEnvironment = (function () {
function CompilationEnvironment(compilationSettings, ioHost) {
this.compilationSettings = compilationSettings;
this.ioHost = ioHost;
this.code = [];
this.inputFileNameToOutputFileName = new TypeScript.StringHashTable();
}
ここの、 this.code にソースファイル名が格納されてるのかな??
違うな。ソースファイル名じゃない。別のところで、
code.path
code.fileInformation
などが参照されているから、なんらかの構造を持ったものだな…。
ずるして、referenceResolution. ts を見ると、
public code: SourceUnit[] = [];
だから、SourceUnitクラスの定義を探そう。
SourceUnit
var SourceUnit = (function () {
function SourceUnit(path, fileInformation) {
this.path = path;
this.fileInformation = fileInformation;
this.referencedFiles = null;
this.lineStarts = null;
}
SourceUnit.prototype.getText = function (start, end) {
return this.fileInformation.contents().substring(start, end);
};
SourceUnit.prototype.getLength = function () {
return this.fileInformation.contents().length;
};
SourceUnit.prototype.getLineStartPositions = function () {
if (this.lineStarts === null) {
this.lineStarts = TypeScript.LineMap.fromString(this.fileInformation.contents()).lineStarts();
}
return this.lineStarts;
};
SourceUnit.prototype.getTextChangeRangeSinceVersion = function (scriptVersion) {
throw TypeScript.Errors.notYetImplemented();
};
return SourceUnit;
})();
TypeScript.SourceUnit = SourceUnit;
これはこのまま使えばいいのかな…。
ところで、FileInformationが何かを調べると、
enum ByteOrderMark {
None,
Utf8,
Utf16BigEndian,
Utf16LittleEndian,
}
class FileInformation {
private _contents: string;
private _byteOrderMark: ByteOrderMark;
constructor(contents: string, byteOrderMark: ByteOrderMark) {
this._contents = contents;
this._byteOrderMark = byteOrderMark;
}
public contents(): string {
return this._contents;
}
public byteOrderMark(): ByteOrderMark {
return this._byteOrderMark;
}
}
テキストとバイトオーダーが入ってるようだ。
StringHashTable
CompilationEnvironmentの中で、よくわからないのがTypeScript.StringHashTable。
メンバー名からして、おそらく、入力ファイルと出力ファイルを紐つけたハッシュテーブルだろうけど…。
一応、ファイルをのぞいておく。
var StringHashTable = (function () {
function StringHashTable() {
this.itemCount = 0;
this.table = (new BlockIntrinsics());
}
StringHashTable.prototype.getAllKeys = function () {
var result = [];
for (var k in this.table) {
if (this.table[k] !== undefined) {
result.push(k);
}
}
return result;
};
StringHashTable.prototype.add = function (key, data) {
if (this.table[key] !== undefined) {
return false;
}
this.table[key] = data;
this.itemCount++;
return true;
};
StringHashTable.prototype.addOrUpdate = function (key, data) {
if (this.table[key] !== undefined) {
this.table[key] = data;
return false;
}
this.table[key] = data;
this.itemCount++;
return true;
};
StringHashTable.prototype.map = function (fn, context) {
for (var k in this.table) {
var data = this.table[k];
if (data !== undefined) {
fn(k, this.table[k], context);
}
}
};
StringHashTable.prototype.every = function (fn, context) {
for (var k in this.table) {
var data = this.table[k];
if (data !== undefined) {
if (!fn(k, this.table[k], context)) {
return false;
}
}
}
return true;
};
StringHashTable.prototype.some = function (fn, context) {
for (var k in this.table) {
var data = this.table[k];
if (data !== undefined) {
if (fn(k, this.table[k], context)) {
return true;
}
}
}
return false;
};
StringHashTable.prototype.count = function () {
return this.itemCount;
};
StringHashTable.prototype.lookup = function (key) {
var data = this.table[key];
return data === undefined ? null : data;
};
いくつかメソッドも定義されているので、このまま使ったほうが良さそう。
ioHost
ここまで見てきて、解決していないのが"ioHost"とかいうやつ。
ソースをざっと眺めた感じでは、io.jsで定義されている
var IO = (function () {
あたりかな。
で、ActiveX関係を使う場合が、
function getWindowsScriptHostIO() {
で、Node.jsの機能を使う場合が
function getNodeIO() {
なんだろう(たぶん)。
で、どちらか意識しないでいいように、var IO の定義の最後で、
if (typeof ActiveXObject === "function")
return getWindowsScriptHostIO(); else if (typeof module !== 'undefined' && module.exports)
return getNodeIO(); else
return null;
})();
のように返してるわけか…。
つまり、IO を使えばよきにはからってくれるのかな…。
いちおう、後で自分で書くときのためにシグネチャをメモしとくか…。
readFile: function (file)
writeFile: function (path, contents, writeByteOrderMark)
deleteFile: function (path)
fileExists: function (path)
dir: function dir(path, spec, options)
createDirectory: function (path)
directoryExists: function (path)
resolvePath: function (path)
dirName: function (path)
findFile: function (rootPath, partialFilePath)
print: function (str)
printLine: function (str)
arguments: process.argv.slice(2)
stderr: {
Write: function (str)
WriteLine: function (str)
Close: function ()
}
stdout: {
Write: function (str)
WriteLine: function (str)
Close: function ()
}
watchFile: function (fileName, callback)
run: function (source, fileName)
getExecutingFilePath: function ()
quit:
あ。結構、めんどい…。
コンパイルするソースの設定
さて、これだけの情報があればコンパイルするコードを書けるかな…?
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, localizedDiagnosticMessages);
まずは、このコードを出発点とする。
localizedDiagnosticMessagesは、nullで構わない。
また、loggerはNullLoggerを使っておく。
var logger = new TypeScript.NullLoger();
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, null);
ひとまず、こうなる。
あとは、実際にコンパイルすることを念頭に考えていく。
BatchCompilerを参考にすると、必要な変数の定義は以下のようになる。
var ioHost = IO;
var compilationSettings = new TypeScript.CompilationSettings();
var compilationEnvironment = new TypeScript.CompilationEnvironment(compilationSettings, ioHost);
tsc.jsでは、必要なファイルをリストアップしてからコンパイルしていたけど、
ここでは、1つのファイルだけコンパイル出来れば十分なので、以下のようにソースファイルを設定する。
var path = "source.file.js";
var fileInformation = ioHost.readFile(path);
var code = new TypeScript.SourceUnit(path, fileInformation);
compiler.addSourceUnit(
code.path,
TypeScript.ScriptSnapshot.fromString(code.fileInformation.contents()),
code.fileInformation.byteOrderMark(),
0,
false,
code.referencedFiles);
これで、ソースファイルをcompilerに結びつけるとこは出来たかな。
コンパイル関連のメソッドの呼び出し
次は実際にコンパイルするあたり。
やはり、BatchCompilerを参考にして考えていく。
構文チェック
最初に構文チェックするみたい。
// シンタックスエラーのチェック?
var syntacticDiagnostics = compiler.getSyntacticDiagnostics(code.path);
compiler.reportDiagnostics(syntacticDiagnostics, this.errorReporter);
var anySintacticErros = false;
if (syntacticDiagnostics.length > 0) {
anySyntacticErrors = true; // シンタックスエラー有り
}
if (anySyntacticErrors) {
return true; // エラーで終了
}
どうでもいいけど、エラーが起きたときに真を返すんだ…<BatchCompiler
型チェックと意味解析
次が型のチェックと意味解析っぽい。
compiler.pullTypeCheck();
var fileNames = compiler.fileNameToDocument.getAllKeys();
for (var i = 0, n = fileNames.length; i < n; i++) {
var fileName = fileNames[i];
var semanticDiagnostics = compiler.getSemanticDiagnostics(fileName);
if (semanticDiagnostics.length > 0) {
anySemanticErrors = true;
compiler.reportDiagnostics(semanticDiagnostics, this.errorReporter);
}
}
コンパイル結果の書出し
あとはコンパイルしてファイルに書き出すのかな。
// ファイル書出し用オブジェクト
var emitterIOHost = {
writeFile: function (fileName, contents, writeByteOrderMark) {
return IOUtils.writeFileAndFolderStructure(_this.ioHost, fileName, contents, writeByteOrderMark);
},
directoryExists: this.ioHost.directoryExists,
fileExists: this.ioHost.fileExists,
resolvePath: this.ioHost.resolvePath
};
// よくわからない何か(ソースファイル名と出力ファイル名がどうしたこうした?)
var mapInputToOutput = function (inputFile, outputFile) {
_this.resolvedEnvironment.inputFileNameToOutputFileName.addOrUpdate(inputFile, outputFile);
};
// 発行(ファイルに書出し?)
var emitDiagnostics = compiler.emitAll(emitterIOHost, mapInputToOutput);
compiler.reportDiagnostics(emitDiagnostics, this.errorReporter);
if (emitDiagnostics.length > 0) {
return true; // エラーで終了
}
if (anySemanticErrors) {
return true; // エラーで終了
}
// 発行(ファイルに書出し?) その2
var emitDeclarationsDiagnostics = compiler.emitAllDeclarations();
compiler.reportDiagnostics(emitDeclarationsDiagnostics, this.errorReporter);
if (emitDeclarationsDiagnostics.length > 0) {
return true; // エラーで終了
}
return false; // 無事コンパイル完了
解析からコンパイルまでは、ほぼこのまま使えそうかな…。
まとめ
ここまでをまとめて、1つのコードに書いてみる。
var ByteOrderMark;
(function (ByteOrderMark) {
ByteOrderMark[ByteOrderMark["None"] = 0] = "None";
ByteOrderMark[ByteOrderMark["Utf8"] = 1] = "Utf8";
ByteOrderMark[ByteOrderMark["Utf16BigEndian"] = 2] = "Utf16BigEndian";
ByteOrderMark[ByteOrderMark["Utf16LittleEndian"] = 3] = "Utf16LittleEndian";
})(ByteOrderMark || (ByteOrderMark = {}));
var FileInformation = (function () {
function FileInformation(contents, byteOrderMark) {
this._contents = contents;
this._byteOrderMark = byteOrderMark;
}
FileInformation.prototype.contents = function () {
return this._contents;
};
FileInformation.prototype.byteOrderMark = function () {
return this._byteOrderMark;
};
return FileInformation;
})();
var Environment = (function () {
function getWindowsScriptHostEnvironment() {
try {
var fso = new ActiveXObject("Scripting.FileSystemObject");
} catch (e) {
return null;
}
var streamObjectPool = [];
function getStreamObject() {
if (streamObjectPool.length > 0) {
return streamObjectPool.pop();
} else {
return new ActiveXObject("ADODB.Stream");
}
}
function releaseStreamObject(obj) {
streamObjectPool.push(obj);
}
var args = [];
for (var i = 0; i < WScript.Arguments.length; i++) {
args[i] = WScript.Arguments.Item(i);
}
return {
currentDirectory: function () {
return (WScript).CreateObject("WScript.Shell").CurrentDirectory;
},
readFile: function (path) {
try {
var streamObj = getStreamObject();
streamObj.Open();
streamObj.Type = 2;
streamObj.Charset = 'x-ansi';
streamObj.LoadFromFile(path);
var bomChar = streamObj.ReadText(2);
streamObj.Position = 0;
var byteOrderMark = ByteOrderMark.None;
if (bomChar.charCodeAt(0) === 0xFE && bomChar.charCodeAt(1) === 0xFF) {
streamObj.Charset = 'unicode';
byteOrderMark = ByteOrderMark.Utf16BigEndian;
} else if (bomChar.charCodeAt(0) === 0xFF && bomChar.charCodeAt(1) === 0xFE) {
streamObj.Charset = 'unicode';
byteOrderMark = ByteOrderMark.Utf16LittleEndian;
} else if (bomChar.charCodeAt(0) === 0xEF && bomChar.charCodeAt(1) === 0xBB) {
streamObj.Charset = 'utf-8';
byteOrderMark = ByteOrderMark.Utf8;
} else {
streamObj.Charset = 'utf-8';
}
var contents = streamObj.ReadText(-1);
streamObj.Close();
releaseStreamObject(streamObj);
return new FileInformation(contents, byteOrderMark);
} catch (err) {
var error = new Error(err.message);
var message;
if (err.number === -2147024809) {
error.isUnsupportedEncoding = true;
}
throw error;
}
},
writeFile: function (path, contents, writeByteOrderMark) {
var textStream = getStreamObject();
textStream.Charset = 'utf-8';
textStream.Open();
textStream.WriteText(contents, 0);
if (!writeByteOrderMark) {
textStream.Position = 3;
} else {
textStream.Position = 0;
}
var fileStream = getStreamObject();
fileStream.Type = 1;
fileStream.Open();
textStream.CopyTo(fileStream);
fileStream.Flush();
fileStream.SaveToFile(path, 2);
fileStream.Close();
textStream.Flush();
textStream.Close();
},
fileExists: function (path) {
return fso.FileExists(path);
},
deleteFile: function (path) {
if (fso.FileExists(path)) {
fso.DeleteFile(path, true);
}
},
directoryExists: function (path) {
return fso.FolderExists(path);
},
listFiles: function (path, spec, options) {
options = options || {};
function filesInFolder(folder, root) {
var paths = [];
var fc;
if (options.recursive) {
fc = new Enumerator(folder.subfolders);
for (; !fc.atEnd(); fc.moveNext()) {
paths = paths.concat(filesInFolder(fc.item(), root + "\\" + fc.item().Name));
}
}
fc = new Enumerator(folder.files);
for (; !fc.atEnd(); fc.moveNext()) {
if (!spec || fc.item().Name.match(spec)) {
paths.push(root + "\\" + fc.item().Name);
}
}
return paths;
}
var folder = fso.GetFolder(path);
var paths = [];
return filesInFolder(folder, path);
},
arguments: args,
standardOut: WScript.StdOut
};
}
;
function getNodeEnvironment() {
var _fs = require('fs');
var _path = require('path');
var _module = require('module');
return {
currentDirectory: function () {
return (process).cwd();
},
readFile: function (file) {
var buffer = _fs.readFileSync(file);
switch (buffer[0]) {
case 0xFE:
if (buffer[1] === 0xFF) {
var i = 0;
while ((i + 1) < buffer.length) {
var temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
i += 2;
}
return new FileInformation(buffer.toString("ucs2", 2), ByteOrderMark.Utf16BigEndian);
}
break;
case 0xFF:
if (buffer[1] === 0xFE) {
return new FileInformation(buffer.toString("ucs2", 2), ByteOrderMark.Utf16LittleEndian);
}
break;
case 0xEF:
if (buffer[1] === 0xBB) {
return new FileInformation(buffer.toString("utf8", 3), ByteOrderMark.Utf8);
}
}
return new FileInformation(buffer.toString("utf8", 0), ByteOrderMark.None);
},
writeFile: function (path, contents, writeByteOrderMark) {
function mkdirRecursiveSync(path) {
var stats = _fs.statSync(path);
if (stats.isFile()) {
throw "\"" + path + "\" exists but isn't a directory.";
} else if (stats.isDirectory()) {
return;
} else {
mkdirRecursiveSync(_path.dirname(path));
_fs.mkdirSync(path, 0775);
}
}
mkdirRecursiveSync(_path.dirname(path));
if (writeByteOrderMark) {
contents = '\uFEFF' + contents;
}
_fs.writeFileSync(path, contents, "utf8");
},
fileExists: function (path) {
return _fs.existsSync(path);
},
deleteFile: function (path) {
try {
_fs.unlinkSync(path);
} catch (e) {
}
},
directoryExists: function (path) {
return _fs.existsSync(path) && _fs.statSync(path).isDirectory();
},
listFiles: function dir(path, spec, options) {
options = options || {};
function filesInFolder(folder) {
var paths = [];
var files = _fs.readdirSync(folder);
for (var i = 0; i < files.length; i++) {
var stat = _fs.statSync(folder + "\\" + files[i]);
if (options.recursive && stat.isDirectory()) {
paths = paths.concat(filesInFolder(folder + "\\" + files[i]));
} else if (stat.isFile() && (!spec || files[i].match(spec))) {
paths.push(folder + "\\" + files[i]);
}
}
return paths;
}
return filesInFolder(path);
},
arguments: process.argv.slice(2),
standardOut: {
Write: function (str) {
process.stdout.write(str);
},
WriteLine: function (str) {
process.stdout.write(str + '\n');
},
Close: function () {
}
}
};
}
;
if (typeof WScript !== "undefined" && typeof ActiveXObject === "function") {
return getWindowsScriptHostEnvironment();
} else if (typeof module !== 'undefined' && module.exports) {
return getNodeEnvironment();
} else {
return null;
}
})();
var IOUtils;
(function (IOUtils) {
function createDirectoryStructure(ioHost, dirName) {
if (ioHost.directoryExists(dirName)) {
return;
}
var parentDirectory = ioHost.dirName(dirName);
if (parentDirectory != "") {
createDirectoryStructure(ioHost, parentDirectory);
}
ioHost.createDirectory(dirName);
}
function writeFileAndFolderStructure(ioHost, fileName, contents, writeByteOrderMark) {
var path = ioHost.resolvePath(fileName);
var dirName = ioHost.dirName(path);
createDirectoryStructure(ioHost, dirName);
return ioHost.writeFile(path, contents, writeByteOrderMark);
}
IOUtils.writeFileAndFolderStructure = writeFileAndFolderStructure;
function throwIOError(message, error) {
var errorMessage = message;
if (error && error.message) {
errorMessage += (" " + error.message);
}
throw new Error(errorMessage);
}
IOUtils.throwIOError = throwIOError;
var BufferedTextWriter = (function () {
function BufferedTextWriter(writer, capacity) {
if (typeof capacity === "undefined") { capacity = 1024; }
this.writer = writer;
this.capacity = capacity;
this.buffer = "";
}
BufferedTextWriter.prototype.Write = function (str) {
this.buffer += str;
if (this.buffer.length >= this.capacity) {
this.writer.Write(this.buffer);
this.buffer = "";
}
};
BufferedTextWriter.prototype.WriteLine = function (str) {
this.Write(str + '\r\n');
};
BufferedTextWriter.prototype.Close = function () {
this.writer.Write(this.buffer);
this.writer.Close();
this.buffer = null;
};
return BufferedTextWriter;
})();
IOUtils.BufferedTextWriter = BufferedTextWriter;
})(IOUtils || (IOUtils = {}));
var IO = (function () {
function getWindowsScriptHostIO() {
var fso = new ActiveXObject("Scripting.FileSystemObject");
var streamObjectPool = [];
function getStreamObject() {
if (streamObjectPool.length > 0) {
return streamObjectPool.pop();
} else {
return new ActiveXObject("ADODB.Stream");
}
}
function releaseStreamObject(obj) {
streamObjectPool.push(obj);
}
var args = [];
for (var i = 0; i < WScript.Arguments.length; i++) {
args[i] = WScript.Arguments.Item(i);
}
return {
readFile: function (path) {
return Environment.readFile(path);
},
writeFile: function (path, contents, writeByteOrderMark) {
Environment.writeFile(path, contents, writeByteOrderMark);
},
fileExists: function (path) {
return fso.FileExists(path);
},
resolvePath: function (path) {
return fso.GetAbsolutePathName(path);
},
dirName: function (path) {
return fso.GetParentFolderName(path);
},
findFile: function (rootPath, partialFilePath) {
var path = fso.GetAbsolutePathName(rootPath) + "/" + partialFilePath;
while (true) {
if (fso.FileExists(path)) {
return { fileInformation: this.readFile(path), path: path };
} else {
rootPath = fso.GetParentFolderName(fso.GetAbsolutePathName(rootPath));
if (rootPath == "") {
return null;
} else {
path = fso.BuildPath(rootPath, partialFilePath);
}
}
}
},
deleteFile: function (path) {
try {
if (fso.FileExists(path)) {
fso.DeleteFile(path, true);
}
} catch (e) {
IOUtils.throwIOError("Couldn't delete file '" + path + "'.", e);
}
},
directoryExists: function (path) {
return fso.FolderExists(path);
},
createDirectory: function (path) {
try {
if (!this.directoryExists(path)) {
fso.CreateFolder(path);
}
} catch (e) {
IOUtils.throwIOError("Couldn't create directory '" + path + "'.", e);
}
},
dir: function (path, spec, options) {
options = options || {};
function filesInFolder(folder, root) {
var paths = [];
var fc;
if (options.recursive) {
fc = new Enumerator(folder.subfolders);
for (; !fc.atEnd(); fc.moveNext()) {
paths = paths.concat(filesInFolder(fc.item(), root + "/" + fc.item().Name));
}
}
fc = new Enumerator(folder.files);
for (; !fc.atEnd(); fc.moveNext()) {
if (!spec || fc.item().Name.match(spec)) {
paths.push(root + "/" + fc.item().Name);
}
}
return paths;
}
var folder = fso.GetFolder(path);
var paths = [];
return filesInFolder(folder, path);
},
print: function (str) {
WScript.StdOut.Write(str);
},
printLine: function (str) {
WScript.Echo(str);
},
arguments: args,
stderr: WScript.StdErr,
stdout: WScript.StdOut,
watchFile: null,
run: function (source, fileName) {
try {
eval(source);
} catch (e) {
IOUtils.throwIOError("Error while executing file '" + fileName + "'.", e);
}
},
getExecutingFilePath: function () {
return WScript.ScriptFullName;
},
quit: function (exitCode) {
if (typeof exitCode === "undefined") { exitCode = 0; }
try {
WScript.Quit(exitCode);
} catch (e) {
}
}
};
}
;
function getNodeIO() {
var _fs = require('fs');
var _path = require('path');
var _module = require('module');
return {
readFile: function (file) {
return Environment.readFile(file);
},
writeFile: function (path, contents, writeByteOrderMark) {
Environment.writeFile(path, contents, writeByteOrderMark);
},
deleteFile: function (path) {
try {
_fs.unlinkSync(path);
} catch (e) {
IOUtils.throwIOError("Couldn't delete file '" + path + "'.", e);
}
},
fileExists: function (path) {
return _fs.existsSync(path);
},
dir: function dir(path, spec, options) {
options = options || {};
function filesInFolder(folder) {
var paths = [];
try {
var files = _fs.readdirSync(folder);
for (var i = 0; i < files.length; i++) {
var stat = _fs.statSync(folder + "/" + files[i]);
if (options.recursive && stat.isDirectory()) {
paths = paths.concat(filesInFolder(folder + "/" + files[i]));
} else if (stat.isFile() && (!spec || files[i].match(spec))) {
paths.push(folder + "/" + files[i]);
}
}
} catch (err) {
}
return paths;
}
return filesInFolder(path);
},
createDirectory: function (path) {
try {
if (!this.directoryExists(path)) {
_fs.mkdirSync(path);
}
} catch (e) {
IOUtils.throwIOError("Couldn't create directory '" + path + "'.", e);
}
},
directoryExists: function (path) {
return _fs.existsSync(path) && _fs.statSync(path).isDirectory();
},
resolvePath: function (path) {
return _path.resolve(path);
},
dirName: function (path) {
return _path.dirname(path);
},
findFile: function (rootPath, partialFilePath) {
var path = rootPath + "/" + partialFilePath;
while (true) {
if (_fs.existsSync(path)) {
return { fileInformation: this.readFile(path), path: path };
} else {
var parentPath = _path.resolve(rootPath, "..");
if (rootPath === parentPath) {
return null;
} else {
rootPath = parentPath;
path = _path.resolve(rootPath, partialFilePath);
}
}
}
},
print: function (str) {
process.stdout.write(str);
},
printLine: function (str) {
process.stdout.write(str + '\n');
},
arguments: process.argv.slice(2),
stderr: {
Write: function (str) {
process.stderr.write(str);
},
WriteLine: function (str) {
process.stderr.write(str + '\n');
},
Close: function () {
}
},
stdout: {
Write: function (str) {
process.stdout.write(str);
},
WriteLine: function (str) {
process.stdout.write(str + '\n');
},
Close: function () {
}
},
watchFile: function (fileName, callback) {
var firstRun = true;
var processingChange = false;
var fileChanged = function (curr, prev) {
if (!firstRun) {
if (curr.mtime < prev.mtime) {
return;
}
_fs.unwatchFile(fileName, fileChanged);
if (!processingChange) {
processingChange = true;
callback(fileName);
setTimeout(function () {
processingChange = false;
}, 100);
}
}
firstRun = false;
_fs.watchFile(fileName, { persistent: true, interval: 500 }, fileChanged);
};
fileChanged();
return {
fileName: fileName,
close: function () {
_fs.unwatchFile(fileName, fileChanged);
}
};
},
run: function (source, fileName) {
require.main.fileName = fileName;
require.main.paths = _module._nodeModulePaths(_path.dirname(_fs.realpathSync(fileName)));
require.main._compile(source, fileName);
},
getExecutingFilePath: function () {
return process.mainModule.filename;
},
quit: process.exit
};
}
;
if (typeof ActiveXObject === "function")
return getWindowsScriptHostIO(); else if (typeof module !== 'undefined' && module.exports)
return getNodeIO(); else
return null;
})();
function compile(logger, sourcefilename){
//
// テンポラリディレクトリにtypescript.jsが無いときには、node_modules/から読み込む。
//
var fs = require("fs"),
path = require("path");
var typescript_filename = process.env.TMPDIR + "typescript.js";
if( ! fs.existsSync(typescript_filename)){
var code = [
fs.readFileSync("/usr/local/lib/node_modules/typescript/bin/typescript.js"), // <-各自の環境に合わせてパスを設定すること。
"exports.TypeScript = TypeScript;"
].join("");
fs.writeFileSync(typescript_filename, code);
}
//
// この辺から、コンパイル本編
//
var TypeScript = require(typescript_filename).TypeScript;
var _this = this;
var ErrorReporter = (function() {
function ErrorReporter(ioHost, compilationEnvironment) {
this.ioHost = ioHost;
this.hasErrors = false;
this.setCompilationEnvironment(compilationEnvironment);
}
ErrorReporter.prototype.addDiagnostic = function(diagnostic) {
this.hasErrors = true;
if (diagnostic.fileName()) {
var soruceUnit = this.compilationEnvironment.getSourceUnit(diagnostic.fileName());
if (!soruceUnit) {
soruceUnit = new TypeScript.SourceUnit(diagnostic.fileName(), this.ioHost.readFile(diagnostic.fileName()));
}
var lineMap = new TypeScript.LineMap(soruceUnit.getLineStartPositions(), soruceUnit.getLength());
var lineCol = {
line : -1,
character : -1
};
lineMap.fillLineAndCharacterFromPosition(diagnostic.start(), lineCol);
this.ioHost.stderr.Write(diagnostic.fileName() + "(" + (lineCol.line + 1) + "," + (lineCol.character + 1) + "): ");
}
this.ioHost.stderr.WriteLine(diagnostic.message());
};
ErrorReporter.prototype.setCompilationEnvironment = function(compilationEnvironment) {
this.compilationEnvironment = compilationEnvironment;
};
ErrorReporter.prototype.reset = function() {
this.hasErrors = false;
};
return ErrorReporter;
})();
this.ioHost = IO;
this.hasResolveErrors = false;
this.compilerVersion = "0.9.0.1";
this.printedVersion = false;
this.errorReporter = null;
this.compilationSettings = new TypeScript.CompilationSettings();
this.compilationEnvironment = new TypeScript.CompilationEnvironment(compilationSettings, ioHost);
this.resolvedEnvironment = this.compilationEnvironment;
this.errorReporter = new ErrorReporter(this.ioHost, this.compilationEnvironment);
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, null);
//
// ソースファイルの設定
//
var fileInformation = ioHost.readFile(sourcefilename);
var code = new TypeScript.SourceUnit(sourcefilename, fileInformation);
compiler.addSourceUnit(
code.path,
TypeScript.ScriptSnapshot.fromString(code.fileInformation.contents()),
code.fileInformation.byteOrderMark(),
0,
false,
code.referencedFiles);
//
// シンタックスエラーのチェック?
//
var syntacticDiagnostics = compiler.getSyntacticDiagnostics(code.path);
compiler.reportDiagnostics(syntacticDiagnostics, this.errorReporter);
var anySyntacticErrors = false;
var anySemanticErrors = false;
if (syntacticDiagnostics.length > 0) {
anySyntacticErrors = true; // シンタックスエラー有り
}
if (anySyntacticErrors) {
return true; // エラーで終了
}
//
// 型チェックと意味解析
//
compiler.pullTypeCheck();
var fileNames = compiler.fileNameToDocument.getAllKeys();
for (var i = 0, n = fileNames.length; i < n; i++) {
var fileName = fileNames[i];
var semanticDiagnostics = compiler.getSemanticDiagnostics(fileName);
if (semanticDiagnostics.length > 0) {
anySemanticErrors = true;
compiler.reportDiagnostics(semanticDiagnostics, this.errorReporter);
}
}
//
// コンパイル結果の書出し
//
// ファイル書出し用オブジェクト
var emitterIOHost = {
writeFile: function (fileName, contents, writeByteOrderMark) {
return IOUtils.writeFileAndFolderStructure(_this.ioHost, fileName, contents, writeByteOrderMark);
},
directoryExists: this.ioHost.directoryExists,
fileExists: this.ioHost.fileExists,
resolvePath: this.ioHost.resolvePath
};
// よくわからない何か(ソースファイル名と出力ファイル名がどうしたこうした?)
var mapInputToOutput = function (inputFile, outputFile) {
_this.resolvedEnvironment.inputFileNameToOutputFileName.addOrUpdate(inputFile, outputFile);
};
// 発行(ファイルに書出し?)
var emitDiagnostics = compiler.emitAll(emitterIOHost, mapInputToOutput);
compiler.reportDiagnostics(emitDiagnostics, this.errorReporter);
if (emitDiagnostics.length > 0) {
return true; // エラーで終了
}
if (anySemanticErrors) {
return true; // エラーで終了
}
// 発行(ファイルに書出し?) その2
var emitDeclarationsDiagnostics = compiler.emitAllDeclarations();
compiler.reportDiagnostics(emitDeclarationsDiagnostics, this.errorReporter);
if (emitDeclarationsDiagnostics.length > 0) {
return true; // エラーで終了
}
return false; // 無事コンパイル完了
}
// usage:
/*
var logger = {
information: function () {
return false;
},
debug: function () {
return false;
},
warning: function () {
return false;
},
error: function () {
return false;
},
fatal: function () {
return false;
},
log: function (s) {
console.log(s);
}
};
compile(logger, "test.ts");
*/
なんか書いてるうちに長くなった…orz
動作テストをしたら、おしまい。
コンパイルテスト用ファイルも一応。
class Human {
name: string;
constructor (name: string) {
this.name = name;
}
hello (): string {
return "Hello, " + this.name + "!";
}
}
自分用のメモと割り切って書いたから、めっちゃ読みにくいなこの記事…。
力技すぎるから、あとでリファクタリングするかも…。