in lib/src/ignore.dart [369:536]
_IgnoreParseResult _parseIgnorePattern(String pattern, bool ignoreCase) {
// Check if patterns is a comment
if (pattern.startsWith('#')) {
return _IgnoreParseResult.empty(pattern);
}
var first = 0;
var end = pattern.length;
// Detect negative patterns
final negative = pattern.startsWith('!');
if (negative) {
first++;
}
// Remove trailing whitespace unless escaped
while (end != 0 &&
pattern[end - 1] == ' ' &&
(end == 1 || pattern[end - 2] != '\\')) {
end--;
}
// Empty patterns match nothing.
if (first == end) return _IgnoreParseResult.empty(pattern);
var current = first;
String? peekChar() => current >= end ? null : pattern[current];
var expr = '';
// Parses the inside of a [] range. Returns the value as a RegExp character
// range, or null if the pattern was broken.
String? parseCharacterRange() {
var characterRange = '';
var first = true;
for (;;) {
final nextChar = peekChar();
if (nextChar == null) {
return null;
}
current++;
if (nextChar == '\\') {
final escaped = peekChar();
if (escaped == null) {
return null;
}
current++;
characterRange += escaped == '-' ? r'\-' : RegExp.escape(escaped);
} else if (nextChar == '!' && first) {
characterRange += '^';
} else if (nextChar == ']' && first) {
characterRange += RegExp.escape(nextChar);
} else if (nextChar == ']') {
assert(!first);
return characterRange;
} else {
characterRange += nextChar;
}
first = false;
}
}
var relativeToPath = false;
var matchesDirectoriesOnly = false;
// slashes have different significance depending on where they are in
// the String. Handle that here.
void handleSlash() {
if (current == end) {
// A slash at the end makes us only match directories.
matchesDirectoriesOnly = true;
} else {
// A slash anywhere else makes the pattern relative anchored at the
// current path.
relativeToPath = true;
}
}
for (;;) {
final nextChar = peekChar();
if (nextChar == null) break;
current++;
if (nextChar == '*') {
if (peekChar() == '*') {
// Handle '**'
current++;
if (peekChar() == '/') {
current++;
if (current == end) {
expr += '.*';
} else {
// Match nothing or a path followed by '/'
expr += '(?:(?:)|(?:.*/))';
}
// Handle the side effects of seeing a slash.
handleSlash();
} else {
expr += '.*';
}
} else if (peekChar() == '/' || peekChar() == null) {
// /a/* should not match '/a/'
expr += '[^/]+';
} else {
// Handle a single '*'
expr += '[^/]*';
}
} else if (nextChar == '?') {
// Handle '?'
expr += '[^/]';
} else if (nextChar == '[') {
// Character ranges
final characterRange = parseCharacterRange();
if (characterRange == null) {
return _IgnoreParseResult.invalid(
pattern,
FormatException(
'Pattern "$pattern" had an invalid `[a-b]` style character range',
pattern,
current),
);
}
expr += '[$characterRange]';
} else if (nextChar == '\\') {
// Escapes
final escaped = peekChar();
if (escaped == null) {
return _IgnoreParseResult.invalid(
pattern,
FormatException(
'Pattern "$pattern" end of pattern inside character escape.',
pattern,
current),
);
}
expr += RegExp.escape(escaped);
current++;
} else {
if (nextChar == '/') {
if (current - 1 != first && current != end) {
// If slash appears in the beginning we don't want it manifest in the
// regexp.
expr += '/';
}
handleSlash();
} else {
expr += RegExp.escape(nextChar);
}
}
}
if (relativeToPath) {
expr = '^$expr';
} else {
expr = '(?:^|/)$expr';
}
if (matchesDirectoriesOnly) {
expr = '$expr/\$';
} else {
expr = '$expr/?\$';
}
try {
return _IgnoreParseResult(
pattern,
_IgnoreRule(
RegExp(expr, caseSensitive: !ignoreCase), negative, pattern));
} on FormatException catch (e) {
throw AssertionError(
'Created broken expression "$expr" from ignore pattern "$pattern" -> $e');
}
}