TypeScriptのソースを読む

自分用メモ

>ここでは、TypeScriptのソースをダウンロードしてきて、
>　cd src/compiler
>　tsc tsc.ts
>でコンパイルして出力されるJavascriptのソースを読んでいます。

# やりたい事

自分の書いているJavaScriptのコードから、TypeScriptCompilerを呼び出してコンパイルしたい。
参照しているファイルとかは考えないで、１ファイルだけコンパイルする関数を書くのが最終的な目標。

>結果だけみたい人は「まとめ」まで飛べばオッケー。

# TypeScriptCompilerのインスタンスの作成

まずは、メソッドBatchCompiler.prototype.compileでTypeScriptCompilerのインスタンスが作成されている行を見てみる。

```javascript:tsc.js
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, localizedDiagnosticMessages);
```

引数の

・logger
・this.compilationSettings
・localizedDiagnosticMessages

が何を渡しているのか探してみる。

## logger

まず、loggerから見ていくと、次の行がみつかる。

```javascript:tsc.js
var logger = this.compilationSettings.gatherDiagnostics ? new DiagnosticsLogger(this.ioHost) : new TypeScript.NullLogger();
```

最悪、TypeScriptNullLoggerというのを使えば良さそうだ。
一応、NullLoggerの定義をみておく。

```javascript:diagnotics.js
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;
```

これ、何もしないというか、ログ吐かないのか…。

シグネチャは

```javascript:logger
information = function ()
debug = function ()
warning = function ()
error = function ()
fatal = function ()
log = function (s)
```

実際に仕事するのは、log(s)かな…。


## this.compilationSettings

BatchCompilerの宣言のところに、次のようなところがある。

```javascript:tsc.js
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については、次の行が参考になりそう。

```javascript:tsc.js
if (typeof localizedDiagnosticMessages === "undefined") {
    localizedDiagnosticMessages = null;
}
```

localizedDiagnosticMessagesの型が"undefined"だったら、nullを代入している。
つまり、よくわからなかったら、nullを渡しておけば良さそう。

# コンパイル

実際のコンパイルの手順をみていく。

```javascript:tsc.js
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の宣言部分では、

```javascript:tsc.js
this.resolvedEnvironment = null;
```

nullが代入されていたので、それがいつソースファイルの情報を含むようになるのか調べてみよう。

## BatchCompiler.prototype.batchCompile

調べていくと、メソッドBatchCompiler.prototype.batchCompileの中で

```javascript:tsc.js
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は、前に出た

```javascript:tsc.js
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に以下の記述がある。

```javascript: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に以下の記述がある。

```javascript:referenceResolutin.js
    var CompilationEnvironment = (function () {
        function CompilationEnvironment(compilationSettings, ioHost) {
            this.compilationSettings = compilationSettings;
            this.ioHost = ioHost;
            this.code = [];
            this.inputFileNameToOutputFileName = new TypeScript.StringHashTable();
        }
```

ここの、 this.code にソースファイル名が格納されてるのかな？？
違うな。ソースファイル名じゃない。別のところで、

```javascript:
code.path
code.fileInformation
```

などが参照されているから、なんらかの構造を持ったものだな…。
ずるして、referenceResolution. **ts** を見ると、

```typescript:referenceResolution.ts
        public code: SourceUnit[] = [];
```

だから、SourceUnitクラスの定義を探そう。

## SourceUnit

```javascript:referenceResolutin.js
    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が何かを調べると、

```typescript:core/environment.ts
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。
メンバー名からして、おそらく、入力ファイルと出力ファイルを紐つけたハッシュテーブルだろうけど…。

一応、ファイルをのぞいておく。

```javascript:hashTable.js
    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で定義されている

```javascript:io.js
var IO = (function () {
```

あたりかな。
で、ActiveX関係を使う場合が、

```javascript:io.js
    function getWindowsScriptHostIO() {
```

で、Node.jsの機能を使う場合が

```javascript:io.js
    function getNodeIO() {
```

なんだろう（たぶん）。
で、どちらか意識しないでいいように、var IO の定義の最後で、

```javascript:io.js
    if (typeof ActiveXObject === "function")
        return getWindowsScriptHostIO(); else if (typeof module !== 'undefined' && module.exports)
        return getNodeIO(); else
	return null;
})();
```

のように返してるわけか…。

つまり、IO を使えばよきにはからってくれるのかな…。
いちおう、後で自分で書くときのためにシグネチャをメモしとくか…。

```javascript:io.js
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:
```

あ。結構、めんどい…。

# コンパイルするソースの設定

さて、これだけの情報があればコンパイルするコードを書けるかな…？

```javascript:tsc.js
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, localizedDiagnosticMessages);
```

まずは、このコードを出発点とする。

localizedDiagnosticMessagesは、nullで構わない。
また、loggerはNullLoggerを使っておく。

```javascript:myCompile.js
var logger = new TypeScript.NullLoger();
var compiler = new TypeScript.TypeScriptCompiler(logger, this.compilationSettings, null);
```

ひとまず、こうなる。
あとは、実際にコンパイルすることを念頭に考えていく。

BatchCompilerを参考にすると、必要な変数の定義は以下のようになる。

```javascript:myCompile.js
var ioHost = IO;
var compilationSettings = new TypeScript.CompilationSettings();
var compilationEnvironment = new TypeScript.CompilationEnvironment(compilationSettings, ioHost);
```

tsc.jsでは、必要なファイルをリストアップしてからコンパイルしていたけど、
ここでは、１つのファイルだけコンパイル出来れば十分なので、以下のようにソースファイルを設定する。

```javascript:myCompile.js
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を参考にして考えていく。

## 構文チェック

最初に構文チェックするみたい。

```javascript:myCompile.js
// シンタックスエラーのチェック？
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

## 型チェックと意味解析

次が型のチェックと意味解析っぽい。

```javascript:myCompile.js
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);
    }
}
```

## コンパイル結果の書出し

あとはコンパイルしてファイルに書き出すのかな。

```javascript:myCompile.js
// ファイル書出し用オブジェクト
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; // エラーで終了
}

// 発行（ファイルに書出し？）　その２
var emitDeclarationsDiagnostics = compiler.emitAllDeclarations();
compiler.reportDiagnostics(emitDeclarationsDiagnostics, this.errorReporter);
if (emitDeclarationsDiagnostics.length > 0) {
    return true; // エラーで終了
}

return false; // 無事コンパイル完了
```

解析からコンパイルまでは、ほぼこのまま使えそうかな…。

# まとめ

ここまでをまとめて、１つのコードに書いてみる。

```javascript:myCompile.js
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; // エラーで終了
	}

	// 発行（ファイルに書出し？）　その２
	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

動作テストをしたら、おしまい。

コンパイルテスト用ファイルも一応。

```typescript:test.ts
class Human {
    name: string;

    constructor (name: string) {
        this.name = name;
    }

    hello (): string {
        return "Hello, " + this.name + "!";
    }
}
```

>自分用のメモと割り切って書いたから、めっちゃ読みにくいなこの記事…。
>力技すぎるから、あとでリファクタリングするかも…。