add avoid_escaping_inner_quotes (#2000)

* add change_quotes

* rename to avoid_escaping_inner_quotes
diff --git a/example/all.yaml b/example/all.yaml
index 74665ec..70c84a4 100644
--- a/example/all.yaml
+++ b/example/all.yaml
@@ -17,6 +17,7 @@
     - avoid_double_and_int_checks
     - avoid_empty_else
     - avoid_equals_and_hash_code_on_mutable_classes
+    - avoid_escaping_inner_quotes
     - avoid_field_initializers_in_const_classes
     - avoid_function_literals_in_foreach_calls
     - avoid_implementing_value_types
diff --git a/lib/src/rules.dart b/lib/src/rules.dart
index 03d40b4..c37d3e9 100644
--- a/lib/src/rules.dart
+++ b/lib/src/rules.dart
@@ -18,6 +18,7 @@
 import 'rules/avoid_double_and_int_checks.dart';
 import 'rules/avoid_empty_else.dart';
 import 'rules/avoid_equals_and_hash_code_on_mutable_classes.dart';
+import 'rules/avoid_escaping_inner_quotes.dart';
 import 'rules/avoid_field_initializers_in_const_classes.dart';
 import 'rules/avoid_function_literals_in_foreach_calls.dart';
 import 'rules/avoid_implementing_value_types.dart';
@@ -185,6 +186,7 @@
     ..register(AvoidClassesWithOnlyStaticMembers())
     ..register(AvoidDoubleAndIntChecks())
     ..register(AvoidEmptyElse())
+    ..register(AvoidEscapingInnerQuotes())
     ..register(AvoidFieldInitializersInConstClasses())
     ..register(AvoidFunctionLiteralInForeachMethod())
     ..register(AvoidImplementingValueTypes())
diff --git a/lib/src/rules/avoid_escaping_inner_quotes.dart b/lib/src/rules/avoid_escaping_inner_quotes.dart
new file mode 100644
index 0000000..30b4ac6
--- /dev/null
+++ b/lib/src/rules/avoid_escaping_inner_quotes.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, 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:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:meta/meta.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'Avoid escaping inner quotes by converting surrounding quotes.';
+
+const _details = r'''
+
+Avoid escaping inner quotes by converting surrounding quotes.
+
+**BAD:**
+```
+var s = 'It\'s not fun';
+```
+
+**GOOD:**
+```
+var s = "It's not fun";
+```
+
+''';
+
+class AvoidEscapingInnerQuotes extends LintRule implements NodeLintRule {
+  AvoidEscapingInnerQuotes()
+      : super(
+            name: 'avoid_escaping_inner_quotes',
+            description: _desc,
+            details: _details,
+            group: Group.style);
+
+  @override
+  void registerNodeProcessors(NodeLintRegistry registry,
+      [LinterContext context]) {
+    final visitor = _Visitor(this);
+    registry.addSimpleStringLiteral(this, visitor);
+    registry.addStringInterpolation(this, visitor);
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitSimpleStringLiteral(SimpleStringLiteral node) {
+    if (node.isRaw || node.isMultiline) return;
+
+    if (isChangeable(node.value, isSingleQuoted: node.isSingleQuoted)) {
+      rule.reportLint(node);
+    }
+  }
+
+  @override
+  void visitStringInterpolation(StringInterpolation node) {
+    if (node.isRaw || node.isMultiline) return;
+
+    final text = node.elements
+        .whereType<InterpolationString>()
+        .map((e) => e.value)
+        .reduce((a, b) => '$a$b');
+    if (isChangeable(text, isSingleQuoted: node.isSingleQuoted)) {
+      rule.reportLint(node);
+    }
+  }
+
+  bool isChangeable(String text, {@required bool isSingleQuoted}) =>
+      text.contains(isSingleQuoted ? "'" : '"') &&
+      !text.contains(isSingleQuoted ? '"' : "'");
+}
diff --git a/test/rules/avoid_escaping_inner_quotes.dart b/test/rules/avoid_escaping_inner_quotes.dart
new file mode 100644
index 0000000..f98e0e3
--- /dev/null
+++ b/test/rules/avoid_escaping_inner_quotes.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, 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.
+
+// test w/ `pub run test -N avoid_escaping_inner_quotes`
+
+f(o) {
+  f(""); // OK
+  f(''); // OK
+  f("\""); // LINT
+  f('\''); // LINT
+  f("\"'"); // OK
+  f('\'"'); // OK
+  f("\"$f"); // LINT
+  f('\'$f'); // LINT
+  f("\"'$f"); // OK
+  f('\'"$f'); // OK
+}
\ No newline at end of file