Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement quick fix for mix_attributes_ordering rule #381

Merged
merged 2 commits into from
Jul 18, 2024
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
139 changes: 123 additions & 16 deletions packages/mix_lint/lib/src/lints/attributes_ordering.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.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:mix_lint/src/utils/type_checker.dart';

const _whiteList = ['Style.asAttribute'];

class AttributesOrdering extends DartLintRule {
AttributesOrdering() : super(code: _code);

Expand All @@ -12,7 +16,10 @@ class AttributesOrdering extends DartLintRule {
'Ensure that the attributes are ordered on groups of the same category in the Style constructor',
);

final _whiteList = ['Style.asAttribute'];
@override
List<Fix> getFixes() => [
_AttributesOrderingFix(),
];

@override
void run(
Expand All @@ -26,7 +33,7 @@ class AttributesOrdering extends DartLintRule {

final arguments = node.argumentList.arguments;

if (hasAnyAttributeOutOfOrder(arguments)) {
if (_hasAnyAttributeOutOfOrder(arguments)) {
reporter.reportErrorForNode(
_code,
node,
Expand All @@ -39,7 +46,7 @@ class AttributesOrdering extends DartLintRule {
if (!condition) return;
final arguments = expression.argumentList.arguments;

if (hasAnyAttributeOutOfOrder(arguments)) {
if (_hasAnyAttributeOutOfOrder(arguments)) {
reporter.reportErrorForNode(
_code,
expression,
Expand All @@ -58,24 +65,124 @@ class AttributesOrdering extends DartLintRule {
);
});
}
}

bool hasAnyAttributeOutOfOrder(List<Expression> listOfUsedTokens) {
for (var i = 0; i < listOfUsedTokens.length; i++) {
final currentToken = listOfUsedTokens[i];
class _AttributesOrderingFix extends DartFix {
@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
AnalysisError analysisError,
List<AnalysisError> others,
) {
ChangeBuilder createChangeBuilder() => reporter.createChangeBuilder(
message: 'Fix the order of the attributes',
priority: 80,
);

if (i + 1 < listOfUsedTokens.length) {
final nextToken = listOfUsedTokens[i + 1];
void addReplacement(
ChangeBuilder changeBuilder,
SourceRange sourceRange,
List<Expression> arguments,
) {
changeBuilder.addDartFileEdit((builder) {
builder.addSimpleReplacement(
sourceRange,
'(${fixParentheses(arguments.join(','))})',
);
builder.format(sourceRange);
});
}

if (currentToken.staticType != nextToken.staticType) {
final result = listOfUsedTokens
.sublist(i + 1)
.any((e) => e.staticType == currentToken.staticType);
void suggestReplacementIfNeeded({
required List<Expression> arguments,
required SourceRange sourceRange,
}) {
if (!_hasAnyAttributeOutOfOrder(arguments)) return;

if (result) return true;
}
}
final copiedList = arguments.map((e) => e).toList();
sortArgument(copiedList);

addReplacement(
createChangeBuilder(),
sourceRange,
copiedList,
);
}

return false;
context.registry.addInstanceCreationExpression((node) {
if (!analysisError.sourceRange.intersects(node.sourceRange)) return;
if (!styleChecker.isAssignableFromType(node.staticType!)) return;

suggestReplacementIfNeeded(
arguments: node.argumentList.arguments,
sourceRange: node.argumentList.sourceRange,
);
});

context.registry.addFunctionExpressionInvocation((expression) {
if (!analysisError.sourceRange.intersects(expression.sourceRange)) return;
if (expression.staticType == null) return;

if (variantAttributeChecker
.isAssignableFromType(expression.staticType!)) {
suggestReplacementIfNeeded(
arguments: expression.argumentList.arguments,
sourceRange: expression.argumentList.sourceRange,
);
}

if (_whiteList.contains(
expression.childEntities.first.toString(),
)) {
suggestReplacementIfNeeded(
arguments: expression.argumentList.arguments,
sourceRange: expression.argumentList.sourceRange,
);
}
});
}

String fixParentheses(String input) {
return input.replaceAllMapped(RegExp(r'\)(?!,)'), (match) => '),');
}

void sortArgument(List<Expression> arguments) {
final currentOrder =
arguments.map((e) => e.staticType!.toString()).toSet().toList();

final mapWithOthers = Map.fromIterables(
currentOrder,
List<int>.generate(currentOrder.length, (i) => i + 1),
);

arguments.sort(
(a, b) {
final weightA = mapWithOthers[a.staticType!.toString()] ?? 0;
final weightB = mapWithOthers[b.staticType!.toString()] ?? 0;
return weightA.compareTo(weightB);
},
);
}
}

bool _hasAnyAttributeOutOfOrder(List<Expression> listOfUsedTokens) {
for (var i = 0; i < listOfUsedTokens.length; i++) {
final currentToken = listOfUsedTokens[i];

if (i + 1 < listOfUsedTokens.length) {
final nextToken = listOfUsedTokens[i + 1];

if (currentToken.staticType != nextToken.staticType) {
final result = listOfUsedTokens
.sublist(i + 1)
.any((e) => e.staticType == currentToken.staticType);

if (result) return true;
}
}
}

return false;
}
5 changes: 5 additions & 0 deletions packages/mix_lint/lib/src/utils/type_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const attributeChecker = TypeChecker.fromName(
packageName: 'mix',
);

const specAttributeChecker = TypeChecker.fromName(
'SpecAttribute',
packageName: 'mix',
);

const styleChecker = TypeChecker.fromName(
'Style',
packageName: 'mix',
Expand Down
1 change: 1 addition & 0 deletions packages/mix_lint_test/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ dev_dependencies:
custom_lint: ^0.6.0
analyzer: ^6.0.0
analyzer_plugin: ^0.11.2
test: ^1.24.9

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:mix/mix.dart';

// expect_lint: mix_attributes_ordering
final test1 = Style(
$flex.gap(3),
$box.color.red(),
$flex.row(),
$box.borderRadius(3),
$text.style.color.red(),
$text.style.fontSize(3),
$icon.size(3),
$icon.fill(3),
);

// expect_lint: mix_attributes_ordering
final test2 = Style(
$box.color.red(),
$flex.gap(3),
$box.borderRadius(3),
);

final a = Variant('a');
final b = Variant('b');
final c = Variant('c');

final test3 = Style(
// expect_lint: mix_attributes_ordering
a(
$box.color.red(),
$flex.gap(3),
$box.borderRadius(3),
),
);

final test4 = Style(
a(
// expect_lint: mix_attributes_ordering
b(
$box.color.red(),
$flex.gap(3),
$box.borderRadius(3),
),
),
);

final test5 = Style(
// expect_lint: mix_attributes_ordering
a(
$box.color.red(),
// expect_lint: mix_attributes_ordering
c(
$box.color.red(),
$flex.gap(3),
$box.borderRadius(3),
),
$box.borderRadius(3),
// expect_lint: mix_attributes_ordering
b(
$box.color.red(),
$flex.gap(3),
$box.borderRadius(3),
),
),
).applyVariant(a, b);
Loading
Loading