Version 2.11.0-237.0.dev
Merge commit 'f3962367a5e1bda1f77b2102557c14833862e558' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_generator.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_generator.dart
index 1214e8b..3bfb639 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_generator.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/value_generator.dart
@@ -51,6 +51,16 @@
return node.argumentList;
} else if (node is InstanceCreationExpression) {
return node.argumentList;
+ } else if (node is SimpleIdentifier) {
+ var parent = node.parent;
+ if (parent is ConstructorName) {
+ var grandparent = parent.parent;
+ if (grandparent is InstanceCreationExpression) {
+ return grandparent.argumentList;
+ }
+ } else if (parent is MethodInvocation && parent.methodName == node) {
+ return parent.argumentList;
+ }
} else if (node is TypeArgumentList) {
var parent = node.parent;
if (parent is InvocationExpression) {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
new file mode 100644
index 0000000..c26ea18
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart
@@ -0,0 +1,1835 @@
+// 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'data_driven_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(FlutterUseCaseTest);
+ });
+}
+
+@reflectiveTest
+class FlutterUseCaseTest extends DataDrivenFixProcessorTest {
+ @failingTest
+ Future<void>
+ test_cupertino_CupertinoDialog_toCupertinoAlertDialog_deprecated() async {
+ // This test fails because we don't rename the parameter to the constructor.
+ setPackageContent('''
+@deprecated
+class CupertinoDialog {
+ CupertinoDialog({String child}) {}
+}
+class CupertinoAlertDialog {
+ CupertinoAlertDialog({String content}) {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Replace with CupertinoAlertDialog'
+ date: 2020-09-24
+ bulkApply: false
+ element:
+ uris: ['$importUri']
+ class: 'CupertinoDialog'
+ changes:
+ - kind: 'rename'
+ newName: 'CupertinoAlertDialog'
+ - kind: 'renameParameter'
+ oldName: 'child'
+ newName: 'content'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ CupertinoDialog(child: 'x');
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ CupertinoAlertDialog(content: 'x');
+}
+''');
+ }
+
+ @failingTest
+ Future<void>
+ test_cupertino_CupertinoDialog_toCupertinoAlertDialog_removed() async {
+ // This test fails because we don't rename the parameter to the constructor.
+ setPackageContent('''
+class CupertinoAlertDialog {
+ CupertinoAlertDialog({String content}) {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Replace with CupertinoAlertDialog'
+ date: 2020-09-24
+ bulkApply: false
+ element:
+ uris: ['$importUri']
+ class: 'CupertinoDialog'
+ changes:
+ - kind: 'rename'
+ newName: 'CupertinoAlertDialog'
+ - kind: 'renameParameter'
+ oldName: 'child'
+ newName: 'content'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ CupertinoDialog(child: 'x');
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ CupertinoAlertDialog(content: 'x');
+}
+''', errorFilter: ignoreUnusedImport);
+ }
+
+ Future<void>
+ test_cupertino_CupertinoDialog_toCupertinoPopupSurface_deprecated() async {
+ setPackageContent('''
+@deprecated
+class CupertinoDialog {
+ CupertinoDialog({String child}) {}
+}
+class CupertinoPopupSurface {
+ CupertinoPopupSurface({String content}) {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Replace with CupertinoPopupSurface'
+ date: 2020-09-24
+ bulkApply: false
+ element:
+ uris: ['$importUri']
+ class: 'CupertinoDialog'
+ changes:
+ - kind: 'rename'
+ newName: 'CupertinoPopupSurface'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ CupertinoDialog(child: 'x');
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ CupertinoPopupSurface(child: 'x');
+}
+''');
+ }
+
+ Future<void>
+ test_cupertino_CupertinoDialog_toCupertinoPopupSurface_removed() async {
+ setPackageContent('''
+class CupertinoPopupSurface {
+ CupertinoPopupSurface({String content}) {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Replace with CupertinoPopupSurface'
+ date: 2020-09-24
+ bulkApply: false
+ element:
+ uris: ['$importUri']
+ class: 'CupertinoDialog'
+ changes:
+ - kind: 'rename'
+ newName: 'CupertinoPopupSurface'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ CupertinoDialog(child: 'x');
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ CupertinoPopupSurface(child: 'x');
+}
+''', errorFilter: ignoreUnusedImport);
+ }
+
+ Future<void>
+ test_cupertino_CupertinoTextThemeData_copyWith_deprecated() async {
+ setPackageContent('''
+class CupertinoTextThemeData {
+ copyWith({Color color, @deprecated Brightness brightness}) {}
+}
+class Color {}
+class Colors {
+ static Color blue = Color();
+}
+class Brightness {
+ static Brightness dark = Brightness();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Removed brightness'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'copyWith'
+ inClass: 'CupertinoTextThemeData'
+ changes:
+ - kind: 'removeParameter'
+ name: 'brightness'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(CupertinoTextThemeData data) {
+ data.copyWith(color: Colors.blue, brightness: Brightness.dark);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(CupertinoTextThemeData data) {
+ data.copyWith(color: Colors.blue);
+}
+''');
+ }
+
+ Future<void> test_cupertino_CupertinoTextThemeData_copyWith_removed() async {
+ setPackageContent('''
+class CupertinoTextThemeData {
+ copyWith({Color color}) {}
+}
+class Color {}
+class Colors {
+ static Color blue = Color();
+}
+class Brightness {
+ static Brightness dark = Brightness();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Removed brightness'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'copyWith'
+ inClass: 'CupertinoTextThemeData'
+ changes:
+ - kind: 'removeParameter'
+ name: 'brightness'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(CupertinoTextThemeData data) {
+ data.copyWith(color: Colors.blue, brightness: Brightness.dark);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(CupertinoTextThemeData data) {
+ data.copyWith(color: Colors.blue);
+}
+''');
+ }
+
+ Future<void>
+ test_cupertino_CupertinoTextThemeData_defaultConstructor_deprecated() async {
+ setPackageContent('''
+class CupertinoTextThemeData {
+ CupertinoTextThemeData({Color color, @deprecated Brightness brightness}) {}
+}
+class Color {}
+class Colors {
+ static Color blue = Color();
+}
+class Brightness {
+ static Brightness dark = Brightness();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Removed brightness'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: ''
+ inClass: 'CupertinoTextThemeData'
+ changes:
+ - kind: 'removeParameter'
+ name: 'brightness'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ CupertinoTextThemeData(color: Colors.blue, brightness: Brightness.dark);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ CupertinoTextThemeData(color: Colors.blue);
+}
+''');
+ }
+
+ Future<void>
+ test_cupertino_CupertinoTextThemeData_defaultConstructor_removed() async {
+ setPackageContent('''
+class CupertinoTextThemeData {
+ CupertinoTextThemeData({Color color}) {}
+}
+class Color {}
+class Colors {
+ static Color blue = Color();
+}
+class Brightness {
+ static Brightness dark = Brightness();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Removed brightness'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: ''
+ inClass: 'CupertinoTextThemeData'
+ changes:
+ - kind: 'removeParameter'
+ name: 'brightness'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ CupertinoTextThemeData(color: Colors.blue, brightness: Brightness.dark);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ CupertinoTextThemeData(color: Colors.blue);
+}
+''');
+ }
+
+ Future<void>
+ test_gestures_PointerEnterEvent_fromHoverEvent_deprecated() async {
+ setPackageContent('''
+class PointerEnterEvent {
+ @deprecated
+ PointerEnterEvent.fromHoverEvent(PointerHoverEvent event);
+ PointerEnterEvent.fromMouseEvent(PointerEvent event);
+}
+class PointerHoverEvent extends PointerEvent {}
+class PointerEvent {}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to fromMouseEvent'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: 'fromHoverEvent'
+ inClass: 'PointerEnterEvent'
+ changes:
+ - kind: 'rename'
+ newName: 'fromMouseEvent'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerEnterEvent.fromHoverEvent(event);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerEnterEvent.fromMouseEvent(event);
+}
+''');
+ }
+
+ Future<void> test_gestures_PointerEnterEvent_fromHoverEvent_removed() async {
+ setPackageContent('''
+class PointerEnterEvent {
+ PointerEnterEvent.fromMouseEvent(PointerEvent event);
+}
+class PointerHoverEvent extends PointerEvent {}
+class PointerEvent {}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to fromMouseEvent'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: 'fromHoverEvent'
+ inClass: 'PointerEnterEvent'
+ changes:
+ - kind: 'rename'
+ newName: 'fromMouseEvent'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerEnterEvent.fromHoverEvent(event);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerEnterEvent.fromMouseEvent(event);
+}
+''');
+ }
+
+ Future<void>
+ test_gestures_PointerExitEvent_fromHoverEvent_deprecated() async {
+ setPackageContent('''
+class PointerExitEvent {
+ @deprecated
+ PointerExitEvent.fromHoverEvent(PointerHoverEvent event);
+ PointerExitEvent.fromMouseEvent(PointerEvent event);
+}
+class PointerHoverEvent extends PointerEvent {}
+class PointerEvent {}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to fromMouseEvent'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: 'fromHoverEvent'
+ inClass: 'PointerExitEvent'
+ changes:
+ - kind: 'rename'
+ newName: 'fromMouseEvent'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerExitEvent.fromHoverEvent(event);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerExitEvent.fromMouseEvent(event);
+}
+''');
+ }
+
+ Future<void> test_gestures_PointerExitEvent_fromHoverEvent_removed() async {
+ setPackageContent('''
+class PointerExitEvent {
+ PointerExitEvent.fromMouseEvent(PointerEvent event);
+}
+class PointerHoverEvent extends PointerEvent {}
+class PointerEvent {}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to fromMouseEvent'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: 'fromHoverEvent'
+ inClass: 'PointerExitEvent'
+ changes:
+ - kind: 'rename'
+ newName: 'fromMouseEvent'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerExitEvent.fromHoverEvent(event);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(PointerHoverEvent event) {
+ PointerExitEvent.fromMouseEvent(event);
+}
+''');
+ }
+
+ Future<void>
+ test_material_Scaffold_resizeToAvoidBottomPadding_deprecated() async {
+ setPackageContent('''
+class Scaffold {
+ @deprecated
+ bool resizeToAvoidBottomPadding;
+ bool resizeToAvoidBottomInset;
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to resizeToAvoidBottomInset'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ field: 'resizeToAvoidBottomPadding'
+ inClass: 'Scaffold'
+ changes:
+ - kind: 'rename'
+ newName: 'resizeToAvoidBottomInset'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Scaffold scaffold) {
+ scaffold.resizeToAvoidBottomPadding;
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Scaffold scaffold) {
+ scaffold.resizeToAvoidBottomInset;
+}
+''');
+ }
+
+ Future<void>
+ test_material_Scaffold_resizeToAvoidBottomPadding_removed() async {
+ setPackageContent('''
+class Scaffold {
+ bool resizeToAvoidBottomInset;
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to resizeToAvoidBottomInset'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ field: 'resizeToAvoidBottomPadding'
+ inClass: 'Scaffold'
+ changes:
+ - kind: 'rename'
+ newName: 'resizeToAvoidBottomInset'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Scaffold scaffold) {
+ scaffold.resizeToAvoidBottomPadding;
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Scaffold scaffold) {
+ scaffold.resizeToAvoidBottomInset;
+}
+''');
+ }
+
+ @failingTest
+ Future<void> test_material_showDialog_deprecated() async {
+ // This test is failing because there is no way to specify that if a `child`
+ // was previously provided that a `builder` should be provided.
+ setPackageContent('''
+void showDialog({
+ @deprecated Widget child,
+ Widget Function(BuildContext) builder}) {}
+
+class Widget {}
+class BuildContext {}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Replace child with builder'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ function: 'showDialog'
+ changes:
+ - kind: 'addParameter'
+ index: 0
+ name: 'builder'
+ style: optional_named
+ argumentValue:
+ expression: '(context) => {% widget %}'
+ variables:
+ widget:
+ kind: argument
+ name: 'child'
+ - kind: 'removeParameter'
+ name: 'child'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Widget widget) {
+ showDialog(child: widget);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Widget widget) {
+ showDialog(builder: (context) => widget);
+}
+''');
+ }
+
+ @failingTest
+ Future<void> test_material_showDialog_removed() async {
+ // This test is failing because there is no way to specify that if a `child`
+ // was previously provided that a `builder` should be provided.
+ setPackageContent('''
+void showDialog({
+ @deprecated Widget child,
+ Widget Function(BuildContext) builder}) {}
+
+class Widget {}
+class BuildContext {}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Replace child with builder'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ function: 'showDialog'
+ changes:
+ - kind: 'addParameter'
+ index: 0
+ name: 'builder'
+ style: optional_named
+ argumentValue:
+ expression: '(context) => {% widget %}'
+ variables:
+ widget:
+ kind: argument
+ name: 'child'
+ - kind: 'removeParameter'
+ name: 'child'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Widget widget) {
+ showDialog(child: widget);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Widget widget) {
+ showDialog(builder: (context) => widget);
+}
+''');
+ }
+
+ Future<void> test_material_TextTheme_display4_deprecated() async {
+ setPackageContent('''
+class TextTheme {
+ @deprecated
+ int get display4 => 0;
+ int get headline1 => 0;
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to headline1'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ getter: display4
+ inClass: 'TextTheme'
+ changes:
+ - kind: 'rename'
+ newName: 'headline1'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(TextTheme theme) {
+ theme.display4;
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(TextTheme theme) {
+ theme.headline1;
+}
+''');
+ }
+
+ Future<void> test_material_TextTheme_display4_removed() async {
+ setPackageContent('''
+class TextTheme {
+ int get headline1 => 0;
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to headline1'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ getter: display4
+ inClass: 'TextTheme'
+ changes:
+ - kind: 'rename'
+ newName: 'headline1'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(TextTheme theme) {
+ theme.display4;
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(TextTheme theme) {
+ theme.headline1;
+}
+''');
+ }
+
+ Future<void> test_material_Typography_defaultConstructor_deprecated() async {
+ setPackageContent('''
+class Typography {
+ @deprecated
+ Typography();
+ Typography.material2014();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Use Typography.material2014'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: ''
+ inClass: 'Typography'
+ changes:
+ - kind: 'rename'
+ newName: 'material2014'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ Typography();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ Typography.material2014();
+}
+''');
+ }
+
+ Future<void> test_material_Typography_defaultConstructor_removed() async {
+ setPackageContent('''
+class Typography {
+ Typography.material2014();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Use Typography.material2014'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: ''
+ inClass: 'Typography'
+ changes:
+ - kind: 'rename'
+ newName: 'material2014'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f() {
+ Typography();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ Typography.material2014();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_BuildContext_ancestorInheritedElementForWidgetOfExactType_deprecated() async {
+ setPackageContent('''
+class BuildContext {
+ @deprecated
+ void ancestorInheritedElementForWidgetOfExactType(Type t) {}
+ void getElementForInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorInheritedElementForWidgetOfExactType'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'getElementForInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.ancestorInheritedElementForWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.getElementForInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_BuildContext_ancestorInheritedElementForWidgetOfExactType_removed() async {
+ setPackageContent('''
+class BuildContext {
+ void getElementForInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorInheritedElementForWidgetOfExactType'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'getElementForInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.ancestorInheritedElementForWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.getElementForInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_BuildContext_ancestorWidgetOfExactType_deprecated() async {
+ setPackageContent('''
+class BuildContext {
+ @deprecated
+ void ancestorWidgetOfExactType(Type t) {}
+ void findAncestorWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorWidgetOfExactType'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'findAncestorWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.ancestorWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.findAncestorWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_BuildContext_ancestorWidgetOfExactType_removed() async {
+ setPackageContent('''
+class BuildContext {
+ void findAncestorWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorWidgetOfExactType'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'findAncestorWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.ancestorWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.findAncestorWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void> test_widgets_BuildContext_inheritFromElement_deprecated() async {
+ setPackageContent('''
+class BuildContext {
+ @deprecated
+ void inheritFromElement() {}
+ void dependOnInheritedElement() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedElement'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromElement'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedElement'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.inheritFromElement();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.dependOnInheritedElement();
+}
+''');
+ }
+
+ Future<void> test_widgets_BuildContext_inheritFromElement_removed() async {
+ setPackageContent('''
+class BuildContext {
+ void dependOnInheritedElement() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedElement'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromElement'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedElement'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.inheritFromElement();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.dependOnInheritedElement();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_BuildContext_inheritFromWidgetOfExactType_deprecated() async {
+ setPackageContent('''
+class BuildContext {
+ @deprecated
+ void inheritFromWidgetOfExactType(Type t) {}
+ void dependOnInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromWidgetOfExactType'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.inheritFromWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.dependOnInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_BuildContext_inheritFromWidgetOfExactType_removed() async {
+ setPackageContent('''
+class BuildContext {
+ void dependOnInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromWidgetOfExactType'
+ inClass: 'BuildContext'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.inheritFromWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(BuildContext context) {
+ context.dependOnInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_Element_ancestorInheritedElementForWidgetOfExactType_deprecated() async {
+ setPackageContent('''
+class Element {
+ @deprecated
+ void ancestorInheritedElementForWidgetOfExactType(Type t) {}
+ void getElementForInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorInheritedElementForWidgetOfExactType'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'getElementForInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.ancestorInheritedElementForWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.getElementForInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_Element_ancestorInheritedElementForWidgetOfExactType_removed() async {
+ setPackageContent('''
+class Element {
+ void getElementForInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorInheritedElementForWidgetOfExactType'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'getElementForInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.ancestorInheritedElementForWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.getElementForInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_Element_ancestorWidgetOfExactType_deprecated() async {
+ setPackageContent('''
+class Element {
+ @deprecated
+ void ancestorWidgetOfExactType(Type t) {}
+ void findAncestorWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorWidgetOfExactType'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'findAncestorWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.ancestorWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.findAncestorWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void> test_widgets_Element_ancestorWidgetOfExactType_removed() async {
+ setPackageContent('''
+class Element {
+ void findAncestorWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to getElementForInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'ancestorWidgetOfExactType'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'findAncestorWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.ancestorWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.findAncestorWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void> test_widgets_Element_inheritFromElement_deprecated() async {
+ setPackageContent('''
+class Element {
+ @deprecated
+ void inheritFromElement() {}
+ void dependOnInheritedElement() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedElement'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromElement'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedElement'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.inheritFromElement();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.dependOnInheritedElement();
+}
+''');
+ }
+
+ Future<void> test_widgets_Element_inheritFromElement_removed() async {
+ setPackageContent('''
+class Element {
+ void dependOnInheritedElement() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedElement'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromElement'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedElement'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.inheritFromElement();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.dependOnInheritedElement();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_Element_inheritFromWidgetOfExactType_deprecated() async {
+ setPackageContent('''
+class Element {
+ @deprecated
+ void inheritFromWidgetOfExactType(Type t) {}
+ void dependOnInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromWidgetOfExactType'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.inheritFromWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.dependOnInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_Element_inheritFromWidgetOfExactType_removed() async {
+ setPackageContent('''
+class Element {
+ void dependOnInheritedWidgetOfExactType<T>() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedWidgetOfExactType'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromWidgetOfExactType'
+ inClass: 'Element'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedWidgetOfExactType'
+ - kind: 'addTypeParameter'
+ index: 0
+ name: 'T'
+ argumentValue:
+ expression: '{% type %}'
+ variables:
+ type:
+ kind: 'argument'
+ index: 0
+ - kind: 'removeParameter'
+ index: 0
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(Element element) {
+ element.inheritFromWidgetOfExactType(String);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(Element element) {
+ element.dependOnInheritedWidgetOfExactType<String>();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_ScrollPosition_jumpToWithoutSettling_deprecated() async {
+ setPackageContent('''
+class ScrollPosition {
+ @deprecated
+ void jumpToWithoutSettling(double d);
+ void jumpTo(double d);
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to jumpTo'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'jumpToWithoutSettling'
+ inClass: 'ScrollPosition'
+ changes:
+ - kind: 'rename'
+ newName: 'jumpTo'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(ScrollPosition position) {
+ position.jumpToWithoutSettling(0.5);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(ScrollPosition position) {
+ position.jumpTo(0.5);
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_ScrollPosition_jumpToWithoutSettling_removed() async {
+ setPackageContent('''
+class ScrollPosition {
+ void jumpTo(double d);
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to jumpTo'
+ date: 2020-09-14
+ element:
+ uris: ['$importUri']
+ method: 'jumpToWithoutSettling'
+ inClass: 'ScrollPosition'
+ changes:
+ - kind: 'rename'
+ newName: 'jumpTo'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(ScrollPosition position) {
+ position.jumpToWithoutSettling(0.5);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(ScrollPosition position) {
+ position.jumpTo(0.5);
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_StatefulElement_inheritFromElement_deprecated() async {
+ setPackageContent('''
+class StatefulElement {
+ @deprecated
+ void inheritFromElement() {}
+ void dependOnInheritedElement() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedElement'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromElement'
+ inClass: 'StatefulElement'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedElement'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(StatefulElement element) {
+ element.inheritFromElement();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(StatefulElement element) {
+ element.dependOnInheritedElement();
+}
+''');
+ }
+
+ Future<void> test_widgets_StatefulElement_inheritFromElement_removed() async {
+ setPackageContent('''
+class StatefulElement {
+ void dependOnInheritedElement() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to dependOnInheritedElement'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'inheritFromElement'
+ inClass: 'StatefulElement'
+ changes:
+ - kind: 'rename'
+ newName: 'dependOnInheritedElement'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(StatefulElement element) {
+ element.inheritFromElement();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(StatefulElement element) {
+ element.dependOnInheritedElement();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_WidgetsBinding_allowFirstFrameReport_deprecated() async {
+ setPackageContent('''
+class WidgetsBinding {
+ @deprecated
+ void allowFirstFrameReport() {}
+ void allowFirstFrame() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to allowFirstFrame'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'allowFirstFrameReport'
+ inClass: 'WidgetsBinding'
+ changes:
+ - kind: 'rename'
+ newName: 'allowFirstFrame'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.allowFirstFrameReport();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.allowFirstFrame();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_WidgetsBinding_allowFirstFrameReport_removed() async {
+ setPackageContent('''
+class WidgetsBinding {
+ void allowFirstFrame() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to allowFirstFrame'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'allowFirstFrameReport'
+ inClass: 'WidgetsBinding'
+ changes:
+ - kind: 'rename'
+ newName: 'allowFirstFrame'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.allowFirstFrameReport();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.allowFirstFrame();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_WidgetsBinding_deferFirstFrameReport_deprecated() async {
+ setPackageContent('''
+class WidgetsBinding {
+ @deprecated
+ void deferFirstFrameReport() {}
+ void deferFirstFrame() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to deferFirstFrame'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'deferFirstFrameReport'
+ inClass: 'WidgetsBinding'
+ changes:
+ - kind: 'rename'
+ newName: 'deferFirstFrame'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.deferFirstFrameReport();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.deferFirstFrame();
+}
+''');
+ }
+
+ Future<void>
+ test_widgets_WidgetsBinding_deferFirstFrameReport_removed() async {
+ setPackageContent('''
+class WidgetsBinding {
+ void deferFirstFrame() {}
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: 'Rename to deferFirstFrame'
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ method: 'deferFirstFrameReport'
+ inClass: 'WidgetsBinding'
+ changes:
+ - kind: 'rename'
+ newName: 'deferFirstFrame'
+''');
+ await resolveTestUnit('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.deferFirstFrameReport();
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f(WidgetsBinding binding) {
+ binding.deferFirstFrame();
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
index bd85ebe..5f2419e 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
@@ -8,6 +8,7 @@
import 'code_template_test.dart' as code_template;
import 'diagnostics/test_all.dart' as diagnostics;
import 'end_to_end_test.dart' as end_to_end;
+import 'flutter_use_case_test.dart' as flutter_use_case;
import 'modify_parameters_test.dart' as modify_parameters;
import 'rename_test.dart' as rename_change;
import 'transform_set_manager_test.dart' as transform_set_manager;
@@ -19,6 +20,7 @@
code_template.main();
diagnostics.main();
end_to_end.main();
+ flutter_use_case.main();
modify_parameters.main();
rename_change.main();
transform_set_manager.main();
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 13a6e34..e67947e 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -2,6 +2,8 @@
* Deprecated `GenericTypeAliasElement`. Use `FunctionTypeAliasElement`.
* Read imports, exports, and parts on demand in `AnalysisDriver`.
Specifically, `parseFileSync` will not read any referenced files.
+* Types are not set anymore for classes/constructors/getters of
+ identifiers in metadata (still set in arguments).
## 0.40.4
* Deprecated `IndexExpression.auxiliaryElements` and
diff --git a/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
index 92fef68..bd112a8 100644
--- a/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
@@ -76,7 +76,6 @@
element = _resolver.toLegacyElement(element);
identifier.staticElement = element;
- identifier.staticType = element?.type ?? DynamicTypeImpl.instance;
// TODO(scheglov) error?
} else if (prefixElement is PrefixElement) {
var resolver = PropertyElementResolver(_resolver);
@@ -108,14 +107,6 @@
var element = result.readElement;
identifier.staticElement = element;
-
- DartType type;
- if (element is PropertyAccessorElement && element.isGetter) {
- type = element.returnType;
- } else {
- type = DynamicTypeImpl.instance;
- }
- identifier.staticType = type;
}
} else {
var identifier = nodeName as SimpleIdentifier;
@@ -137,16 +128,6 @@
[identifier.name],
);
}
-
- DartType type;
- if (element is ClassElement) {
- type = _resolver.typeProvider.typeType;
- } else if (element is PropertyAccessorElement && element.isGetter) {
- type = element.returnType;
- } else {
- type = DynamicTypeImpl.instance;
- }
- identifier.staticType = type;
}
_resolveAnnotationElement(node);
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart
index 7f16523..e955fb1 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart
@@ -151,7 +151,7 @@
SimpleIdentifier identifier_1 = annotation.name;
expect(identifier_1.staticElement, same(myElement.getter));
- expect(identifier_1.staticType, typeProvider.intType);
+ expect(identifier_1.staticType, isNull);
}
{
@@ -202,7 +202,7 @@
SimpleIdentifier aRef = annotation.name;
assertElement(aRef, findElement.topGet('a'));
- assertType(aRef, 'int');
+ assertTypeNull(aRef);
}
test_annotation_onDirective_import() async {
@@ -222,7 +222,7 @@
SimpleIdentifier aRef = annotation.name;
assertElement(aRef, findElement.topGet('a'));
- assertType(aRef, 'int');
+ assertTypeNull(aRef);
}
test_annotation_onDirective_library() async {
@@ -242,7 +242,7 @@
SimpleIdentifier aRef = annotation.name;
assertElement(aRef, findElement.topGet('a'));
- assertType(aRef, 'int');
+ assertTypeNull(aRef);
}
test_annotation_onDirective_part() async {
@@ -265,7 +265,7 @@
SimpleIdentifier aRef = annotation.name;
assertElement(aRef, findElement.topGet('a'));
- assertType(aRef, 'int');
+ assertTypeNull(aRef);
}
test_annotation_onDirective_partOf() async {
@@ -288,7 +288,7 @@
SimpleIdentifier aRef = annotation.name;
assertElement(aRef, findElement.topGet('a'));
- assertType(aRef, 'int');
+ assertTypeNull(aRef);
}
test_annotation_onFormalParameter_redirectingFactory() async {
@@ -310,7 +310,7 @@
SimpleIdentifier ref = annotation.name;
assertElement(ref, getter);
- assertType(ref, 'int');
+ assertTypeNull(ref);
}
{
@@ -386,7 +386,7 @@
SimpleIdentifier identifier_1 = annotation.name;
expect(identifier_1.staticElement, same(myElement.getter));
- expect(identifier_1.staticType, typeProvider.intType);
+ assertTypeNull(identifier_1);
}
test_annotation_prefixed_classField() async {
@@ -570,7 +570,7 @@
assertTypeNull(prefixed.prefix);
expect(prefixed.identifier.staticElement, same(aGetter));
- expect(prefixed.identifier.staticType, typeProvider.intType);
+ assertTypeNull(prefixed.identifier);
expect(annotation.constructorName, isNull);
expect(annotation.arguments, isNull);
@@ -630,7 +630,7 @@
assertTypeNull(prefixed.prefix);
expect(prefixed.identifier.staticElement, same(constructor));
- assertType(prefixed.identifier, 'A Function(int, {int b})');
+ assertTypeNull(prefixed.identifier);
expect(annotation.constructorName, isNull);
@@ -699,14 +699,14 @@
SimpleIdentifier identifier_1 = annotation_1.name;
expect(identifier_1.staticElement, same(element_1.getter));
- expect(identifier_1.staticType, typeProvider.intType);
+ assertTypeNull(identifier_1);
Annotation annotation_2 = main.metadata[1];
expect(annotation_2.element, same(element_2.getter));
SimpleIdentifier identifier_2 = annotation_2.name;
expect(identifier_2.staticElement, same(element_2.getter));
- expect(identifier_2.staticType, typeProvider.intType);
+ assertTypeNull(identifier_2);
}
test_asExpression() async {
diff --git a/pkg/analyzer/test/src/dart/resolution/metadata_test.dart b/pkg/analyzer/test/src/dart/resolution/metadata_test.dart
index 0e2a55e..c325cf2 100644
--- a/pkg/analyzer/test/src/dart/resolution/metadata_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/metadata_test.dart
@@ -22,6 +22,120 @@
@reflectiveTest
class MetadataResolutionTest extends PubPackageResolutionTest {
+ test_genericClass_instanceGetter() async {
+ await resolveTestCode(r'''
+class A<T> {
+ T get foo {}
+}
+
+@A.foo
+void f() {}
+''');
+
+ _assertResolvedNodeText(findNode.annotation('@A'), r'''
+Annotation
+ element: self::@class::A::@getter::foo
+ name: PrefixedIdentifier
+ identifier: SimpleIdentifier
+ staticElement: self::@class::A::@getter::foo
+ staticType: null
+ token: foo
+ period: .
+ prefix: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ staticElement: self::@class::A::@getter::foo
+ staticType: null
+''');
+ }
+
+ test_genericClass_namedConstructor() async {
+ await assertNoErrorsInCode(r'''
+class A<T> {
+ const A.named();
+}
+
+@A.named()
+void f() {}
+''');
+
+ _assertResolvedNodeText(findNode.annotation('@A'), r'''
+Annotation
+ arguments: ArgumentList
+ element: ConstructorMember
+ base: self::@class::A::@constructor::named
+ substitution: {T: dynamic}
+ name: PrefixedIdentifier
+ identifier: SimpleIdentifier
+ staticElement: ConstructorMember
+ base: self::@class::A::@constructor::named
+ substitution: {T: dynamic}
+ staticType: null
+ token: named
+ period: .
+ prefix: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ staticElement: ConstructorMember
+ base: self::@class::A::@constructor::named
+ substitution: {T: dynamic}
+ staticType: null
+''');
+ }
+
+ test_genericClass_staticGetter() async {
+ await resolveTestCode(r'''
+class A<T> {
+ static T get foo {}
+}
+
+@A.foo
+void f() {}
+''');
+
+ _assertResolvedNodeText(findNode.annotation('@A'), r'''
+Annotation
+ element: self::@class::A::@getter::foo
+ name: PrefixedIdentifier
+ identifier: SimpleIdentifier
+ staticElement: self::@class::A::@getter::foo
+ staticType: null
+ token: foo
+ period: .
+ prefix: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ staticElement: self::@class::A::@getter::foo
+ staticType: null
+''');
+ }
+
+ test_genericClass_unnamedConstructor() async {
+ await assertNoErrorsInCode(r'''
+class A<T> {
+ const A();
+}
+
+@A()
+void f() {}
+''');
+
+ _assertResolvedNodeText(findNode.annotation('@A'), r'''
+Annotation
+ arguments: ArgumentList
+ element: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: dynamic}
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+''');
+ }
+
test_onFieldFormal() async {
await assertNoErrorsInCode(r'''
class A {
@@ -55,7 +169,7 @@
element: self::@class::A::@constructor::•
name: SimpleIdentifier
staticElement: self::@class::A
- staticType: Type
+ staticType: null
token: A
''');
}
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 863692f..a3cfc3c 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -8884,7 +8884,7 @@
element: self::@class::C::@getter::foo
name: SimpleIdentifier
staticElement: self::@class::C::@getter::foo
- staticType: int
+ staticType: null
token: foo
}
metadata
@@ -8892,7 +8892,7 @@
element: self::@getter::foo
name: SimpleIdentifier
staticElement: self::@getter::foo
- staticType: int
+ staticType: null
token: foo
typeParameters
T
@@ -8903,7 +8903,7 @@
element: self::@getter::foo
name: SimpleIdentifier
staticElement: self::@getter::foo
- staticType: int
+ staticType: null
token: foo
const int foo;
constantInitializer
@@ -9121,7 +9121,7 @@
element: self::@extension::E::@getter::foo
name: SimpleIdentifier
staticElement: self::@extension::E::@getter::foo
- staticType: int
+ staticType: null
token: foo
}
metadata
@@ -9129,7 +9129,7 @@
element: self::@getter::foo
name: SimpleIdentifier
staticElement: self::@getter::foo
- staticType: int
+ staticType: null
token: foo
typeParameters
T
@@ -9140,7 +9140,7 @@
element: self::@getter::foo
name: SimpleIdentifier
staticElement: self::@getter::foo
- staticType: int
+ staticType: null
token: foo
const int foo;
constantInitializer
@@ -9430,7 +9430,7 @@
element: self::@mixin::M::@getter::foo
name: SimpleIdentifier
staticElement: self::@mixin::M::@getter::foo
- staticType: int
+ staticType: null
token: foo
}
metadata
@@ -9438,7 +9438,7 @@
element: self::@getter::foo
name: SimpleIdentifier
staticElement: self::@getter::foo
- staticType: int
+ staticType: null
token: foo
typeParameters
T
@@ -9449,7 +9449,7 @@
element: self::@getter::foo
name: SimpleIdentifier
staticElement: self::@getter::foo
- staticType: int
+ staticType: null
token: foo
const int foo;
constantInitializer
@@ -11789,7 +11789,7 @@
element: <null>
name: SimpleIdentifier
staticElement: <null>
- staticType: dynamic
+ staticType: null
token: foo
''',
withFullyResolvedAst: true);
@@ -11817,7 +11817,7 @@
element: <null>
name: SimpleIdentifier
staticElement: <null>
- staticType: dynamic
+ staticType: null
token: v
''',
withFullyResolvedAst: true);
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_test.dart
index f3e46aa..5636b5f 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_test.dart
@@ -262,6 +262,7 @@
group('Expression compiler tests in extension method:', () {
const source = '''
+ // @dart = 2.9
extension NumberParsing on String {
int parseInt() {
var ret = int.parse(this);
@@ -312,6 +313,7 @@
group('Expression compiler tests in method:', () {
const source = '''
+ // @dart = 2.9
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
@@ -565,6 +567,7 @@
group('Expression compiler tests in method with no field access:', () {
const source = '''
+ // @dart = 2.9
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
@@ -726,6 +729,7 @@
group('Expression compiler tests in async method:', () {
const source = '''
+ // @dart = 2.9
class C {
C(int this.field, int this._field);
@@ -786,6 +790,7 @@
group('Expression compiler tests in global function:', () {
const source = '''
+ // @dart = 2.9
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
@@ -1044,6 +1049,7 @@
group('Expression compiler tests in closures:', () {
const source = r'''
+ // @dart = 2.9
int globalFunction() {
int x = 15;
var c = C(1, 2);
@@ -1108,6 +1114,7 @@
group('Expression compiler tests in method with no type use', () {
const source = '''
+ // @dart = 2.9
abstract class Key {
const factory Key(String value) = ValueKey;
const Key.empty();
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
index 1e649aa..65167c7 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
@@ -102,13 +102,14 @@
File.fromUri(main)
..createSync(recursive: true)
..writeAsStringSync('''
+// @dart = 2.9
import 'package:_testPackage/test_library.dart';
var global = 0;
void main() {
var count = 0;
- // line 7
+ // line 8
print('Global is: \${++global}');
print('Count is: \${++count}');
@@ -120,10 +121,11 @@
File.fromUri(testLibrary)
..createSync()
..writeAsStringSync('''
+// @dart = 2.9
import 'package:_testPackage/test_library2.dart';
int testLibraryFunction(int formal) {
- return formal; // line 4
+ return formal; // line 5
}
int callLibraryFunction2(int formal) {
@@ -139,8 +141,9 @@
File.fromUri(testLibrary2)
..createSync()
..writeAsStringSync('''
+// @dart = 2.9
int testLibraryFunction2(int formal) {
- return formal; // line 2
+ return formal; // line 3
}
class C {
@@ -259,7 +262,7 @@
requestController.add({
'command': 'CompileExpression',
'expression': 'formal',
- 'line': 4,
+ 'line': 5,
'column': 1,
'jsModules': {},
'jsScope': {'formal': 'formal'},
@@ -291,7 +294,7 @@
requestController.add({
'command': 'CompileExpression',
'expression': 'count',
- 'line': 7,
+ 'line': 8,
'column': 1,
'jsModules': {},
'jsScope': {'count': 'count'},
@@ -324,7 +327,7 @@
requestController.add({
'command': 'CompileExpression',
'expression': 'B().c().getNumber()',
- 'line': 7,
+ 'line': 8,
'column': 1,
'jsModules': {},
'jsScope': {},
@@ -445,7 +448,7 @@
requestController.add({
'command': 'CompileExpression',
'expression': 'formal',
- 'line': 4,
+ 'line': 5,
'column': 1,
'jsModules': {},
'jsScope': {'formal': 'formal'},
@@ -477,7 +480,7 @@
requestController.add({
'command': 'CompileExpression',
'expression': 'count',
- 'line': 7,
+ 'line': 8,
'column': 1,
'jsModules': {},
'jsScope': {'count': 'count'},
diff --git a/pkg/scrape/.gitignore b/pkg/scrape/.gitignore
new file mode 100644
index 0000000..37c2b44
--- /dev/null
+++ b/pkg/scrape/.gitignore
@@ -0,0 +1,6 @@
+# Files and directories created by pub
+.packages
+.dart_tool/
+
+# Remove the following pattern if you wish to check in your lock file
+pubspec.lock
diff --git a/pkg/scrape/README.md b/pkg/scrape/README.md
new file mode 100644
index 0000000..58b0cda
--- /dev/null
+++ b/pkg/scrape/README.md
@@ -0,0 +1,108 @@
+# Scrape
+
+The scrape package is sort of a micro-framework to make it easier to write
+little scripts that parse and traverse Dart code and gather statistics about the
+contents.
+
+For example, say you want to find out how many if statements in a body of Dart
+code contain else clauses. A script using this package to measure that is:
+
+```dart
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:scrape/scrape.dart';
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram("If")
+ ..addVisitor(() => IfVisitor())
+ ..runCommandLine(arguments);
+}
+
+class IfVisitor extends ScrapeVisitor {
+ @override
+ void visitIfStatement(IfStatement node) {
+ if (node.elseStatement != null) {
+ record("If", "else");
+ } else {
+ record("If", "no else");
+ }
+ super.visitIfStatement(node);
+ }
+}
+```
+
+Run that script on the package itself:
+
+```
+$ dart example/if.dart .
+```
+
+And it prints out:
+
+```
+-- If (137 total) --
+ 104 ( 75.912%): no else =======================================
+ 33 ( 24.088%): else =============
+Took 262ms to scrape 1349 lines in 12 files.
+```
+
+So it looks like if statements without elses are about three times more common
+than ones with elses. We use data like this to inform how we design and evolve
+the language.
+
+## A scrape script
+
+I wanted to make scrape flexible enough to let you write whatever kinds of
+logic to analyze code. That meant that instead of scrape being a *tool you run*,
+it is more like a *library you consume*. This way, inside your script, you have
+access to the full Dart language. At the same time, I didn't want every script
+to have to copy/paste the same boring argument parsing and other code.
+
+The compromise between those is the Scrape class. It is a builder for an
+analysis over some code. It has a few methods you call on it to set up an
+analysis:
+
+* `addHistogram()` registers a new named histogram. This is the main way you
+ count occurences of datapoints you care about in the code you are analyzing.
+ Each histogram is a named collection of datapoints. When the analysis
+ completes, scrape prints out each histogram, buckets the datapoints, and
+ shows how many of each datapoint occurred.
+
+ In the example above, we have one histogram named "If" and we count two
+ different datapoints, "no else", and "else".
+
+* `addVisitor()` registers a callback that creates a visitor. This is the
+ main way you analyze code. When the analysis runs, scrape parses every
+ Dart file you specify. For each file and each registered visitor callback,
+ it invokes the callback to create a visitor and then runs that to walk over
+ the parsed code.
+
+ You call this passing in a callback that creates an instance of your own
+ visitor class, which should extend `ScrapeVisitor`.
+
+* Then at the end call `runCommandLine()`, passing in your script's command
+ line arguments. This reads the file paths the user wants to analyze and
+ a few other command line options and flags that scrape automatically
+ supports. To learn more, call that with `--help`.
+
+## A visitor class
+
+The way your script analyzes code is through one or more custom subclasses of
+`ScrapeVisitor`. That base class itself extends the analyzer package's
+[`RecursiveAstVisitor`][visitor] class. It will walk over every single syntax
+tree node in the parsed Dart file and invoke visit methods specific to each one.
+You override the visit methods for the AST nodes you care about and put
+whatever logic you want in there to analyze the code.
+
+An important limitation of scrape is that it only *parses* Dart files. It does
+not to any static analysis, name resolution, or type checking. This makes it
+lightweight and fast to run (for example you can run it on a pub package
+without needing to download its dependencies), but significantly limits the
+kinds of analysis you can do.
+
+It's good for syntax and tolerable for things like API usage if you're willing
+to assume that certain names do refer to the API you think they do. If you look
+in the examples directory, you'll get a sense for what kinds of tasks scrape is
+well suited for.
+
+[visitor]: https://pub.dev/documentation/analyzer/0.40.0/dart_ast_visitor/RecursiveAstVisitor-class.html
diff --git a/pkg/scrape/analysis_options.yaml b/pkg/scrape/analysis_options.yaml
new file mode 100644
index 0000000..f6dcda64
--- /dev/null
+++ b/pkg/scrape/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:pedantic/analysis_options.yaml
+analyzer:
+ strong-mode:
+ implicit-casts: false
+ implicit-dynamic: false
diff --git a/pkg/scrape/example/build_control_flow.dart b/pkg/scrape/example/build_control_flow.dart
new file mode 100644
index 0000000..bdd851a
--- /dev/null
+++ b/pkg/scrape/example/build_control_flow.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+
+import 'package:scrape/scrape.dart';
+
+/// Known cases where an "if" statement could instead be an "if" element inside
+/// a list or map literal. Usually this is an optional child widget in a list
+/// of children.
+final _knownCollection = {'flutter/examples/layers/widgets/styled_text.dart'};
+
+final _buildMethods = <String>[];
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('build methods')
+ ..addVisitor(() => ControlFlowVisitor())
+ ..runCommandLine(arguments);
+
+ _buildMethods.shuffle();
+ _buildMethods.take(100).forEach(print);
+}
+
+class ControlFlowVisitor extends ScrapeVisitor {
+ final List<String> _controlFlow = [];
+
+ @override
+ void beforeVisitBuildMethod(Declaration node) {
+ _controlFlow.clear();
+ }
+
+ @override
+ void afterVisitBuildMethod(Declaration node) {
+ if (_controlFlow.isNotEmpty) {
+ _buildMethods.add(nodeToString(node));
+
+ var hasIf = _controlFlow.any((s) => s.startsWith('if'));
+ var hasConditional = _controlFlow.any((s) => s.startsWith('conditional'));
+
+ if (hasIf && hasConditional) {
+ record('build methods', 'both');
+ } else if (hasIf) {
+ record('build methods', 'if');
+ } else {
+ record('build methods', 'conditional');
+ }
+ } else {
+ record('build methods', 'no control flow');
+ }
+ }
+
+ @override
+ void visitConditionalExpression(ConditionalExpression node) {
+ super.visitConditionalExpression(node);
+
+ if (!isInFlutterBuildMethod) return;
+
+ if (node.parent is NamedExpression) {
+ _controlFlow.add('conditional named arg');
+ } else if (node.parent is ArgumentList) {
+ _controlFlow.add('conditional positional arg');
+ } else if (node.parent is VariableDeclaration) {
+ _controlFlow.add('conditional variable');
+ } else if (node.parent is InterpolationExpression) {
+ _controlFlow.add('conditional interpolation');
+ } else {
+ _controlFlow.add('conditional');
+ }
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ super.visitIfStatement(node);
+
+ if (!isInFlutterBuildMethod) return;
+
+ if (_isReturn(node.thenStatement) && _isReturn(node.elseStatement)) {
+ _controlFlow.add('if return');
+ } else if (_isAdd(node.thenStatement) && _isAdd(node.elseStatement)) {
+ _controlFlow.add('if add');
+ } else if (_knownCollection.contains(path)) {
+ _controlFlow.add('if collection');
+ } else {
+ _controlFlow.add('if');
+ }
+ }
+
+ bool _isReturn(Statement statement) {
+ // Ignore empty "else" clauses.
+ if (statement == null) return true;
+
+ if (statement is ReturnStatement) return true;
+
+ if (statement is Block && statement.statements.length == 1) {
+ return _isReturn(statement.statements.first);
+ }
+
+ return false;
+ }
+
+ bool _isAdd(Statement statement) {
+ // Ignore empty "else" clauses.
+ if (statement == null) return true;
+
+ if (statement is ExpressionStatement) {
+ var expr = statement.expression;
+ if (expr is MethodInvocation) {
+ if (expr.methodName.name == 'add' || expr.methodName.name == 'addAll') {
+ return true;
+ }
+ }
+ } else if (statement is Block && statement.statements.length == 1) {
+ return _isAdd(statement.statements.first);
+ }
+
+ return false;
+ }
+}
diff --git a/pkg/scrape/example/class_reuse.dart b/pkg/scrape/example/class_reuse.dart
new file mode 100644
index 0000000..8fb60c6
--- /dev/null
+++ b/pkg/scrape/example/class_reuse.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:scrape/scrape.dart';
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('Declarations')
+ ..addHistogram('Uses')
+ ..addHistogram('Superclass names')
+ ..addHistogram('Superinterface names')
+ ..addHistogram('Mixin names')
+ ..addVisitor(() => ClassReuseVisitor())
+ ..runCommandLine(arguments);
+}
+
+class ClassReuseVisitor extends ScrapeVisitor {
+ @override
+ void visitClassDeclaration(ClassDeclaration node) {
+ if (node.isAbstract) {
+ record('Declarations', 'abstract class');
+ } else {
+ record('Declarations', 'class');
+ }
+
+ if (node.extendsClause != null) {
+ record('Uses', 'extend');
+ record('Superclass names', node.extendsClause.superclass.toString());
+ }
+
+ if (node.withClause != null) {
+ for (var mixin in node.withClause.mixinTypes) {
+ record('Uses', 'mixin');
+ record('Mixin names', mixin.toString());
+ }
+ }
+
+ if (node.implementsClause != null) {
+ for (var type in node.implementsClause.interfaces) {
+ record('Uses', 'implement');
+ record('Superinterface names', type.toString());
+ }
+ }
+ }
+}
diff --git a/pkg/scrape/example/control_flow.dart b/pkg/scrape/example/control_flow.dart
new file mode 100644
index 0000000..e8c9dacf
--- /dev/null
+++ b/pkg/scrape/example/control_flow.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:scrape/scrape.dart';
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('Statements')
+ ..addVisitor(() => ControlFlowVisitor())
+ ..runCommandLine(arguments);
+}
+
+class ControlFlowVisitor extends ScrapeVisitor {
+ @override
+ void visitDoStatement(DoStatement node) {
+ record('Statements', 'do while');
+ super.visitDoStatement(node);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ if (node.forLoopParts is ForEachParts) {
+ record('Statements', 'for in');
+ } else {
+ record('Statements', 'for ;;');
+ }
+ super.visitForStatement(node);
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ if (node.elseStatement != null) {
+ record('Statements', 'if else');
+ } else {
+ record('Statements', 'if');
+ }
+ super.visitIfStatement(node);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ record('Statements', 'switch');
+ super.visitSwitchStatement(node);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ record('Statements', 'while');
+ super.visitWhileStatement(node);
+ }
+}
diff --git a/pkg/scrape/example/generic_classes.dart b/pkg/scrape/example/generic_classes.dart
new file mode 100644
index 0000000..09ecc32
--- /dev/null
+++ b/pkg/scrape/example/generic_classes.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+
+import 'package:scrape/scrape.dart';
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('Classes', order: SortOrder.numeric)
+ ..addVisitor(() => GenericClassVisitor())
+ ..runCommandLine(arguments);
+}
+
+class GenericClassVisitor extends ScrapeVisitor {
+ @override
+ void visitCompilationUnit(CompilationUnit node) {
+ super.visitCompilationUnit(node);
+ }
+
+ @override
+ void visitClassDeclaration(ClassDeclaration node) {
+ if (node.typeParameters == null) {
+ record('Classes', 0);
+ } else {
+ record('Classes', node.typeParameters.typeParameters.length);
+ }
+ super.visitClassDeclaration(node);
+ }
+}
diff --git a/pkg/scrape/example/if.dart b/pkg/scrape/example/if.dart
new file mode 100644
index 0000000..57573ff
--- /dev/null
+++ b/pkg/scrape/example/if.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:scrape/scrape.dart';
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('If')
+ ..addVisitor(() => IfVisitor())
+ ..runCommandLine(arguments);
+}
+
+class IfVisitor extends ScrapeVisitor {
+ @override
+ void visitIfStatement(IfStatement node) {
+ if (node.elseStatement != null) {
+ record('If', 'else');
+ } else {
+ record('If', 'no else');
+ }
+ super.visitIfStatement(node);
+ }
+}
diff --git a/pkg/scrape/example/nesting.dart b/pkg/scrape/example/nesting.dart
new file mode 100644
index 0000000..6541d6d
--- /dev/null
+++ b/pkg/scrape/example/nesting.dart
@@ -0,0 +1,164 @@
+// 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 'dart:math' as math;
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+
+import 'package:scrape/scrape.dart';
+
+/// The paths to each build() method and its maximum nesting level.
+final buildMethods = <String, int>{};
+
+bool simplifyNames = false;
+
+void main(List<String> arguments) {
+ arguments = arguments.toList();
+ simplifyNames = arguments.remove('--simplify');
+ var allCode = arguments.remove('--all');
+
+ Scrape()
+ // The number of levels of nesting active when each argument appears.
+ ..addHistogram('Nesting depth')
+ // The number of levels of nesting active when each argument appears without
+ // counting lists.
+ ..addHistogram('Ignoring lists')
+ // The number of levels of "child" or "children" named parameter nesting
+ // when each argument appears.
+ ..addHistogram('Child nesting depth')
+ ..addHistogram('Argument names')
+ // Strings that describe the structure of each nested argument.
+ ..addHistogram('Argument nesting')
+ ..addVisitor(() => NestingVisitor(allCode: allCode))
+ ..runCommandLine(arguments);
+
+ var methods = buildMethods.keys.toList();
+ methods.sort((a, b) => buildMethods[b].compareTo(buildMethods[a]));
+ for (var method in methods) {
+ print('${buildMethods[method].toString().padLeft(3)}: $method');
+ }
+ print('${buildMethods.length} build() methods');
+}
+
+class NestingVisitor extends ScrapeVisitor {
+ final List<String> _stack = [];
+
+ final bool _allCode;
+ bool _pushed = false;
+ int _deepestNesting = 0;
+
+ NestingVisitor({bool allCode}) : _allCode = allCode ?? false;
+
+ @override
+ void beforeVisitBuildMethod(Declaration node) {
+ _deepestNesting = 0;
+ }
+
+ @override
+ void afterVisitBuildMethod(Declaration node) {
+ var startLine = lineInfo.getLocation(node.offset).lineNumber;
+ buildMethods['$path:$startLine'] = _deepestNesting;
+ }
+
+ @override
+ void visitArgumentList(ArgumentList node) {
+ // Only argument lists with trailing commas get indentation.
+ if (node.arguments.isNotEmpty &&
+ node.arguments.last.endToken.next.type == TokenType.COMMA) {
+ String name;
+ var parent = node.parent;
+ if (parent is MethodInvocation) {
+ name = parent.methodName.name;
+ } else if (parent is InstanceCreationExpression) {
+ name = parent.constructorName.toString();
+ } else if (parent is SuperConstructorInvocation) {
+ name = 'super.${parent.constructorName}';
+ } else {
+ name = '?(${parent.runtimeType})?';
+ }
+
+ if (simplifyNames) {
+ name = '';
+ }
+
+ for (var argument in node.arguments) {
+ var argName =
+ argument is NamedExpression ? argument.name.label.name : '';
+
+ if (_allCode || isInFlutterBuildMethod) {
+ record('Argument names', argName);
+ }
+
+ if (simplifyNames && argName != 'child' && argName != 'children') {
+ argName = '_';
+ }
+
+ _push('$name($argName:');
+ argument.accept(this);
+ _pop();
+ }
+ } else {
+ node.visitChildren(this);
+ }
+ }
+
+ @override
+ void visitBlock(Block node) {
+ var isFunction = node.parent is BlockFunctionBody;
+ if (!isFunction) _push('{');
+ node.visitChildren(this);
+ if (!isFunction) _pop();
+ }
+
+ @override
+ void visitExpressionFunctionBody(ExpressionFunctionBody node) {
+ _push('=>');
+ node.visitChildren(this);
+ _pop();
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ var isDeclaration = node.parent is FunctionDeclaration;
+ if (!isDeclaration) _push('(){');
+ node.visitChildren(this);
+ if (!isDeclaration) _pop();
+ }
+
+ @override
+ void visitListLiteral(ListLiteral node) {
+ for (var element in node.elements) {
+ _push('[');
+ element.accept(this);
+ _pop();
+ }
+ }
+
+ @override
+ void visitSetOrMapLiteral(SetOrMapLiteral node) {
+ for (var element in node.elements) {
+ _push('{');
+ element.accept(this);
+ _pop();
+ }
+ }
+
+ void _push(String string) {
+ _stack.add(string);
+ _pushed = true;
+ _deepestNesting = math.max(_deepestNesting, _stack.length);
+ }
+
+ void _pop() {
+ if (_pushed && (_allCode || isInFlutterBuildMethod)) {
+ record('Argument nesting', _stack.join(' '));
+ record('Nesting depth', _stack.length);
+ record('Ignoring lists', _stack.where((s) => s != '[').length);
+ record('Child nesting depth',
+ _stack.where((s) => s.contains('child')).length);
+ }
+ _pushed = false;
+ _stack.removeLast();
+ }
+}
diff --git a/pkg/scrape/example/null_aware.dart b/pkg/scrape/example/null_aware.dart
new file mode 100644
index 0000000..392798e
--- /dev/null
+++ b/pkg/scrape/example/null_aware.dart
@@ -0,0 +1,307 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+
+import 'package:scrape/scrape.dart';
+
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('Null-aware types')
+ ..addHistogram('Null-aware chain lengths')
+ // Boolean contexts where null-aware operators are used.
+ ..addHistogram('Boolean contexts')
+ // Expressions used to convert a null-aware expression to a Boolean for use in
+ // a Boolean context.
+ ..addHistogram('Boolean conversions')
+ ..addVisitor(() => NullVisitor())
+ ..runCommandLine(arguments);
+}
+
+class NullVisitor extends ScrapeVisitor {
+ @override
+ void visitMethodInvocation(MethodInvocation node) {
+ if (node.operator != null &&
+ node.operator.type == TokenType.QUESTION_PERIOD) {
+ _nullAware(node);
+ }
+
+ super.visitMethodInvocation(node);
+ }
+
+ @override
+ void visitPropertyAccess(PropertyAccess node) {
+ if (node.operator.type == TokenType.QUESTION_PERIOD) {
+ _nullAware(node);
+ }
+
+ super.visitPropertyAccess(node);
+ }
+
+ void _nullAware(AstNode node) {
+ var parent = node.parent;
+
+ // Parentheses are purely syntactic.
+ if (parent is ParenthesizedExpression) parent = parent.parent;
+
+ // We want to treat a chain of null-aware operators as a single unit. We
+ // use the top-most node (the last method in the chain) as the "real" one
+ // because its parent is the context where the whole chain appears.
+ if (parent is PropertyAccess &&
+ parent.operator.type == TokenType.QUESTION_PERIOD &&
+ parent.target == node ||
+ parent is MethodInvocation &&
+ parent.operator != null &&
+ parent.operator.type == TokenType.QUESTION_PERIOD &&
+ parent.target == node) {
+ // This node is not the root of a null-aware chain, so skip it.
+ return;
+ }
+
+ // This node is the root of a null-aware chain. See how long the chain is.
+ var length = 0;
+ var chain = node;
+ while (true) {
+ if (chain is PropertyAccess &&
+ chain.operator.type == TokenType.QUESTION_PERIOD) {
+ chain = (chain as PropertyAccess).target;
+ } else if (chain is MethodInvocation &&
+ chain.operator != null &&
+ chain.operator.type == TokenType.QUESTION_PERIOD) {
+ chain = (chain as MethodInvocation).target;
+ } else {
+ break;
+ }
+
+ length++;
+ }
+
+ record('Null-aware chain lengths', length.toString());
+
+ void recordType(String label) {
+ record('Null-aware types', label);
+ }
+
+ // See if the expression is an if condition.
+ _checkCondition(node);
+
+ if (parent is ExpressionStatement) {
+ recordType("Expression statement 'foo?.bar();'");
+ return;
+ }
+
+ if (parent is ReturnStatement) {
+ recordType("Return statement 'return foo?.bar();'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.BANG_EQ &&
+ parent.leftOperand == node &&
+ parent.rightOperand is BooleanLiteral &&
+ (parent.rightOperand as BooleanLiteral).value == true) {
+ recordType("Compare to true 'foo?.bar() != true'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.EQ_EQ &&
+ parent.leftOperand == node &&
+ parent.rightOperand is BooleanLiteral &&
+ (parent.rightOperand as BooleanLiteral).value == true) {
+ recordType("Compare to true 'foo?.bar() == true'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.BANG_EQ &&
+ parent.leftOperand == node &&
+ parent.rightOperand is BooleanLiteral &&
+ (parent.rightOperand as BooleanLiteral).value == false) {
+ recordType("Compare to false 'foo?.bar() != false'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.EQ_EQ &&
+ parent.leftOperand == node &&
+ parent.rightOperand is BooleanLiteral &&
+ (parent.rightOperand as BooleanLiteral).value == false) {
+ recordType("Compare to false 'foo?.bar() == false'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.BANG_EQ &&
+ parent.leftOperand == node &&
+ parent.rightOperand is NullLiteral) {
+ recordType("Compare to null 'foo?.bar() != null'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.EQ_EQ &&
+ parent.leftOperand == node &&
+ parent.rightOperand is NullLiteral) {
+ recordType("Compare to null 'foo?.bar() == null'");
+ return;
+ }
+
+ if (parent is BinaryExpression && parent.operator.type == TokenType.EQ_EQ) {
+ recordType("Compare to other expression 'foo?.bar() == bang'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.BANG_EQ) {
+ recordType("Compare to other expression 'foo?.bar() != bang'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.QUESTION_QUESTION &&
+ parent.leftOperand == node) {
+ recordType("Coalesce 'foo?.bar() ?? baz'");
+ return;
+ }
+
+ if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.QUESTION_QUESTION &&
+ parent.rightOperand == node) {
+ recordType("Reverse coalesce 'baz ?? foo?.bar()'");
+ return;
+ }
+
+ if (parent is ConditionalExpression && parent.condition == node) {
+ recordType(
+ "Condition in conditional expression 'foo?.bar() ? baz : bang");
+ return;
+ }
+
+ if (parent is ConditionalExpression) {
+ recordType("Then or else branch of conditional 'baz ? foo?.bar() : bang");
+ return;
+ }
+
+ if (parent is AsExpression && parent.expression == node) {
+ recordType("Cast expression 'foo?.bar as Baz'");
+ return;
+ }
+
+ if (parent is AssignmentExpression && parent.leftHandSide == node) {
+ recordType("Assign target 'foo?.bar ${parent.operator} baz'");
+ return;
+ }
+
+ if (parent is AssignmentExpression && parent.rightHandSide == node) {
+ recordType("Assign value 'baz = foo?.bar()'");
+ return;
+ }
+
+ if (parent is VariableDeclaration && parent.initializer == node) {
+ recordType("Variable initializer 'var baz = foo?.bar();'");
+ return;
+ }
+
+ if (parent is NamedExpression) {
+ recordType("Named argument 'fn(name: foo?.bar())'");
+ return;
+ }
+
+ if (parent is ArgumentList && parent.arguments.contains(node)) {
+ recordType("Positional argument 'fn(foo?.bar())'");
+ return;
+ }
+
+ if (parent is AwaitExpression) {
+ recordType("Await 'await foo?.bar()'");
+ return;
+ }
+
+ if (parent is MapLiteralEntry || parent is ListLiteral) {
+ recordType("Collection literal element '[foo?.bar()]'");
+ return;
+ }
+
+ if (parent is ExpressionFunctionBody) {
+ recordType("Member body 'member() => foo?.bar();'");
+ return;
+ }
+
+ if (parent is InterpolationExpression) {
+ recordType("String interpolation '\"blah \${foo?.bar()}\"'");
+ return;
+ }
+
+ if (parent is BinaryExpression) {
+ recordType('Uncategorized ${parent}');
+ return;
+ }
+
+ recordType('Uncategorized ${parent.runtimeType}');
+
+ // Find the surrounding statement containing the null-aware.
+ while (node is Expression) {
+ node = node.parent;
+ }
+
+ printNode(node);
+ }
+
+ void _checkCondition(AstNode node) {
+ String expression;
+
+ // Look at the expression that immediately wraps the null-aware to see if
+ // it deals with it somehow, like "foo?.bar ?? otherwise".
+ var parent = node.parent;
+ if (parent is ParenthesizedExpression) parent = parent.parent;
+
+ if (parent is BinaryExpression &&
+ (parent.operator.type == TokenType.EQ_EQ ||
+ parent.operator.type == TokenType.BANG_EQ ||
+ parent.operator.type == TokenType.QUESTION_QUESTION) &&
+ (parent.rightOperand is NullLiteral ||
+ parent.rightOperand is BooleanLiteral)) {
+ var binary = parent as BinaryExpression;
+ expression = 'foo?.bar ${binary.operator} ${binary.rightOperand}';
+
+ // This does handle it, so see the context where it appears.
+ node = parent as Expression;
+ if (node is ParenthesizedExpression) node = node.parent as Expression;
+ parent = node.parent;
+ if (parent is ParenthesizedExpression) parent = parent.parent;
+ }
+
+ String context;
+ if (parent is IfStatement && node == parent.condition) {
+ context = 'if';
+ } else if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.AMPERSAND_AMPERSAND) {
+ context = '&&';
+ } else if (parent is BinaryExpression &&
+ parent.operator.type == TokenType.BAR_BAR) {
+ context = '||';
+ } else if (parent is WhileStatement && node == parent.condition) {
+ context = 'while';
+ } else if (parent is DoStatement && node == parent.condition) {
+ context = 'do';
+ } else if (parent is ForStatement &&
+ parent.forLoopParts is ForParts &&
+ node == (parent.forLoopParts as ForParts).condition) {
+ context = 'for';
+ } else if (parent is ConditionalExpression && node == parent.condition) {
+ context = '?:';
+ }
+
+ if (context != null) {
+ record('Boolean contexts', context);
+
+ if (expression != null) {
+ record('Boolean conversions', expression);
+ } else {
+ record('Boolean conversions', 'unknown: $node');
+ }
+ }
+ }
+}
diff --git a/pkg/scrape/example/spread_sizes.dart b/pkg/scrape/example/spread_sizes.dart
new file mode 100644
index 0000000..d1257e1
--- /dev/null
+++ b/pkg/scrape/example/spread_sizes.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+
+import 'package:scrape/scrape.dart';
+
+/// Looks at expressions that could likely be converted to spread operators and
+/// measures their length. "Likely" means calls to `addAll()` where the
+/// receiver is a list or map literal.
+void main(List<String> arguments) {
+ Scrape()
+ ..addHistogram('Arguments')
+ ..addHistogram('Lengths', order: SortOrder.numeric)
+ ..addVisitor(() => SpreadVisitor())
+ ..runCommandLine(arguments);
+}
+
+class SpreadVisitor extends ScrapeVisitor {
+ @override
+ void visitCascadeExpression(CascadeExpression node) {
+ for (var section in node.cascadeSections) {
+ if (section is MethodInvocation) {
+ _countCall(node, section.methodName, node.target, section.argumentList);
+ }
+ }
+
+ super.visitCascadeExpression(node);
+ }
+
+ @override
+ void visitMethodInvocation(MethodInvocation node) {
+ _countCall(node, node.methodName, node.target, node.argumentList);
+ super.visitMethodInvocation(node);
+ }
+
+ void _countCall(Expression node, SimpleIdentifier name, Expression target,
+ ArgumentList args) {
+ if (name.name != 'addAll') return;
+
+ // See if the target is a collection literal.
+ while (target is MethodInvocation) {
+ target = (target as MethodInvocation).target;
+ }
+
+ if (target is ListLiteral || target is SetOrMapLiteral) {
+ if (args.arguments.length == 1) {
+ var arg = args.arguments[0];
+ record('Arguments', arg.toString());
+ record('Lengths', arg.length);
+ }
+
+ printNode(node);
+ }
+ }
+}
diff --git a/pkg/scrape/lib/scrape.dart b/pkg/scrape/lib/scrape.dart
new file mode 100644
index 0000000..6359494
--- /dev/null
+++ b/pkg/scrape/lib/scrape.dart
@@ -0,0 +1,291 @@
+// 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 'dart:io';
+import 'dart:math' as math;
+
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/scanner/reader.dart';
+import 'package:analyzer/src/dart/scanner/scanner.dart';
+import 'package:analyzer/src/generated/parser.dart';
+import 'package:analyzer/src/string_source.dart';
+import 'package:args/args.dart';
+import 'package:path/path.dart' as p;
+
+import 'src/error_listener.dart';
+import 'src/histogram.dart';
+import 'src/scrape_visitor.dart';
+
+export 'src/histogram.dart';
+export 'src/scrape_visitor.dart' hide bindVisitor;
+
+class Scrape {
+ final List<ScrapeVisitor Function()> _visitorFactories = [];
+
+ /// What percent of files should be processed.
+ int _percent;
+
+ /// Process package test files.
+ bool _includeTests = true;
+
+ /// Process Dart SDK language tests.
+ bool _includeLanguageTests = true;
+
+ /// Process Dart files generated from protobufs.
+ bool _includeProtobufs = false;
+
+ /// Whether every file should be printed before being processed.
+ bool _printFiles = true;
+
+ /// Whether parse errors should be printed.
+ bool _printErrors = true;
+
+ /// The number of files that have been processed.
+ int get scrapedFileCount => _scrapedFileCount;
+ int _scrapedFileCount = 0;
+
+ /// The number of lines of code that have been processed.
+ int get scrapedLineCount => _scrapedLineCount;
+ int _scrapedLineCount = 0;
+
+ final Map<String, Histogram> _histograms = {};
+
+ /// Whether we're in the middle of writing the running file count and need a
+ /// newline before any other output should be shown.
+ bool _needClearLine = false;
+
+ /// Register a new visitor factory.
+ ///
+ /// This function will be called for each scraped file and the resulting
+ /// [ScrapeVisitor] will traverse the parsed file's AST.
+ void addVisitor(ScrapeVisitor Function() createVisitor) {
+ _visitorFactories.add(createVisitor);
+ }
+
+ /// Defines a new histogram with [name] to collect occurrences.
+ ///
+ /// After the scrape completes, each defined histogram's collected counts
+ /// are shown, ordered by [order]. If [showBar] is not `false`, then shows an
+ /// ASCII bar chart for the counts.
+ ///
+ /// If [showAll] is `true`, then every item that occurred is shown. Otherwise,
+ /// only shows the first 100 items or occurrences that represent at least
+ /// 0.1% of the total, whichever is longer.
+ ///
+ /// If [minCount] is passed, then only shows items that occurred at least
+ /// that many times.
+ void addHistogram(String name,
+ {SortOrder order = SortOrder.descending,
+ bool showBar,
+ bool showAll,
+ int minCount}) {
+ _histograms.putIfAbsent(
+ name,
+ () => Histogram(
+ order: order,
+ showBar: showBar,
+ showAll: showAll,
+ minCount: minCount));
+ }
+
+ /// Add an occurrence of [item] to [histogram].
+ void record(String histogram, Object item) {
+ _histograms[histogram].add(item);
+ }
+
+ /// Run the scrape using the given set of command line arguments.
+ void runCommandLine(List<String> arguments) {
+ var parser = ArgParser(allowTrailingOptions: true);
+ parser.addOption('percent',
+ help: 'Only process a randomly selected percentage of files.');
+ parser.addFlag('tests',
+ defaultsTo: true, help: 'Process package test files.');
+ parser.addFlag('language-tests',
+ help: 'Process Dart SDK language test files.');
+ parser.addFlag('protobufs',
+ help: 'Process Dart files generated from protobufs.');
+ parser.addFlag('print-files',
+ defaultsTo: true, help: 'Print the path for each parsed file.');
+ parser.addFlag('print-errors',
+ defaultsTo: true, help: 'Print parse errors.');
+ parser.addFlag('help', negatable: false, help: 'Print help text.');
+
+ var results = parser.parse(arguments);
+
+ if (results['help'] as bool) {
+ var script = p.url.basename(Platform.script.toString());
+ print('Usage: $script [options] <paths...>');
+ print(parser.usage);
+ return;
+ }
+
+ _includeTests = results['tests'] as bool;
+ _includeLanguageTests = results['language-tests'] as bool;
+ _includeProtobufs = results['protobufs'] as bool;
+ _printFiles = results['print-files'] as bool;
+ _printErrors = results['print-errors'] as bool;
+
+ if (results.wasParsed('percent')) {
+ _percent = int.tryParse(results['percent'] as String);
+ if (_percent == null) {
+ print("--percent must be an integer, was '${results["percent"]}'.");
+ exit(1);
+ }
+ }
+
+ if (results.rest.isEmpty) {
+ print('Must pass at least one path to process.');
+ exit(1);
+ }
+
+ var watch = Stopwatch()..start();
+ for (var path in results.rest) {
+ _processPath(path);
+ }
+ watch.stop();
+
+ clearLine();
+ _histograms.forEach((name, histogram) {
+ histogram.printCounts(name);
+ });
+
+ var elapsed = _formatDuration(watch.elapsed);
+ var lines = _scrapedLineCount != 1 ? '$_scrapedLineCount lines' : '1 line';
+ var files = _scrapedFileCount != 1 ? '$_scrapedFileCount files' : '1 file';
+ print('Took $elapsed to scrape $lines in $files.');
+ }
+
+ /// Display [message], clearing the line if necessary.
+ void log(Object message) {
+ // TODO(rnystrom): Consider using cli_util package.
+ clearLine();
+ print(message);
+ }
+
+ /// Clear the current line if it needs it.
+ void clearLine() {
+ if (!_needClearLine) return;
+ stdout.write('\u001b[2K\r');
+ _needClearLine = false;
+ }
+
+ String _formatDuration(Duration duration) {
+ String pad(int width, int n) => n.toString().padLeft(width, '0');
+
+ if (duration.inMinutes >= 1) {
+ var minutes = duration.inMinutes;
+ var seconds = duration.inSeconds % 60;
+ var ms = duration.inMilliseconds % 1000;
+ return '$minutes:${pad(2, seconds)}.${pad(3, ms)}';
+ } else if (duration.inSeconds >= 1) {
+ return '${(duration.inMilliseconds / 1000).toStringAsFixed(3)}s';
+ } else {
+ return '${duration.inMilliseconds}ms';
+ }
+ }
+
+ void _processPath(String path) {
+ var random = math.Random();
+
+ if (File(path).existsSync()) {
+ _parseFile(File(path), path);
+ return;
+ }
+
+ for (var entry in Directory(path).listSync(recursive: true)) {
+ if (entry is! File) continue;
+
+ if (!entry.path.endsWith('.dart')) continue;
+
+ // For unknown reasons, some READMEs have a ".dart" extension. They aren't
+ // Dart files.
+ if (entry.path.endsWith('README.dart')) continue;
+
+ if (!_includeLanguageTests) {
+ if (entry.path.contains('/sdk/tests/')) continue;
+ if (entry.path.contains('/testcases/')) continue;
+ if (entry.path.contains('/sdk/runtime/tests/')) continue;
+ if (entry.path.contains('/linter/test/_data/')) continue;
+ if (entry.path.contains('/analyzer/test/')) continue;
+ if (entry.path.contains('/dev_compiler/test/')) continue;
+ if (entry.path.contains('/analyzer_cli/test/')) continue;
+ if (entry.path.contains('/analysis_server/test/')) continue;
+ if (entry.path.contains('/kernel/test/')) continue;
+ }
+
+ if (!_includeTests) {
+ if (entry.path.contains('/test/')) continue;
+ if (entry.path.endsWith('_test.dart')) continue;
+ }
+
+ // Don't care about cached packages.
+ if (entry.path.contains('sdk/third_party/pkg/')) continue;
+ if (entry.path.contains('sdk/third_party/pkg_tested/')) continue;
+ if (entry.path.contains('/.dart_tool/')) continue;
+
+ // Don't care about generated protobuf code.
+ if (!_includeProtobufs) {
+ if (entry.path.endsWith('.pb.dart')) continue;
+ if (entry.path.endsWith('.pbenum.dart')) continue;
+ }
+
+ if (_percent != null && random.nextInt(100) >= _percent) continue;
+
+ var relative = p.relative(entry.path, from: path);
+ _parseFile(entry as File, relative);
+ }
+ }
+
+ void _parseFile(File file, String shortPath) {
+ var source = file.readAsStringSync();
+
+ var errorListener = ErrorListener(this, _printErrors);
+
+ // Tokenize the source.
+ var reader = CharSequenceReader(source);
+ var stringSource = StringSource(source, file.path);
+ var scanner = Scanner(stringSource, reader, errorListener);
+ var startToken = scanner.tokenize();
+
+ // Parse it.
+ var parser = Parser(stringSource, errorListener,
+ featureSet: FeatureSet.latestLanguageVersion());
+ parser.enableOptionalNewAndConst = true;
+ parser.enableSetLiterals = true;
+
+ if (_printFiles) {
+ var line =
+ '[$_scrapedFileCount files, $_scrapedLineCount lines] ' '$shortPath';
+ if (Platform.isWindows) {
+ // No ANSI escape codes on Windows.
+ print(line);
+ } else {
+ // Overwrite the same line.
+ stdout.write('\u001b[2K\r'
+ '[$_scrapedFileCount files, $_scrapedLineCount lines] $shortPath');
+ _needClearLine = true;
+ }
+ }
+
+ AstNode node;
+ try {
+ node = parser.parseCompilationUnit(startToken);
+ } catch (error) {
+ print('Got exception parsing $shortPath:\n$error');
+ return;
+ }
+
+ var lineInfo = LineInfo(scanner.lineStarts);
+
+ _scrapedFileCount++;
+ _scrapedLineCount += lineInfo.lineCount;
+
+ for (var visitorFactory in _visitorFactories) {
+ var visitor = visitorFactory();
+ bindVisitor(visitor, this, shortPath, source, lineInfo);
+ node.accept(visitor);
+ }
+ }
+}
diff --git a/pkg/scrape/lib/src/error_listener.dart b/pkg/scrape/lib/src/error_listener.dart
new file mode 100644
index 0000000..69c9118
--- /dev/null
+++ b/pkg/scrape/lib/src/error_listener.dart
@@ -0,0 +1,21 @@
+// 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/error/error.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:scrape/scrape.dart';
+
+/// A simple [AnalysisErrorListener] that just collects the reported errors.
+class ErrorListener implements AnalysisErrorListener {
+ final Scrape _scrape;
+ final bool _printErrors;
+
+ ErrorListener(this._scrape, this._printErrors);
+
+ @override
+ void onError(AnalysisError error) {
+ if (_printErrors) {
+ _scrape.log(error);
+ }
+ }
+}
diff --git a/pkg/scrape/lib/src/histogram.dart b/pkg/scrape/lib/src/histogram.dart
new file mode 100644
index 0000000..12f2b9f
--- /dev/null
+++ b/pkg/scrape/lib/src/histogram.dart
@@ -0,0 +1,100 @@
+// 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 'dart:math' as math;
+
+enum SortOrder {
+ /// From fewest occurrences to most.
+ ascending,
+
+ /// From most occurrences to fewest.
+ descending,
+
+ /// Lexicographically sorted by name.
+ alphabetical,
+
+ /// Keys are parsed as integers and sorted by value.
+ numeric,
+}
+
+/// Counts occurrences of strings and displays the results as a histogram.
+class Histogram {
+ final Map<Object, int> _counts = {};
+ final SortOrder _order;
+ final bool _showBar;
+ final bool _showAll;
+ final int _minCount;
+
+ int get totalCount => _counts.values.fold(0, (a, b) => a + b);
+
+ Histogram({SortOrder order, bool showBar, bool showAll, int minCount})
+ : _order = order ?? SortOrder.descending,
+ _showBar = showBar ?? true,
+ _showAll = showAll ?? false,
+ _minCount = minCount ?? 0;
+
+ void add(Object item) {
+ _counts.putIfAbsent(item, () => 0);
+ _counts[item]++;
+ }
+
+ void printCounts(String label) {
+ var total = totalCount;
+ print('');
+ print('-- $label ($total total) --');
+
+ var keys = _counts.keys.toList();
+ switch (_order) {
+ case SortOrder.ascending:
+ keys.sort((a, b) => _counts[a].compareTo(_counts[b]));
+ break;
+ case SortOrder.descending:
+ keys.sort((a, b) => _counts[b].compareTo(_counts[a]));
+ break;
+ case SortOrder.alphabetical:
+ keys.sort();
+ break;
+ case SortOrder.numeric:
+ // TODO(rnystrom): Using string keys but treating them as integers is
+ // kind of hokey. But it keeps the [ScrapeVisitor] API simpler.
+ keys.sort((a, b) => (a as int).compareTo(b as int));
+ break;
+ }
+
+ var longest = keys.fold<int>(
+ 0, (length, key) => math.max(length, key.toString().length));
+ var barScale = 80 - 22 - longest;
+
+ var shown = 0;
+ var skipped = 0;
+ for (var object in keys) {
+ var count = _counts[object];
+ var countString = count.toString().padLeft(7);
+ var percent = 100 * count / total;
+ var percentString = percent.toStringAsFixed(3).padLeft(7);
+
+ if (_showAll || ((shown < 100 || percent >= 0.1) && count >= _minCount)) {
+ var line = '$countString ($percentString%): $object';
+ if (_showBar && barScale > 1) {
+ line = line.padRight(longest + 22);
+ line += '=' * (percent / 100 * barScale).ceil();
+ }
+ print(line);
+ shown++;
+ } else {
+ skipped++;
+ }
+ }
+
+ if (skipped > 0) print('And $skipped more less than 0.1%...');
+
+ // If we're counting numeric keys, show other statistics too.
+ if (_order == SortOrder.numeric && keys.isNotEmpty) {
+ var sum = keys.fold<int>(
+ 0, (result, key) => result + (key as int) * _counts[key]);
+ var average = sum / total;
+ var median = _counts[keys[keys.length ~/ 2]];
+ print('Sum $sum, average ${average.toStringAsFixed(3)}, median $median');
+ }
+ }
+}
diff --git a/pkg/scrape/lib/src/scrape_visitor.dart b/pkg/scrape/lib/src/scrape_visitor.dart
new file mode 100644
index 0000000..0b8a405
--- /dev/null
+++ b/pkg/scrape/lib/src/scrape_visitor.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/source/line_info.dart';
+
+import '../scrape.dart';
+
+/// Wire up [visitor] to [scrape] the given [path] containing [source] with
+/// [info].
+///
+/// This is a top-level function instead of an instance method so that we can
+/// hide it and not export it from scrape's public API. Only [Scrape] itself
+/// should call this. We bind separately instead of passing these through the
+/// [ScrapeVisitor] constructor so that subclasses of [ScrapeVisitor] don't
+/// need to define a pass-through constructor.
+void bindVisitor(ScrapeVisitor visitor, Scrape scrape, String path,
+ String source, LineInfo info) {
+ assert(visitor._scrape == null, 'Should only bind once.');
+ visitor._scrape = scrape;
+ visitor._path = path;
+ visitor._source = source;
+ visitor.lineInfo = info;
+}
+
+/// Base Visitor class with some utility functionality.
+class ScrapeVisitor extends RecursiveAstVisitor<void> {
+ // These final-ish fields are initialized by [bindVisitor()].
+ Scrape _scrape;
+ String _path;
+ String _source;
+ LineInfo lineInfo;
+
+ /// How many levels deep the visitor is currently nested inside build methods.
+ int _inFlutterBuildMethods = 0;
+
+ String get path => _path;
+
+ // TODO(rnystrom): Remove this in favor of using surveyor for these kinds of
+ // analyses.
+ /// Whether the visitor is currently inside a Flutter "build" method,
+ /// either directly or nested inside some other function inside one.
+ ///
+ /// This is only an approximate guess. It assumes a method is a "build"-like
+ /// method if it returns "Widget", or has a parameter list that starts with
+ /// "BuildContext context".
+ bool get isInFlutterBuildMethod => _inFlutterBuildMethods > 0;
+
+ bool _isBuildMethod(TypeAnnotation returnType, SimpleIdentifier name,
+ FormalParameterList parameters) {
+ var parameterString = parameters.toString();
+
+ if (returnType.toString() == 'void') return false;
+ if (parameterString.startsWith('(BuildContext context')) return true;
+ if (returnType.toString() == 'Widget') return true;
+
+ return false;
+ }
+
+ /// Add an occurrence of [item] to [histogram].
+ void record(String histogram, Object item) {
+ _scrape.record(histogram, item);
+ }
+
+ /// Write [message] to stdout, clearing the current line if needed.
+ void log(Object message) {
+ _scrape.log(message);
+ }
+
+ /// Print a nice representation of [node].
+ void printNode(AstNode node) {
+ log(nodeToString(node));
+ }
+
+ /// Generate a nice string representation of [node] include file path and
+ /// line information.
+ String nodeToString(AstNode node) {
+ var startLine = lineInfo.getLocation(node.offset).lineNumber;
+ var endLine = lineInfo.getLocation(node.end).lineNumber;
+
+ startLine = startLine.clamp(0, lineInfo.lineCount - 1) as int;
+ endLine = endLine.clamp(0, lineInfo.lineCount - 1) as int;
+
+ var buffer = StringBuffer();
+ buffer.writeln('// $path:$startLine');
+ for (var line = startLine; line <= endLine; line++) {
+ // Note that getLocation() returns 1-based lines, but getOffsetOfLine()
+ // expects 0-based.
+ var offset = lineInfo.getOffsetOfLine(line - 1);
+ // -1 to not include the newline.
+ var end = lineInfo.getOffsetOfLine(line) - 1;
+
+ buffer.writeln(_source.substring(offset, end));
+ }
+
+ return buffer.toString();
+ }
+
+ /// Get the line number of the code at [offset].
+ int getLine(int offset) => lineInfo.getLocation(offset).lineNumber;
+
+ /// Override this to execute custom code before visiting a Flutter build
+ /// method.
+ void beforeVisitBuildMethod(Declaration node) {}
+
+ /// Override this to execute custom code after visiting a Flutter build
+ /// method.
+ void afterVisitBuildMethod(Declaration node) {}
+
+ @override
+ void visitMethodDeclaration(MethodDeclaration node) {
+ var isBuild = _isBuildMethod(node.returnType, node.name, node.parameters);
+ if (isBuild) _inFlutterBuildMethods++;
+
+ try {
+ if (isBuild) beforeVisitBuildMethod(node);
+ super.visitMethodDeclaration(node);
+ if (isBuild) afterVisitBuildMethod(node);
+ } finally {
+ if (isBuild) _inFlutterBuildMethods--;
+ }
+ }
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ var isBuild = _isBuildMethod(
+ node.returnType, node.name, node.functionExpression.parameters);
+ if (isBuild) _inFlutterBuildMethods++;
+
+ try {
+ if (isBuild) beforeVisitBuildMethod(node);
+ super.visitFunctionDeclaration(node);
+ if (isBuild) afterVisitBuildMethod(node);
+ } finally {
+ if (isBuild) _inFlutterBuildMethods--;
+ }
+ }
+}
diff --git a/pkg/scrape/pubspec.yaml b/pkg/scrape/pubspec.yaml
new file mode 100644
index 0000000..a011644
--- /dev/null
+++ b/pkg/scrape/pubspec.yaml
@@ -0,0 +1,12 @@
+name: scrape
+description: Helper package for analyzing the syntax of Dart programs.
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+environment:
+ sdk: ^2.10.0
+dependencies:
+ args: ^1.6.0
+ analyzer: ^0.40.4
+ path: ^1.7.0
+dev_dependencies:
+ pedantic: ^1.9.2
diff --git a/tools/VERSION b/tools/VERSION
index 0168c17..0838600 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 11
PATCH 0
-PRERELEASE 236
+PRERELEASE 237
PRERELEASE_PATCH 0
\ No newline at end of file