Skip to content

Commit bca53dc

Browse files
nwoltmansilverwind
authored andcommitted
path: refactor for performance and consistency
Improve performance by: + Not leaking the `arguments` object! + Getting the last character of a string by index, instead of with `.substr()` or `.slice()` Improve code consistency by: + Using `[]` instead of `.charAt()` where possible + Using a function declaration instead of a var declaration + Using `.slice()` with clearer arguments + Checking if `dir` is truthy in `win32.format` (added tests for this) Improve both by: + Making the reusable `trimArray()` function + Standardizing getting certain path statistics with the new `win32StatPath()` function PR-URL: #1778 Reviewed-By: Сковорода Никита Андреевич <[email protected]> Reviewed-By: Roman Reiss <[email protected]>
1 parent 4614033 commit bca53dc

File tree

2 files changed

+91
-74
lines changed

2 files changed

+91
-74
lines changed

lib/path.js

+69-70
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,29 @@ function normalizeArray(parts, allowAboveRoot) {
3737
return res;
3838
}
3939

40+
// Returns an array with empty elements removed from either end of the input
41+
// array or the original array if no elements need to be removed
42+
function trimArray(arr) {
43+
var lastIndex = arr.length - 1;
44+
var start = 0;
45+
for (; start <= lastIndex; start++) {
46+
if (arr[start])
47+
break;
48+
}
49+
50+
var end = lastIndex;
51+
for (; end >= 0; end--) {
52+
if (arr[end])
53+
break;
54+
}
55+
56+
if (start === 0 && end === lastIndex)
57+
return arr;
58+
if (start > end)
59+
return [];
60+
return arr.slice(start, end + 1);
61+
}
62+
4063
// Regex to split a windows path into three parts: [*, device, slash,
4164
// tail] windows-only
4265
const splitDeviceRe =
@@ -62,9 +85,21 @@ function win32SplitPath(filename) {
6285
return [device, dir, basename, ext];
6386
}
6487

65-
var normalizeUNCRoot = function(device) {
88+
function win32StatPath(path) {
89+
var result = splitDeviceRe.exec(path),
90+
device = result[1] || '',
91+
isUnc = !!device && device[1] !== ':';
92+
return {
93+
device,
94+
isUnc,
95+
isAbsolute: isUnc || !!result[2], // UNC paths are always absolute
96+
tail: result[3]
97+
};
98+
}
99+
100+
function normalizeUNCRoot(device) {
66101
return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
67-
};
102+
}
68103

