Add hints to check the SDK constraints for both set literals and for the ui-as-code features

Change-Id: I1046e57304d56e6d7f088ea1a8a054318a45ac37
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96837
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index adeb0f9..a5f14e7 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -335,7 +335,13 @@
 //    }
     if (errorCode == HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE) {
       await _addFix_importAsync();
-      await _addFix_updateSdkConstraints();
+      await _addFix_updateSdkConstraints('2.1.0');
+    }
+    if (errorCode == HintCode.SDK_VERSION_SET_LITERAL) {
+      await _addFix_updateSdkConstraints('2.2.0');
+    }
+    if (errorCode == HintCode.SDK_VERSION_UI_AS_CODE) {
+      await _addFix_updateSdkConstraints('2.2.2');
     }
     if (errorCode == HintCode.TYPE_CHECK_IS_NOT_NULL) {
       await _addFix_isNotNull();
@@ -3203,16 +3209,6 @@
     }
   }
 
-  Future<void> _addFix_replaceVarWithDynamic() async {
-    // TODO(brianwilkerson) Determine whether this await is necessary.
-    await null;
-    var changeBuilder = _newDartChangeBuilder();
-    await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
-      builder.addSimpleReplacement(range.error(error), 'dynamic');
-    });
-    _addFixFromBuilder(changeBuilder, DartFixKind.REPLACE_VAR_WITH_DYNAMIC);
-  }
-
   Future<void> _addFix_replaceNullWithClosure() async {
     var nodeToFix;
     var parameters = const <ParameterElement>[];
@@ -3246,6 +3242,16 @@
     }
   }
 
+  Future<void> _addFix_replaceVarWithDynamic() async {
+    // TODO(brianwilkerson) Determine whether this await is necessary.
+    await null;
+    var changeBuilder = _newDartChangeBuilder();
+    await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
+      builder.addSimpleReplacement(range.error(error), 'dynamic');
+    });
+    _addFixFromBuilder(changeBuilder, DartFixKind.REPLACE_VAR_WITH_DYNAMIC);
+  }
+
   Future<void> _addFix_replaceWithConditionalAssignment() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
@@ -3301,16 +3307,6 @@
     }
   }
 
-  Future<void> _addFix_replaceWithRethrow() async {
-    if (coveredNode is ThrowExpression) {
-      var changeBuilder = _newDartChangeBuilder();
-      await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
-        builder.addSimpleReplacement(range.node(coveredNode), 'rethrow');
-      });
-      _addFixFromBuilder(changeBuilder, DartFixKind.USE_RETHROW);
-    }
-  }
-
   Future<void> _addFix_replaceWithIdentifier() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
@@ -3328,6 +3324,16 @@
     }
   }
 
+  Future<void> _addFix_replaceWithRethrow() async {
+    if (coveredNode is ThrowExpression) {
+      var changeBuilder = _newDartChangeBuilder();
+      await changeBuilder.addFileEdit(file, (DartFileEditBuilder builder) {
+        builder.addSimpleReplacement(range.node(coveredNode), 'rethrow');
+      });
+      _addFixFromBuilder(changeBuilder, DartFixKind.USE_RETHROW);
+    }
+  }
+
   Future<void> _addFix_replaceWithTearOff() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
@@ -3746,7 +3752,7 @@
     _addFixFromBuilder(changeBuilder, DartFixKind.ADD_FIELD_FORMAL_PARAMETERS);
   }
 
