Skip to content
Open
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
10 changes: 10 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:analysis_server_plugin/plugin.dart';
import 'package:analysis_server_plugin/registry.dart';
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_final_with_getter/avoid_final_with_getter_rule.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/fixes/avoid_final_with_getter_fix.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';
Expand All @@ -21,6 +23,14 @@ class SolidLintsPlugin extends Plugin {

@override
void register(PluginRegistry registry) {
registry.registerLintRule(
AvoidFinalWithGetterRule(),
);
registry.registerFixForRule(
AvoidFinalWithGetterRule.code,
AvoidFinalWithGetterFix.new,
);

registry.registerLintRule(
AvoidGlobalStateRule(),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:analyzer/analysis_rule/analysis_rule.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_final_with_getter/visitors/avoid_final_with_getter_visitor.dart';
import 'package:solid_lints/src/models/rule_config.dart';
import 'package:solid_lints/src/models/solid_lint_rule.dart';

part 'fixes/avoid_final_with_getter_fix.dart';

/// Avoid using final private fields with getters.
///
Expand Down Expand Up @@ -34,45 +30,32 @@ part 'fixes/avoid_final_with_getter_fix.dart';
/// }
/// ```
///
class AvoidFinalWithGetterRule extends SolidLintRule {
class AvoidFinalWithGetterRule extends AnalysisRule {
/// This lint rule represents
/// the error whether we use final private fields with getters.
static const lintName = 'avoid_final_with_getter';

final _diagnosticsInfoExpando = Expando<FinalWithGetterInfo>();

AvoidFinalWithGetterRule._(super.config);
/// The code to report for a violation
static const LintCode code = LintCode(
lintName,
'Avoid final private fields with getters.',
correctionMessage: 'Remove the getter and make the field public.',
);

/// Creates a new instance of [AvoidFinalWithGetterRule]
/// based on the lint configuration.
factory AvoidFinalWithGetterRule.createRule(CustomLintConfigs configs) {
final rule = RuleConfig(
configs: configs,
name: lintName,
problemMessage: (_) => 'Avoid final private fields with getters.',
);
AvoidFinalWithGetterRule()
: super(name: lintName, description: code.problemMessage);

return AvoidFinalWithGetterRule._(rule);
}
@override
LintCode get diagnosticCode => code;

@override
void run(
CustomLintResolver resolver,
DiagnosticReporter reporter,
CustomLintContext context,
void registerNodeProcessors(
RuleVisitorRegistry registry,
RuleContext context,
) {
context.registry.addCompilationUnit((node) {
final visitor = AvoidFinalWithGetterVisitor();
node.accept(visitor);
final visitor = AvoidFinalWithGetterVisitor(this);

for (final element in visitor.getters) {
final diagnostic = reporter.atNode(element.getter, code);

_diagnosticsInfoExpando[diagnostic] = element;
}
});
registry.addCompilationUnit(this, visitor);
}

@override
List<Fix> getFixes() => [_FinalWithGetterFix(_diagnosticsInfoExpando)];
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,84 @@
part of '../avoid_final_with_getter_rule.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/variable_references_visitor.dart';

class _FinalWithGetterFix extends DartFix {
final Expando<FinalWithGetterInfo> _diagnosticsInfoExpando;
/// A Quick fix for [AvoidFinalWithGetterRule] rule
class AvoidFinalWithGetterFix extends ResolvedCorrectionProducer {
static const _avoidFinalWithGetterKind = FixKind(
'solid_lints.fix.${AvoidFinalWithGetterRule.lintName}',
DartFixKindPriority.standard,
"Remove the getter and make the field public",
);

_FinalWithGetterFix(this._diagnosticsInfoExpando);
/// Creates a new instance of [AvoidFinalWithGetterFix]
AvoidFinalWithGetterFix({required super.context});

@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
Diagnostic diagnostic,
List<Diagnostic> others,
) {
context.registry.addMethodDeclaration((node) {
if (!diagnostic.sourceRange.intersects(node.sourceRange)) return;

final info = _diagnosticsInfoExpando[diagnostic];
if (info == null) return;

_addReplacement(reporter, info);
});
}
CorrectionApplicability get applicability =>
CorrectionApplicability.acrossFiles;

@override
FixKind get fixKind => _avoidFinalWithGetterKind;

@override
Future<void> compute(ChangeBuilder builder) async {
final getterNode = node;
if (getterNode
case MethodDeclaration(
isGetter: true,
declaredFragment: ExecutableFragment(
element: GetterElement(
isAbstract: false,
isPublic: true,
),
),
)) {
final compilationUnit = node.thisOrAncestorOfType<CompilationUnit>();
if (compilationUnit == null) return;

final getterVariableVisitor = GetterVariableVisitor(getterNode);
compilationUnit.accept(getterVariableVisitor);

final variableDeclaration = getterVariableVisitor.variable;
if (variableDeclaration == null) return;

final referencesVisitor = VariableReferencesVisitor(variableDeclaration);
compilationUnit.accept(referencesVisitor);

final variableReferences = referencesVisitor.references;

final variableName = variableDeclaration.name.lexeme;
final newPublicVariableName = variableName.startsWith('_')
? variableName.substring(1)
: variableName;

await builder.addDartFileEdit(file, (builder) {
builder.addDeletion(getterNode.sourceRange);

builder.addSimpleReplacement(
variableDeclaration.name.sourceRange,
newPublicVariableName,
);

for (final reference in variableReferences) {
if (reference.sourceRange.intersects(getterNode.sourceRange)) {
continue;
}

builder.addSimpleReplacement(
reference.sourceRange,
newPublicVariableName,
);
}

void _addReplacement(
ChangeReporter reporter,
FinalWithGetterInfo info,
) {
final changeBuilder = reporter.createChangeBuilder(
message: "Remove getter and make variable public.",
priority: 1,
);

changeBuilder.addDartFileEdit((builder) {
builder.addDeletion(info.getter.sourceRange);
builder.addDeletion(SourceRange(info.variable.name.offset, 1));
});
builder.format(compilationUnit.sourceRange);
});
}
}
}
Comment thread
andrew-bekhiet-solid marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';

/// Extension method to get the reference id of the getter.
extension GetterReferenceId on MethodDeclaration {
/// Get the reference id of the getter.
int? get getterReferenceId {
final returnExpression = switch (body) {
ExpressionFunctionBody(expression: final expr) ||
BlockFunctionBody(
block: Block(
statements: [
ReturnStatement(expression: final expr?),
],
)
) =>
expr,
_ => null,
};

final identifier = switch (returnExpression) {
SimpleIdentifier() => returnExpression,
PropertyAccess(
target: ThisExpression(),
:final propertyName,
) =>
propertyName,
_ => null,
};

return switch (identifier) {
SimpleIdentifier(element: PropertyAccessorElement(:final variable)) =>
variable.id,
_ => null,
};
}
Comment thread
andrew-bekhiet-solid marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/visitors/getter_variable_visitor.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/avoid_final_with_getter_rule.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/utils/getter_reference_id.dart';

/// A visitor that checks for final private fields with getters.
/// If a final private field has a getter, it is considered as a public field.
class AvoidFinalWithGetterVisitor extends RecursiveAstVisitor<void> {
final _getters = <FinalWithGetterInfo>{};
final AvoidFinalWithGetterRule _rule;

/// List of getters
Set<FinalWithGetterInfo> get getters => _getters;
final _gettersPairLookup = <int, MethodDeclaration>{};
final _fieldsPairLookup = <int, VariableDeclaration>{};

/// Creates a new instance of [AvoidFinalWithGetterVisitor]
AvoidFinalWithGetterVisitor(this._rule);

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

if (node
case MethodDeclaration(
isGetter: true,
Expand All @@ -21,29 +27,36 @@ class AvoidFinalWithGetterVisitor extends RecursiveAstVisitor<void> {
isAbstract: false,
isPublic: true,
)
)
),
getterReferenceId: final getterId?,
)) {
final visitor = GetterVariableVisitor(node);
node.parent?.accept(visitor);
_gettersPairLookup[getterId] = node;

final variable = visitor.variable;

if (variable != null) {
_getters.add(FinalWithGetterInfo(node, variable));
if (_fieldsPairLookup.containsKey(getterId)) {
_rule.reportAtNode(node);
}
}
super.visitMethodDeclaration(node);
}
}

/// Information about the final private field with a getter.
class FinalWithGetterInfo {
/// The getter method declaration.
final MethodDeclaration getter;
@override
void visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);

/// The variable declaration.
final VariableDeclaration variable;
if (node
case VariableDeclaration(
declaredFragment: VariableFragment(
element: VariableElement(
isPrivate: true,
isFinal: true,
id: final variableId,
)
)
)) {
_fieldsPairLookup[variableId] = node;

/// Creates a new instance of [FinalWithGetterInfo]
const FinalWithGetterInfo(this.getter, this.variable);
if (_gettersPairLookup[variableId] case final getter?) {
_rule.reportAtNode(getter);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:solid_lints/src/lints/avoid_final_with_getter/utils/getter_reference_id.dart';

/// A visitor that checks the association of the getter with
/// the final private variable
/// A visitor that gets the final private variable associated with the getter.
class GetterVariableVisitor extends RecursiveAstVisitor<void> {
final int? _getterId;
VariableDeclaration? _variable;
Expand All @@ -17,29 +17,19 @@ class GetterVariableVisitor extends RecursiveAstVisitor<void> {

@override
void visitVariableDeclaration(VariableDeclaration node) {
final element = node.declaredFragment?.element;
if (element != null &&
element.isPrivate &&
element.isFinal &&
element.id == _getterId) {
if (node
case VariableDeclaration(
declaredFragment: VariableFragment(
element: VariableElement(
isPrivate: true,
isFinal: true,
:final id,
)
)
) when id == _getterId) {
_variable = node;
}

super.visitVariableDeclaration(node);
}
}

extension on MethodDeclaration {
int? get getterReferenceId {
if (body
case ExpressionFunctionBody(
expression: SimpleIdentifier(
element: PropertyAccessorElement(:final variable)
)
)) {
return variable.id;
}

return null;
}
}
Loading
Loading