Skip to content

Commit c1fc2b9

Browse files
authored
Merge pull request #171 from cartant/issue-161
Refactored to use canonical file names
2 parents f233eb6 + e714a14 commit c1fc2b9

File tree

3 files changed

+152
-72
lines changed

3 files changed

+152
-72
lines changed

lib/Host.js

Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
var commondir = require('commondir');
44
var events = require('events');
55
var fs = require('fs');
6+
var realpath = require('fs.realpath')
67
var log = require('util').debuglog(require('../package').name);
8+
var trace = require('util').debuglog(require('../package').name + '-trace');
79
var os = require('os');
810
var path = require('path');
911
var util = require('util');
1012

1113
module.exports = function (ts) {
12-
function Host(currentDirectory, languageVersion) {
13-
this.currentDirectory = currentDirectory;
14+
function Host(currentDirectory, outputDirectory, languageVersion) {
15+
this.currentDirectory = this.getCanonicalFileName(path.resolve(currentDirectory));
16+
this.outputDirectory = this.getCanonicalFileName(path.resolve(outputDirectory));
1417
this.languageVersion = languageVersion;
1518
this.files = {};
1619
this.previousFiles = {};
@@ -31,13 +34,21 @@ module.exports = function (ts) {
3134
log('Resetting (version %d)', this.version);
3235
};
3336

34-
Host.prototype._normalizedRelative = function(filename) {
35-
return ts.normalizePath(path.relative(this.currentDirectory, path.resolve(filename)));
36-
};
37-
3837
Host.prototype._addFile = function (filename, root) {
39-
var normalized = this._normalizedRelative(filename);
40-
log('Parsing %s (norm: %s)', filename, normalized);
38+
39+
// Ensure that the relative, non-canonical file name is what's passed
40+
// to 'createSourceFile', as that's the name that will be used in error
41+
// messages, etc.
42+
43+
var relative = ts.normalizeSlashes(path.relative(
44+
this.currentDirectory,
45+
path.resolve(
46+
this.currentDirectory,
47+
filename
48+
)
49+
));
50+
var canonical = this._canonical(filename);
51+
trace('Parsing %s', canonical);
4152

4253
var text;
4354
try {
@@ -47,52 +58,44 @@ module.exports = function (ts) {
4758
}
4859

4960
var file;
50-
var current = this.files[normalized];
51-
var previous = this.previousFiles[normalized];
61+
var current = this.files[canonical];
62+
var previous = this.previousFiles[canonical];
5263
var version;
5364

5465
if (current && current.contents === text) {
5566
file = current.ts;
5667
version = current.version;
57-
log('Reused current file %s (version %d)', normalized, version);
68+
trace('Reused current file %s (version %d)', canonical, version);
5869
} else if (previous && previous.contents === text) {
5970
file = previous.ts;
6071
version = previous.version;
61-
log('Reused previous file %s (version %d)', normalized, version);
72+
trace('Reused previous file %s (version %d)', canonical, version);
6273
} else {
63-
file = ts.createSourceFile(filename, text, this.languageVersion, true);
74+
file = ts.createSourceFile(relative, text, this.languageVersion, true);
6475
version = this.version;
65-
log('New version of source file %s (version %d)', normalized, version);
76+
trace('New version of source file %s (version %d)', canonical, version);
6677
}
6778

68-
this.files[normalized] = {
69-
filename: filename,
79+
this.files[canonical] = {
80+
filename: relative,
7081
contents: text,
7182
ts: file,
7283
root: root,
7384
version: version
7485
};
75-
76-
this._emitFile(normalized);
86+
this.emit('file', canonical, relative);
7787

7888
return file;
7989
};
8090

81-
Host.prototype._emitFile = function (normalized) {
82-
var idPath = './' + normalized;
83-
var fullPath = path.resolve(idPath);
84-
this.emit('file', fullPath, idPath);
85-
}
86-
8791
Host.prototype.getSourceFile = function (filename) {
88-
var normalized = this._normalizedRelative(filename);
89-
90-
if (this.files[normalized])
91-
return this.files[normalized].ts;
92-
93-
if (normalized === '__lib.d.ts')
92+
if (filename === '__lib.d.ts') {
9493
return this.libDefault;
95-
94+
}
95+
var canonical = this._canonical(filename);
96+
if (this.files[canonical]) {
97+
return this.files[canonical].ts;
98+
}
9699
return this._addFile(filename, false);
97100
};
98101

@@ -103,17 +106,27 @@ module.exports = function (ts) {
103106
};
104107

105108
Host.prototype.writeFile = function (filename, data) {
106-
var normalized = this._normalizedRelative(filename);
107-
log('Cache write %s (norm: %s)', filename, normalized);
108-
this.output[normalized] = data;
109+
110+
var outputCanonical = this._canonical(filename);
111+
log('Cache write %s', outputCanonical);
112+
this.output[outputCanonical] = data;
113+
114+
var sourceCanonical = this._inferSourceCanonical(outputCanonical);
115+
var sourceFollowed = this._follow(path.dirname(sourceCanonical)) + '/' + path.basename(sourceCanonical);
116+
117+
if (sourceFollowed !== sourceCanonical) {
118+
outputCanonical = this._inferOutputCanonical(sourceFollowed);
119+
log('Cache write (followed) %s', outputCanonical);
120+
this.output[outputCanonical] = data;
121+
}
109122
};
110123

111124
Host.prototype.getCurrentDirectory = function () {
112125
return this.currentDirectory;
113126
};
114127

115128
Host.prototype.getCanonicalFileName = function (filename) {
116-
return this._normalizedRelative(filename);
129+
return ts.normalizeSlashes(ts.sys.useCaseSensitiveFileNames ? filename : filename.toLowerCase());
117130
};
118131

119132
Host.prototype.useCaseSensitiveFileNames = function () {
@@ -130,8 +143,7 @@ module.exports = function (ts) {
130143
};
131144

132145
Host.prototype.readFile = function (filename) {
133-
var normalized = this._normalizedRelative(filename);
134-
return ts.sys.readFile(normalized);
146+
return ts.sys.readFile(filename);
135147
};
136148

137149
Host.prototype._rootDir = function () {
@@ -140,10 +152,91 @@ module.exports = function (ts) {
140152
if (!Object.hasOwnProperty.call(this.files, filename)) continue;
141153
if (/\.d\.ts$/.test(filename)) continue;
142154

143-
dirs.push(path.dirname(filename));
155+
dirs.push(this.getCanonicalFileName(path.dirname(filename)));
144156
}
145157
var result = commondir(this.currentDirectory, dirs);
146-
return result;
158+
return this.getCanonicalFileName(result);
159+
};
160+
161+
Host.prototype._rootFilenames = function () {
162+
163+
var rootFilenames = [];
164+
165+
for (var filename in this.files) {
166+
if (!Object.hasOwnProperty.call(this.files, filename)) continue;
167+
if (!this.files[filename].root) continue;
168+
rootFilenames.push(filename);
169+
}
170+
return rootFilenames;
171+
}
172+
173+
Host.prototype._output = function (filename) {
174+
175+
var outputCanonical = this._inferOutputCanonical(filename);
176+
log('Cache read %s', outputCanonical);
177+
178+
var output = this.output[outputCanonical];
179+
if (!output) {
180+
log('Cache miss on %s', outputCanonical);
181+
}
182+
return output;
183+
}
184+
185+
Host.prototype._canonical = function (filename) {
186+
return this.getCanonicalFileName(path.resolve(
187+
this.currentDirectory,
188+
filename
189+
));
190+
}
191+
192+
Host.prototype._inferOutputCanonical = function (filename) {
193+
194+
var sourceCanonical = this._canonical(filename);
195+
var outputRelative = path.relative(
196+
this._rootDir(),
197+
sourceCanonical
198+
);
199+
var outputCanonical = this.getCanonicalFileName(path.resolve(
200+
this.outputDirectory,
201+
outputRelative
202+
));
203+
return outputCanonical;
204+
}
205+
206+
Host.prototype._inferSourceCanonical = function (filename) {
207+
208+
var outputCanonical = this._canonical(filename);
209+
var outputRelative = path.relative(
210+
this.outputDirectory,
211+
outputCanonical
212+
);
213+
var sourceCanonical = this.getCanonicalFileName(path.resolve(
214+
this._rootDir(),
215+
outputRelative
216+
));
217+
return sourceCanonical;
218+
}
219+
220+
Host.prototype._follow = function (filename) {
221+
222+
filename = this._canonical(filename);
223+
var basename;
224+
var parts = [];
225+
226+
do {
227+
var stats = fs.lstatSync(filename);
228+
if (stats.isSymbolicLink()) {
229+
filename = realpath.realpathSync(filename);
230+
} else {
231+
basename = path.basename(filename);
232+
if (basename) {
233+
parts.unshift(basename);
234+
filename = path.dirname(filename);
235+
}
236+
}
237+
} while (basename);
238+
239+
return ts.normalizeSlashes(filename + parts.join('/'));
147240
};
148241

149242
return Host;

lib/Tsifier.js

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var events = require('events');
55
var extend = require('util')._extend;
66
var fs = require('fs');
77
var log = require('util').debuglog(require('../package').name);
8+
var trace = require('util').debuglog(require('../package').name + '-trace');
89
var path = require('path');
910
var through = require('through2');
1011
var time = require('./time');
@@ -38,10 +39,6 @@ module.exports = function (ts) {
3839
return file.replace(/\.\w+$/i, extension);
3940
}
4041

41-
function getRelativeFilename(file) {
42-
return './' + ts.normalizeSlashes(path.relative(currentDirectory, file));
43-
}
44-
4542
function fileExists(file) {
4643
try {
4744
var stats = fs.lstatSync(file);
@@ -128,7 +125,11 @@ module.exports = function (ts) {
128125
self.opts = parsedOptions.options;
129126
self.files = parsedOptions.fileNames;
130127
self.bopts = bopts;
131-
self.host = new Host(currentDirectory, this.opts.target);
128+
self.host = new Host(
129+
currentDirectory,
130+
this.opts.outDir,
131+
this.opts.target
132+
);
132133

133134
self.host.on('file', function (file, id) {
134135
self.emit('file', file, id);
@@ -140,15 +141,15 @@ module.exports = function (ts) {
140141
Tsifier.prototype.reset = function () {
141142
var self = this;
142143
self.host._reset();
143-
self.addAll(self.files.map(getRelativeFilename));
144+
self.addFiles(self.files);
144145
};
145146

146147
Tsifier.prototype.generateCache = function (files) {
147-
this.addAll(files.map(getRelativeFilename));
148+
this.addFiles(files);
148149
this.compile();
149150
};
150151

151-
Tsifier.prototype.addAll = function (files) {
152+
Tsifier.prototype.addFiles = function (files) {
152153
var self = this;
153154
files.forEach(function (file) {
154155
self.host._addFile(file, true);
@@ -157,12 +158,7 @@ module.exports = function (ts) {
157158

158159
Tsifier.prototype.compile = function () {
159160
var self = this;
160-
var rootFilenames = [];
161-
for (var filename in self.host.files) {
162-
if (!Object.hasOwnProperty.call(self.host.files, filename)) continue;
163-
if (!self.host.files[filename].root) continue;
164-
rootFilenames.push(filename);
165-
}
161+
var rootFilenames = self.host._rootFilenames();
166162

167163
log('Compiling files:');
168164
rootFilenames.forEach(function (file) { log(' %s', file); });
@@ -252,7 +248,7 @@ module.exports = function (ts) {
252248
Tsifier.prototype.transform = function (file) {
253249
var self = this;
254250

255-
log('Transforming %s', file);
251+
trace('Transforming %s', file);
256252

257253
if (isTypescriptDeclaration(file)) {
258254
return through(transform);
@@ -271,7 +267,7 @@ module.exports = function (ts) {
271267
if (self.host.error)
272268
return;
273269

274-
var compiled = self.getCompiledFile(getRelativeFilename(file));
270+
var compiled = self.getCompiledFile(file);
275271
if (compiled) {
276272
this.push(compiled);
277273
}
@@ -282,40 +278,30 @@ module.exports = function (ts) {
282278

283279
Tsifier.prototype.getCompiledFile = function (inputFile, alreadyMissedCache) {
284280
var self = this;
285-
var normalized = ts.normalizePath(inputFile);
286-
var rootDir = self.host._rootDir();
287-
288281
var outputExtension = (self.opts.jsx === ts.JsxEmit.Preserve && isTsx(inputFile)) ? '.jsx' : '.js';
289-
var outputFile = '__tsify__/' + ts.normalizeSlashes(path.relative(
290-
rootDir,
291-
path.resolve(replaceFileExtension(normalized, outputExtension))
292-
));
293-
var output = self.host.output[outputFile];
294-
295-
log('Cache read %s (norm: %s)', outputFile, normalized);
282+
var output = self.host._output(replaceFileExtension(inputFile, outputExtension));
296283

297284
if (!output) {
298285
if (alreadyMissedCache)
299286
return;
300-
log('Cache miss on %s (norm: %s)', outputFile, normalized);
301287
self.generateCache([inputFile]);
302288
if (self.host.error)
303289
return;
304290
return self.getCompiledFile(inputFile, true);
305291
}
306292

307293
if (self.opts.inlineSourceMap) {
308-
output = self.setFullSourcePathInSourcemap(output, normalized);
294+
output = self.setSourcePathInSourcemap(output, inputFile);
309295
}
310-
311296
return output;
312297
};
313298

314-
Tsifier.prototype.setFullSourcePathInSourcemap = function (output, normalized) {
299+
Tsifier.prototype.setSourcePathInSourcemap = function (output, inputFile) {
315300
var self = this;
316-
if (self.bopts.basedir) {
317-
normalized = ts.normalizeSlashes(path.relative(self.bopts.basedir, normalized));
318-
}
301+
var normalized = ts.normalizePath(path.relative(
302+
self.bopts.basedir || currentDirectory,
303+
inputFile
304+
));
319305

320306
var sourcemap = convert.fromComment(output);
321307
sourcemap.setProperty('sources', [normalized]);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"eslint": "^2.7.0",
4040
"event-stream": "^3.3.1",
4141
"fs-extra": "^0.28.0",
42+
"fs.realpath": "^1.0.0",
4243
"node-foo": "^0.2.3",
4344
"semver": "^5.1.0",
4445
"source-map": "^0.5.3",

0 commit comments

Comments
 (0)