From 892194dc19e29285e1f6ac862efcdde8770978cd Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Wed, 3 Aug 2016 18:45:57 +1000 Subject: [PATCH 1/5] Used the TypeScript getCanonicalFileName --- lib/Host.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Host.js b/lib/Host.js index 9b670fb..57db103 100644 --- a/lib/Host.js +++ b/lib/Host.js @@ -113,7 +113,7 @@ module.exports = function (ts) { }; Host.prototype.getCanonicalFileName = function (filename) { - return this._normalizedRelative(filename); + return ts.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase(); }; Host.prototype.useCaseSensitiveFileNames = function () { From d6ea8832f019f1b22904e7b86d8fceae7f8780df Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Thu, 4 Aug 2016 10:22:33 +1000 Subject: [PATCH 2/5] Normalized slashes in getCanonicalFileName --- lib/Host.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Host.js b/lib/Host.js index 57db103..3f96aa1 100644 --- a/lib/Host.js +++ b/lib/Host.js @@ -113,7 +113,7 @@ module.exports = function (ts) { }; Host.prototype.getCanonicalFileName = function (filename) { - return ts.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase(); + return ts.normalizeSlashes(ts.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase()); }; Host.prototype.useCaseSensitiveFileNames = function () { From b5b41426d75154269b011a7d44044693571d22b8 Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Thu, 4 Aug 2016 15:42:05 +1000 Subject: [PATCH 3/5] Refactored to use canonical file names --- lib/Host.js | 119 +++++++++++++++++++++++++++++++++---------------- lib/Tsifier.js | 53 +++++++--------------- 2 files changed, 98 insertions(+), 74 deletions(-) diff --git a/lib/Host.js b/lib/Host.js index 3f96aa1..17c18a3 100644 --- a/lib/Host.js +++ b/lib/Host.js @@ -9,8 +9,13 @@ var path = require('path'); var util = require('util'); module.exports = function (ts) { - function Host(currentDirectory, languageVersion) { - this.currentDirectory = currentDirectory; + function replaceFileExtension(file, extension) { + return file.replace(/\.\w+$/i, extension); + } + + function Host(currentDirectory, outputDirectory, languageVersion) { + this.currentDirectory = this.getCanonicalFileName(path.resolve(currentDirectory)); + this.outputDirectory = this.getCanonicalFileName(path.resolve(outputDirectory)); this.languageVersion = languageVersion; this.files = {}; this.previousFiles = {}; @@ -31,13 +36,21 @@ module.exports = function (ts) { log('Resetting (version %d)', this.version); }; - Host.prototype._normalizedRelative = function(filename) { - return ts.normalizePath(path.relative(this.currentDirectory, path.resolve(filename))); - }; - Host.prototype._addFile = function (filename, root) { - var normalized = this._normalizedRelative(filename); - log('Parsing %s (norm: %s)', filename, normalized); + + // Ensure that the relative, non-canonical file name is what's passed + // to 'createSourceFile', as that's the name that will be used in error + // messages, etc. + + var relative = ts.normalizeSlashes(path.relative( + this.currentDirectory, + path.resolve( + this.currentDirectory, + filename + ) + )); + var canonical = this._currentCanonical(filename); + log('Parsing %s', canonical); var text; try { @@ -47,52 +60,44 @@ module.exports = function (ts) { } var file; - var current = this.files[normalized]; - var previous = this.previousFiles[normalized]; + var current = this.files[canonical]; + var previous = this.previousFiles[canonical]; var version; if (current && current.contents === text) { file = current.ts; version = current.version; - log('Reused current file %s (version %d)', normalized, version); + log('Reused current file %s (version %d)', canonical, version); } else if (previous && previous.contents === text) { file = previous.ts; version = previous.version; - log('Reused previous file %s (version %d)', normalized, version); + log('Reused previous file %s (version %d)', canonical, version); } else { - file = ts.createSourceFile(filename, text, this.languageVersion, true); + file = ts.createSourceFile(relative, text, this.languageVersion, true); version = this.version; - log('New version of source file %s (version %d)', normalized, version); + log('New version of source file %s (version %d)', canonical, version); } - this.files[normalized] = { - filename: filename, + this.files[canonical] = { + filename: relative, contents: text, ts: file, root: root, version: version }; - - this._emitFile(normalized); + this.emit('file', canonical, relative); return file; }; - Host.prototype._emitFile = function (normalized) { - var idPath = './' + normalized; - var fullPath = path.resolve(idPath); - this.emit('file', fullPath, idPath); - } - Host.prototype.getSourceFile = function (filename) { - var normalized = this._normalizedRelative(filename); - - if (this.files[normalized]) - return this.files[normalized].ts; - - if (normalized === '__lib.d.ts') + if (filename === '__lib.d.ts') { return this.libDefault; - + } + var canonical = this._currentCanonical(filename); + if (this.files[canonical]) { + return this.files[canonical].ts; + } return this._addFile(filename, false); }; @@ -103,9 +108,9 @@ module.exports = function (ts) { }; Host.prototype.writeFile = function (filename, data) { - var normalized = this._normalizedRelative(filename); - log('Cache write %s (norm: %s)', filename, normalized); - this.output[normalized] = data; + var canonical = this._currentCanonical(filename); + log('Cache write %s', canonical); + this.output[canonical] = data; }; Host.prototype.getCurrentDirectory = function () { @@ -130,21 +135,59 @@ module.exports = function (ts) { }; Host.prototype.readFile = function (filename) { - var normalized = this._normalizedRelative(filename); - return ts.sys.readFile(normalized); + return ts.sys.readFile(filename); }; + Host.prototype._currentCanonical = function (filename) { + return this.getCanonicalFileName(path.resolve( + this.currentDirectory, + filename + )); + } + Host.prototype._rootDir = function () { var dirs = []; for (var filename in this.files) { if (!Object.hasOwnProperty.call(this.files, filename)) continue; if (/\.d\.ts$/.test(filename)) continue; - dirs.push(path.dirname(filename)); + dirs.push(this.getCanonicalFileName(path.dirname(filename))); } var result = commondir(this.currentDirectory, dirs); - return result; + return this.getCanonicalFileName(result); }; + Host.prototype._rootFilenames = function () { + + var rootFilenames = []; + + for (var filename in this.files) { + if (!Object.hasOwnProperty.call(this.files, filename)) continue; + if (!this.files[filename].root) continue; + rootFilenames.push(filename); + } + return rootFilenames; + } + + Host.prototype._output = function (filename, extension) { + + var inputCanonical = this._currentCanonical(filename); + var outputRelative = path.relative( + this._rootDir(), + replaceFileExtension(inputCanonical, extension) + ); + var outputCanonical = this.getCanonicalFileName(path.resolve( + this.outputDirectory, + outputRelative + )); + log('Cache read %s', outputCanonical); + + var output = this.output[outputCanonical]; + if (!output) { + log('Cache miss on %s', outputCanonical); + } + return output; + } + return Host; }; diff --git a/lib/Tsifier.js b/lib/Tsifier.js index 87835dd..231227b 100644 --- a/lib/Tsifier.js +++ b/lib/Tsifier.js @@ -34,14 +34,6 @@ module.exports = function (ts) { return (/\.d\.ts$/i).test(file); } - function replaceFileExtension(file, extension) { - return file.replace(/\.\w+$/i, extension); - } - - function getRelativeFilename(file) { - return './' + ts.normalizeSlashes(path.relative(currentDirectory, file)); - } - function fileExists(file) { try { var stats = fs.lstatSync(file); @@ -128,7 +120,11 @@ module.exports = function (ts) { self.opts = parsedOptions.options; self.files = parsedOptions.fileNames; self.bopts = bopts; - self.host = new Host(currentDirectory, this.opts.target); + self.host = new Host( + currentDirectory, + this.opts.outDir, + this.opts.target + ); self.host.on('file', function (file, id) { self.emit('file', file, id); @@ -140,15 +136,15 @@ module.exports = function (ts) { Tsifier.prototype.reset = function () { var self = this; self.host._reset(); - self.addAll(self.files.map(getRelativeFilename)); + self.addFiles(self.files); }; Tsifier.prototype.generateCache = function (files) { - this.addAll(files.map(getRelativeFilename)); + this.addFiles(files); this.compile(); }; - Tsifier.prototype.addAll = function (files) { + Tsifier.prototype.addFiles = function (files) { var self = this; files.forEach(function (file) { self.host._addFile(file, true); @@ -157,12 +153,7 @@ module.exports = function (ts) { Tsifier.prototype.compile = function () { var self = this; - var rootFilenames = []; - for (var filename in self.host.files) { - if (!Object.hasOwnProperty.call(self.host.files, filename)) continue; - if (!self.host.files[filename].root) continue; - rootFilenames.push(filename); - } + var rootFilenames = self.host._rootFilenames(); log('Compiling files:'); rootFilenames.forEach(function (file) { log(' %s', file); }); @@ -271,7 +262,7 @@ module.exports = function (ts) { if (self.host.error) return; - var compiled = self.getCompiledFile(getRelativeFilename(file)); + var compiled = self.getCompiledFile(file); if (compiled) { this.push(compiled); } @@ -282,22 +273,12 @@ module.exports = function (ts) { Tsifier.prototype.getCompiledFile = function (inputFile, alreadyMissedCache) { var self = this; - var normalized = ts.normalizePath(inputFile); - var rootDir = self.host._rootDir(); - var outputExtension = (self.opts.jsx === ts.JsxEmit.Preserve && isTsx(inputFile)) ? '.jsx' : '.js'; - var outputFile = '__tsify__/' + ts.normalizeSlashes(path.relative( - rootDir, - path.resolve(replaceFileExtension(normalized, outputExtension)) - )); - var output = self.host.output[outputFile]; - - log('Cache read %s (norm: %s)', outputFile, normalized); + var output = self.host._output(inputFile, outputExtension); if (!output) { if (alreadyMissedCache) return; - log('Cache miss on %s (norm: %s)', outputFile, normalized); self.generateCache([inputFile]); if (self.host.error) return; @@ -305,17 +286,17 @@ module.exports = function (ts) { } if (self.opts.inlineSourceMap) { - output = self.setFullSourcePathInSourcemap(output, normalized); + output = self.setSourcePathInSourcemap(output, inputFile); } - return output; }; - Tsifier.prototype.setFullSourcePathInSourcemap = function (output, normalized) { + Tsifier.prototype.setSourcePathInSourcemap = function (output, inputFile) { var self = this; - if (self.bopts.basedir) { - normalized = ts.normalizeSlashes(path.relative(self.bopts.basedir, normalized)); - } + var normalized = ts.normalizePath(path.relative( + self.bopts.basedir || currentDirectory, + inputFile + )); var sourcemap = convert.fromComment(output); sourcemap.setProperty('sources', [normalized]); From a56849c73b054732dda6bf2d7abd4b34d38b2caa Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Fri, 5 Aug 2016 09:35:59 +1000 Subject: [PATCH 4/5] Followed symlinks --- lib/Host.js | 99 +++++++++++++++++++++++++++++++++++++------------- lib/Tsifier.js | 6 ++- package.json | 1 + 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/lib/Host.js b/lib/Host.js index 17c18a3..175db2f 100644 --- a/lib/Host.js +++ b/lib/Host.js @@ -3,16 +3,13 @@ var commondir = require('commondir'); var events = require('events'); var fs = require('fs'); +var realpath = require('fs.realpath') var log = require('util').debuglog(require('../package').name); var os = require('os'); var path = require('path'); var util = require('util'); module.exports = function (ts) { - function replaceFileExtension(file, extension) { - return file.replace(/\.\w+$/i, extension); - } - function Host(currentDirectory, outputDirectory, languageVersion) { this.currentDirectory = this.getCanonicalFileName(path.resolve(currentDirectory)); this.outputDirectory = this.getCanonicalFileName(path.resolve(outputDirectory)); @@ -49,7 +46,7 @@ module.exports = function (ts) { filename ) )); - var canonical = this._currentCanonical(filename); + var canonical = this._canonical(filename); log('Parsing %s', canonical); var text; @@ -94,7 +91,7 @@ module.exports = function (ts) { if (filename === '__lib.d.ts') { return this.libDefault; } - var canonical = this._currentCanonical(filename); + var canonical = this._canonical(filename); if (this.files[canonical]) { return this.files[canonical].ts; } @@ -108,9 +105,19 @@ module.exports = function (ts) { }; Host.prototype.writeFile = function (filename, data) { - var canonical = this._currentCanonical(filename); - log('Cache write %s', canonical); - this.output[canonical] = data; + + var outputCanonical = this._canonical(filename); + log('Cache write %s', outputCanonical); + this.output[outputCanonical] = data; + + var sourceCanonical = this._inferSourceCanonical(outputCanonical); + var sourceFollowed = this._follow(path.dirname(sourceCanonical)) + '/' + path.basename(sourceCanonical); + + if (sourceFollowed !== sourceCanonical) { + outputCanonical = this._inferOutputCanonical(sourceFollowed); + log('Cache write (followed) %s', outputCanonical); + this.output[outputCanonical] = data; + } }; Host.prototype.getCurrentDirectory = function () { @@ -138,13 +145,6 @@ module.exports = function (ts) { return ts.sys.readFile(filename); }; - Host.prototype._currentCanonical = function (filename) { - return this.getCanonicalFileName(path.resolve( - this.currentDirectory, - filename - )); - } - Host.prototype._rootDir = function () { var dirs = []; for (var filename in this.files) { @@ -169,25 +169,74 @@ module.exports = function (ts) { return rootFilenames; } - Host.prototype._output = function (filename, extension) { + Host.prototype._output = function (filename) { + + var outputCanonical = this._inferOutputCanonical(filename); + log('Cache read %s', outputCanonical); + + var output = this.output[outputCanonical]; + if (!output) { + log('Cache miss on %s', outputCanonical); + } + return output; + } + + Host.prototype._canonical = function (filename) { + return this.getCanonicalFileName(path.resolve( + this.currentDirectory, + filename + )); + } + + Host.prototype._inferOutputCanonical = function (filename) { - var inputCanonical = this._currentCanonical(filename); + var sourceCanonical = this._canonical(filename); var outputRelative = path.relative( this._rootDir(), - replaceFileExtension(inputCanonical, extension) + sourceCanonical ); var outputCanonical = this.getCanonicalFileName(path.resolve( this.outputDirectory, outputRelative )); - log('Cache read %s', outputCanonical); + return outputCanonical; + } - var output = this.output[outputCanonical]; - if (!output) { - log('Cache miss on %s', outputCanonical); - } - return output; + Host.prototype._inferSourceCanonical = function (filename) { + + var outputCanonical = this._canonical(filename); + var outputRelative = path.relative( + this.outputDirectory, + outputCanonical + ); + var sourceCanonical = this.getCanonicalFileName(path.resolve( + this._rootDir(), + outputRelative + )); + return sourceCanonical; } + Host.prototype._follow = function (filename) { + + filename = this._canonical(filename); + var basename; + var parts = []; + + do { + var stats = fs.lstatSync(filename); + if (stats.isSymbolicLink()) { + filename = realpath.realpathSync(filename); + } else { + basename = path.basename(filename); + if (basename) { + parts.unshift(basename); + filename = path.dirname(filename); + } + } + } while (basename); + + return ts.normalizeSlashes(filename + parts.join('/')); + }; + return Host; }; diff --git a/lib/Tsifier.js b/lib/Tsifier.js index 231227b..d9cc377 100644 --- a/lib/Tsifier.js +++ b/lib/Tsifier.js @@ -34,6 +34,10 @@ module.exports = function (ts) { return (/\.d\.ts$/i).test(file); } + function replaceFileExtension(file, extension) { + return file.replace(/\.\w+$/i, extension); + } + function fileExists(file) { try { var stats = fs.lstatSync(file); @@ -274,7 +278,7 @@ module.exports = function (ts) { Tsifier.prototype.getCompiledFile = function (inputFile, alreadyMissedCache) { var self = this; var outputExtension = (self.opts.jsx === ts.JsxEmit.Preserve && isTsx(inputFile)) ? '.jsx' : '.js'; - var output = self.host._output(inputFile, outputExtension); + var output = self.host._output(replaceFileExtension(inputFile, outputExtension)); if (!output) { if (alreadyMissedCache) diff --git a/package.json b/package.json index 359bd5b..32851f1 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint": "^2.7.0", "event-stream": "^3.3.1", "fs-extra": "^0.28.0", + "fs.realpath": "^1.0.0", "node-foo": "^0.2.3", "semver": "^5.1.0", "source-map": "^0.5.3", From e714a140998e01d8a314160565987bc17b173178 Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Fri, 5 Aug 2016 17:46:22 +1000 Subject: [PATCH 5/5] Moved some verbose logging to another logger --- lib/Host.js | 9 +++++---- lib/Tsifier.js | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/Host.js b/lib/Host.js index 175db2f..42f8b14 100644 --- a/lib/Host.js +++ b/lib/Host.js @@ -5,6 +5,7 @@ var events = require('events'); var fs = require('fs'); var realpath = require('fs.realpath') var log = require('util').debuglog(require('../package').name); +var trace = require('util').debuglog(require('../package').name + '-trace'); var os = require('os'); var path = require('path'); var util = require('util'); @@ -47,7 +48,7 @@ module.exports = function (ts) { ) )); var canonical = this._canonical(filename); - log('Parsing %s', canonical); + trace('Parsing %s', canonical); var text; try { @@ -64,15 +65,15 @@ module.exports = function (ts) { if (current && current.contents === text) { file = current.ts; version = current.version; - log('Reused current file %s (version %d)', canonical, version); + trace('Reused current file %s (version %d)', canonical, version); } else if (previous && previous.contents === text) { file = previous.ts; version = previous.version; - log('Reused previous file %s (version %d)', canonical, version); + trace('Reused previous file %s (version %d)', canonical, version); } else { file = ts.createSourceFile(relative, text, this.languageVersion, true); version = this.version; - log('New version of source file %s (version %d)', canonical, version); + trace('New version of source file %s (version %d)', canonical, version); } this.files[canonical] = { diff --git a/lib/Tsifier.js b/lib/Tsifier.js index d9cc377..32d9596 100644 --- a/lib/Tsifier.js +++ b/lib/Tsifier.js @@ -5,6 +5,7 @@ var events = require('events'); var extend = require('util')._extend; var fs = require('fs'); var log = require('util').debuglog(require('../package').name); +var trace = require('util').debuglog(require('../package').name + '-trace'); var path = require('path'); var through = require('through2'); var time = require('./time'); @@ -247,7 +248,7 @@ module.exports = function (ts) { Tsifier.prototype.transform = function (file) { var self = this; - log('Transforming %s', file); + trace('Transforming %s', file); if (isTypescriptDeclaration(file)) { return through(transform);