replaceSourceIndent method

String replaceSourceIndent(
  1. String source,
  2. String oldIndent,
  3. String newIndent, {
  4. bool includeLeading = false,
  5. bool ensureTrailingNewline = false,
})

Returns the source with indentation changed from oldIndent to newIndent, keeping indentation of lines relative to each other.

Indentation on the first line will only be updated if includeLeading is true.

If ensureTrailingNewline is true, a newline will be added to the end of the returned code if it does not already have one.

Usually includeLeading and ensureTrailingNewline are set together, when indenting a set of statements to go inside a block (as opposed to just wrapping a nested expression that might span multiple lines).

Implementation

String replaceSourceIndent(
  String source,
  String oldIndent,
  String newIndent, {
  bool includeLeading = false,
  bool ensureTrailingNewline = false,
}) {
  // Prepare token ranges.
  var lineRanges = <SourceRange>[];
  {
    var tokens = TokenUtils.getTokens(source, _unit.featureSet);
    for (var token in tokens) {
      if (token.type == TokenType.STRING) {
        lineRanges.add(range.token(token));
      }
    }
  }
  // Re-indent lines.
  var sb = StringBuffer();
  var eol = endOfLine;
  var lines = source.split(eol);
  var lineOffset = 0;
  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    // Exit early if this is the last line and it's already empty, to avoid
    // inserting any whitespace or appending an additional newline if
    // `ensureTrailingNewline`.
    if (i == lines.length - 1 && isEmpty(line)) {
      break;
    }
    // Don't replace whitespace on first line unless `includeLeading`.
    var doReplaceWhitespace = i != 0 || includeLeading;
    // Don't add eol to last line unless `ensureTrailingNewline`.
    var doAppendEol = i != lines.length - 1 || ensureTrailingNewline;

    // Check if "offset" is in one of the ranges.
    var inString = false;
    for (var lineRange in lineRanges) {
      if (lineOffset > lineRange.offset && lineOffset < lineRange.end) {
        inString = true;
        break;
      }
      // We can skip the rest if this line ends before the end of this range
      // because subsequent ranges are after it.
      if (lineOffset < lineRange.end) {
        break;
      }
    }
    lineOffset += line.length + eol.length;
    // Update line indent.
    if (!inString && doReplaceWhitespace) {
      line = '$newIndent${removeStart(line, oldIndent)}';
    }
    // Append line.
    sb.write(line);
    if (doAppendEol) {
      sb.write(eol);
    }
  }
  return sb.toString();
}