Skip to content
Draft
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
22 changes: 12 additions & 10 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import 'package:solid_lints/src/common/parameter_parser/analysis_options_loader.
import 'package:solid_lints/src/lints/avoid_debug_print_in_release/avoid_debug_print_in_release_rule.dart';
import 'package:solid_lints/src/lints/avoid_global_state/avoid_global_state_rule.dart';
import 'package:solid_lints/src/lints/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart';

/// The entry point for the Solid Lints analyser server plugin.
///
Expand All @@ -23,17 +24,18 @@ class SolidLintsPlugin extends Plugin {
@override
void register(PluginRegistry registry) {
final analysisLoader = AnalysisOptionsLoader();
registry.registerLintRule(
final lintRules = [
AvoidGlobalStateRule(analysisLoader),
);
registry.registerLintRule(
AvoidNonNullAssertionRule(),
);
registry.registerLintRule(
AvoidDebugPrintInReleaseRule(),
);
registry.registerLintRule(
ProperSuperCallsRule(),
);
AvoidReturningWidgetsRule(
analysisOptionsLoader: analysisLoader,
parametersParser: AvoidReturningWidgetsParameters.fromJson,
),
];

for (final lintRule in lintRules) {
registry.registerLintRule(lintRule);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class ExcludedIdentifiersListParameter {
final classDeclaration = node.thisOrAncestorOfType<ClassDeclaration>();

return classDeclaration != null &&
classDeclaration.name.toString() == className;
classDeclaration.namePart.typeName.lexeme == className;
Comment thread
andrew-bekhiet-solid marked this conversation as resolved.
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:analyzer/analysis_rule/rule_context.dart';
import 'package:analyzer/analysis_rule/rule_visitor_registry.dart';
import 'package:analyzer/error/error.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/visitors/avoid_returning_widgets_visitor.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';
import 'package:solid_lints/src/utils/types_utils.dart';

/// A rule which warns about returning widgets from functions and methods.
///
Expand Down Expand Up @@ -54,64 +52,38 @@ class AvoidReturningWidgetsRule
/// This lint rule represents
/// the error whether we return a widget.
static const lintName = 'avoid_returning_widgets';
static const _override = 'override';

AvoidReturningWidgetsRule._(super.config);
static const _code = LintCode(
lintName,
'Returning a widget from a function is considered an anti-pattern. '
'Unless you are overriding an existing method, '
'consider extracting your widget to a separate class.',
);

/// Creates a new instance of [AvoidReturningWidgetsRule]
/// based on the lint configuration.
factory AvoidReturningWidgetsRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
paramsParser: AvoidReturningWidgetsParameters.fromJson,
problemMessage: (_) =>
'Returning a widget from a function is considered an anti-pattern. '
'Unless you are overriding an existing method, '
'consider extracting your widget to a separate class.',
);
@override
DiagnosticCode get diagnosticCode => _code;

return AvoidReturningWidgetsRule._(rule);
}
/// Creates a new instance of [AvoidReturningWidgetsRule]
AvoidReturningWidgetsRule({
required super.analysisOptionsLoader,
required super.parametersParser,
}) : super.withParameters(
name: _code.lowerCaseName,
description: _code.problemMessage,
);

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addDeclaration((node) {
// Check if declaration is function or method,
// simultaneously checks if return type is [DartType]
final DartType? returnType = switch (node) {
FunctionDeclaration(returnType: TypeAnnotation(:final type?)) ||
MethodDeclaration(returnType: TypeAnnotation(:final type?)) =>
type,
_ => null,
};

if (returnType == null) {
return;
}

final isWidgetReturned = hasWidgetType(returnType);
super.registerNodeProcessors(registry, context);

final isIgnored = config.parameters.exclude.shouldIgnore(node);
final parameters = getParametersForContext(context) ??
AvoidReturningWidgetsParameters.empty();

final isOverriden = switch (node) {
FunctionDeclaration(:final functionExpression) =>
functionExpression.parent is MethodDeclaration &&
(functionExpression.parent! as MethodDeclaration)
.metadata
.any((m) => m.name.name == _override),
MethodDeclaration(:final metadata) =>
metadata.any((m) => m.name.name == _override),
_ => false,
};
final visitor = AvoidReturningWidgetsVisitor(this, parameters);

if (isWidgetReturned && !isOverriden && !isIgnored) {
reporter.atNode(node, code);
}
});
registry.addCompilationUnit(this, visitor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ class AvoidReturningWidgetsParameters {
required this.exclude,
});

/// Empty [AvoidReturningWidgetsParameters] model, excludes nothing.
factory AvoidReturningWidgetsParameters.empty() {
return AvoidReturningWidgetsParameters(
exclude: ExcludedIdentifiersListParameter(exclude: []),
);
}

/// Method for creating from json data
factory AvoidReturningWidgetsParameters.fromJson(Map<String, dynamic> json) {
return AvoidReturningWidgetsParameters(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
import 'package:solid_lints/src/lints/avoid_returning_widgets/models/avoid_returning_widgets_parameters.dart';
import 'package:solid_lints/src/utils/types_utils.dart';

/// A visitor that reports on functions that return widgets.
class AvoidReturningWidgetsVisitor extends RecursiveAstVisitor<void> {
final AvoidReturningWidgetsRule _rule;
final AvoidReturningWidgetsParameters _parameters;

/// Creates a new instance of [AvoidReturningWidgetsVisitor]
AvoidReturningWidgetsVisitor(this._rule, this._parameters);

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);

_visitDeclaration(node);
}

@override
void visitMethodDeclaration(MethodDeclaration node) {
super.visitMethodDeclaration(node);

_visitDeclaration(node);
}

@override
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
super.visitFunctionDeclarationStatement(node);

_visitDeclaration(node.functionDeclaration);
}

void _visitDeclaration(Declaration node) {
if (node is! FunctionDeclaration && node is! MethodDeclaration) {
return;
}

final returnType = switch (node) {
Declaration(
declaredFragment: ExecutableFragment(
element: ExecutableElement(type: FunctionType(:final returnType))
)
) =>
returnType,
MethodDeclaration(returnType: TypeAnnotation(:final type)) => type,
FunctionDeclaration(returnType: TypeAnnotation(:final type)) => type,
_ => null,
};
if (returnType == null) return;

final isWidgetReturned = hasWidgetType(returnType);
if (!isWidgetReturned) return;

final isIgnored = _parameters.exclude.shouldIgnore(node);
if (isIgnored) return;

if (_isOverridden(node)) return;

_rule.reportAtNode(node);
}

bool _isOverridden(Declaration node) {
return switch (node) {
Declaration(
declaredFragment: Fragment(
element: Element(
name: final String name,
enclosingElement: final InterfaceElement enclosingElement
)
),
) =>
enclosingElement.getInheritedMember(
Name.forLibrary(enclosingElement.library, name),
) !=
null,
_ => false,
};
}
}
11 changes: 0 additions & 11 deletions lint_test/avoid_returning_widget_test/analysis_options.yaml

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ dependencies:
path: ^1.9.1
yaml: ^3.1.3
# These packages are required for pana analysis to run correctly
flutter:
sdk: flutter
test: ^1.25.14

dev_dependencies:
args: ^2.6.0
analyzer_testing: ^0.1.9
test_reflective_loader: ^0.3.0

Loading
Loading