Last active
March 6, 2023 09:48
-
-
Save MarcinusX/9548f66251c4784aeb2de5eeaa7c9da5 to your computer and use it in GitHub Desktop.
A linter for parsing protobuf's Timestamps to DateTime. The goal is to always include isLocal: true when doing it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:analyzer/dart/ast/ast.dart'; | |
import 'package:analyzer/dart/element/type.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'; | |
class PreferTimestampsToLocalDateTimes extends DartLintRule { | |
PreferTimestampsToLocalDateTimes() : super(code: _code); | |
static const _code = LintCode( | |
name: 'timestamp_to_date_time_is_local', | |
problemMessage: | |
'Parsing Timestamp to DateTime should always be to local time', | |
); | |
@override | |
void run( | |
CustomLintResolver resolver, | |
ErrorReporter reporter, | |
CustomLintContext context, | |
) { | |
context.registry.addMethodInvocation((node) { | |
if (node.isFromTimestamp && | |
node.isToDateTime && | |
!node.hasArgumentToLocalSetToTrue) { | |
reporter.reportErrorForNode(_code, node); | |
} | |
}); | |
} | |
@override | |
List<Fix> getFixes() => [_AddIsLocalFix()]; | |
} | |
class _AddIsLocalFix extends DartFix { | |
@override | |
void run( | |
CustomLintResolver resolver, | |
ChangeReporter reporter, | |
CustomLintContext context, | |
AnalysisError analysisError, | |
List<AnalysisError> others, | |
) { | |
context.registry.addMethodInvocation((node) { | |
if (!analysisError.sourceRange.intersects(node.sourceRange) || | |
!node.isFromTimestamp && !node.isToDateTime) { | |
return; | |
} | |
final leftParenthesisOffset = node.argumentList.leftParenthesis.offset; | |
final rightParenthesisOffset = node.argumentList.rightParenthesis.offset; | |
final fix = 'toLocal: true'; | |
if (node.hasArgumentToLocalSetToFalse) { | |
final changeBuilder = reporter.createChangeBuilder( | |
message: 'Replace with "toLocal: true"', | |
priority: 1, | |
); | |
changeBuilder.addDartFileEdit((builder) { | |
builder.addSimpleReplacement( | |
SourceRange( | |
leftParenthesisOffset + 1, | |
rightParenthesisOffset - leftParenthesisOffset - 1, | |
), | |
fix, | |
); | |
}); | |
} else { | |
final changeBuilder = reporter.createChangeBuilder( | |
message: 'Add "toLocal: true"', | |
priority: 1, | |
); | |
changeBuilder.addDartFileEdit((builder) { | |
builder.addSimpleInsertion( | |
leftParenthesisOffset + 1, | |
fix, | |
); | |
}); | |
} | |
}); | |
} | |
} | |
extension on MethodInvocation { | |
DartType? get targetType => target?.staticType; | |
bool get isFromTimestamp => | |
targetType != null && | |
TypeChecker.fromName( | |
'Timestamp', | |
packageName: 'cheddar_api', | |
).isAssignableFromType(targetType!); | |
bool get isToDateTime => methodName.name == 'toDateTime'; | |
bool get hasArgumentToLocalSetToTrue => hasArgumentToLocalSetToValue(true); | |
bool get hasArgumentToLocalSetToFalse => hasArgumentToLocalSetToValue(false); | |
bool hasArgumentToLocalSetToValue(bool value) => | |
argumentList.arguments.any((argument) => | |
argument is NamedExpression && | |
argument.name.label.name == 'toLocal' && | |
argument.expression.isBooleanWithValue(value)); | |
} | |
extension on Expression { | |
bool isBooleanWithValue(bool value) => | |
this is BooleanLiteral && (this as BooleanLiteral).value == value; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Right! Thanks! Updated :)