findImportBlockIndices static method

  1. @visibleForTesting
({int? importEnd, int? importStart}) findImportBlockIndices(
  1. List<String> lines
)

Finds the indices of the import block (library, import, export directives) and the index of the first line after it. Handles multi-line show/hide clauses correctly.

Implementation

@visibleForTesting
static ({int? importStart, int? importEnd}) findImportBlockIndices(
  List<String> lines,
) {
  int? importStart;
  int? importEnd;
  int? commentStart;

  final skippable = [
    'library',
    'as',
    'hide',
    'show',
    'export',
    RegExp(r'^\w+[,;]$'),
  ];

  var inShowOrHide = false;

  bool endsWithSemicolon(String line) {
    final withoutComment = line.contains('//')
        ? line.substring(0, line.indexOf('//')).trimRight()
        : line;
    return withoutComment.endsWith(';');
  }

  bool skippableMatches(String trimmed) => skippable.any(
    (s) => s is RegExp ? s.hasMatch(trimmed) : trimmed.startsWith(s),
  );

  for (final (index, line) in lines.indexed) {
    final trimmed = line.trim();
    if (trimmed.isEmpty) continue;
    if (trimmed.startsWith('//')) {
      if (trimmed != '// dart format on') {
        commentStart ??= index;
      }

      continue;
    }

    // When inside a multi-line show/hide clause, continue until we find
    // the terminating semicolon (ignoring any trailing comment).
    if (inShowOrHide) {
      if (endsWithSemicolon(trimmed)) {
        inShowOrHide = false;
      }
      continue;
    }

    // A line starting with show/hide may span multiple lines until `;`.
    if (trimmed.startsWith('show') || trimmed.startsWith('hide')) {
      inShowOrHide = !endsWithSemicolon(trimmed);
      continue;
    }

    if (skippableMatches(trimmed)) {
      continue;
    }

    if (trimmed.startsWith('import ')) {
      importStart ??= index;
      commentStart = null;
      continue;
    }

    importEnd = commentStart ?? index;
    break;
  }

  return (importStart: importStart, importEnd: importEnd);
}