Skip to content

Commit

Permalink
feat: Added Button assist and two Lints.
Browse files Browse the repository at this point in the history
  • Loading branch information
mathrunet committed Apr 3, 2024
1 parent 8c31a14 commit e2e5311
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 6 deletions.
70 changes: 70 additions & 0 deletions packages/masamune_lints/lib/buttons/masamune_button_add_icon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
part of '/masamune_lints.dart';

class _MasamuneButtonAddIcon extends DartAssist {
_MasamuneButtonAddIcon();

@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
SourceRange target,
) {
context.registry.addInstanceCreationExpression((node) {
if (!target.intersects(node.constructorName.sourceRange)) {
return;
}
final createdType = node.constructorName.type.type;
if (createdType == null ||
!TypeChecker.any(_MaterialButtonType.values.map((e) => e.typeChecker))
.isExactlyType(createdType)) {
return;
}
final simpleIdentifier = node.constructorName.name;
final supportedIdentifier = simpleIdentifier?.toSupportedIdentifier();

if (supportedIdentifier != null && supportedIdentifier.hasIcon) {
return;
}
final changeBuilder = reporter.createChangeBuilder(
message: "Add icon to button",
priority: _kAddOrRemoveIconPriority,
);

changeBuilder.addDartFileEdit((builder) {
if (supportedIdentifier == _SupportedIdentifier.tonal) {
builder.addSimpleReplacement(
node.constructorName.sourceRange,
"FilledButton.tonalIcon",
);
} else {
builder.addSimpleInsertion(
node.constructorName.sourceRange.end,
".icon",
);
}

bool existIcon = false;
for (var argument in node.argumentList.arguments) {
if (argument is NamedExpression) {
if (argument.name.label.name == "child") {
builder.addSimpleReplacement(
argument.name.sourceRange,
"label:",
);
}
if (argument.name.label.name == "icon") {
existIcon = true;
}
}
}
if (!existIcon) {
builder.addSimpleInsertion(
node.argumentList.arguments.last.sourceRange.end,
", icon: Icon(Icons.add // TODO: Change icon)",
);
}
});
});
}
}
82 changes: 82 additions & 0 deletions packages/masamune_lints/lib/buttons/masamune_button_convert.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
part of '/masamune_lints.dart';

class _MasamuneButtonConvert extends DartAssist {
_MasamuneButtonConvert({
required this.targetType,
});
final _MaterialButtonType targetType;
late final baseType = targetType != _MaterialButtonType.filled
? targetType.getBaseType()
: null;

@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
SourceRange target,
) {
context.registry.addInstanceCreationExpression((node) {
if (!target.intersects(node.constructorName.sourceRange)) {
return;
}

final createdType = node.constructorName.type.type;
if (createdType == null ||
!(baseType?.isExactlyType(createdType) ?? false)) {
return;
}

final simpleIdentifier = node.constructorName.name;
final isFilledButton = const TypeChecker.fromName(
"FilledButton",
packageName: "flutter",
).isExactlyType(createdType);
final supportedIdentifier = simpleIdentifier?.toSupportedIdentifier();

if (isFilledButton) {
if (supportedIdentifier?.isTonal ?? false) {
if (targetType == _MaterialButtonType.filledTonal) {
return;
}
} else {
if (targetType == _MaterialButtonType.filled) {
return;
}
}
}

final changeBuilder = reporter.createChangeBuilder(
message: "Convert to ${targetType.buttonName}",
priority: targetType.priority,
);

changeBuilder.addDartFileEdit(
(builder) {
builder.addSimpleReplacement(
node.constructorName.sourceRange,
targetType.className +
_getReplacementIdentifier(supportedIdentifier, targetType),
);
},
);
});
}

