@mrclrchtr/supi-code-intelligence 6.1.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +23 -3
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/README.md +52 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/index.js +785 -129
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/legacy.js +660 -98
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/ignore/package.json +10 -4
- package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +17 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
- package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
- package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +0 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +0 -29
- package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-process-crash-report.ts +106 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +6 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +577 -152
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +4 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +54 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +74 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +5 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +29 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +296 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/README.md +29 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs +99 -19
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js +94 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +14 -14
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +13 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs +97 -19
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts +28 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts.map +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts +28 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js +92 -18
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js.map +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +17 -17
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +2 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
- package/package.json +5 -5
- package/src/analysis/capability/capability-warnings.ts +4 -22
- package/src/analysis/search/ast-scan-timing.ts +4 -2
- package/src/analysis/search/ast-scan.ts +1 -1
- package/src/api.ts +1 -0
- package/src/extension.ts +4 -2
- package/src/session/health-refresh.ts +26 -2
- package/src/session/health-types.ts +19 -0
- package/src/session/input/health-refactor.ts +16 -14
- package/src/session/refactor-types.ts +14 -1
- package/src/session/refactor-workflow.ts +21 -16
- package/src/substrate/lsp/lifecycle.ts +0 -3
- package/src/substrate/lsp/maintenance.ts +4 -5
- package/src/substrate/lsp/settings.ts +7 -9
- package/src/substrate/lsp/state.ts +0 -2
- package/src/substrate/workspace-provider-host.ts +2 -2
- package/src/tool/code_health/file-scope-markdown.ts +43 -0
- package/src/tool/code_health/markdown.ts +66 -66
- package/src/tool/code_health/refresh-outcome.ts +76 -0
- package/src/tool/code_health/refresh-status.ts +212 -23
- package/src/tool/code_health/result.ts +1 -0
- package/src/tool/code_health/tui.ts +18 -5
- package/src/tool/code_refactor_apply/guidance.ts +3 -2
- package/src/tool/code_refactor_plan/guidance.ts +1 -1
- package/src/tool/register.ts +5 -2
- package/src/tool/schemas.ts +21 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
|
@@ -9,7 +9,14 @@ const UNDEFINED = undefined
|
|
|
9
9
|
const EMPTY = ''
|
|
10
10
|
const SPACE = ' '
|
|
11
11
|
const ESCAPE = '\\'
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
// The characters that carry a meaning of their own inside a regular
|
|
14
|
+
// expression, so a literal one has to be escaped before it is emitted.
|
|
15
|
+
const REGEX_LITERAL_SPECIAL = /[.*+?()[\]{}^$|\\/]/
|
|
16
|
+
// A line of only spaces is blank -- the trailing-space trimming empties it --
|
|
17
|
+
// but a line holding a tab is a pattern for a tab-named path, since git
|
|
18
|
+
// never trims a tab.
|
|
19
|
+
const REGEX_TEST_BLANK_LINE = /^ +$/
|
|
13
20
|
const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/
|
|
14
21
|
const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/
|
|
15
22
|
const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/
|
|
@@ -23,9 +30,10 @@ const REGEX_SPLITALL_CRLF = /\r?\n/g
|
|
|
23
30
|
// - ..
|
|
24
31
|
// Valid:
|
|
25
32
|
// - .foo
|
|
26
|
-
const
|
|
33
|
+
const DOUBLE_SLASH = '//'
|
|
34
|
+
const SLASH_CODE = 47
|
|
35
|
+
const DOT_CODE = 46
|
|
27
36
|
|
|
28
|
-
const REGEX_TEST_TRAILING_SLASH = /\/$/
|
|
29
37
|
|
|
30
38
|
const SLASH = '/'
|
|
31
39
|
|
|
@@ -42,36 +50,302 @@ const define = (object, key, value) => {
|
|
|
42
50
|
return value
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g
|
|
46
|
-
|
|
47
53
|
const RETURN_FALSE = () => false
|
|
48
54
|
|
|
49
|
-
// Sanitize the range of a regular expression
|
|
50
|
-
// The cases are complicated, see test cases for details
|
|
51
|
-
const sanitizeRange = range => range.replace(
|
|
52
|
-
REGEX_REGEXP_RANGE,
|
|
53
|
-
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
|
|
54
|
-
? match
|
|
55
|
-
// Invalid range (out of order) which is ok for gitignore rules but
|
|
56
|
-
// fatal for JavaScript regular expression, so eliminate it.
|
|
57
|
-
: EMPTY
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// > An optional `!` or `^` at the start of a class negates it, so that it
|
|
61
|
-
// > matches any character not in the set. (gitignore(5), fnmatch(3))
|
|
62
|
-
// The leading `^` has already been escaped to `\^` by the metacharacter
|
|
63
|
-
// escaper, so we strip the literal `!` or escaped `^` and emit a single
|
|
64
|
-
// regex `^` which is the JavaScript negation token.
|
|
65
|
-
const negateRange = range => range.startsWith('!') || range.startsWith('\\^')
|
|
66
|
-
? `^${range.slice(range[0] === '!' ? 1 : 2)}`
|
|
67
|
-
: range
|
|
68
|
-
|
|
69
55
|
// See fixtures #59
|
|
70
56
|
const cleanRangeBackSlash = slashes => {
|
|
71
57
|
const {length} = slashes
|
|
72
58
|
return slashes.slice(0, length - length % 2)
|
|
73
59
|
}
|
|
74
60
|
|
|
61
|
+
// > The range notation, e.g. [a-zA-Z],
|
|
62
|
+
// > can be used to match one of the characters in a range.
|
|
63
|
+
//
|
|
64
|
+
// gitignore(5) defers to fnmatch(3) for this, and git implements it in
|
|
65
|
+
// `wildmatch.c`. A bracket expression has a sub-grammar of its own, which
|
|
66
|
+
// is neither the surrounding pattern grammar nor the JavaScript one:
|
|
67
|
+
//
|
|
68
|
+
// - a `]` right after `[` or `[!` is a literal member, not the terminator
|
|
69
|
+
// - `[:alpha:]` names one of twelve POSIX classes
|
|
70
|
+
// - `\` escapes the next member, `]` included
|
|
71
|
+
// - `*`, `?` and `.` are plain literal members
|
|
72
|
+
// - an unterminated expression makes the whole pattern match nothing
|
|
73
|
+
//
|
|
74
|
+
// which means the expression can not be located -- let alone translated -- by
|
|
75
|
+
// a regular expression. It is scanned out of the pattern before the
|
|
76
|
+
// replacers below run, and put back once they are done, so that neither the
|
|
77
|
+
// metacharacter escaper nor the `?` / `*` replacers ever see its body.
|
|
78
|
+
|
|
79
|
+
// git classifies with its own ASCII-only ctype macros (`wildmatch.c`), never
|
|
80
|
+
// with the C library ones, so these must not be mapped onto `\d` / `\w` /
|
|
81
|
+
// `\s`, which are wider. `/` is left out of every expansion, because a
|
|
82
|
+
// bracket expression never matches a path separator.
|
|
83
|
+
const POSIX_CLASSES = {
|
|
84
|
+
alnum: '0-9A-Za-z',
|
|
85
|
+
alpha: 'A-Za-z',
|
|
86
|
+
blank: ' \\t',
|
|
87
|
+
cntrl: '\\x00-\\x1f\\x7f',
|
|
88
|
+
digit: '0-9',
|
|
89
|
+
graph: '!-.0-~',
|
|
90
|
+
lower: 'a-z',
|
|
91
|
+
print: ' -.0-~',
|
|
92
|
+
punct: '!-.:-@\\[-`{-~',
|
|
93
|
+
// git's `sane-ctype.h` classifies \v and \f as control, not space,
|
|
94
|
+
// unlike C's `isspace`
|
|
95
|
+
space: ' \\t\\n\\r',
|
|
96
|
+
upper: 'A-Z',
|
|
97
|
+
xdigit: '0-9A-Fa-f'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const CLASS_MEMBERS_TO_ESCAPE = '\\]^-['
|
|
101
|
+
|
|
102
|
+
const escapeMember = char => CLASS_MEMBERS_TO_ESCAPE.indexOf(char) < 0
|
|
103
|
+
? char
|
|
104
|
+
: ESCAPE + char
|
|
105
|
+
|
|
106
|
+
// > if (matched == negated || ((flags & WM_PATHNAME) && text_ch == '/'))
|
|
107
|
+
// > return WM_ABORT_TO_STARSTAR; (git, `wildmatch.c`)
|
|
108
|
+
// A bracket expression never matches a path separator, whatever its members
|
|
109
|
+
// say, so a negated class gets `/` as one more excluded character, while a
|
|
110
|
+
// plain one -- where a literal member or a range could still let `/` in --
|
|
111
|
+
// is guarded with a lookahead, `/` being impossible to subtract from a
|
|
112
|
+
// JavaScript character class.
|
|
113
|
+
const NON_SLASH = '(?!\\/)'
|
|
114
|
+
|
|
115
|
+
const classSource = (negated, body) => {
|
|
116
|
+
if (negated) {
|
|
117
|
+
return `[^\\/${body}]`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const source = `[${body}]`
|
|
121
|
+
|
|
122
|
+
return new RegExp(source).test('/')
|
|
123
|
+
? NON_SLASH + source
|
|
124
|
+
: source
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Scan the bracket expression that starts at `pattern[start] === '['`,
|
|
128
|
+
// mirroring the member loop of git's `wildmatch.c`.
|
|
129
|
+
// @returns {{end: number, source: string} | null} `null` if the expression is
|
|
130
|
+
// never terminated, which makes the whole pattern match nothing.
|
|
131
|
+
const scanBracket = (pattern, start) => {
|
|
132
|
+
const {length} = pattern
|
|
133
|
+
let index = start + 1
|
|
134
|
+
let negated = EMPTY
|
|
135
|
+
|
|
136
|
+
const lead = pattern[index]
|
|
137
|
+
if (lead === '!' || lead === '^') {
|
|
138
|
+
negated = '^'
|
|
139
|
+
index ++
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let body = EMPTY
|
|
143
|
+
|
|
144
|
+
// The member a `-` could start a range from, or EMPTY when the previous
|
|
145
|
+
// member can not open one (start of the body, or a range / POSIX class
|
|
146
|
+
// that has just closed)
|
|
147
|
+
let prev = EMPTY
|
|
148
|
+
|
|
149
|
+
// git scans the members with a do-while, so the first one is consumed
|
|
150
|
+
// unconditionally. That is the whole reason a leading `]` is a member
|
|
151
|
+
// and not the terminator.
|
|
152
|
+
for (;;) {
|
|
153
|
+
const char = pattern[index]
|
|
154
|
+
|
|
155
|
+
if (char === UNDEFINED) {
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (char === ESCAPE) {
|
|
160
|
+
const escaped = pattern[index + 1]
|
|
161
|
+
if (escaped === UNDEFINED) {
|
|
162
|
+
return null
|
|
163
|
+
}
|
|
164
|
+
body += escapeMember(escaped)
|
|
165
|
+
prev = escaped
|
|
166
|
+
index ++
|
|
167
|
+
} else if (
|
|
168
|
+
char === '-'
|
|
169
|
+
&& prev
|
|
170
|
+
&& index + 1 < length
|
|
171
|
+
&& pattern[index + 1] !== ']'
|
|
172
|
+
) {
|
|
173
|
+
index ++
|
|
174
|
+
let to = pattern[index]
|
|
175
|
+
if (to === ESCAPE) {
|
|
176
|
+
// A pattern can not end on a lone backslash -- `checkPattern` has
|
|
177
|
+
// already thrown it away -- so there is an upper bound to read.
|
|
178
|
+
to = pattern[index += 1]
|
|
179
|
+
}
|
|
180
|
+
// An out-of-order range matches nothing in git but is a syntax error in
|
|
181
|
+
// JavaScript, so it is dropped. Its lower bound stays: git tests it
|
|
182
|
+
// as a plain member before it ever looks at the `-`, so `[c-a]` does
|
|
183
|
+
// match `c`.
|
|
184
|
+
if (prev <= to) {
|
|
185
|
+
body += `-${escapeMember(to)}`
|
|
186
|
+
}
|
|
187
|
+
prev = EMPTY
|
|
188
|
+
} else if (char === '[' && pattern[index + 1] === ':') {
|
|
189
|
+
const nameStart = index + 2
|
|
190
|
+
let end = nameStart
|
|
191
|
+
while (end < length && pattern[end] !== ']') {
|
|
192
|
+
end ++
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (end === length) {
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (end > nameStart && pattern[end - 1] === ':') {
|
|
200
|
+
const expanded = POSIX_CLASSES[pattern.slice(nameStart, end - 1)]
|
|
201
|
+
|
|
202
|
+
// An unknown class name makes the whole pattern match nothing
|
|
203
|
+
if (expanded === UNDEFINED) {
|
|
204
|
+
return null
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
body += expanded
|
|
208
|
+
prev = EMPTY
|
|
209
|
+
index = end
|
|
210
|
+
} else {
|
|
211
|
+
// No `:]` to close it, so the `[` is a plain member and scanning
|
|
212
|
+
// resumes right after it.
|
|
213
|
+
body += escapeMember('[')
|
|
214
|
+
prev = '['
|
|
215
|
+
index = nameStart - 2
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
body += escapeMember(char)
|
|
219
|
+
prev = char
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
index ++
|
|
223
|
+
|
|
224
|
+
if (pattern[index] === ']') {
|
|
225
|
+
return {
|
|
226
|
+
end: index,
|
|
227
|
+
source: classSource(negated, body)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// An empty JavaScript class can never match, which is how a pattern that git
|
|
234
|
+
// gives up on (`WM_ABORT_ALL`) is expressed here.
|
|
235
|
+
const NEVER_MATCH = '[]'
|
|
236
|
+
|
|
237
|
+
// A NUL can appear in neither a `.gitignore` line nor a path, which makes it
|
|
238
|
+
// the one safe placeholder character. A literal one in the pattern is
|
|
239
|
+
// held aside all the same, so a collision is impossible by construction.
|
|
240
|
+
const PLACEHOLDER = '\u0000'
|
|
241
|
+
const REGEX_RESTORE_PLACEHOLDER = new RegExp(
|
|
242
|
+
`${PLACEHOLDER}(\\d+)${PLACEHOLDER}`, 'g'
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
// The one wildcard the chain does not expand for itself is a trailing `*`. How
|
|
246
|
+
// it expands depends on the mode (`regex` vs `checkRegex`), so `_make` is
|
|
247
|
+
// left to do it, and until then the pending wildcard travels as this marker.
|
|
248
|
+
// That is what keeps it apart from a user-escaped literal `\*`: the unescape
|
|
249
|
+
// steps collapse the escaped one to the exact `\*` a wildcard would leave
|
|
250
|
+
// behind, so by the time `_make` runs the two are otherwise the same string
|
|
251
|
+
// and a literal star gets wrongly rewritten into a wildcard. This marker is a
|
|
252
|
+
// private-use character no compiled pattern carries, and it never survives
|
|
253
|
+
// into a `RegExp` -- `_make` always turns it back into a real wildcard first.
|
|
254
|
+
const TRAILING_WILDCARD = '\uE000'
|
|
255
|
+
|
|
256
|
+
// Replace every bracket expression with a placeholder the replacers below
|
|
257
|
+
// leave alone, and translate it separately.
|
|
258
|
+
const extractBrackets = pattern => {
|
|
259
|
+
const sources = []
|
|
260
|
+
const hold = source =>
|
|
261
|
+
`${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`
|
|
262
|
+
|
|
263
|
+
const {length} = pattern
|
|
264
|
+
let out = EMPTY
|
|
265
|
+
let index = 0
|
|
266
|
+
|
|
267
|
+
while (index < length) {
|
|
268
|
+
const char = pattern[index]
|
|
269
|
+
|
|
270
|
+
if (char === ESCAPE) {
|
|
271
|
+
// > Put a backslash ("\") in front of ... a character to make it literal.
|
|
272
|
+
// (gitignore(5) -> fnmatch(3))
|
|
273
|
+
// A backslash quotes the next character, whatever it is, so `\d` is a
|
|
274
|
+
// literal `d`, not the regex digit class, and `\?` is a literal `?`,
|
|
275
|
+
// not a wildcard. Held aside as its literal here, the escape never
|
|
276
|
+
// reaches the replacers below, which would otherwise let `\d`, `\b`,
|
|
277
|
+
// `\1`, `\/` keep their regular-expression meaning, and would turn a
|
|
278
|
+
// `\?` into `[^\/]`.
|
|
279
|
+
//
|
|
280
|
+
// Four escapes are left for the chain, each with dedicated handling it
|
|
281
|
+
// would be wrong to bypass: `\*` (a literal star, told apart from a
|
|
282
|
+
// wildcard by `TRAILING_WILDCARD` and the wildcard replacers), `\[`
|
|
283
|
+
// (a literal bracket, the one `[` the bracket replacer still expects),
|
|
284
|
+
// `\ ` (a quoted trailing space), and `\\` (a literal backslash). A
|
|
285
|
+
// lone trailing backslash never reaches here -- `checkPattern` throws
|
|
286
|
+
// it out first.
|
|
287
|
+
const escaped = pattern[index + 1]
|
|
288
|
+
|
|
289
|
+
if (
|
|
290
|
+
escaped === '*'
|
|
291
|
+
|| escaped === '['
|
|
292
|
+
|| escaped === SPACE
|
|
293
|
+
|| escaped === ESCAPE
|
|
294
|
+
) {
|
|
295
|
+
out += pattern.slice(index, index + 2)
|
|
296
|
+
} else {
|
|
297
|
+
out += hold(
|
|
298
|
+
REGEX_LITERAL_SPECIAL.test(escaped)
|
|
299
|
+
? ESCAPE + escaped
|
|
300
|
+
: escaped
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
index += 2
|
|
305
|
+
} else if (char === PLACEHOLDER) {
|
|
306
|
+
// Hold a literal placeholder character aside as well, so that pattern
|
|
307
|
+
// text can never be mistaken for a placeholder we emitted.
|
|
308
|
+
out += hold(`[${PLACEHOLDER}]`)
|
|
309
|
+
index ++
|
|
310
|
+
} else if (char === '[') {
|
|
311
|
+
const scanned = scanBracket(pattern, index)
|
|
312
|
+
|
|
313
|
+
if (scanned === null) {
|
|
314
|
+
// git gives up on the whole pattern (`WM_ABORT_ALL`), so whatever
|
|
315
|
+
// follows can not make it match either.
|
|
316
|
+
out += hold(NEVER_MATCH)
|
|
317
|
+
index = length
|
|
318
|
+
} else {
|
|
319
|
+
out += hold(scanned.source)
|
|
320
|
+
index = scanned.end + 1
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
out += char
|
|
324
|
+
index ++
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
source: out,
|
|
330
|
+
sources
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// A step of the chain below is normally a `[matcher, replacer]` pair handed
|
|
335
|
+
// to `String.replace`. `DIRECT` marks the ones that are plain string work
|
|
336
|
+
// instead, and take `(source, pattern)`.
|
|
337
|
+
//
|
|
338
|
+
// Anchoring the two ends is exactly that -- a test of one character and a
|
|
339
|
+
// concatenation -- and putting it through the regular expression engine
|
|
340
|
+
// cost a third of this chain for nothing: 167ns where 10ns does the same
|
|
341
|
+
// job. The two are still steps in the same list, in the same places,
|
|
342
|
+
// because their position in the order is part of what they mean.
|
|
343
|
+
const DIRECT = null
|
|
344
|
+
|
|
345
|
+
// A separator at the beginning or in the middle of a pattern, as opposed to
|
|
346
|
+
// one at the very end.
|
|
347
|
+
const REGEX_INNER_SLASH = /\/(?!$)/
|
|
348
|
+
|
|
75
349
|
// > If the pattern ends with a slash,
|
|
76
350
|
// > it is removed for the purpose of the following description,
|
|
77
351
|
// > but it would only find a match with a directory.
|
|
@@ -83,6 +357,21 @@ const cleanRangeBackSlash = slashes => {
|
|
|
83
357
|
// you could use option `mark: true` with `glob`
|
|
84
358
|
|
|
85
359
|
// '`foo/`' should not continue with the '`..`'
|
|
360
|
+
// The chain that turns one gitignore pattern into a regular expression
|
|
361
|
+
// source, in order. A step is either
|
|
362
|
+
//
|
|
363
|
+
// [matcher, replacer] handed to `String.replace`
|
|
364
|
+
// [matcher, replacer, required] the same, but skipped outright unless
|
|
365
|
+
// `required` appears in the string, which
|
|
366
|
+
// the matcher cannot match without
|
|
367
|
+
// [DIRECT, transform] plain string work, taking (source, pattern)
|
|
368
|
+
//
|
|
369
|
+
// The `required` character is only ever a shortcut: finding it does not mean
|
|
370
|
+
// the matcher will match, and not finding it means it cannot. It is there
|
|
371
|
+
// because scanning for one character costs a fraction of running a matcher
|
|
372
|
+
// that then finds nothing -- for the wildcard step, whose `[^\\]+`
|
|
373
|
+
// backtracks its way through the whole string before giving up, 11ns
|
|
374
|
+
// against 400ns.
|
|
86
375
|
const REPLACERS = [
|
|
87
376
|
|
|
88
377
|
[
|
|
@@ -90,16 +379,30 @@ const REPLACERS = [
|
|
|
90
379
|
// TODO:
|
|
91
380
|
// Other similar zero-width characters?
|
|
92
381
|
/^\uFEFF/,
|
|
382
|
+
() => EMPTY,
|
|
383
|
+
'\uFEFF'
|
|
384
|
+
],
|
|
385
|
+
|
|
386
|
+
[
|
|
387
|
+
// A trailing line terminator, left on when a whole file's contents are
|
|
388
|
+
// added as one pattern rather than split into lines. git never sees one
|
|
389
|
+
// -- it reads a `.gitignore` line by line -- so it is not part of the
|
|
390
|
+
// pattern and is dropped here, apart from the trailing-space trimming,
|
|
391
|
+
// which follows git in touching spaces and nothing else.
|
|
392
|
+
/[\r\n]+$/,
|
|
93
393
|
() => EMPTY
|
|
94
394
|
],
|
|
95
395
|
|
|
96
396
|
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
97
397
|
[
|
|
398
|
+
// Only spaces, never tabs or other whitespace: git trims a trailing run
|
|
399
|
+
// of `' '` and nothing else (dir.c, `trim_trailing_spaces`, a single
|
|
400
|
+
// `case ' '`), so a pattern ending in a tab keeps it as a literal.
|
|
98
401
|
// (a\ ) -> (a )
|
|
99
402
|
// (a ) -> (a)
|
|
100
403
|
// (a ) -> (a)
|
|
101
404
|
// (a \ ) -> (a )
|
|
102
|
-
/((?:\\\\)*?)(
|
|
405
|
+
/((?:\\\\)*?)(\\? +)$/,
|
|
103
406
|
(_, m1, m2) => m1 + (
|
|
104
407
|
m2.indexOf('\\') === 0
|
|
105
408
|
? SPACE
|
|
@@ -108,11 +411,14 @@ const REPLACERS = [
|
|
|
108
411
|
],
|
|
109
412
|
|
|
110
413
|
// Replace (\ ) with ' '
|
|
414
|
+
// Only a space: an escaped tab or other whitespace is already a literal by
|
|
415
|
+
// the time it reaches here, and a bare tab must be left as one, not turned
|
|
416
|
+
// into a space.
|
|
111
417
|
// (\ ) -> ' '
|
|
112
418
|
// (\\ ) -> '\\ '
|
|
113
419
|
// (\\\ ) -> '\\ '
|
|
114
420
|
[
|
|
115
|
-
/(\\+?)
|
|
421
|
+
/(\\+?) /g,
|
|
116
422
|
(_, m1) => {
|
|
117
423
|
const {length} = m1
|
|
118
424
|
return m1.slice(0, length - length % 2) + SPACE
|
|
@@ -144,7 +450,8 @@ const REPLACERS = [
|
|
|
144
450
|
[
|
|
145
451
|
// > a question mark (?) matches a single character
|
|
146
452
|
/(?!\\)\?/g,
|
|
147
|
-
() => '[^/]'
|
|
453
|
+
() => '[^/]',
|
|
454
|
+
'?'
|
|
148
455
|
],
|
|
149
456
|
|
|
150
457
|
// leading slash
|
|
@@ -154,13 +461,15 @@ const REPLACERS = [
|
|
|
154
461
|
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
155
462
|
// A leading slash matches the beginning of the pathname
|
|
156
463
|
/^\//,
|
|
157
|
-
() => '^'
|
|
464
|
+
() => '^',
|
|
465
|
+
SLASH
|
|
158
466
|
],
|
|
159
467
|
|
|
160
468
|
// replace special metacharacter slash after the leading slash
|
|
161
469
|
[
|
|
162
470
|
/\//g,
|
|
163
|
-
() => '\\/'
|
|
471
|
+
() => '\\/',
|
|
472
|
+
SLASH
|
|
164
473
|
],
|
|
165
474
|
|
|
166
475
|
[
|
|
@@ -173,7 +482,8 @@ const REPLACERS = [
|
|
|
173
482
|
/^\^*(?:\\\*\\\*\\\/)+/,
|
|
174
483
|
|
|
175
484
|
// '**/foo' <-> 'foo'
|
|
176
|
-
() => '^(?:.*\\/)?'
|
|
485
|
+
() => '^(?:.*\\/)?',
|
|
486
|
+
'*'
|
|
177
487
|
],
|
|
178
488
|
|
|
179
489
|
// starting
|
|
@@ -181,10 +491,15 @@ const REPLACERS = [
|
|
|
181
491
|
// there will be no leading '/'
|
|
182
492
|
// (which has been replaced by section "leading slash")
|
|
183
493
|
// If starts with '**', adding a '^' to the regular expression also works
|
|
184
|
-
|
|
185
|
-
|
|
494
|
+
DIRECT,
|
|
495
|
+
(source, pattern) => {
|
|
496
|
+
// Nothing to anchor to, or already anchored
|
|
497
|
+
if (!source || source[0] === '^') {
|
|
498
|
+
return source
|
|
499
|
+
}
|
|
500
|
+
|
|
186
501
|
// If has a slash `/` at the beginning or middle
|
|
187
|
-
|
|
502
|
+
const anchor = !REGEX_INNER_SLASH.test(pattern)
|
|
188
503
|
// > Prior to 2.22.1
|
|
189
504
|
// > If the pattern does not contain a slash /,
|
|
190
505
|
// > Git treats it as a shell glob pattern
|
|
@@ -202,6 +517,8 @@ const REPLACERS = [
|
|
|
202
517
|
// > Otherwise, Git treats the pattern as a shell glob suitable for
|
|
203
518
|
// > consumption by fnmatch(3)
|
|
204
519
|
: '^'
|
|
520
|
+
|
|
521
|
+
return anchor + source
|
|
205
522
|
}
|
|
206
523
|
],
|
|
207
524
|
|
|
@@ -216,18 +533,27 @@ const REPLACERS = [
|
|
|
216
533
|
// Check if it is not the last `'/**'`
|
|
217
534
|
(_, index, str) => index + 6 < str.length
|
|
218
535
|
|
|
219
|
-
// case: /**/
|
|
220
|
-
// > A
|
|
221
|
-
// >
|
|
222
|
-
// >
|
|
223
|
-
// '/**/'
|
|
224
|
-
?
|
|
536
|
+
// case: /**/ at the end of the pattern, i.e. a trailing `'/**/'`
|
|
537
|
+
// > A trailing `"/**/"` (a trailing `"/**"` restricted to directories)
|
|
538
|
+
// > matches everything inside, but it should not match the current
|
|
539
|
+
// > folder itself, so it requires at least one directory segment.
|
|
540
|
+
// 'a/**/' matches 'a/b/', 'a/x/y/' but not 'a/'
|
|
541
|
+
? str.slice(index + 6) === '\\/'
|
|
542
|
+
? '(?:\\/[^\\/]+)+'
|
|
543
|
+
|
|
544
|
+
// case: /**/
|
|
545
|
+
// > A slash followed by two consecutive asterisks then a slash matches
|
|
546
|
+
// > zero or more directories.
|
|
547
|
+
// > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
|
|
548
|
+
// '/**/'
|
|
549
|
+
: '(?:\\/[^\\/]+)*'
|
|
225
550
|
|
|
226
551
|
// case: /**
|
|
227
552
|
// > A trailing `"/**"` matches everything inside.
|
|
228
553
|
|
|
229
554
|
// #21: everything inside but it should not include the current folder
|
|
230
|
-
: '\\/.+'
|
|
555
|
+
: '\\/.+',
|
|
556
|
+
'*'
|
|
231
557
|
],
|
|
232
558
|
|
|
233
559
|
// normal intermediate wildcards
|
|
@@ -250,7 +576,33 @@ const REPLACERS = [
|
|
|
250
576
|
// > and will match according to the previous rules.
|
|
251
577
|
const unescaped = p2.replace(/\\\*/g, '[^\\/]*')
|
|
252
578
|
return p1 + unescaped
|
|
253
|
-
}
|
|
579
|
+
},
|
|
580
|
+
'*'
|
|
581
|
+
],
|
|
582
|
+
|
|
583
|
+
// trailing wildcard, held apart from a literal star
|
|
584
|
+
[
|
|
585
|
+
// The step above leaves a trailing `*` alone, so a single `\*` is all that
|
|
586
|
+
// can be left at the end here. Whether it is a wildcard or a literal
|
|
587
|
+
// turns on the backslashes the user put in front of it: the escaper has
|
|
588
|
+
// since doubled every one, so what stands here is those `2N` doubled
|
|
589
|
+
// backslashes and then the star's own escape. An even number of the
|
|
590
|
+
// original `N` leaves the star unescaped -- a wildcard -- and an odd
|
|
591
|
+
// number escapes it -- a literal. This runs while the two are still
|
|
592
|
+
// distinct, before the unescape steps below collapse the literal onto
|
|
593
|
+
// the very `\*` a wildcard leaves behind.
|
|
594
|
+
/(^|[^\\])((?:\\\\)*)\\\*$/,
|
|
595
|
+
|
|
596
|
+
(match, p1, p2) =>
|
|
597
|
+
// `p2` holds the doubled user backslashes; half of them is `N`.
|
|
598
|
+
(p2.length / 2) % 2 === 0
|
|
599
|
+
// A real wildcard: carry it to `_make` as the marker, so the unescape
|
|
600
|
+
// steps and the trailing-wildcard rewrite can never mistake it for a
|
|
601
|
+
// literal `\*` (nor the reverse).
|
|
602
|
+
? p1 + p2 + TRAILING_WILDCARD
|
|
603
|
+
// A literal star: leave it exactly as it stands for the unescape steps.
|
|
604
|
+
: match,
|
|
605
|
+
'*'
|
|
254
606
|
],
|
|
255
607
|
|
|
256
608
|
[
|
|
@@ -258,41 +610,36 @@ const REPLACERS = [
|
|
|
258
610
|
// For example, if a user escape a '\\*',
|
|
259
611
|
// after step 3, the result will be '\\\\\\*'
|
|
260
612
|
/\\\\\\(?=[$.|*+(){^])/g,
|
|
261
|
-
() => ESCAPE
|
|
613
|
+
() => ESCAPE,
|
|
614
|
+
ESCAPE + ESCAPE
|
|
262
615
|
],
|
|
263
616
|
|
|
264
617
|
[
|
|
265
618
|
// '\\\\' -> '\\'
|
|
266
619
|
/\\\\/g,
|
|
267
|
-
() => ESCAPE
|
|
620
|
+
() => ESCAPE,
|
|
621
|
+
ESCAPE + ESCAPE
|
|
268
622
|
],
|
|
269
623
|
|
|
270
624
|
[
|
|
271
|
-
//
|
|
272
|
-
//
|
|
625
|
+
// Every real bracket expression -- POSIX classes included -- has already
|
|
626
|
+
// been held aside by `extractBrackets`, so the only `[` left in the
|
|
627
|
+
// pattern is an escaped, literal one.
|
|
273
628
|
|
|
274
629
|
// `\` is escaped by step 3
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// A normal case, and it is a range notation
|
|
282
|
-
// '[bar]'
|
|
283
|
-
// '[bar\\\\]'
|
|
284
|
-
? `[${negateRange(sanitizeRange(range))}${endEscape}]`
|
|
285
|
-
// Invalid range notaton
|
|
286
|
-
// '[bar\\]' -> '[bar\\\\]'
|
|
287
|
-
: '[]'
|
|
288
|
-
: '[]'
|
|
630
|
+
/\\\[([^\]/]*?)(\\*)($|\])/g,
|
|
631
|
+
|
|
632
|
+
// '\\[bar]' -> '\\\\[bar\\]'
|
|
633
|
+
(match, range, endEscape, close) =>
|
|
634
|
+
`\\[${range}${cleanRangeBackSlash(endEscape)}${close}`,
|
|
635
|
+
'['
|
|
289
636
|
],
|
|
290
637
|
|
|
291
638
|
// ending
|
|
292
639
|
[
|
|
293
640
|
// 'js' will not match 'js.'
|
|
294
641
|
// 'ab' will not match 'abc'
|
|
295
|
-
|
|
642
|
+
DIRECT,
|
|
296
643
|
|
|
297
644
|
// WTF!
|
|
298
645
|
// https://git-scm.com/docs/gitignore
|
|
@@ -306,15 +653,26 @@ const REPLACERS = [
|
|
|
306
653
|
// 'js*' will not match 'a.js'
|
|
307
654
|
// 'js/' will not match 'a.js'
|
|
308
655
|
// 'js' will match 'a.js' and 'a.js/'
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
//
|
|
313
|
-
|
|
656
|
+
source => {
|
|
657
|
+
const last = source[source.length - 1]
|
|
658
|
+
|
|
659
|
+
// The pattern is empty, or ends in the pending trailing wildcard the next
|
|
660
|
+
// step owns. A trailing `*` that is not the marker is a literal star,
|
|
661
|
+
// which anchors like any other final character.
|
|
662
|
+
if (!last || last === TRAILING_WILDCARD) {
|
|
663
|
+
return source
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return last === SLASH
|
|
667
|
+
// foo/ will not match 'foo'
|
|
668
|
+
? `${source}$`
|
|
669
|
+
// foo matches 'foo' and 'foo/'
|
|
670
|
+
: `${source}(?=$|\\/$)`
|
|
671
|
+
}
|
|
314
672
|
]
|
|
315
673
|
]
|
|
316
674
|
|
|
317
|
-
const REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)
|
|
675
|
+
const REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\uE000$/
|
|
318
676
|
const MODE_IGNORE = 'regex'
|
|
319
677
|
const MODE_CHECK_IGNORE = 'checkRegex'
|
|
320
678
|
const UNDERSCORE = '_'
|
|
@@ -352,12 +710,250 @@ const TRAILING_WILD_CARD_REPLACERS = {
|
|
|
352
710
|
}
|
|
353
711
|
}
|
|
354
712
|
|
|
713
|
+
const WILDCARD = '[^\\/]*'
|
|
714
|
+
|
|
715
|
+
// Where a run of non-slash wildcards is split by single fixed characters --
|
|
716
|
+
// `[^/]*x[^/]*y...` -- the engine has many equivalent ways to lay the
|
|
717
|
+
// input across the wildcards, and on input that does not match it works
|
|
718
|
+
// through all of them, so the time grows with the number of wildcards
|
|
719
|
+
// rather than the length of the path. Every wildcard but the last in such a
|
|
720
|
+
// run can be pinned to stop at the character that follows it, which leaves
|
|
721
|
+
// one way to lay out the input and no rewinding, without changing which
|
|
722
|
+
// paths match: the fixed characters still bound the count, and the last
|
|
723
|
+
// wildcard still absorbs the rest.
|
|
724
|
+
//
|
|
725
|
+
// The source is read one token at a time -- a wildcard, a single-character
|
|
726
|
+
// piece (a literal, an escape, a class), or a parenthesised group or anchor
|
|
727
|
+
// that ends the run -- so only a genuine wildcard is touched.
|
|
728
|
+
const pinWildcards = source => {
|
|
729
|
+
if (source.indexOf(WILDCARD) < 0) {
|
|
730
|
+
return source
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const tokens = []
|
|
734
|
+
const {length} = source
|
|
735
|
+
let index = 0
|
|
736
|
+
|
|
737
|
+
while (index < length) {
|
|
738
|
+
const char = source[index]
|
|
739
|
+
|
|
740
|
+
if (source.startsWith(WILDCARD, index)) {
|
|
741
|
+
tokens.push({wildcard: true})
|
|
742
|
+
index += WILDCARD.length
|
|
743
|
+
} else if (char === '[') {
|
|
744
|
+
let end = index + 1
|
|
745
|
+
|
|
746
|
+
if (source[end] === '^') {
|
|
747
|
+
end ++
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (source[end] === ']') {
|
|
751
|
+
end ++
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
while (end < length && source[end] !== ']') {
|
|
755
|
+
end += source[end] === ESCAPE
|
|
756
|
+
? 2
|
|
757
|
+
: 1
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
end ++
|
|
761
|
+
tokens.push({single: source.slice(index, end)})
|
|
762
|
+
index = end
|
|
763
|
+
} else if (char === ESCAPE) {
|
|
764
|
+
tokens.push({single: source.slice(index, index + 2)})
|
|
765
|
+
index += 2
|
|
766
|
+
} else if (char === '(') {
|
|
767
|
+
let depth = 0
|
|
768
|
+
let end = index
|
|
769
|
+
|
|
770
|
+
do {
|
|
771
|
+
if (source[end] === ESCAPE) {
|
|
772
|
+
end ++
|
|
773
|
+
} else if (source[end] === '(') {
|
|
774
|
+
depth ++
|
|
775
|
+
} else if (source[end] === ')') {
|
|
776
|
+
depth --
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
end ++
|
|
780
|
+
} while (end < length && depth > 0)
|
|
781
|
+
|
|
782
|
+
if ('*+?'.indexOf(source[end]) >= 0) {
|
|
783
|
+
end ++
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
tokens.push({boundary: source.slice(index, end)})
|
|
787
|
+
index = end
|
|
788
|
+
} else if (char === '^' || char === '$') {
|
|
789
|
+
tokens.push({boundary: char})
|
|
790
|
+
index ++
|
|
791
|
+
} else {
|
|
792
|
+
tokens.push({single: char})
|
|
793
|
+
index ++
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
let out = EMPTY
|
|
798
|
+
let run = []
|
|
799
|
+
|
|
800
|
+
const flush = () => {
|
|
801
|
+
let lastWildcard
|
|
802
|
+
|
|
803
|
+
run.forEach((token, at) => {
|
|
804
|
+
if (token.wildcard) {
|
|
805
|
+
lastWildcard = at
|
|
806
|
+
}
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
run.forEach((token, at) => {
|
|
810
|
+
if (!token.wildcard) {
|
|
811
|
+
out += token.single
|
|
812
|
+
return
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// A wildcard that is not the last in the run is always immediately
|
|
816
|
+
// followed by the single character that separates it from the next
|
|
817
|
+
// one, because a run never holds two wildcards in a row, so it can be
|
|
818
|
+
// pinned to stop there. The last wildcard stays as it is and takes up
|
|
819
|
+
// the rest.
|
|
820
|
+
out += at === lastWildcard
|
|
821
|
+
? WILDCARD
|
|
822
|
+
: `(?:(?!${run[at + 1].single})[^\\/])*`
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
run = []
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
tokens.forEach(token => {
|
|
829
|
+
if (token.boundary === undefined) {
|
|
830
|
+
run.push(token)
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
flush()
|
|
835
|
+
out += token.boundary
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
flush()
|
|
839
|
+
|
|
840
|
+
return out
|
|
841
|
+
}
|
|
842
|
+
|
|
355
843
|
// @param {pattern}
|
|
356
|
-
const makeRegexPrefix = pattern =>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
844
|
+
const makeRegexPrefix = pattern => {
|
|
845
|
+
const {source, sources} = extractBrackets(pattern)
|
|
846
|
+
|
|
847
|
+
const replaced = REPLACERS.reduce(
|
|
848
|
+
// A pass whose matcher finds nothing hands back the very string it was
|
|
849
|
+
// given, so asking first costs a search and saves a rewrite. Ten of the
|
|
850
|
+
// fifteen passes never fire for a typical .gitignore line, and between
|
|
851
|
+
// them they were 45% of this chain.
|
|
852
|
+
(prev, [matcher, replacer, required]) => {
|
|
853
|
+
if (matcher === DIRECT) {
|
|
854
|
+
return replacer(prev, pattern)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (required !== UNDEFINED && prev.indexOf(required) < 0) {
|
|
858
|
+
return prev
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// A pass whose matcher finds nothing hands back the very string it was
|
|
862
|
+
// given, so asking first costs a search and saves a rewrite.
|
|
863
|
+
return matcher.test(prev)
|
|
864
|
+
? prev.replace(matcher, replacer.bind(pattern))
|
|
865
|
+
: prev
|
|
866
|
+
},
|
|
867
|
+
source
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
// Most patterns hold no bracket expression at all, and then there is
|
|
871
|
+
// nothing to put back.
|
|
872
|
+
return sources.length
|
|
873
|
+
? replaced.replace(
|
|
874
|
+
REGEX_RESTORE_PLACEHOLDER,
|
|
875
|
+
(match, index) => sources[index]
|
|
876
|
+
)
|
|
877
|
+
: replaced
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// A trailing slash does not stop a pattern being basename-only: it restricts
|
|
881
|
+
// the match to a directory, it does not let the pattern reach across one.
|
|
882
|
+
// Everything else a pattern can hold -- a wildcard, a character class, an
|
|
883
|
+
// escape -- stays inside a single path segment too, so a pattern with no
|
|
884
|
+
// separator in it can only ever describe the last one.
|
|
885
|
+
const matchesBasename = body => {
|
|
886
|
+
const index = body.indexOf(SLASH)
|
|
887
|
+
|
|
888
|
+
return index < 0 || index === body.length - 1
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// The last segment of a path, keeping a trailing slash, because a pattern that
|
|
892
|
+
// ends in one matches only a directory.
|
|
893
|
+
// 'a/b/c.js' -> 'c.js'; 'a/b/' -> 'b/'; 'c.js' -> 'c.js' (no copy made)
|
|
894
|
+
const basenameOf = path => {
|
|
895
|
+
const end = path.length - 1
|
|
896
|
+
|
|
897
|
+
const index = path.lastIndexOf(
|
|
898
|
+
SLASH,
|
|
899
|
+
path[end] === SLASH
|
|
900
|
+
? end - 1
|
|
901
|
+
: end
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
return index < 0
|
|
905
|
+
? path
|
|
906
|
+
: path.slice(index + 1)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// The parent directory of a path, with its trailing separator, or EMPTY when
|
|
910
|
+
// the path has none.
|
|
911
|
+
// 'a/b/c' -> 'a/b/'; 'a/b/' -> 'a/'; 'a' -> EMPTY; 'a/' -> EMPTY
|
|
912
|
+
//
|
|
913
|
+
// A path holding an empty segment has to be taken apart, because its
|
|
914
|
+
// ancestors are not prefixes of it: the parent of 'a//b' is 'a/', not
|
|
915
|
+
// 'a//', and the parent of '/a/' is nothing at all. Both shapes reach here
|
|
916
|
+
// -- 'a//b' is accepted outright, and `checkIgnore` does not put the path
|
|
917
|
+
// it is given through the relative-path check.
|
|
918
|
+
//
|
|
919
|
+
// Every other path is a prefix of itself, and cutting one costs a fraction of
|
|
920
|
+
// splitting it into an array and joining that back at every level: 73ns
|
|
921
|
+
// against 207ns.
|
|
922
|
+
const parentOf = path => {
|
|
923
|
+
if (
|
|
924
|
+
path.charCodeAt(0) === SLASH_CODE
|
|
925
|
+
|| path.indexOf(DOUBLE_SLASH) >= 0
|
|
926
|
+
) {
|
|
927
|
+
const slices = path.split(SLASH).filter(Boolean)
|
|
928
|
+
|
|
929
|
+
slices.pop()
|
|
930
|
+
|
|
931
|
+
return slices.length
|
|
932
|
+
? slices.join(SLASH) + SLASH
|
|
933
|
+
: EMPTY
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const end = path.length - 1
|
|
937
|
+
|
|
938
|
+
// Look back from before a trailing separator, since that one belongs to the
|
|
939
|
+
// path itself.
|
|
940
|
+
//
|
|
941
|
+
// A negative place to start would be a trap here: `lastIndexOf` clamps one
|
|
942
|
+
// to 0 and searches there rather than reporting no match, so '/' would
|
|
943
|
+
// come back as its own parent and the walk would never end. It cannot
|
|
944
|
+
// happen -- that is the only path short enough to produce one, and the
|
|
945
|
+
// test above has already sent it the other way.
|
|
946
|
+
const cut = path.lastIndexOf(
|
|
947
|
+
SLASH,
|
|
948
|
+
path.charCodeAt(end) === SLASH_CODE
|
|
949
|
+
? end - 1
|
|
950
|
+
: end
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
return cut < 0
|
|
954
|
+
? EMPTY
|
|
955
|
+
: path.slice(0, cut + 1)
|
|
956
|
+
}
|
|
361
957
|
|
|
362
958
|
const isString = subject => typeof subject === 'string'
|
|
363
959
|
|
|
@@ -392,6 +988,15 @@ class IgnoreRule {
|
|
|
392
988
|
define(this, 'regexPrefix', prefix)
|
|
393
989
|
}
|
|
394
990
|
|
|
991
|
+
// Worked out on first use and kept behind an own property, the way `regex`
|
|
992
|
+
// caches itself in `_regex`. Deciding it in the constructor instead would
|
|
993
|
+
// add a fourth `defineProperty` to every rule ever built, which cost 4% of
|
|
994
|
+
// every compile -- including the compiles of rules that are never matched
|
|
995
|
+
// against anything.
|
|
996
|
+
get _basenameOnly () {
|
|
997
|
+
return define(this, '_basenameOnly', matchesBasename(this.body))
|
|
998
|
+
}
|
|
999
|
+
|
|
395
1000
|
get regex () {
|
|
396
1001
|
const key = UNDERSCORE + MODE_IGNORE
|
|
397
1002
|
|
|
@@ -413,12 +1018,12 @@ class IgnoreRule {
|
|
|
413
1018
|
}
|
|
414
1019
|
|
|
415
1020
|
_make (mode, key) {
|
|
416
|
-
const str = this.regexPrefix.replace(
|
|
1021
|
+
const str = pinWildcards(this.regexPrefix.replace(
|
|
417
1022
|
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
418
1023
|
|
|
419
1024
|
// It does not need to bind pattern
|
|
420
1025
|
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
421
|
-
)
|
|
1026
|
+
))
|
|
422
1027
|
|
|
423
1028
|
const regex = this.ignoreCase
|
|
424
1029
|
? new RegExp(str, 'i')
|
|
@@ -465,12 +1070,28 @@ class RuleManager {
|
|
|
465
1070
|
constructor (ignoreCase) {
|
|
466
1071
|
this._ignoreCase = ignoreCase
|
|
467
1072
|
this._rules = []
|
|
1073
|
+
|
|
1074
|
+
// How many of the rules git would tag `EXC_FLAG_NODIR`.
|
|
1075
|
+
//
|
|
1076
|
+
// The scan uses it to decide, once for the whole set, whether handing
|
|
1077
|
+
// those rules the basename is worth what it costs the others. The
|
|
1078
|
+
// shortcut saves a full-path scan on every rule it applies to and costs
|
|
1079
|
+
// a check on every rule it does not, so a set where almost nothing is
|
|
1080
|
+
// basename-only comes out behind -- a 955 pattern set with 44 of them
|
|
1081
|
+
// measured 19% slower with the shortcut always on.
|
|
1082
|
+
//
|
|
1083
|
+
// Deciding this by measurement rather than by meaning is safe: a
|
|
1084
|
+
// basename-only pattern gives the very same answer against the whole
|
|
1085
|
+
// path, it just takes longer to say so. The choice can only change how
|
|
1086
|
+
// fast the scan runs, never what it returns.
|
|
1087
|
+
this._basenameCount = 0
|
|
468
1088
|
}
|
|
469
1089
|
|
|
470
1090
|
_add (pattern) {
|
|
471
1091
|
// #32
|
|
472
1092
|
if (pattern && pattern[KEY_IGNORE]) {
|
|
473
1093
|
this._rules = this._rules.concat(pattern._rules._rules)
|
|
1094
|
+
this._basenameCount += pattern._rules._basenameCount
|
|
474
1095
|
this._added = true
|
|
475
1096
|
return
|
|
476
1097
|
}
|
|
@@ -485,6 +1106,13 @@ class RuleManager {
|
|
|
485
1106
|
const rule = createRule(pattern, this._ignoreCase)
|
|
486
1107
|
this._added = true
|
|
487
1108
|
this._rules.push(rule)
|
|
1109
|
+
|
|
1110
|
+
// Deliberately not `rule._basenameOnly`: reading that would materialise
|
|
1111
|
+
// the rule's own copy, and the whole point of leaving it lazy is that
|
|
1112
|
+
// a rule which is compiled and never matched never pays for it.
|
|
1113
|
+
if (matchesBasename(rule.body)) {
|
|
1114
|
+
this._basenameCount ++
|
|
1115
|
+
}
|
|
488
1116
|
}
|
|
489
1117
|
}
|
|
490
1118
|
|
|
@@ -514,7 +1142,26 @@ class RuleManager {
|
|
|
514
1142
|
let unignored = false
|
|
515
1143
|
let matchedRule
|
|
516
1144
|
|
|
517
|
-
|
|
1145
|
+
// Most of a .gitignore is patterns with no slash in them, and running
|
|
1146
|
+
// those against the whole path makes the regular expression engine walk
|
|
1147
|
+
// every directory name on the way to the only segment that could match.
|
|
1148
|
+
// Handing them the basename instead is what git does, and it is where
|
|
1149
|
+
// the time in a directory walk goes: the rule scan was two thirds of it.
|
|
1150
|
+
const rules = this._rules
|
|
1151
|
+
const {length} = rules
|
|
1152
|
+
|
|
1153
|
+
const shortcut = this._basenameCount * 2 >= length
|
|
1154
|
+
|
|
1155
|
+
const basename = shortcut
|
|
1156
|
+
? basenameOf(path)
|
|
1157
|
+
: path
|
|
1158
|
+
|
|
1159
|
+
// A plain loop rather than `forEach`, so that `path`, `basename` and
|
|
1160
|
+
// `shortcut` are locals. As a callback they became closure variables,
|
|
1161
|
+
// and reaching for one of those once per rule cost 10% of a scan over a
|
|
1162
|
+
// large rule set -- more than the shortcut they were there to serve.
|
|
1163
|
+
for (let index = 0; index < length; index ++) {
|
|
1164
|
+
const rule = rules[index]
|
|
518
1165
|
const {negative} = rule
|
|
519
1166
|
|
|
520
1167
|
// | ignored : unignored
|
|
@@ -528,26 +1175,22 @@ class RuleManager {
|
|
|
528
1175
|
// - TEST: always test
|
|
529
1176
|
// - TESTIF: only test if checkUnignored
|
|
530
1177
|
// - X: that never happen
|
|
531
|
-
|
|
532
|
-
unignored === negative && ignored !== unignored
|
|
1178
|
+
const skip = unignored === negative && ignored !== unignored
|
|
533
1179
|
|| negative && !ignored && !unignored && !checkUnignored
|
|
534
|
-
) {
|
|
535
|
-
return
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const matched = rule[mode].test(path)
|
|
539
1180
|
|
|
540
|
-
if (!
|
|
541
|
-
|
|
1181
|
+
if (!skip && rule[mode].test(
|
|
1182
|
+
shortcut && rule._basenameOnly
|
|
1183
|
+
? basename
|
|
1184
|
+
: path
|
|
1185
|
+
)) {
|
|
1186
|
+
ignored = !negative
|
|
1187
|
+
unignored = negative
|
|
1188
|
+
|
|
1189
|
+
matchedRule = negative
|
|
1190
|
+
? UNDEFINED
|
|
1191
|
+
: rule
|
|
542
1192
|
}
|
|
543
|
-
|
|
544
|
-
ignored = !negative
|
|
545
|
-
unignored = negative
|
|
546
|
-
|
|
547
|
-
matchedRule = negative
|
|
548
|
-
? UNDEFINED
|
|
549
|
-
: rule
|
|
550
|
-
})
|
|
1193
|
+
}
|
|
551
1194
|
|
|
552
1195
|
const ret = {
|
|
553
1196
|
ignored,
|
|
@@ -591,7 +1234,43 @@ const checkPath = (path, originalPath, doThrow) => {
|
|
|
591
1234
|
return true
|
|
592
1235
|
}
|
|
593
1236
|
|
|
594
|
-
|
|
1237
|
+
// > pathname should be a `path.relative()`d one
|
|
1238
|
+
//
|
|
1239
|
+
// The same thing `REGEX_TEST_INVALID_PATH` says, spelled out: a path is not
|
|
1240
|
+
// relative if it begins with a separator, or with `./` or `../`, or is
|
|
1241
|
+
// nothing but `.` or `..`. Every match is decided by the first three
|
|
1242
|
+
// characters, and this runs on every path handed to the library -- where it
|
|
1243
|
+
// was 55% of a cached lookup, more than the cache lookup itself.
|
|
1244
|
+
const isNotRelative = path => {
|
|
1245
|
+
const first = path.charCodeAt(0)
|
|
1246
|
+
|
|
1247
|
+
if (first === SLASH_CODE) {
|
|
1248
|
+
return true
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (first !== DOT_CODE) {
|
|
1252
|
+
return false
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// '.'
|
|
1256
|
+
if (path.length === 1) {
|
|
1257
|
+
return true
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const second = path.charCodeAt(1)
|
|
1261
|
+
|
|
1262
|
+
// './'
|
|
1263
|
+
if (second === SLASH_CODE) {
|
|
1264
|
+
return true
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (second !== DOT_CODE) {
|
|
1268
|
+
return false
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// '..' or '../'
|
|
1272
|
+
return path.length === 2 || path.charCodeAt(2) === SLASH_CODE
|
|
1273
|
+
}
|
|
595
1274
|
|
|
596
1275
|
checkPath.isNotRelative = isNotRelative
|
|
597
1276
|
|
|
@@ -638,7 +1317,7 @@ class Ignore {
|
|
|
638
1317
|
}
|
|
639
1318
|
|
|
640
1319
|
// @returns {TestResult}
|
|
641
|
-
_test (originalPath, cache, checkUnignored
|
|
1320
|
+
_test (originalPath, cache, checkUnignored) {
|
|
642
1321
|
const path = originalPath
|
|
643
1322
|
// Supports nullable path
|
|
644
1323
|
&& checkPath.convert(originalPath)
|
|
@@ -651,26 +1330,20 @@ class Ignore {
|
|
|
651
1330
|
: RETURN_FALSE
|
|
652
1331
|
)
|
|
653
1332
|
|
|
654
|
-
return this._t(path, cache, checkUnignored
|
|
1333
|
+
return this._t(path, cache, checkUnignored)
|
|
655
1334
|
}
|
|
656
1335
|
|
|
657
1336
|
checkIgnore (path) {
|
|
658
1337
|
// If the path doest not end with a slash, `.ignores()` is much equivalent
|
|
659
1338
|
// to `git check-ignore`
|
|
660
|
-
if (
|
|
1339
|
+
if (path.charCodeAt(path.length - 1) !== SLASH_CODE) {
|
|
661
1340
|
return this.test(path)
|
|
662
1341
|
}
|
|
663
1342
|
|
|
664
|
-
const
|
|
665
|
-
slices.pop()
|
|
1343
|
+
const parentPath = parentOf(path)
|
|
666
1344
|
|
|
667
|
-
if (
|
|
668
|
-
const parent = this._t(
|
|
669
|
-
slices.join(SLASH) + SLASH,
|
|
670
|
-
this._testCache,
|
|
671
|
-
true,
|
|
672
|
-
slices
|
|
673
|
-
)
|
|
1345
|
+
if (parentPath) {
|
|
1346
|
+
const parent = this._t(parentPath, this._testCache, true)
|
|
674
1347
|
|
|
675
1348
|
if (parent.ignored) {
|
|
676
1349
|
return parent
|
|
@@ -688,37 +1361,20 @@ class Ignore {
|
|
|
688
1361
|
cache,
|
|
689
1362
|
|
|
690
1363
|
// Whether should check if the path is unignored
|
|
691
|
-
checkUnignored
|
|
692
|
-
|
|
693
|
-
// The path slices
|
|
694
|
-
slices
|
|
1364
|
+
checkUnignored
|
|
695
1365
|
) {
|
|
696
1366
|
if (path in cache) {
|
|
697
1367
|
return cache[path]
|
|
698
1368
|
}
|
|
699
1369
|
|
|
700
|
-
|
|
701
|
-
// path/to/a.js
|
|
702
|
-
// ['path', 'to', 'a.js']
|
|
703
|
-
slices = path.split(SLASH).filter(Boolean)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
slices.pop()
|
|
707
|
-
|
|
708
|
-
// If the path has no parent directory, just test it
|
|
709
|
-
if (!slices.length) {
|
|
710
|
-
return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE)
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const parent = this._t(
|
|
714
|
-
slices.join(SLASH) + SLASH,
|
|
715
|
-
cache,
|
|
716
|
-
checkUnignored,
|
|
717
|
-
slices
|
|
718
|
-
)
|
|
1370
|
+
const parentPath = parentOf(path)
|
|
719
1371
|
|
|
720
1372
|
// If the path contains a parent directory, check the parent first
|
|
721
|
-
|
|
1373
|
+
const parent = parentPath
|
|
1374
|
+
? this._t(parentPath, cache, checkUnignored)
|
|
1375
|
+
: UNDEFINED
|
|
1376
|
+
|
|
1377
|
+
return cache[path] = parent && parent.ignored
|
|
722
1378
|
// > It is not possible to re-include a file if a parent directory of
|
|
723
1379
|
// > that file is excluded.
|
|
724
1380
|
? parent
|