Skip to content

Commit

Permalink
Merge a100c3e into f31a8f7
Browse files Browse the repository at this point in the history
  • Loading branch information
domesticmouse committed Oct 27, 2021
2 parents f31a8f7 + a100c3e commit e8d6869
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 22 deletions.
1 change: 1 addition & 0 deletions example/all.yaml
Expand Up @@ -146,6 +146,7 @@ linter:
- recursive_getters
- require_trailing_commas
- sized_box_for_whitespace
- sized_box_shrink_expand
- slash_for_doc_comments
- sort_child_properties_last
- sort_constructors_first
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Expand Up @@ -150,6 +150,7 @@ import 'rules/public_member_api_docs.dart';
import 'rules/recursive_getters.dart';
import 'rules/require_trailing_commas.dart';
import 'rules/sized_box_for_whitespace.dart';
import 'rules/sized_box_shrink_expand.dart';
import 'rules/slash_for_doc_comments.dart';
import 'rules/sort_child_properties_last.dart';
import 'rules/sort_constructors_first.dart';
Expand Down Expand Up @@ -348,6 +349,7 @@ void registerLintRules({bool inTestMode = false}) {
..register(RecursiveGetters())
..register(RequireTrailingCommas())
..register(SizedBoxForWhitespace())
..register(SizedBoxShrinkExpand())
..register(SlashForDocComments())
..register(SortChildPropertiesLast())
..register(SortConstructorsFirst())
Expand Down
46 changes: 24 additions & 22 deletions lib/src/rules/sized_box_for_whitespace.dart
Expand Up @@ -74,40 +74,42 @@ class _Visitor extends SimpleAstVisitor {
return;
}

var visitor = _WidthOrHeightArgumentVisitor();
node.visitChildren(visitor);
if (visitor.seenIncompatibleParams) {
var data = _ArgumentData(node.argumentList);

if (data.incompatibleParamsFound || data.positionalArgumentFound) {
return;
}
if (visitor.seenChild && (visitor.seenWidth || visitor.seenHeight) ||
visitor.seenWidth && visitor.seenHeight) {
if (data.seenChild && (data.seenWidth || data.seenHeight) ||
data.seenWidth && data.seenHeight) {
rule.reportLint(node.constructorName);
}
}
}

class _WidthOrHeightArgumentVisitor extends SimpleAstVisitor<void> {
var seenWidth = false;
var seenHeight = false;
var seenChild = false;
var seenIncompatibleParams = false;

@override
void visitArgumentList(ArgumentList node) {
for (var name in node.arguments
.cast<NamedExpression>()
.map((arg) => arg.name.label.name)) {
if (name == 'width') {
class _ArgumentData {
_ArgumentData(ArgumentList node) {
for (var argument in node.arguments) {
if (argument is! NamedExpression) {
positionalArgumentFound = true;
return;
}
if (argument.name.label.name == 'width') {
seenWidth = true;
} else if (name == 'height') {
} else if (argument.name.label.name == 'height') {
seenHeight = true;
} else if (name == 'child') {
} else if (argument.name.label.name == 'child') {
seenChild = true;
} else if (name == 'key') {
// key doesn't matter (both SiezdBox and Container have it)
} else if (argument.name.label.name == 'key') {
// key doesn't matter (both SizedBox and Container have it)
} else {
seenIncompatibleParams = true;
incompatibleParamsFound = true;
}
}
}

var incompatibleParamsFound = false;
var positionalArgumentFound = false;
var seenWidth = false;
var seenHeight = false;
var seenChild = false;
}
141 changes: 141 additions & 0 deletions lib/src/rules/sized_box_shrink_expand.dart
@@ -0,0 +1,141 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../analyzer.dart';
import '../util/flutter_utils.dart';

const _details =
r'''Use `SizedBox.shrink(...)` and `SizedBox.expand(...)` constructors appropriately.
The `SizedBox.shrink(...)` and `SizedBox.expand(...)` constructors should be used
instead of the more general `SizedBox(...)` constructor when the named constructors
capture the intent of the code more succinctly.
**Examples**
**BAD:**
```dart
Widget buildLogo() {
return SizedBox(
height: 0,
width: 0,
child:const MyLogo(),
);
}
```
```dart
Widget buildLogo() {
return SizedBox(
height: double.infinity,
width: double.infinity,
child:const MyLogo(),
);
}
```
**GOOD:**
```dart
Widget buildLogo() {
return SizedBox.shrink(
child:const MyLogo(),
);
}
```
```dart
Widget buildLogo() {
return SizedBox.expand(
child:const MyLogo(),
);
}
```
''';

class SizedBoxShrinkExpand extends LintRule {
SizedBoxShrinkExpand()
: super(
name: 'sized_box_shrink_expand',
description: 'Use SizedBox shrink and expand named constructors.',
details: _details,
group: Group.style);

@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this);

registry.addInstanceCreationExpression(this, visitor);
}
}

class _Visitor extends SimpleAstVisitor {
final SizedBoxShrinkExpand rule;

_Visitor(this.rule);

static const LintCode useShrink = LintCode(
'sized_box_shrink_expand', 'Use the `SizedBox.shrink` constructor.');
static const LintCode useExpand = LintCode(
'sized_box_shrink_expand', 'Use the `SizedBox.expand` constructor.');

@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
// Only interested in the default constructor for the SizedBox widget
if (!isExactWidgetTypeSizedBox(node.staticType) ||
node.constructorName.name != null) {
return;
}

var data = _ArgumentData(node.argumentList);
if (data.positionalArgumentFound) {
return;
}
if (data.width == 0 && data.height == 0) {
rule.reportLint(node.constructorName, errorCode: useShrink);
} else if (data.width == double.infinity &&
data.height == double.infinity) {
rule.reportLint(node.constructorName, errorCode: useExpand);
}
}
}

class _ArgumentData {
_ArgumentData(ArgumentList node) {
for (var argument in node.arguments) {
if (argument is! NamedExpression) {
positionalArgumentFound = true;
return;
}
if (argument.name.label.name == 'width') {
width = _argumentValue(argument.expression);
} else if (argument.name.label.name == 'height') {
height = _argumentValue(argument.expression);
}
}
}

double? _argumentValue(Expression argument) {
if (argument is IntegerLiteral) {
return argument.value?.toDouble();
} else if (argument is DoubleLiteral) {
return argument.value;
} else if (argument is PropertyAccess &&
(argument.propertyName.name == 'infinity' ||
argument.propertyName.name == 'INFINITY')) {
var target = argument.target;
if (target is SimpleIdentifier && target.name == 'double') {
return double.infinity;
}
}
return null;
}

var positionalArgumentFound = false;
double? width;
double? height;
}
10 changes: 10 additions & 0 deletions lib/src/util/flutter_utils.dart
Expand Up @@ -31,6 +31,9 @@ bool isExactWidget(ClassElement element) => _flutter.isExactWidget(element);
bool isExactWidgetTypeContainer(DartType? type) =>
_flutter.isExactWidgetTypeContainer(type);

bool isExactWidgetTypeSizedBox(DartType? type) =>
_flutter.isExactWidgetTypeSizedBox(type);

bool isKDebugMode(Element? element) => _flutter.isKDebugMode(element);

bool isStatefulWidget(ClassElement? element) =>
Expand All @@ -56,16 +59,19 @@ class _Flutter {
static const _nameStatefulWidget = 'StatefulWidget';
static const _nameWidget = 'Widget';
static const _nameContainer = 'Container';
static const _nameSizedBox = 'SizedBox';

final String packageName;
final String widgetsUri;

final Uri _uriBasic;
final Uri _uriContainer;
final Uri _uriFramework;
final Uri _uriFoundation;

_Flutter(this.packageName, String uriPrefix)
: widgetsUri = '$uriPrefix/widgets.dart',
_uriBasic = Uri.parse('$uriPrefix/src/widgets/basic.dart'),
_uriContainer = Uri.parse('$uriPrefix/src/widgets/container.dart'),
_uriFramework = Uri.parse('$uriPrefix/src/widgets/framework.dart'),
_uriFoundation = Uri.parse('$uriPrefix/src/foundation/constants.dart');
Expand Down Expand Up @@ -99,6 +105,10 @@ class _Flutter {
type is InterfaceType &&
_isExactWidget(type.element, _nameContainer, _uriContainer);

bool isExactWidgetTypeSizedBox(DartType? type) =>
type is InterfaceType &&
_isExactWidget(type.element, _nameSizedBox, _uriBasic);

bool isKDebugMode(Element? element) =>
element != null &&
element.name == 'kDebugMode' &&
Expand Down
8 changes: 8 additions & 0 deletions test_data/mock_packages/flutter/lib/src/widgets/basic.dart
Expand Up @@ -122,6 +122,14 @@ class Row extends Flex {
});
}

class SizedBox extends SingleChildRenderObjectWidget {
const SizedBox({Key? key, double? width, double? height, Widget? child});
const SizedBox.expand({Key? key, Widget? child});
const SizedBox.shrink({Key? key, Widget? child});
SizedBox.fromSize({Key? key, Widget? child, Size? size});
const SizedBox.square({Key? key, Widget? child, double? dimension});
}

class Transform extends SingleChildRenderObjectWidget {
const Transform({
Key key,
Expand Down
85 changes: 85 additions & 0 deletions test_data/rules/sized_box_shrink_expand.dart
@@ -0,0 +1,85 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// test w/ `dart test -N sized_box_shrink_expand`

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Widget sizedBoxWithZeroWidthZeroHeight() {
return SizedBox(
// LINT
height: 0,
width: 0,
child: Container(),
);
}

Widget sizedBoxWithInfiniteWidthInfiniteHeight() {
return SizedBox(
// LINT
height: double.INFINITY,
width: double.INFINITY,
child: Container(),
);
}

Widget sizedBoxWithInfiniteWidthzeroHeight() {
return SizedBox(
// OK
height: 0,
width: double.INFINITY,
child: Container(),
);
}

Widget sizedBoxWithZeroWidthInfiniteHeight() {
return SizedBox(
// OK
height: double.INFINITY,
width: 0,
child: Container(),
);
}

Widget sizedBoxWithMixedWidthsAndHeights() {
return SizedBox(
// OK
height: 26,
width: 42,
child: Container(),
);
}

Widget sizedBoxWithZeroWidth() {
return SizedBox(
// OK
width: 0,
child: Container(),
);
}

Widget sizedBoxWithInfiniteWidth() {
return SizedBox(
// OK
width: double.INFINITY,
child: Container(),
);
}

Widget sizedBoxWithZeroHeight() {
return SizedBox(
// OK
height: 0,
child: Container(),
);
}

Widget sizedBoxWithInfiniteHeight() {
return SizedBox(
// OK
height: double.INFINITY,
child: Container(),
);
}

0 comments on commit e8d6869

Please sign in to comment.