From 746cef8d64bf388a7076b1a82db9ee257b3a941f Mon Sep 17 00:00:00 2001 From: Dmitry Krutskikh Date: Thu, 22 Jul 2021 07:58:15 +0300 Subject: [PATCH 1/5] feat: introduce new code diagnostics `prefer-single-widget-per-file-rule`. --- CHANGELOG.md | 1 + .../lint_analyzer/rules/rules_factory.dart | 3 ++ .../prefer_single_widget_per_file.dart | 53 +++++++++++++++++++ .../visitor.dart | 17 ++++++ .../examples/correct_example.dart | 8 +++ .../examples/flutter_defines.dart | 5 ++ .../examples/incorrect_example.dart | 37 +++++++++++++ .../prefer_single_widget_per_file_test.dart | 51 ++++++++++++++++++ 8 files changed, 175 insertions(+) create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart create mode 100644 test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart create mode 100644 test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/flutter_defines.dart create mode 100644 test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart create mode 100644 test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index cbcaf77b44..13fd869150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Add better monorepos support for CLI * Add support merge analysis options with detail rule config. +* Add static code diagnostics `prefer-single-widget-per-file`. ## 4.0.2-dev.1 diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart index 2cc042fb91..3036f970a6 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_factory.dart @@ -23,6 +23,7 @@ import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions import 'rules_list/prefer_extracting_callbacks/prefer_extracting_callbacks.dart'; import 'rules_list/prefer_intl_name/prefer_intl_name.dart'; import 'rules_list/prefer_on_push_cd_strategy/prefer_on_push_cd_strategy.dart'; +import 'rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart'; import 'rules_list/prefer_trailing_comma/prefer_trailing_comma.dart'; import 'rules_list/provide_correct_intl_args/provide_correct_intl_args.dart'; @@ -64,6 +65,8 @@ final _implementedRules = )>{ PreferIntlNameRule.ruleId: (config) => PreferIntlNameRule(config), PreferOnPushCdStrategyRule.ruleId: (config) => PreferOnPushCdStrategyRule(config), + PreferSingleWidgetPerFileRule.ruleId: (config) => + PreferSingleWidgetPerFileRule(config), PreferTrailingCommaRule.ruleId: (config) => PreferTrailingCommaRule(config), ProvideCorrectIntlArgsRule.ruleId: (config) => ProvideCorrectIntlArgsRule(config), diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart new file mode 100644 index 0000000000..7e9bb75be7 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart @@ -0,0 +1,53 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +import '../../../../../utils/node_utils.dart'; +import '../../../models/internal_resolved_unit_result.dart'; +import '../../../models/issue.dart'; +import '../../../models/severity.dart'; +import '../../flutter_rule_utils.dart'; +import '../../models/rule.dart'; +import '../../models/rule_documentation.dart'; +import '../../rule_utils.dart'; + +part 'visitor.dart'; + +class PreferSingleWidgetPerFileRule extends Rule { + static const String ruleId = 'prefer-single-widget-per-file'; + + static const _warningMessage = 'A maximum of widget per file is allowed.'; + + PreferSingleWidgetPerFileRule([Map config = const {}]) + : super( + id: ruleId, + documentation: const RuleDocumentation( + name: 'Prefer single widget per file', + brief: 'A file may not contain more than one widget.', + ), + severity: readSeverity(config, Severity.style), + excludes: readExcludes(config), + ); + + @override + Iterable check(InternalResolvedUnitResult source) { + final visitor = _Visitor(); + + source.unit.visitChildren(visitor); + + return visitor.nodes.length > 1 + ? visitor.nodes + .skip(1) + .map( + (node) => createIssue( + rule: this, + location: nodeLocation( + node: node, + source: source, + ), + message: _warningMessage, + ), + ) + .toList(growable: false) + : []; + } +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart new file mode 100644 index 0000000000..2e6f9109c7 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart @@ -0,0 +1,17 @@ +part of 'prefer_single_widget_per_file.dart'; + +class _Visitor extends SimpleAstVisitor { + final _nodes = []; + + Iterable get nodes => _nodes; + + @override + void visitClassDeclaration(ClassDeclaration node) { + super.visitClassDeclaration(node); + + final classType = node.extendsClause?.superclass.type; + if (isWidgetOrSubclass(classType)) { + _nodes.add(node); + } + } +} diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart new file mode 100644 index 0000000000..5112c891c7 --- /dev/null +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart @@ -0,0 +1,8 @@ +import 'flutter_defines.dart'; + +class someWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { +// ... + } +} diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/flutter_defines.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/flutter_defines.dart new file mode 100644 index 0000000000..5d6ff7d8f2 --- /dev/null +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/flutter_defines.dart @@ -0,0 +1,5 @@ +class Widget {} + +class StatefulWidget extends Widget {} + +class StatelessWidget extends Widget {} diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart new file mode 100644 index 0000000000..5dbe7a8109 --- /dev/null +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart @@ -0,0 +1,37 @@ +import 'flutter_defines.dart'; + +class someWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { +// ... + } +} + +// LINT +class someOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { +// ... + } +} + +// LINT +class _someOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { +// ... + } +} + +// LINT +class someStatefulWidget extends StatefulWidget { + @override + _someStatefulWidgetState createState() => _someStatefulWidgetState(); +} + +class _someStatefulWidgetState extends State { + @override + Widget build(BuildContext context) { +// ... + } +} diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart new file mode 100644 index 0000000000..f4bf56ac7b --- /dev/null +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart @@ -0,0 +1,51 @@ +@TestOn('vm') +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart'; +import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart'; +import 'package:test/test.dart'; + +import '../../../../../helpers/rule_test_helper.dart'; + +const _correctExamplePath = + 'prefer_single_widget_per_file/examples/correct_example.dart'; +const _incorrectExamplePath = + 'prefer_single_widget_per_file/examples/incorrect_example.dart'; + +void main() { + group('PreferSingleWidgetPerFileRule', () { + test('initialization', () async { + final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath); + final issues = PreferSingleWidgetPerFileRule().check(unit); + + RuleTestHelper.verifyInitialization( + issues: issues, + ruleId: 'prefer-single-widget-per-file', + severity: Severity.style, + ); + }); + + test('with default config reports about found issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_incorrectExamplePath); + final issues = PreferSingleWidgetPerFileRule().check(unit); + + RuleTestHelper.verifyIssues( + issues: issues, + startOffsets: [151, 275, 400], + startLines: [11, 19, 27], + startColumns: [1, 1, 1], + endOffsets: [265, 390, 535], + messages: [ + 'A maximum of widget per file is allowed.', + 'A maximum of widget per file is allowed.', + 'A maximum of widget per file is allowed.', + ], + ); + }); + + test('with default config reports no issues', () async { + final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath); + final issues = PreferSingleWidgetPerFileRule().check(unit); + + RuleTestHelper.verifyNoIssues(issues); + }); + }); +} From c9a435d00cbebd67a509faf9c2a6d717be7ac6aa Mon Sep 17 00:00:00 2001 From: Dmitry Krutskikh Date: Thu, 22 Jul 2021 08:13:54 +0300 Subject: [PATCH 2/5] documentation --- README.md | 1 + .../prefer-single-widget-per-file-rule.md | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 doc/rules/prefer-single-widget-per-file-rule.md diff --git a/README.md b/README.md index 22d7aa6463..23e9caf09f 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,7 @@ Rules configuration is [described here](#configuring-a-rules-entry). - [avoid-unnecessary-setstate](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/avoid-unnecessary-setstate.md) - [avoid-wrapping-in-padding](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/avoid-wrapping-in-padding.md) - [prefer-extracting-callbacks](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-extracting-callbacks.md)   [![Configurable](https://img.shields.io/badge/-configurable-informational)](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-extracting-callbacks.md#config-example) +- [prefer-single-widget-per-file](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-single-widget-per-file.md) ### Intl specific diff --git a/doc/rules/prefer-single-widget-per-file-rule.md b/doc/rules/prefer-single-widget-per-file-rule.md new file mode 100644 index 0000000000..fc6ac7e113 --- /dev/null +++ b/doc/rules/prefer-single-widget-per-file-rule.md @@ -0,0 +1,85 @@ +# Prefer single widget per file + +## Rule id + +prefer-trailing-comma + +## Description + +A file may not contain more than one widget. + +Ensures that files have a single responsibility so that that widget each exist in their own files. + +```dart +function('some string', () { + return; +}); +``` + +### Example + +Bad: + +some_widgets.dart + +```dart +class someWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} + +// LINT +class someOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} + +// LINT +class _someOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} + +// LINT +class someStatefulWidget extends StatefulWidget { + @override + _someStatefulWidgetState createState() => _someStatefulWidgetState(); +} + +class _someStatefulWidgetState extends State { + @override + Widget build(BuildContext context) { + ... + } +} +``` + +Good: + +some_widget.dart + +```dart +class someWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} +``` + +some_other_widget.dart + +```dart +class someOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} +``` From a2110d4cc0518ef42054077f9176f616e5af1698 Mon Sep 17 00:00:00 2001 From: Dmitry Krutskikh Date: Fri, 23 Jul 2021 00:49:20 +0300 Subject: [PATCH 3/5] fixes after review --- CHANGELOG.md | 5 +- .../prefer-single-widget-per-file-rule.md | 85 ------------------- doc/rules/prefer-single-widget-per-file.md | 79 +++++++++++++++++ .../prefer_single_widget_per_file.dart | 33 ++++--- .../visitor.dart | 3 +- .../correct_statefull_widget_example.dart | 13 +++ ... => correct_stateless_widget_example.dart} | 2 +- .../examples/incorrect_example.dart | 10 +-- .../prefer_single_widget_per_file_test.dart | 38 ++++++--- 9 files changed, 147 insertions(+), 121 deletions(-) delete mode 100644 doc/rules/prefer-single-widget-per-file-rule.md create mode 100644 doc/rules/prefer-single-widget-per-file.md create mode 100644 test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_statefull_widget_example.dart rename test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/{correct_example.dart => correct_stateless_widget_example.dart} (69%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13fd869150..e2d3139837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ # Changelog +## Unresolved + +* Add static code diagnostic `prefer-single-widget-per-file`. + ## 4.1.0 * Add better monorepos support for CLI * Add support merge analysis options with detail rule config. -* Add static code diagnostics `prefer-single-widget-per-file`. ## 4.0.2-dev.1 diff --git a/doc/rules/prefer-single-widget-per-file-rule.md b/doc/rules/prefer-single-widget-per-file-rule.md deleted file mode 100644 index fc6ac7e113..0000000000 --- a/doc/rules/prefer-single-widget-per-file-rule.md +++ /dev/null @@ -1,85 +0,0 @@ -# Prefer single widget per file - -## Rule id - -prefer-trailing-comma - -## Description - -A file may not contain more than one widget. - -Ensures that files have a single responsibility so that that widget each exist in their own files. - -```dart -function('some string', () { - return; -}); -``` - -### Example - -Bad: - -some_widgets.dart - -```dart -class someWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - ... - } -} - -// LINT -class someOtherWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - ... - } -} - -// LINT -class _someOtherWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - ... - } -} - -// LINT -class someStatefulWidget extends StatefulWidget { - @override - _someStatefulWidgetState createState() => _someStatefulWidgetState(); -} - -class _someStatefulWidgetState extends State { - @override - Widget build(BuildContext context) { - ... - } -} -``` - -Good: - -some_widget.dart - -```dart -class someWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - ... - } -} -``` - -some_other_widget.dart - -```dart -class someOtherWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - ... - } -} -``` diff --git a/doc/rules/prefer-single-widget-per-file.md b/doc/rules/prefer-single-widget-per-file.md new file mode 100644 index 0000000000..5a6652db4d --- /dev/null +++ b/doc/rules/prefer-single-widget-per-file.md @@ -0,0 +1,79 @@ +# Prefer single widget per file + +## Rule id + +prefer-single-widget-per-file + +## Description + +Warns when a file contains more than a single widget. + +Ensures that files have a single responsibility so that each widget exists in its own file. + +### Example + +Bad: + +some_widgets.dart + +```dart +class SomeWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} + +// LINT +class SomeOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} + +// LINT +class _SomeOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} + +// LINT +class SomeStatefulWidget extends StatefulWidget { + @override + _SomeStatefulWidgetState createState() => _someStatefulWidgetState(); +} + +class _SomeStatefulWidgetState extends State { + @override + Widget build(BuildContext context) { + ... + } +} +``` + +Good: + +some_widget.dart + +```dart +class SomeWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} +``` + +some_other_widget.dart + +```dart +class SomeOtherWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + ... + } +} +``` diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart index 7e9bb75be7..d03f1f1527 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart @@ -15,14 +15,14 @@ part 'visitor.dart'; class PreferSingleWidgetPerFileRule extends Rule { static const String ruleId = 'prefer-single-widget-per-file'; - static const _warningMessage = 'A maximum of widget per file is allowed.'; + static const _warningMessage = 'Only a single widget per file is allowed.'; PreferSingleWidgetPerFileRule([Map config = const {}]) : super( id: ruleId, documentation: const RuleDocumentation( - name: 'Prefer single widget per file', - brief: 'A file may not contain more than one widget.', + name: 'Prefer a single widget per file', + brief: 'Warns when a file contains more than a single widget.', ), severity: readSeverity(config, Severity.style), excludes: readExcludes(config), @@ -34,20 +34,17 @@ class PreferSingleWidgetPerFileRule extends Rule { source.unit.visitChildren(visitor); - return visitor.nodes.length > 1 - ? visitor.nodes - .skip(1) - .map( - (node) => createIssue( - rule: this, - location: nodeLocation( - node: node, - source: source, - ), - message: _warningMessage, - ), - ) - .toList(growable: false) - : []; + return visitor.nodes + .map( + (node) => createIssue( + rule: this, + location: nodeLocation( + node: node, + source: source, + ), + message: _warningMessage, + ), + ) + .toList(growable: false); } } diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart index 2e6f9109c7..85b64c33cd 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart @@ -3,7 +3,8 @@ part of 'prefer_single_widget_per_file.dart'; class _Visitor extends SimpleAstVisitor { final _nodes = []; - Iterable get nodes => _nodes; + Iterable get nodes => + _nodes.length > 1 ? _nodes.skip(1) : []; @override void visitClassDeclaration(ClassDeclaration node) { diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_statefull_widget_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_statefull_widget_example.dart new file mode 100644 index 0000000000..7f27b8ccd3 --- /dev/null +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_statefull_widget_example.dart @@ -0,0 +1,13 @@ +import 'flutter_defines.dart'; + +class SomeStatefulWidget extends StatefulWidget { + @override + _someStatefulWidgetState createState() => _someStatefulWidgetState(); +} + +class _SomeStatefulWidgetState extends State { + @override + Widget build(BuildContext context) { +// ... + } +} diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_stateless_widget_example.dart similarity index 69% rename from test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart rename to test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_stateless_widget_example.dart index 5112c891c7..d6250109ab 100644 --- a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_example.dart +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/correct_stateless_widget_example.dart @@ -1,6 +1,6 @@ import 'flutter_defines.dart'; -class someWidget extends StatelessWidget { +class SomeWidget extends StatelessWidget { @override Widget build(BuildContext context) { // ... diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart index 5dbe7a8109..5a41bb3e61 100644 --- a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/incorrect_example.dart @@ -1,6 +1,6 @@ import 'flutter_defines.dart'; -class someWidget extends StatelessWidget { +class SomeWidget extends StatelessWidget { @override Widget build(BuildContext context) { // ... @@ -8,7 +8,7 @@ class someWidget extends StatelessWidget { } // LINT -class someOtherWidget extends StatelessWidget { +class SomeOtherWidget extends StatelessWidget { @override Widget build(BuildContext context) { // ... @@ -16,7 +16,7 @@ class someOtherWidget extends StatelessWidget { } // LINT -class _someOtherWidget extends StatelessWidget { +class _SomeOtherWidget extends StatelessWidget { @override Widget build(BuildContext context) { // ... @@ -24,12 +24,12 @@ class _someOtherWidget extends StatelessWidget { } // LINT -class someStatefulWidget extends StatefulWidget { +class SomeStatefulWidget extends StatefulWidget { @override _someStatefulWidgetState createState() => _someStatefulWidgetState(); } -class _someStatefulWidgetState extends State { +class _SomeStatefulWidgetState extends State { @override Widget build(BuildContext context) { // ... diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart index f4bf56ac7b..5a22c4fd3c 100644 --- a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart @@ -5,15 +5,19 @@ import 'package:test/test.dart'; import '../../../../../helpers/rule_test_helper.dart'; -const _correctExamplePath = - 'prefer_single_widget_per_file/examples/correct_example.dart'; +const _correctStatefullWidgetExamplePath = + 'prefer_single_widget_per_file/examples/correct_statefull_widget_example.dart'; +const _correctStatelessWidgetExamplePath = + 'prefer_single_widget_per_file/examples/correct_stateless_widget_example.dart'; const _incorrectExamplePath = 'prefer_single_widget_per_file/examples/incorrect_example.dart'; void main() { group('PreferSingleWidgetPerFileRule', () { test('initialization', () async { - final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath); + final unit = await RuleTestHelper.resolveFromFile( + _correctStatefullWidgetExamplePath, + ); final issues = PreferSingleWidgetPerFileRule().check(unit); RuleTestHelper.verifyInitialization( @@ -34,18 +38,32 @@ void main() { startColumns: [1, 1, 1], endOffsets: [265, 390, 535], messages: [ - 'A maximum of widget per file is allowed.', - 'A maximum of widget per file is allowed.', - 'A maximum of widget per file is allowed.', + 'Only a single widget per file is allowed.', + 'Only a single widget per file is allowed.', + 'Only a single widget per file is allowed.', ], ); }); - test('with default config reports no issues', () async { - final unit = await RuleTestHelper.resolveFromFile(_correctExamplePath); - final issues = PreferSingleWidgetPerFileRule().check(unit); + group('with default config reports no issues for', () { + test('single statefull widget', () async { + final statefullWidgetUnit = await RuleTestHelper.resolveFromFile( + _correctStatefullWidgetExamplePath, + ); + final issues = + PreferSingleWidgetPerFileRule().check(statefullWidgetUnit); + + RuleTestHelper.verifyNoIssues(issues); + }); + test('single stateless widget', () async { + final statelessWidgetUnit = await RuleTestHelper.resolveFromFile( + _correctStatelessWidgetExamplePath, + ); + final issues = + PreferSingleWidgetPerFileRule().check(statelessWidgetUnit); - RuleTestHelper.verifyNoIssues(issues); + RuleTestHelper.verifyNoIssues(issues); + }); }); }); } From f2f922583e6ccd1501e3d39671c7b6027cf65e7e Mon Sep 17 00:00:00 2001 From: Dmitry Krutskikh Date: Sat, 31 Jul 2021 12:53:27 +0300 Subject: [PATCH 4/5] feat: add support ignore private widgets --- README.md | 2 +- doc/rules/prefer-single-widget-per-file.md | 13 +++++++ .../config_parser.dart | 8 +++++ .../prefer_single_widget_per_file.dart | 8 +++-- .../visitor.dart | 8 ++++- .../examples/multi_widgets_example.dart | 27 +++++++++++++++ .../prefer_single_widget_per_file_test.dart | 34 +++++++++++++++++++ 7 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/config_parser.dart create mode 100644 test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart diff --git a/README.md b/README.md index 23e9caf09f..7d12da2860 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ Rules configuration is [described here](#configuring-a-rules-entry). - [avoid-unnecessary-setstate](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/avoid-unnecessary-setstate.md) - [avoid-wrapping-in-padding](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/avoid-wrapping-in-padding.md) - [prefer-extracting-callbacks](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-extracting-callbacks.md)   [![Configurable](https://img.shields.io/badge/-configurable-informational)](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-extracting-callbacks.md#config-example) -- [prefer-single-widget-per-file](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-single-widget-per-file.md) +- [prefer-single-widget-per-file](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-single-widget-per-file.md)   [![Configurable](https://img.shields.io/badge/-configurable-informational)](https://github.com/dart-code-checker/dart-code-metrics/blob/master/doc/rules/prefer-single-widget-per-file.md#config-example) ### Intl specific diff --git a/doc/rules/prefer-single-widget-per-file.md b/doc/rules/prefer-single-widget-per-file.md index 5a6652db4d..bf9d1421d5 100644 --- a/doc/rules/prefer-single-widget-per-file.md +++ b/doc/rules/prefer-single-widget-per-file.md @@ -1,5 +1,7 @@ # Prefer single widget per file +![Configurable](https://img.shields.io/badge/-configurable-informational) + ## Rule id prefer-single-widget-per-file @@ -10,6 +12,17 @@ Warns when a file contains more than a single widget. Ensures that files have a single responsibility so that each widget exists in its own file. +### Config example + +```yaml +dart_code_metrics: + ... + rules: + ... + - prefer-single-widget-per-file: + ignore-private-widgets: true +``` + ### Example Bad: diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/config_parser.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/config_parser.dart new file mode 100644 index 0000000000..ea1ed12d41 --- /dev/null +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/config_parser.dart @@ -0,0 +1,8 @@ +part of 'prefer_single_widget_per_file.dart'; + +class _ConfigParser { + static const _ignorePrivateWidgetsName = 'ignore-private-widgets'; + + static bool parseIgnorePrivateWidgets(Map config) => + config[_ignorePrivateWidgetsName] == true; +} diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart index d03f1f1527..ebc09b378a 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file.dart @@ -10,6 +10,7 @@ import '../../models/rule.dart'; import '../../models/rule_documentation.dart'; import '../../rule_utils.dart'; +part 'config_parser.dart'; part 'visitor.dart'; class PreferSingleWidgetPerFileRule extends Rule { @@ -17,8 +18,11 @@ class PreferSingleWidgetPerFileRule extends Rule { static const _warningMessage = 'Only a single widget per file is allowed.'; + final bool _ignorePrivateWidgets; + PreferSingleWidgetPerFileRule([Map config = const {}]) - : super( + : _ignorePrivateWidgets = _ConfigParser.parseIgnorePrivateWidgets(config), + super( id: ruleId, documentation: const RuleDocumentation( name: 'Prefer a single widget per file', @@ -30,7 +34,7 @@ class PreferSingleWidgetPerFileRule extends Rule { @override Iterable check(InternalResolvedUnitResult source) { - final visitor = _Visitor(); + final visitor = _Visitor(ignorePrivateWidgets: _ignorePrivateWidgets); source.unit.visitChildren(visitor); diff --git a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart index 85b64c33cd..0edf5d0f64 100644 --- a/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart +++ b/lib/src/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/visitor.dart @@ -1,8 +1,13 @@ part of 'prefer_single_widget_per_file.dart'; class _Visitor extends SimpleAstVisitor { + final bool _ignorePrivateWidgets; + final _nodes = []; + _Visitor({required bool ignorePrivateWidgets}) + : _ignorePrivateWidgets = ignorePrivateWidgets; + Iterable get nodes => _nodes.length > 1 ? _nodes.skip(1) : []; @@ -11,7 +16,8 @@ class _Visitor extends SimpleAstVisitor { super.visitClassDeclaration(node); final classType = node.extendsClause?.superclass.type; - if (isWidgetOrSubclass(classType)) { + if (isWidgetOrSubclass(classType) && + (!_ignorePrivateWidgets || !Identifier.isPrivateName(node.name.name))) { _nodes.add(node); } } diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart new file mode 100644 index 0000000000..74a38c77e8 --- /dev/null +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart @@ -0,0 +1,27 @@ +import 'flutter_defines.dart'; + +class ExampleWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { +// ... + } +} + +class _privateWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { +// ... + } +} + +class _anotherPrivateWidget extends StatefulWidget { + @override + _someStatefulWidgetState createState() => _someStatefulWidgetState(); +} + +class _SomeStatefulWidgetState extends State { + @override + Widget build(BuildContext context) { +// ... + } +} diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart index 5a22c4fd3c..7954e3288a 100644 --- a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_test.dart @@ -11,6 +11,8 @@ const _correctStatelessWidgetExamplePath = 'prefer_single_widget_per_file/examples/correct_stateless_widget_example.dart'; const _incorrectExamplePath = 'prefer_single_widget_per_file/examples/incorrect_example.dart'; +const _multiWidgetsExamplePath = + 'prefer_single_widget_per_file/examples/multi_widgets_example.dart'; void main() { group('PreferSingleWidgetPerFileRule', () { @@ -65,5 +67,37 @@ void main() { RuleTestHelper.verifyNoIssues(issues); }); }); + + group('analyze multi widget file', () { + test('with default config', () async { + final multiWidgetsUnit = await RuleTestHelper.resolveFromFile( + _multiWidgetsExamplePath, + ); + final issues = PreferSingleWidgetPerFileRule().check(multiWidgetsUnit); + + RuleTestHelper.verifyIssues( + issues: issues, + startOffsets: [146, 261], + startLines: [10, 17], + startColumns: [1, 1], + endOffsets: [259, 399], + messages: [ + 'Only a single widget per file is allowed.', + 'Only a single widget per file is allowed.', + ], + ); + }); + + test('with custom config', () async { + final multiWidgetsUnit = await RuleTestHelper.resolveFromFile( + _multiWidgetsExamplePath, + ); + final issues = + PreferSingleWidgetPerFileRule({'ignore-private-widgets': true}) + .check(multiWidgetsUnit); + + RuleTestHelper.verifyNoIssues(issues); + }); + }); }); } From 45960e144e0a0c726ec8846d501db47e03ced134 Mon Sep 17 00:00:00 2001 From: Dmitry Krutskikh Date: Sun, 1 Aug 2021 16:58:35 +0300 Subject: [PATCH 5/5] fixes after review --- .../examples/multi_widgets_example.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart index 74a38c77e8..cfb47fd9f1 100644 --- a/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart +++ b/test/analyzers/lint_analyzer/rules/rules_list/prefer_single_widget_per_file/examples/multi_widgets_example.dart @@ -7,16 +7,16 @@ class ExampleWidget extends StatelessWidget { } } -class _privateWidget extends StatelessWidget { +class _PrivateWidget extends StatelessWidget { @override Widget build(BuildContext context) { // ... } } -class _anotherPrivateWidget extends StatefulWidget { +class _AnotherPrivateWidget extends StatefulWidget { @override - _someStatefulWidgetState createState() => _someStatefulWidgetState(); + _SomeStatefulWidgetState createState() => _SomeStatefulWidgetState(); } class _SomeStatefulWidgetState extends State {