69104
// path.resolve([from ...], to)
70105
win32.resolve = function() {
@@ -99,11 +134,11 @@ win32.resolve = function() {
99134
continue;
100135
}
101136

102-
var result = splitDeviceRe.exec(path),
103-
device = result[1] || '',
104-
isUnc = device && device.charAt(1) !== ':',
105-
isAbsolute = win32.isAbsolute(path),
106-
tail = result[3];
137+
var result = win32StatPath(path),
138+
device = result.device,
139+
isUnc = result.isUnc,
140+
isAbsolute = result.isAbsolute,
141+
tail = result.tail;
107142

108143
if (device &&
109144
resolvedDevice &&
@@ -147,11 +182,11 @@ win32.resolve = function() {
147182
win32.normalize = function(path) {
148183
assertPath(path);
149184

150-
var result = splitDeviceRe.exec(path),
151-
device = result[1] || '',
152-
isUnc = device && device.charAt(1) !== ':',
153-
isAbsolute = win32.isAbsolute(path),
154-
tail = result[3],
185+
var result = win32StatPath(path),
186+
device = result.device,
187+
isUnc = result.isUnc,
188+
isAbsolute = result.isAbsolute,
189+
tail = result.tail,
155190
trailingSlash = /[\\\/]$/.test(tail);
156191

157192
// Normalize the tail path
@@ -176,23 +211,19 @@ win32.normalize = function(path) {
176211

177212
win32.isAbsolute = function(path) {
178213
assertPath(path);
179-
180-
var result = splitDeviceRe.exec(path),
181-
device = result[1] || '',
182-
isUnc = !!device && device.charAt(1) !== ':';
183-
// UNC paths are always absolute
184-
return !!result[2] || isUnc;
214+
return win32StatPath(path).isAbsolute;
185215
};
186216

187217
win32.join = function() {
188-
function f(p) {
189-
if (typeof p !== 'string') {
190-
throw new TypeError('Arguments to path.join must be strings');
218+
var paths = [];
219+
for (var i = 0; i < arguments.length; i++) {
220+
var arg = arguments[i];
221+
assertPath(arg);
222+
if (arg) {
223+
paths.push(arg);
191224
}
192-
return p;
193225
}
194226

195-
var paths = Array.prototype.filter.call(arguments, f);
196227
var joined = paths.join('\\');
197228

198229
// Make sure that the joined path doesn't start with two slashes, because
@@ -232,25 +263,10 @@ win32.relative = function(from, to) {
232263
var lowerFrom = from.toLowerCase();
233264
var lowerTo = to.toLowerCase();
234265

235-
function trim(arr) {
236-
var start = 0;
237-
for (; start < arr.length; start++) {
238-
if (arr[start] !== '') break;
239-
}
240-
241-
var end = arr.length - 1;
242-
for (; end >= 0; end--) {
243-
if (arr[end] !== '') break;
244-
}
245-
246-
if (start > end) return [];
247-
return arr.slice(start, end + 1);
248-
}
266+
var toParts = trimArray(to.split('\\'));
249267

250-
var toParts = trim(to.split('\\'));
251-
252-
var lowerFromParts = trim(lowerFrom.split('\\'));
253-
var lowerToParts = trim(lowerTo.split('\\'));
268+
var lowerFromParts = trimArray(lowerFrom.split('\\'));
269+
var lowerToParts = trimArray(lowerTo.split('\\'));
254270

255271
var length = Math.min(lowerFromParts.length, lowerToParts.length);
256272
var samePartsLength = length;
@@ -356,15 +372,13 @@ win32.format = function(pathObject) {
356372

357373
var dir = pathObject.dir;
358374
var base = pathObject.base || '';
359-
if (dir.slice(dir.length - 1, dir.length) === win32.sep) {
360-
return dir + base;
375+
if (!dir) {
376+
return base;
361377
}
362-
363-
if (dir) {
364-
return dir + win32.sep + base;
378+
if (dir[dir.length - 1] === win32.sep) {
379+
return dir + base;
365380
}
366-
367-
return base;
381+
return dir + win32.sep + base;
368382
};
369383

370384

@@ -377,7 +391,7 @@ win32.parse = function(pathString) {
377391
}
378392
return {
379393
root: allParts[0],
380-
dir: allParts[0] + allParts[1].slice(0, allParts[1].length - 1),
394+
dir: allParts[0] + allParts[1].slice(0, -1),
381395
base: allParts[2],
382396
ext: allParts[3],
383397
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)
@@ -418,7 +432,7 @@ posix.resolve = function() {
418432
}
419433

420434
resolvedPath = path + '/' + resolvedPath;
421-
resolvedAbsolute = path.charAt(0) === '/';
435+
resolvedAbsolute = path[0] === '/';
422436
}
423437

424438
// At this point the path should be resolved to a full absolute path, but
@@ -437,7 +451,7 @@ posix.normalize = function(path) {
437451
assertPath(path);
438452

439453
var isAbsolute = posix.isAbsolute(path),
440-
trailingSlash = path.substr(-1) === '/';
454+
trailingSlash = path && path[path.length - 1] === '/';
441455

442456
// Normalize the path
443457
path = normalizeArray(path.split('/'), !isAbsolute).join('/');
@@ -455,7 +469,7 @@ posix.normalize = function(path) {
455469
// posix version
456470
posix.isAbsolute = function(path) {
457471
assertPath(path);
458-
return path.charAt(0) === '/';
472+
return !!path && path[0] === '/';
459473
};
460474

461475
// posix version
@@ -487,23 +501,8 @@ posix.relative = function(from, to) {
487501
from = posix.resolve(from).substr(1);
488502
to = posix.resolve(to).substr(1);
489503

490-
function trim(arr) {
491-
var start = 0;
492-
for (; start < arr.length; start++) {
493-
if (arr[start] !== '') break;
494-
}
495-
496-
var end = arr.length - 1;
497-
for (; end >= 0; end--) {
498-
if (arr[end] !== '') break;
499-
}
500-
501-
if (start > end) return [];
502-
return arr.slice(start, end + 1);
503-
}
504-
505-
var fromParts = trim(from.split('/'));
506-
var toParts = trim(to.split('/'));
504+
var fromParts = trimArray(from.split('/'));
505+
var toParts = trimArray(to.split('/'));
507506

508507
var length = Math.min(fromParts.length, toParts.length);
509508
var samePartsLength = length;
@@ -602,7 +601,7 @@ posix.parse = function(pathString) {
602601

603602
return {
604603
root: allParts[0],
605-
dir: allParts[0] + allParts[1].slice(0, allParts[1].length - 1),
604+
dir: allParts[0] + allParts[1].slice(0, -1),
606605
base: allParts[2],
607606
ext: allParts[3],
608607
name: allParts[2].slice(0, allParts[2].length - allParts[3].length)

test/parallel/test-path-parse-format.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ var winPaths = [
1515
'\\\\server two\\shared folder\\file path.zip',
1616
'\\\\teela\\admin$\\system32',
1717
'\\\\?\\UNC\\server\\share'
18+
];
1819

20+
var winSpecialCaseFormatTests = [
21+
[{dir: 'some\\dir'}, 'some\\dir\\'],
22+
[{base: 'index.html'}, 'index.html'],
23+
[{}, '']
1924
];
2025

2126
var unixPaths = [
@@ -30,6 +35,12 @@ var unixPaths = [
3035
'C:\\foo'
3136
];
3237

38+
var unixSpecialCaseFormatTests = [
39+
[{dir: 'some/dir'}, 'some/dir/'],
40+
[{base: 'index.html'}, 'index.html'],
41+
[{}, '']
42+
];
43+
3344
var errors = [
3445
{method: 'parse', input: [null],
3546
message: /Path must be a string. Received null/},
@@ -57,10 +68,12 @@ var errors = [
5768
message: /'pathObject.root' must be a string or undefined, not number/},
5869
];
5970

60-
check(path.win32, winPaths);
61-
check(path.posix, unixPaths);
71+
checkParseFormat(path.win32, winPaths);
72+
checkParseFormat(path.posix, unixPaths);
6273
checkErrors(path.win32);
6374
checkErrors(path.posix);
75+
checkFormat(path.win32, winSpecialCaseFormatTests);
76+
checkFormat(path.posix, unixSpecialCaseFormatTests);
6477

6578
function checkErrors(path) {
6679
errors.forEach(function(errorCase) {
@@ -79,8 +92,7 @@ function checkErrors(path) {
7992
});
8093
}
8194

82-
83-
function check(path, paths) {
95+
function checkParseFormat(path, paths) {
8496
paths.forEach(function(element, index, array) {
8597
var output = path.parse(element);
8698
assert.strictEqual(path.format(output), element);
@@ -89,3 +101,9 @@ function check(path, paths) {
89101
assert.strictEqual(output.ext, path.extname(element));
90102
});
91103
}
104+
105+
function checkFormat(path, testCases) {
106+
testCases.forEach(function(testCase) {
107+
assert.strictEqual(path.format(testCase[0]), testCase[1]);
108+
});
109+
}

0 commit comments

Comments
 (0)