-  Future<void> _addFix_updateSdkConstraints() async {
+  Future<void> _addFix_updateSdkConstraints(String minimumVersion) async {
     Context context = resourceProvider.pathContext;
     File pubspecFile = null;
     Folder folder = resourceProvider.getFolder(context.dirname(file));
@@ -3774,13 +3780,13 @@
       length = spaceOffset;
     }
     if (text == 'any') {
-      newText = '^2.1.0';
+      newText = '^$minimumVersion';
     } else if (text.startsWith('^')) {
-      newText = '^2.1.0';
+      newText = '^$minimumVersion';
     } else if (text.startsWith('>=')) {
-      newText = '>=2.1.0';
+      newText = '>=$minimumVersion';
     } else if (text.startsWith('>')) {
-      newText = '>=2.1.0';
+      newText = '>=$minimumVersion';
     }
     if (newText == null) {
       return;
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 8475ef8..202f77f 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -329,6 +329,8 @@
   HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER,
   HintCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT,
   HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE,
+  HintCode.SDK_VERSION_SET_LITERAL,
+  HintCode.SDK_VERSION_UI_AS_CODE,
   HintCode.STRICT_RAW_TYPE,
   HintCode.SUBTYPE_OF_SEALED_CLASS,
   HintCode.TYPE_CHECK_IS_NOT_NULL,
diff --git a/pkg/analyzer/lib/src/dart/error/hint_codes.dart b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
index 39f9dee..950e5ea 100644
--- a/pkg/analyzer/lib/src/dart/error/hint_codes.dart
+++ b/pkg/analyzer/lib/src/dart/error/hint_codes.dart
@@ -550,6 +550,26 @@
           "Try either importing 'dart:async' or updating the SDK constraints.");
 
   /**
+   * A set literal is being used in code that is expected to run on versions of
+   * the SDK that did not support them.
+   */
+  static const HintCode SDK_VERSION_SET_LITERAL = const HintCode(
+      'SDK_VERSION_SET_LITERAL',
+      "Set literals were not supported until version 2.2, "
+      "but this code is required to be able to run on earlier versions.",
+      correction: "Try updating the SDK constraints.");
+
+  /**
+   * The for, if or spread element is being used in code that is expected to run
+   * on versions of the SDK that did not support them.
+   */
+  static const HintCode SDK_VERSION_UI_AS_CODE = const HintCode(
+      'SDK_VERSION_UI_AS_CODE',
+      "The for, if and spread elements were not supported until version 2.2.2, "
+      "but this code is required to be able to run on earlier versions.",
+      correction: "Try updating the SDK constraints.");
+
+  /**
    * This hint is generated anywhere where a `@sealed` class or mixin is used as
    * a super-type of a class.
    */
diff --git a/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart b/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart
index cc2231a..b7a9c21 100644
--- a/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart
+++ b/pkg/analyzer/lib/src/hint/sdk_constraint_verifier.dart
@@ -27,9 +27,26 @@
   final VersionConstraint _versionConstraint;
 
   /// A cached flag indicating whether references to Future and Stream need to
-  /// be checked. Use [] to access this field.
+  /// be checked. Use [checkFutureAndStream] to access this field.
   bool _checkFutureAndStream;
 
+  /// A cached flag indicating whether references to set literals need to
+  /// be checked. Use [checkSetLiterals] to access this field.
+  bool _checkSetLiterals;
+
+  /// A flag indicating whether we are visiting code inside a set literal. Used
+  /// to prevent over-reporting uses of set literals.
+  bool _inSetLiteral = false;
+
+  /// A cached flag indicating whether references to the ui-as-code features
+  /// need to be checked. Use [checkUiAsCode] to access this field.
+  bool _checkUiAsCode;
+
+  /// A flag indicating whether we are visiting code inside one of the
+  /// ui-as-code features. Used to prevent over-reporting uses of these
+  /// features.
+  bool _inUiAsCode = false;
+
   /// Initialize a newly created verifier to use the given [_errorReporter] to
   /// report errors.
   SdkConstraintVerifier(this._errorReporter, this._containingLibrary,
@@ -39,16 +56,62 @@
   VersionRange get before_2_1_0 =>
       new VersionRange(max: Version.parse('2.1.0'), includeMax: false);
 
+  /// Return a range covering every version up to, but not including, 2.2.0.
+  VersionRange get before_2_2_0 =>
+      new VersionRange(max: Version.parse('2.2.0'), includeMax: false);
+
+  /// Return a range covering every version up to, but not including, 2.2.2.
+  VersionRange get before_2_2_2 =>
+      new VersionRange(max: Version.parse('2.2.2'), includeMax: false);
+
   /// Return `true` if references to Future and Stream need to be checked.
   bool get checkFutureAndStream => _checkFutureAndStream ??=
       !before_2_1_0.intersect(_versionConstraint).isEmpty;
 
+  /// Return `true` if references to set literals need to be checked.
+  bool get checkSetLiterals =>
+      _checkSetLiterals ??= !before_2_2_0.intersect(_versionConstraint).isEmpty;
+
+  /// Return `true` if references to the ui-as-code features (control flow and
+  /// spread collections) need to be checked.
+  bool get checkUiAsCode =>
+      _checkUiAsCode ??= !before_2_2_2.intersect(_versionConstraint).isEmpty;
+
+  @override
+  void visitForElement(ForElement node) {
+    _validateUiAsCode(node);
+    bool wasInUiAsCode = _inUiAsCode;
+    _inUiAsCode = true;
+    super.visitForElement(node);
+    _inUiAsCode = wasInUiAsCode;
+  }
+
   @override
   void visitHideCombinator(HideCombinator node) {
     // Don't flag references to either `Future` or `Stream` within a combinator.
   }
 
   @override
+  void visitIfElement(IfElement node) {
+    _validateUiAsCode(node);
+    bool wasInUiAsCode = _inUiAsCode;
+    _inUiAsCode = true;
+    super.visitIfElement(node);
+    _inUiAsCode = wasInUiAsCode;
+  }
+
+  @override
+  void visitSetOrMapLiteral(SetOrMapLiteral node) {
+    if (node.isSet && checkSetLiterals && !_inSetLiteral) {
+      _errorReporter.reportErrorForNode(HintCode.SDK_VERSION_SET_LITERAL, node);
+    }
+    bool wasInSetLiteral = _inSetLiteral;
+    _inSetLiteral = true;
+    super.visitSetOrMapLiteral(node);
+    _inSetLiteral = wasInSetLiteral;
+  }
+
+  @override
   void visitShowCombinator(ShowCombinator node) {
     // Don't flag references to either `Future` or `Stream` within a combinator.
   }
@@ -75,4 +138,22 @@
           HintCode.SDK_VERSION_ASYNC_EXPORTED_FROM_CORE, node, [element.name]);
     }
   }
