Version 2.15.0-299.0.dev
Merge commit '93ac98d6629f9a6f41e4ee505c054d6076aaae8a' into 'dev'
diff --git a/pkg/analysis_server/lib/src/cider/rename.dart b/pkg/analysis_server/lib/src/cider/rename.dart
index 0aa1a3f..29f89fc 100644
--- a/pkg/analysis_server/lib/src/cider/rename.dart
+++ b/pkg/analysis_server/lib/src/cider/rename.dart
@@ -51,7 +51,7 @@
RenameResponse? computeRenameRanges() {
var matches = canRename._fileResolver.findReferences(
- canRename.refactoringElement.offset, canRename.filePath);
+ canRename.refactoringElement.element, canRename.filePath);
return RenameResponse(matches, this);
}
}
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 950eb5b..56a23d3 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -633,6 +633,8 @@
HintCode.UNNECESSARY_QUESTION_MARK,
HintCode.UNNECESSARY_TYPE_CHECK_FALSE,
HintCode.UNNECESSARY_TYPE_CHECK_TRUE,
+ HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT,
+ HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL,
HintCode.UNUSED_CATCH_CLAUSE,
HintCode.UNUSED_CATCH_STACK,
HintCode.UNUSED_ELEMENT,
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index 588e474..076e54f 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -33,6 +33,7 @@
import 'package:analyzer/src/error/language_version_override_verifier.dart';
import 'package:analyzer/src/error/override_verifier.dart';
import 'package:analyzer/src/error/todo_finder.dart';
+import 'package:analyzer/src/error/unicode_text_verifier.dart';
import 'package:analyzer/src/error/unused_local_elements_verifier.dart';
import 'package:analyzer/src/generated/declaration_resolver.dart';
import 'package:analyzer/src/generated/engine.dart';
@@ -348,6 +349,8 @@
);
}
+ UnicodeTextVerifier(errorReporter).verify(unit, file.content);
+
unit.accept(DeadCodeVerifier(errorReporter));
unit.accept(
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
index 8dc8f2a..ecb67b1 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.g.dart
@@ -2914,6 +2914,92 @@
);
/**
+ * Parameters:
+ * 0: the unicode sequence of the code point.
+ */
+ // #### Description
+ //
+ // The analyzer produces this diagnostic when it encounters source that
+ // contains text direction Unicode code points. These code points cause
+ // source code in either a string literal or a comment to be interpreted
+ // and compiled differently than how it appears in editors, leading to
+ // possible security vulnerabilities.
+ //
+ // #### Example
+ //
+ // The following code produces this diagnostic twice because there are
+ // hidden characters at the start and end of the label string:
+ //
+ // ```dart
+ // var label = '[!I!]nteractive text[!'!];
+ // ```
+ //
+ // #### Common fixes
+ //
+ // If the code points are intended to be included in the string literal,
+ // then escape them:
+ //
+ // ```dart
+ // var label = '\u202AInteractive text\u202C';
+ // ```
+ //
+ // If the code points aren't intended to be included in the string literal,
+ // then remove them:
+ //
+ // ```dart
+ // var label = 'Interactive text';
+ // ```
+ static const HintCode TEXT_DIRECTION_CODE_POINT_IN_COMMENT = HintCode(
+ 'TEXT_DIRECTION_CODE_POINT_IN_COMMENT',
+ "The Unicode code point 'U+{0}' changes the appearance of text from how it's interpreted by the compiler.",
+ correctionMessage:
+ "Try removing the code point or using the Unicode escape sequence '\\u{0}'.",
+ );
+
+ /**
+ * Parameters:
+ * 0: the unicode sequence of the code point.
+ */
+ // #### Description
+ //
+ // The analyzer produces this diagnostic when it encounters source that
+ // contains text direction Unicode code points. These code points cause
+ // source code in either a string literal or a comment to be interpreted
+ // and compiled differently than how it appears in editors, leading to
+ // possible security vulnerabilities.
+ //
+ // #### Example
+ //
+ // The following code produces this diagnostic twice because there are
+ // hidden characters at the start and end of the label string:
+ //
+ // ```dart
+ // var label = '[!I!]nteractive text[!'!];
+ // ```
+ //
+ // #### Common fixes
+ //
+ // If the code points are intended to be included in the string literal,
+ // then escape them:
+ //
+ // ```dart
+ // var label = '\u202AInteractive text\u202C';
+ // ```
+ //
+ // If the code points aren't intended to be included in the string literal,
+ // then remove them:
+ //
+ // ```dart
+ // var label = 'Interactive text';
+ // ```
+ static const HintCode TEXT_DIRECTION_CODE_POINT_IN_LITERAL = HintCode(
+ 'TEXT_DIRECTION_CODE_POINT_IN_LITERAL',
+ "The Unicode code point 'U+{0}' changes the appearance of text from how it's interpreted by the compiler.",
+ correctionMessage:
+ "Try removing the code point or using the Unicode escape sequence '\\u{0}'.",
+ );
+
+ /**
* No parameters.
*/
// #### Description
diff --git a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
index be5b2ba..e904ee0 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
@@ -31,6 +31,7 @@
import 'package:analyzer/src/error/inheritance_override.dart';
import 'package:analyzer/src/error/override_verifier.dart';
import 'package:analyzer/src/error/todo_finder.dart';
+import 'package:analyzer/src/error/unicode_text_verifier.dart';
import 'package:analyzer/src/error/unused_local_elements_verifier.dart';
import 'package:analyzer/src/generated/declaration_resolver.dart';
import 'package:analyzer/src/generated/engine.dart';
@@ -270,6 +271,8 @@
unit.accept(DeadCodeVerifier(errorReporter));
var content = getFileContent(file);
+ UnicodeTextVerifier(errorReporter).verify(unit, content);
+
unit.accept(
BestPracticesVerifier(
errorReporter,
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index b9fb939..24876d2 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -18,7 +18,6 @@
import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/results.dart';
-import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/micro/analysis_context.dart';
import 'package:analyzer/src/dart/micro/cider_byte_store.dart';
import 'package:analyzer/src/dart/micro/library_analyzer.dart';
@@ -186,30 +185,25 @@
removedCacheIds.addAll(libraryContext!.collectSharedDataIdentifiers());
}
- /// Looks for references to the Element at the given offset and path. All the
+ /// Looks for references to the given Element in the path. All the
/// files currently cached by the resolver are searched, generated files are
/// ignored.
- List<CiderSearchMatch> findReferences(int offset, String path,
+ List<CiderSearchMatch> findReferences(Element element, String path,
{OperationPerformanceImpl? performance}) {
var references = <CiderSearchMatch>[];
- var unit = resolve(path: path);
- var node = NodeLocator(offset).searchWithin(unit.unit);
- var element = getElementOfNode(node);
- if (element != null) {
- // TODO(keertip): check if element is named constructor.
- var result = fsState!.getFilesContaining(element.displayName);
- result.forEach((filePath) {
- var resolved = resolve(path: filePath);
- var collector = ReferencesCollector(element);
- resolved.unit.accept(collector);
- var offsets = collector.offsets;
- if (offsets.isNotEmpty) {
- var lineInfo = resolved.unit.lineInfo;
- references.add(CiderSearchMatch(filePath,
- offsets.map((offset) => lineInfo?.getLocation(offset)).toList()));
- }
- });
- }
+ // TODO(keertip): check if element is named constructor.
+ var result = fsState!.getFilesContaining(element.displayName);
+ result.forEach((filePath) {
+ var resolved = resolve(path: filePath);
+ var collector = ReferencesCollector(element);
+ resolved.unit.accept(collector);
+ var offsets = collector.offsets;
+ if (offsets.isNotEmpty) {
+ var lineInfo = resolved.unit.lineInfo;
+ references.add(CiderSearchMatch(filePath,
+ offsets.map((offset) => lineInfo?.getLocation(offset)).toList()));
+ }
+ });
return references;
}
diff --git a/pkg/analyzer/lib/src/error/unicode_text_verifier.dart b/pkg/analyzer/lib/src/error/unicode_text_verifier.dart
new file mode 100644
index 0000000..cdbe7ad
--- /dev/null
+++ b/pkg/analyzer/lib/src/error/unicode_text_verifier.dart
@@ -0,0 +1,37 @@
+// 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:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/ast/utilities.dart';
+import 'package:analyzer/src/error/codes.dart';
+
+/// A verifier that checks for unsafe Unicode text.
+/// todo(pq): update w/ a Dart CVE link once published
+class UnicodeTextVerifier {
+ final ErrorReporter errorReporter;
+ UnicodeTextVerifier(this.errorReporter);
+
+ void verify(CompilationUnit unit, String source) {
+ for (var offset = 0; offset < source.length; ++offset) {
+ var codeUnit = source.codeUnitAt(offset);
+ // U+202A, U+202B, U+202C, U+202D, U+202E, U+2066, U+2067, U+2068, U+2069.
+ if (0x202a <= codeUnit &&
+ codeUnit <= 0x2069 &&
+ (codeUnit <= 0x202e || 0x2066 <= codeUnit)) {
+ // This uses an AST visitor; consider a more direct approach.
+ var node = NodeLocator(offset).searchWithin(unit);
+ // If it's not in a string literal, we assume we're in a comment.
+ // This can potentially over-report on syntactically incorrect sources
+ // (where Unicode is outside a string or comment).
+ var errorCode =
+ node is SimpleStringLiteral || node is InterpolationString
+ ? HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL
+ : HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT;
+ var code = codeUnit.toRadixString(16).toUpperCase();
+ errorReporter.reportErrorForOffset(errorCode, offset, 1, [code]);
+ }
+ }
+ }
+}
diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml
index b1868a7..4c8327f 100644
--- a/pkg/analyzer/messages.yaml
+++ b/pkg/analyzer/messages.yaml
@@ -16459,6 +16459,86 @@
If the class needs to be a subtype of the sealed class, then either change
the sealed class so that it's no longer sealed or move the subclass into
the same package as the sealed class.
+ TEXT_DIRECTION_CODE_POINT_IN_COMMENT:
+ problemMessage: The Unicode code point 'U+{0}' changes the appearance of text from how it's interpreted by the compiler.
+ correctionMessage: Try removing the code point or using the Unicode escape sequence '\u{0}'.
+ hasPublishedDocs: false
+ comment: |-
+ Parameters:
+ 0: the unicode sequence of the code point.
+ documentation: |-
+ #### Description
+
+ The analyzer produces this diagnostic when it encounters source that
+ contains text direction Unicode code points. These code points cause
+ source code in either a string literal or a comment to be interpreted
+ and compiled differently than how it appears in editors, leading to
+ possible security vulnerabilities.
+
+ #### Example
+
+ The following code produces this diagnostic twice because there are
+ hidden characters at the start and end of the label string:
+
+ ```dart
+ var label = '[!I!]nteractive text[!'!];
+ ```
+
+ #### Common fixes
+
+ If the code points are intended to be included in the string literal,
+ then escape them:
+
+ ```dart
+ var label = '\u202AInteractive text\u202C';
+ ```
+
+ If the code points aren't intended to be included in the string literal,
+ then remove them:
+
+ ```dart
+ var label = 'Interactive text';
+ ```
+ TEXT_DIRECTION_CODE_POINT_IN_LITERAL:
+ problemMessage: The Unicode code point 'U+{0}' changes the appearance of text from how it's interpreted by the compiler.
+ correctionMessage: Try removing the code point or using the Unicode escape sequence '\u{0}'.
+ hasPublishedDocs: false
+ comment: |-
+ Parameters:
+ 0: the unicode sequence of the code point.
+ documentation: |-
+ #### Description
+
+ The analyzer produces this diagnostic when it encounters source that
+ contains text direction Unicode code points. These code points cause
+ source code in either a string literal or a comment to be interpreted
+ and compiled differently than how it appears in editors, leading to
+ possible security vulnerabilities.
+
+ #### Example
+
+ The following code produces this diagnostic twice because there are
+ hidden characters at the start and end of the label string:
+
+ ```dart
+ var label = '[!I!]nteractive text[!'!];
+ ```
+
+ #### Common fixes
+
+ If the code points are intended to be included in the string literal,
+ then escape them:
+
+ ```dart
+ var label = '\u202AInteractive text\u202C';
+ ```
+
+ If the code points aren't intended to be included in the string literal,
+ then remove them:
+
+ ```dart
+ var label = 'Interactive text';
+ ```
TYPE_CHECK_IS_NOT_NULL:
sharedName: TYPE_CHECK_WITH_NULL
problemMessage: "Tests for non-null should be done with '!= null'."
diff --git a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
index 52861ce..3b59012 100644
--- a/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
+++ b/pkg/analyzer/test/src/dart/micro/simple_file_resolver_test.dart
@@ -2,11 +2,14 @@
// 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/element/element.dart';
import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/dart/micro/cider_byte_store.dart';
import 'package:analyzer/src/dart/micro/library_graph.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
+import 'package:analyzer/src/dart/micro/utils.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/lint/registry.dart';
import 'package:test/test.dart';
@@ -391,7 +394,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(6, aPath);
+ var result = fileResolver.findReferences(_findElement(6, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(4, 11)]),
CiderSearchMatch(aPath, [CharacterLocation(1, 7)])
@@ -412,7 +415,7 @@
''');
await resolveFile(aPath);
- var result = fileResolver.findReferences(16, aPath);
+ var result = fileResolver.findReferences(_findElement(16, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(
aPath, [CharacterLocation(2, 7), CharacterLocation(5, 5)])
@@ -431,7 +434,7 @@
''');
await resolveFile(aPath);
- var result = fileResolver.findReferences(11, aPath);
+ var result = fileResolver.findReferences(_findElement(11, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(
aPath, [CharacterLocation(2, 3), CharacterLocation(5, 1)])
@@ -457,7 +460,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(20, aPath);
+ var result = fileResolver.findReferences(_findElement(20, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(5, 15)]),
CiderSearchMatch(aPath, [CharacterLocation(2, 11)])
@@ -476,7 +479,7 @@
}
''');
await resolveFile(aPath);
- var result = fileResolver.findReferences(39, aPath);
+ var result = fileResolver.findReferences(_findElement(39, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(
aPath, [CharacterLocation(3, 9), CharacterLocation(4, 11)])
@@ -509,7 +512,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(17, aPath);
+ var result = fileResolver.findReferences(_findElement(17, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(5, 5)]),
CiderSearchMatch(
@@ -536,7 +539,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(21, aPath);
+ var result = fileResolver.findReferences(_findElement(21, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(5, 5)]),
CiderSearchMatch(aPath, [CharacterLocation(2, 12)])
@@ -563,7 +566,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(19, aPath);
+ var result = fileResolver.findReferences(_findElement(19, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(4, 13)]),
CiderSearchMatch(aPath, [CharacterLocation(3, 9)])
@@ -590,7 +593,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(20, aPath);
+ var result = fileResolver.findReferences(_findElement(20, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(4, 3)]),
CiderSearchMatch(aPath, [CharacterLocation(3, 10)])
@@ -610,7 +613,7 @@
''');
await resolveFile(aPath);
- var result = fileResolver.findReferences(10, aPath);
+ var result = fileResolver.findReferences(_findElement(10, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(
aPath, [CharacterLocation(1, 11), CharacterLocation(4, 11)])
@@ -628,7 +631,7 @@
}
''');
await resolveFile(aPath);
- var result = fileResolver.findReferences(10, aPath);
+ var result = fileResolver.findReferences(_findElement(10, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(aPath, [
CharacterLocation(1, 11),
@@ -656,7 +659,7 @@
''');
await resolveFile(bPath);
- var result = fileResolver.findReferences(8, aPath);
+ var result = fileResolver.findReferences(_findElement(8, aPath), aPath);
var expected = <CiderSearchMatch>[
CiderSearchMatch(bPath, [CharacterLocation(3, 8)]),
CiderSearchMatch(aPath, [CharacterLocation(1, 9)])
@@ -1276,4 +1279,11 @@
void _assertRemovedPaths(Matcher matcher) {
expect(fileResolver.fsState!.testView.removedPaths, matcher);
}
+
+ Element _findElement(int offset, String filePath) {
+ var resolvedUnit = fileResolver.resolve(path: filePath);
+ var node = NodeLocator(offset).searchWithin(resolvedUnit.unit);
+ var element = getElementOfNode(node);
+ return element!;
+ }
}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 5b02fd3..779326c 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -631,6 +631,7 @@
as switch_expression_not_assignable;
import 'tearoff_of_generative_constructor_of_abstract_class_test.dart'
as tearoff_of_generative_constructor_of_abstract_class;
+import 'text_direction_code_point_test.dart' as text_direction_code_point;
import 'throw_of_invalid_type_test.dart' as throw_of_invalid_type;
import 'todo_test.dart' as todo_test;
import 'top_level_cycle_test.dart' as top_level_cycle;
@@ -1134,6 +1135,7 @@
switch_case_completes_normally.main();
switch_expression_not_assignable.main();
tearoff_of_generative_constructor_of_abstract_class.main();
+ text_direction_code_point.main();
throw_of_invalid_type.main();
todo_test.main();
top_level_cycle.main();
diff --git a/pkg/analyzer/test/src/diagnostics/text_direction_code_point_test.dart b/pkg/analyzer/test/src/diagnostics/text_direction_code_point_test.dart
new file mode 100644
index 0000000..be19a86
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/text_direction_code_point_test.dart
@@ -0,0 +1,92 @@
+// 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:analyzer/src/dart/error/hint_codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(UnsafeTextDirectionCodepointTest);
+ });
+}
+
+@reflectiveTest
+class UnsafeTextDirectionCodepointTest extends PubPackageResolutionTest {
+ test_comments() async {
+ await assertErrorsInCode('''
+// \u2066
+/// \u2066
+void f() { // \u2066
+ // \u2066
+}
+''', [
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT, 3, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT, 9, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT, 25, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT, 32, 1),
+ ]);
+ }
+
+ /// https://github.com/flutter/flutter/pull/93029
+ test_file_ok() async {
+ // Raw strings preserve the escapes.
+ await assertNoErrorsInCode(r'''
+var u202a = '\u202AInteractive text\u202C';
+''');
+ }
+
+ test_message_escape() async {
+ await assertErrorsInCode('''
+var u202a = '\u202A';
+''', [
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 13, 1,
+ messageContains: ['U+202A']),
+ ]);
+ }
+
+ test_multiLineString() async {
+ await assertErrorsInCode('''
+var s = """ \u202a
+ Multiline!
+""";
+''', [
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 12, 1),
+ ]);
+ }
+
+ test_simpleStrings() async {
+ await assertErrorsInCode('''
+var u202a = '\u202A';
+var u202b = '\u202B';
+var u202c = '\u202C';
+var u202d = '\u202D';
+var u202e = '\u202E';
+var u2066 = '\u2066';
+var u2067 = '\u2067';
+var u2068 = '\u2068';
+var u2069 = '\u2069';
+''', [
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 13, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 30, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 47, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 64, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 81, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 98, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 115, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 132, 1),
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 149, 1),
+ ]);
+ }
+
+ test_stringInterpolation() async {
+ await assertErrorsInCode('''
+var x = 'x';
+var u202a = '\u202A\$x';
+''', [
+ error(HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL, 26, 1),
+ ]);
+ }
+}
diff --git a/pkg/analyzer/test/verify_diagnostics_test.dart b/pkg/analyzer/test/verify_diagnostics_test.dart
index bb0643d..d5ee1bf 100644
--- a/pkg/analyzer/test/verify_diagnostics_test.dart
+++ b/pkg/analyzer/test/verify_diagnostics_test.dart
@@ -82,6 +82,11 @@
'CompileTimeErrorCode.YIELD_EACH_IN_NON_GENERATOR',
// The code has been replaced but is not yet removed.
'HintCode.DEPRECATED_MEMBER_USE',
+ // Produces more than one error range by design.
+ // TODO: update verification to allow for multiple highlight ranges.
+ 'HintCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT',
+ // Produces more than one error range by design.
+ 'HintCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL',
// Produces two diagnostics when it should only produce one (see
// https://github.com/dart-lang/sdk/issues/43051)
'HintCode.UNNECESSARY_NULL_COMPARISON_FALSE',
diff --git a/pkg/analyzer/tool/diagnostics/diagnostics.md b/pkg/analyzer/tool/diagnostics/diagnostics.md
index ba40e44..74533fb 100644
--- a/pkg/analyzer/tool/diagnostics/diagnostics.md
+++ b/pkg/analyzer/tool/diagnostics/diagnostics.md
@@ -13019,6 +13019,82 @@
Tear off the constructor of a concrete class.
+### text_direction_code_point_in_comment
+
+_The Unicode code point 'U+{0}' changes the appearance of text from how it's
+interpreted by the compiler._
+
+#### Description
+
+The analyzer produces this diagnostic when it encounters source that
+contains text direction Unicode code points. These code points cause
+source code in either a string literal or a comment to be interpreted
+and compiled differently than how it appears in editors, leading to
+possible security vulnerabilities.
+
+#### Example
+
+The following code produces this diagnostic twice because there are
+hidden characters at the start and end of the label string:
+
+{% prettify dart tag=pre+code %}
+var label = '[!I!]nteractive text[!'!];
+{% endprettify %}
+
+#### Common fixes
+
+If the code points are intended to be included in the string literal,
+then escape them:
+
+{% prettify dart tag=pre+code %}
+var label = '\u202AInteractive text\u202C';
+{% endprettify %}
+
+If the code points aren't intended to be included in the string literal,
+then remove them:
+
+{% prettify dart tag=pre+code %}
+var label = 'Interactive text';
+{% endprettify %}
+
+### text_direction_code_point_in_literal
+
+_The Unicode code point 'U+{0}' changes the appearance of text from how it's
+interpreted by the compiler._
+
+#### Description
+
+The analyzer produces this diagnostic when it encounters source that
+contains text direction Unicode code points. These code points cause
+source code in either a string literal or a comment to be interpreted
+and compiled differently than how it appears in editors, leading to
+possible security vulnerabilities.
+
+#### Example
+
+The following code produces this diagnostic twice because there are
+hidden characters at the start and end of the label string:
+
+{% prettify dart tag=pre+code %}
+var label = '[!I!]nteractive text[!'!];
+{% endprettify %}
+
+#### Common fixes
+
+If the code points are intended to be included in the string literal,
+then escape them:
+
+{% prettify dart tag=pre+code %}
+var label = '\u202AInteractive text\u202C';
+{% endprettify %}
+
+If the code points aren't intended to be included in the string literal,
+then remove them:
+
+{% prettify dart tag=pre+code %}
+var label = 'Interactive text';
+{% endprettify %}
+
### throw_of_invalid_type
_The type '{0}' of the thrown expression must be assignable to 'Object'._
diff --git a/tools/VERSION b/tools/VERSION
index bd7b815..dc9f248 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 298
+PRERELEASE 299
PRERELEASE_PATCH 0
\ No newline at end of file