LoginSignup
11

More than 5 years have passed since last update.

TypeScriptのソースを読む

Last updated at Posted at 2013-07-22

自分用メモ

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

やりたい事

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

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

TypeScriptCompilerのインスタンスの作成

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

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

引数の

・logger
・this.compilationSettings
・localizedDiagnosticMessages

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

logger

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

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

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

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;

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

シグネチャは

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

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

this.compilationSettings

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

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

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

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

コンパイル

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

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の宣言部分では、

tsc.js
this.resolvedEnvironment = null;

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

BatchCompiler.prototype.batchCompile

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

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は、前に出た

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

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

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 を見ると、

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

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

SourceUnit

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が何かを調べると、

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

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

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で定義されている

io.js
var IO = (function () {

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

io.js
    function getWindowsScriptHostIO() {

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

io.js
    function getNodeIO() {

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

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

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

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

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:

あ。結構、めんどい…。

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

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

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

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

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

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

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

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

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

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

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を参考にして考えていく。

構文チェック

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

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

型チェックと意味解析

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

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);
    }
}

コンパイル結果の書出し

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

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; // エラーで終了
}

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

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

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

まとめ

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

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; // エラーで終了
    }

    // 発行(ファイルに書出し?) その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

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

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

test.ts
class Human {
    name: string;

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

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11