+
+  @override
+  void visitSpreadElement(SpreadElement node) {
+    _validateUiAsCode(node);
+    bool wasInUiAsCode = _inUiAsCode;
+    _inUiAsCode = true;
+    super.visitSpreadElement(node);
+    _inUiAsCode = wasInUiAsCode;
+  }
+
+  /// Given that the [node] is only valid when the ui-as-code feature is
+  /// enabled, check that the code will not be executed with a version of the
+  /// SDK that does not support the feature.
+  void _validateUiAsCode(AstNode node) {
+    if (checkUiAsCode && !_inUiAsCode) {
+      _errorReporter.reportErrorForNode(HintCode.SDK_VERSION_UI_AS_CODE, node);
+    }
+  }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart b/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart
new file mode 100644
index 0000000..81658b9
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/sdk_constraint_verifier_support.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, 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/error/error.dart';
+import 'package:pub_semver/pub_semver.dart';
+
+import '../dart/resolution/driver_resolution.dart';
+
+/// A base class designed to be used by tests of the hints produced by an
+/// SdkConstraintVerifier.
+class SdkConstraintVerifierTest extends DriverResolutionTest {
+  verifyVersion(String version, String source,
+      {List<ErrorCode> errorCodes}) async {
+    driver.configure(
+        analysisOptions: analysisOptions
+          ..sdkVersionConstraint = VersionConstraint.parse(version));
+    await assertErrorsInCode(source, errorCodes ?? []);
+  }
+}
diff --git a/pkg/analyzer/test/src/hint/sdk_version_async_exported_from_core_test.dart b/pkg/analyzer/test/src/diagnostics/sdk_version_async_exported_from_core_test.dart
similarity index 94%
rename from pkg/analyzer/test/src/hint/sdk_version_async_exported_from_core_test.dart
rename to pkg/analyzer/test/src/diagnostics/sdk_version_async_exported_from_core_test.dart
index a9f42b6..7e04d6d 100644
--- a/pkg/analyzer/test/src/hint/sdk_version_async_exported_from_core_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/sdk_version_async_exported_from_core_test.dart
@@ -5,7 +5,7 @@
 import 'package:analyzer/src/dart/error/hint_codes.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import 'sdk_constraint_verifier.dart';