String _getReplacementIdentifier(
_SupportedIdentifier? identifier, _MaterialButtonType targetType) {
if (identifier?.hasIcon ?? false) {
if (targetType == _MaterialButtonType.filledTonal) {
return ".tonalIcon";
} else {
return ".icon";
}
} else {
if (targetType == _MaterialButtonType.filledTonal) {
return ".tonal";
} else {
return "";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
part of '/masamune_lints.dart';

class _MasamuneButtonRemoveIcon extends DartAssist {
_MasamuneButtonRemoveIcon();

@override
void run(
CustomLintResolver resolver,
ChangeReporter reporter,
CustomLintContext context,
SourceRange target,
) {
context.registry.addInstanceCreationExpression((node) {
if (!target.intersects(node.constructorName.sourceRange)) {
return;
}

final createdType = node.constructorName.type.type;
if (createdType == null ||
!TypeChecker.any(_MaterialButtonType.values.map((e) => e.typeChecker))
.isExactlyType(createdType)) {
return;
}

final simpleIdentifier = node.constructorName.name;
final supportedIdentifier = simpleIdentifier?.toSupportedIdentifier();

if (supportedIdentifier != null && !supportedIdentifier.hasIcon) {
return;
}

final changeBuilder = reporter.createChangeBuilder(
message: "Remove icon from button",
priority: _kAddOrRemoveIconPriority,
);

changeBuilder.addDartFileEdit((builder) {
if (supportedIdentifier == _SupportedIdentifier.tonalIcon) {
builder.addSimpleReplacement(
node.constructorName.sourceRange,
"FilledButton.tonal",
);
} else {
builder.addSimpleReplacement(
node.constructorName.sourceRange,
node.constructorName.type.name2.lexeme,
);
}

for (var argument in node.argumentList.arguments) {
if (argument is NamedExpression) {
if (argument.name.label.name == "label") {
builder.addSimpleReplacement(
argument.name.sourceRange,
"child:",
);
}

if (argument.name.label.name == "icon") {
builder.addDeletion(
SourceRange(
argument.sourceRange.offset,
argument.sourceRange.length +
(argument.endToken.next?.lexeme == "," ? 1 : 0),
),
);
}
}
}
});
});
}
}
117 changes: 117 additions & 0 deletions packages/masamune_lints/lib/buttons/masamune_button_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
part of '/masamune_lints.dart';

const _kConvertToOtherButtonPriority = 27;
const _kAddOrRemoveIconPriority = 27;

enum _MaterialButtonType {
elevated(
buttonName: "ElevatedButton",
className: "ElevatedButton",
priority: _kConvertToOtherButtonPriority,
typeChecker: TypeChecker.fromName(
"ElevatedButton",
packageName: "flutter",
),
),
filled(
buttonName: "FilledButton",
className: "FilledButton",
priority: _kConvertToOtherButtonPriority,
typeChecker: TypeChecker.fromName(
"FilledButton",
packageName: "flutter",
),
),
filledTonal(
buttonName: "FilledTonalButton",
className: "FilledButton",
priority: _kConvertToOtherButtonPriority,
typeChecker: TypeChecker.fromName(
"FilledButton",
packageName: "flutter",
),
),
outlined(
buttonName: "OutlinedButton",
className: "OutlinedButton",
priority: _kConvertToOtherButtonPriority,
typeChecker: TypeChecker.fromName(
"OutlinedButton",
packageName: "flutter",
),
),
text(
buttonName: "TextButton",
className: "TextButton",
priority: _kConvertToOtherButtonPriority,
typeChecker: TypeChecker.fromName(
"TextButton",
packageName: "flutter",
),
);

const _MaterialButtonType({
required this.buttonName,
required this.className,
required this.priority,
required this.typeChecker,
});
final String buttonName;
final String className;
final int priority;
final TypeChecker typeChecker;

TypeChecker getBaseType() {
return TypeChecker.any(
_MaterialButtonType.values
.where((e) => e != this)
.map((e) => e.typeChecker),
);
}
}

enum _SupportedIdentifier {
icon,
tonal,
tonalIcon;

bool get isTonal {
switch (this) {
case _SupportedIdentifier.icon:
return false;
case _SupportedIdentifier.tonal:
return true;
case _SupportedIdentifier.tonalIcon:
return true;
default:
return false;
}
}

bool get hasIcon {
switch (this) {
case _SupportedIdentifier.icon:
return true;
case _SupportedIdentifier.tonal:
return false;
case _SupportedIdentifier.tonalIcon:
return true;
default:
return false;
}
}
}

extension _SimpleIdentifierExtensions on SimpleIdentifier {
_SupportedIdentifier? toSupportedIdentifier() {
switch (name) {
case "icon":
return _SupportedIdentifier.icon;
case "tonal":
return _SupportedIdentifier.tonal;
case "tonalIcon":
return _SupportedIdentifier.tonalIcon;
}
return null;
}
}
40 changes: 40 additions & 0 deletions packages/masamune_lints/lib/common/masamune_limit_if_nesting.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
part of '/masamune_lints.dart';

const _kMaxNestingCount = 3;

class _MasamuneLimitIfNesting extends DartLintRule {
const _MasamuneLimitIfNesting()
: super(
code: _code,
);

static const _code = LintCode(
name: "masamune_if_nesting_should_limit",
problemMessage:
"Nesting hierarchy for if should be limited to $_kMaxNestingCount units. ifのネスト階層は$_kMaxNestingCount個までにしてください。",
errorSeverity: ErrorSeverity.WARNING,
);

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addIfStatement((node) {
int nestingLevel = 0;
AstNode? currentNode = node;

while (currentNode != null) {
if (currentNode is IfStatement && currentNode.elseStatement == null) {
nestingLevel++;
}
currentNode = currentNode.parent;
}

if (nestingLevel > _kMaxNestingCount) {
reporter.reportErrorForNode(_code, node);
}
});
}
}

0 comments on commit e2e5311

Please sign in to comment.