_IgnoreParseResult _parseIgnorePattern()

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');
  }
}