Version 2.16.0-9.0.dev
Merge commit '11dc2f4ff684e4c0d9bd874957a03978d657fb06' into 'dev'
diff --git a/pkg/analysis_server/benchmark/benchmarks.dart b/pkg/analysis_server/benchmark/benchmarks.dart
index c6b391f..0d3904b 100644
--- a/pkg/analysis_server/benchmark/benchmarks.dart
+++ b/pkg/analysis_server/benchmark/benchmarks.dart
@@ -241,8 +241,14 @@
if (benchmark is FlutterBenchmark) {
if (flutterRepository != null) {
- (benchmark as FlutterBenchmark).flutterRepositoryPath =
- flutterRepository;
+ if (path.isAbsolute(flutterRepository) &&
+ path.normalize(flutterRepository) == flutterRepository) {
+ (benchmark as FlutterBenchmark).flutterRepositoryPath =
+ flutterRepository;
+ } else {
+ print('The path must be absolute and normalized: $flutterRepository');
+ exit(1);
+ }
} else {
print('The option --flutter-repository is required to '
"run '$benchmarkId'.");
diff --git a/pkg/analysis_server/benchmark/perf/flutter_completion_benchmark.dart b/pkg/analysis_server/benchmark/perf/flutter_completion_benchmark.dart
index e2bac08..78497cb 100644
--- a/pkg/analysis_server/benchmark/perf/flutter_completion_benchmark.dart
+++ b/pkg/analysis_server/benchmark/perf/flutter_completion_benchmark.dart
@@ -77,8 +77,10 @@
// time analyzing, and do apply the filter.
// Total number of suggestions: 2322.
// Filtered to: 82.
+ // Long name: completion-smallFile-body
+ var name = 'completion-1';
result.add(
- 'completion-smallFile-body',
+ name,
BenchMarkResult(
'micros',
await _completionTiming(
@@ -86,7 +88,7 @@
filePath: '$flutterPkgPath/lib/src/material/flutter_logo.dart',
uniquePrefix: 'Widget build(BuildContext context) {',
insertStringGenerator: () => 'M',
- name: 'completion-smallFile-body',
+ name: name,
),
),
);
@@ -98,8 +100,10 @@
// JSON in the server, and deserializing on the client.
// Total number of suggestions: 2322.
// Filtered to: 2322.
+ // Long name: completion-smallFile-body-withoutPrefix
+ name = 'completion-2';
result.add(
- 'completion-smallFile-body-withoutPrefix',
+ name,
BenchMarkResult(
'micros',
await _completionTiming(
@@ -107,7 +111,7 @@
filePath: '$flutterPkgPath/lib/src/material/flutter_logo.dart',
uniquePrefix: 'Widget build(BuildContext context) {',
insertStringGenerator: null,
- name: 'completion-smallFile-body-withoutPrefix',
+ name: name,
),
),
);
@@ -118,8 +122,10 @@
// The target method body is small, so something could be optimized.
// Total number of suggestions: 4654.
// Filtered to: 182.
+ // Long name: completion-smallLibraryCycle-largeFile-smallBody
+ name = 'completion-3';
result.add(
- 'completion-smallLibraryCycle-largeFile-smallBody',
+ name,
BenchMarkResult(
'micros',
await _completionTiming(
@@ -127,7 +133,7 @@
filePath: '$flutterPkgPath/test/material/text_field_test.dart',
uniquePrefix: 'getOpacity(WidgetTester tester, Finder finder) {',
insertStringGenerator: () => 'M',
- name: 'completion-smallLibraryCycle-largeFile-smallBody',
+ name: name,
),
),
);
@@ -143,8 +149,10 @@
// TODO(scheglov) Remove the previous sentence when improved.
// Total number of suggestions: 3429.
// Filtered to: 133.
+ // Long name: completion-mediumLibraryCycle-mediumFile-smallBody
+ name = 'completion-4';
result.add(
- 'completion-mediumLibraryCycle-mediumFile-smallBody',
+ name,
BenchMarkResult(
'micros',
await _completionTiming(
@@ -152,7 +160,7 @@
filePath: '$flutterPkgPath/lib/src/material/app_bar.dart',
uniquePrefix: 'computeDryLayout(BoxConstraints constraints) {',
insertStringGenerator: () => 'M',
- name: 'completion-mediumLibraryCycle-mediumFile-smallBody',
+ name: name,
),
),
);
@@ -163,8 +171,10 @@
// cycle. This is expensive.
// Total number of suggestions: 1510.
// Filtered to: 0.
+ // Long name: completion-mediumLibraryCycle-mediumFile-api-parameterType
+ name = 'completion-5';
result.add(
- 'completion-mediumLibraryCycle-mediumFile-api-parameterType',
+ name,
BenchMarkResult(
'micros',
await _completionTiming(
@@ -172,7 +182,7 @@
filePath: '$flutterPkgPath/lib/src/material/app_bar.dart',
uniquePrefix: 'computeDryLayout(BoxConstraints',
insertStringGenerator: _IncrementingStringGenerator(),
- name: 'completion-mediumLibraryCycle-mediumFile-api-parameterType',
+ name: name,
),
),
);
diff --git a/pkg/analysis_server/lib/src/computer/computer_color.dart b/pkg/analysis_server/lib/src/computer/computer_color.dart
new file mode 100644
index 0000000..56352d1
--- /dev/null
+++ b/pkg/analysis_server/lib/src/computer/computer_color.dart
@@ -0,0 +1,361 @@
+// 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/utilities/flutter.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/constant/value.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/constant/value.dart' show GenericState;
+import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
+import 'package:analyzer/src/dart/element/type_system.dart';
+import 'package:analyzer/src/lint/linter.dart';
+import 'package:collection/collection.dart';
+
+/// Computer for dart:ui/Flutter Color references.
+class ColorComputer {
+ final ResolvedUnitResult resolvedUnit;
+ final LinterContext _linterContext;
+ final List<ColorReference> _colors = [];
+ final Flutter _flutter = Flutter.instance;
+
+ ColorComputer(this.resolvedUnit)
+ : _linterContext = LinterContextImpl(
+ [], // unused
+ LinterContextUnit(resolvedUnit.content, resolvedUnit.unit),
+ resolvedUnit.session.declaredVariables,
+ resolvedUnit.typeProvider,
+ resolvedUnit.typeSystem as TypeSystemImpl,
+ InheritanceManager3(), // unused
+ resolvedUnit.session.analysisContext.analysisOptions,
+ null,
+ );
+
+ /// Returns information about the color references in [resolvedUnit].
+ ///
+ /// This method should only be called once for any instance of this class.
+ List<ColorReference> compute() {
+ final visitor = _ColorBuilder(this);
+ resolvedUnit.unit.accept(visitor);
+ return _colors;
+ }
+
+ /// Tries to add a color for the [expression].
+ ///
+ /// If [target] is supplied, will be used instead of [expression] allowing
+ /// a value to be read from the member [memberName] or from a swatch value
+ /// with index [index].
+ bool tryAddColor(
+ Expression expression, {
+ Expression? target,
+ String? memberName,
+ int? index,
+ }) {
+ if (!_isColor(expression.staticType)) return false;
+
+ target ??= expression;
+
+ // Try to evaluate the constant target.
+ final colorConstResult = _linterContext.evaluateConstant(target);
+ var colorConst = colorConstResult.value;
+ if (colorConstResult.errors.isNotEmpty || colorConst == null) return false;
+
+ // If we want a specific member or swatch index, read that.
+ if (memberName != null) {
+ colorConst = _getMember(colorConst, memberName);
+ } else if (index != null) {
+ colorConst = _getSwatchValue(colorConst, index);
+ }
+
+ return _tryRecordColor(expression, colorConst);
+ }
+
+ /// Tries to add a color for the instance creation [expression].
+ ///
+ /// This handles constructor calls that cannot be evaluated (for example
+ /// because they are not const) but are simple well-known dart:ui/Flutter
+ /// color constructors that we can manually parse.
+ bool tryAddKnownColorConstructor(InstanceCreationExpression expression) {
+ if (!_isColor(expression.staticType)) return false;
+
+ final constructor = expression.constructorName;
+ final staticElement = constructor.staticElement;
+ final classElement = staticElement?.enclosingElement;
+ final className = classElement?.name;
+ final constructorName = constructor.name?.name;
+ final constructorArgs = expression.argumentList.arguments
+ .map((e) => e is Literal ? e : null)
+ .toList();
+
+ int? colorValue;
+ if (_isDartUi(classElement) && className == 'Color') {
+ colorValue = _getDartUiColorValue(constructorName, constructorArgs);
+ } else if (_isFlutterPainting(classElement) && className == 'ColorSwatch') {
+ colorValue =
+ _getFlutterSwatchColorValue(constructorName, constructorArgs);
+ } else if (_isFlutterMaterial(classElement) &&
+ className == 'MaterialAccentColor') {
+ colorValue =
+ _getFlutterMaterialAccentColorValue(constructorName, constructorArgs);
+ }
+
+ return _tryRecordColorValue(expression, colorValue);
+ }
+
+ /// Creates a [ColorInformation] by extracting the argb values from
+ /// [value] encoded as 0xAARRGGBB as in the dart:ui Color class.
+ ColorInformation _colorInformationForColorValue(int value) {
+ // Extract color information according to dart:ui Color values.
+ final alpha = (0xff000000 & value) >> 24;
+ final red = (0x00ff0000 & value) >> 16;
+ final blue = (0x000000ff & value) >> 0;
+ final green = (0x0000ff00 & value) >> 8;
+
+ return ColorInformation(alpha, red, green, blue);
+ }
+
+ /// Extracts the integer color value from the dart:ui Color constant [color].
+ int? _colorValueForColorConst(DartObject? color) {
+ if (color == null || color.isNull) return null;
+
+ // If the object has a "color" field, walk down to that, because some colors
+ // like CupertinoColors have a "value=0" with an overridden getter that
+ // would always result in a value representing black.
+ color = color.getFieldFromHierarchy('color') ?? color;
+
+ return color.getFieldFromHierarchy('value')?.toIntValue();
+ }
+
+ /// Converts ARGB values into a single int value as 0xAARRGGBB as used by
+ /// the dart:ui Color class.
+ int _colorValueForComponents(int alpha, int red, int green, int blue) {
+ return (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+ }
+
+ /// Extracts the color value from dart:ui Color constructor args.
+ int? _getDartUiColorValue(String? name, List<Literal?> args) {
+ if (name == null && args.length == 1) {
+ final arg0 = args[0];
+ return arg0 is IntegerLiteral ? arg0.value : null;
+ } else if (name == 'fromARGB' && args.length == 4) {
+ final arg0 = args[0];
+ final arg1 = args[1];
+ final arg2 = args[2];
+ final arg3 = args[3];
+
+ final alpha = arg0 is IntegerLiteral ? arg0.value : null;
+ final red = arg1 is IntegerLiteral ? arg1.value : null;
+ final green = arg2 is IntegerLiteral ? arg2.value : null;
+ final blue = arg3 is IntegerLiteral ? arg3.value : null;
+
+ return alpha != null && red != null && green != null && blue != null
+ ? _colorValueForComponents(alpha, red, green, blue)
+ : null;
+ } else if (name == 'fromRGBO' && args.length == 4) {
+ final arg0 = args[0];
+ final arg1 = args[1];
+ final arg2 = args[2];
+ final arg3 = args[3];
+
+ final red = arg0 is IntegerLiteral ? arg0.value : null;
+ final green = arg1 is IntegerLiteral ? arg1.value : null;
+ final blue = arg2 is IntegerLiteral ? arg2.value : null;
+ final opacity = arg3 is IntegerLiteral
+ ? arg3.value
+ : arg3 is DoubleLiteral
+ ? arg3.value
+ : null;
+ final alpha = opacity != null ? (opacity * 255).toInt() : null;
+
+ return alpha != null && red != null && green != null && blue != null
+ ? _colorValueForComponents(alpha, red, green, blue)
+ : null;
+ }
+ }
+
+ /// Extracts the color value from Flutter MaterialAccentColor constructor args.
+ int? _getFlutterMaterialAccentColorValue(String? name, List<Literal?> args) =>
+ // MaterialAccentColor is a subclass of SwatchColor and has the same
+ // constructor.
+ _getFlutterSwatchColorValue(name, args);
+
+ /// Extracts the color value from Flutter ColorSwatch constructor args.
+ int? _getFlutterSwatchColorValue(String? name, List<Literal?> args) {
+ if (name == null && args.isNotEmpty) {
+ final arg0 = args[0];
+ return arg0 is IntegerLiteral ? arg0.value : null;
+ }
+ }
+
+ /// Extracts a named member from a color.
+ ///
+ /// Well-known getters like `shade500` will be mapped onto the swatch value
+ /// with a matching index.
+ DartObject? _getMember(DartObject target, String memberName) {
+ final colorValue = target.getFieldFromHierarchy(memberName);
+ if (colorValue != null) {
+ return colorValue;
+ }
+
+ // If we didn't get a value but it's a getter we know how to read from a
+ // swatch, try that.
+ if (memberName.startsWith('shade')) {
+ final shadeNumber = int.tryParse(memberName.substring(5));
+ if (shadeNumber != null) {
+ return _getSwatchValue(target, shadeNumber);
+ }
+ }
+ }
+
+ /// Extracts a specific shade index from a Flutter SwatchColor.
+ DartObject? _getSwatchValue(DartObject target, int swatchValue) {
+ final swatch = target.getFieldFromHierarchy('_swatch')?.toMapValue();
+ if (swatch == null) return null;
+
+ final key = swatch.keys.firstWhereOrNull(
+ (key) => key?.toIntValue() == swatchValue,
+ );
+ if (key == null) return null;
+
+ return swatch[key];
+ }
+
+ /// Checks whether [type] is - or extends - the dart:ui Color class.
+ bool _isColor(DartType? type) => type != null && _flutter.isColor(type);
+
+ /// Checks whether this elements library is dart:ui.
+ bool _isDartUi(Element? element) => element?.library?.name == 'dart.ui';
+
+ /// Checks whether this elements library is Flutter Material colors.
+ bool _isFlutterMaterial(Element? element) =>
+ element?.library?.identifier ==
+ 'package:flutter/src/material/colors.dart';
+
+ /// Checks whether this elements library is Flutter Painting colors.
+ bool _isFlutterPainting(Element? element) =>
+ element?.library?.identifier ==
+ 'package:flutter/src/painting/colors.dart';
+
+ /// Tries to record a color value from [colorConst] for [expression].
+ ///
+ /// Returns whether a valid color was found and recorded.
+ bool _tryRecordColor(Expression expression, DartObject? colorConst) =>
+ _tryRecordColorValue(expression, _colorValueForColorConst(colorConst));
+
+ /// Tries to record the [colorValue] for [expression].
+ ///
+ /// Returns whether a valid color was found and recorded.
+ bool _tryRecordColorValue(Expression expression, int? colorValue) {
+ if (colorValue == null) return false;
+
+ // Build color information from the Color value.
+ final color = _colorInformationForColorValue(colorValue);
+
+ // Record the color against the original entire expression.
+ _colors.add(ColorReference(expression.offset, expression.length, color));
+ return true;
+ }
+}
+
+/// Information about a color that is present in a document.
+class ColorInformation {
+ final int alpha;
+ final int red;
+ final int green;
+ final int blue;
+
+ ColorInformation(this.alpha, this.red, this.green, this.blue);
+}
+
+/// Information about a specific known location of a [ColorInformation]
+/// reference in a document.
+class ColorReference {
+ final int offset;
+ final int length;
+ final ColorInformation color;
+
+ ColorReference(this.offset, this.length, this.color);
+}
+
+class _ColorBuilder extends RecursiveAstVisitor<void> {
+ final ColorComputer computer;
+
+ _ColorBuilder(this.computer);
+
+ @override
+ void visitIndexExpression(IndexExpression node) {
+ // Colors.redAccent[500].
+ final index = node.index;
+ final indexValue = index is IntegerLiteral ? index.value : null;
+ if (indexValue != null) {
+ if (computer.tryAddColor(
+ node,
+ target: node.realTarget,
+ index: indexValue,
+ )) {
+ return;
+ }
+ }
+ super.visitIndexExpression(node);
+ }
+
+ @override
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
+ // Usually we return after finding a color, but constructors can
+ // have nested colors in their arguments so we walk all the way down.
+ if (!computer.tryAddColor(node)) {
+ // If we couldn't evaluate the constant, try the well-known color
+ // constructors for dart:ui/Flutter.
+ computer.tryAddKnownColorConstructor(node);
+ }
+
+ super.visitInstanceCreationExpression(node);
+ }
+
+ @override
+ void visitPrefixedIdentifier(PrefixedIdentifier node) {
+ // Try the whole node as a constant (eg. `MyThemeClass.staticField`).
+ if (computer.tryAddColor(node)) {
+ return;
+ }
+
+ // Try a field of a static, (eg. `const MyThemeClass().instanceField`).
+ if (computer.tryAddColor(
+ node,
+ target: node.prefix,
+ memberName: node.identifier.name,
+ )) {
+ return;
+ }
+
+ super.visitPrefixedIdentifier(node);
+ }
+
+ @override
+ void visitPropertyAccess(PropertyAccess node) {
+ // Handle things like CupterinoColors.activeBlue.darkColor where we can't
+ // evaluate the whole expression, but can evaluate CupterinoColors.activeBlue
+ // and read the darkColor.
+ if (computer.tryAddColor(
+ node,
+ target: node.realTarget,
+ memberName: node.propertyName.name,
+ )) {
+ return;
+ }
+
+ super.visitPropertyAccess(node);
+ }
+}
+
+extension _DartObjectExtensions on DartObject {
+ /// Reads the value of the field [field] from this object.
+ ///
+ /// If the field is not found, recurses up the super classes.
+ DartObject? getFieldFromHierarchy(String fieldName) =>
+ getField(fieldName) ??
+ getField(GenericState.SUPERCLASS_FIELD)?.getFieldFromHierarchy(fieldName);
+}
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/cupertino.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/cupertino.dart
new file mode 100644
index 0000000..828a456
--- /dev/null
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/cupertino.dart
@@ -0,0 +1,5 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export 'src/cupertino/colors.dart';
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/material.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/material.dart
index 466ed8e..a1cecbd 100644
--- a/pkg/analysis_server/test/mock_packages/flutter/lib/material.dart
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/material.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
export 'src/material/app_bar.dart';
+export 'src/material/colors.dart';
export 'src/material/icons.dart';
export 'src/material/scaffold.dart';
export 'widgets.dart';
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/painting.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/painting.dart
index a3323f6..6eec7c5 100644
--- a/pkg/analysis_server/test/mock_packages/flutter/lib/painting.dart
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/painting.dart
@@ -5,6 +5,7 @@
export 'src/painting/alignment.dart';
export 'src/painting/basic_types.dart';
export 'src/painting/box_decoration.dart';
+export 'src/painting/colors.dart';
export 'src/painting/decoration.dart';
export 'src/painting/edge_insets.dart';
export 'src/painting/text_painter.dart';
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/src/cupertino/colors.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/src/cupertino/colors.dart
new file mode 100644
index 0000000..62356d5
--- /dev/null
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/src/cupertino/colors.dart
@@ -0,0 +1,102 @@
+// Copyright 2021 The Chromium Authors. 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:flutter/painting.dart';
+
+class CupertinoColors {
+ CupertinoColors._();
+
+ static const CupertinoDynamicColor activeBlue = systemBlue;
+
+ static const Color black = Color(0xFF000000);
+ static const Color white = Color(0xFFFFFFFF);
+
+ static const CupertinoDynamicColor systemBlue =
+ CupertinoDynamicColor.withBrightnessAndContrast(
+ color: Color.fromARGB(255, 0, 0, 0xFF),
+ darkColor: Color.fromARGB(255, 0, 0, 0x99),
+ highContrastColor: Color.fromARGB(255, 0, 0, 0x66),
+ darkHighContrastColor: Color.fromARGB(255, 0, 0, 0x33),
+ );
+}
+
+class CupertinoDynamicColor extends Color {
+ const CupertinoDynamicColor({
+ Color color,
+ Color darkColor,
+ Color highContrastColor,
+ Color darkHighContrastColor,
+ Color elevatedColor,
+ Color darkElevatedColor,
+ Color highContrastElevatedColor,
+ Color darkHighContrastElevatedColor,
+ }) : this._(
+ color,
+ color,
+ darkColor,
+ highContrastColor,
+ darkHighContrastColor,
+ elevatedColor,
+ darkElevatedColor,
+ highContrastElevatedColor,
+ darkHighContrastElevatedColor,
+ null,
+ );
+
+ const CupertinoDynamicColor.withBrightnessAndContrast({
+ Color color,
+ Color darkColor,
+ Color highContrastColor,
+ Color darkHighContrastColor,
+ }) : this(
+ color: color,
+ darkColor: darkColor,
+ highContrastColor: highContrastColor,
+ darkHighContrastColor: darkHighContrastColor,
+ elevatedColor: color,
+ darkElevatedColor: darkColor,
+ highContrastElevatedColor: highContrastColor,
+ darkHighContrastElevatedColor: darkHighContrastColor,
+ );
+
+ const CupertinoDynamicColor.withBrightness({
+ Color color,
+ Color darkColor,
+ }) : this(
+ color: color,
+ darkColor: darkColor,
+ highContrastColor: color,
+ darkHighContrastColor: darkColor,
+ elevatedColor: color,
+ darkElevatedColor: darkColor,
+ highContrastElevatedColor: color,
+ darkHighContrastElevatedColor: darkColor,
+ );
+
+ const CupertinoDynamicColor._(
+ this._effectiveColor,
+ this.color,
+ this.darkColor,
+ this.highContrastColor,
+ this.darkHighContrastColor,
+ this.elevatedColor,
+ this.darkElevatedColor,
+ this.highContrastElevatedColor,
+ this.darkHighContrastElevatedColor,
+ ) : super(0);
+
+ final Color _effectiveColor;
+
+ @override
+ int get value => _effectiveColor.value;
+
+ final Color color;
+ final Color darkColor;
+ final Color highContrastColor;
+ final Color darkHighContrastColor;
+ final Color elevatedColor;
+ final Color darkElevatedColor;
+ final Color highContrastElevatedColor;
+ final Color darkHighContrastElevatedColor;
+}
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/src/material/colors.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/src/material/colors.dart
new file mode 100644
index 0000000..803f7a5
--- /dev/null
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/src/material/colors.dart
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors. 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:flutter/painting.dart';
+
+class MaterialColor extends ColorSwatch<int> {
+ const MaterialColor(int primary, Map<int, Color> swatch)
+ : super(primary, swatch);
+
+ Color get shade100 => this[100];
+ Color get shade200 => this[200];
+ Color get shade300 => this[300];
+ Color get shade400 => this[400];
+ Color get shade50 => this[50];
+ Color get shade500 => this[500];
+ Color get shade600 => this[600];
+ Color get shade700 => this[700];
+ Color get shade800 => this[800];
+ Color get shade900 => this[900];
+}
+
+class MaterialAccentColor extends ColorSwatch<int> {
+ const MaterialAccentColor(int primary, Map<int, Color> swatch)
+ : super(primary, swatch);
+
+ Color get shade50 => this[50];
+ Color get shade100 => this[100];
+ Color get shade200 => this[200];
+ Color get shade400 => this[400];
+ Color get shade700 => this[700];
+}
+
+class Colors {
+ Colors._();
+
+ static const Color black = Color(0xFF000000);
+ static const Color white = Color(0xFFFFFFFF);
+
+ static const MaterialColor red = MaterialColor(
+ _redPrimaryValue,
+ <int, Color>{
+ // For simpler testing, these values are not the real Flutter values
+ // but just varying alphas on a primary value.
+ 50: Color(0x05FF0000),
+ 100: Color(0x10FF0000),
+ 200: Color(0x20FF0000),
+ 300: Color(0x30FF0000),
+ 400: Color(0x40FF0000),
+ 500: Color(0x50FF0000),
+ 600: Color(0x60FF0000),
+ 700: Color(0x70FF0000),
+ 800: Color(0x80FF0000),
+ 900: Color(0x90FF0000),
+ },
+ );
+ static const int _redPrimaryValue = 0xFFFF0000;
+
+ static const MaterialAccentColor redAccent = MaterialAccentColor(
+ _redAccentValue,
+ <int, Color>{
+ // For simpler testing, these values are not the real Flutter values
+ // but just varying alphas on a primary value.
+ 100: Color(0x10FFAA00),
+ 200: Color(0x20FFAA00),
+ 400: Color(0x40FFAA00),
+ 700: Color(0x70FFAA00),
+ },
+ );
+ static const int _redAccentValue = 0xFFFFAA00;
+}
diff --git a/pkg/analysis_server/test/mock_packages/flutter/lib/src/painting/colors.dart b/pkg/analysis_server/test/mock_packages/flutter/lib/src/painting/colors.dart
new file mode 100644
index 0000000..f0dbfae
--- /dev/null
+++ b/pkg/analysis_server/test/mock_packages/flutter/lib/src/painting/colors.dart
@@ -0,0 +1,16 @@
+// Copyright 2021 The Chromium Authors. 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:flutter/foundation.dart';
+import 'package:flutter/painting.dart';
+
+@immutable
+class ColorSwatch<T> extends Color {
+ const ColorSwatch(int primary, this._swatch) : super(primary);
+
+ @protected
+ final Map<T, Color> _swatch;
+
+ Color operator [](T index) => _swatch[index];
+}
diff --git a/pkg/analysis_server/test/src/computer/color_computer_test.dart b/pkg/analysis_server/test/src/computer/color_computer_test.dart
new file mode 100644
index 0000000..190f0d9
--- /dev/null
+++ b/pkg/analysis_server/test/src/computer/color_computer_test.dart
@@ -0,0 +1,343 @@
+// 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/computer/computer_color.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/diagnostic/diagnostic.dart';
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../abstract_context.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ColorComputerTest);
+ });
+}
+
+@reflectiveTest
+class ColorComputerTest extends AbstractContextTest {
+ /// A map of Dart source code that represents different types/formats
+ /// that are valid in const contexts.
+ ///
+ /// Values are the color that should be discovered (in 0xAARRGGBB format).
+ ///
+ /// Color values may not match the actual Flutter framework but are
+ /// values that are more identifyable for ease of testing. They are
+ /// defined in:
+ /// - test/mock_packages/flutter/lib/src/material/colors.dart.
+ /// - test/mock_packages/flutter/lib/src/cupertino/colors.dart.
+ ///
+ /// These values will be iterated in tests and inserted into various
+ /// code snippets for testing.
+ static const colorCodesConst = {
+ // dart:ui Colors
+ 'Colors.white': 0xFFFFFFFF,
+ 'Color(0xFF0000FF)': 0xFF0000FF,
+ 'Color.fromARGB(255, 0, 0, 255)': 0xFF0000FF,
+ 'Color.fromRGBO(0, 0, 255, 1)': 0xFF0000FF,
+ // Flutter Painting
+ 'ColorSwatch(0xFF89ABCD, {})': 0xFF89ABCD,
+ // Flutter Material
+ 'Colors.red': 0xFFFF0000,
+ 'Colors.redAccent': 0xFFFFAA00,
+ 'MaterialAccentColor(0xFF89ABCD, {})': 0xFF89ABCD,
+ // Flutter Cupertino
+ 'CupertinoColors.black': 0xFF000000,
+ 'CupertinoColors.systemBlue': 0xFF0000FF,
+ 'CupertinoColors.activeBlue': 0xFF0000FF,
+ };
+
+ /// A map of Dart source code that represents different types/formats
+ /// that are not valid in const contexts.
+ ///
+ /// Values are the color that should be discovered (in 0xAARRGGBB format).
+ static const colorCodesNonConst = {
+ // Flutter Material
+ 'Colors.red.shade100': 0x10FF0000,
+ 'Colors.red[100]': 0x10FF0000,
+ // Flutter Cupertino
+ 'CupertinoColors.systemBlue.color': 0xFF0000FF,
+ 'CupertinoColors.systemBlue.darkColor': 0xFF000099,
+ 'CupertinoColors.activeBlue.color': 0xFF0000FF,
+ 'CupertinoColors.activeBlue.darkColor': 0xFF000099,
+ 'CupertinoColors.activeBlue.highContrastColor': 0xFF000066,
+ 'CupertinoColors.activeBlue.darkHighContrastColor': 0xFF000033,
+ 'CupertinoColors.activeBlue.elevatedColor': 0xFF0000FF,
+ 'CupertinoColors.activeBlue.darkElevatedColor': 0xFF000099,
+ };
+
+ /// A map of Dart source code that creates multiple nested color references.
+ ///
+ /// The key is the source code, and the value is a map of the expressions and
+ /// colors that should be produced (where the null key represents the
+ /// entire expression).
+ static const colorCodesNested = {
+ // TODO(dantup): Remove this "const" when we can evaluate constructors
+ // in non-const contexts.
+ 'const CupertinoDynamicColor.withBrightness(color: CupertinoColors.white, darkColor: CupertinoColors.black)':
+ {
+ null: 0xFFFFFFFF,
+ 'CupertinoColors.white': 0xFFFFFFFF,
+ 'CupertinoColors.black': 0xFF000000,
+ },
+ };
+
+ late String testPath;
+ late String otherPath;
+
+ late ColorComputer computer;
+
+ /// Tests that all of the known color codes replaced into [code] produce the
+ /// expected nested color values.
+ ///
+ /// If [onlyConst] is `true`, only the test values that are const will be
+ /// tested.
+ Future<void> checkAllColors(String code, {bool onlyConst = false}) async {
+ // Combine the flat and nested colours into the same format.
+ final allColorCodes = <String, Map<String?, int>>{
+ ...colorCodesConst.map((key, value) => MapEntry(key, {key: value})),
+ if (!onlyConst)
+ ...colorCodesNonConst.map((key, value) => MapEntry(key, {key: value})),
+ ...colorCodesNested,
+ };
+
+ for (final entry in allColorCodes.entries) {
+ final colorDartCode = entry.key;
+ final expectedColorValues = entry.value.map(
+ // A null key means we should expect the full code.
+ (key, value) => MapEntry(key ?? colorDartCode, value),
+ );
+
+ await expectColors(
+ code.replaceAll('[[COLOR]]', colorDartCode),
+ expectedColorValues,
+ );
+ }
+ }
+
+ /// Checks that all of [expectedColorValues] are produced for [dartCode].
+ Future<void> expectColors(
+ String dartCode,
+ Map<String, int> expectedColorValues, {
+ String? otherCode,
+ }) async {
+ dartCode = _withCommonImports(dartCode);
+ otherCode = otherCode != null ? _withCommonImports(otherCode) : null;
+
+ newFile(testPath, content: dartCode);
+ if (otherCode != null) {
+ newFile(otherPath, content: otherCode);
+ final otherResult =
+ await session.getResolvedUnit(otherPath) as ResolvedUnitResult;
+ expectNoErrors(otherResult);
+ }
+ final result =
+ await session.getResolvedUnit(testPath) as ResolvedUnitResult;
+ expectNoErrors(result);
+
+ computer = ColorComputer(result);
+ final colors = computer.compute();
+
+ expect(
+ colors,
+ hasLength(expectedColorValues.length),
+ reason: '${expectedColorValues.length} colors should be detected in:\n'
+ '$dartCode',
+ );
+
+ expectedColorValues.entries.forEachIndexed((i, expectedColor) {
+ final color = colors[i];
+ final expectedColorCode = expectedColor.key;
+ final expectedColorValue = expectedColor.value;
+ final expectedAlpha = (0xff000000 & expectedColorValue) >> 24;
+ final expectedRed = (0x00ff0000 & expectedColorValue) >> 16;
+ final expectedGreen = (0x0000ff00 & expectedColorValue) >> 8;
+ final expectedBlue = (0x000000ff & expectedColorValue) >> 0;
+
+ final regionText =
+ dartCode.substring(color.offset, color.offset + color.length);
+ expect(
+ regionText,
+ equals(expectedColorCode),
+ reason: 'Color $i expected $expectedColorCode but was $regionText',
+ );
+
+ void expectComponent(int actual, int expected, String name) => expect(
+ actual,
+ expected,
+ reason: '$name value for $expectedColorCode is not correct',
+ );
+
+ expectComponent(color.color.alpha, expectedAlpha, 'Alpha');
+ expectComponent(color.color.red, expectedRed, 'Red');
+ expectComponent(color.color.green, expectedGreen, 'Green');
+ expectComponent(color.color.blue, expectedBlue, 'Blue');
+ });
+ }
+
+ void expectNoErrors(ResolvedUnitResult result) {
+ // If the test code has errors, generate a suitable failure to help debug.
+ final errors = result.errors
+ .where((error) => error.severity == Severity.error)
+ .toList();
+ if (errors.isNotEmpty) {
+ throw 'Code has errors: $errors\n\n${result.content}';
+ }
+ }
+
+ @override
+ void setUp() {
+ super.setUp();
+ writeTestPackageConfig(flutter: true);
+ testPath = convertPath('/home/test/lib/test.dart');
+ otherPath = convertPath('/home/test/lib/other_file.dart');
+ }
+
+ Future<void> test_collectionLiteral_const() async {
+ const testCode = '''
+main() {
+ const colors = [
+ [[COLOR]],
+ ];
+}
+''';
+ await checkAllColors(testCode, onlyConst: true);
+ }
+
+ Future<void> test_collectionLiteral_nonConst() async {
+ const testCode = '''
+main() {
+ final colors = [
+ [[COLOR]],
+ ];
+}
+''';
+ await checkAllColors(testCode);
+ }
+
+ Future<void> test_customClass() async {
+ const testCode = '''
+import 'other_file.dart';
+
+void main() {
+ final a1 = MyTheme.staticWhite;
+ final a2 = MyTheme.staticMaterialRedAccent;
+ const theme = MyTheme();
+ final b1 = theme.instanceWhite;
+ final b2 = theme.instanceMaterialRedAccent;
+}
+''';
+
+ const otherCode = '''
+class MyTheme {
+ static const Color staticWhite = Colors.white;
+ static const MaterialAccentColor staticMaterialRedAccent = Colors.redAccent;
+
+ final Color instanceWhite;
+ final MaterialAccentColor instanceMaterialRedAccent;
+
+ const MyTheme()
+ : instanceWhite = Colors.white,
+ instanceMaterialRedAccent = Colors.redAccent;
+}
+''';
+ await expectColors(
+ testCode,
+ {
+ 'MyTheme.staticWhite': 0xFFFFFFFF,
+ 'MyTheme.staticMaterialRedAccent': 0xFFFFAA00,
+ 'theme.instanceWhite': 0xFFFFFFFF,
+ 'theme.instanceMaterialRedAccent': 0xFFFFAA00,
+ },
+ otherCode: otherCode,
+ );
+ }
+
+ Future<void> test_local_const() async {
+ const testCode = '''
+main() {
+ const a = [[COLOR]];
+}
+''';
+ await checkAllColors(testCode, onlyConst: true);
+ }
+
+ Future<void> test_local_nonConst() async {
+ const testCode = '''
+main() {
+ final a = [[COLOR]];
+}
+''';
+ await checkAllColors(testCode);
+ }
+
+ Future<void> test_namedParameter_const() async {
+ const testCode = '''
+main() {
+ const w = Widget(color: [[COLOR]]);
+}
+
+class Widget {
+ final Color? color;
+ const Widget({this.color});
+}
+''';
+ await checkAllColors(testCode, onlyConst: true);
+ }
+
+ Future<void> test_namedParameter_nonConst() async {
+ const testCode = '''
+main() {
+ final w = Widget(color: [[COLOR]]);
+}
+
+class Widget {
+ final Color? color;
+ Widget({this.color});
+}
+''';
+ await checkAllColors(testCode);
+ }
+
+ Future<void> test_nested_const() async {
+ const testCode = '''
+main() {
+ const a = [[COLOR]];
+}
+''';
+ await checkAllColors(testCode, onlyConst: true);
+ }
+
+ Future<void> test_nested_nonConst() async {
+ const testCode = '''
+main() {
+ final a = [[COLOR]];
+}
+''';
+ await checkAllColors(testCode);
+ }
+
+ Future<void> test_topLevel_const() async {
+ const testCode = '''
+const a = [[COLOR]];
+''';
+ await checkAllColors(testCode, onlyConst: true);
+ }
+
+ Future<void> test_topLevel_nonConst() async {
+ const testCode = '''
+final a = [[COLOR]];
+''';
+ await checkAllColors(testCode);
+ }
+
+ String _withCommonImports(String code) => '''
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/painting.dart';
+import 'package:flutter/material.dart';
+
+$code''';
+}
diff --git a/pkg/analysis_server/test/src/computer/test_all.dart b/pkg/analysis_server/test/src/computer/test_all.dart
index 6f67c7a..af15c51 100644
--- a/pkg/analysis_server/test/src/computer/test_all.dart
+++ b/pkg/analysis_server/test/src/computer/test_all.dart
@@ -5,6 +5,7 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'closing_labels_computer_test.dart' as closing_labels_computer;
+import 'color_computer_test.dart' as color_computer;
import 'folding_computer_test.dart' as folding_computer;
import 'highlights_computer_test.dart' as highlights_computer;
import 'import_elements_computer_test.dart' as import_elements_computer;
@@ -15,6 +16,7 @@
void main() {
defineReflectiveSuite(() {
closing_labels_computer.main();
+ color_computer.main();
folding_computer.main();
highlights_computer.main();
import_elements_computer.main();
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 517eabc..767d937 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -25,7 +25,7 @@
static const _opaqueClassName = 'Opaque';
static const _ffiNativeName = 'FfiNative';
- static const List<String> _primitiveIntegerNativeTypes = [
+ static const Set<String> _primitiveIntegerNativeTypes = {
'Int8',
'Int16',
'Int32',
@@ -35,12 +35,12 @@
'Uint32',
'Uint64',
'IntPtr'
- ];
+ };
- static const List<String> _primitiveDoubleNativeTypes = [
+ static const Set<String> _primitiveDoubleNativeTypes = {
'Float',
'Double',
- ];
+ };
static const _primitiveBoolNativeType = 'Bool';
@@ -1516,13 +1516,7 @@
bool get isCompoundSubtype {
var element = name.staticElement;
if (element is ClassElement) {
- bool isCompound(InterfaceType? type) {
- return type != null && type.isCompound;
- }
-
- return isCompound(element.supertype) ||
- element.interfaces.any(isCompound) ||
- element.mixins.any(isCompound);
+ return element.allSupertypes.any((e) => e.isCompound);
}
return false;
}
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index afb6393..44f4d37 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -1030,8 +1030,22 @@
if (explicitTypeNeeded) {
var firstNeededType = neededTypes[0];
if (neededTypes.any((t) => t != firstNeededType)) {
- throw UnimplementedError(
- 'Different explicit types needed in multi-variable declaration');
+ // Different variables need different types. We handle this by
+ // introducing casts, which is not great but gets the job done.
+ for (int i = 0; i < node.variables.length; i++) {
+ if (neededTypes[i] != inferredTypes[i]) {
+ // We only have to worry about variables with initializers because
+ // variables without initializers will get the type `dynamic`.
+ var initializer = node.variables[i].initializer;
+ if (initializer != null) {
+ (_fixBuilder._getChange(initializer) as NodeChangeForExpression)
+ .addExpressionChange(
+ IntroduceAsChange(neededTypes[i], isDowncast: false),
+ AtomicEditInfo(
+ NullabilityFixDescription.otherCastExpression, {}));
+ }
+ }
+ }
} else {
(_fixBuilder._getChange(node) as NodeChangeForVariableDeclarationList)
.addExplicitType = firstNeededType;
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 69a8936..286aa4e 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -8952,6 +8952,24 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_var_with_different_types_becoming_explicit() async {
+ // When types need to be added to some variables in a declaration but not
+ // others, we handle it by introducing `as` casts.
+ var content = '''
+f(int i, String s) {
+ var x = i, y = s;
+ x = null;
+}
+''';
+ var expected = '''
+f(int i, String s) {
+ var x = i as int?, y = s;
+ x = null;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_weak_if_visit_weak_subexpression() async {
var content = '''
int f(int x, int/*?*/ y) {
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 1ae463a..cc93c1e 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -13099,13 +13099,9 @@
/**
* Allows access to all custom data attributes (data-*) set on this element.
*
- * The keys for the map must follow these rules:
- *
- * * The name must not begin with 'xml'.
- * * The name cannot contain a semi-colon (';').
- * * The name cannot contain any capital letters.
- *
- * Any keys from markup will be converted to camel-cased keys in the map.
+ * Any data attributes in the markup will be converted to camel-cased keys
+ * in the map based on [these conversion
+ * rules](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset).
*
* For example, HTML specified as:
*
@@ -13117,6 +13113,8 @@
*
* See also:
*
+ * * [HTML data-* attributes naming
+ restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*)
* * [Custom data
* attributes](http://dev.w3.org/html5/spec-preview/global-attributes.html#custom-data-attribute)
*/
diff --git a/tools/VERSION b/tools/VERSION
index 78db584..21d2a58 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 16
PATCH 0
-PRERELEASE 8
+PRERELEASE 9
PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate
index 8ced8e6..046addf 100644
--- a/tools/dom/templates/html/impl/impl_Element.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
@@ -756,13 +756,9 @@
/**
* Allows access to all custom data attributes (data-*) set on this element.
*
- * The keys for the map must follow these rules:
- *
- * * The name must not begin with 'xml'.
- * * The name cannot contain a semi-colon (';').
- * * The name cannot contain any capital letters.
- *
- * Any keys from markup will be converted to camel-cased keys in the map.
+ * Any data attributes in the markup will be converted to camel-cased keys
+ * in the map based on [these conversion
+ * rules](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset).
*
* For example, HTML specified as:
*
@@ -774,6 +770,8 @@
*
* See also:
*
+ * * [HTML data-* attributes naming
+ restrictions](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*)
* * [Custom data
* attributes](http://dev.w3.org/html5/spec-preview/global-attributes.html#custom-data-attribute)
*/