diff --git a/benchmark/common.js b/benchmark/common.js index 511ae384f418bf..c0c7805a5c53db 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -188,11 +188,13 @@ function parseOpts(options) { var num = keys.length; var conf = {}; for (var i = 2; i < process.argv.length; i++) { - var match = process.argv[i].match(/^(.+)=(.+)$/); - if (!match || !match[1] || !match[2] || !options[match[1]]) { + var match = process.argv[i].match(/^(.+)=(.*)$/); + if (!match || !match[1] || !options[match[1]]) { return null; } else { - conf[match[1]] = isFinite(match[2]) ? +match[2] : match[2] + conf[match[1]] = (match[2].length && isFinite(match[2]) + ? +match[2] + : match[2]); num--; } } diff --git a/benchmark/path/basename-posix.js b/benchmark/path/basename-posix.js new file mode 100644 index 00000000000000..d4ea072b57c52d --- /dev/null +++ b/benchmark/path/basename-posix.js @@ -0,0 +1,43 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + pathext: [ + '', + '/', + '/foo', + '/foo/.bar.baz', + ['/foo/.bar.baz', '.baz'].join('|'), + 'foo', + 'foo/bar.', + ['foo/bar.', '.'].join('|'), + '/foo/bar/baz/asdf/quux.html', + ['/foo/bar/baz/asdf/quux.html', '.html'].join('|') + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.posix; + var input = '' + conf.pathext; + var ext; + var extIdx = input.indexOf('|'); + if (extIdx !== -1) { + ext = input.slice(extIdx + 1); + input = input.slice(0, extIdx); + } + + // Force optimization before starting the benchmark + p.basename(input, ext); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.basename)'); + p.basename(input, ext); + + bench.start(); + for (var i = 0; i < n; i++) { + p.basename(input, ext); + } + bench.end(n); +} diff --git a/benchmark/path/basename-win32.js b/benchmark/path/basename-win32.js new file mode 100644 index 00000000000000..08938a20502cc6 --- /dev/null +++ b/benchmark/path/basename-win32.js @@ -0,0 +1,43 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + pathext: [ + '', + 'C:\\', + 'C:\\foo', + 'D:\\foo\\.bar.baz', + ['E:\\foo\\.bar.baz','.baz'].join('|'), + 'foo', + 'foo\\bar.', + ['foo\\bar.', '.'].join('|'), + '\\foo\\bar\\baz\\asdf\\quux.html', + ['\\foo\\bar\\baz\\asdf\\quux.html', '.html'].join('|') + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.pathext; + var ext; + var extIdx = input.indexOf('|'); + if (extIdx !== -1) { + ext = input.slice(extIdx + 1); + input = input.slice(0, extIdx); + } + + // Force optimization before starting the benchmark + p.basename(input, ext); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.basename)'); + p.basename(input, ext); + + bench.start(); + for (var i = 0; i < n; i++) { + p.basename(input, ext); + } + bench.end(n); +} diff --git a/benchmark/path/basename.js b/benchmark/path/basename.js deleted file mode 100644 index 57d9492746b32c..00000000000000 --- a/benchmark/path/basename.js +++ /dev/null @@ -1,26 +0,0 @@ -var common = require('../common.js'); -var path = require('path'); -var v8 = require('v8'); - -var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], -}); - -function main(conf) { - var n = +conf.n; - var p = path[conf.type]; - - // Force optimization before starting the benchmark - p.basename('/foo/bar/baz/asdf/quux.html'); - v8.setFlagsFromString('--allow_natives_syntax'); - eval('%OptimizeFunctionOnNextCall(p.basename)'); - p.basename('/foo/bar/baz/asdf/quux.html'); - - bench.start(); - for (var i = 0; i < n; i++) { - p.basename('/foo/bar/baz/asdf/quux.html'); - p.basename('/foo/bar/baz/asdf/quux.html', '.html'); - } - bench.end(n); -} diff --git a/benchmark/path/dirname.js b/benchmark/path/dirname-posix.js similarity index 62% rename from benchmark/path/dirname.js rename to benchmark/path/dirname-posix.js index e95adf28811a4e..037ac636708793 100644 --- a/benchmark/path/dirname.js +++ b/benchmark/path/dirname-posix.js @@ -3,23 +3,32 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], + path: [ + '', + '/', + '/foo', + '/foo/bar', + 'foo', + 'foo/bar', + '/foo/bar/baz/asdf/quux' + ], + n: [1e6] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; + var p = path.posix; + var input = '' + conf.path; // Force optimization before starting the benchmark - p.dirname('/foo/bar/baz/asdf/quux'); + p.dirname(input); v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(p.dirname)'); - p.dirname('/foo/bar/baz/asdf/quux'); + p.dirname(input); bench.start(); for (var i = 0; i < n; i++) { - p.dirname('/foo/bar/baz/asdf/quux'); + p.dirname(input); } bench.end(n); } diff --git a/benchmark/path/dirname-win32.js b/benchmark/path/dirname-win32.js new file mode 100644 index 00000000000000..cd8c190e978cd2 --- /dev/null +++ b/benchmark/path/dirname-win32.js @@ -0,0 +1,34 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + '', + '\\', + '\\foo', + 'C:\\foo\\bar', + 'foo', + 'foo\\bar', + 'D:\\foo\\bar\\baz\\asdf\\quux' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p.dirname(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.dirname)'); + p.dirname(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p.dirname(input); + } + bench.end(n); +} diff --git a/benchmark/path/extname-posix.js b/benchmark/path/extname-posix.js new file mode 100644 index 00000000000000..41a4a778834b51 --- /dev/null +++ b/benchmark/path/extname-posix.js @@ -0,0 +1,37 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + '', + '/', + '/foo', + 'foo/.bar.baz', + 'index.html', + 'index', + 'foo/bar/..baz.quux', + 'foo/bar/...baz.quux', + '/foo/bar/baz/asdf/quux', + '/foo/bar/baz/asdf/quux.foobarbazasdfquux' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.posix; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p.extname(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.extname)'); + p.extname(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p.extname(input); + } + bench.end(n); +} diff --git a/benchmark/path/extname-win32.js b/benchmark/path/extname-win32.js new file mode 100644 index 00000000000000..fcc1af933749ed --- /dev/null +++ b/benchmark/path/extname-win32.js @@ -0,0 +1,37 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + '', + '\\', + 'C:\\foo', + 'foo\\.bar.baz', + 'index.html', + 'index', + 'foo\\bar\\..baz.quux', + 'foo\\bar\\...baz.quux', + 'D:\\foo\\bar\\baz\\asdf\\quux', + '\\foo\\bar\\baz\\asdf\\quux.foobarbazasdfquux' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p.extname(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.extname)'); + p.extname(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p.extname(input); + } + bench.end(n); +} diff --git a/benchmark/path/format.js b/benchmark/path/format-posix.js similarity index 51% rename from benchmark/path/format.js rename to benchmark/path/format-posix.js index bc77f888116963..6dce6d3fae733a 100644 --- a/benchmark/path/format.js +++ b/benchmark/path/format-posix.js @@ -3,36 +3,33 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e7], + props: [ + ['/', '/home/user/dir', 'index.html', '.html', 'index'].join('|') + ], + n: [1e7] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; - var test = conf.type === 'win32' ? { - root: 'C:\\', - dir: 'C:\\path\\dir', - base: 'index.html', - ext: '.html', - name: 'index' - } : { - root : '/', - dir : '/home/user/dir', - base : 'index.html', - ext : '.html', - name : 'index' + var p = path.posix; + var props = ('' + conf.props).split('|'); + var obj = { + root: props[0] || '', + dir: props[1] || '', + base: props[2] || '', + ext: props[3] || '', + name: props[4] || '', }; // Force optimization before starting the benchmark - p.format(test); + p.format(obj); v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(p.format)'); - p.format(test); + p.format(obj); bench.start(); for (var i = 0; i < n; i++) { - p.format(test); + p.format(obj); } bench.end(n); } diff --git a/benchmark/path/format-win32.js b/benchmark/path/format-win32.js new file mode 100644 index 00000000000000..598393b836b9fb --- /dev/null +++ b/benchmark/path/format-win32.js @@ -0,0 +1,35 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + props: [ + ['C:\\', 'C:\\path\\dir', 'index.html', '.html', 'index'].join('|') + ], + n: [1e7] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var props = ('' + conf.props).split('|'); + var obj = { + root: props[0] || '', + dir: props[1] || '', + base: props[2] || '', + ext: props[3] || '', + name: props[4] || '', + }; + + // Force optimization before starting the benchmark + p.format(obj); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.format)'); + p.format(obj); + + bench.start(); + for (var i = 0; i < n; i++) { + p.format(obj); + } + bench.end(n); +} diff --git a/benchmark/path/extname.js b/benchmark/path/isAbsolute-posix.js similarity index 57% rename from benchmark/path/extname.js rename to benchmark/path/isAbsolute-posix.js index c655ee7e3e160a..4f8d89e654425d 100644 --- a/benchmark/path/extname.js +++ b/benchmark/path/isAbsolute-posix.js @@ -3,24 +3,30 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], + path: [ + '', + '.', + '/foo/bar', + '/baz/..', + 'bar/baz' + ], + n: [1e6] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; + var p = path.posix; + var input = '' + conf.path; // Force optimization before starting the benchmark - p.extname('index.html'); + p.isAbsolute(input); v8.setFlagsFromString('--allow_natives_syntax'); - eval('%OptimizeFunctionOnNextCall(p.extname)'); - p.extname('index.html'); + eval('%OptimizeFunctionOnNextCall(p.isAbsolute)'); + p.isAbsolute(input); bench.start(); for (var i = 0; i < n; i++) { - p.extname('index.html'); - p.extname('index'); + p.isAbsolute(input); } bench.end(n); } diff --git a/benchmark/path/isAbsolute-win32.js b/benchmark/path/isAbsolute-win32.js new file mode 100644 index 00000000000000..c6771a0e7d7b1e --- /dev/null +++ b/benchmark/path/isAbsolute-win32.js @@ -0,0 +1,33 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + '', + '.', + '//server', + 'C:\\baz\\..', + 'C:baz\\..', + 'bar\\baz' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p.isAbsolute(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.isAbsolute)'); + p.isAbsolute(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p.isAbsolute(input); + } + bench.end(n); +} diff --git a/benchmark/path/isAbsolute.js b/benchmark/path/isAbsolute.js deleted file mode 100644 index d4c79b99cdbeb7..00000000000000 --- a/benchmark/path/isAbsolute.js +++ /dev/null @@ -1,34 +0,0 @@ -var common = require('../common.js'); -var path = require('path'); -var v8 = require('v8'); - -var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], -}); - -function main(conf) { - var n = +conf.n; - var p = path[conf.type]; - var tests = conf.type === 'win32' - ? ['//server', 'C:\\baz\\..', 'bar\\baz', '.'] - : ['/foo/bar', '/baz/..', 'bar/baz', '.']; - - // Force optimization before starting the benchmark - p.isAbsolute(tests[0]); - v8.setFlagsFromString('--allow_natives_syntax'); - eval('%OptimizeFunctionOnNextCall(p.isAbsolute)'); - p.isAbsolute(tests[0]); - - bench.start(); - for (var i = 0; i < n; i++) { - runTests(p, tests); - } - bench.end(n); -} - -function runTests(p, tests) { - for (var i = 0; i < tests.length; i++) { - p.isAbsolute(tests[i]); - } -} diff --git a/benchmark/path/join.js b/benchmark/path/join-posix.js similarity index 62% rename from benchmark/path/join.js rename to benchmark/path/join-posix.js index 58f4dc3e1e9e44..4b8bb924a2c006 100644 --- a/benchmark/path/join.js +++ b/benchmark/path/join-posix.js @@ -3,23 +3,26 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], + paths: [ + ['/foo', 'bar', '', 'baz/asdf', 'quux', '..'].join('|') + ], + n: [1e6] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; + var p = path.posix; + var args = ('' + conf.paths).split('|'); // Force optimization before starting the benchmark - p.join('/foo', 'bar', '', 'baz/asdf', 'quux', '..'); + p.join.apply(null, args); v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(p.join)'); - p.join('/foo', 'bar', '', 'baz/asdf', 'quux', '..'); + p.join.apply(null, args); bench.start(); for (var i = 0; i < n; i++) { - p.join('/foo', 'bar', '', 'baz/asdf', 'quux', '..'); + p.join.apply(null, args); } bench.end(n); } diff --git a/benchmark/path/join-win32.js b/benchmark/path/join-win32.js new file mode 100644 index 00000000000000..7309d1f91c773d --- /dev/null +++ b/benchmark/path/join-win32.js @@ -0,0 +1,28 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + paths: [ + ['C:\\foo', 'bar', '', 'baz\\asdf', 'quux', '..'].join('|') + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var args = ('' + conf.paths).split('|'); + + // Force optimization before starting the benchmark + p.join.apply(null, args); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.join)'); + p.join.apply(null, args); + + bench.start(); + for (var i = 0; i < n; i++) { + p.join.apply(null, args); + } + bench.end(n); +} diff --git a/benchmark/path/makeLong-win32.js b/benchmark/path/makeLong-win32.js new file mode 100644 index 00000000000000..c0b0ac124ed2b0 --- /dev/null +++ b/benchmark/path/makeLong-win32.js @@ -0,0 +1,31 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + 'foo\\bar', + 'C:\\foo', + '\\\\foo\\bar', + '\\\\?\\foo' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p._makeLong(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p._makeLong)'); + p._makeLong(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p._makeLong(input); + } + bench.end(n); +} diff --git a/benchmark/path/normalize.js b/benchmark/path/normalize-posix.js similarity index 63% rename from benchmark/path/normalize.js rename to benchmark/path/normalize-posix.js index 6f7f05e4f9e1c8..87cf903b8c897b 100644 --- a/benchmark/path/normalize.js +++ b/benchmark/path/normalize-posix.js @@ -3,23 +3,31 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], + path: [ + '', + '.', + '/../', + '/foo', + '/foo/bar', + '/foo/bar//baz/asdf/quux/..' + ], + n: [1e6] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; + var p = path.posix; + var input = '' + conf.path; // Force optimization before starting the benchmark - p.normalize('/foo/bar//baz/asdf/quux/..'); + p.normalize(input); v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(p.normalize)'); - p.normalize('/foo/bar//baz/asdf/quux/..'); + p.normalize(input); bench.start(); for (var i = 0; i < n; i++) { - p.normalize('/foo/bar//baz/asdf/quux/..'); + p.normalize(input); } bench.end(n); } diff --git a/benchmark/path/normalize-win32.js b/benchmark/path/normalize-win32.js new file mode 100644 index 00000000000000..50f1e759f72f74 --- /dev/null +++ b/benchmark/path/normalize-win32.js @@ -0,0 +1,33 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + '', + '.', + 'C:\\..\\', + 'C:\\foo', + 'C:\\foo\\bar', + 'C:\\foo\\bar\\\\baz\\asdf\\quux\\..' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p.normalize(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.normalize)'); + p.normalize(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p.normalize(input); + } + bench.end(n); +} diff --git a/benchmark/path/parse.js b/benchmark/path/parse-posix.js similarity index 62% rename from benchmark/path/parse.js rename to benchmark/path/parse-posix.js index f3fbb2a1f5c98f..97d84e52754726 100644 --- a/benchmark/path/parse.js +++ b/benchmark/path/parse-posix.js @@ -3,26 +3,32 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], + path: [ + '', + '/', + '/foo', + '/foo/bar.baz', + 'foo/.bar.baz', + 'foo/bar', + '/foo/bar/baz/asdf/.quux' + ], + n: [1e6] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; - var test = conf.type === 'win32' - ? 'C:\\path\\dir\\index.html' - : '/home/user/dir/index.html'; + var p = path.posix; + var input = '' + conf.path; // Force optimization before starting the benchmark - p.parse(test); + p.parse(input); v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(p.parse)'); - p.parse(test); + p.parse(input); bench.start(); for (var i = 0; i < n; i++) { - p.parse(test); + p.parse(input); } bench.end(n); } diff --git a/benchmark/path/parse-win32.js b/benchmark/path/parse-win32.js new file mode 100644 index 00000000000000..7746a10b797a36 --- /dev/null +++ b/benchmark/path/parse-win32.js @@ -0,0 +1,35 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + path: [ + '', + 'C:\\', + 'C:\\foo', + '\\foo', + 'E:\\foo\\bar.baz', + 'foo\\.bar.baz', + 'foo\\bar', + '\\foo\\bar\\baz\\asdf\\.quux' + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var input = '' + conf.path; + + // Force optimization before starting the benchmark + p.parse(input); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.parse)'); + p.parse(input); + + bench.start(); + for (var i = 0; i < n; i++) { + p.parse(input); + } + bench.end(n); +} diff --git a/benchmark/path/relative-posix.js b/benchmark/path/relative-posix.js new file mode 100644 index 00000000000000..59058babaf3e37 --- /dev/null +++ b/benchmark/path/relative-posix.js @@ -0,0 +1,40 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + paths: [ + ['/data/orandea/test/aaa', '/data/orandea/impl/bbb'].join('|'), + ['/', '/var'].join('|'), + ['/', '/'].join('|'), + ['/var', '/bin'].join('|'), + ['/foo/bar/baz/quux', '/'].join('|'), + ['/foo/bar/baz/quux', '/foo/bar/baz/quux'].join('|'), + ['/foo/bar/baz/quux', '/var/log'].join('|') + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.posix; + var from = '' + conf.paths; + var to = ''; + var delimIdx = from.indexOf('|'); + if (delimIdx > -1) { + to = from.slice(delimIdx + 1); + from = from.slice(0, delimIdx); + } + + // Force optimization before starting the benchmark + p.relative(from, to); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.relative)'); + p.relative(from, to); + + bench.start(); + for (var i = 0; i < n; i++) { + p.relative(from, to); + } + bench.end(n); +} diff --git a/benchmark/path/relative-win32.js b/benchmark/path/relative-win32.js new file mode 100644 index 00000000000000..f3eab74d268cb0 --- /dev/null +++ b/benchmark/path/relative-win32.js @@ -0,0 +1,38 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + paths: [ + ['C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb'].join('|'), + ['C:\\', 'D:\\'].join('|'), + ['C:\\foo\\bar\\baz', 'C:\\foo\\bar\\baz'].join('|'), + ['C:\\foo\\BAR\\BAZ', 'C:\\foo\\bar\\baz'].join('|'), + ['C:\\foo\\bar\\baz\\quux', 'C:\\'].join('|') + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var from = '' + conf.paths; + var to = ''; + var delimIdx = from.indexOf('|'); + if (delimIdx > -1) { + to = from.slice(delimIdx + 1); + from = from.slice(0, delimIdx); + } + + // Force optimization before starting the benchmark + p.relative(from, to); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.relative)'); + p.relative(from, to); + + bench.start(); + for (var i = 0; i < n; i++) { + p.relative(from, to); + } + bench.end(n); +} diff --git a/benchmark/path/relative.js b/benchmark/path/relative.js deleted file mode 100644 index d61c3961a62d8b..00000000000000 --- a/benchmark/path/relative.js +++ /dev/null @@ -1,33 +0,0 @@ -var common = require('../common.js'); -var path = require('path'); -var v8 = require('v8'); - -var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e5], -}); - -function main(conf) { - var n = +conf.n; - var runTest = conf.type === 'win32' ? runWin32Test : runPosixTest; - - // Force optimization before starting the benchmark - runTest(); - v8.setFlagsFromString('--allow_natives_syntax'); - eval('%OptimizeFunctionOnNextCall(path[conf.type].relative)'); - runTest(); - - bench.start(); - for (var i = 0; i < n; i++) { - runTest(); - } - bench.end(n); -} - -function runWin32Test() { - path.win32.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb'); -} - -function runPosixTest() { - path.posix.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb'); -} diff --git a/benchmark/path/resolve.js b/benchmark/path/resolve-posix.js similarity index 56% rename from benchmark/path/resolve.js rename to benchmark/path/resolve-posix.js index 375e8b134ba0ea..0f0554d6fe25c5 100644 --- a/benchmark/path/resolve.js +++ b/benchmark/path/resolve-posix.js @@ -3,23 +3,29 @@ var path = require('path'); var v8 = require('v8'); var bench = common.createBenchmark(main, { - type: ['win32', 'posix'], - n: [1e6], + paths: [ + '', + ['', ''].join('|'), + ['foo/bar', '/tmp/file/', '..', 'a/../subfile'].join('|'), + ['a/b/c/', '../../..'].join('|') + ], + n: [1e6] }); function main(conf) { var n = +conf.n; - var p = path[conf.type]; + var p = path.posix; + var args = ('' + conf.paths).split('|'); // Force optimization before starting the benchmark - p.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile'); + p.resolve.apply(null, args); v8.setFlagsFromString('--allow_natives_syntax'); eval('%OptimizeFunctionOnNextCall(p.resolve)'); - p.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile'); + p.resolve.apply(null, args); bench.start(); for (var i = 0; i < n; i++) { - p.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile'); + p.resolve.apply(null, args); } bench.end(n); } diff --git a/benchmark/path/resolve-win32.js b/benchmark/path/resolve-win32.js new file mode 100644 index 00000000000000..8a5502288f2996 --- /dev/null +++ b/benchmark/path/resolve-win32.js @@ -0,0 +1,31 @@ +var common = require('../common.js'); +var path = require('path'); +var v8 = require('v8'); + +var bench = common.createBenchmark(main, { + paths: [ + '', + ['', ''].join('|'), + ['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'].join('|'), + ['c:/blah\\blah', 'd:/games', 'c:../a'].join('|') + ], + n: [1e6] +}); + +function main(conf) { + var n = +conf.n; + var p = path.win32; + var args = ('' + conf.paths).split('|'); + + // Force optimization before starting the benchmark + p.resolve.apply(null, args); + v8.setFlagsFromString('--allow_natives_syntax'); + eval('%OptimizeFunctionOnNextCall(p.resolve)'); + p.resolve.apply(null, args); + + bench.start(); + for (var i = 0; i < n; i++) { + p.resolve.apply(null, args); + } + bench.end(n); +} diff --git a/lib/path.js b/lib/path.js index a2162cae7c9c97..064c4102ccb10d 100644 --- a/lib/path.js +++ b/lib/path.js @@ -1,602 +1,1605 @@ 'use strict'; -const util = require('util'); -const isWindows = process.platform === 'win32'; +const inspect = require('util').inspect; function assertPath(path) { if (typeof path !== 'string') { - throw new TypeError('Path must be a string. Received ' + - util.inspect(path)); + throw new TypeError('Path must be a string. Received ' + inspect(path)); } } -// resolves . and .. elements in a path array with directory names there -// must be no slashes or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - var res = []; - for (var i = 0; i < parts.length; i++) { - var p = parts[i]; - - // ignore empty parts - if (!p || p === '.') - continue; - - if (p === '..') { - if (res.length && res[res.length - 1] !== '..') { - res.pop(); - } else if (allowAboveRoot) { - res.push('..'); +// Resolves . and .. elements in a path with directory names +function normalizeStringWin32(path, allowAboveRoot) { + var res = ''; + var lastSlash = -1; + var dots = 0; + var code; + for (var i = 0; i <= path.length; ++i) { + if (i < path.length) + code = path.charCodeAt(i); + else if (code === 47/*/*/ || code === 92/*\*/) + break; + else + code = 47/*/*/; + if (code === 47/*/*/ || code === 92/*\*/) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if (res.length < 2 || + res.charCodeAt(res.length - 1) !== 46/*.*/ || + res.charCodeAt(res.length - 2) !== 46/*.*/) { + if (res.length > 2) { + const start = res.length - 1; + var j = start; + for (; j >= 0; --j) { + if (res.charCodeAt(j) === 92/*\*/) + break; + } + if (j !== start) { + if (j === -1) + res = ''; + else + res = res.slice(0, j); + lastSlash = i; + dots = 0; + continue; + } + } else if (res.length === 2) { + res = ''; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) + res += '\\..'; + else + res = '..'; + } + } else { + if (res.length > 0) + res += '\\' + path.slice(lastSlash + 1, i); + else + res = path.slice(lastSlash + 1, i); } + lastSlash = i; + dots = 0; + } else if (code === 46/*.*/ && dots !== -1) { + ++dots; } else { - res.push(p); + dots = -1; } } - return res; } -// Returns an array with empty elements removed from either end of the input -// array or the original array if no elements need to be removed -function trimArray(arr) { - var lastIndex = arr.length - 1; - var start = 0; - for (; start <= lastIndex; start++) { - if (arr[start]) +// Resolves . and .. elements in a path with directory names +function normalizeStringPosix(path, allowAboveRoot) { + var res = ''; + var lastSlash = -1; + var dots = 0; + var code; + for (var i = 0; i <= path.length; ++i) { + if (i < path.length) + code = path.charCodeAt(i); + else if (code === 47/*/*/) break; + else + code = 47/*/*/; + if (code === 47/*/*/) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if (res.length < 2 || + res.charCodeAt(res.length - 1) !== 46/*.*/ || + res.charCodeAt(res.length - 2) !== 46/*.*/) { + if (res.length > 2) { + const start = res.length - 1; + var j = start; + for (; j >= 0; --j) { + if (res.charCodeAt(j) === 47/*/*/) + break; + } + if (j !== start) { + if (j === -1) + res = ''; + else + res = res.slice(0, j); + lastSlash = i; + dots = 0; + continue; + } + } else if (res.length === 2) { + res = ''; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) + res += '/..'; + else + res = '..'; + } + } else { + if (res.length > 0) + res += '/' + path.slice(lastSlash + 1, i); + else + res = path.slice(lastSlash + 1, i); + } + lastSlash = i; + dots = 0; + } else if (code === 46/*.*/ && dots !== -1) { + ++dots; + } else { + dots = -1; + } } + return res; +} - var end = lastIndex; - for (; end >= 0; end--) { - if (arr[end]) - break; - } - if (start === 0 && end === lastIndex) - return arr; - if (start > end) - return []; - return arr.slice(start, end + 1); -} +const win32 = { + // path.resolve([from ...], to) + resolve: function resolve() { + var resolvedDevice = ''; + var resolvedTail = ''; + var resolvedAbsolute = false; -// Regex to split a windows path into three parts: [*, device, slash, -// tail] windows-only -const splitDeviceRe = - /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; - -// Regex to split the tail part of the above into [*, dir, basename, ext] -const splitTailRe = - /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; - -var win32 = {}; - -// Function to split a filename into [root, dir, basename, ext] -function win32SplitPath(filename) { - // Separate device+slash from tail - const result = splitDeviceRe.exec(filename); - const device = (result[1] || '') + (result[2] || ''); - const tail = result[3]; - // Split the tail into dir, basename and extension - const result2 = splitTailRe.exec(tail); - const dir = result2[1]; - const basename = result2[2]; - const ext = result2[3]; - return [device, dir, basename, ext]; -} + for (var i = arguments.length - 1; i >= -1; i--) { + var path; + if (i >= 0) { + path = arguments[i]; + } else if (!resolvedDevice) { + path = process.cwd(); + } else { + // Windows has the concept of drive-specific current working + // directories. If we've resolved a drive letter but not yet an + // absolute path, get cwd for that drive. We're sure the device is not + // a UNC path at this points, because UNC paths are always absolute. + path = process.env['=' + resolvedDevice]; + // Verify that a drive-local cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if (path === undefined || + path.slice(0, 3).toLowerCase() !== + resolvedDevice.toLowerCase() + '\\') { + path = resolvedDevice + '\\'; + } + } -function win32StatPath(path) { - const result = splitDeviceRe.exec(path); - const device = result[1] || ''; - const isUnc = !!device && device[1] !== ':'; - return { - device, - isUnc, - isAbsolute: isUnc || !!result[2], // UNC paths are always absolute - tail: result[3] - }; -} + assertPath(path); -function normalizeUNCRoot(device) { - return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); -} + // Skip empty entries + if (path.length === 0) { + continue; + } -// path.resolve([from ...], to) -win32.resolve = function() { - var resolvedDevice = ''; - var resolvedTail = ''; - var resolvedAbsolute = false; - - for (var i = arguments.length - 1; i >= -1; i--) { - var path; - if (i >= 0) { - path = arguments[i]; - } else if (!resolvedDevice) { - path = process.cwd(); - } else { - // Windows has the concept of drive-specific current working - // directories. If we've resolved a drive letter but not yet an - // absolute path, get cwd for that drive. We're sure the device is not - // an unc path at this points, because unc paths are always absolute. - path = process.env['=' + resolvedDevice]; - // Verify that a drive-local cwd was found and that it actually points - // to our drive. If not, default to the drive's root. - if (!path || path.substr(0, 3).toLowerCase() !== - resolvedDevice.toLowerCase() + '\\') { - path = resolvedDevice + '\\'; + var len = path.length; + var rootEnd = 0; + var code = path.charCodeAt(0); + var device = ''; + var isAbsolute = false; + + // Try to match a root + if (len > 1) { + if (code === 47/*/*/ || code === 92/*\*/) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + code = path.charCodeAt(1); + if (code === 47/*/*/ || code === 92/*\*/) { + // Matched double path separator at beginning + var j = 2; + var last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code !== 47/*/*/ && code !== 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j === len) { + // We matched a UNC root only + + device = '\\\\' + firstPart + '\\' + path.slice(last); + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = '\\\\' + firstPart + '\\' + path.slice(last, j); + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if ((code >= 65/*A*/ && code <= 90/*Z*/) || + (code >= 97/*a*/ && code <= 122/*z*/)) { + // Possible device root + + code = path.charCodeAt(1); + if (path.charCodeAt(1) === 58/*:*/) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + code = path.charCodeAt(2); + if (code === 47/*/*/ || code === 92/*\*/) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (code === 47/*/*/ || code === 92/*\*/) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; } - } - assertPath(path); + if (device.length > 0 && + resolvedDevice.length > 0 && + device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + + if (resolvedDevice.length === 0 && device.length > 0) { + resolvedDevice = device; + } + if (!resolvedAbsolute) { + resolvedTail = path.slice(rootEnd) + '\\' + resolvedTail; + resolvedAbsolute = isAbsolute; + } - // Skip empty entries - if (path === '') { - continue; + if (resolvedDevice.length > 0 && resolvedAbsolute) { + break; + } } - const result = win32StatPath(path); - const device = result.device; - var isUnc = result.isUnc; - const isAbsolute = result.isAbsolute; - const tail = result.tail; + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) - if (device && - resolvedDevice && - device.toLowerCase() !== resolvedDevice.toLowerCase()) { - // This path points to another device so it is not applicable - continue; - } + // Normalize the tail path + resolvedTail = normalizeStringWin32(resolvedTail, !resolvedAbsolute); - if (!resolvedDevice) { - resolvedDevice = device; - } - if (!resolvedAbsolute) { - resolvedTail = tail + '\\' + resolvedTail; - resolvedAbsolute = isAbsolute; + return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || + '.'; + }, + + normalize: function normalize(path) { + assertPath(path); + const len = path.length; + if (len === 0) + return '.'; + var rootEnd = 0; + var code = path.charCodeAt(0); + var device; + var isAbsolute = false; + + // Try to match a root + if (len > 1) { + if (code === 47/*/*/ || code === 92/*\*/) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + code = path.charCodeAt(1); + if (code === 47/*/*/ || code === 92/*\*/) { + // Matched double path separator at beginning + var j = 2; + var last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code !== 47/*/*/ && code !== 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + + return '\\\\' + firstPart + '\\' + path.slice(last) + '\\'; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = '\\\\' + firstPart + '\\' + path.slice(last, j); + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if ((code >= 65/*A*/ && code <= 90/*Z*/) || + (code >= 97/*a*/ && code <= 122/*z*/)) { + // Possible device root + + code = path.charCodeAt(1); + if (path.charCodeAt(1) === 58/*:*/) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + code = path.charCodeAt(2); + if (code === 47/*/*/ || code === 92/*\*/) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (code === 47/*/*/ || code === 92/*\*/) { + // `path` contains just a path separator, exit early to avoid unnecessary + // work + return '\\'; } - if (resolvedDevice && resolvedAbsolute) { - break; + code = path.charCodeAt(len - 1); + var trailingSeparator = (code === 47/*/*/ || code === 92/*\*/); + var tail; + if (rootEnd < len) + tail = normalizeStringWin32(path.slice(rootEnd), !isAbsolute); + else + tail = ''; + if (tail.length === 0 && !isAbsolute) + tail = '.'; + if (tail.length > 0 && trailingSeparator) + tail += '\\'; + if (device === undefined) { + if (isAbsolute) { + if (tail.length > 0) + return '\\' + tail; + else + return '\\'; + } else if (tail.length > 0) { + return tail; + } else { + return ''; + } + } else { + if (isAbsolute) { + if (tail.length > 0) + return device + '\\' + tail; + else + return device + '\\'; + } else if (tail.length > 0) { + return device + tail; + } else { + return device; + } } - } + }, - // Convert slashes to backslashes when `resolvedDevice` points to an UNC - // root. Also squash multiple slashes into a single one where appropriate. - if (isUnc) { - resolvedDevice = normalizeUNCRoot(resolvedDevice); - } - // At this point the path should be resolved to a full absolute path, - // but handle relative paths to be safe (might happen when process.cwd() - // fails) + isAbsolute: function isAbsolute(path) { + assertPath(path); + const len = path.length; + if (len === 0) + return false; + var code = path.charCodeAt(0); + if (len > 1) { + if (code === 47/*/*/ || code === 92/*\*/) { + // Possible UNC root + + code = path.charCodeAt(1); + if (code === 47/*/*/ || code === 92/*\*/) { + // Matched double path separator at beginning + var j = 2; + var last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code !== 47/*/*/ && code !== 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j !== last) + return true; + } + } + } + } else if ((code >= 65/*A*/ && code <= 90/*Z*/) || + (code >= 97/*a*/ && code <= 122/*z*/)) { + // Possible device root + + code = path.charCodeAt(1); + if (path.charCodeAt(1) === 58/*:*/ && len > 2) { + code = path.charCodeAt(2); + if (code === 47/*/*/ || code === 92/*\*/) + return true; + } + } + } else if (code === 47/*/*/ || code === 92/*\*/) { + return true; + } + return false; + }, + + + join: function join() { + if (arguments.length === 0) + return '.'; + + var joined; + var firstPart; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + assertPath(arg); + if (arg.length > 0) { + if (joined === undefined) + joined = firstPart = arg; + else + joined += '\\' + arg; + } + } - // Normalize the tail path - resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/), - !resolvedAbsolute).join('\\'); + if (joined === undefined) + return '.'; + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + //var firstPart = paths[0]; + var needsReplace = true; + var slashCount = 0; + var code = firstPart.charCodeAt(0); + if (code === 47/*/*/ || code === 92/*\*/) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1) { + code = firstPart.charCodeAt(1); + if (code === 47/*/*/ || code === 92/*\*/) { + ++slashCount; + if (firstLen > 2) { + code = firstPart.charCodeAt(2); + if (code === 47/*/*/ || code === 92/*\*/) + ++slashCount; + else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + for (; slashCount < joined.length; ++slashCount) { + code = joined.charCodeAt(slashCount); + if (code !== 47/*/*/ && code !== 92/*\*/) + break; + } - return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || - '.'; -}; + // Replace the slashes if needed + if (slashCount >= 2) + joined = '\\' + joined.slice(slashCount); + } + return win32.normalize(joined); + }, -win32.normalize = function(path) { - assertPath(path); - const result = win32StatPath(path); - var device = result.device; - const isUnc = result.isUnc; - const isAbsolute = result.isAbsolute; - var tail = result.tail; - const trailingSlash = /[\\\/]$/.test(tail); + // It will solve the relative path from `from` to `to`, for instance: + // from = 'C:\\orandea\\test\\aaa' + // to = 'C:\\orandea\\impl\\bbb' + // The output of the function should be: '..\\..\\impl\\bbb' + relative: function relative(from, to) { + assertPath(from); + assertPath(to); - // Normalize the tail path - tail = normalizeArray(tail.split(/[\\\/]+/), !isAbsolute).join('\\'); + if (from === to) + return ''; - if (!tail && !isAbsolute) { - tail = '.'; - } - if (tail && trailingSlash) { - tail += '\\'; - } + var fromOrig = win32.resolve(from); + var toOrig = win32.resolve(to); - // Convert slashes to backslashes when `device` points to an UNC root. - // Also squash multiple slashes into a single one where appropriate. - if (isUnc) { - device = normalizeUNCRoot(device); - } + if (fromOrig === toOrig) + return ''; - return device + (isAbsolute ? '\\' : '') + tail; -}; + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + if (from === to) + return ''; -win32.isAbsolute = function(path) { - assertPath(path); - return win32StatPath(path).isAbsolute; -}; + // Trim any leading backslashes + var fromStart = 0; + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== 92/*\*/) + break; + } + var fromEnd = from.length; + var fromLen = (fromEnd - fromStart); + + // Trim any leading backslashes + var toStart = 0; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== 92/*\*/) + break; + } + var toEnd = to.length; + var toLen = (toEnd - toStart); + + // Compare paths to find the longest common path from root + var length = (fromLen < toLen ? fromLen : toLen); + var lastCommonSep = -1; + var i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (lastCommonSep > 2 && // ignore separator match following device root + toLen > length && + to.charCodeAt(i) === 92/*\*/) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(i + 1); + } + lastCommonSep = i; + break; + } + var fromCode = from.charCodeAt(fromStart + i); + var toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) + break; + else if (fromCode === 92/*\*/) + lastCommonSep = i; + } -win32.join = function() { - var paths = []; - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - assertPath(arg); - if (arg) { - paths.push(arg); + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + // TODO: do this just for device roots (and not UNC paths)? + if (i !== length && lastCommonSep === -1) { + if (toStart > 0) + return toOrig.slice(toStart); + else + return toOrig; } - } - var joined = paths.join('\\'); - - // Make sure that the joined path doesn't start with two slashes, because - // normalize() will mistake it for an UNC path then. - // - // This step is skipped when it is very clear that the user actually - // intended to point at an UNC path. This is assumed when the first - // non-empty string arguments starts with exactly two slashes followed by - // at least one more non-slash character. - // - // Note that for normalize() to treat a path as an UNC path it needs to - // have at least 2 components, so we don't filter for that here. - // This means that the user can use join to construct UNC paths from - // a server name and a share name; for example: - // path.join('//server', 'share') -> '\\\\server\\share\') - if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) { - joined = joined.replace(/^[\\\/]{2,}/, '\\'); - } + var out = ''; + if (lastCommonSep === -1) + lastCommonSep = 0; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === 92/*\*/) { + if (out.length === 0) + out += '..'; + else + out += '\\..'; + } + } - return win32.normalize(joined); -}; + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) + return out + toOrig.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (toOrig.charCodeAt(toStart) === 92/*\*/) + ++toStart; + return toOrig.slice(toStart); + } + }, -// path.relative(from, to) -// it will solve the relative path from 'from' to 'to', for instance: -// from = 'C:\\orandea\\test\\aaa' -// to = 'C:\\orandea\\impl\\bbb' -// The output of the function should be: '..\\..\\impl\\bbb' -win32.relative = function(from, to) { - assertPath(from); - assertPath(to); + _makeLong: function _makeLong(path) { + // Note: this will *probably* throw somewhere. + if (typeof path !== 'string') + return path; - from = win32.resolve(from); - to = win32.resolve(to); + if (path.length === 0) { + return ''; + } - // windows is not case sensitive - var lowerFrom = from.toLowerCase(); - var lowerTo = to.toLowerCase(); + const resolvedPath = win32.resolve(path); + + if (resolvedPath.length >= 3) { + var code = resolvedPath.charCodeAt(0); + if (code === 92/*\*/) { + // Possible UNC root + + if (resolvedPath.charCodeAt(1) === 92/*\*/) { + code = resolvedPath.charCodeAt(2); + if (code !== 63/*?*/ && code !== 46/*.*/) { + // Matched non-long UNC root, convert the path to a long UNC path + return '\\\\?\\UNC\\' + resolvedPath.slice(2); + } + } + } else if ((code >= 65/*A*/ && code <= 90/*Z*/) || + (code >= 97/*a*/ && code <= 122/*z*/)) { + // Possible device root + + if (resolvedPath.charCodeAt(1) === 58/*:*/ && + resolvedPath.charCodeAt(2) === 92/*\*/) { + // Matched device root, convert the path to a long UNC path + return '\\\\?\\' + resolvedPath; + } + } + } - var toParts = trimArray(to.split('\\')); + return path; + }, - var lowerFromParts = trimArray(lowerFrom.split('\\')); - var lowerToParts = trimArray(lowerTo.split('\\')); - var length = Math.min(lowerFromParts.length, lowerToParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (lowerFromParts[i] !== lowerToParts[i]) { - samePartsLength = i; - break; + dirname: function dirname(path) { + assertPath(path); + const len = path.length; + if (len === 0) + return '.'; + var rootEnd = -1; + var end = -1; + var matchedSlash = true; + var offset = 0; + var code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (code === 47/*/*/ || code === 92/*\*/) { + // Possible UNC root + + rootEnd = offset = 1; + + code = path.charCodeAt(1); + if (code === 47/*/*/ || code === 92/*\*/) { + // Matched double path separator at beginning + var j = 2; + var last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code !== 47/*/*/ && code !== 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + } else if ((code >= 65/*A*/ && code <= 90/*Z*/) || + (code >= 97/*a*/ && code <= 122/*z*/)) { + // Possible device root + + code = path.charCodeAt(1); + if (path.charCodeAt(1) === 58/*:*/) { + rootEnd = offset = 2; + if (len > 2) { + code = path.charCodeAt(2); + if (code === 47/*/*/ || code === 92/*\*/) + rootEnd = offset = 3; + } + } + } + } else if (code === 47/*/*/ || code === 92/*\*/) { + return path[0]; } - } - if (samePartsLength === 0) { - return to; - } - - var outputParts = []; - for (var j = samePartsLength; j < lowerFromParts.length; j++) { - outputParts.push('..'); - } + for (var i = len - 1; i >= offset; --i) { + code = path.charCodeAt(i); + if (code === 47/*/*/ || code === 92/*\*/) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } - outputParts = outputParts.concat(toParts.slice(samePartsLength)); + if (end === -1) { + if (rootEnd === -1) + end = len; + else + end = rootEnd; + } + return path.slice(0, end); + }, - return outputParts.join('\\'); -}; + basename: function basename(path, ext) { + assertPath(path); + if (ext !== undefined && typeof ext !== 'string') + throw new TypeError('"ext" argument must be a string'); + var start = 0; + var end = -1; + var matchedSlash = true; + var i; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2) { + const drive = path.charCodeAt(0); + if ((drive >= 65/*A*/ && drive <= 90/*Z*/) || + (drive >= 97/*a*/ && drive <= 122/*z*/)) { + if (path.charCodeAt(1) === 58/*:*/) + start = 2; + } + } -win32._makeLong = function(path) { - // Note: this will *probably* throw somewhere. - if (typeof path !== 'string') - return path; + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) + return ''; + var extIdx = ext.length - 1; + var firstNonSlashEnd = -1; + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === 47/*/*/ || code === 92/*\*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } - if (!path) { - return ''; - } + if (end === -1) + return ''; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === 47/*/*/ || code === 92/*\*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } - var resolvedPath = win32.resolve(path); + if (end === -1) + return ''; + return path.slice(start, end); + } + }, - if (/^[a-zA-Z]\:\\/.test(resolvedPath)) { - // path is local filesystem path, which needs to be converted - // to long UNC path. - return '\\\\?\\' + resolvedPath; - } else if (/^\\\\[^?.]/.test(resolvedPath)) { - // path is network UNC path, which needs to be converted - // to long UNC path. - return '\\\\?\\UNC\\' + resolvedPath.substring(2); - } - return path; -}; + extname: function extname(path) { + assertPath(path); + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + for (var i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === 47/*/*/ || code === 92/*\*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46/*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, -win32.dirname = function(path) { - const result = win32SplitPath(path); - const root = result[0]; - var dir = result[1]; - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } + format: function format(pathObject) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new TypeError( + 'Parameter "pathObject" must be an object, not ' + typeof pathObject + ); + } - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } + var dir = pathObject.dir || pathObject.root; + var base = pathObject.base || + ((pathObject.name || '') + (pathObject.ext || '')); + if (!dir) { + return base; + } + if (dir === pathObject.root) { + return dir + base; + } + return dir + win32.sep + base; + }, - return root + dir; -}; + parse: function parse(path) { + assertPath(path); -win32.basename = function(path, ext) { - if (ext !== undefined && typeof ext !== 'string') - throw new TypeError('"ext" argument must be a string'); + var ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) + return ret; + + var len = path.length; + var rootEnd = 0; + var code = path.charCodeAt(0); + var isAbsolute = false; + + // Try to match a root + if (len > 1) { + if (code === 47/*/*/ || code === 92/*\*/) { + // Possible UNC root + + isAbsolute = true; + + code = path.charCodeAt(1); + if (code === 47/*/*/ || code === 92/*\*/) { + rootEnd = 1; + // Matched double path separator at beginning + var j = 2; + var last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code !== 47/*/*/ && code !== 92/*\*/) + break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + code = path.charCodeAt(j); + if (code === 47/*/*/ || code === 92/*\*/) + break; + } + if (j === len) { + // We matched a UNC root only + + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + rootEnd = j + 1; + } + } + } + } + } else if ((code >= 65/*A*/ && code <= 90/*Z*/) || + (code >= 97/*a*/ && code <= 122/*z*/)) { + // Possible device root + + code = path.charCodeAt(1); + if (path.charCodeAt(1) === 58/*:*/) { + rootEnd = 2; + if (len > 2) { + code = path.charCodeAt(2); + if (code === 47/*/*/ || code === 92/*\*/) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path.slice(0, 3); + return ret; + } + isAbsolute = true; + rootEnd = 3; + } + } else { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path.slice(0, 2); + return ret; + } + } + } + } else if (code === 47/*/*/ || code === 92/*\*/) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path[0]; + return ret; + } - var f = win32SplitPath(path)[2]; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; + if (rootEnd > 0) + ret.root = path.slice(0, rootEnd); + + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + var i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (code === 47/*/*/ || code === 92/*\*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46/*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) + ret.base = ret.name = path.slice(rootEnd, end); + else + ret.base = ret.name = path.slice(startPart, end); + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(rootEnd, startDot); + ret.base = path.slice(rootEnd, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } -win32.extname = function(path) { - return win32SplitPath(path)[3]; -}; + if (startPart > 0) + ret.dir = path.slice(0, startPart - 1); + else if (isAbsolute) + ret.dir = path.slice(0, rootEnd); + return ret; + }, -win32.format = function(pathObject) { - if (pathObject === null || typeof pathObject !== 'object') { - throw new TypeError( - 'Parameter "pathObject" must be an object, not ' + typeof pathObject - ); - } - var dir = pathObject.dir || pathObject.root; - var base = pathObject.base || - ((pathObject.name || '') + (pathObject.ext || '')); - if (!dir) { - return base; - } - if (dir === pathObject.root) { - return dir + base; - } - return dir + win32.sep + base; + sep: '\\', + delimiter: ';', + win32: null, + posix: null }; -win32.parse = function(pathString) { - assertPath(pathString); - - var allParts = win32SplitPath(pathString); - return { - root: allParts[0], - dir: allParts[0] + allParts[1].slice(0, -1), - base: allParts[2], - ext: allParts[3], - name: allParts[2].slice(0, allParts[2].length - allParts[3].length) - }; -}; +const posix = { + // path.resolve([from ...], to) + resolve: function resolve() { + var resolvedPath = ''; + var resolvedAbsolute = false; + var cwd; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path; + if (i >= 0) + path = arguments[i]; + else { + if (cwd === undefined) + cwd = process.cwd(); + path = cwd; + } + assertPath(path); -win32.sep = '\\'; -win32.delimiter = ';'; + // Skip empty entries + if (path.length === 0) { + continue; + } + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charCodeAt(0) === 47/*/*/; + } -// Split a filename into [root, dir, basename, ext], unix version -// 'root' is just a slash, or nothing. -const splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; -var posix = {}; + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + // Normalize the path + resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute); -function posixSplitPath(filename) { - const out = splitPathRe.exec(filename); - out.shift(); - return out; -} + if (resolvedAbsolute) { + if (resolvedPath.length > 0) + return '/' + resolvedPath; + else + return '/'; + } else if (resolvedPath.length > 0) { + return resolvedPath; + } else { + return '.'; + } + }, -// path.resolve([from ...], to) -// posix version -posix.resolve = function() { - var resolvedPath = ''; - var resolvedAbsolute = false; + normalize: function normalize(path) { + assertPath(path); - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : process.cwd(); + if (path.length === 0) + return '.'; - assertPath(path); + const isAbsolute = path.charCodeAt(0) === 47/*/*/; + const trailingSeparator = path.charCodeAt(path.length - 1) === 47/*/*/; - // Skip empty entries - if (path === '') { - continue; - } + // Normalize the path + path = normalizeStringPosix(path, !isAbsolute); - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path[0] === '/'; - } + if (path.length === 0 && !isAbsolute) + path = '.'; + if (path.length > 0 && trailingSeparator) + path += '/'; - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) + if (isAbsolute) + return '/' + path; + return path; + }, - // Normalize the path - resolvedPath = normalizeArray(resolvedPath.split('/'), - !resolvedAbsolute).join('/'); - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; -}; + isAbsolute: function isAbsolute(path) { + assertPath(path); + return path.length > 0 && path.charCodeAt(0) === 47/*/*/; + }, + + + join: function join() { + if (arguments.length === 0) + return '.'; + var joined; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + assertPath(arg); + if (arg.length > 0) { + if (joined === undefined) + joined = arg; + else + joined += '/' + arg; + } + } + if (joined === undefined) + return '.'; + return posix.normalize(joined); + }, -// path.normalize(path) -// posix version -posix.normalize = function(path) { - assertPath(path); - const isAbsolute = posix.isAbsolute(path); - const trailingSlash = path && path[path.length - 1] === '/'; + relative: function relative(from, to) { + assertPath(from); + assertPath(to); - // Normalize the path - path = normalizeArray(path.split('/'), !isAbsolute).join('/'); + if (from === to) + return ''; - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } + from = posix.resolve(from); + to = posix.resolve(to); - return (isAbsolute ? '/' : '') + path; -}; + if (from === to) + return ''; -// posix version -posix.isAbsolute = function(path) { - assertPath(path); - return !!path && path[0] === '/'; -}; + // Trim any leading backslashes + var fromStart = 1; + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== 47/*/*/) + break; + } + var fromEnd = from.length; + var fromLen = (fromEnd - fromStart); + + // Trim any leading backslashes + var toStart = 1; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== 47/*/*/) + break; + } + var toEnd = to.length; + var toLen = (toEnd - toStart); + + // Compare paths to find the longest common path from root + var length = (fromLen < toLen ? fromLen : toLen); + var lastCommonSep = -1; + var i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (lastCommonSep === -1) { + lastCommonSep = i; + } else if (toLen > length && to.charCodeAt(i + 1) === 47/*/*/) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(i + 2); + } + break; + } + var fromCode = from.charCodeAt(fromStart + i); + var toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) + break; + else if (fromCode === 47/*/*/) + lastCommonSep = i; + } -// posix version -posix.join = function() { - var path = ''; - for (var i = 0; i < arguments.length; i++) { - var segment = arguments[i]; - assertPath(segment); - if (segment) { - if (!path) { - path += segment; - } else { - path += '/' + segment; + var out = ''; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === 47/*/*/) { + if (out.length === 0) + out += '..'; + else + out += '/..'; } } - } - return posix.normalize(path); -}; + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) + return out + to.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (to.charCodeAt(toStart) === 47/*/*/) + ++toStart; + return to.slice(toStart); + } + }, -// path.relative(from, to) -// posix version -posix.relative = function(from, to) { - assertPath(from); - assertPath(to); - from = posix.resolve(from).substr(1); - to = posix.resolve(to).substr(1); + _makeLong: function _makeLong(path) { + return path; + }, - var fromParts = trimArray(from.split('/')); - var toParts = trimArray(to.split('/')); - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; + dirname: function dirname(path) { + assertPath(path); + if (path.length === 0) + return '.'; + var code = path.charCodeAt(0); + var hasRoot = (code === 47/*/*/); + var end = -1; + var matchedSlash = true; + for (var i = path.length - 1; i >= 1; --i) { + code = path.charCodeAt(i); + if (code === 47/*/*/) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } } - } - var outputParts = []; - for (var j = samePartsLength; j < fromParts.length; j++) { - outputParts.push('..'); - } + if (end === -1) + return hasRoot ? '/' : '.'; + if (hasRoot && end === 1) + return '//'; + return path.slice(0, end); + }, - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - - return outputParts.join('/'); -}; - - -posix._makeLong = function(path) { - return path; -}; + basename: function basename(path, ext) { + assertPath(path); + if (ext !== undefined && typeof ext !== 'string') + throw new TypeError('"ext" argument must be a string'); + + var start = 0; + var end = -1; + var matchedSlash = true; + var i; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) + return ''; + var extIdx = ext.length - 1; + var firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === 47/*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } -posix.dirname = function(path) { - const result = posixSplitPath(path); - const root = result[0]; - var dir = result[1]; + if (end === -1) + return ''; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === 47/*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } + if (end === -1) + return ''; + return path.slice(start, end); + } + }, - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } - return root + dir; -}; + extname: function extname(path) { + assertPath(path); + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + for (var i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === 47/*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46/*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, -posix.basename = function(path, ext) { - if (ext !== undefined && typeof ext !== 'string') - throw new TypeError('"ext" argument must be a string'); - var f = posixSplitPath(path)[2]; + format: function format(pathObject) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new TypeError( + 'Parameter "pathObject" must be an object, not ' + typeof pathObject + ); + } - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; + var dir = pathObject.dir || pathObject.root; + var base = pathObject.base || + ((pathObject.name || '') + (pathObject.ext || '')); + if (!dir) { + return base; + } + if (dir === pathObject.root) { + return dir + base; + } + return dir + posix.sep + base; + }, -posix.extname = function(path) { - return posixSplitPath(path)[3]; -}; + parse: function parse(path) { + assertPath(path); + var ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) + return ret; + var code = path.charCodeAt(0); + var isAbsolute = (code === 47/*/*/); + var start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + var i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + code = path.charCodeAt(i); + if (code === 47/*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46/*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } -posix.format = function(pathObject) { - if (pathObject === null || typeof pathObject !== 'object') { - throw new TypeError( - 'Parameter "pathObject" must be an object, not ' + typeof pathObject - ); - } + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) + ret.base = ret.name = path.slice(1, end); + else + ret.base = ret.name = path.slice(startPart, end); + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } - var dir = pathObject.dir || pathObject.root; - var base = pathObject.base || - ((pathObject.name || '') + (pathObject.ext || '')); - if (!dir) { - return base; - } - if (dir === pathObject.root) { - return dir + base; - } - return dir + posix.sep + base; -}; + if (startPart > 0) + ret.dir = path.slice(0, startPart - 1); + else if (isAbsolute) + ret.dir = '/'; + return ret; + }, -posix.parse = function(pathString) { - assertPath(pathString); - var allParts = posixSplitPath(pathString); - return { - root: allParts[0], - dir: allParts[0] + allParts[1].slice(0, -1), - base: allParts[2], - ext: allParts[3], - name: allParts[2].slice(0, allParts[2].length - allParts[3].length) - }; + sep: '/', + delimiter: ':', + win32: null, + posix: null }; -posix.sep = '/'; -posix.delimiter = ':'; +posix.win32 = win32.win32 = win32; +posix.posix = win32.posix = posix; -if (isWindows) +if (process.platform === 'win32') module.exports = win32; -else /* posix */ +else module.exports = posix; - -module.exports.posix = posix; -module.exports.win32 = win32; diff --git a/test/parallel/test-path-parse-format.js b/test/parallel/test-path-parse-format.js index a28444cd3bca76..21bebfedee85b2 100644 --- a/test/parallel/test-path-parse-format.js +++ b/test/parallel/test-path-parse-format.js @@ -41,7 +41,16 @@ const unixPaths = [ './file', 'C:\\foo', '/', - '' + '', + '.', + '..', + '/foo', + '/foo.', + '/foo.bar', + '/.', + '/.foo', + '/.foo.bar', + '/foo/bar.baz', ]; const unixSpecialCaseFormatTests = [ @@ -82,6 +91,67 @@ checkErrors(path.posix); checkFormat(path.win32, winSpecialCaseFormatTests); checkFormat(path.posix, unixSpecialCaseFormatTests); +// Test removal of trailing path separators +const trailingTests = [ + [ path.win32.parse, + [['.\\', { root: '', dir: '', base: '.', ext: '', name: '.' }], + ['\\\\', { root: '\\', dir: '\\', base: '', ext: '', name: '' }], + ['\\\\', { root: '\\', dir: '\\', base: '', ext: '', name: '' }], + ['c:\\foo\\\\\\', + { root: 'c:\\', dir: 'c:\\', base: 'foo', ext: '', name: 'foo' }], + ['D:\\foo\\\\\\bar.baz', + { root: 'D:\\', + dir: 'D:\\foo\\\\', + base: 'bar.baz', + ext: '.baz', + name: 'bar' + } + ] + ] + ], + [ path.posix.parse, + [['./', { root: '', dir: '', base: '.', ext: '', name: '.' }], + ['//', { root: '/', dir: '/', base: '', ext: '', name: '' }], + ['///', { root: '/', dir: '/', base: '', ext: '', name: '' }], + ['/foo///', { root: '/', dir: '/', base: 'foo', ext: '', name: 'foo' }], + ['/foo///bar.baz', + { root: '/', dir: '/foo//', base: 'bar.baz', ext: '.baz', name: 'bar' } + ] + ] + ] +]; +const failures = []; +trailingTests.forEach(function(test) { + const parse = test[0]; + test[1].forEach(function(test) { + const actual = parse(test[0]); + const expected = test[1]; + const fn = 'path.' + + (parse === path.win32.parse ? 'win32' : 'posix') + + '.parse('; + const message = fn + + JSON.stringify(test[0]) + + ')' + + '\n expect=' + JSON.stringify(expected) + + '\n actual=' + JSON.stringify(actual); + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + let failed = (actualKeys.length !== expectedKeys.length); + if (!failed) { + for (let i = 0; i < actualKeys.length; ++i) { + const key = actualKeys[i]; + if (expectedKeys.indexOf(key) === -1 || actual[key] !== expected[key]) { + failed = true; + break; + } + } + } + if (failed) + failures.push('\n' + message); + }); +}); +assert.equal(failures.length, 0, failures.join('')); + function checkErrors(path) { errors.forEach(function(errorCase) { try { diff --git a/test/parallel/test-path-zero-length-strings.js b/test/parallel/test-path-zero-length-strings.js index 07de030aa0b323..b08ef4809201eb 100644 --- a/test/parallel/test-path-zero-length-strings.js +++ b/test/parallel/test-path-zero-length-strings.js @@ -12,17 +12,21 @@ const pwd = process.cwd(); // join will internally ignore all the zero-length strings and it will return // '.' if the joined string is a zero-length string. -assert.equal(path.join(''), '.'); -assert.equal(path.join('', ''), '.'); +assert.equal(path.posix.join(''), '.'); +assert.equal(path.posix.join('', ''), '.'); +assert.equal(path.win32.join(''), '.'); +assert.equal(path.win32.join('', ''), '.'); assert.equal(path.join(pwd), pwd); assert.equal(path.join(pwd, ''), pwd); // normalize will return '.' if the input is a zero-length string -assert.equal(path.normalize(''), '.'); +assert.equal(path.posix.normalize(''), '.'); +assert.equal(path.win32.normalize(''), '.'); assert.equal(path.normalize(pwd), pwd); // Since '' is not a valid path in any of the common environments, return false -assert.equal(path.isAbsolute(''), false); +assert.equal(path.posix.isAbsolute(''), false); +assert.equal(path.win32.isAbsolute(''), false); // resolve, internally ignores all the zero-length strings and returns the // current working directory diff --git a/test/parallel/test-path.js b/test/parallel/test-path.js index e8c282053550cf..d9029a5d7ce595 100644 --- a/test/parallel/test-path.js +++ b/test/parallel/test-path.js @@ -1,11 +1,12 @@ 'use strict'; -var common = require('../common'); -var assert = require('assert'); +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); -var path = require('path'); - -var f = __filename; +const f = __filename; +const failures = []; +// path.basename tests assert.equal(path.basename(f), 'test-path.js'); assert.equal(path.basename(f, '.js'), 'test-path'); assert.equal(path.basename(''), ''); @@ -31,22 +32,21 @@ assert.equal(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\'); // POSIX filenames may include control characters // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html -if (!common.isWindows) { - var controlCharFilename = 'Icon' + String.fromCharCode(13); - assert.equal(path.basename('/a/b/' + controlCharFilename), - controlCharFilename); -} +const controlCharFilename = 'Icon' + String.fromCharCode(13); +assert.equal(path.posix.basename('/a/b/' + controlCharFilename), + controlCharFilename); -assert.equal(path.extname(f), '.js'); +// path.dirname tests assert.equal(path.dirname(f).substr(-13), common.isWindows ? 'test\\parallel' : 'test/parallel'); -assert.equal(path.dirname('/a/b/'), '/a'); -assert.equal(path.dirname('/a/b'), '/a'); -assert.equal(path.dirname('/a'), '/'); -assert.equal(path.dirname(''), '.'); -assert.equal(path.dirname('/'), '/'); -assert.equal(path.dirname('////'), '/'); + +assert.equal(path.posix.dirname('/a/b/'), '/a'); +assert.equal(path.posix.dirname('/a/b'), '/a'); +assert.equal(path.posix.dirname('/a'), '/'); +assert.equal(path.posix.dirname(''), '.'); +assert.equal(path.posix.dirname('/'), '/'); +assert.equal(path.posix.dirname('////'), '/'); assert.equal(path.win32.dirname('c:\\'), 'c:\\'); assert.equal(path.win32.dirname('c:\\foo'), 'c:\\'); @@ -75,51 +75,79 @@ assert.equal(path.win32.dirname('\\\\unc\\share\\foo\\bar\\'), '\\\\unc\\share\\foo'); assert.equal(path.win32.dirname('\\\\unc\\share\\foo\\bar\\baz'), '\\\\unc\\share\\foo\\bar'); +assert.equal(path.win32.dirname('/a/b/'), '/a'); +assert.equal(path.win32.dirname('/a/b'), '/a'); +assert.equal(path.win32.dirname('/a'), '/'); +assert.equal(path.win32.dirname(''), '.'); +assert.equal(path.win32.dirname('/'), '/'); +assert.equal(path.win32.dirname('////'), '/'); + + +// path.extname tests +[ + [f, '.js'], + ['', ''], + ['/path/to/file', ''], + ['/path/to/file.ext', '.ext'], + ['/path.to/file.ext', '.ext'], + ['/path.to/file', ''], + ['/path.to/.file', ''], + ['/path.to/.file.ext', '.ext'], + ['/path/to/f.ext', '.ext'], + ['/path/to/..ext', '.ext'], + ['/path/to/..', ''], + ['file', ''], + ['file.ext', '.ext'], + ['.file', ''], + ['.file.ext', '.ext'], + ['/file', ''], + ['/file.ext', '.ext'], + ['/.file', ''], + ['/.file.ext', '.ext'], + ['.path/file.ext', '.ext'], + ['file.ext.ext', '.ext'], + ['file.', '.'], + ['.', ''], + ['./', ''], + ['.file.ext', '.ext'], + ['.file', ''], + ['.file.', '.'], + ['.file..', '.'], + ['..', ''], + ['../', ''], + ['..file.ext', '.ext'], + ['..file', '.file'], + ['..file.', '.'], + ['..file..', '.'], + ['...', '.'], + ['...ext', '.ext'], + ['....', '.'], + ['file.ext/', '.ext'], + ['file.ext//', '.ext'], + ['file/', ''], + ['file//', ''], + ['file./', '.'], + ['file.//', '.'], +].forEach(function(test) { + [path.posix.extname, path.win32.extname].forEach(function(extname) { + let input = test[0]; + if (extname === path.win32.extname) + input = input.replace(/\//g, '\\'); + const actual = extname(input); + const expected = test[1]; + const fn = 'path.' + + (extname === path.win32.extname ? 'win32' : 'posix') + + '.extname('; + const message = fn + JSON.stringify(input) + ')' + + '\n expect=' + JSON.stringify(expected) + + '\n actual=' + JSON.stringify(actual); + if (actual !== expected) + failures.push('\n' + message); + }); +}); +assert.equal(failures.length, 0, failures.join('')); - -assert.equal(path.extname(''), ''); -assert.equal(path.extname('/path/to/file'), ''); -assert.equal(path.extname('/path/to/file.ext'), '.ext'); -assert.equal(path.extname('/path.to/file.ext'), '.ext'); -assert.equal(path.extname('/path.to/file'), ''); -assert.equal(path.extname('/path.to/.file'), ''); -assert.equal(path.extname('/path.to/.file.ext'), '.ext'); -assert.equal(path.extname('/path/to/f.ext'), '.ext'); -assert.equal(path.extname('/path/to/..ext'), '.ext'); -assert.equal(path.extname('file'), ''); -assert.equal(path.extname('file.ext'), '.ext'); -assert.equal(path.extname('.file'), ''); -assert.equal(path.extname('.file.ext'), '.ext'); -assert.equal(path.extname('/file'), ''); -assert.equal(path.extname('/file.ext'), '.ext'); -assert.equal(path.extname('/.file'), ''); -assert.equal(path.extname('/.file.ext'), '.ext'); -assert.equal(path.extname('.path/file.ext'), '.ext'); -assert.equal(path.extname('file.ext.ext'), '.ext'); -assert.equal(path.extname('file.'), '.'); -assert.equal(path.extname('.'), ''); -assert.equal(path.extname('./'), ''); -assert.equal(path.extname('.file.ext'), '.ext'); -assert.equal(path.extname('.file'), ''); -assert.equal(path.extname('.file.'), '.'); -assert.equal(path.extname('.file..'), '.'); -assert.equal(path.extname('..'), ''); -assert.equal(path.extname('../'), ''); -assert.equal(path.extname('..file.ext'), '.ext'); -assert.equal(path.extname('..file'), '.file'); -assert.equal(path.extname('..file.'), '.'); -assert.equal(path.extname('..file..'), '.'); -assert.equal(path.extname('...'), '.'); -assert.equal(path.extname('...ext'), '.ext'); -assert.equal(path.extname('....'), '.'); -assert.equal(path.extname('file.ext/'), '.ext'); -assert.equal(path.extname('file.ext//'), '.ext'); -assert.equal(path.extname('file/'), ''); -assert.equal(path.extname('file//'), ''); -assert.equal(path.extname('file./'), '.'); -assert.equal(path.extname('file.//'), '.'); - -// On windows, backspace is a path separator. +// On Windows, backslash is a path separator. assert.equal(path.win32.extname('.\\'), ''); assert.equal(path.win32.extname('..\\'), ''); assert.equal(path.win32.extname('file.ext\\'), '.ext'); @@ -129,7 +157,7 @@ assert.equal(path.win32.extname('file\\\\'), ''); assert.equal(path.win32.extname('file.\\'), '.'); assert.equal(path.win32.extname('file.\\\\'), '.'); -// On unix, backspace is a valid name component like any other character. +// On *nix, backslash is a valid name component like any other character. assert.equal(path.posix.extname('.\\'), ''); assert.equal(path.posix.extname('..\\'), '.\\'); assert.equal(path.posix.extname('file.ext\\'), '.ext\\'); @@ -139,9 +167,10 @@ assert.equal(path.posix.extname('file\\\\'), ''); assert.equal(path.posix.extname('file.\\'), '.\\'); assert.equal(path.posix.extname('file.\\\\'), '.\\\\'); + // path.join tests -var failures = []; -var joinTests = +const joinTests = [ + [ [path.posix.join, path.win32.join], // arguments result [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], @@ -189,70 +218,90 @@ var joinTests = [['/', '', '/foo'], '/foo'], [['', '/', 'foo'], '/foo'], [['', '/', '/foo'], '/foo'] - ]; + ] + ] +]; // Windows-specific join tests -if (common.isWindows) { - joinTests = joinTests.concat( - [// UNC path expected - [['//foo/bar'], '//foo/bar/'], - [['\\/foo/bar'], '//foo/bar/'], - [['\\\\foo/bar'], '//foo/bar/'], +joinTests.push([ + path.win32.join, + joinTests[0][1].slice(0).concat( + [// arguments result + // UNC path expected + [['//foo/bar'], '\\\\foo\\bar\\'], + [['\\/foo/bar'], '\\\\foo\\bar\\'], + [['\\\\foo/bar'], '\\\\foo\\bar\\'], // UNC path expected - server and share separate - [['//foo', 'bar'], '//foo/bar/'], - [['//foo/', 'bar'], '//foo/bar/'], - [['//foo', '/bar'], '//foo/bar/'], + [['//foo', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', 'bar'], '\\\\foo\\bar\\'], + [['//foo', '/bar'], '\\\\foo\\bar\\'], // UNC path expected - questionable - [['//foo', '', 'bar'], '//foo/bar/'], - [['//foo/', '', 'bar'], '//foo/bar/'], - [['//foo/', '', '/bar'], '//foo/bar/'], + [['//foo', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', '/bar'], '\\\\foo\\bar\\'], // UNC path expected - even more questionable - [['', '//foo', 'bar'], '//foo/bar/'], - [['', '//foo/', 'bar'], '//foo/bar/'], - [['', '//foo/', '/bar'], '//foo/bar/'], + [['', '//foo', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', '/bar'], '\\\\foo\\bar\\'], // No UNC path expected (no double slash in first component) - [['\\', 'foo/bar'], '/foo/bar'], - [['\\', '/foo/bar'], '/foo/bar'], - [['', '/', '/foo/bar'], '/foo/bar'], - // No UNC path expected (no non-slashes in first component - questionable) - [['//', 'foo/bar'], '/foo/bar'], - [['//', '/foo/bar'], '/foo/bar'], - [['\\\\', '/', '/foo/bar'], '/foo/bar'], + [['\\', 'foo/bar'], '\\foo\\bar'], + [['\\', '/foo/bar'], '\\foo\\bar'], + [['', '/', '/foo/bar'], '\\foo\\bar'], + // No UNC path expected (no non-slashes in first component - + // questionable) + [['//', 'foo/bar'], '\\foo\\bar'], + [['//', '/foo/bar'], '\\foo\\bar'], + [['\\\\', '/', '/foo/bar'], '\\foo\\bar'], [['//'], '/'], // No UNC path expected (share name missing - questionable). - [['//foo'], '/foo'], - [['//foo/'], '/foo/'], - [['//foo', '/'], '/foo/'], - [['//foo', '', '/'], '/foo/'], + [['//foo'], '\\foo'], + [['//foo/'], '\\foo\\'], + [['//foo', '/'], '\\foo\\'], + [['//foo', '', '/'], '\\foo\\'], // No UNC path expected (too many leading slashes - questionable) - [['///foo/bar'], '/foo/bar'], - [['////foo', 'bar'], '/foo/bar'], - [['\\\\\\/foo/bar'], '/foo/bar'], + [['///foo/bar'], '\\foo\\bar'], + [['////foo', 'bar'], '\\foo\\bar'], + [['\\\\\\/foo/bar'], '\\foo\\bar'], // Drive-relative vs drive-absolute paths. This merely describes the // status quo, rather than being obviously right [['c:'], 'c:.'], [['c:.'], 'c:.'], [['c:', ''], 'c:.'], [['', 'c:'], 'c:.'], - [['c:.', '/'], 'c:./'], + [['c:.', '/'], 'c:.\\'], [['c:.', 'file'], 'c:file'], - [['c:', '/'], 'c:/'], - [['c:', 'file'], 'c:/file'] - ]); -} - -// Run the join tests. + [['c:', '/'], 'c:\\'], + [['c:', 'file'], 'c:\\file'] + ] + ) +]); joinTests.forEach(function(test) { - var actual = path.join.apply(path, test[0]); - var expected = common.isWindows ? test[1].replace(/\//g, '\\') : test[1]; - var message = 'path.join(' + test[0].map(JSON.stringify).join(',') + ')' + - '\n expect=' + JSON.stringify(expected) + - '\n actual=' + JSON.stringify(actual); - if (actual !== expected) failures.push('\n' + message); - // assert.equal(actual, expected, message); + if (!Array.isArray(test[0])) + test[0] = [test[0]]; + test[0].forEach(function(join) { + test[1].forEach(function(test) { + const actual = join.apply(null, test[0]); + const expected = test[1]; + let actualAlt; + // For non-Windows specific tests with the Windows join(), we need to try + // replacing the slashes since the non-Windows specific tests' `expected` + // use forward slashes + if (join === path.win32.join) + actualAlt = actual.replace(/\\/g, '/'); + const fn = 'path.' + + (join === path.win32.join ? 'win32' : 'posix') + + '.join('; + const message = fn + test[0].map(JSON.stringify).join(',') + ')' + + '\n expect=' + JSON.stringify(expected) + + '\n actual=' + JSON.stringify(actual); + if (actual !== expected && actualAlt !== expected) + failures.push('\n' + message); + }); + }); }); assert.equal(failures.length, 0, failures.join('')); + // Test thrown TypeErrors var typeErrorTests = [true, false, 7, null, {}, undefined, [], NaN]; @@ -286,7 +335,7 @@ typeErrorTests.forEach(function(test) { }); -// path normalize tests +// path.normalize tests assert.equal(path.win32.normalize('./fixtures///b/../b/c.js'), 'fixtures\\b\\c.js'); assert.equal(path.win32.normalize('/foo/../../../bar'), '\\bar'); @@ -303,46 +352,60 @@ assert.equal(path.posix.normalize('a//b//../b'), 'a/b'); assert.equal(path.posix.normalize('a//b//./c'), 'a/b/c'); assert.equal(path.posix.normalize('a//b//.'), 'a/b'); + // path.resolve tests -var resolveTests; -if (common.isWindows) { - // windows - resolveTests = - // arguments result - [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], - [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], - [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], - [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], - [['.'], process.cwd()], - [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], - [['c:/', '//'], 'c:\\'], - [['c:/', '//dir'], 'c:\\dir'], - [['c:/', '//server/share'], '\\\\server\\share\\'], - [['c:/', '//server//share'], '\\\\server\\share\\'], - [['c:/', '///some//dir'], 'c:\\some\\dir'] - ]; -} else { - // Posix - resolveTests = - // arguments result - [[['/var/lib', '../', 'file/'], '/var/file'], - [['/var/lib', '/../', 'file/'], '/file'], - [['a/b/c/', '../../..'], process.cwd()], - [['.'], process.cwd()], - [['/some/dir', '.', '/absolute/'], '/absolute']]; -} -failures = []; +const resolveTests = [ + [ path.win32.resolve, + // arguments result + [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], + [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], + [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], + [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], + [['.'], process.cwd()], + [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], + [['c:/', '//'], 'c:\\'], + [['c:/', '//dir'], 'c:\\dir'], + [['c:/', '//server/share'], '\\\\server\\share\\'], + [['c:/', '//server//share'], '\\\\server\\share\\'], + [['c:/', '///some//dir'], 'c:\\some\\dir'], + [['C:\\foo\\tmp.3\\', '..\\tmp.3\\cycles\\root.js'], + 'C:\\foo\\tmp.3\\cycles\\root.js'] + ] + ], + [ path.posix.resolve, + // arguments result + [[['/var/lib', '../', 'file/'], '/var/file'], + [['/var/lib', '/../', 'file/'], '/file'], + [['a/b/c/', '../../..'], process.cwd()], + [['.'], process.cwd()], + [['/some/dir', '.', '/absolute/'], '/absolute'], + [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'] + ] + ] +]; resolveTests.forEach(function(test) { - var actual = path.resolve.apply(path, test[0]); - var expected = test[1]; - var message = 'path.resolve(' + test[0].map(JSON.stringify).join(',') + ')' + - '\n expect=' + JSON.stringify(expected) + - '\n actual=' + JSON.stringify(actual); - if (actual !== expected) failures.push('\n' + message); - // assert.equal(actual, expected, message); + const resolve = test[0]; + test[1].forEach(function(test) { + const actual = resolve.apply(null, test[0]); + let actualAlt; + if (resolve === path.win32.resolve && !common.isWindows) + actualAlt = actual.replace(/\\/g, '/'); + else if (resolve !== path.win32.resolve && common.isWindows) + actualAlt = actual.replace(/\//g, '\\'); + const expected = test[1]; + const fn = 'path.' + + (resolve === path.win32.resolve ? 'win32' : 'posix') + + '.resolve('; + const message = fn + test[0].map(JSON.stringify).join(',') + ')' + + '\n expect=' + JSON.stringify(expected) + + '\n actual=' + JSON.stringify(actual); + if (actual !== expected && actualAlt !== expected) + failures.push('\n' + message); + }); }); assert.equal(failures.length, 0, failures.join('')); + // path.isAbsolute tests assert.equal(path.win32.isAbsolute('//server/file'), true); assert.equal(path.win32.isAbsolute('\\\\server\\file'), true); @@ -358,44 +421,58 @@ assert.equal(path.posix.isAbsolute('/home/foo/..'), true); assert.equal(path.posix.isAbsolute('bar/'), false); assert.equal(path.posix.isAbsolute('./baz'), false); + // path.relative tests -var relativeTests; -if (common.isWindows) { - // windows - relativeTests = - // arguments result - [['c:/blah\\blah', 'd:/games', 'd:\\games'], - ['c:/aaaa/bbbb', 'c:/aaaa', '..'], - ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], - ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], - ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], - ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], - ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], - ['c:/aaaa/bbbb', 'd:\\', 'd:\\']]; -} else { - // posix - relativeTests = - // arguments result - [['/var/lib', '/var', '..'], - ['/var/lib', '/bin', '../../bin'], - ['/var/lib', '/var/lib', ''], - ['/var/lib', '/var/apache', '../apache'], - ['/var/', '/var/lib', 'lib'], - ['/', '/var/lib', 'var/lib']]; -} -failures = []; +const relativeTests = [ + [ path.win32.relative, + // arguments result + [['c:/blah\\blah', 'd:/games', 'd:\\games'], + ['c:/aaaa/bbbb', 'c:/aaaa', '..'], + ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], + ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], + ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], + ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], + ['c:/aaaa/bbbb', 'd:\\', 'd:\\'], + ['c:/AaAa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaaa/', 'c:/aaaa/cccc', '..\\aaaa\\cccc'], + ['C:\\foo\\bar\\baz\\quux', 'C:\\', '..\\..\\..\\..'], + ['C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json', 'bar\\package.json'] + ] + ], + [ path.posix.relative, + // arguments result + [['/var/lib', '/var', '..'], + ['/var/lib', '/bin', '../../bin'], + ['/var/lib', '/var/lib', ''], + ['/var/lib', '/var/apache', '../apache'], + ['/var/', '/var/lib', 'lib'], + ['/', '/var/lib', 'var/lib'], + ['/foo/test', '/foo/test/bar/package.json', 'bar/package.json'] + ] + ] +]; relativeTests.forEach(function(test) { - var actual = path.relative(test[0], test[1]); - var expected = test[2]; - var message = 'path.relative(' + - test.slice(0, 2).map(JSON.stringify).join(',') + - ')' + - '\n expect=' + JSON.stringify(expected) + - '\n actual=' + JSON.stringify(actual); - if (actual !== expected) failures.push('\n' + message); + const relative = test[0]; + test[1].forEach(function(test) { + const actual = relative(test[0], test[1]); + const expected = test[2]; + const fn = 'path.' + + (relative === path.win32.relative ? 'win32' : 'posix') + + '.relative('; + const message = fn + + test.slice(0, 2).map(JSON.stringify).join(',') + + ')' + + '\n expect=' + JSON.stringify(expected) + + '\n actual=' + JSON.stringify(actual); + if (actual !== expected) + failures.push('\n' + message); + }); }); assert.equal(failures.length, 0, failures.join('')); + +// path.sep tests // windows assert.equal(path.win32.sep, '\\'); // posix @@ -404,11 +481,32 @@ assert.equal(path.posix.sep, '/'); // path.delimiter tests // windows assert.equal(path.win32.delimiter, ';'); - // posix assert.equal(path.posix.delimiter, ':'); +// path._makeLong tests +assert.equal(path.posix._makeLong('/foo/bar'), '/foo/bar'); +assert.equal(path.posix._makeLong('foo/bar'), 'foo/bar'); +if (common.isWindows) { + // These tests cause resolve() to insert the cwd, so we cannot test them from + // non-Windows platforms (easily) + assert.equal(path.win32._makeLong('foo\\bar').toLowerCase(), + '\\\\?\\' + process.cwd().toLowerCase() + '\\foo\\bar'); + assert.equal(path.win32._makeLong('foo/bar').toLowerCase(), + '\\\\?\\' + process.cwd().toLowerCase() + '\\foo\\bar'); + assert.equal(path.win32._makeLong('C:').toLowerCase(), + '\\\\?\\' + process.cwd().toLowerCase()); + assert.equal(path.win32._makeLong('C').toLowerCase(), + '\\\\?\\' + process.cwd().toLowerCase() + '\\c'); +} +assert.equal(path.win32._makeLong('C:\\foo'), '\\\\?\\C:\\foo'); +assert.equal(path.win32._makeLong('C:/foo'), '\\\\?\\C:\\foo'); +assert.equal(path.win32._makeLong('\\\\foo\\bar'), '\\\\?\\UNC\\foo\\bar\\'); +assert.equal(path.win32._makeLong('//foo//bar'), '\\\\?\\UNC\\foo\\bar\\'); +assert.equal(path.win32._makeLong('\\\\?\\foo'), '\\\\?\\foo'); + + if (common.isWindows) assert.deepEqual(path, path.win32, 'should be win32 path module'); else