+import 'sdk_constraint_verifier_support.dart';
 
 main() {
   defineReflectiveSuite(() {
@@ -32,7 +32,7 @@
   }
 
   test_equals_explicitImportOfExportingLibrary() async {
-    addNamedSource('/exporter.dart', '''
+    newFile('/test/lib/exporter.dart', content: '''
 export 'dart:async';
 ''');
     await verifyVersion('2.1.0', '''
@@ -76,7 +76,7 @@
   }
 
   test_lessThan_explicitImportOfExportingLibrary() async {
-    addNamedSource('/exporter.dart', '''
+    newFile('/test/lib/exporter.dart', content: '''
 export 'dart:async';
 ''');
     await verifyVersion('2.0.0', '''
diff --git a/pkg/analyzer/test/src/diagnostics/sdk_version_set_literal_test.dart b/pkg/analyzer/test/src/diagnostics/sdk_version_set_literal_test.dart
new file mode 100644
index 0000000..4a7b294
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/sdk_version_set_literal_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2018, 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 'sdk_constraint_verifier_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SdkVersionSetLiteralTest);
+  });
+}
+
+@reflectiveTest
+class SdkVersionSetLiteralTest extends SdkConstraintVerifierTest {
+  test_equals() async {
+    await verifyVersion('2.2.0', '''
+Set<int> zero() => <int>{0};
+''');
+  }
+
+  test_greaterThan() async {
+    await verifyVersion('2.3.0', '''
+Set<int> zero() => <int>{0};
+''');
+  }
+
+  test_lessThan() async {
+    await verifyVersion('2.1.0', '''
+Set<int> zero() => <int>{0};
+''', errorCodes: [HintCode.SDK_VERSION_SET_LITERAL]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/sdk_version_ui_as_code_test.dart b/pkg/analyzer/test/src/diagnostics/sdk_version_ui_as_code_test.dart
new file mode 100644
index 0000000..d14a9a2
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/sdk_version_ui_as_code_test.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2018, 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/analysis/experiments.dart';
+import 'package:analyzer/src/dart/error/hint_codes.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'sdk_constraint_verifier_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SdkVersionUiAsCodeTest);
+  });
+}
+
+@reflectiveTest
+class SdkVersionUiAsCodeTest extends SdkConstraintVerifierTest {
+  @override
+  AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
+    ..enabledExperiments = [
+      EnableString.control_flow_collections,
+      EnableString.spread_collections
+    ];
+
+  test_equals() async {
+    await verifyVersion('2.2.2', '''
+List<int> zero() => [for (var e in [0]) e];
+''');
+  }
+
+  test_greaterThan() async {
+    await verifyVersion('2.3.0', '''
+List<int> zero() => [...[0]];
+''');
+  }
+
+  test_lessThan() async {
+    await verifyVersion('2.2.1', '''
+List<int> zero() => [if (0 < 1) 0];
+''', errorCodes: [HintCode.SDK_VERSION_UI_AS_CODE]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index e9f000b..1d6030e 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -41,6 +41,10 @@
 import 'must_call_super_test.dart' as must_call_super;
 import 'non_bool_condition_test.dart' as non_bool_condition;
 import 'non_constant_map_element_test.dart' as non_constant_map_element;
+import 'sdk_version_async_exported_from_core_test.dart'
+    as sdk_version_async_exported_from_core;
+import 'sdk_version_set_literal_test.dart' as sdk_version_set_literal;
+import 'sdk_version_ui_as_code_test.dart' as sdk_version_ui_as_code;
 import 'set_element_type_not_assignable_test.dart'
     as set_element_type_not_assignable;
 import 'subtype_of_sealed_class_test.dart' as subtype_of_sealed_class;
@@ -101,6 +105,9 @@
     must_call_super.main();
     non_bool_condition.main();
     non_constant_map_element.main();
+    sdk_version_async_exported_from_core.main();
+    sdk_version_set_literal.main();
+    sdk_version_ui_as_code.main();
     set_element_type_not_assignable.main();
     subtype_of_sealed_class.main();
     top_level_instance_getter.main();
diff --git a/pkg/analyzer/test/src/hint/sdk_constraint_verifier.dart b/pkg/analyzer/test/src/hint/sdk_constraint_verifier.dart
deleted file mode 100644
index ad910f0..0000000
--- a/pkg/analyzer/test/src/hint/sdk_constraint_verifier.dart
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2018, 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/error/error.dart';
-import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
-import 'package:pub_semver/pub_semver.dart';
-
-import '../../generated/resolver_test_case.dart';
-import '../../generated/test_support.dart';
-
-/// A base class designed to be used by tests of the hints produced by an
-/// SdkConstraintVerifier.
-class SdkConstraintVerifierTest extends ResolverTestCase {
-  bool get enableNewAnalysisDriver => true;
-
-  verifyVersion(String version, String source,
-      {List<ErrorCode> errorCodes}) async {
-    driver.configure(
-        analysisOptions: AnalysisOptionsImpl()
-          ..sdkVersionConstraint = VersionConstraint.parse(version));
-
-    TestAnalysisResult result = await computeTestAnalysisResult(source);
-    GatheringErrorListener listener = new GatheringErrorListener();
-    listener.addAll(result.errors);
-    if (errorCodes == null) {
-      listener.assertNoErrors();
-    } else {
-      listener.assertErrorsWithCodes(errorCodes);
-    }
-  }
-}
diff --git a/pkg/analyzer/test/src/hint/test_all.dart b/pkg/analyzer/test/src/hint/test_all.dart
index 0234ba4..0933671 100644
--- a/pkg/analyzer/test/src/hint/test_all.dart
+++ b/pkg/analyzer/test/src/hint/test_all.dart
@@ -5,12 +5,9 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'sdk_constraint_extractor_test.dart' as sdk_constraint_extractor;
-import 'sdk_version_async_exported_from_core_test.dart'
-    as sdk_version_async_exported_from_core;
 
 main() {
   defineReflectiveSuite(() {
     sdk_constraint_extractor.main();
-    sdk_version_async_exported_from_core.main();
   }, name: 'hint');
 }