invalid `@doNotStore` assignment analysis
Another pass at https://dart-review.googlesource.com/c/sdk/+/159841 (reverted) with a guarding type-check.
Change-Id: Ia270be4e880d71642a96c5ab93a306efba1ceedb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/160961
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index aba9b3e..5d9f60a 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -465,6 +465,7 @@
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_EXTENDS,
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_IMPLEMENTS,
FfiCode.SUBTYPE_OF_STRUCT_CLASS_IN_WITH,
+ HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE,
HintCode.DEAD_CODE,
HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH,
diff --git a/pkg/analyzer/lib/src/dart/element/extensions.dart b/pkg/analyzer/lib/src/dart/element/extensions.dart
index ff2a475..bd4bd24 100644
--- a/pkg/analyzer/lib/src/dart/element/extensions.dart
+++ b/pkg/analyzer/lib/src/dart/element/extensions.dart
@@ -7,6 +7,26 @@
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
+extension ElementExtension on Element {
+ /// Return `true` if this element, the enclosing class (if there is one), or
+ /// the enclosing library, has been annotated with the `@doNotStore`
+ /// annotation.
+ bool get hasOrInheritsDoNotStore {
+ if (hasDoNotStore) {
+ return true;
+ }
+ var ancestor = enclosingElement;
+ if (ancestor is ClassElement || ancestor is ExtensionElement) {
+ if (ancestor.hasDoNotStore) {
+ return true;
+ }
+ ancestor = ancestor.enclosingElement;
+ }
+ return ancestor is CompilationUnitElement &&
+ ancestor.enclosingElement.hasDoNotStore;
+ }
+}
+
extension ParameterElementExtensions on ParameterElement {
/// Return [ParameterElement] with the specified properties replaced.
ParameterElement copyWith({DartType type, ParameterKind kind}) {
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
index f413987..7aef562 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
@@ -15,6 +15,14 @@
*/
class HintCode extends AnalyzerErrorCode {
/**
+ * Users should not assign values marked `@doNotStore`.
+ */
+ static const HintCode ASSIGNMENT_OF_DO_NOT_STORE = HintCode(
+ 'ASSIGNMENT_OF_DO_NOT_STORE',
+ "'{0}' is marked 'doNotStore' and shouldn't be assigned to a field.",
+ correction: "Try removing the assignment.");
+
+ /**
* When the target expression uses '?.' operator, it can be `null`, so all the
* subsequent invocations should also use '?.' operator.
*/
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index aef2cc9..8cfc83d 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -14,6 +14,7 @@
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart' show ExecutableMember;
import 'package:analyzer/src/dart/element/type.dart';
@@ -359,6 +360,37 @@
field.name,
[field.name, overriddenElement.enclosingElement.name]);
}
+
+ var expression = field.initializer;
+
+ Element element;
+ if (expression is PropertyAccess) {
+ element = expression.propertyName.staticElement;
+ // Tear-off.
+ if (element is FunctionElement || element is MethodElement) {
+ element = null;
+ }
+ } else if (expression is MethodInvocation) {
+ element = expression.methodName.staticElement;
+ } else if (expression is Identifier) {
+ element = expression.staticElement;
+ // Tear-off.
+ if (element is FunctionElement || element is MethodElement) {
+ element = null;
+ }
+ }
+ if (element != null) {
+ if (element is PropertyAccessorElement && element.isSynthetic) {
+ element = (element as PropertyAccessorElement).variable;
+ }
+ if (element.hasOrInheritsDoNotStore) {
+ _errorReporter.reportErrorForNode(
+ HintCode.ASSIGNMENT_OF_DO_NOT_STORE,
+ expression,
+ [element.name],
+ );
+ }
+ }
}
} finally {
_inDeprecatedMember = wasInDeprecatedMember;
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_packages.dart b/pkg/analyzer/lib/src/test_utilities/mock_packages.dart
index edc7bd4..2f054b5 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_packages.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_packages.dart
@@ -25,6 +25,7 @@
library meta;
const _AlwaysThrows alwaysThrows = const _AlwaysThrows();
+const _DoNotStore doNotStore = _DoNotStore();
const _Factory factory = const _Factory();
const Immutable immutable = const Immutable();
const _Literal literal = const _Literal();
@@ -36,16 +37,19 @@
const _Sealed sealed = const _Sealed();
const _VisibleForTesting visibleForTesting = const _VisibleForTesting();
-class Immutable {
- final String reason;
- const Immutable([this.reason]);
-}
class _AlwaysThrows {
const _AlwaysThrows();
}
+class _DoNotStore {
+ const _DoNotStore();
+}
class _Factory {
const _Factory();
}
+class Immutable {
+ final String reason;
+ const Immutable([this.reason]);
+}
class _Literal {
const _Literal();
}
diff --git a/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart b/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart
new file mode 100644
index 0000000..ef2c8c2
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/assignment_of_do_not_store_test.dart
@@ -0,0 +1,179 @@
+// 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/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(AssignmentOfDoNotStoreTest);
+ });
+}
+
+@reflectiveTest
+class AssignmentOfDoNotStoreTest extends PubPackageResolutionTest {
+ @override
+ void setUp() {
+ super.setUp();
+ writeTestPackageConfigWithMeta();
+ }
+
+ test_classMemberGetter() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+class A {
+ @doNotStore
+ String get v => '';
+}
+
+class B {
+ String f = A().v;
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 106, 5),
+ ]);
+ }
+
+ test_classMemberVariable() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+class A{
+ @doNotStore
+ final f = '';
+}
+
+class B {
+ String f = A().f;
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 99, 5),
+ ]);
+ }
+
+ test_classStaticGetter() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+class A {
+ @doNotStore
+ static String get v => '';
+}
+
+class B {
+ String f = A.v;
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 113, 3),
+ ]);
+ }
+
+ test_classStaticVariable() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+class A{
+ @doNotStore
+ static final f = '';
+}
+
+class B {
+ String f = A.f;
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 106, 3),
+ ]);
+ }
+
+ test_functionAssignment() async {
+ await assertNoErrorsInCode('''
+import 'package:meta/meta.dart';
+
+@doNotStore
+String g(int i) => '';
+
+class C {
+ String Function(int) f = g;
+}
+''');
+ }
+
+ test_functionReturnValue() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+@doNotStore
+String getV() => '';
+
+class A {
+ final f = getV();
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 90, 6),
+ ]);
+ }
+
+ test_methodReturnValue() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+class A {
+ @doNotStore
+ String getV() => '';
+}
+
+class B {
+ final f = A().getV();
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 106, 10),
+ ]);
+ }
+
+ test_tearOff() async {
+ await assertNoErrorsInCode('''
+import 'package:meta/meta.dart';
+
+@doNotStore
+String getV() => '';
+
+class A {
+ final f = getV;
+}
+''');
+ }
+
+ test_topLevelGetter() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+@doNotStore
+String get v => '';
+
+class A {
+ final f = v;
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 89, 1),
+ ]);
+ }
+
+ test_topLevelVariable() async {
+ await assertErrorsInCode('''
+import 'package:meta/meta.dart';
+
+@doNotStore
+final v = '';
+
+class A {
+ final f = v;
+}
+''', [
+ error(HintCode.ASSIGNMENT_OF_DO_NOT_STORE, 83, 1),
+ ]);
+ }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 0341386..e23b641 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -22,6 +22,7 @@
import 'argument_type_not_assignable_test.dart' as argument_type_not_assignable;
import 'assert_in_redirecting_constructor_test.dart'
as assert_in_redirecting_constructor;
+import 'assignment_of_do_not_store_test.dart' as assignment_of_do_not_store;
import 'assignment_to_const_test.dart' as assignment_to_const;
import 'assignment_to_final_local_test.dart' as assignment_to_final_local;
import 'assignment_to_final_no_setter_test.dart'
@@ -653,6 +654,7 @@
annotation_with_non_class.main();
argument_type_not_assignable.main();
assert_in_redirecting_constructor.main();
+ assignment_of_do_not_store.main();
assignment_to_const.main();
assignment_to_final_local.main();
assignment_to_final_no_setter.main();