in node/internal.ts [570:662]
export function _getFindInfoFromPattern(defaultRoot: string, pattern: string, matchOptions: _MatchOptions): _PatternFindInfo {
// parameter validation
if (!defaultRoot) {
throw new Error('getFindRootFromPattern() parameter defaultRoot cannot be empty');
}
if (!pattern) {
throw new Error('getFindRootFromPattern() parameter pattern cannot be empty');
}
if (!matchOptions.nobrace) {
throw new Error('getFindRootFromPattern() expected matchOptions.nobrace to be true');
}
// for the sake of determining the findPath, pretend nocase=false
matchOptions = _cloneMatchOptions(matchOptions);
matchOptions.nocase = false;
// check if basename only and matchBase=true
if (matchOptions.matchBase &&
!_isRooted(pattern) &&
(process.platform == 'win32' ? pattern.replace(/\\/g, '/') : pattern).indexOf('/') < 0) {
return <_PatternFindInfo>{
adjustedPattern: pattern, // for basename only scenarios, do not root the pattern
findPath: defaultRoot,
statOnly: false,
};
}
// the technique applied by this function is to use the information on the Minimatch object determine
// the findPath. Minimatch breaks the pattern into path segments, and exposes information about which
// segments are literal vs patterns.
//
// note, the technique currently imposes a limitation for drive-relative paths with a glob in the
// first segment, e.g. C:hello*/world. it's feasible to overcome this limitation, but is left unsolved
// for now.
let minimatchObj = new minimatch.Minimatch(pattern, matchOptions);
// the "set" property is an array of arrays of parsed path segment info. the outer array should only
// contain one item, otherwise something went wrong. brace expansion can result in multiple arrays,
// but that should be turned off by the time this function is reached.
if (minimatchObj.set.length != 1) {
throw new Error('getFindRootFromPattern() expected Minimatch(...).set.length to be 1. Actual: ' + minimatchObj.set.length);
}
let literalSegments: string[] = [];
for (let parsedSegment of minimatchObj.set[0]) {
if (typeof parsedSegment == 'string') {
// the item is a string when the original input for the path segment does not contain any
// unescaped glob characters.
//
// note, the string here is already unescaped (i.e. glob escaping removed), so it is ready
// to pass to find() as-is. for example, an input string 'hello\\*world' => 'hello*world'.
literalSegments.push(parsedSegment);
continue;
}
break;
}
// join the literal segments back together. Minimatch converts '\' to '/' on Windows, then squashes
// consequetive slashes, and finally splits on slash. this means that UNC format is lost, but can
// be detected from the original pattern.
let joinedSegments = literalSegments.join('/');
if (joinedSegments && process.platform == 'win32' && _startsWith(pattern.replace(/\\/g, '/'), '//')) {
joinedSegments = '/' + joinedSegments; // restore UNC format
}
// determine the find path
let findPath: string;
if (_isRooted(pattern)) { // the pattern was rooted
findPath = joinedSegments;
}
else if (joinedSegments) { // the pattern was not rooted, and literal segments were found
findPath = _ensureRooted(defaultRoot, joinedSegments);
}
else { // the pattern was not rooted, and no literal segments were found
findPath = defaultRoot;
}
// clean up the path
if (findPath) {
findPath = _getDirectoryName(_ensureRooted(findPath, '_')); // hack to remove unnecessary trailing slash
findPath = _normalizeSeparators(findPath); // normalize slashes
}
return <_PatternFindInfo>{
adjustedPattern: _ensurePatternRooted(defaultRoot, pattern),
findPath: findPath,
statOnly: literalSegments.length == minimatchObj.set[0].length,
};
}