Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
43a6648
Created analysis_options.yaml rules parser
Dariaa14 Apr 22, 2026
87abe51
Improved yaml parser and added the analysis_options loader
Dariaa14 Apr 23, 2026
4c604a3
Improved rules loader from yaml
Dariaa14 Apr 23, 2026
87f5870
Added verification before looking for .yaml's path
Dariaa14 Apr 23, 2026
ca9df7c
Fields and getters are now declared before the constructor
Dariaa14 Apr 23, 2026
226a748
Added method to get options of a rule by it's name
Dariaa14 Apr 23, 2026
08c3e8e
Made suggested changes to file upward finder
Dariaa14 Apr 23, 2026
1eee0b2
Removed top-level variable
Dariaa14 Apr 23, 2026
0ef7917
Improved name of variable in loadRuleFromContext
Dariaa14 Apr 23, 2026
86c3f4d
Updated analysis options to have rules for each configuration file path
Dariaa14 Apr 23, 2026
e0490f7
Updated file upward finder to not mix File from dart.io with file fro…
Dariaa14 Apr 23, 2026
03a53ba
Added usage example in avoid_global_state_rule
Dariaa14 Apr 23, 2026
9f90265
style: move getters and fields before constructor
andrew-bekhiet-solid Jun 2, 2026
a8d53e4
style: improve readability
andrew-bekhiet-solid Jun 2, 2026
73514fd
fix: don't parse enabled if the rule has configured options
andrew-bekhiet-solid Jun 2, 2026
c6a2453
feat: reload rules from file if newer
andrew-bekhiet-solid Jun 2, 2026
55af03a
test: add AnalysisOptionsLoaderTest
andrew-bekhiet-solid Jun 2, 2026
06c5367
feat(SolidLintRule): add parameter parsing
andrew-bekhiet-solid Jun 3, 2026
affc62c
fix: use Map<String, Object?> for raw rule config
andrew-bekhiet-solid Jun 3, 2026
1b10b3d
fix: method name
andrew-bekhiet-solid Jun 3, 2026
4c30ac9
fix: make sure rules options are loaded before getting parameters
andrew-bekhiet-solid Jun 3, 2026
1cb4497
refactor: remove unused AnalysisOptionsLoader from AvoidGlobalStateRule
andrew-bekhiet-solid Jun 8, 2026
0975ba7
refactor: extract duplicate logic
andrew-bekhiet-solid Jun 8, 2026
20d709e
refactor: migrate avoid_returning_widgets rule
andrew-bekhiet-solid Jun 8, 2026
5d06255
Merge remote-tracking branch 'origin/analysis_server_migration' into …
andrew-bekhiet-solid Jun 10, 2026
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
6 changes: 6 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +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/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/lints/double_literal_format/double_literal_format_rule.dart';
import 'package:solid_lints/src/lints/double_literal_format/fixes/double_literal_format_fix.dart';
import 'package:solid_lints/src/lints/proper_super_calls/proper_super_calls_rule.dart';
Expand Down Expand Up @@ -33,6 +35,10 @@ class SolidLintsPlugin extends Plugin {
AvoidDebugPrintInReleaseRule(),
doubleLiteralFormatRule,
ProperSuperCallsRule(),
AvoidReturningWidgetsRule(
analysisOptionsLoader: analysisLoader,
parametersParser: AvoidReturningWidgetsParameters.fromJson,
),
// TODO: Add more lint rules and use analysisLoader
// for rules that need parameters
// For example: `CyclomaticComplexityRule(analysisLoader)`
Expand Down
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.

1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies:
glob: ^2.1.3
path: ^1.9.1
yaml: ^3.1.3
# These packages are required for pana analysis to run correctly
test: ^1.25.14

dev_dependencies:
Expand Down
Loading
Loading