Comparing version 1.4.1 to 1.4.2
{ | ||
"name": "gulp-tsb", | ||
"version": "1.4.1", | ||
"version": "1.4.2", | ||
"author": "Johannes Rieken <johannes.rieken@gmail.com>", | ||
"description": "An incremental TypeScript builder for gulp", | ||
"description": "A gulp plugin for very fast TypeScript compilation.", | ||
"main": "./src/index", | ||
@@ -7,0 +7,0 @@ "repository": { |
@@ -6,3 +6,5 @@ gulp-tsb | ||
A gulp plugin for incremental TypeScript compilation. This plugin uses reverse dependencies of import-require statements and only works well with external modules (amd, commonjs). | ||
A gulp plugin for **very fast** TypeScript compilation. This plugin works by | ||
* keeping a compiler alive to improve speed (at the cost of memory) | ||
* always recompiling the smallest set of files possible | ||
@@ -9,0 +11,0 @@ ## Usage |
@@ -6,35 +6,20 @@ /// <reference path="../typings/node/node.d.ts" /> | ||
var fs = require('fs'); | ||
var vinyl = require('vinyl'); | ||
var path = require('path'); | ||
var crypto = require('crypto'); | ||
var utils = require('./utils'); | ||
var gutil = require('gulp-util'); | ||
var ts = require('./typescript/typescriptServices'); | ||
var Vinyl = require('vinyl'); | ||
function createTypeScriptBuilder(config) { | ||
var host = new LanguageServiceHost(createCompilationSettings(config)), languageService = ts.createLanguageService(host, ts.createDocumentRegistry()), oldErrors = Object.create(null), headUsed = process.memoryUsage().heapUsed; | ||
function createCompilationSettings(config) { | ||
// language version | ||
if (!config['target']) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
var settings = createCompilationSettings(config), host = new LanguageServiceHost(settings), service = ts.createLanguageService(host, ts.createDocumentRegistry()), lastBuildVersion = Object.create(null), lastDtsHash = Object.create(null), userWantsDeclarations = settings.declaration, oldErrors = Object.create(null), headUsed = process.memoryUsage().heapUsed; | ||
// always emit declaraction files | ||
host.getCompilationSettings().declaration = true; | ||
if (!host.getCompilationSettings().noLib) { | ||
var defaultLib = host.getDefaultLibFilename(); | ||
host.addScriptSnapshot(defaultLib, new ScriptSnapshot(fs.readFileSync(defaultLib), fs.statSync(defaultLib))); | ||
} | ||
function log(topic, message) { | ||
if (config.verbose) { | ||
gutil.log(gutil.colors.cyan(topic), message); | ||
} | ||
else if (/ES3/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} | ||
else if (/ES5/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES5; | ||
} | ||
else if (/ES6/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES6; | ||
} | ||
// module generation | ||
if (/commonjs/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.CommonJS; | ||
} | ||
else if (/amd/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.AMD; | ||
} | ||
var result = config; | ||
// if(config.verbose) { | ||
// gutil.log(JSON.stringify(result)); | ||
// } | ||
return result; | ||
} | ||
@@ -56,63 +41,164 @@ function printDiagnostic(diag, onError) { | ||
} | ||
if (!host.getCompilationSettings().noLib) { | ||
var defaultLib = host.getDefaultLibFilename(); | ||
host.addScriptSnapshot(defaultLib, new ScriptSnapshot(fs.readFileSync(defaultLib), fs.statSync(defaultLib))); | ||
function file(file) { | ||
var snapshot = new ScriptSnapshot(file.contents, file.stat); | ||
host.addScriptSnapshot(file.path, snapshot); | ||
} | ||
return { | ||
build: function (out, onError) { | ||
var task = host.createSnapshotAndAdviseValidation(), newErrors = Object.create(null), t1 = Date.now(); | ||
// (1) check for syntax errors | ||
task.changed.forEach(function (fileName) { | ||
if (config.verbose) { | ||
gutil.log(gutil.colors.cyan('[check syntax]'), fileName); | ||
function build(out, onError) { | ||
var filenames = host.getScriptFileNames(), newErrors = Object.create(null), checkedThisRound = Object.create(null), filesWithShapeChanges = [], t1 = Date.now(); | ||
function shouldCheck(filename) { | ||
if (checkedThisRound[filename]) { | ||
return false; | ||
} | ||
else { | ||
checkedThisRound[filename] = true; | ||
return true; | ||
} | ||
} | ||
for (var i = 0, len = filenames.length; i < len; i++) { | ||
var filename = filenames[i], version = host.getScriptVersion(filename); | ||
if (lastBuildVersion[filename] === version) { | ||
continue; | ||
} | ||
var output = service.getEmitOutput(filename), checkSyntax = false, checkSemantics = false, dtsHash = undefined; | ||
// emit output has fast as possible | ||
output.outputFiles.forEach(function (file) { | ||
if (/\.d\.ts$/.test(file.name)) { | ||
dtsHash = crypto.createHash('md5').update(file.text).digest('base64'); | ||
if (!userWantsDeclarations) { | ||
// don't leak .d.ts files if users don't want them | ||
return; | ||
} | ||
} | ||
delete oldErrors[fileName]; | ||
languageService.getSyntacticDiagnostics(fileName).forEach(function (diag) { | ||
printDiagnostic(diag, onError); | ||
utils.collections.lookupOrInsert(newErrors, fileName, []).push(diag); | ||
}); | ||
log('[emit output]', file.name); | ||
out(new Vinyl({ | ||
path: file.name, | ||
contents: new Buffer(file.text) | ||
})); | ||
}); | ||
// (2) emit | ||
task.changed.forEach(function (fileName) { | ||
if (config.verbose) { | ||
gutil.log(gutil.colors.cyan('[emit code]'), fileName); | ||
switch (output.emitOutputStatus) { | ||
case ts.EmitReturnStatus.Succeeded: | ||
break; | ||
case ts.EmitReturnStatus.AllOutputGenerationSkipped: | ||
log('[syntax errors]', filename); | ||
checkSyntax = true; | ||
break; | ||
case ts.EmitReturnStatus.JSGeneratedWithSemanticErrors: | ||
case ts.EmitReturnStatus.DeclarationGenerationSkipped: | ||
log('[semantic errors]', filename); | ||
checkSemantics = true; | ||
break; | ||
case ts.EmitReturnStatus.EmitErrorsEncountered: | ||
case ts.EmitReturnStatus.CompilerOptionsErrors: | ||
default: | ||
// don't really know what to do with these | ||
checkSyntax = true; | ||
checkSemantics = true; | ||
break; | ||
} | ||
// print and store syntax and semantic errors | ||
delete oldErrors[filename]; | ||
var diagnostics = utils.collections.lookupOrInsert(newErrors, filename, []); | ||
if (checkSyntax) { | ||
diagnostics.push.apply(diagnostics, service.getSyntacticDiagnostics(filename)); | ||
} | ||
if (checkSemantics) { | ||
diagnostics.push.apply(diagnostics, service.getSemanticDiagnostics(filename)); | ||
} | ||
diagnostics.forEach(function (diag) { | ||
printDiagnostic(diag, onError); | ||
}); | ||
// dts comparing | ||
if (dtsHash && lastDtsHash[filename] !== dtsHash) { | ||
lastDtsHash[filename] = dtsHash; | ||
if (service.getSourceFile(filename).externalModuleIndicator) { | ||
filesWithShapeChanges.push(filename); | ||
} | ||
var output = languageService.getEmitOutput(fileName); | ||
output.outputFiles.forEach(function (file) { | ||
out(new vinyl({ | ||
path: file.name, | ||
contents: new Buffer(file.text) | ||
})); | ||
else { | ||
filesWithShapeChanges.unshift(filename); | ||
} | ||
} | ||
lastBuildVersion[filename] = version; | ||
checkedThisRound[filename] = true; | ||
} | ||
if (filesWithShapeChanges.length === 0) { | ||
} | ||
else if (!service.getSourceFile(filesWithShapeChanges[0]).externalModuleIndicator) { | ||
// at least one internal module changes which means that | ||
// we have to type check all others | ||
log('[shape changes]', 'internal module changed → FULL check required'); | ||
host.getScriptFileNames().forEach(function (filename) { | ||
if (!shouldCheck(filename)) { | ||
return; | ||
} | ||
log('[semantic check*]', filename); | ||
var diagnostics = utils.collections.lookupOrInsert(newErrors, filename, []); | ||
service.getSemanticDiagnostics(filename).forEach(function (diag) { | ||
diagnostics.push(diag); | ||
printDiagnostic(diag, onError); | ||
}); | ||
}); | ||
// (3) semantic check | ||
task.changedOrDependencyChanged.forEach(function (fileName) { | ||
if (config.verbose) { | ||
gutil.log(gutil.colors.cyan('[check semantics]'), fileName); | ||
} | ||
else { | ||
// reverse dependencies | ||
log('[shape changes]', 'external module changed → check REVERSE dependencies'); | ||
var needsSemanticCheck = []; | ||
filesWithShapeChanges.forEach(function (filename) { return host.collectDependents(filename, needsSemanticCheck); }); | ||
while (needsSemanticCheck.length) { | ||
var filename = needsSemanticCheck.pop(); | ||
if (!shouldCheck(filename)) { | ||
continue; | ||
} | ||
delete oldErrors[fileName]; | ||
languageService.getSemanticDiagnostics(fileName).forEach(function (diag) { | ||
log('[semantic check*]', filename); | ||
var diagnostics = utils.collections.lookupOrInsert(newErrors, filename, []), hasSemanticErrors = false; | ||
service.getSemanticDiagnostics(filename).forEach(function (diag) { | ||
diagnostics.push(diag); | ||
printDiagnostic(diag, onError); | ||
utils.collections.lookupOrInsert(newErrors, fileName, []).push(diag); | ||
hasSemanticErrors = true; | ||
}); | ||
}); | ||
// (4) dump old errors | ||
utils.collections.forEach(oldErrors, function (entry) { | ||
entry.value.forEach(function (diag) { return printDiagnostic(diag, onError); }); | ||
newErrors[entry.key] = entry.value; | ||
}); | ||
oldErrors = newErrors; | ||
if (config.verbose) { | ||
var headNow = process.memoryUsage().heapUsed, MB = 1024 * 1024; | ||
gutil.log('[tsb]', 'time:', gutil.colors.yellow((Date.now() - t1) + 'ms'), 'mem:', gutil.colors.cyan(Math.ceil(headNow / MB) + 'MB'), gutil.colors.bgCyan('Δ' + Math.ceil((headNow - headUsed) / MB))); | ||
headUsed = headNow; | ||
if (!hasSemanticErrors) { | ||
host.collectDependents(filename, needsSemanticCheck); | ||
} | ||
} | ||
}, | ||
file: function (file) { | ||
var snapshot = new ScriptSnapshot(file.contents, file.stat); | ||
host.addScriptSnapshot(file.path, snapshot); | ||
} | ||
// (4) dump old errors | ||
utils.collections.forEach(oldErrors, function (entry) { | ||
entry.value.forEach(function (diag) { return printDiagnostic(diag, onError); }); | ||
newErrors[entry.key] = entry.value; | ||
}); | ||
oldErrors = newErrors; | ||
if (config.verbose) { | ||
var headNow = process.memoryUsage().heapUsed, MB = 1024 * 1024; | ||
gutil.log('[tsb]', 'time:', gutil.colors.yellow((Date.now() - t1) + 'ms'), 'mem:', gutil.colors.cyan(Math.ceil(headNow / MB) + 'MB'), gutil.colors.bgCyan('Δ' + Math.ceil((headNow - headUsed) / MB))); | ||
headUsed = headNow; | ||
} | ||
} | ||
return { | ||
file: file, | ||
build: build | ||
}; | ||
} | ||
exports.createTypeScriptBuilder = createTypeScriptBuilder; | ||
function createCompilationSettings(config) { | ||
// language version | ||
if (!config['target']) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} | ||
else if (/ES3/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} | ||
else if (/ES5/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES5; | ||
} | ||
else if (/ES6/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES6; | ||
} | ||
// module generation | ||
if (/commonjs/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.CommonJS; | ||
} | ||
else if (/amd/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.AMD; | ||
} | ||
return config; | ||
} | ||
var ScriptSnapshot = (function () { | ||
@@ -141,82 +227,2 @@ function ScriptSnapshot(buffer, stat) { | ||
})(); | ||
var ProjectSnapshot = (function () { | ||
function ProjectSnapshot(host) { | ||
this._captureState(host); | ||
} | ||
ProjectSnapshot.prototype._captureState = function (host) { | ||
var _this = this; | ||
this._dependencies = new utils.graph.Graph(function (s) { return s; }); | ||
this._versions = Object.create(null); | ||
host.getScriptFileNames().forEach(function (fileName) { | ||
fileName = path.normalize(fileName); | ||
// (1) paths and versions | ||
_this._versions[fileName] = host.getScriptVersion(fileName); | ||
// (2) dependency graph for *.ts files | ||
if (!fileName.match(/.*\.d\.ts$/)) { | ||
var snapshot = host.getScriptSnapshot(fileName), info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); | ||
info.referencedFiles.forEach(function (ref) { | ||
var resolvedPath = path.resolve(path.dirname(fileName), ref.filename), normalizedPath = path.normalize(resolvedPath); | ||
_this._dependencies.inertEdge(fileName, normalizedPath); | ||
// console.log(fileName + ' -> ' + normalizedPath); | ||
}); | ||
info.importedFiles.forEach(function (ref) { | ||
var stopDirname = path.normalize(host.getCurrentDirectory()), dirname = fileName; | ||
while (dirname.indexOf(stopDirname) === 0) { | ||
dirname = path.dirname(dirname); | ||
var resolvedPath = path.resolve(dirname, ref.filename), normalizedPath = path.normalize(resolvedPath); | ||
// try .ts | ||
if (['.ts', '.d.ts'].some(function (suffix) { | ||
var candidate = normalizedPath + suffix; | ||
if (host.getScriptSnapshot(candidate)) { | ||
_this._dependencies.inertEdge(fileName, candidate); | ||
// console.log(fileName + ' -> ' + candidate); | ||
return true; | ||
} | ||
return false; | ||
})) { | ||
break; | ||
} | ||
; | ||
} | ||
}); | ||
} | ||
}); | ||
}; | ||
ProjectSnapshot.prototype.whatToValidate = function (host) { | ||
var _this = this; | ||
var changed = [], added = [], removed = []; | ||
// compile file delta (changed, added, removed) | ||
var idx = Object.create(null); | ||
host.getScriptFileNames().forEach(function (fileName) { return idx[fileName] = host.getScriptVersion(fileName); }); | ||
utils.collections.forEach(this._versions, function (entry) { | ||
var versionNow = idx[entry.key]; | ||
if (typeof versionNow === 'undefined') { | ||
// removed | ||
removed.push(entry.key); | ||
} | ||
else if (typeof versionNow === 'string' && versionNow !== entry.value) { | ||
// changed | ||
changed.push(entry.key); | ||
} | ||
delete idx[entry.key]; | ||
}); | ||
// cos we removed all we saw earlier | ||
added = Object.keys(idx); | ||
// what to validate? | ||
var syntax = changed.concat(added), semantic = []; | ||
if (removed.length > 0 || added.length > 0) { | ||
semantic = host.getScriptFileNames(); | ||
} | ||
else { | ||
// validate every change file *plus* the files | ||
// that depend on the changed file | ||
changed.forEach(function (fileName) { return _this._dependencies.traverse(fileName, false, function (data) { return semantic.push(data); }); }); | ||
} | ||
return { | ||
changed: syntax, | ||
changedOrDependencyChanged: semantic | ||
}; | ||
}; | ||
return ProjectSnapshot; | ||
})(); | ||
var LanguageServiceHost = (function () { | ||
@@ -227,2 +233,4 @@ function LanguageServiceHost(settings) { | ||
this._defaultLib = path.normalize(path.join(__dirname, 'typescript', 'lib.d.ts')); | ||
this._dependencies = new utils.graph.Graph(function (s) { return s; }); | ||
this._dependenciesRecomputeList = []; | ||
} | ||
@@ -238,17 +246,24 @@ LanguageServiceHost.prototype.log = function (s) { | ||
}; | ||
LanguageServiceHost.prototype.getScriptVersion = function (fileName) { | ||
fileName = path.normalize(fileName); | ||
return this._snapshots[fileName].getVersion(); | ||
LanguageServiceHost.prototype.getScriptVersion = function (filename) { | ||
filename = path.normalize(filename); | ||
return this._snapshots[filename].getVersion(); | ||
}; | ||
LanguageServiceHost.prototype.getScriptIsOpen = function (fileName) { | ||
LanguageServiceHost.prototype.getScriptIsOpen = function (filename) { | ||
return false; | ||
}; | ||
LanguageServiceHost.prototype.getScriptSnapshot = function (fileName) { | ||
fileName = path.normalize(fileName); | ||
return this._snapshots[fileName]; | ||
LanguageServiceHost.prototype.getScriptSnapshot = function (filename) { | ||
filename = path.normalize(filename); | ||
return this._snapshots[filename]; | ||
}; | ||
LanguageServiceHost.prototype.addScriptSnapshot = function (fileName, snapshot) { | ||
fileName = path.normalize(fileName); | ||
var old = this._snapshots[fileName]; | ||
this._snapshots[fileName] = snapshot; | ||
LanguageServiceHost.prototype.addScriptSnapshot = function (filename, snapshot) { | ||
filename = path.normalize(filename); | ||
var old = this._snapshots[filename]; | ||
if (!old || old.getVersion() !== snapshot.getVersion()) { | ||
this._dependenciesRecomputeList.push(filename); | ||
var node = this._dependencies.lookup(filename); | ||
if (node) { | ||
node.outgoing = Object.create(null); | ||
} | ||
} | ||
this._snapshots[filename] = snapshot; | ||
return old; | ||
@@ -268,17 +283,43 @@ }; | ||
}; | ||
LanguageServiceHost.prototype.createSnapshotAndAdviseValidation = function () { | ||
var ret; | ||
if (!this._projectSnapshot) { | ||
ret = { | ||
changed: this.getScriptFileNames(), | ||
changedOrDependencyChanged: this.getScriptFileNames() | ||
}; | ||
// ---- dependency management | ||
LanguageServiceHost.prototype.collectDependents = function (filename, target) { | ||
while (this._dependenciesRecomputeList.length) { | ||
this._processFile(this._dependenciesRecomputeList.pop()); | ||
} | ||
else { | ||
ret = this._projectSnapshot.whatToValidate(this); | ||
filename = path.normalize(filename); | ||
var node = this._dependencies.lookup(filename); | ||
if (node) { | ||
utils.collections.forEach(node.incoming, function (entry) { return target.push(entry.key); }); | ||
} | ||
this._projectSnapshot = new ProjectSnapshot(this); | ||
return ret; | ||
}; | ||
LanguageServiceHost.prototype._processFile = function (filename) { | ||
var _this = this; | ||
if (filename.match(/.*\.d\.ts$/)) { | ||
return; | ||
} | ||
filename = path.normalize(filename); | ||
var snapshot = this.getScriptSnapshot(filename), info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); | ||
// (1) ///-references | ||
info.referencedFiles.forEach(function (ref) { | ||
var resolvedPath = path.resolve(path.dirname(filename), ref.filename), normalizedPath = path.normalize(resolvedPath); | ||
_this._dependencies.inertEdge(filename, normalizedPath); | ||
}); | ||
// (2) import-require statements | ||
info.importedFiles.forEach(function (ref) { | ||
var stopDirname = path.normalize(_this.getCurrentDirectory()), dirname = filename, found = false; | ||
while (!found && dirname.indexOf(stopDirname) === 0) { | ||
dirname = path.dirname(dirname); | ||
var resolvedPath = path.resolve(dirname, ref.filename), normalizedPath = path.normalize(resolvedPath); | ||
if (_this.getScriptSnapshot(normalizedPath + '.ts')) { | ||
_this._dependencies.inertEdge(filename, normalizedPath + '.ts'); | ||
found = true; | ||
} | ||
else if (_this.getScriptSnapshot(normalizedPath + '.d.ts')) { | ||
_this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); | ||
found = true; | ||
} | ||
} | ||
}); | ||
}; | ||
return LanguageServiceHost; | ||
})(); |
@@ -8,7 +8,8 @@ /// <reference path="../typings/node/node.d.ts" /> | ||
import fs = require('fs'); | ||
import vinyl = require('vinyl'); | ||
import path = require('path'); | ||
import crypto = require('crypto'); | ||
import utils = require('./utils'); | ||
import gutil = require('gulp-util'); | ||
import ts = require('./typescript/typescriptServices'); | ||
import Vinyl = require('vinyl'); | ||
@@ -21,59 +22,44 @@ export interface IConfiguration { | ||
export interface IFileDelta { | ||
added?:vinyl[]; | ||
changed?:vinyl[]; | ||
deleted?:vinyl[]; | ||
} | ||
export interface ITypeScriptBuilder { | ||
build(out: (file:vinyl)=>void, onError:(err:any)=>void): void; | ||
file(file: vinyl): void; | ||
build(out: (file: Vinyl) => void, onError: (err: any) => void): void; | ||
file(file: Vinyl): void; | ||
} | ||
export function createTypeScriptBuilder(config:IConfiguration): ITypeScriptBuilder { | ||
var host = new LanguageServiceHost(createCompilationSettings(config)), | ||
languageService = ts.createLanguageService(host, ts.createDocumentRegistry()), | ||
oldErrors: { [path:string]: ts.Diagnostic[] } = Object.create(null), | ||
export function createTypeScriptBuilder(config: IConfiguration): ITypeScriptBuilder { | ||
var settings = createCompilationSettings(config), | ||
host = new LanguageServiceHost(settings), | ||
service = ts.createLanguageService(host, ts.createDocumentRegistry()), | ||
lastBuildVersion: { [path: string]: string } = Object.create(null), | ||
lastDtsHash: { [path: string]: string } = Object.create(null), | ||
userWantsDeclarations = settings.declaration, | ||
oldErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null), | ||
headUsed = process.memoryUsage().heapUsed; | ||
// always emit declaraction files | ||
host.getCompilationSettings().declaration = true; | ||
function createCompilationSettings(config:IConfiguration): ts.CompilerOptions { | ||
// language version | ||
if(!config['target']) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} else if(/ES3/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} else if(/ES5/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES5; | ||
} else if(/ES6/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES6; | ||
if (!host.getCompilationSettings().noLib) { | ||
var defaultLib = host.getDefaultLibFilename(); | ||
host.addScriptSnapshot(defaultLib, new ScriptSnapshot(fs.readFileSync(defaultLib), fs.statSync(defaultLib))); | ||
} | ||
function log(topic: string, message: string): void { | ||
if(config.verbose) { | ||
gutil.log(gutil.colors.cyan(topic), message); | ||
} | ||
// module generation | ||
if (/commonjs/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.CommonJS; | ||
} else if (/amd/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.AMD; | ||
} | ||
var result = <ts.CompilerOptions> config; | ||
// if(config.verbose) { | ||
// gutil.log(JSON.stringify(result)); | ||
// } | ||
return result; | ||
} | ||
function printDiagnostic(diag:ts.Diagnostic, onError:(err:any)=>void):void { | ||
function printDiagnostic(diag: ts.Diagnostic, onError: (err: any) => void): void { | ||
var lineAndCh = diag.file.getLineAndCharacterFromPosition(diag.start), | ||
message:string; | ||
if(!config.json) { | ||
message = utils.strings.format('{0}({1},{2}): {3}', | ||
diag.file.filename, | ||
lineAndCh.line, | ||
lineAndCh.character, | ||
message: string; | ||
if (!config.json) { | ||
message = utils.strings.format('{0}({1},{2}): {3}', | ||
diag.file.filename, | ||
lineAndCh.line, | ||
lineAndCh.character, | ||
diag.messageText); | ||
} else { | ||
@@ -87,93 +73,211 @@ message = JSON.stringify({ | ||
} | ||
onError(message); | ||
} | ||
if(!host.getCompilationSettings().noLib) { | ||
var defaultLib = host.getDefaultLibFilename(); | ||
host.addScriptSnapshot(defaultLib, new ScriptSnapshot(fs.readFileSync(defaultLib), fs.statSync(defaultLib))); | ||
function file(file: Vinyl): void { | ||
var snapshot = new ScriptSnapshot(file.contents, file.stat); | ||
host.addScriptSnapshot(file.path, snapshot); | ||
} | ||
return { | ||
build: (out: (file:vinyl)=>void, onError: (err: any) => void) => { | ||
var task = host.createSnapshotAndAdviseValidation(), | ||
newErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null), | ||
t1 = Date.now(); | ||
function build(out: (file: Vinyl) => void, onError: (err: any) => void): void { | ||
var filenames = host.getScriptFileNames(), | ||
newErrors: { [path: string]: ts.Diagnostic[] } = Object.create(null), | ||
checkedThisRound: { [path: string]: boolean } = Object.create(null), | ||
filesWithShapeChanges: string[] = [], | ||
t1 = Date.now(); | ||
function shouldCheck(filename: string): boolean { | ||
if (checkedThisRound[filename]) { | ||
return false; | ||
} else { | ||
checkedThisRound[filename] = true; | ||
return true; | ||
} | ||
} | ||
for (var i = 0, len = filenames.length; i < len; i++) { | ||
var filename = filenames[i], | ||
version = host.getScriptVersion(filename); | ||
if (lastBuildVersion[filename] === version) { | ||
// unchanged since the last time | ||
continue; | ||
} | ||
var output = service.getEmitOutput(filename), | ||
checkSyntax = false, | ||
checkSemantics = false, | ||
dtsHash: string = undefined; | ||
// (1) check for syntax errors | ||
task.changed.forEach(fileName => { | ||
if(config.verbose) { | ||
gutil.log(gutil.colors.cyan('[check syntax]'), fileName); | ||
// emit output has fast as possible | ||
output.outputFiles.forEach(file => { | ||
if (/\.d\.ts$/.test(file.name)) { | ||
dtsHash = crypto.createHash('md5') | ||
.update(file.text) | ||
.digest('base64'); | ||
if (!userWantsDeclarations) { | ||
// don't leak .d.ts files if users don't want them | ||
return; | ||
} | ||
} | ||
delete oldErrors[fileName]; | ||
log('[emit output]', file.name); | ||
languageService.getSyntacticDiagnostics(fileName).forEach(diag => { | ||
printDiagnostic(diag, onError); | ||
utils.collections.lookupOrInsert(newErrors, fileName, []).push(diag); | ||
}); | ||
out(new Vinyl({ | ||
path: file.name, | ||
contents: new Buffer(file.text) | ||
})); | ||
}); | ||
// (2) emit | ||
task.changed.forEach(fileName => { | ||
if(config.verbose) { | ||
gutil.log(gutil.colors.cyan('[emit code]'), fileName); | ||
// make use of emit status | ||
switch (output.emitOutputStatus) { | ||
case ts.EmitReturnStatus.Succeeded: | ||
// good | ||
break; | ||
case ts.EmitReturnStatus.AllOutputGenerationSkipped: | ||
log('[syntax errors]', filename); | ||
checkSyntax = true; | ||
break; | ||
case ts.EmitReturnStatus.JSGeneratedWithSemanticErrors: | ||
case ts.EmitReturnStatus.DeclarationGenerationSkipped: | ||
log('[semantic errors]', filename); | ||
checkSemantics = true; | ||
break; | ||
case ts.EmitReturnStatus.EmitErrorsEncountered: | ||
case ts.EmitReturnStatus.CompilerOptionsErrors: | ||
default: | ||
// don't really know what to do with these | ||
checkSyntax = true; | ||
checkSemantics = true; | ||
break; | ||
} | ||
// print and store syntax and semantic errors | ||
delete oldErrors[filename]; | ||
var diagnostics = utils.collections.lookupOrInsert(newErrors, filename, []); | ||
if (checkSyntax) { | ||
diagnostics.push.apply(diagnostics, service.getSyntacticDiagnostics(filename)); | ||
} | ||
if (checkSemantics) { | ||
diagnostics.push.apply(diagnostics, service.getSemanticDiagnostics(filename)); | ||
} | ||
diagnostics.forEach(diag => { | ||
printDiagnostic(diag, onError); | ||
}); | ||
// dts comparing | ||
if (dtsHash && lastDtsHash[filename] !== dtsHash) { | ||
lastDtsHash[filename] = dtsHash; | ||
if (service.getSourceFile(filename).externalModuleIndicator) { | ||
filesWithShapeChanges.push(filename); | ||
} else { | ||
filesWithShapeChanges.unshift(filename); | ||
} | ||
var output = languageService.getEmitOutput(fileName); | ||
output.outputFiles.forEach(file => { | ||
out(new vinyl({ | ||
path: file.name, | ||
contents: new Buffer(file.text) | ||
})); | ||
} | ||
lastBuildVersion[filename] = version; | ||
checkedThisRound[filename] = true; | ||
} | ||
if (filesWithShapeChanges.length === 0) { | ||
// nothing to do here | ||
} else if (!service.getSourceFile(filesWithShapeChanges[0]).externalModuleIndicator) { | ||
// at least one internal module changes which means that | ||
// we have to type check all others | ||
log('[shape changes]', 'internal module changed → FULL check required'); | ||
host.getScriptFileNames().forEach(filename => { | ||
if(!shouldCheck(filename)) { | ||
return; | ||
} | ||
log('[semantic check*]', filename); | ||
var diagnostics = utils.collections.lookupOrInsert(newErrors, filename, []); | ||
service.getSemanticDiagnostics(filename).forEach(diag => { | ||
diagnostics.push(diag); | ||
printDiagnostic(diag, onError); | ||
}); | ||
}); | ||
// (3) semantic check | ||
task.changedOrDependencyChanged.forEach(fileName => { | ||
} else { | ||
// reverse dependencies | ||
log('[shape changes]', 'external module changed → check REVERSE dependencies'); | ||
var needsSemanticCheck: string[] = []; | ||
filesWithShapeChanges.forEach(filename => host.collectDependents(filename, needsSemanticCheck)); | ||
while(needsSemanticCheck.length) { | ||
var filename = needsSemanticCheck.pop(); | ||
if(!shouldCheck(filename)) { | ||
continue; | ||
} | ||
log('[semantic check*]', filename); | ||
var diagnostics = utils.collections.lookupOrInsert(newErrors, filename, []), | ||
hasSemanticErrors = false; | ||
if(config.verbose) { | ||
gutil.log(gutil.colors.cyan('[check semantics]'), fileName); | ||
} | ||
delete oldErrors[fileName]; | ||
languageService.getSemanticDiagnostics(fileName).forEach(diag => { | ||
service.getSemanticDiagnostics(filename).forEach(diag => { | ||
diagnostics.push(diag); | ||
printDiagnostic(diag, onError); | ||
utils.collections.lookupOrInsert(newErrors, fileName, []).push(diag); | ||
hasSemanticErrors = true; | ||
}); | ||
}); | ||
// (4) dump old errors | ||
utils.collections.forEach(oldErrors, entry => { | ||
entry.value.forEach(diag => printDiagnostic(diag, onError)); | ||
newErrors[entry.key] = entry.value; | ||
}); | ||
oldErrors = newErrors; | ||
if(config.verbose) { | ||
var headNow = process.memoryUsage().heapUsed, | ||
MB = 1024 * 1024; | ||
gutil.log( | ||
'[tsb]', | ||
'time:', | ||
gutil.colors.yellow((Date.now() - t1) + 'ms'), | ||
'mem:', | ||
gutil.colors.cyan(Math.ceil(headNow / MB) + 'MB'), | ||
gutil.colors.bgCyan('Δ' + Math.ceil((headNow - headUsed) / MB))); | ||
headUsed = headNow; | ||
if(!hasSemanticErrors) { | ||
host.collectDependents(filename, needsSemanticCheck); | ||
} | ||
} | ||
}, | ||
} | ||
file: (file) => { | ||
var snapshot = new ScriptSnapshot(file.contents, file.stat); | ||
host.addScriptSnapshot(file.path, snapshot); | ||
// (4) dump old errors | ||
utils.collections.forEach(oldErrors, entry => { | ||
entry.value.forEach(diag => printDiagnostic(diag, onError)); | ||
newErrors[entry.key] = entry.value; | ||
}); | ||
oldErrors = newErrors; | ||
if (config.verbose) { | ||
var headNow = process.memoryUsage().heapUsed, | ||
MB = 1024 * 1024; | ||
gutil.log( | ||
'[tsb]', | ||
'time:', | ||
gutil.colors.yellow((Date.now() - t1) + 'ms'), | ||
'mem:', | ||
gutil.colors.cyan(Math.ceil(headNow / MB) + 'MB'), | ||
gutil.colors.bgCyan('Δ' + Math.ceil((headNow - headUsed) / MB))); | ||
headUsed = headNow; | ||
} | ||
} | ||
return { | ||
file, | ||
build | ||
}; | ||
} | ||
function createCompilationSettings(config: IConfiguration): ts.CompilerOptions { | ||
// language version | ||
if (!config['target']) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} else if (/ES3/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES3; | ||
} else if (/ES5/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES5; | ||
} else if (/ES6/i.test(String(config['target']))) { | ||
config['target'] = ts.ScriptTarget.ES6; | ||
} | ||
// module generation | ||
if (/commonjs/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.CommonJS; | ||
} else if (/amd/i.test(String(config['module']))) { | ||
config['module'] = ts.ModuleKind.AMD; | ||
} | ||
return <ts.CompilerOptions> config; | ||
} | ||
class ScriptSnapshot implements ts.IScriptSnapshot { | ||
@@ -184,4 +288,4 @@ | ||
private _mtime: Date; | ||
constructor(buffer:Buffer, stat:fs.Stats) { | ||
constructor(buffer: Buffer, stat: fs.Stats) { | ||
this._text = buffer.toString(); | ||
@@ -191,7 +295,7 @@ this._lineStarts = ts.computeLineStarts(this._text); | ||
} | ||
public getVersion():string { | ||
public getVersion(): string { | ||
return this._mtime.toUTCString(); | ||
} | ||
public getText(start: number, end: number): string { | ||
@@ -208,4 +312,4 @@ return this._text.substring(start, end); | ||
} | ||
public getChangeRange(oldSnapshot:ts.IScriptSnapshot):ts.TextChangeRange { | ||
public getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange { | ||
return null; | ||
@@ -215,117 +319,2 @@ } | ||
interface IValidationTask { | ||
changed: string[]; | ||
changedOrDependencyChanged: string[]; | ||
} | ||
class ProjectSnapshot { | ||
private _dependencies: utils.graph.Graph<string>; | ||
private _versions: { [path: string]: string; }; | ||
constructor(host:ts.LanguageServiceHost) { | ||
this._captureState(host); | ||
} | ||
private _captureState(host:ts.LanguageServiceHost):void { | ||
this._dependencies = new utils.graph.Graph<string>(s => s); | ||
this._versions = Object.create(null); | ||
host.getScriptFileNames().forEach(fileName => { | ||
fileName = path.normalize(fileName); | ||
// (1) paths and versions | ||
this._versions[fileName] = host.getScriptVersion(fileName); | ||
// (2) dependency graph for *.ts files | ||
if(!fileName.match(/.*\.d\.ts$/)) { | ||
var snapshot = host.getScriptSnapshot(fileName), | ||
info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); | ||
info.referencedFiles.forEach(ref => { | ||
var resolvedPath = path.resolve(path.dirname(fileName), ref.filename), | ||
normalizedPath = path.normalize(resolvedPath); | ||
this._dependencies.inertEdge(fileName, normalizedPath); | ||
// console.log(fileName + ' -> ' + normalizedPath); | ||
}); | ||
info.importedFiles.forEach(ref => { | ||
var stopDirname = path.normalize(host.getCurrentDirectory()), | ||
dirname = fileName; | ||
while(dirname.indexOf(stopDirname) === 0) { | ||
dirname = path.dirname(dirname); | ||
var resolvedPath = path.resolve(dirname, ref.filename), | ||
normalizedPath = path.normalize(resolvedPath); | ||
// try .ts | ||
if (['.ts', '.d.ts'].some(suffix => { | ||
var candidate = normalizedPath + suffix; | ||
if (host.getScriptSnapshot(candidate)) { | ||
this._dependencies.inertEdge(fileName, candidate); | ||
// console.log(fileName + ' -> ' + candidate); | ||
return true; | ||
} | ||
return false; | ||
})) { | ||
// found, ugly code! | ||
break; | ||
}; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
public whatToValidate(host: ts.LanguageServiceHost):IValidationTask { | ||
var changed: string[] = [], | ||
added: string[] = [], | ||
removed: string[] = []; | ||
// compile file delta (changed, added, removed) | ||
var idx: { [path: string]: string } = Object.create(null); | ||
host.getScriptFileNames().forEach(fileName => idx[fileName] = host.getScriptVersion(fileName)); | ||
utils.collections.forEach(this._versions, entry => { | ||
var versionNow = idx[entry.key]; | ||
if(typeof versionNow === 'undefined') { | ||
// removed | ||
removed.push(entry.key); | ||
} else if(typeof versionNow === 'string' && versionNow !== entry.value) { | ||
// changed | ||
changed.push(entry.key); | ||
} | ||
delete idx[entry.key]; | ||
}); | ||
// cos we removed all we saw earlier | ||
added = Object.keys(idx); | ||
// what to validate? | ||
var syntax = changed.concat(added), | ||
semantic: string[] = []; | ||
if(removed.length > 0 || added.length > 0) { | ||
semantic = host.getScriptFileNames(); | ||
} else { | ||
// validate every change file *plus* the files | ||
// that depend on the changed file | ||
changed.forEach(fileName => this._dependencies.traverse(fileName, false, data => semantic.push(data))); | ||
} | ||
return { | ||
changed: syntax, | ||
changedOrDependencyChanged: semantic | ||
}; | ||
} | ||
} | ||
class LanguageServiceHost implements ts.LanguageServiceHost { | ||
@@ -336,55 +325,65 @@ | ||
private _defaultLib: string; | ||
private _projectSnapshot: ProjectSnapshot; | ||
constructor(settings:ts.CompilerOptions) { | ||
private _dependencies: utils.graph.Graph<string>; | ||
private _dependenciesRecomputeList: string[]; | ||
constructor(settings: ts.CompilerOptions) { | ||
this._settings = settings; | ||
this._snapshots = Object.create(null); | ||
this._defaultLib = path.normalize(path.join(__dirname, 'typescript', 'lib.d.ts')); | ||
this._dependencies = new utils.graph.Graph<string>(s => s); | ||
this._dependenciesRecomputeList = []; | ||
} | ||
log(s: string): void { | ||
// nothing | ||
} | ||
getCompilationSettings(): ts.CompilerOptions { | ||
return this._settings; | ||
} | ||
getScriptFileNames(): string[] { | ||
return Object.keys(this._snapshots); | ||
} | ||
getScriptVersion(fileName: string): string { | ||
fileName = path.normalize(fileName); | ||
return this._snapshots[fileName].getVersion(); | ||
getScriptVersion(filename: string): string { | ||
filename = path.normalize(filename); | ||
return this._snapshots[filename].getVersion(); | ||
} | ||
getScriptIsOpen(fileName: string): boolean { | ||
getScriptIsOpen(filename: string): boolean { | ||
return false; | ||
} | ||
getScriptSnapshot(fileName: string): ts.IScriptSnapshot { | ||
fileName = path.normalize(fileName); | ||
return this._snapshots[fileName]; | ||
getScriptSnapshot(filename: string): ts.IScriptSnapshot { | ||
filename = path.normalize(filename); | ||
return this._snapshots[filename]; | ||
} | ||
addScriptSnapshot(fileName:string, snapshot:ScriptSnapshot):ScriptSnapshot { | ||
fileName = path.normalize(fileName); | ||
var old = this._snapshots[fileName]; | ||
this._snapshots[fileName] = snapshot; | ||
addScriptSnapshot(filename: string, snapshot: ScriptSnapshot): ScriptSnapshot { | ||
filename = path.normalize(filename); | ||
var old = this._snapshots[filename]; | ||
if(!old || old.getVersion() !== snapshot.getVersion()) { | ||
this._dependenciesRecomputeList.push(filename); | ||
var node = this._dependencies.lookup(filename); | ||
if(node) { | ||
node.outgoing = Object.create(null); | ||
} | ||
} | ||
this._snapshots[filename] = snapshot; | ||
return old; | ||
} | ||
getLocalizedDiagnosticMessages(): any { | ||
return null; | ||
} | ||
getCancellationToken(): ts.CancellationToken { | ||
return { isCancellationRequested: () => false }; | ||
} | ||
getCurrentDirectory(): string { | ||
return process.cwd(); | ||
} | ||
getDefaultLibFilename(): string { | ||
@@ -394,15 +393,53 @@ return this._defaultLib; | ||
createSnapshotAndAdviseValidation():IValidationTask { | ||
var ret: IValidationTask; | ||
if(!this._projectSnapshot) { | ||
ret = { | ||
changed: this.getScriptFileNames(), | ||
changedOrDependencyChanged: this.getScriptFileNames() | ||
}; | ||
} else { | ||
ret = this._projectSnapshot.whatToValidate(this); | ||
// ---- dependency management | ||
collectDependents(filename: string, target: string[]): void { | ||
while (this._dependenciesRecomputeList.length) { | ||
this._processFile(this._dependenciesRecomputeList.pop()); | ||
} | ||
this._projectSnapshot = new ProjectSnapshot(this); | ||
return ret; | ||
filename = path.normalize(filename); | ||
var node = this._dependencies.lookup(filename); | ||
if (node) { | ||
utils.collections.forEach(node.incoming, entry => target.push(entry.key)); | ||
} | ||
} | ||
} | ||
_processFile(filename: string): void { | ||
if(filename.match(/.*\.d\.ts$/)) { | ||
return; | ||
} | ||
filename = path.normalize(filename); | ||
var snapshot = this.getScriptSnapshot(filename), | ||
info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); | ||
// (1) ///-references | ||
info.referencedFiles.forEach(ref => { | ||
var resolvedPath = path.resolve(path.dirname(filename), ref.filename), | ||
normalizedPath = path.normalize(resolvedPath); | ||
this._dependencies.inertEdge(filename, normalizedPath); | ||
}); | ||
// (2) import-require statements | ||
info.importedFiles.forEach(ref => { | ||
var stopDirname = path.normalize(this.getCurrentDirectory()), | ||
dirname = filename, | ||
found = false; | ||
while (!found && dirname.indexOf(stopDirname) === 0) { | ||
dirname = path.dirname(dirname); | ||
var resolvedPath = path.resolve(dirname, ref.filename), | ||
normalizedPath = path.normalize(resolvedPath); | ||
if(this.getScriptSnapshot(normalizedPath + '.ts')) { | ||
this._dependencies.inertEdge(filename, normalizedPath + '.ts'); | ||
found = true; | ||
} else if(this.getScriptSnapshot(normalizedPath + '.d.ts')) { | ||
this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); | ||
found = true; | ||
} | ||
} | ||
}); | ||
} | ||
} |
104
src/utils.ts
@@ -5,5 +5,5 @@ | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
export function lookup<T>(collection:{[keys:string]:T}, key:string):T { | ||
if(hasOwnProperty.call(collection, key)) { | ||
export function lookup<T>(collection: { [keys: string]: T }, key: string): T { | ||
if (hasOwnProperty.call(collection, key)) { | ||
return collection[key]; | ||
@@ -13,9 +13,9 @@ } | ||
} | ||
export function insert<T>(collection:{[keys:string]:T}, key:string, value:T):void { | ||
export function insert<T>(collection: { [keys: string]: T }, key: string, value: T): void { | ||
collection[key] = value; | ||
} | ||
export function lookupOrInsert<T>(collection:{[keys:string]:T}, key:string, value:T):T { | ||
if(hasOwnProperty.call(collection, key)) { | ||
export function lookupOrInsert<T>(collection: { [keys: string]: T }, key: string, value: T): T { | ||
if (hasOwnProperty.call(collection, key)) { | ||
return collection[key]; | ||
@@ -27,8 +27,8 @@ } else { | ||
} | ||
export function forEach<T>(collection:{[keys:string]:T}, callback:(entry:{key:string; value:T;})=>void):void { | ||
export function forEach<T>(collection: { [keys: string]: T }, callback: (entry: { key: string; value: T; }) => void): void { | ||
for (var key in collection) { | ||
if (hasOwnProperty.call(collection, key)) { | ||
callback({ | ||
key: key, | ||
key: key, | ||
value: collection[key] | ||
@@ -39,4 +39,4 @@ }); | ||
} | ||
export function contains(collection:{[keys:string]:any}, key:string):boolean { | ||
export function contains(collection: { [keys: string]: any }, key: string): boolean { | ||
return hasOwnProperty.call(collection, key); | ||
@@ -52,7 +52,7 @@ } | ||
export var empty = ''; | ||
export var eolUnix = '\r\n'; | ||
export function format(value:string, ...rest:any[]):string { | ||
return value.replace(/({\d+})/g, function(match) { | ||
export function format(value: string, ...rest: any[]): string { | ||
return value.replace(/({\d+})/g, function (match) { | ||
var index = match.substring(1, match.length - 1); | ||
@@ -65,28 +65,28 @@ return rest[index] || match; | ||
export module graph { | ||
export interface Node<T> { | ||
data:T; | ||
incoming:{[key:string]:Node<T>}; | ||
outgoing:{[key:string]:Node<T>}; | ||
data: T; | ||
incoming: { [key: string]: Node<T> }; | ||
outgoing: { [key: string]: Node<T> }; | ||
} | ||
export function newNode<T>(data:T):Node<T> { | ||
export function newNode<T>(data: T): Node<T> { | ||
return { | ||
data: data, | ||
incoming: {}, | ||
data: data, | ||
incoming: {}, | ||
outgoing: {} | ||
}; | ||
} | ||
export class Graph<T> { | ||
private _nodes:{[key:string]:Node<T>} = {}; | ||
constructor(private _hashFn:(element:T)=>string) { | ||
private _nodes: { [key: string]: Node<T> } = {}; | ||
constructor(private _hashFn: (element: T) => string) { | ||
// empty | ||
} | ||
public traverse(start:T, inwards:boolean, callback:(data:T)=>void):void { | ||
public traverse(start: T, inwards: boolean, callback: (data: T) => void): void { | ||
var startNode = this.lookup(start); | ||
if(!startNode) { | ||
if (!startNode) { | ||
return; | ||
@@ -96,6 +96,6 @@ } | ||
} | ||
private _traverse(node:Node<T>, inwards:boolean, seen:{[key:string]:boolean}, callback:(data:T)=>void):void { | ||
private _traverse(node: Node<T>, inwards: boolean, seen: { [key: string]: boolean }, callback: (data: T) => void): void { | ||
var key = this._hashFn(node.data); | ||
if(collections.contains(seen, key)) { | ||
if (collections.contains(seen, key)) { | ||
return; | ||
@@ -106,17 +106,17 @@ } | ||
var nodes = inwards ? node.outgoing : node.incoming; | ||
collections.forEach(nodes, (entry) => this._traverse(entry.value, inwards, seen, callback)); | ||
collections.forEach(nodes,(entry) => this._traverse(entry.value, inwards, seen, callback)); | ||
} | ||
public inertEdge(from:T, to:T):void { | ||
var fromNode = this.lookupOrInsertNode(from), | ||
public inertEdge(from: T, to: T): void { | ||
var fromNode = this.lookupOrInsertNode(from), | ||
toNode = this.lookupOrInsertNode(to); | ||
fromNode.outgoing[this._hashFn(to)] = toNode; | ||
toNode.incoming[this._hashFn(from)] = fromNode; | ||
} | ||
public removeNode(data:T):void { | ||
public removeNode(data: T): void { | ||
var key = this._hashFn(data); | ||
delete this._nodes[key]; | ||
collections.forEach(this._nodes, (entry) => { | ||
collections.forEach(this._nodes,(entry) => { | ||
delete entry.value.outgoing[key]; | ||
@@ -126,20 +126,20 @@ delete entry.value.incoming[key]; | ||
} | ||
public lookupOrInsertNode(data:T):Node<T> { | ||
public lookupOrInsertNode(data: T): Node<T> { | ||
var key = this._hashFn(data), | ||
node = collections.lookup(this._nodes, key); | ||
if(!node) { | ||
if (!node) { | ||
node = newNode(data); | ||
this._nodes[key] = node; | ||
} | ||
return node; | ||
} | ||
public lookup(data:T):Node<T> { | ||
public lookup(data: T): Node<T> { | ||
return collections.lookup(this._nodes, this._hashFn(data)); | ||
} | ||
} | ||
} |
2236513
44622
32