directiveUris function

Iterable<String> directiveUris(
  1. String source
)

Every import / export URI in source, directives only.

Comment-aware, and it stops at the first real declaration — a dart:io named in a doc comment or a string literal is not a dependency, and a rule that counted one would be unfixable without rewording prose.

It deliberately stops at the URI itself: show / hide clauses and the trailing as alias are irrelevant to every question asked here, and a stricter pattern that silently matched nothing would make each caller pass for the wrong reason.

Implementation

Iterable<String> directiveUris(String source) sync* {
  var inBlockComment = false;
  for (final rawLine in const LineSplitter().convert(source)) {
    var line = rawLine;
    if (inBlockComment) {
      final end = line.indexOf('*/');
      if (end < 0) continue;
      inBlockComment = false;
      line = line.substring(end + 2);
    }
    // A block comment that closes on its own line is removed in place, so a
    // `/* … */` before or between directives does not end the scan; one that
    // stays open runs to a later line.
    var blockStart = line.indexOf('/*');
    while (blockStart >= 0) {
      final end = line.indexOf('*/', blockStart + 2);
      if (end < 0) {
        inBlockComment = true;
        line = line.substring(0, blockStart);
        break;
      }
      line = line.substring(0, blockStart) + line.substring(end + 2);
      blockStart = line.indexOf('/*');
    }
    final trimmed = line.trim();
    if (trimmed.isEmpty || trimmed.startsWith('//')) continue;
    final match = directiveLinePattern.firstMatch(line);
    if (match != null) {
      yield match.group(1)!;
      continue;
    }
    if (trimmed.startsWith('library') ||
        trimmed.startsWith('part') ||
        trimmed.startsWith('@')) {
      continue;
    }
    // The continuation lines of a directive. A conditional import puts its
    // alternative on a line of its own — `if (dart.library.io) 'x.dart'` — and
    // that alternative is linked on the platform it names, so it is a
    // dependency like any other. Treating the line as "the first declaration"
    // ended the scan there and dropped every directive after it.
    final conditional = _conditionalUri.firstMatch(trimmed);
    if (conditional != null) {
      yield conditional.group(1)!;
      continue;
    }
    if (_directiveContinuation.hasMatch(trimmed)) continue;
    return; // first real declaration — directives are done
  }
}