Skip to content
This repository
Browse code

path.join stuff

(and some minor fixing in path.resolve)
  • Loading branch information...
commit 6e71b65651125efc53036b745cef005605cbbada 1 parent 7c75ca7
Bert Belder authored August 21, 2012
38  lib/path.js
@@ -59,7 +59,7 @@ if (isWindows) {
59 59
   // Regex to split a windows path into three parts: [*, device, slash,
60 60
   // tail] windows-only
61 61
   var splitDeviceRe =
62  
-      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;
  62
+      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
63 63
 
64 64
   // Regex to split the tail part of the above into [*, dir, basename, ext]
65 65
   var splitTailRe =
@@ -80,6 +80,10 @@ if (isWindows) {
80 80
     return [device, dir, basename, ext];
81 81
   };
82 82
 
  83
+  function normalizeUNCRoot(device) {
  84
+    return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
  85
+  }
  86
+
83 87
   // path.resolve([from ...], to)
84 88
   // windows version
85 89
   exports.resolve = function() {
@@ -138,8 +142,11 @@ if (isWindows) {
138 142
       }
139 143
     }
140 144
 
141  
-    // Replace slashes (in UNC share name) by backslashes
142  
-    resolvedDevice = resolvedDevice.replace(/\//g, '\\');
  145
+    // Convert slashes to backslashes when `resolvedDevice` points to an UNC
  146
+    // root. Also squash multiple slashes into a single one where appropriate.
  147
+    if (isUnc) {
  148
+      resolvedDevice = normalizeUNCRoot(resolvedDevice);
  149
+    }
143 150
 
144 151
     // At this point the path should be resolved to a full absolute path,
145 152
     // but handle relative paths to be safe (might happen when process.cwd()
@@ -180,7 +187,10 @@ if (isWindows) {
180 187
     }
181 188
 
182 189
     // Convert slashes to backslashes when `device` points to an UNC root.
183  
-    device = device.replace(/\//g, '\\');
  190
+    // Also squash multiple slashes into a single one where appropriate.
  191
+    if (isUnc) {
  192
+      device = normalizeUNCRoot(device);
  193
+    }
184 194
 
185 195
     return device + (isAbsolute ? '\\' : '') + tail;
186 196
   };
@@ -194,11 +204,21 @@ if (isWindows) {
194 204
     var paths = Array.prototype.slice.call(arguments, 0).filter(f);
195 205
     var joined = paths.join('\\');
196 206
 
197  
-    // Make sure that the joined path doesn't start with two slashes
198  
-    // - it will be mistaken for an unc path by normalize() -
199  
-    // unless the paths[0] also starts with two slashes
200  
-    if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) {
201  
-      joined = joined.slice(1);
  207
+    // Make sure that the joined path doesn't start with two slashes, because
  208
+    // normalize() will mistake it for an UNC path then.
  209
+    //
  210
+    // This step is skipped when it is very clear that the user actually
  211
+    // intended to point at an UNC path. This is assumed when the first
  212
+    // non-empty string arguments starts with exactly two slashes followed by
  213
+    // at least one more non-slash character.
  214
+    //
  215
+    // Note that for normalize() to treat a path as an UNC path it needs to
  216
+    // have at least 2 components, so we don't filter for that here.
  217
+    // This means that the user can use join to construct UNC paths from
  218
+    // a server name and a share name; for example:
  219
+    //   path.join('//server', 'share') -> '\\\\server\\share\')
  220
+    if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
  221
+      joined = joined.replace(/^[\\\/]{2,}/, '\\');
202 222
     }
203 223
 
204 224
     return exports.normalize(joined);
66  test/simple/test-path.js
@@ -172,9 +172,67 @@ var joinTests =
172 172
      [[' ', '.'], ' '],
173 173
      [[' ', '/'], ' /'],
174 174
      [[' ', ''], ' '],
  175
+     [['/', 'foo'], '/foo'],
  176
+     [['/', '/foo'], '/foo'],
  177
+     [['/', '//foo'], '/foo'],
  178
+     [['/', '', '/foo'], '/foo'],
  179
+     [['', '/', 'foo'], '/foo'],
  180
+     [['', '/', '/foo'], '/foo'],
175 181
      // filtration of non-strings.
176 182
      [['x', true, 7, 'y', null, {}], 'x/y']
177 183
     ];
  184
+
  185
+// Windows-specific join tests
  186
+if (isWindows) {
  187
+  joinTests = joinTests.concat(
  188
+    [// UNC path expected
  189
+     [['//foo/bar'], '//foo/bar/'],
  190
+     [['\\/foo/bar'], '//foo/bar/'],
  191
+     [['\\\\foo/bar'], '//foo/bar/'],
  192
+     // UNC path expected - server and share separate
  193
+     [['//foo', 'bar'], '//foo/bar/'],
  194
+     [['//foo/', 'bar'], '//foo/bar/'],
  195
+     [['//foo', '/bar'], '//foo/bar/'],
  196
+     // UNC path expected - questionable
  197
+     [['//foo', '', 'bar'], '//foo/bar/'],
  198
+     [['//foo/', '', 'bar'], '//foo/bar/'],
  199
+     [['//foo/', '', '/bar'], '//foo/bar/'],
  200
+     // UNC path expected - even more questionable
  201
+     [['', '//foo', 'bar'], '//foo/bar/'],
  202
+     [['', '//foo/', 'bar'], '//foo/bar/'],
  203
+     [['', '//foo/', '/bar'], '//foo/bar/'],
  204
+     // No UNC path expected (no double slash in first component)
  205
+     [['\\', 'foo/bar'], '/foo/bar'],
  206
+     [['\\', '/foo/bar'], '/foo/bar'],
  207
+     [['', '/', '/foo/bar'], '/foo/bar'],
  208
+     // No UNC path expected (no non-slashes in first component - questionable)
  209
+     [['//', 'foo/bar'], '/foo/bar'],
  210
+     [['//', '/foo/bar'], '/foo/bar'],
  211
+     [['\\\\', '/', '/foo/bar'], '/foo/bar'],
  212
+     [['//'], '/'],
  213
+     // No UNC path expected (share name missing - questionable).
  214
+     [['//foo'], '/foo'],
  215
+     [['//foo/'], '/foo/'],
  216
+     [['//foo', '/'], '/foo/'],
  217
+     [['//foo', '', '/'], '/foo/'],
  218
+     // No UNC path expected (too many leading slashes - questionable)
  219
+     [['///foo/bar'], '/foo/bar'],
  220
+     [['////foo', 'bar'], '/foo/bar'],
  221
+     [['\\\\\\/foo/bar'], '/foo/bar'],
  222
+     // Drive-relative vs drive-absolute paths. This merely describes the
  223
+     // status quo, rather than being obviously right
  224
+     [['c:'], 'c:.'],
  225
+     [['c:.'], 'c:.'],
  226
+     [['c:', ''], 'c:.'],
  227
+     [['', 'c:'], 'c:.'],
  228
+     [['c:.', '/'], 'c:./'],
  229
+     [['c:.', 'file'], 'c:file'],
  230
+     [['c:', '/'], 'c:/'],
  231
+     [['c:', 'file'], 'c:/file']
  232
+    ]);
  233
+}
  234
+
  235
+// Run the join tests.
178 236
 joinTests.forEach(function(test) {
179 237
   var actual = path.join.apply(path, test[0]);
180 238
   var expected = isWindows ? test[1].replace(/\//g, '\\') : test[1];
@@ -215,7 +273,13 @@ if (isWindows) {
215 273
        [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'],
216 274
        [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'],
217 275
        [['.'], process.cwd()],
218  
-       [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative']];
  276
+       [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'],
  277
+       [['c:/', '//'], 'c:\\'],
  278
+       [['c:/', '//dir'], 'c:\\dir'],
  279
+       [['c:/', '//server/share'], '\\\\server\\share\\'],
  280
+       [['c:/', '//server//share'], '\\\\server\\share\\'],
  281
+       [['c:/', '///some//dir'], 'c:\\some\\dir']
  282
+      ];
219 283
 } else {
220 284
   // Posix
221 285
   var resolveTests =

2 notes on commit 6e71b65

Bert Belder
Collaborator

@isaacs I suggest landing this in master and marking #3610 WONTFIX for 0.8.

Bert Belder
Collaborator

@isaacs Did you manage to review this yet?

Please sign in to comment.
Something went wrong with that request. Please try again.