Skip to content

Commit

Permalink
Merge pull request #171 from cartant/issue-161
Browse files Browse the repository at this point in the history
Refactored to use canonical file names
  • Loading branch information
cartant committed Aug 5, 2016
2 parents f233eb6 + e714a14 commit c1fc2b9
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 72 deletions.
171 changes: 132 additions & 39 deletions lib/Host.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
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 trace = require('util').debuglog(require('../package').name + '-trace');
var os = require('os');
var path = require('path');
var util = require('util');

module.exports = function (ts) {
function Host(currentDirectory, languageVersion) {
this.currentDirectory = currentDirectory;
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 = {};
Expand All @@ -31,13 +34,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._canonical(filename);
trace('Parsing %s', canonical);

var text;
try {
Expand All @@ -47,52 +58,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);
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)', normalized, version);
trace('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);
trace('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._canonical(filename);
if (this.files[canonical]) {
return this.files[canonical].ts;
}
return this._addFile(filename, false);
};

Expand All @@ -103,17 +106,27 @@ 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 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 () {
return this.currentDirectory;
};

Host.prototype.getCanonicalFileName = function (filename) {
return this._normalizedRelative(filename);
return ts.normalizeSlashes(ts.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase());
};

Host.prototype.useCaseSensitiveFileNames = function () {
Expand All @@ -130,8 +143,7 @@ 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._rootDir = function () {
Expand All @@ -140,10 +152,91 @@ module.exports = function (ts) {
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) {

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 sourceCanonical = this._canonical(filename);
var outputRelative = path.relative(
this._rootDir(),
sourceCanonical
);
var outputCanonical = this.getCanonicalFileName(path.resolve(
this.outputDirectory,
outputRelative
));
return outputCanonical;
}

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;
Expand Down
52 changes: 19 additions & 33 deletions lib/Tsifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -38,10 +39,6 @@ module.exports = function (ts) {
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);
Expand Down Expand Up @@ -128,7 +125,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);
Expand All @@ -140,15 +141,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);
Expand All @@ -157,12 +158,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); });
Expand Down Expand Up @@ -252,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);
Expand All @@ -271,7 +267,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);
}
Expand All @@ -282,40 +278,30 @@ 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(replaceFileExtension(inputFile, outputExtension));

if (!output) {
if (alreadyMissedCache)
return;
log('Cache miss on %s (norm: %s)', outputFile, normalized);
self.generateCache([inputFile]);
if (self.host.error)
return;
return self.getCompiledFile(inputFile, true);
}

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]);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit c1fc2b9

Please sign in to comment.