-
-
Notifications
You must be signed in to change notification settings - Fork 606
Simplification of toUnix and normalizePath #1441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ative lookbehind for the DOUBLE_SLASH_RE constant. Removes the now unused DOUBLE_SLASH constant.
|
Not sure what is causing the tests to fail here. I've just run them locally on every version of node listed, and all tests pass in every case. |
|
some nonsense is going on with CI for sure ill see if i can figure it out |
|
@43081j Small additional thing: is there any case where function normalizePath(path: Path): Path {
if (typeof path !== 'string') throw new Error('string expected');
return toUnix(sp.normalize(path));
}const normalizePathToUnix = (path: Path) => toUnix(sp.normalize(toUnix(path)));I'm pretty sure that inner call to |
|
One final thing, there's a TODO regarding refactoring const normalizeIgnored =
(cwd = '') =>
(path: Matcher): Matcher => {
if (typeof path === 'string') {
return normalizePathToUnix(sp.isAbsolute(path) ? path : sp.join(cwd, path));
} else {
return path;
}
};I propose creating an function isStringMatcher(matcher: Matcher): matcher is string {
return typeof matcher === 'string';
}The const normalizeIgnored =
(cwd = '') =>
(path: Matcher): Matcher => {
if (isStringMatcher(path)) {
return normalizePathToUnix(getAbsolutePath(path, cwd));
}
return path;
};Or, if you prefer something more concise: const normalizeIgnored =
(cwd = '') =>
(path: Matcher): Matcher =>
isStringMatcher(path)
? normalizePathToUnix(getAbsolutePath(path, cwd))
: path;Both approaches make use of the existing |
|
they are likely the same but have existed for different amounts of time (so just tech debt) we should unify them if we can. not necessarily in this PR as we need to figure out what would be affected by no longer throwing an error for extracting the string type guard, do you have an example of where else it would be useful? by itself it doesn't seem to give us much benefit, especially since |
In terms of type narrowing, it doesn't provide any benefit. However, there are a lot of cases where we check the matcher type is a string, and I think it reads nicer if the implementation details are hidden behind a simple utility. I admit that for strings, this yields the smallest benefit, but moving something like the function createPattern(matcher: Matcher): MatchFunction {
if (isFunctionMatcher(matcher)) return matcher;
if (isStringMatcher(matcher)) return (string) => matcher === string;
if (isRegExpMatcher(matcher)) return (string) => matcher.test(string);
if (isObjectMatcher(matcher)) {
return (string) => {
if (matcher.path === string) return true;
if (matcher.recursive) {
const relative = sp.relative(matcher.path, string);
if (!relative) {
return false;
}
return !relative.startsWith('..') && !sp.isAbsolute(relative);
}
return false;
};
}
return () => false;
}I also think that the isMatcherObject(new Date())will return This version, for example, would rule out arrays, dates, and several other object types, and it'll ensure that whenever function isObjectMatcher(matcher: Matcher): matcher is MatcherObject {
return typeof matcher === 'object' && isStringMatcher(matcher?.path);
}EDIT: I suppose better yet would be this: function isObjectMatcher(matcher: unknown): matcher is MatcherObject {
return typeof matcher === 'object' && isStringMatcher((matcher as any)?.path);
}EDIT 2: Here is what I envision the function createMatchFunctionFromObject(matcher: MatcherObject): MatchFunction {
if (!matcher.recursive) return (string: string) => matcher.path === string;
return (string: string) => {
if (matcher.path === string) return true;
const relative = sp.relative(matcher.path, string);
return relative ? !relative.startsWith('..') && !sp.isAbsolute(relative) : false;
};
}
function createPattern(matcher: Matcher): MatchFunction {
if (isFunctionMatcher(matcher)) return matcher;
if (isStringMatcher(matcher)) return (string) => matcher === string;
if (isRegExpMatcher(matcher)) return (string) => matcher.test(string);
if (isObjectMatcher(matcher)) return createMatchFunctionFromObject(matcher);
return () => false;
}Or even this: function createPattern(matcher: Matcher): MatchFunction {
if (isFunctionMatcher(matcher)) return matcher;
if (isStringMatcher(matcher)) return createMatchFunctionFromString(matcher);
if (isRegExpMatcher(matcher)) return createMatchFunctionFromRegExp(matcher);
if (isObjectMatcher(matcher)) return createMatchFunctionFromObject(matcher);
return () => false;
} |
|
you're basically suggesting runtime type checking, which i'm not sure i agree with. we statically type this to already accept the right shape, so this if someone passes an object which doesn't satisfy this type definition, i don't think we need to care, that's their own fault. |
My proposed check differs from the current one only in that we check for a Checking for a string if (matcher.path === string) return true;
if (matcher.recursive) {
const relative = sp.relative(matcher.path, string);
if (!relative) {
return false;
}
return !relative.startsWith('..') && !sp.isAbsolute(relative);
} |
if (matcher.path === string) return true;
if (matcher.recursive) {
const relative = sp.relative(matcher.path, string);
if (!relative) {
return false;
}
return !relative.startsWith('..') && !sp.isAbsolute(relative);
}this only happens as part of the test function of a matcher object (not a RegExp) if (matcher instanceof RegExp) return (string) => matcher.test(string);
if (typeof matcher === 'object' && matcher !== null) {
return (string) => {
if (matcher.path === string) return true;
if (matcher.recursive) {
const relative = sp.relative(matcher.path, string);
if (!relative) {
return false;
}
return !relative.startsWith('..') && !sp.isAbsolute(relative);
}
return false;
};
}everything inside the anonymous function assumes
so i don't understand this part. this function is only called for matcher objects, what am i missing? 🤔
we have strongly typed what our function accepts. if the user passes nonsense into it, i think its their own fault. i feel like you're trying to guard against invalid inputs from users. but im saying we don't need to care because we've strongly defined what we accept and its your own fault if you pass something that isn't that. this is already correctly narrowing for the types we accept. we don't need to check if |
Yes, I understand that. The RegExp thing is actually not relevant, as I misremembered the condition for the matcher object branch. There's a utility defined right above called const isMatcherObject = (matcher: Matcher): matcher is MatcherObject =>
typeof matcher === 'object' && matcher !== null && !(matcher instanceof RegExp);As you can see, it performs essentially the same check as the one in the conditional statement, but it also checks whether the object is a RegExp instance. It turns out, however, that this utility is used just about everywhere except in the chain of conditions inside That being said, the argument for my alternative condition is the same if we're trying to rule out For example, if we end up with a matcher object being passed in with an incorrect key, like so: { pth: "somePath", recursive: true }Why should we give the user back a matcher function in this form: (string) => {
if (matcher.path === string) return true;
if (matcher.recursive) {
const relative = sp.relative(matcher.path, string);
if (!relative) {
return false;
}
return !relative.startsWith('..') && !sp.isAbsolute(relative);
}
return false;
};When the updated check can give them the following, avoiding a bunch of useless work when the matcher function is used:
If that's true, then why do we need to check for The thing is, we just say that the options we're getting from the user are We have this type where the user is passing in config: type FSWInstanceOptions = BasicOpts & {
ignored: Matcher[]; // string | fn ->
awaitWriteFinish: false | AWF;
};which defines the options being passed into the If I've missed something, I'm happy to be corrected. |
|
I will also say that this has nothing really to do with the current PR, so I'm happy to move this to somewhere more appropriate. |
Simplifies the implementation of
toUnixandnormalizePath. Adds a negative lookbehind for theDOUBLE_SLASH_REconstant. Removes the now unusedDOUBLE_SLASHconstant.