From 0114d322287e935c4e2731b9281890e3b31b5442 Mon Sep 17 00:00:00 2001 From: Rob Eisenberg Date: Thu, 18 Dec 2014 01:38:11 -0500 Subject: [PATCH] fix(join): numerous bug fixes to the join algorithm This fix addresses issues with absolute paths and null/empty paths in the join algorithm. The normalize function was also renamed to relativeToFile. This is a breaking change. --- ACKNOWLEDGEMENTS.md | 4 +- src/index.js | 71 ++++++++++++++++------------ test/path.spec.js | 110 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 146 insertions(+), 39 deletions(-) diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md index dc54501..9745a7b 100644 --- a/ACKNOWLEDGEMENTS.md +++ b/ACKNOWLEDGEMENTS.md @@ -1,8 +1,6 @@ # Acknowledgements -The `normalize` function in this library was adapted from the internals of [http://requirejs.org/](http://requirejs.org/). Much thanks goes to the authors who worked out this algorithm. Below is the required notice from the original library. - -Also, the `join` function comes from [this gist](https://gist.github.com/creationix/7435851). +The `relativeToFile` function in this library was adapted from the internals of [http://requirejs.org/](http://requirejs.org/). Much thanks goes to the authors who worked out this algorithm. Below is the required notice from the original library. --- diff --git a/src/index.js b/src/index.js index 4a512c5..a82fce0 100644 --- a/src/index.js +++ b/src/index.js @@ -21,21 +21,21 @@ function trimDots(ary) { } } -export function normalize(name, baseName){ +export function relativeToFile(name, file){ var lastIndex, normalizedBaseParts, - baseParts = (baseName && baseName.split('/')); + fileParts = (file && file.split('/')); name = name.trim(); name = name.split('/'); - if (name[0].charAt(0) === '.' && baseParts) { - //Convert baseName to array, and lop off the last part, - //so that . matches that 'directory' and not name of the baseName's - //module. For instance, baseName of 'one/two/three', maps to + if (name[0].charAt(0) === '.' && fileParts) { + //Convert file to array, and lop off the last part, + //so that . matches that 'directory' and not name of the file's + //module. For instance, file of 'one/two/three', maps to //'one/two/three.js', but we want the directory, 'one/two' for //this normalization. - normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + normalizedBaseParts = fileParts.slice(0, fileParts.length - 1); name = normalizedBaseParts.concat(name); } @@ -44,29 +44,40 @@ export function normalize(name, baseName){ return name.join('/'); } -// Joins path segments. Preserves initial "/" and resolves ".." and "." -// Does not support using ".." to go above/outside the root. -// This means that join("foo", "../../bar") will not resolve to "../bar" -export function join(/* path segments */) { - // Split the inputs into a list of path commands. - var parts = []; - for (var i = 0, l = arguments.length; i < l; i++) { - parts = parts.concat(arguments[i].split("/")); +export function join(path1, path2) { + var url1, url2, url3; + + if(!path1){ + return path2; + } + + if(!path2){ + return path1; + } + + url1 = path1.split('/'); + url2 = path2.split('/'); + url3 = []; + + for (var i = 0, l = url1.length; i < l; i ++) { + if (url1[i] == '..') { + url3.pop(); + } else if (url1[i] == '.' || url1[i] == '') { + continue; + } else { + url3.push(url1[i]); + } } - // Interpret the path commands to get the new resolved path. - var newParts = []; - for (i = 0, l = parts.length; i < l; i++) { - var part = parts[i]; - // Remove leading and trailing slashes - // Also remove "." segments - if (!part || part === ".") continue; - // Interpret ".." to pop the last segment - if (part === "..") newParts.pop(); - // Push new path segments. - else newParts.push(part); + + for (var i = 0, l = url2.length; i < l; i ++) { + if (url2[i] == '..') { + url3.pop(); + } else if (url2[i] == '.' || url2[i] == '') { + continue; + } else { + url3.push(url2[i]); + } } - // Preserve the initial slash if there was one. - if (parts[0] === "") newParts.unshift(""); - // Turn back into a single string path. - return newParts.join("/") || (newParts.length ? "/" : "."); + + return url3.join('/').replace(/\:\//g, '://');; } \ No newline at end of file diff --git a/test/path.spec.js b/test/path.spec.js index a17b5b5..bd081a8 100644 --- a/test/path.spec.js +++ b/test/path.spec.js @@ -1,19 +1,117 @@ -import { normalize, join } from '../src/index'; +import { relativeToFile, join } from '../src/index'; -describe('normalize', () => { - it('can make a path relative to a file', () => { - var baseUrl = 'some/file.html'; +describe('relativeToFile', () => { + it('can make a dot path relative to a simple file', () => { + var file = 'some/file.html'; var path = './other/module'; - expect(normalize(path, baseUrl)).toBe('some/other/module'); + expect(relativeToFile(path, file)).toBe('some/other/module'); + }); + + it('can make a dot path relative to an absolute file', () => { + var file = 'http://durandal.io/some/file.html'; + var path = './other/module'; + + expect(relativeToFile(path, file)).toBe('http://durandal.io/some/other/module'); + }); + + it('can make a double dot path relative to an absolute file', () => { + var file = 'http://durandal.io/some/file.html'; + var path = '../other/module'; + + expect(relativeToFile(path, file)).toBe('http://durandal.io/other/module'); + }); + + it('returns path if null file provided', () => { + var file = null; + var path = 'module'; + + expect(relativeToFile(path, file)).toBe('module'); + }); + + it('returns path if empty file provided', () => { + var file = ''; + var path = 'module'; + + expect(relativeToFile(path, file)).toBe('module'); }); }); describe('join', () => { - it('can join two paths', () => { + it('can combine two simple paths', () => { var path1 = 'one'; var path2 = 'two'; expect(join(path1, path2)).toBe('one/two'); }); + + it('can combine an absolute path and a simple path', () => { + var path1 = 'http://durandal.io'; + var path2 = 'two'; + + expect(join(path1, path2)).toBe('http://durandal.io/two'); + }); + + it('can combine an absolute path and a simple path with slash', () => { + var path1 = 'http://durandal.io'; + var path2 = '/two'; + + expect(join(path1, path2)).toBe('http://durandal.io/two'); + }); + + it('can combine an absolute path and a simple path with a dot', () => { + var path1 = 'http://durandal.io'; + var path2 = './two'; + + expect(join(path1, path2)).toBe('http://durandal.io/two'); + }); + + it('can combine a simple path and a relative path', () => { + var path1 = 'one'; + var path2 = '../two'; + + expect(join(path1, path2)).toBe('two'); + }); + + it('can combine an absolute path and a relative path', () => { + var path1 = 'http://durandal.io/somewhere'; + var path2 = '../two'; + + expect(join(path1, path2)).toBe('http://durandal.io/two'); + }); + + it('can combine a complex path and a relative path', () => { + var path1 = 'one/three'; + var path2 = '../two'; + + expect(join(path1, path2)).toBe('one/two'); + }); + + it('returns path2 if path1 null', () => { + var path1 = null; + var path2 = 'two'; + + expect(join(path1, path2)).toBe('two'); + }); + + it('returns path2 if path1 empty', () => { + var path1 = ''; + var path2 = 'two'; + + expect(join(path1, path2)).toBe('two'); + }); + + it('returns path1 if path2 null', () => { + var path1 = 'one'; + var path2 = null; + + expect(join(path1, path2)).toBe('one'); + }); + + it('returns path1 if path2 empty', () => { + var path1 = 'one'; + var path2 = ''; + + expect(join(path1, path2)).toBe('one'); + }); }); \ No newline at end of file