path: Add support for retaining whether a path is an explicit folder reference
MODIFY
packages/@romejs/path/__rtests__/index.ts
packages/@romejs/path/__rtests__/index.ts
@@ -17,7 +17,7 @@ const relativeTests: Array<[string, string, string]> = [
['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', 'd:\\', 'D:\\'],
['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'],
@@ -72,7 +72,7 @@ for (let i = 0; i < relativeTests.length; i++) {
},
out: {
filename: relative.join(),
- segments: relative.getSegments(),
+ segments: relative.getRawSegments(),
},
},
});
@@ -83,12 +83,13 @@ for (let i = 0; i < relativeTests.length; i++) {
const segmentTests: Array<[string, Array<string>]> = [
['./../images/test.png', ['..', 'images', 'test.png']],
+ ['foo/', ['foo', '']],
];
for (let i = 0; i < segmentTests.length; i++) {
const [loc, expectedSegments] = segmentTests[i];
test(`segments: ${i}: ${loc}`, t => {
- t.looksLike(createUnknownFilePath(loc).getSegments(), expectedSegments);
+ t.looksLike(createUnknownFilePath(loc).getRawSegments(), expectedSegments);
});
}

MODIFY
packages/@romejs/path/collections.ts
packages/@romejs/path/collections.ts
@@ -61,24 +61,25 @@ class FilePathMap<FilePath extends UnknownFilePath, Value> {
}
delete(path: FilePath) {
- const joined = path.join();
+ const joined = path.getUnique().join();
this.joinedToValue.delete(joined);
this.joinedToPath.delete(joined);
this._updateSize();
}
has(path: FilePath): boolean {
- return this.joinedToValue.has(path.join());
+ return this.joinedToValue.has(path.getUnique().join());
}
get(path: FilePath): undefined | Value {
- return this.joinedToValue.get(path.join());
+ return this.joinedToValue.get(path.getUnique().join());
}
set(path: FilePath, value: Value) {
- const joined = path.join();
+ const uniq = path.getUnique() as FilePath;
+ const joined = uniq.join();
this.joinedToValue.set(joined, value);
- this.joinedToPath.set(joined, path);
+ this.joinedToPath.set(joined, uniq);
this._updateSize();
}
}

MODIFY
packages/@romejs/path/index.ts
packages/@romejs/path/index.ts
@@ -36,6 +36,7 @@ class BaseFilePath<Super extends UnknownFilePath> {
this.absoluteType = parsed.absoluteType;
// Memoized
+ this.memoizedUnique = undefined;
this.memoizedParent = opts.parent;
this.memoizedFilename = opts.filename;
this.memoizedExtension = opts.ext;
@@ -44,6 +45,7 @@ class BaseFilePath<Super extends UnknownFilePath> {
segments: PathSegments;
+ memoizedUnique: undefined | Super;
memoizedFilename: undefined | string;
memoizedExtension: undefined | string;
memoizedParent: undefined | Super;
@@ -104,7 +106,8 @@ class BaseFilePath<Super extends UnknownFilePath> {
getBasename(): string {
const {segments} = this;
- return segments[segments.length - 1];
+ const offset = this.isExplicitFolder() ? 2 : 1;
+ return segments[segments.length - offset];
}
getExtensionlessBasename(): string {
@@ -135,7 +138,12 @@ class BaseFilePath<Super extends UnknownFilePath> {
}
getParentSegments(): PathSegments {
- return this.segments.slice(0, -1);
+ const segments = this.getSegments().slice(0, -1);
+ // Always make this an explicit folder
+ if (segments.length > 0 && segments[0] !== '') {
+ segments.push('');
+ }
+ return segments;
}
toExplicitRelative(): RelativeFilePath {
@@ -244,6 +252,11 @@ class BaseFilePath<Super extends UnknownFilePath> {
return !this.isURL() && (firstSeg === '.' || firstSeg === '..');
}
+ isExplicitFolder(): boolean {
+ const {segments} = this;
+ return segments[segments.length - 1] === '';
+ }
+
getExtensions(): string {
if (this.memoizedExtension === undefined) {
const ext = getExtension(this.getBasename());
@@ -259,9 +272,43 @@ class BaseFilePath<Super extends UnknownFilePath> {
}
getSegments(): PathSegments {
+ if (this.isExplicitFolder()) {
+ return this.segments.slice(0, -1);
+ } else {
+ return this.segments;
+ }
+ }
+
+ getRawSegments(): PathSegments {
return this.segments;
}
+ getUnique(): Super {
+ if (this.memoizedUnique !== undefined) {
+ return this.memoizedUnique;
+ }
+
+ let segments: undefined | PathSegments;
+
+ if (this.isExplicitFolder()) {
+ segments = this.getSegments();
+
+ if (this.isExplicitRelative()) {
+ segments = segments.slice(1);
+ }
+ } else if (this.isExplicitRelative()) {
+ segments = this.getRawSegments().slice(1);
+ }
+
+ if (segments === undefined) {
+ return this._assert();
+ } else {
+ const path = this._fork(parsePathSegments(segments), {});
+ this.memoizedUnique = path;
+ return path;
+ }
+ }
+
// Support some bad string coercion. Such as serialization in CLI flags.
toString(): string {
return this.join();
@@ -667,8 +714,8 @@ function normalizeSegments(
continue;
}
- // Ignore empty segments, important scenarios have already been handled above
- if (seg === '') {
+ // Ignore empty segments unless it's last
+ if (seg === '' && i !== segments.length - 1) {
continue;
}

path: Add support for retaining whether a path is an explicit folder reference