From 764ec8a319858e177c29a3a0b3105aa9f1f6cf18 Mon Sep 17 00:00:00 2001 From: Seiya Kokushi Date: Tue, 31 Dec 2024 23:34:11 +0900 Subject: [PATCH] feat: Add low_readability_numeric_literals lint rule and quick fix (#210) --- melos.yaml | 6 +- packages/nilts/README.md | 29 +++++ packages/nilts/lib/nilts.dart | 3 + packages/nilts/lib/src/change_priority.dart | 3 + .../low_readability_numeric_literals.dart | 114 ++++++++++++++++++ packages/nilts_test/pubspec.yaml | 4 +- .../low_readability_numeric_literals.dart | 63 ++++++++++ pubspec.yaml | 2 +- 8 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 packages/nilts/lib/src/lints/low_readability_numeric_literals.dart create mode 100644 packages/nilts_test/test/lints/low_readability_numeric_literals.dart diff --git a/melos.yaml b/melos.yaml index 1b4f59c..42b2f6b 100644 --- a/melos.yaml +++ b/melos.yaml @@ -75,11 +75,13 @@ scripts: description: format (dry-run) fmt:dart: - run: melos exec -c 1 --fail-fast -- dart format . + # https://github.com/dart-lang/sdk/issues/59815 + run: melos exec -c 1 --fail-fast -- dart format --enable-experiment=digit-separators . description: format dart fmt:dart:dry: - run: melos exec -c 1 --fail-fast -- dart format --set-exit-if-changed . + # https://github.com/dart-lang/sdk/issues/59815 + run: melos exec -c 1 --fail-fast -- dart format --set-exit-if-changed --enable-experiment=digit-separators . description: format dart (dry-run) fix: diff --git a/packages/nilts/README.md b/packages/nilts/README.md index bb57887..cdd8bcc 100644 --- a/packages/nilts/README.md +++ b/packages/nilts/README.md @@ -101,6 +101,7 @@ Some of lint rules support quick fixes on IDE. | [defined\_void\_callback\_type](#defined_void_callback_type) | Checks `void Function()` definitions. | Any versions nilts supports | Practice | Experimental | ✅️ | | [fixed\_text\_scale\_rich\_text](#fixed_text_scale_rich_text) | Checks usage of `textScaler` or `textScaleFactor` in `RichText` constructor. | Any versions nilts supports | Practice | Experimental | ✅️ | | [flaky\_tests\_with\_set\_up\_all](#flaky_tests_with_set_up_all) | Checks `setUpAll` usages. | Any versions nilts supports | Practice | Experimental | ✅️ | +| [low\_readability\_numeric\_literals](#low_readability_numeric_literals) | Checks numeric literals with 5 or more digits. | >= Flutter 3.27.0 (Dart 3.6.0) | Practice | Experimental | ✅️ | | [no\_support\_multi\_text\_direction](#no_support_multi_text_direction) | Checks if supports `TextDirection` changes. | Any versions nilts supports | Practice | Experimental | ✅️ | | [no\_support\_web\_platform\_check](#no_support_web_platform_check) | Checks if `Platform.isXxx` usages. | Any versions nilts supports | Practice | Experimental | ✅️ | | [shrink\_wrapped\_scroll\_view](#shrink_wrapped_scroll_view) | Checks the content of the scroll view is shrink wrapped. | Any versions nilts supports | Practice | Experimental | ✅️ | @@ -412,6 +413,34 @@ See also: +#### low_readability_numeric_literals + +
+ +- Target SDK : >= Flutter 3.27.0 (Dart 3.6.0) +- Rule type : Practice +- Maturity level : Experimental +- Quick fix : ✅ + +**Consider** using digit separators for numeric literals with 5 or more digits to improve readability. + +**BAD:** +```dart +const int value = 123456; +``` + +**GOOD:** +```dart +const int value = 123_456; +``` + +See also: + +- [Digit Separators in Dart 3.6](https://medium.com/dartlang/announcing-dart-3-6-778dd7a80983) +- [Built-in types | Dart](https://dart.dev/language/built-in-types#numbers) + +
+ #### no_support_multi_text_direction
diff --git a/packages/nilts/lib/nilts.dart b/packages/nilts/lib/nilts.dart index 54a7789..b0e4c30 100644 --- a/packages/nilts/lib/nilts.dart +++ b/packages/nilts/lib/nilts.dart @@ -8,6 +8,7 @@ import 'package:nilts/src/lints/defined_value_getter_type.dart'; import 'package:nilts/src/lints/defined_void_callback_type.dart'; import 'package:nilts/src/lints/fixed_text_scale_rich_text.dart'; import 'package:nilts/src/lints/flaky_tests_with_set_up_all.dart'; +import 'package:nilts/src/lints/low_readability_numeric_literals.dart'; import 'package:nilts/src/lints/no_support_multi_text_direction.dart'; import 'package:nilts/src/lints/no_support_web_platform_check.dart'; import 'package:nilts/src/lints/shrink_wrapped_scroll_view.dart'; @@ -35,6 +36,8 @@ class _NiltsLint extends PluginBase { else const FixedTextScaleRichTextLegacy(), const FlakyTestsWithSetUpAll(), + if (_dartVersion >= const DartVersion(major: 3, minor: 6, patch: 0)) + const LowReadabilityNumericLiterals(), const NoSupportMultiTextDirection(), const NoSupportWebPlatformCheck(), const ShrinkWrappedScrollView(), diff --git a/packages/nilts/lib/src/change_priority.dart b/packages/nilts/lib/src/change_priority.dart index e44105b..8063df7 100644 --- a/packages/nilts/lib/src/change_priority.dart +++ b/packages/nilts/lib/src/change_priority.dart @@ -8,6 +8,9 @@ /// See also: /// - [IgnoreCode](https://github.com/invertase/dart_custom_lint/blob/1df2851a80ccdc5a2bda4418006560f49c03b8ec/packages/custom_lint_builder/lib/src/ignore.dart#L102) class ChangePriority { + /// The priority for [_AddDigitSeparators] + static const int addDigitSeparators = 100; + /// The priority for [_AddTextScaleFactor]. static const int addTextScaleFactor = 90; diff --git a/packages/nilts/lib/src/lints/low_readability_numeric_literals.dart b/packages/nilts/lib/src/lints/low_readability_numeric_literals.dart new file mode 100644 index 0000000..21ab21c --- /dev/null +++ b/packages/nilts/lib/src/lints/low_readability_numeric_literals.dart @@ -0,0 +1,114 @@ +import 'package:analyzer/error/error.dart' as analyzer; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:nilts/src/change_priority.dart'; + +/// A class for `low_readability_numeric_literals` rule. +/// +/// This rule checks numeric literals with 5 or more digits. +/// +/// - Target SDK : >= Flutter 3.27.0 (Dart 3.6.0) +/// - Rule type : Practice +/// - Maturity level : Experimental +/// - Quick fix : ✅ +/// +/// **Consider** using digit separators for numeric literals with 5 or more +/// digits to improve readability. +/// +/// **BAD:** +/// ```dart +/// const int value = 123456; +/// ``` +/// +/// **GOOD:** +/// ```dart +/// const int value = 123_456; +/// ``` +/// +/// See also: +/// +/// - [Digit Separators in Dart 3.6](https://medium.com/dartlang/announcing-dart-3-6-778dd7a80983) +/// - [Built-in types | Dart](https://dart.dev/language/built-in-types#numbers) +class LowReadabilityNumericLiterals extends DartLintRule { + /// Creates a new instance of [LowReadabilityNumericLiterals]. + const LowReadabilityNumericLiterals() : super(code: _code); + + static const _code = LintCode( + name: 'low_readability_numeric_literals', + problemMessage: + 'Numeric literals with 5 or more digits should use digit separators ' + 'for better readability.', + url: 'https://github.com/ronnnnn/nilts#low_readability_numeric_literals', + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addIntegerLiteral((node) { + final value = node.value; + if (value == null) return; + + final literal = node.literal.lexeme; + if (literal.contains('_')) return; + + if (value.abs() >= 10000) { + reporter.atNode(node, _code); + } + }); + } + + @override + List getFixes() => [ + _AddDigitSeparators(), + ]; +} + +class _AddDigitSeparators extends DartFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + analyzer.AnalysisError analysisError, + List others, + ) { + context.registry.addIntegerLiteral((node) { + if (!node.sourceRange.intersects(analysisError.sourceRange)) return; + + final value = node.value; + if (value == null) return; + + final literal = node.literal.lexeme; + if (literal.contains('_')) return; + + reporter + .createChangeBuilder( + message: 'Add digit separators', + priority: ChangePriority.addDigitSeparators, + ) + .addDartFileEdit((builder) { + final newLiteral = _addSeparators(literal); + builder.addSimpleReplacement(node.sourceRange, newLiteral); + }); + }); + } + + String _addSeparators(String literal) { + final buffer = StringBuffer(); + var count = 0; + + for (var i = literal.length - 1; i >= 0; i--) { + buffer.write(literal[i]); + count++; + if (count == 3 && i != 0) { + buffer.write('_'); + count = 0; + } + } + + return buffer.toString().split('').reversed.join(); + } +} diff --git a/packages/nilts_test/pubspec.yaml b/packages/nilts_test/pubspec.yaml index 8184512..307a620 100644 --- a/packages/nilts_test/pubspec.yaml +++ b/packages/nilts_test/pubspec.yaml @@ -3,8 +3,8 @@ description: A new Flutter module project. publish_to: 'none' environment: - sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.10.0' + sdk: '>=3.6.0 <4.0.0' + flutter: '>=3.27.1' dependencies: flutter: diff --git a/packages/nilts_test/test/lints/low_readability_numeric_literals.dart b/packages/nilts_test/test/lints/low_readability_numeric_literals.dart new file mode 100644 index 0000000..8b652e8 --- /dev/null +++ b/packages/nilts_test/test/lints/low_readability_numeric_literals.dart @@ -0,0 +1,63 @@ +// ignore_for_file: avoid_js_rounded_ints +// ignore_for_file: prefer_const_declarations +// ignore_for_file: prefer_final_locals +// ignore_for_file: unused_element +// ignore_for_file: unused_local_variable + +const _globalConstant1 = 1234; +// expect_lint: low_readability_numeric_literals +const _globalConstant2 = 12345; + +var _globalVariable1 = 1234; +// expect_lint: low_readability_numeric_literals +var _globalVariable2 = 12345; + +final _globalFinal1 = 1234; +// expect_lint: low_readability_numeric_literals +final _globalFinal2 = 12345; + +void main() { + const constant1 = 1234; + // expect_lint: low_readability_numeric_literals + const constant2 = 12345; + const constant3 = 12_345; + // expect_lint: low_readability_numeric_literals + const constant4 = 1234567890; + const constant5 = 1_234_567_890; + // expect_lint: low_readability_numeric_literals + const constant6 = 123456789012345; + const constant7 = 123_456_789_012_345; + + var variable1 = 1234; + // expect_lint: low_readability_numeric_literals + var variable2 = 12345; + var variable3 = 12_345; + // expect_lint: low_readability_numeric_literals + var variable4 = 1234567890; + var variable5 = 1_234_567_890; + // expect_lint: low_readability_numeric_literals + var variable6 = 123456789012345; + var variable7 = 123_456_789_012_345; + + final final1 = 1234; + // expect_lint: low_readability_numeric_literals + final final2 = 12345; + final final3 = 12_345; + // expect_lint: low_readability_numeric_literals + final final4 = 1234567890; + final final5 = 1_234_567_890; + // expect_lint: low_readability_numeric_literals + final final6 = 123456789012345; + final final7 = 123_456_789_012_345; + + const hex1 = 0x1234; + // expect_lint: low_readability_numeric_literals + const hex2 = 0x12345; + const hex3 = 0x12_345; + // expect_lint: low_readability_numeric_literals + const hex4 = 0x1234567890; + const hex5 = 0x1_234_567_890; + // expect_lint: low_readability_numeric_literals + const hex6 = 0x123456789012345; + const hex7 = 0x123_456_789_012_345; +} diff --git a/pubspec.yaml b/pubspec.yaml index ac50a03..72e44ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: nilts_workspace publish_to: 'none' environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' dev_dependencies: melos: ^6.2.0