426 lines
20 KiB
Markdown
426 lines
20 KiB
Markdown
# Glob Matching Algorithm Specification
|
||
|
||
This document is a formal algorithmic specification of the file-path glob matching
|
||
logic. An implementation conforming to this specification must produce identical
|
||
results for all inputs. All subroutine errors are propagated as errors of the
|
||
calling routine unless stated otherwise.
|
||
|
||
---
|
||
|
||
## 1. Definitions
|
||
|
||
**Path** — A normalized, `/`-separated absolute file path (e.g., `/project/src/index.ts`).
|
||
|
||
**Path component** — A single segment between `/` delimiters (e.g., `src`, `index.ts`). The leading `/` produces the root component, which is the empty string `""`.
|
||
|
||
**Spec** — A user-provided glob string (e.g., `src/**/*.ts`), before compilation.
|
||
|
||
**Base path** — The absolute directory against which relative specs are resolved.
|
||
|
||
**Usage mode** — One of three modes that alter matching semantics:
|
||
- **Files** — Matches complete file paths.
|
||
- **Directories** — Matches directory prefixes for traversal pruning.
|
||
- **Exclude** — Matches paths to be excluded.
|
||
|
||
**Component kind** — One of:
|
||
- **Literal** — Contains no `*` or `?` characters.
|
||
- **Wildcard** — Contains at least one `*` or `?` character.
|
||
- **DoubleAsterisk** — The exact string `**`.
|
||
|
||
**Character** — A single Unicode scalar value (codepoint). See Section 9 for the precise character-boundary requirements that apply during segment matching.
|
||
|
||
**Segment kind** — One of:
|
||
- **SegLiteral** — An exact literal substring.
|
||
- **SegStar** — Matches zero or more characters excluding `/`.
|
||
- **SegQuestion** — Matches exactly one character excluding `/`.
|
||
|
||
**Pattern** — A compiled spec consisting of a component list, a usage mode, and a case-sensitivity flag.
|
||
|
||
---
|
||
|
||
## 2. Helper Predicates
|
||
|
||
**IS_HIDDEN_PATH**(_component_)
|
||
|
||
> 1. If the length of _component_ is 0, return **false**.
|
||
> 2. If the first character of _component_ is `"."`, return **true**.
|
||
> 3. Return **false**.
|
||
|
||
**IS_PACKAGE_FOLDER**(_component_)
|
||
|
||
> 1. If _component_ equals `"node_modules"` (case-insensitive), return **true**.
|
||
> 2. If _component_ equals `"bower_components"` (case-insensitive), return **true**.
|
||
> 3. If _component_ equals `"jspm_packages"` (case-insensitive), return **true**.
|
||
> 4. Return **false**.
|
||
|
||
**ENSURE_TRAILING_SLASH**(_s_)
|
||
|
||
> 1. If the length of _s_ is 0, return _s_.
|
||
> 2. If the last character of _s_ is `"/"`, return _s_.
|
||
> 3. Return _s_ concatenated with `"/"`.
|
||
|
||
**STRINGS_EQUAL**(_a_, _b_, _caseSensitive_)
|
||
|
||
> 1. If _caseSensitive_ is **true**, return whether _a_ and _b_ are byte-for-byte identical.
|
||
> 2. Return whether _a_ and _b_ are equal under Unicode case folding.
|
||
|
||
**IS_IMPLICIT_GLOB**(_component_)
|
||
|
||
> 1. If _component_ contains any of the characters `"."`, `"*"`, or `"?"`, return **false**.
|
||
> 2. Return **true**.
|
||
|
||
---
|
||
|
||
## 3. Spec Normalization
|
||
|
||
**NORMALIZE_SPEC**(_spec_, _basePath_)
|
||
|
||
> 1. Let _components_ be the result of resolving _spec_ against _basePath_ into an ordered list of normalized path components. The first element is the absolute root prefix (e.g., `"/home"`). The resolution uses `/` as the path separator, resolves `.` and `..` segments, and collapses consecutive separators.
|
||
> 2. If the last character of _components_\[0\] is `"/"`, remove it.
|
||
> 3. Return _components_.
|
||
|
||
---
|
||
|
||
## 4. Segment Parsing
|
||
|
||
**PARSE_SEGMENTS**(_string_)
|
||
|
||
> 1. Let _segments_ be an empty list.
|
||
> 2. Let _start_ be 0.
|
||
> 3. For each index _i_ from 0 to the length of _string_ − 1:
|
||
> 1. If _string_\[_i_\] is `"*"` or `"?"`, then:
|
||
> 1. If _i_ > _start_, append a **SegLiteral** segment with value _string_\[_start_.._i_\] to _segments_.
|
||
> 2. If _string_\[_i_\] is `"*"`, append a **SegStar** segment to _segments_.
|
||
> 3. Otherwise, append a **SegQuestion** segment to _segments_.
|
||
> 4. Set _start_ to _i_ + 1.
|
||
> 4. If _start_ < length of _string_, append a **SegLiteral** segment with value _string_\[_start_..\] to _segments_.
|
||
> 5. Return _segments_.
|
||
|
||
---
|
||
|
||
## 5. Pattern Compilation
|
||
|
||
**COMPILE_PATTERN**(_spec_, _basePath_, _usage_, _caseSensitive_)
|
||
|
||
> 1. Let _components_ be the result of NORMALIZE_SPEC(_spec_, _basePath_).
|
||
> 2. If the last element of _components_ is `"**"` and _usage_ is not **Exclude**, return **failure**. (The pattern compiles to nothing.)
|
||
> 3. If IS_IMPLICIT_GLOB(last element of _components_) is **true** (note: this check is applied to the _normalized_ component, not the raw spec string), then:
|
||
> 1. Append `"**"` to _components_.
|
||
> 2. Append `"*"` to _components_.
|
||
> 4. Let _compiledComponents_ be an empty list.
|
||
> 5. For each _part_ in _components_:
|
||
> 1. If _part_ is `"**"`, append a **DoubleAsterisk** component to _compiledComponents_.
|
||
> 2. Otherwise, if _part_ contains no `"*"` or `"?"` characters, append a **Literal** component with value _part_ to _compiledComponents_.
|
||
> 3. Otherwise, append a **Wildcard** component with segments PARSE_SEGMENTS(_part_) to _compiledComponents_.
|
||
> 6. Return a pattern with component list _compiledComponents_, usage mode _usage_, and case-sensitivity flag _caseSensitive_.
|
||
|
||
---
|
||
|
||
## 6. Path Component Extraction
|
||
|
||
**NEXT_PATH_COMPONENT**(_path_, _offset_)
|
||
|
||
> 1. If _offset_ ≥ length of _path_, return (**none**, _offset_, **false**).
|
||
> 2. If _offset_ is 0 and _path_\[0\] is `"/"`, return (`""`, 1, **true**).
|
||
> 3. While _offset_ < length of _path_ and _path_\[_offset_\] is `"/"`, increment _offset_.
|
||
> 4. If _offset_ ≥ length of _path_, return (**none**, _offset_, **false**).
|
||
> 5. Let _start_ be _offset_.
|
||
> 6. While _offset_ < length of _path_ and _path_\[_offset_\] is not `"/"`, increment _offset_.
|
||
> 7. Return (_path_\[_start_.._offset_\], _offset_, **true**).
|
||
|
||
---
|
||
|
||
## 7. Full-Path Matching
|
||
|
||
**MATCH_PATH**(_pattern_, _path_)
|
||
|
||
> 1. Return the result of MATCH_PATH_INNER(_pattern_, _path_, 0, 0, **false**).
|
||
|
||
**MATCH_PATH_PREFIX**(_pattern_, _path_)
|
||
|
||
> 1. Return the result of MATCH_PATH_INNER(_pattern_, _path_, 0, 0, **true**).
|
||
|
||
**MATCH_PATH_INNER**(_pattern_, _path_, _pathOffset_, _compIdx_, _prefixOnly_)
|
||
|
||
> 1. Let _components_ be the component list of _pattern_.
|
||
> 2. Let _usage_ be the usage mode of _pattern_.
|
||
> 3. Let _caseSensitive_ be the case-sensitivity flag of _pattern_.
|
||
> 4. Loop:
|
||
> 1. Let (_part_, _nextOffset_, _ok_) be the result of NEXT_PATH_COMPONENT(_path_, _pathOffset_).
|
||
> 2. If _ok_ is **false**, then:
|
||
> 1. If _prefixOnly_ is **true**, return **true**.
|
||
> 2. Return the result of PATTERN_SATISFIED(_components_, _compIdx_).
|
||
> 3. If _compIdx_ ≥ length of _components_, then:
|
||
> 1. If _usage_ is **Exclude** and _prefixOnly_ is **false**, return **true**.
|
||
> 2. Return **false**.
|
||
> 4. Let _comp_ be _components_\[_compIdx_\].
|
||
> 5. If the kind of _comp_ is **DoubleAsterisk**, then:
|
||
> 1. Let _skipResult_ be the result of MATCH_PATH_INNER(_pattern_, _path_, _pathOffset_, _compIdx_ + 1, _prefixOnly_).
|
||
> 2. If _skipResult_ is **true**, return **true**.
|
||
> 3. If _usage_ is not **Exclude**, then:
|
||
> 1. If IS_HIDDEN_PATH(_part_) is **true**, return **false**.
|
||
> 2. If IS_PACKAGE_FOLDER(_part_) is **true**, return **false**.
|
||
> 4. Set _pathOffset_ to _nextOffset_.
|
||
> 5. Continue the loop.
|
||
> 6. If the kind of _comp_ is **Literal**, then:
|
||
> 1. If STRINGS_EQUAL(_comp_.value, _part_, _caseSensitive_) is **false**, return **false**.
|
||
> 7. If the kind of _comp_ is **Wildcard**, then:
|
||
> 1. If _usage_ is not **Exclude** and IS_PACKAGE_FOLDER(_part_) is **true**, return **false**.
|
||
> 2. If the result of MATCH_WILDCARD(_pattern_, _comp_.segments, _part_) is **false**, return **false**.
|
||
> 8. Set _pathOffset_ to _nextOffset_.
|
||
> 9. Increment _compIdx_.
|
||
|
||
**PATTERN_SATISFIED**(_components_, _compIdx_)
|
||
|
||
> 1. For each index _i_ from _compIdx_ to length of _components_ − 1:
|
||
> 1. If the kind of _components_\[_i_\] is not **DoubleAsterisk**, return **false**.
|
||
> 2. Return **true**.
|
||
|
||
---
|
||
|
||
## 8. Wildcard Component Matching
|
||
|
||
**MATCH_WILDCARD**(_pattern_, _segments_, _string_)
|
||
|
||
> 1. Let _usage_ be the usage mode of _pattern_.
|
||
> 2. Let _caseSensitive_ be the case-sensitivity flag of _pattern_.
|
||
> 3. If _usage_ is not **Exclude**, then:
|
||
> 1. If the length of _segments_ > 0, then:
|
||
> 1. Let _firstKind_ be the kind of _segments_\[0\].
|
||
> 2. If (_firstKind_ is **SegStar** or _firstKind_ is **SegQuestion**) and IS_HIDDEN_PATH(_string_) is **true**, return **false**.
|
||
> 4. Let _matched_ be the result of MATCH_SEGMENTS(_segments_, _string_, _caseSensitive_).
|
||
> 5. If _matched_ is **false**, return **false**.
|
||
> 6. Let _accepted_ be the result of SHOULD_ACCEPT_MIN_JS(_pattern_, _segments_, _string_).
|
||
> 7. Return _accepted_.
|
||
|
||
---
|
||
|
||
## 9. Segment Matching
|
||
|
||
In this section, all string positions refer to **character** (codepoint) boundaries.
|
||
Implementations must advance by full codepoints, not by encoding units (e.g., not
|
||
by individual bytes in UTF-8, nor by individual code units in UTF-16). "Increment
|
||
_sIdx_" means advance _sIdx_ past the next character (one codepoint). Likewise,
|
||
"length of _s_" is the number of characters, and _s_\[_sIdx_\] is the character
|
||
at position _sIdx_.
|
||
|
||
The original TypeScript implementation uses ECMAScript regexes without the `u` flag,
|
||
which operate on UTF-16 code units; a conforming implementation may match on
|
||
codepoints instead, as the difference is only observable for supplementary-plane
|
||
characters (U+10000 and above) in filenames.
|
||
|
||
**MATCH_SEGMENTS**(_segments_, _s_, _caseSensitive_)
|
||
|
||
> 1. Let _segIdx_ be 0.
|
||
> 2. Let _sIdx_ be 0.
|
||
> 3. Let _starSegIdx_ be −1.
|
||
> 4. Let _starSIdx_ be 0.
|
||
> 5. While _sIdx_ < length of _s_:
|
||
> 1. If _segIdx_ < length of _segments_, then:
|
||
> 1. Let _seg_ be _segments_\[_segIdx_\].
|
||
> 2. If the kind of _seg_ is **SegLiteral**, then:
|
||
> 1. Let _lit_ be the value of _seg_.
|
||
> 2. If _sIdx_ + length of _lit_ ≤ length of _s_ and STRINGS_EQUAL(_lit_, _s_\[_sIdx_.._sIdx_+len(_lit_)\], _caseSensitive_) is **true**, then:
|
||
> 1. Set _sIdx_ to _sIdx_ + length of _lit_.
|
||
> 2. Increment _segIdx_.
|
||
> 3. Continue the loop.
|
||
> 3. If the kind of _seg_ is **SegQuestion**, then:
|
||
> 1. If _s_\[_sIdx_\] is not `"/"`, then:
|
||
> 1. Increment _sIdx_.
|
||
> 2. Increment _segIdx_.
|
||
> 3. Continue the loop.
|
||
> 4. If the kind of _seg_ is **SegStar**, then:
|
||
> 1. Set _starSegIdx_ to _segIdx_.
|
||
> 2. Set _starSIdx_ to _sIdx_.
|
||
> 3. Increment _segIdx_.
|
||
> 4. Continue the loop.
|
||
> 2. If _starSegIdx_ ≥ 0 and _starSIdx_ < length of _s_ and _s_\[_starSIdx_\] is not `"/"`, then:
|
||
> 1. Increment _starSIdx_.
|
||
> 2. Set _sIdx_ to _starSIdx_.
|
||
> 3. Set _segIdx_ to _starSegIdx_ + 1.
|
||
> 4. Continue the loop.
|
||
> 3. Return **false**.
|
||
> 6. While _segIdx_ < length of _segments_ and the kind of _segments_\[_segIdx_\] is **SegStar**:
|
||
> 1. Increment _segIdx_.
|
||
> 7. Return _segIdx_ ≥ length of _segments_.
|
||
|
||
---
|
||
|
||
## 10. `.min.js` Default Exclusion
|
||
|
||
**SHOULD_ACCEPT_MIN_JS**(_pattern_, _segments_, _filename_)
|
||
|
||
> 1. Let _usage_ be the usage mode of _pattern_.
|
||
> 2. If _usage_ is not **Files**, return **true**.
|
||
> 3. If the result of HAS_MIN_JS_SUFFIX(_filename_, _pattern_.caseSensitive) is **false**, return **true**.
|
||
> 4. If the result of PATTERN_MENTIONS_MIN_SUFFIX(_segments_, _pattern_.caseSensitive) is **true**, return **true**.
|
||
> 5. Return **false**.
|
||
|
||
**HAS_MIN_JS_SUFFIX**(_filename_, _caseSensitive_)
|
||
|
||
> 1. Let _suffix_ be `".min.js"`.
|
||
> 2. If length of _filename_ < length of _suffix_, return **false**.
|
||
> 3. Let _tail_ be the last 7 characters of _filename_.
|
||
> 4. If _caseSensitive_ is **true**, return whether _tail_ equals `".min.js"`.
|
||
> 5. Return whether _tail_ equals `".min.js"` under Unicode case folding.
|
||
|
||
**PATTERN_MENTIONS_MIN_SUFFIX**(_segments_, _caseSensitive_)
|
||
|
||
> 1. For each _seg_ in _segments_:
|
||
> 1. If the kind of _seg_ is not **SegLiteral**, continue.
|
||
> 2. Let _lit_ be the value of _seg_.
|
||
> 3. If _caseSensitive_ is **false**, let _lit_ be the lowercase form of _lit_.
|
||
> 4. If _lit_ contains the substring `".min.js"` or `".min."`, return **true**.
|
||
> 2. Return **false**.
|
||
|
||
---
|
||
|
||
## 11. Composite Matchers
|
||
|
||
**MATCH_FILE**(_path_, _includePatterns_, _excludePatterns_, _hadIncludes_)
|
||
|
||
> 1. For each _pattern_ in _excludePatterns_:
|
||
> 1. If the result of MATCH_PATH(_pattern_, _path_) is **true**, return (0, **false**).
|
||
> 2. If length of _includePatterns_ is 0, then:
|
||
> 1. If _hadIncludes_ is **true**, return (0, **false**).
|
||
> 2. Return (0, **true**).
|
||
> 3. For each index _i_ from 0 to length of _includePatterns_ − 1:
|
||
> 1. If the result of MATCH_PATH(_includePatterns_\[_i_\], _path_) is **true**, return (_i_, **true**).
|
||
> 4. Return (0, **false**).
|
||
|
||
**MATCH_DIRECTORY**(_path_, _includePatterns_, _excludePatterns_, _hadIncludes_)
|
||
|
||
> 1. For each _pattern_ in _excludePatterns_:
|
||
> 1. If the result of MATCH_PATH(_pattern_, _path_) is **true**, return **false**.
|
||
> 2. If length of _includePatterns_ is 0, then:
|
||
> 1. If _hadIncludes_ is **true**, return **false**.
|
||
> 2. Return **true**.
|
||
> 3. For each _pattern_ in _includePatterns_:
|
||
> 1. If the result of MATCH_PATH_PREFIX(_pattern_, _path_) is **true**, return **true**.
|
||
> 4. Return **false**.
|
||
|
||
**MATCH_SPEC**(_patterns_, _path_)
|
||
|
||
> 1. For each _pattern_ in _patterns_:
|
||
> 1. If the result of MATCH_PATH(_pattern_, _path_) is **true**, return **true**.
|
||
> 2. Return **false**.
|
||
|
||
**MATCH_SPEC_INDEX**(_patterns_, _path_)
|
||
|
||
> 1. For each index _i_ from 0 to length of _patterns_ − 1:
|
||
> 1. If the result of MATCH_PATH(_patterns_\[_i_\], _path_) is **true**, return _i_.
|
||
> 2. Return −1.
|
||
|
||
---
|
||
|
||
## 12. Pattern Set Compilation
|
||
|
||
**COMPILE_PATTERNS**(_specs_, _basePath_, _usage_, _caseSensitive_)
|
||
|
||
> 1. Let _patterns_ be an empty list.
|
||
> 2. For each _spec_ in _specs_:
|
||
> 1. Let _result_ be the result of COMPILE_PATTERN(_spec_, _basePath_, _usage_, _caseSensitive_).
|
||
> 2. If _result_ is not **failure**, append _result_ to _patterns_.
|
||
> 3. Return _patterns_.
|
||
|
||
**COMPILE_FILE_MATCHER**(_includeSpecs_, _excludeSpecs_, _basePath_, _caseSensitive_)
|
||
|
||
> 1. Let _includePatterns_ be the result of COMPILE_PATTERNS(_includeSpecs_, _basePath_, **Files**, _caseSensitive_).
|
||
> 2. Let _excludePatterns_ be the result of COMPILE_PATTERNS(_excludeSpecs_, _basePath_, **Exclude**, _caseSensitive_).
|
||
> 3. Let _hadIncludes_ be whether length of _includeSpecs_ > 0.
|
||
> 4. Return (_includePatterns_, _excludePatterns_, _hadIncludes_).
|
||
|
||
**COMPILE_DIRECTORY_MATCHER**(_includeSpecs_, _excludeSpecs_, _basePath_, _caseSensitive_)
|
||
|
||
> 1. Let _includePatterns_ be the result of COMPILE_PATTERNS(_includeSpecs_, _basePath_, **Directories**, _caseSensitive_).
|
||
> 2. Let _excludePatterns_ be the result of COMPILE_PATTERNS(_excludeSpecs_, _basePath_, **Exclude**, _caseSensitive_).
|
||
> 3. Let _hadIncludes_ be whether length of _includeSpecs_ > 0.
|
||
> 4. Return (_includePatterns_, _excludePatterns_, _hadIncludes_).
|
||
|
||
---
|
||
|
||
## 13. Base Path Computation
|
||
|
||
**GET_BASE_PATHS**(_rootPath_, _includeSpecs_, _caseSensitive_)
|
||
|
||
> 1. Let _basePaths_ be a list containing _rootPath_.
|
||
> 2. If _includeSpecs_ is empty, return _basePaths_.
|
||
> 3. Let _includeBasePaths_ be an empty list.
|
||
> 4. For each _spec_ in _includeSpecs_:
|
||
> 1. Let _absolute_ be the result of resolving _spec_ to an absolute normalized path against _rootPath_.
|
||
> 2. Let _basePath_ be GET_INCLUDE_BASE_PATH(_absolute_).
|
||
> 3. Append _basePath_ to _includeBasePaths_.
|
||
> 5. Sort _includeBasePaths_ using a string comparator that is case-insensitive if _caseSensitive_ is **false**.
|
||
> 6. For each _candidate_ in _includeBasePaths_:
|
||
> 1. If no element of _basePaths_ is a path-prefix of _candidate_ (respecting _caseSensitive_), append _candidate_ to _basePaths_.
|
||
> 7. Return _basePaths_.
|
||
|
||
**GET_INCLUDE_BASE_PATH**(_absoluteSpec_)
|
||
|
||
> 1. Let _wildcardOffset_ be the index of the first `"*"` or `"?"` character in _absoluteSpec_.
|
||
> 2. If _wildcardOffset_ < 0, then:
|
||
> 1. If _absoluteSpec_ has a file extension (contains `"."`), return the parent directory of _absoluteSpec_.
|
||
> 2. Return _absoluteSpec_.
|
||
> 3. Return the substring of _absoluteSpec_ up to and including the last `"/"` before _wildcardOffset_.
|
||
|
||
---
|
||
|
||
## 14. Directory Traversal
|
||
|
||
**READ_DIRECTORY**(_host_, _currentDir_, _path_, _extensions_, _excludeSpecs_, _includeSpecs_, _caseSensitive_, _depth_)
|
||
|
||
The _host_ must provide the following operations:
|
||
- **Realpath**(_path_) — Resolves symlinks and returns the canonical absolute path.
|
||
- **GetAccessibleEntries**(_path_) — Returns the sorted lists of files and subdirectories in the directory at _path_.
|
||
|
||
> 1. Let _path_ be the normalized form of _path_.
|
||
> 2. Let _currentDir_ be the normalized form of _currentDir_.
|
||
> 3. Let _absolutePath_ be the concatenation of _currentDir_, `"/"`, and _path_ (normalized).
|
||
> 4. Let (_fileIncludes_, _fileExcludes_, _fileHadIncludes_) be the result of COMPILE_FILE_MATCHER(_includeSpecs_, _excludeSpecs_, _absolutePath_, _caseSensitive_).
|
||
> 5. Let (_dirIncludes_, _dirExcludes_, _dirHadIncludes_) be the result of COMPILE_DIRECTORY_MATCHER(_includeSpecs_, _excludeSpecs_, _absolutePath_, _caseSensitive_).
|
||
> 6. Let _resultBuckets_ be a list of empty lists, with length equal to max(length of _fileIncludes_, 1).
|
||
> 7. Let _visited_ be an empty set of strings.
|
||
> 8. Let _basePaths_ be the result of GET_BASE_PATHS(_path_, _includeSpecs_, _caseSensitive_).
|
||
> 9. For each _basePath_ in _basePaths_:
|
||
> 1. Let _baseAbsolute_ be the concatenation of _currentDir_, `"/"`, and _basePath_ (normalized).
|
||
> 2. Perform VISIT(_host_, _basePath_, _baseAbsolute_, _depth_, _extensions_, _fileIncludes_, _fileExcludes_, _fileHadIncludes_, _dirIncludes_, _dirExcludes_, _dirHadIncludes_, _caseSensitive_, _visited_, _resultBuckets_).
|
||
> 10. Return the concatenation of all lists in _resultBuckets_, in order.
|
||
|
||
**VISIT**(_host_, _path_, _absolutePath_, _depth_, _extensions_, _fileIncludes_, _fileExcludes_, _fileHadIncludes_, _dirIncludes_, _dirExcludes_, _dirHadIncludes_, _caseSensitive_, _visited_, _resultBuckets_)
|
||
|
||
> 1. Let _realPath_ be the result of _host_.Realpath(_absolutePath_).
|
||
> 2. Let _canonicalPath_ be the canonical form of _realPath_ under the file system's case-sensitivity rules.
|
||
> 3. If _visited_ contains _canonicalPath_, return.
|
||
> 4. Add _canonicalPath_ to _visited_.
|
||
> 5. Let _entries_ be the result of _host_.GetAccessibleEntries(_absolutePath_).
|
||
> 6. Let _absPrefix_ be ENSURE_TRAILING_SLASH(_absolutePath_).
|
||
> 7. Let _pathPrefix_ be ENSURE_TRAILING_SLASH(_path_).
|
||
> 8. For each _file_ in _entries_.files:
|
||
> 1. If _extensions_ is non-empty and the file extension of _file_ is not in _extensions_, continue.
|
||
> 2. Let _absFile_ be _absPrefix_ concatenated with _file_.
|
||
> 3. Let (_index_, _matched_) be the result of MATCH_FILE(_absFile_, _fileIncludes_, _fileExcludes_, _fileHadIncludes_).
|
||
> 4. If _matched_ is **true**, append _pathPrefix_ concatenated with _file_ to _resultBuckets_\[_index_\].
|
||
> 9. If _depth_ is finite (i.e., not the sentinel value representing unlimited depth), then:
|
||
> 1. Decrement _depth_.
|
||
> 2. If _depth_ is 0, return.
|
||
> 10. For each _dir_ in _entries_.directories:
|
||
> 1. Let _absDir_ be _absPrefix_ concatenated with _dir_.
|
||
> 2. If the result of MATCH_DIRECTORY(_absDir_, _dirIncludes_, _dirExcludes_, _dirHadIncludes_) is **false**, continue.
|
||
> 3. Perform VISIT(_host_, _pathPrefix_ concatenated with _dir_, _absDir_, _depth_, _extensions_, _fileIncludes_, _fileExcludes_, _fileHadIncludes_, _dirIncludes_, _dirExcludes_, _dirHadIncludes_, _caseSensitive_, _visited_, _resultBuckets_).
|
||
|
||
---
|
||
|
||
## 15. Invariants
|
||
|
||
The following properties hold for all conforming implementations:
|
||
|
||
1. COMPILE_PATTERN returns **failure** for any include or directory spec whose last component is `"**"`.
|
||
2. When the pattern is exhausted but path components remain, exclude patterns return **true** and all other patterns return **false**.
|
||
3. MATCH_SEGMENTS is guaranteed **O(n·m)** where _n_ is the string length and _m_ is the segment count.
|
||
4. The `.min.js` default exclusion applies only under **Files** usage mode and only to wildcard components.
|
||
5. Symlink cycles are detected through real-path canonicalization in VISIT and cause the directory to be skipped.
|
||
6. Excludes are always evaluated before includes in MATCH_FILE and MATCH_DIRECTORY.
|
||
7. For include patterns, wildcard components reject package folders; literal components do not.
|
||
8. For include patterns, `**` does not descend into hidden paths or package folders.
|
||
9. For include patterns, a wildcard component whose first segment is **SegStar** or **SegQuestion** does not match hidden path components.
|