Add a quick fix for avoid_escaping_inner_quotes

Fixes #47158

Change-Id: I447cf68d2da682bab93fb43b0ad61546a2217755
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212825
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Ahmed Ashour 2021-10-01 21:55:15 +00:00 committed by Brian Wilkerson
parent 7fb8c7afea
commit a4d8f343ae
6 changed files with 250 additions and 46 deletions

View file

@ -6,14 +6,97 @@ import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
abstract class ConvertQuotes extends CorrectionProducer {
ConvertQuotes();
class ConvertQuotes extends _ConvertQuotes {
@override
late bool _fromDouble;
@override
bool get canBeAppliedInBulk => true;
@override
bool get canBeAppliedToFile => true;
@override
FixKind get fixKind => DartFixKind.CONVERT_QUOTES;
@override
FixKind get multiFixKind => DartFixKind.CONVERT_QUOTES_MULTI;
@override
Future<void> compute(ChangeBuilder builder) async {
final node = this.node;
if (node is SimpleStringLiteral) {
_fromDouble = !node.isSingleQuoted;
await _simpleStringLiteral(builder, node);
await removeBackslash(builder, node.literal);
} else if (node is StringInterpolation) {
_fromDouble = !node.isSingleQuoted;
await _stringInterpolation(builder, node);
for (var child in node.childEntities.whereType<InterpolationString>()) {
await removeBackslash(builder, child.contents);
}
}
}
Future<void> removeBackslash(ChangeBuilder builder, Token token) async {
var quote = _fromDouble ? '"' : "'";
var text = utils.getText(token.offset, token.length);
for (var i = 0; i + 1 < text.length; i++) {
if (text[i] == r'\' && text[i + 1] == quote) {
await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(SourceRange(token.offset + i, 1));
});
i++;
}
}
}
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ConvertQuotes newInstance() => ConvertQuotes();
}
class ConvertToDoubleQuotes extends _ConvertQuotes {
@override
AssistKind get assistKind => DartAssistKind.CONVERT_TO_DOUBLE_QUOTED_STRING;
@override
bool get _fromDouble => false;
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ConvertToDoubleQuotes newInstance() => ConvertToDoubleQuotes();
}
class ConvertToSingleQuotes extends _ConvertQuotes {
@override
AssistKind get assistKind => DartAssistKind.CONVERT_TO_SINGLE_QUOTED_STRING;
@override
bool get canBeAppliedInBulk => true;
@override
bool get canBeAppliedToFile => true;
@override
FixKind get fixKind => DartFixKind.CONVERT_TO_SINGLE_QUOTED_STRING;
@override
FixKind get multiFixKind => DartFixKind.CONVERT_TO_SINGLE_QUOTED_STRING_MULTI;
@override
bool get _fromDouble => true;
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ConvertToSingleQuotes newInstance() => ConvertToSingleQuotes();
}
abstract class _ConvertQuotes extends CorrectionProducer {
/// Return `true` if this producer is converting from double quotes to single
/// quotes, or `false` if it's converting from single quotes to double quotes.
bool get _fromDouble;
@ -31,9 +114,7 @@ abstract class ConvertQuotes extends CorrectionProducer {
}
Future<void> _simpleStringLiteral(
ChangeBuilder builder,
SimpleStringLiteral node,
) async {
ChangeBuilder builder, SimpleStringLiteral node) async {
if (_fromDouble ? !node.isSingleQuoted : node.isSingleQuoted) {
var newQuote = node.isMultiline
? (_fromDouble ? "'''" : '"""')
@ -56,9 +137,7 @@ abstract class ConvertQuotes extends CorrectionProducer {
}
Future<void> _stringInterpolation(
ChangeBuilder builder,
StringInterpolation node,
) async {
ChangeBuilder builder, StringInterpolation node) async {
if (_fromDouble ? !node.isSingleQuoted : node.isSingleQuoted) {
var newQuote = node.isMultiline
? (_fromDouble ? "'''" : '"""')
@ -87,41 +166,3 @@ abstract class ConvertQuotes extends CorrectionProducer {
}
}
}
class ConvertToDoubleQuotes extends ConvertQuotes {
ConvertToDoubleQuotes();
@override
AssistKind get assistKind => DartAssistKind.CONVERT_TO_DOUBLE_QUOTED_STRING;
@override
bool get _fromDouble => false;
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ConvertToDoubleQuotes newInstance() => ConvertToDoubleQuotes();
}
class ConvertToSingleQuotes extends ConvertQuotes {
ConvertToSingleQuotes();
@override
AssistKind get assistKind => DartAssistKind.CONVERT_TO_SINGLE_QUOTED_STRING;
@override
bool get canBeAppliedInBulk => true;
@override
bool get canBeAppliedToFile => true;
@override
FixKind get fixKind => DartFixKind.CONVERT_TO_SINGLE_QUOTED_STRING;
@override
FixKind get multiFixKind => DartFixKind.CONVERT_TO_SINGLE_QUOTED_STRING_MULTI;
@override
bool get _fromDouble => true;
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ConvertToSingleQuotes newInstance() => ConvertToSingleQuotes();
}

View file

@ -309,6 +309,16 @@ class DartFixKind {
DartFixKindPriority.IN_FILE,
'Convert to expression bodies everywhere in file',
);
static const CONVERT_QUOTES = FixKind(
'dart.fix.convert.quotes',
DartFixKindPriority.DEFAULT,
'Convert the quotes and remove escapes',
);
static const CONVERT_QUOTES_MULTI = FixKind(
'dart.fix.convert.quotes.multi',
DartFixKindPriority.IN_FILE,
'Convert the quotes and remove escapes everywhere in file',
);
static const CONVERT_TO_CONTAINS = FixKind(
'dart.fix.convert.toContains',
DartFixKindPriority.DEFAULT,

View file

@ -345,6 +345,9 @@ class FixProcessor extends BaseProcessor {
LintNames.avoid_empty_else: [
RemoveEmptyElse.newInstance,
],
LintNames.avoid_escaping_inner_quotes: [
ConvertQuotes.newInstance,
],
LintNames.avoid_function_literals_in_foreach_calls: [
ConvertForEachToForLoop.newInstance,
],

View file

@ -13,6 +13,8 @@ class LintNames {
static const String avoid_annotating_with_dynamic =
'avoid_annotating_with_dynamic';
static const String avoid_empty_else = 'avoid_empty_else';
static const String avoid_escaping_inner_quotes =
'avoid_escaping_inner_quotes';
static const String avoid_function_literals_in_foreach_calls =
'avoid_function_literals_in_foreach_calls';
static const String avoid_init_to_null = 'avoid_init_to_null';

View file

@ -0,0 +1,146 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:test/expect.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'fix_processor.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ConvertQuotesBulkTest);
defineReflectiveTests(ConvertQuotesInFileTest);
defineReflectiveTests(ConvertQuotesTest);
});
}
@reflectiveTest
class ConvertQuotesBulkTest extends BulkFixProcessorTest {
@override
String get lintCode => LintNames.avoid_escaping_inner_quotes;
Future<void> test_string_in_interpolation_string() async {
await resolveTestCode(r'''
void f() {
print('a\'${'b\'c'}\'d');
}
''');
await assertHasFix(r'''
void f() {
print("a'${"b'c"}'d");
}
''');
}
}
@reflectiveTest
class ConvertQuotesInFileTest extends FixInFileProcessorTest {
Future<void> test_File() async {
createAnalysisOptionsFile(lints: [LintNames.avoid_escaping_inner_quotes]);
await resolveTestCode(r'''
void f() {
print("a\"b\"c");
print('d\'e\'f');
}
''');
var fixes = await getFixesForFirstError();
expect(fixes, hasLength(1));
assertProduces(fixes.first, r'''
void f() {
print('a"b"c');
print("d'e'f");
}
''');
}
}
@reflectiveTest
class ConvertQuotesTest extends FixProcessorLintTest {
@override
FixKind get kind => DartFixKind.CONVERT_QUOTES;
@override
String get lintCode => LintNames.avoid_escaping_inner_quotes;
Future<void> test_backslash() async {
await resolveTestCode(r'''
void f() {
print('\\\'b\'\$');
}
''');
await assertHasFix(r'''
void f() {
print("\\'b'\$");
}
''');
}
Future<void> test_double_quotes() async {
await resolveTestCode(r'''
void f() {
print("a\"\"c");
}
''');
await assertHasFix(r'''
void f() {
print('a""c');
}
''');
}
Future<void> test_interpolation() async {
await resolveTestCode(r'''
void f(String d) {
print('a\'b\'c $d');
}
''');
await assertHasFix(r'''
void f(String d) {
print("a'b'c $d");
}
''');
}
Future<void> test_interpolation_string() async {
await resolveTestCode(r'''
void f(String d) {
print('a\'b\'c ${d.length}');
}
''');
await assertHasFix(r'''
void f(String d) {
print("a'b'c ${d.length}");
}
''');
}
Future<void> test_single_quotes() async {
await resolveTestCode(r'''
void f() {
print('a\'b\'c');
}
''');
await assertHasFix(r'''
void f() {
print("a'b'c");
}
''');
}
Future<void> test_string_in_interpolation_string() async {
await resolveTestCode(r'''
void f() {
print('a${'b\'c'}d');
}
''');
await assertHasFix(r'''
void f() {
print('a${"b'c"}d');
}
''');
}
}

View file

@ -51,6 +51,7 @@ import 'convert_flutter_children_test.dart' as convert_flutter_children;
import 'convert_for_each_to_for_loop_test.dart' as convert_for_each_to_for_loop;
import 'convert_into_expression_body_test.dart' as convert_into_expression_body;
import 'convert_into_is_not_test.dart' as convert_into_is_not;
import 'convert_quotes_test.dart' as convert_quotes;
import 'convert_to_contains_test.dart' as convert_to_contains;
import 'convert_to_for_element_test.dart' as convert_to_for_element;
import 'convert_to_generic_function_syntax_test.dart'
@ -245,6 +246,7 @@ void main() {
convert_for_each_to_for_loop.main();
convert_into_expression_body.main();
convert_into_is_not.main();
convert_quotes.main();
convert_to_contains.main();
convert_to_for_element.main();
convert_to_generic_function_syntax.main();