Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

feat: add static code diagnostic prefer-static-class #1086

Merged
merged 4 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* feat: add static code diagnostic [`arguments-ordering`](https://dartcodemetrics.dev/docs/rules/common/arguments-ordering).
* feat: add method call chains support for [`ban-name`](https://dartcodemetrics.dev/docs/rules/common/ban-name).
* fix: update `dart_all.yaml` preset to contain missing rules.
* feat: add static code diagnostic [`prefer-static-class`](https://dartcodemetrics.dev/docs/rules/common/prefer-static-class).
* feat: ignore `hcwidget` annotations in ['avoid-returning-widgets'](https://dartcodemetrics.dev/docs/rules/common/avoid-returning-widgets) rule by default.

## 5.0.1

Expand Down
2 changes: 1 addition & 1 deletion lib/presets/dart_all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ dart_code_metrics:
- prefer-last
- prefer-match-file-name
- prefer-moving-to-variable
- prefer-trailing-comma
- prefer-static-class
- prefer-trailing-comma
- tag-name
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import 'rules_list/prefer_match_file_name/prefer_match_file_name_rule.dart';
import 'rules_list/prefer_moving_to_variable/prefer_moving_to_variable_rule.dart';
import 'rules_list/prefer_on_push_cd_strategy/prefer_on_push_cd_strategy_rule.dart';
import 'rules_list/prefer_single_widget_per_file/prefer_single_widget_per_file_rule.dart';
import 'rules_list/prefer_static_class/prefer_static_class_rule.dart';
import 'rules_list/prefer_trailing_comma/prefer_trailing_comma_rule.dart';
import 'rules_list/provide_correct_intl_args/provide_correct_intl_args_rule.dart';
import 'rules_list/tag_name/tag_name_rule.dart';
Expand Down Expand Up @@ -141,6 +142,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
PreferMovingToVariableRule.ruleId: PreferMovingToVariableRule.new,
PreferOnPushCdStrategyRule.ruleId: PreferOnPushCdStrategyRule.new,
PreferSingleWidgetPerFileRule.ruleId: PreferSingleWidgetPerFileRule.new,
PreferStaticClassRule.ruleId: PreferStaticClassRule.new,
PreferTrailingCommaRule.ruleId: PreferTrailingCommaRule.new,
ProvideCorrectIntlArgsRule.ruleId: ProvideCorrectIntlArgsRule.new,
TagNameRule.ruleId: TagNameRule.new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import '../../../models/issue.dart';
import '../../../models/severity.dart';
import '../../models/flutter_rule.dart';
import '../../rule_utils.dart';
import '../common_config.dart';

part 'config_parser.dart';
part 'visit_declaration.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,12 @@ class _ConfigParser {
static const _ignoredNamesConfig = 'ignored-names';
static const _ignoredAnnotationsConfig = 'ignored-annotations';

static const _defaultIgnoredAnnotations = [
'FunctionalWidget',
'swidget',
'hwidget',
];

static Iterable<String> getIgnoredNames(Map<String, Object> config) =>
_getIterable(config, _ignoredNamesConfig) ?? [];

static Iterable<String> getIgnoredAnnotations(Map<String, Object> config) =>
_getIterable(config, _ignoredAnnotationsConfig) ??
_defaultIgnoredAnnotations;
functionalWidgetAnnotations;

static Iterable<String>? _getIterable(
Map<String, Object> config,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const functionalWidgetAnnotations = [
'FunctionalWidget',
'swidget',
'hwidget',
'hcwidget',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
part of 'prefer_static_class_rule.dart';

class _ConfigParser {
static const _ignorePrivate = 'ignore-private';
static const _ignoreNames = 'ignore-names';
static const _ignoreAnnotations = 'ignore-annotations';

static const _defaultIgnorePrivate = false;
static const _defaultIgnoreNames = <String>[];
static const _defaultIgnoreAnnotations = [
...functionalWidgetAnnotations,
'riverpod',
];

static bool getIgnorePrivate(Map<String, Object> config) =>
config[_ignorePrivate] as bool? ?? _defaultIgnorePrivate;

static Iterable<String> getIgnoreNames(Map<String, Object> config) =>
_getIterable(config, _ignoreNames) ?? _defaultIgnoreNames;

static Iterable<String> getIgnoreAnnotations(Map<String, Object> config) =>
_getIterable(config, _ignoreAnnotations) ?? _defaultIgnoreAnnotations;

static Iterable<String>? _getIterable(
Map<String, Object> config,
String name,
) =>
config[name] is Iterable
? List<String>.from(config[name] as Iterable)
: null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// ignore_for_file: public_member_api_docs

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

import '../../../../../utils/node_utils.dart';
import '../../../lint_utils.dart';
import '../../../models/internal_resolved_unit_result.dart';
import '../../../models/issue.dart';
import '../../../models/severity.dart';
import '../../models/common_rule.dart';
import '../../rule_utils.dart';
import '../common_config.dart';

part 'config_parser.dart';
part 'visitor.dart';

class PreferStaticClassRule extends CommonRule {
static const String ruleId = 'prefer-static-class';

static const _warningMessage =
'Prefer declaring static class members instead of global functions, variables and constants.';

final bool _ignorePrivate;
final Iterable<String> _ignoreNames;
final Iterable<String> _ignoreAnnotations;

PreferStaticClassRule([Map<String, Object> config = const {}])
: _ignorePrivate = _ConfigParser.getIgnorePrivate(config),
_ignoreAnnotations = _ConfigParser.getIgnoreAnnotations(config),
_ignoreNames = _ConfigParser.getIgnoreNames(config),
super(
id: ruleId,
severity: readSeverity(config, Severity.style),
excludes: readExcludes(config),
includes: readIncludes(config),
);

@override
Map<String, Object?> toJson() {
final json = super.toJson();
json[_ConfigParser._ignorePrivate] = _ignorePrivate;
json[_ConfigParser._ignoreNames] = _ignoreNames.toList();
json[_ConfigParser._ignoreAnnotations] = _ignoreAnnotations.toList();

return json;
}

@override
Iterable<Issue> check(InternalResolvedUnitResult source) {
final visitor = _Visitor(
ignorePrivate: _ignorePrivate,
ignoreNames: _ignoreNames,
ignoreAnnotations: _ignoreAnnotations,
);

source.unit.visitChildren(visitor);

return visitor.declarations
.map((declaration) => createIssue(
rule: this,
location: nodeLocation(
node: declaration,
source: source,
),
message: _warningMessage,
))
.toList(growable: false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
part of 'prefer_static_class_rule.dart';

class _Visitor extends RecursiveAstVisitor<void> {
_Visitor({
required bool ignorePrivate,
required Iterable<String> ignoreNames,
required Iterable<String> ignoreAnnotations,
}) : _ignorePrivate = ignorePrivate,
_ignoreNames = ignoreNames.map(RegExp.new),
_ignoreAnnotations = ignoreAnnotations;

final bool _ignorePrivate;
final Iterable<RegExp> _ignoreNames;
final Iterable<String> _ignoreAnnotations;

final _declarations = <AstNode>[];

Iterable<AstNode> get declarations => _declarations;

@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
super.visitTopLevelVariableDeclaration(node);

if (!_hasIgnoredAnnotation(node) &&
!node.variables.variables
.every((variable) => _shouldIgnoreName(variable.name.lexeme))) {
_declarations.add(node);
}
}

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
final nodeName = node.name.lexeme;
if (node.parent is CompilationUnit &&
!_hasIgnoredAnnotation(node) &&
!_shouldIgnoreName(nodeName) &&
!isEntrypoint(nodeName, node.metadata)) {
_declarations.add(node);
}
}

bool _shouldIgnoreName(String name) =>
(_ignorePrivate && Identifier.isPrivateName(name)) ||
_ignoreNames.any((element) => element.hasMatch(name));

bool _hasIgnoredAnnotation(Declaration node) => node.metadata.any(
(node) =>
_ignoreAnnotations.contains(node.name.name) &&
node.atSign.type == TokenType.AT,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void main() {

RuleTestHelper.verifyIssues(
issues: issues,
startLines: [36, 38, 40, 42, 44, 47, 16, 53, 67],
startLines: [36, 38, 40, 42, 44, 47, 16, 53, 70],
startColumns: [5, 5, 5, 5, 5, 14, 3, 1, 1],
locationTexts: [
'_localBuildMyWidget()',
Expand Down Expand Up @@ -73,8 +73,8 @@ void main() {

RuleTestHelper.verifyIssues(
issues: issues,
startLines: [36, 38, 40, 16, 53, 57, 60, 63],
startColumns: [5, 5, 5, 3, 1, 1, 1, 1],
startLines: [36, 38, 40, 16, 53, 57, 60, 63, 66],
startColumns: [5, 5, 5, 3, 1, 1, 1, 1, 1],
locationTexts: [
'_localBuildMyWidget()',
'_getWidgetsIterable()',
Expand All @@ -89,6 +89,8 @@ void main() {
'Widget _getStatelessFunctionalWidget() => Container();',
'@hwidget\n'
'Widget _getHookFunctionalWidget() => Container();',
'@hcwidget\n'
'Widget _getHookConsumerFunctionalWidget() => Container();',
],
messages: [
'Avoid returning widgets from a function.',
Expand All @@ -99,6 +101,7 @@ void main() {
'Avoid returning widgets from a global function.',
'Avoid returning widgets from a global function.',
'Avoid returning widgets from a global function.',
'Avoid returning widgets from a global function.',
],
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ Widget _getStatelessFunctionalWidget() => Container();
@hwidget
Widget _getHookFunctionalWidget() => Container();

@hcwidget
Widget _getHookConsumerFunctionalWidget() => Container();

// LINT
@ignoredAnnotation
Widget _getWidgetWithIgnoredAnnotation() => Container();
Expand All @@ -73,6 +76,7 @@ class FunctionalWidget {

const swidget = FunctionalWidget();
const hwidget = FunctionalWidget();
const hcwidget = FunctionalWidget();

class IgnoredAnnotation {
const IgnoredAnnotation();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
void main() {}

Future<void> main() {}

class Example {
void classMethod() {}
static void staticClassMethod() {}
var classVariable = 42;
static var staticClassVariable = 42;
static final staticClassFinalVariable = 42;
static const classConstant = 42;

void _privateClassMethod() {}
static void _privateStaticClassMethod() {}
var _privateClassVariable = 42;
static var _privateStaticClassVariable = 42;
static final _privateStaticClassFinalVariable = 42;
static const _privateClassConstant = 42;
}

@FunctionalWidget
Widget functionalWidget() {}

@swidget
Widget statelessWidget() {}

@hwidget
Widget hookWidget() {}

@hcwidget
Widget hookConsumerWidget() {}

@riverpod
int riverpodFunction(riverpodFunction ref) => 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@ignoredAnnotation
void globalFunction() {}

@ignoredAnnotation
var globalVariable = 42;

@ignoredAnnotation
final globalFinalVariable = 42;

@ignoredAnnotation
const globalConstant = 42;

@ignoredAnnotation
void _privateGlobalFunction() {}

@ignoredAnnotation
var _privateGlobalVariable = 42;

@ignoredAnnotation
final _privateGlobalFinalVariable = 42;

@ignoredAnnotation
const _privateGlobalConstant = 42;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
final someRiverpodProvider = 42;

final useSomeRiverpodHook = 42;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
void _privateGlobalFunction() {}

var _privateGlobalVariable = 42;

final _privateGlobalFinalVariable = 42;

const _privateGlobalConstant = 42;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
void globalFunction() {} // LINT
var globalVariable = 42; // LINT
final globalFinalVariable = 42; // LINT
const globalConstant = 42; // LINT

void _privateGlobalFunction() {} // LINT
var _privateGlobalVariable = 42; // LINT
final _privateGlobalFinalVariable = 42; // LINT
const _privateGlobalConstant = 42; // LINT
Loading