Simplify some CorrectionUtils code

The big one is `getIndent`, which I think can be simpler, and also used a handwritten implementation of `String.operator *`. It turns out:

* > 90% of calls to `getIndent` were `getIndent(1)`, which can be
  replaced with a constant `'  '`.
* Then 4 calls to `getIndent` were `getIndent(2)`, which can be replaced
  with a constant `'  ' + '  '`.
* Then one last call, in CorrectionUtils itself, used a variable number
  of indents which can be replaced with `'  ' * n`. This is in the
  `indentRight` function.

Also:

* Privatize `getInsertionLocationTop`, `isClassWithEmptyBody`, `isJustWhitespaceOrComment`, `skipEmptyLinesLeft`
* Rewrite CorrectionUtils.findSimplePrintInvocation as an extension
  method on AstNode; does not need the `_buffer`.

Change-Id: I15c19b8c0cc354adcd4ffea5803b3a182cf28336
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358400
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Sam Rawlins 2024-03-19 17:05:49 +00:00 committed by Commit Queue
parent 8f995c27f2
commit a8a42cffd2
37 changed files with 228 additions and 240 deletions

View file

@ -309,7 +309,7 @@ class PostfixCompletionProcessor {
builder.write(eol);
var indent = utils.getNodePrefix(expr);
builder.write(indent);
builder.write(utils.getIndent(1));
builder.write(utils.oneIndent);
builder.selectHere();
builder.write(eol);
builder.write(indent);
@ -351,7 +351,7 @@ class PostfixCompletionProcessor {
builder.write('try {');
builder.write(eol);
builder.write(utils.replaceSourceIndent(
src, indent, '$indent${utils.getIndent(1)}',
src, indent, '$indent${utils.oneIndent}',
includeLeading: true, ensureTrailingNewline: true));
builder.selectHere();
builder.write(indent);
@ -363,7 +363,7 @@ class PostfixCompletionProcessor {
builder.write(' catch (e, s) {');
builder.write(eol);
builder.write(indent);
builder.write(utils.getIndent(1));
builder.write(utils.oneIndent);
builder.write('print(s);');
builder.write(eol);
builder.write(indent);

View file

@ -231,7 +231,7 @@ class StatementCompletionProcessor {
sb.append(eol);
var indent = utils.getLinePrefix(selectionOffset);
sb.append(indent);
sb.append(utils.getIndent(1));
sb.append(utils.oneIndent);
if (needsExitMark && sb.exitOffset == null) {
sb.setExitOffset();
}

View file

@ -82,7 +82,7 @@ class AddCallSuper extends ResolvedCorrectionProducer {
var expression = body.expression;
var semicolon = body.semicolon;
var prefix = utils.getLinePrefix(expression.offset);
var prefixWithLine = eol + prefix + utils.getIndent(1);
var prefixWithLine = eol + prefix + utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleReplacement(

View file

@ -131,8 +131,8 @@ class AddDiagnosticPropertyReference extends ResolvedCorrectionProducer {
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(utils.getLineNext(insertOffset), (builder) {
final declPrefix =
utils.getLinePrefix(classDeclaration.offset) + utils.getIndent(1);
final bodyPrefix = declPrefix + utils.getIndent(1);
utils.getLinePrefix(classDeclaration.offset) + utils.oneIndent;
final bodyPrefix = declPrefix + utils.oneIndent;
builder.writeln('$declPrefix@override');
builder.writeln(
@ -155,7 +155,7 @@ class AddDiagnosticPropertyReference extends ResolvedCorrectionProducer {
String prefix;
if (functionBody.block.statements.isEmpty) {
offset = functionBody.block.leftBracket.offset;
prefix = utils.getLinePrefix(offset) + utils.getIndent(1);
prefix = utils.getLinePrefix(offset) + utils.oneIndent;
} else {
offset = functionBody.block.statements.last.endToken.offset;
prefix = utils.getLinePrefix(offset);

View file

@ -81,7 +81,7 @@ class AddMissingEnumCaseClauses extends ResolvedCorrectionProducer {
}
var statementIndent = utils.getLinePrefix(statement.offset);
var singleIndent = utils.getIndent(1);
var singleIndent = utils.oneIndent;
var location = utils.newCaseClauseAtEndLocation(
switchKeyword: statement.switchKeyword,
leftBracket: statement.leftBracket,

View file

@ -34,7 +34,7 @@ class AddMissingEnumLikeCaseClauses extends ResolvedCorrectionProducer {
missingNames.sort();
var statementIndent = utils.getLinePrefix(node.offset);
var singleIndent = utils.getIndent(1);
var singleIndent = utils.oneIndent;
var location = utils.newCaseClauseAtEndLocation(
switchKeyword: node.switchKeyword,
leftBracket: node.leftBracket,

View file

@ -58,7 +58,7 @@ class AddMissingSwitchCases extends ResolvedCorrectionProducer {
required List<MissingPatternPart> patternParts,
}) async {
final lineIndent = utils.getLinePrefix(node.offset);
final singleIndent = utils.getIndent(1);
final singleIndent = utils.oneIndent;
final location = utils.newCaseClauseAtEndLocation(
switchKeyword: node.switchKeyword,
leftBracket: node.leftBracket,
@ -86,7 +86,7 @@ class AddMissingSwitchCases extends ResolvedCorrectionProducer {
required List<MissingPatternPart> patternParts,
}) async {
final lineIndent = utils.getLinePrefix(node.offset);
final singleIndent = utils.getIndent(1);
final singleIndent = utils.oneIndent;
final location = utils.newCaseClauseAtEndLocation(
switchKeyword: node.switchKeyword,
leftBracket: node.leftBracket,

View file

@ -40,7 +40,7 @@ class AddOverride extends ResolvedCorrectionProducer {
}
var exitPosition = Position(file, token.offset - 1);
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
builder.addSimpleReplacement(
range.startLength(token, 0), '@override$eol$indent');

View file

@ -27,7 +27,7 @@ class AddRedeclare extends ResolvedCorrectionProducer {
if (member == null) return;
var token = member.firstTokenAfterCommentAndMetadata;
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(token.offset, (builder) {
builder.write('@');

View file

@ -50,8 +50,7 @@ class AddReturnNull extends ResolvedCorrectionProducer {
if (block.statements.isEmpty) {
position = block.offset + 1;
var prefix = utils.getLinePrefix(block.offset);
returnStatement =
'$eol$prefix${utils.getIndent(1)}return null;$eol$prefix';
returnStatement = '$eol$prefix${utils.oneIndent}return null;$eol$prefix';
} else {
var lastStatement = block.statements.last;
position = lastStatement.offset + lastStatement.length;

View file

@ -187,7 +187,7 @@ class _EnumDescription {
// Compute the declarations of the enum constants and delete the fields
// being converted.
var members = classDeclaration.members;
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var eol = utils.endOfLine;
var constantsBuffer = StringBuffer();
var fieldsToConvert = fields.fieldsToConvert;

View file

@ -32,7 +32,7 @@ class ConvertFlutterChild extends ResolvedCorrectionProducer {
eol,
utils.getNodeText,
utils.getLinePrefix,
utils.getIndent,
utils.oneIndent,
utils.getText,
utils.replaceSourceIndent,
range.node);

View file

@ -72,7 +72,7 @@ class ConvertIntoBlockBody extends ResolvedCorrectionProducer {
// prepare prefix
var prefix = utils.getNodePrefix(body.parent!);
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var sourceRange = range.endEnd(body.beginToken.previous!, body);
await builder.addDartFileEdit(file, (builder) {

View file

@ -78,7 +78,7 @@ class ConvertIntoForIndex extends ResolvedCorrectionProducer {
}
// prepare environment
var prefix = utils.getNodePrefix(forStatement);
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var firstBlockLine = utils.getLineContentEnd(body.leftBracket.end);
// add change
await builder.addDartFileEdit(file, (builder) {

View file

@ -132,7 +132,7 @@ class ConvertToIfCaseStatementChain extends ResolvedCorrectionProducer {
final range = utils.getLinesRangeStatements(statements);
final firstIndent = utils.getLinePrefix(first.offset);
final singleIndent = utils.getIndent(1);
final singleIndent = utils.oneIndent;
final code = utils.replaceSourceRangeIndent(
range,

View file

@ -41,7 +41,7 @@ class ConvertIfStatementToSwitchStatement extends ResolvedCorrectionProducer {
}
final indent = utils.getLinePrefix(ifStatement.offset);
final singleIndent = utils.getIndent(1);
final singleIndent = utils.oneIndent;
final caseIndent = '$indent$singleIndent';
await builder.addDartFileEdit(file, (builder) {
@ -185,7 +185,7 @@ class ConvertIfStatementToSwitchStatement extends ResolvedCorrectionProducer {
// switch
// case
// statement
final singleIndent = utils.getIndent(1);
final singleIndent = utils.oneIndent;
final newIndent = '$ifStatementIndent$singleIndent';
final code = utils.replaceSourceRangeIndent(

View file

@ -52,7 +52,7 @@ class CreateMissingOverrides extends ResolvedCorrectionProducer {
return;
}
var prefix = utils.getIndent(1);
var prefix = utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
final syntheticLeftBracket = targetClass.leftBracket.isSynthetic;
if (syntheticLeftBracket) {

View file

@ -19,7 +19,7 @@ class CreateNoSuchMethod extends ResolvedCorrectionProducer {
return;
}
// prepare environment
var prefix = utils.getIndent(1);
var prefix = utils.oneIndent;
var insertOffset = targetClass.end - 1;
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(insertOffset, (builder) {

View file

@ -36,7 +36,7 @@ class FlutterConvertToChildren extends ResolvedCorrectionProducer {
await builder.addDartFileEdit(file, (builder) {
_convertFlutterChildToChildren(namedExp, eol, utils.getNodeText,
utils.getLinePrefix, utils.getIndent, utils.getText, builder);
utils.getLinePrefix, utils.getText, builder);
});
}
@ -45,7 +45,6 @@ class FlutterConvertToChildren extends ResolvedCorrectionProducer {
String eol,
String Function(Expression) getNodeText,
String Function(int) getLinePrefix,
String Function(int) getIndent,
String Function(int, int) getText,
FileEditBuilder builder) {
var childArg = namedExp.expression;
@ -62,7 +61,7 @@ class FlutterConvertToChildren extends ResolvedCorrectionProducer {
newlineLoc -= 1;
}
var indentOld = getLinePrefix(childArg.offset + eol.length + newlineLoc);
var indentNew = '$indentOld${getIndent(1)}';
var indentNew = '$indentOld${utils.oneIndent}';
// The separator includes 'child:' but that has no newlines.
var separator =
getText(namedExp.offset, childArg.offset - namedExp.offset);

View file

@ -223,8 +223,8 @@ abstract class _WrapMultipleWidgets extends ResolvedCorrectionProducer {
builder.write('(');
var indentOld = utils.getLinePrefix(firstWidget.offset);
var indentNew1 = indentOld + utils.getIndent(1);
var indentNew2 = indentOld + utils.getIndent(2);
var indentNew1 = indentOld + utils.oneIndent;
var indentNew2 = indentOld + utils.twoIndents;
builder.write(eol);
builder.write(indentNew1);
@ -299,7 +299,7 @@ abstract class _WrapSingleWidget extends ResolvedCorrectionProducer {
var leadingLines = _leadingLines;
if (widgetSrc.contains(eol) || leadingLines.isNotEmpty) {
var indentOld = utils.getLinePrefix(widgetExpr.offset);
var indentNew = '$indentOld${utils.getIndent(1)}';
var indentNew = '$indentOld${utils.oneIndent}';
for (var leadingLine in leadingLines) {
builder.write(eol);

View file

@ -40,8 +40,8 @@ class FlutterWrapBuilder extends ResolvedCorrectionProducer {
builder.writeln('(');
var indentOld = utils.getLinePrefix(widgetExpr.offset);
var indentNew1 = indentOld + utils.getIndent(1);
var indentNew2 = indentOld + utils.getIndent(2);
var indentNew1 = indentOld + utils.oneIndent;
var indentNew2 = indentOld + utils.twoIndents;
builder.write(indentNew1);
builder.writeln('builder: (context) {');

View file

@ -30,8 +30,8 @@ class FlutterWrapGeneric extends ResolvedCorrectionProducer {
return; // Lists need to be in multi-line format already.
}
var indentOld = utils.getLinePrefix(node.offset + eol.length + newlineIdx);
var indentArg = '$indentOld${utils.getIndent(1)}';
var indentList = '$indentOld${utils.getIndent(2)}';
var indentArg = '$indentOld${utils.oneIndent}';
var indentList = '$indentOld${utils.twoIndents}';
await builder.addDartFileEdit(file, (builder) {
builder.addReplacement(range.node(node), (builder) {

View file

@ -42,8 +42,8 @@ class FlutterWrapStreamBuilder extends ResolvedCorrectionProducer {
builder.writeln('>(');
var indentOld = utils.getLinePrefix(widgetExpr.offset);
var indentNew1 = indentOld + utils.getIndent(1);
var indentNew2 = indentOld + utils.getIndent(2);
var indentNew1 = indentOld + utils.oneIndent;
var indentNew2 = indentOld + utils.twoIndents;
builder.write(indentNew1);
builder.writeln('stream: null,');

View file

@ -4,6 +4,7 @@
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/utilities/extensions/ast.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
@ -25,14 +26,14 @@ class MakeConditionalOnDebugMode extends ResolvedCorrectionProducer {
if (unitResult.session.uriConverter.uriToPath(_foundationUri) == null) {
return;
}
var printInvocation = utils.findSimplePrintInvocation(node);
var printInvocation = node.findSimplePrintInvocation();
if (printInvocation != null) {
var indent = utils.getLinePrefix(printInvocation.offset);
await builder.addDartFileEdit(file, (builder) {
builder.addInsertion(printInvocation.offset, (builder) {
builder.writeln('if (kDebugMode) {');
builder.write(indent);
builder.write(utils.getIndent(1));
builder.write(utils.oneIndent);
});
builder.addInsertion(printInvocation.end, (builder) {
builder.writeln();

View file

@ -4,6 +4,7 @@
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/utilities/extensions/ast.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
@ -25,7 +26,7 @@ class RemovePrint extends ResolvedCorrectionProducer {
@override
Future<void> compute(ChangeBuilder builder) async {
final printInvocation = utils.findSimplePrintInvocation(node);
final printInvocation = node.findSimplePrintInvocation();
if (printInvocation != null) {
await builder.addDartFileEdit(file, (builder) {
var start = utils.getLineContentStart(printInvocation.offset);

View file

@ -49,7 +49,7 @@ class ReplaceConditionalWithIfElse extends ResolvedCorrectionProducer {
var conditional = assignment.rightHandSide;
if (assignment.operator.type == TokenType.EQ &&
conditional is ConditionalExpression) {
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var prefix = utils.getNodePrefix(statement);
await builder.addDartFileEdit(file, (builder) {
@ -75,7 +75,7 @@ class ReplaceConditionalWithIfElse extends ResolvedCorrectionProducer {
) async {
var conditional = statement.expression;
if (conditional is ConditionalExpression) {
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var prefix = utils.getNodePrefix(statement);
await builder.addDartFileEdit(file, (builder) {
@ -100,7 +100,7 @@ class ReplaceConditionalWithIfElse extends ResolvedCorrectionProducer {
for (var variable in statement.variables.variables) {
var conditional = variable.initializer;
if (conditional is ConditionalExpression) {
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var prefix = utils.getNodePrefix(statement);
await builder.addDartFileEdit(file, (builder) {

View file

@ -51,7 +51,7 @@ class SplitAndCondition extends ResolvedCorrectionProducer {
}
// prepare environment
var prefix = utils.getNodePrefix(ifStatement);
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
// prepare "rightCondition"
String rightConditionSource;
{

View file

@ -48,7 +48,7 @@ class SurroundWith extends MultiCorrectionProducer {
var statementsRange = utils.getLinesRangeStatements(selectedStatements);
// prepare environment
var indentOld = utils.getNodePrefix(firstStatement);
var indentNew = '$indentOld${utils.getIndent(1)}';
var indentNew = '$indentOld${utils.oneIndent}';
var indentedCode = utils.replaceSourceRangeIndent(
statementsRange, indentOld, indentNew,
includeLeading: true, ensureTrailingNewline: true);

View file

@ -77,7 +77,7 @@ class UseCurlyBraces extends ParsedCorrectionProducer {
if (body is Block) return;
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var indent = prefix + utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
_replaceRange(
@ -100,7 +100,7 @@ class UseCurlyBraces extends ParsedCorrectionProducer {
if (body is Block) return;
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var indent = prefix + utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
_replace(builder, node.rightParenthesis, body, indent, prefix);
@ -115,7 +115,7 @@ class UseCurlyBraces extends ParsedCorrectionProducer {
}
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var indent = prefix + utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
var thenStatement = node.thenStatement;
@ -182,7 +182,7 @@ class UseCurlyBraces extends ParsedCorrectionProducer {
if (body is Block) return;
var prefix = utils.getLinePrefix(node.offset);
var indent = prefix + utils.getIndent(1);
var indent = prefix + utils.oneIndent;
await builder.addDartFileEdit(file, (builder) {
_replace(builder, node.rightParenthesis, body, indent, prefix);

View file

@ -128,7 +128,7 @@ Future<void> addLibraryImports(AnalysisSession session, SourceChange change,
// If still at the beginning of the file, skip shebang and line comments.
{
var desc = libUtils.getInsertionLocationTop();
var desc = libUtils._getInsertionLocationTop();
var offset = desc.offset;
for (var i = 0; i < uriList.length; i++) {
var importUri = uriList[i];
@ -539,9 +539,15 @@ bool _allListsIdentical(List<List<Object>> lists, int position) {
return true;
}
class CorrectionUtils {
final class CorrectionUtils {
static const String _oneIndent = ' ';
static const String _twoIndents = _oneIndent + _oneIndent;
final CompilationUnit unit;
final LibraryElement? _library;
final String _buffer;
/// The [ClassElement] the generated code is inserted to, so we can decide if
@ -571,6 +577,10 @@ class CorrectionUtils {
}
}
String get oneIndent => _oneIndent;
String get twoIndents => _twoIndents;
/// Returns the [AstNode] that encloses the given offset.
AstNode? findNode(int offset) => NodeLocator(offset).searchWithin(unit);
@ -588,28 +598,6 @@ class CorrectionUtils {
return conflicts;
}
/// Returns the [ExpressionStatement] associated with [node] if [node] points
/// to the identifier for a simple `print`. Returns `null`,
/// otherwise.
ExpressionStatement? findSimplePrintInvocation(AstNode node) {
var parent = node.parent;
var grandparent = parent?.parent;
if (node is SimpleIdentifier) {
var element = node.staticElement;
if (element is FunctionElement &&
element.name == 'print' &&
element.library.isDartCore &&
parent is MethodInvocation &&
grandparent is ExpressionStatement) {
return grandparent;
}
}
return null;
}
/// Returns the indentation with the given level.
String getIndent(int level) => repeat(' ', level);
/// Returns a description of the place in which to insert an `ignore_for_file`
/// comment.
///
@ -666,60 +654,6 @@ class CorrectionUtils {
);
}
/// Returns a description of the place in which to insert a new directive or a
/// top-level declaration at the top of the file.
InsertionLocation getInsertionLocationTop() {
// skip leading line comments
var offset = 0;
var insertEmptyLineBefore = false;
var insertEmptyLineAfter = false;
var source = _buffer;
// skip hash-bang
if (offset < source.length - 2) {
var linePrefix = getText(offset, 2);
if (linePrefix == '#!') {
insertEmptyLineBefore = true;
offset = getLineNext(offset);
// skip empty lines to first line comment
var emptyOffset = offset;
while (emptyOffset < source.length - 2) {
var nextLineOffset = getLineNext(emptyOffset);
var line = source.substring(emptyOffset, nextLineOffset);
if (line.trim().isEmpty) {
emptyOffset = nextLineOffset;
continue;
} else if (line.startsWith('//')) {
offset = emptyOffset;
break;
} else {
break;
}
}
}
}
// skip line comments
while (offset < source.length - 2) {
var linePrefix = getText(offset, 2);
if (linePrefix == '//') {
insertEmptyLineBefore = true;
offset = getLineNext(offset);
} else {
break;
}
}
// determine if empty line is required after
var nextLineOffset = getLineNext(offset);
var insertLine = source.substring(offset, nextLineOffset);
if (insertLine.trim().isNotEmpty) {
insertEmptyLineAfter = true;
}
return InsertionLocation(
prefix: insertEmptyLineBefore ? endOfLine : '',
offset: offset,
suffix: insertEmptyLineAfter ? endOfLine : '',
);
}
/// Skips whitespace characters and single EOL on the right from [index].
///
/// If [index] the end of a statement or method, then in the most cases it is
@ -811,7 +745,7 @@ class CorrectionUtils {
var startOffset = sourceRange.offset;
var startLineOffset = getLineContentStart(startOffset);
if (skipLeadingEmptyLines) {
startLineOffset = skipEmptyLinesLeft(startLineOffset);
startLineOffset = _skipEmptyLinesLeft(startLineOffset);
}
// end
var endOffset = sourceRange.end;
@ -971,7 +905,7 @@ class CorrectionUtils {
/// Lines that don't start with indentation are left as is.
String indentLeft(String text) {
final buffer = StringBuffer();
final indent = getIndent(1);
final indent = oneIndent;
final eol = endOfLine;
final lines = text.split(eol);
for (final line in lines) {
@ -989,10 +923,10 @@ class CorrectionUtils {
return buffer.toString();
}
/// Splits [text] into lines, and adds [level] indents to each line.
/// Adds [level] indents to each line.
String indentRight(String text, {int level = 1}) {
final buffer = StringBuffer();
final indent = getIndent(level);
final indent = _oneIndent * level;
final eol = endOfLine;
final lines = text.split(eol);
for (final line in lines) {
@ -1007,7 +941,7 @@ class CorrectionUtils {
/// Indents given source left or right.
String indentSourceLeftRight(String source, {bool indentLeft = true}) {
var sb = StringBuffer();
var indent = getIndent(1);
var indent = oneIndent;
var eol = endOfLine;
var lines = source.split(eol);
for (var i = 0; i < lines.length; i++) {
@ -1034,24 +968,6 @@ class CorrectionUtils {
String invertCondition(Expression expression) =>
_invertCondition0(expression)._source;
/// Return `true` if the given class, mixin, enum or extension [declaration]
/// has open '{' and close '}' on the same line, e.g. `class X {}`.
bool isClassWithEmptyBody(CompilationUnitMember declaration) {
return getLineThis(_getLeftBracket(declaration)!.offset) ==
getLineThis(_getRightBracket(declaration)!.offset);
}
/// Return <code>true</code> if [range] contains only whitespace or comments.
bool isJustWhitespaceOrComment(SourceRange range) {
var trimmedText = getRangeText(range).trim();
// may be whitespace
if (trimmedText.isEmpty) {
return true;
}
// may be comment
return TokenUtils.getTokens(trimmedText, unit.featureSet).isEmpty;
}
InsertionLocation newCaseClauseAtEndLocation({
required Token switchKeyword,
required Token leftBracket,
@ -1074,14 +990,12 @@ class CorrectionUtils {
InsertionLocation prepareEnumNewConstructorLocation(
EnumDeclaration enumDeclaration,
) {
var indent = getIndent(1);
var targetMember = enumDeclaration.members
.where((e) => e is FieldDeclaration || e is ConstructorDeclaration)
.lastOrNull;
if (targetMember != null) {
return InsertionLocation(
prefix: endOfLine + endOfLine + indent,
prefix: endOfLine + endOfLine + oneIndent,
offset: targetMember.end,
suffix: '',
);
@ -1090,7 +1004,7 @@ class CorrectionUtils {
var semicolon = enumDeclaration.semicolon;
if (semicolon != null) {
return InsertionLocation(
prefix: endOfLine + endOfLine + indent,
prefix: endOfLine + endOfLine + oneIndent,
offset: semicolon.end,
suffix: '',
);
@ -1098,7 +1012,7 @@ class CorrectionUtils {
var lastConstant = enumDeclaration.constants.last;
return InsertionLocation(
prefix: ';$endOfLine$endOfLine$indent',
prefix: ';$endOfLine$endOfLine$oneIndent',
offset: lastConstant.end,
suffix: '',
);
@ -1107,7 +1021,6 @@ class CorrectionUtils {
InsertionLocation? prepareNewClassMemberLocation(
CompilationUnitMember declaration,
bool Function(ClassMember existingMember) shouldSkip) {
var indent = getIndent(1);
// Find the last target member.
ClassMember? targetMember;
var members = _getMembers(declaration);
@ -1124,17 +1037,17 @@ class CorrectionUtils {
// After the last target member.
if (targetMember != null) {
return InsertionLocation(
prefix: endOfLine + endOfLine + indent,
prefix: endOfLine + endOfLine + oneIndent,
offset: targetMember.end,
suffix: '',
);
}
// At the beginning of the class.
var suffix = members.isNotEmpty || isClassWithEmptyBody(declaration)
var suffix = members.isNotEmpty || _isClassWithEmptyBody(declaration)
? endOfLine
: '';
return InsertionLocation(
prefix: endOfLine + indent,
prefix: endOfLine + oneIndent,
offset: _getLeftBracket(declaration)!.end,
suffix: suffix,
);
@ -1189,14 +1102,13 @@ class CorrectionUtils {
var last = empty || first ? block.leftBracket : statements.last;
var linePrefix = getLinePrefix(last.offset);
var indent = getIndent(1);
String prefix;
String suffix;
if (empty) {
prefix = endOfLine + linePrefix + indent;
prefix = endOfLine + linePrefix + oneIndent;
suffix = endOfLine + linePrefix;
} else if (first) {
prefix = endOfLine + linePrefix + indent;
prefix = endOfLine + linePrefix + oneIndent;
suffix = '';
} else {
prefix = endOfLine + linePrefix;
@ -1303,25 +1215,6 @@ class CorrectionUtils {
selection, range.node(node));
}
/// Skip spaces, tabs and EOLs on the left from [index].
///
/// If [index] is the start of a method, then in the most cases return the end
/// of the previous not-whitespace line.
int skipEmptyLinesLeft(int index) {
var lastLine = index;
while (index > 0) {
var c = _buffer.codeUnitAt(index - 1);
if (!isWhitespace(c)) {
return lastLine;
}
if (isEOL(c)) {
lastLine = index;
}
index--;
}
return 0;
}
/// Return the import element used to import given [element] into the library.
/// May be `null` if was not imported, i.e. declared in the same library.
LibraryImportElement? _getImportElement(Element element) {
@ -1338,6 +1231,60 @@ class CorrectionUtils {
return null;
}
/// Returns a description of the place in which to insert a new directive or a
/// top-level declaration at the top of the file.
InsertionLocation _getInsertionLocationTop() {
// skip leading line comments
var offset = 0;
var insertEmptyLineBefore = false;
var insertEmptyLineAfter = false;
var source = _buffer;
// skip hash-bang
if (offset < source.length - 2) {
var linePrefix = getText(offset, 2);
if (linePrefix == '#!') {
insertEmptyLineBefore = true;
offset = getLineNext(offset);
// skip empty lines to first line comment
var emptyOffset = offset;
while (emptyOffset < source.length - 2) {
var nextLineOffset = getLineNext(emptyOffset);
var line = source.substring(emptyOffset, nextLineOffset);
if (line.trim().isEmpty) {
emptyOffset = nextLineOffset;
continue;
} else if (line.startsWith('//')) {
offset = emptyOffset;
break;
} else {
break;
}
}
}
}
// skip line comments
while (offset < source.length - 2) {
var linePrefix = getText(offset, 2);
if (linePrefix == '//') {
insertEmptyLineBefore = true;
offset = getLineNext(offset);
} else {
break;
}
}
// determine if empty line is required after
var nextLineOffset = getLineNext(offset);
var insertLine = source.substring(offset, nextLineOffset);
if (insertLine.trim().isNotEmpty) {
insertEmptyLineAfter = true;
}
return InsertionLocation(
prefix: insertEmptyLineBefore ? endOfLine : '',
offset: offset,
suffix: insertEmptyLineAfter ? endOfLine : '',
);
}
Token? _getLeftBracket(CompilationUnitMember declaration) {
if (declaration is ClassDeclaration) {
return declaration.leftBracket;
@ -1549,6 +1496,24 @@ class CorrectionUtils {
return _InvertedCondition._simple(getNodeText(expression));
}
/// Return `true` if the given class, mixin, enum or extension [declaration]
/// has open '{' and close '}' on the same line, e.g. `class X {}`.
bool _isClassWithEmptyBody(CompilationUnitMember declaration) {
return getLineThis(_getLeftBracket(declaration)!.offset) ==
getLineThis(_getRightBracket(declaration)!.offset);
}
/// Returns whether [range] contains only whitespace or comments.
bool _isJustWhitespaceOrComment(SourceRange range) {
var trimmedText = getRangeText(range).trim();
// may be whitespace
if (trimmedText.isEmpty) {
return true;
}
// may be comment
return TokenUtils.getTokens(trimmedText, unit.featureSet).isEmpty;
}
/// Checks if [element] is visible in [targetExecutableElement] or
/// [targetClassElement].
bool _isTypeParameterVisible(TypeParameterElement element) {
@ -1566,18 +1531,37 @@ class CorrectionUtils {
return false;
}
// non-whitespace between selection start and range start
if (!isJustWhitespaceOrComment(
if (!_isJustWhitespaceOrComment(
range.startOffsetEndOffset(selection.offset, sourceRange.offset))) {
return true;
}
// non-whitespace after range
if (!isJustWhitespaceOrComment(
if (!_isJustWhitespaceOrComment(
range.startOffsetEndOffset(sourceRange.end, selection.end))) {
return true;
}
// only whitespace in selection around range
return false;
}
/// Skip spaces, tabs and EOLs on the left from [index].
///
/// If [index] is the start of a method, then in the most cases return the end
/// of the previous not-whitespace line.
int _skipEmptyLinesLeft(int index) {
var lastLine = index;
while (index > 0) {
var c = _buffer.codeUnitAt(index - 1);
if (!isWhitespace(c)) {
return lastLine;
}
if (isEOL(c)) {
lastLine = index;
}
index--;
}
return 0;
}
}
/// Describes where to insert new text.

View file

@ -191,7 +191,7 @@ class ExtractLocalRefactoringImpl extends RefactoringImpl
occurrencesShift = edit.replacement.length;
} else if (target is ExpressionFunctionBody) {
var prefix = utils.getNodePrefix(target.parent!);
var indent = utils.getIndent(1);
var indent = utils.oneIndent;
var expr = target.expression;
{
var code = '{$eol$prefix$indent';

View file

@ -132,6 +132,23 @@ extension AstNodeExtension on AstNode {
current = parent;
}
}
/// Returns the [ExpressionStatement] associated with `this` if `this` points
/// to the identifier for a simple `print`, and `null` otherwise.
ExpressionStatement? findSimplePrintInvocation() {
var parent = this.parent;
var grandparent = parent?.parent;
if (this case SimpleIdentifier(:var staticElement)) {
if (staticElement is FunctionElement &&
staticElement.name == 'print' &&
staticElement.library.isDartCore &&
parent is MethodInvocation &&
grandparent is ExpressionStatement) {
return grandparent;
}
}
return null;
}
}
extension BinaryExpressionExtension on BinaryExpression {

View file

@ -61,7 +61,7 @@ abstract final class Flutter {
String eol,
String Function(Expression) getNodeText,
String Function(int) getLinePrefix,
String Function(int) getIndent,
String oneIndent,
String Function(int, int) getText,
String Function(String, String, String,
{bool includeLeading, bool ensureTrailingNewline})
@ -80,7 +80,7 @@ abstract final class Flutter {
newlineLoc -= 1;
}
var indentOld = getLinePrefix(childArg.offset + eol.length + newlineLoc);
var indentNew = '$indentOld${getIndent(1)}';
var indentNew = '$indentOld$oneIndent';
// The separator includes 'child:' but that has no newlines.
var separator =
getText(namedExp.offset, childArg.offset - namedExp.offset);

View file

@ -146,14 +146,6 @@ String? removeEnd(String? str, String? remove) {
return str;
}
String repeat(String s, int n) {
var sb = StringBuffer();
for (var i = 0; i < n; i++) {
sb.write(s);
}
return sb.toString();
}
/// If the [text] length is above the [limit], replace the middle with `...`.
String shorten(String text, int limit) {
if (text.length > limit) {

View file

@ -441,46 +441,6 @@ import 'package:ddd/ddd.dart';
''');
}
Future<void> test_findSimplePrintInvocation() async {
await resolveTestCode('''
void f() {
print('hi');
}
''');
var printIdentifier = findNode.simple('print');
var expected = findNode.expressionStatement('print');
var result = CorrectionUtils(testAnalysisResult)
.findSimplePrintInvocation(printIdentifier);
expect(result, expected);
}
Future<void> test_findSimplePrintInvocation_custom_print() async {
await resolveTestCode('''
void print(String toPrint) {
}
void f() {
print('hi');
}
''');
var printIdentifier = findNode.simple('print(\'hi\'');
var result = CorrectionUtils(testAnalysisResult)
.findSimplePrintInvocation(printIdentifier);
expect(result, null);
}
Future<void> test_findSimplePrintInvocation_negative() async {
await resolveTestCode('''
void f() {
true ? print('hi') : print('false');
}
''');
var printIdentifier = findNode.simple('print(\'false');
var result = CorrectionUtils(testAnalysisResult)
.findSimplePrintInvocation(printIdentifier);
expect(result, null);
}
Future<void> test_invertCondition_binary_compare() async {
await assert_invertCondition('0 < 1', '0 >= 1');
await assert_invertCondition('0 > 1', '0 <= 1');

View file

@ -11,6 +11,7 @@ import '../../../services/search/search_engine_test.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(CompilationUnitFileHeaderTest);
defineReflectiveTests(FindSimplePrintInvocationTest);
});
}
@ -179,3 +180,43 @@ class C {}
orderedEquals(expected));
}
}
@reflectiveTest
class FindSimplePrintInvocationTest extends PubPackageResolutionTest {
Future<void> test_customPrint() async {
await resolveTestCode('''
void print(String toPrint) {
}
void f() {
print('hi');
}
''');
var printIdentifier = findNode.simple('print(\'hi\'');
var result = printIdentifier.findSimplePrintInvocation();
expect(result, null);
}
Future<void> test_negative() async {
await resolveTestCode('''
void f() {
true ? print('hi') : print('false');
}
''');
var printIdentifier = findNode.simple('print(\'false');
var result = printIdentifier.findSimplePrintInvocation();
expect(result, null);
}
Future<void> test_simplePrintInvocation() async {
await resolveTestCode('''
void f() {
print('hi');
}
''');
var printIdentifier = findNode.simple('print');
var expected = findNode.expressionStatement('print');
var result = printIdentifier.findSimplePrintInvocation();
expect(result, expected);
}
}

View file

@ -141,12 +141,6 @@ class StringsTest {
expect(removeEnd('www.domain.com', '.com'), 'www.domain');
}
void test_repeat() {
expect(repeat('x', 0), '');
expect(repeat('x', 5), 'xxxxx');
expect(repeat('abc', 3), 'abcabcabc');
}
void test_shorten() {
expect(shorten('', 10), '');
expect(shorten('0', 10), '0');