add-lints (#3356)
* test add lint as separate action * nicer logging * only detect changes in dart files * fix printV warnings being displayed when editing printV --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
malik1004x committed
Jul 14, 2026 at 12:50 UTC
01c977e82be522b27b08a76d79da5306c29a8ead
37 files changed
+587
-199
.github/workflows/lint.yml
new
+109
@@ -0,0 +1,109 @@
1
+name: Lint
2
+
3
+on: [pull_request]
4
+
5
+defaults:
6
+ run:
7
+ shell: bash
8
+jobs:
9
+ lint:
10
+ runs-on: [Linux, amd64, android]
11
+ container:
12
+ image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly
13
+ env:
14
+ STORE_PASS: test@cake_wallet
15
+ KEY_PASS: test@cake_wallet
16
+ MONEROC_CACHE_DIR_ROOT: /opt/generic_cache
17
+ BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
18
+ ANDROID_AVD_HOME: /root/.android/avd
19
+ volumes:
20
+ - /opt/cw_cache_android/root/.cache:/root/.cache
21
+ - /opt/cw_cache_android/root/.android/avd/:/root/.android/avd
22
+ - /opt/cw_cache_android/root/.ccache:/root/.ccache
23
+ - /opt/cw_cache_android/root/.pub-cache/:/root/.pub-cache
24
+ - /opt/cw_cache_android/root/.gradle/:/root/.gradle
25
+ - /opt/cw_cache_android/root/.android/:/root/.android
26
+ - /opt/cw_cache_android/root/go/pkg:/root/go/pkg
27
+ - /opt/cw_cache_android/opt/generic_cache:/opt/generic_cache
28
+ - /dev/kvm:/dev/kvm
29
+ - /var/run/docker.sock:/var/run/docker.sock
30
+ strategy:
31
+ matrix:
32
+ api-level: [ 29 ]
33
+
34
+ steps:
35
+ - name: configure git
36
+ run: |
37
+ git config --global --add safe.directory '*'
38
+ git config --global user.email "ci@cakewallet.com"
39
+ git config --global user.name "CakeWallet CI"
40
+
41
+ - name: Fetch prebuilt Torch
42
+ run: |
43
+ set -x -e
44
+ pushd scripts
45
+ wget https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz -O torch_dart.tar.gz
46
+ rm -rf torch_dart
47
+ mkdir torch_dart
48
+ pushd torch_dart
49
+ tar -xzf ../torch_dart.tar.gz
50
+ popd
51
+ rm ./torch_dart.tar.gz
52
+ popd
53
+
54
+ - name: Fetch prebuilt Reown
55
+ run: |
56
+ set -x -e
57
+ pushd scripts
58
+ # cleaning
59
+ rm -rf reown_flutter
60
+ rm -f reown_flutter.tar.gz
61
+
62
+ wget https://github.com/cake-tech/reown_flutter/releases/download/v0.0.4/reown_flutter-v0.0.4.tar.gz -O reown_flutter.tar.gz
63
+ mkdir reown_flutter
64
+ pushd reown_flutter
65
+ tar -xzf ../reown_flutter.tar.gz
66
+ popd
67
+ rm ./reown_flutter.tar.gz
68
+ popd
69
+
70
+ - name: Build Bitbox Flutter
71
+ run: |
72
+ set -x -e
73
+ pushd scripts
74
+ ./build_bitbox_flutter.sh
75
+ popd
76
+
77
+ - name: Prepare Zcash
78
+ run: |
79
+ set -x -e
80
+ scripts/prepare_zcash.sh
81
+
82
+ - name: Execute Build and Setup Commands
83
+ run: |
84
+ pushd scripts/android
85
+ source ./app_env.sh cakewallet
86
+ export GITHUB_HUH=yeah
87
+ ./app_config.sh
88
+ popd
89
+
90
+ - name: Install Flutter dependencies
91
+ run: |
92
+ flutter pub get
93
+
94
+ - name: Build generated code
95
+ run: |
96
+ flutter --version
97
+ flutter clean
98
+ rm -rf .dart_tool
99
+ rm pubspec.lock
100
+ flutter pub get
101
+ ./model_generator.sh async
102
+
103
+ - name: Generate localization
104
+ run: |
105
+ dart run tool/generate_localization.dart
106
+
107
+ - name: Lint
108
+ run: |
109
+ ABORT_ON_CHANGE=yeah scripts/lint.sh
\ No newline at end of file
.github/workflows/no_http_imports.yaml
+1
-2
@@ -11,11 +11,10 @@ jobs:
11
- name: Check for http package usage
12
if: github.event_name == 'pull_request'
13
run: |
14
- GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)"
14
+ GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) | (grep -v '^cw_custom_lints/' || test $? = 1) || true)"
15
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
16
echo "$GIT_GREP_OUT"
17
echo "There are .dart files which use http imports"
18
echo "Using http package breaks proxy integration"
19
echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart"
20
exit 1
21
-
\ No newline at end of file
.github/workflows/no_print_in_dart.yaml
+1
-1
@@ -11,7 +11,7 @@ jobs:
11
- name: Check for print() statements in dart code (use printV() instead)
12
if: github.event_name == 'pull_request'
13
run: |
14
- GIT_GREP_OUT="$(git grep ' print(' | (grep .dart: || test $? = 1) | (grep -v print_verbose.dart || test $? = 1) | (grep -v print_verbose_dummy.dart || test $? = 1) || true)"
14
+ GIT_GREP_OUT="$(git grep ' print(' | (grep .dart: || test $? = 1) | (grep -v print_verbose.dart || test $? = 1) | (grep -v print_verbose_dummy.dart || test $? = 1) | (grep -v '^tool/' || test $? = 1) | (grep -v '^cw_custom_lints/' || test $? = 1) || true)"
15
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
16
echo "$GIT_GREP_OUT"
17
echo "There are .dart files which use print() statements"
.github/workflows/pr_test_build_linux.yml
-3
@@ -179,9 +179,6 @@ jobs:
179
with:
180
path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip
181
name: cakewallet_linux
182
- - name: Verify lint
183
- run: |
184
- exec env ABORT_ON_CHANGE=true ./scripts/lint.sh
182
183
- name: Prepare virtual desktop
184
if: ${{ contains(env.message, 'run tests') }}
analysis_options.yaml
+113
-54
@@ -1,4 +1,3 @@
1
-include: package:lints/recommended.yaml
1
2
3
analyzer:
@@ -9,6 +8,7 @@ analyzer:
8
cw_core/lib/**.g.dart,
9
cw_haven/lib/**.g.dart,
10
cw_monero/lib/**.g.dart,
11
+ cw_mweb/lib/**.g.dart,
12
lib/generated/*.dart,
13
cw_monero/ios/External/**,
14
cw_shared_external/**,
@@ -18,65 +18,124 @@ analyzer:
18
strict-casts: true
19
strict-raw-types: true
20
21
+plugins:
22
+ cw_custom_lints:
23
+ path: cw_custom_lints
24
+
25
linter:
26
rules:
27
- cancel_subscriptions
28
- always_declare_return_types
29
- prefer_final_fields
30
+ - always_put_control_body_on_new_line
31
+ - always_put_required_named_parameters_first
32
+ - always_use_package_imports
33
+ - annotate_overrides
34
+ - annotate_redeclares
35
+ - avoid_empty_else
36
+ - avoid_function_literals_in_foreach_calls
37
+ - avoid_init_to_null
38
+ - avoid_multiple_declarations_per_line
39
+ - avoid_positional_boolean_parameters
40
+ - avoid_relative_lib_imports
41
+ - avoid_renaming_method_parameters
42
+ - avoid_return_types_on_setters
43
+ - avoid_returning_null_for_void
44
+ - avoid_slow_async_io
45
+ - avoid_types_as_parameter_names
46
+ - avoid_types_on_closure_parameters
47
+ - avoid_unnecessary_containers
48
+ - avoid_unused_constructor_parameters
49
+ - avoid_void_async
50
+ - await_only_futures
51
+ - camel_case_extensions
52
+ - camel_case_types
53
+ - cast_nullable_to_non_nullable
54
+ - collection_methods_unrelated_type
55
+ - constant_identifier_names
56
+ - curly_braces_in_flow_control_structures
57
+ - directives_ordering
58
+ - empty_constructor_bodies
59
+ - empty_statements
60
+ - eol_at_end_of_file
61
+ - flutter_style_todos
62
+ - hash_and_equals
63
+ - implicit_reopen
64
+ - library_private_types_in_public_api
65
+ - non_constant_identifier_names
66
+ - only_throw_errors
67
+ - package_names
68
+ - prefer_asserts_with_message
69
+ - prefer_conditional_assignment
70
+ - prefer_const_constructors
71
+ - prefer_const_constructors_in_immutables
72
+ - prefer_const_declarations
73
+ - prefer_constructors_over_static_methods
74
+ - prefer_contains
75
+ - prefer_double_quotes
76
+ - prefer_expression_function_bodies
77
+ - prefer_final_in_for_each
78
+ - prefer_function_declarations_over_variables
79
+ - prefer_if_null_operators
80
+ - prefer_initializing_formals
81
+ - prefer_inlined_adds
82
+ - prefer_int_literals
83
+ - prefer_is_empty
84
+ - prefer_is_not_empty
85
+ - prefer_is_not_operator
86
+ - prefer_iterable_wheretype
87
+ - prefer_null_aware_operators
88
+ - prefer_spread_collections
89
+ - prefer_typing_uninitialized_variables
90
+ - recursive_getters
91
+ - require_trailing_commas
92
+ - sized_box_for_whitespace
93
+ - sort_child_properties_last
94
+ - sort_constructors_first
95
+ - sort_unnamed_constructors_first
96
+ - test_types_in_equals
97
+ - unawaited_futures
98
+ - unnecessary_async
99
+ - unnecessary_await_in_return
100
+ - unnecessary_const
101
+ - unnecessary_constructor_name
102
+ - unnecessary_lambdas
103
+ - unnecessary_late
104
+ - unnecessary_new
105
+ - unnecessary_null_aware_assignments
106
+ - unnecessary_null_checks
107
+ - unnecessary_null_in_if_null_operators
108
+ - unnecessary_nullable_for_final_variable_declarations
109
+ - unnecessary_overrides
110
+ - unnecessary_parenthesis
111
+ - unnecessary_string_escapes
112
+ - unnecessary_string_interpolations
113
+ - unnecessary_this
114
+ - unnecessary_to_list_in_spreads
115
+ - unnecessary_underscores
116
+ - unreachable_from_main
117
+ - unrelated_type_equality_checks
118
+ - use_build_context_synchronously
119
+ - use_colored_box
120
+ - use_full_hex_values_for_flutter_colors
121
+ - use_function_type_syntax_for_parameters
122
+ - use_is_even_rather_than_modulo
123
+ - use_named_constants
124
+ - use_null_aware_elements
125
+ - use_raw_strings
126
+ - use_rethrow_when_possible
127
+ - use_string_buffers
128
+ - use_super_parameters
129
+ - use_truncating_division
130
+ - valid_regexps
131
+ - void_checks
132
+ - prefer_final_locals
133
+ - prefer_interpolation_to_compose_strings
134
+ - null_closures
135
+ - no_wildcard_variable_uses
136
137
28
-formatter:
29
- page_width: 100
30
-
138
32
-# analyzer:
33
-# strong-mode:
34
-# implicit-casts: false
35
-# implicit-dynamic: false
36
-# exclude: [build/**, lib/generated/*.dart, lib/**.g.dart, cw_monero/ios/External/**, cw_shared_external/**, shared_external/**]
139
38
-# linter:
39
-# rules:
40
-# - always_declare_return_types
41
-# - annotate_overrides
42
-# - avoid_empty_else
43
-# - avoid_init_to_null
44
-# - avoid_return_types_on_setters
45
-# - await_only_futures
46
-# - camel_case_types
47
-# - cancel_subscriptions
48
-# - close_sinks
49
-# - comment_references
50
-# - constant_identifier_names
51
-# - control_flow_in_finally
52
-# - empty_catches
53
-# - empty_constructor_bodies
54
-# - empty_statements
55
-# - hash_and_equals
56
-# - invariant_booleans
57
-# - iterable_contains_unrelated_type
58
-# - library_names
59
-# - library_prefixes
60
-# - list_remove_unrelated_type
61
-# - literal_only_boolean_expressions
62
-# - non_constant_identifier_names
63
-# - one_member_abstracts
64
-# - only_throw_errors
65
-# - overridden_fields
66
-# - package_api_docs
67
-# - package_names
68
-# - package_prefixed_library_names
69
-# - parameter_assignments
70
-# - prefer_final_fields
71
-# - prefer_final_locals
72
-# - prefer_is_not_empty
73
-# - slash_for_doc_comments
74
-# - sort_constructors_first
75
-# - sort_unnamed_constructors_first
76
-# - test_types_in_equals
77
-# - throw_in_finally
78
-# - type_init_formals
79
-# - unawaited_futures
80
-# - unnecessary_getters_setters
81
-# - unrelated_type_equality_checks
82
-# - valid_regexps
140
+formatter:
141
+ page_width: 100
cw_bitcoin/pubspec.yaml
+1
-1
@@ -6,7 +6,7 @@ author: Cake Wallet
6
homepage: https://cakewallet.com
7
8
environment:
9
- sdk: ">=2.17.5 <3.0.0"
9
+ sdk: ">=3.0.0 <4.0.0"
10
flutter: ">=1.20.0"
11
12
dependencies:
cw_bitcoin_cash/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_custom_lints/lib/http_force_proxy/http_force_proxy_rule.dart
new
+62
@@ -0,0 +1,62 @@
1
+import "package:analyzer/analysis_rule/analysis_rule.dart";
2
+import "package:analyzer/analysis_rule/rule_context.dart";
3
+import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
4
+import "package:analyzer/dart/ast/ast.dart";
5
+import "package:analyzer/dart/ast/visitor.dart";
6
+import "package:analyzer/error/error.dart";
7
+
8
+class HttpForceProxyRule extends AnalysisRule {
9
+ HttpForceProxyRule()
10
+ : super(
11
+ name: "no_http_imports",
12
+ description:
13
+ "Using the http package breaks proxy integration. Please use ProxyWrapper or alias it as \"very_insecure_http_do_not_use\".",
14
+ );
15
+
16
+ static const LintCode code = LintCode(
17
+ "no_http_imports",
18
+ "Using the http package breaks proxy integration. Please use ProxyWrapper or alias it as \"very_insecure_http_do_not_use\".",
19
+ severity: DiagnosticSeverity.WARNING,
20
+ );
21
+
22
+ @override
23
+ LintCode get diagnosticCode => code;
24
+
25
+ @override
26
+ void registerNodeProcessors(
27
+ RuleVisitorRegistry registry,
28
+ RuleContext context,
29
+ ) {
30
+ final filePath = context.definingUnit.file.path;
31
+
32
+ if (filePath.endsWith("proxy_wrapper.dart")) {
33
+ return;
34
+ }
35
+
36
+ registry.addImportDirective(this, _Visitor(this));
37
+ }
38
+}
39
+
40
+class _Visitor extends SimpleAstVisitor<void> {
41
+ _Visitor(this.rule);
42
+
43
+ final AnalysisRule rule;
44
+
45
+ @override
46
+ void visitImportDirective(ImportDirective node) {
47
+ final uriString = node.uri.stringValue;
48
+ if (uriString == null) {
49
+ return;
50
+ }
51
+
52
+ if (uriString.startsWith("package:http")) {
53
+ final prefixNode = node.prefix;
54
+ if (prefixNode != null &&
55
+ prefixNode.name == "very_insecure_http_do_not_use") {
56
+ return;
57
+ }
58
+
59
+ rule.reportAtNode(node);
60
+ }
61
+ }
62
+}
cw_custom_lints/lib/main.dart
new
+22
@@ -0,0 +1,22 @@
1
+import "package:analysis_server_plugin/plugin.dart";
2
+import "package:analysis_server_plugin/registry.dart";
3
+import "package:cw_custom_lints/http_force_proxy/http_force_proxy_rule.dart";
4
+import "package:cw_custom_lints/print_verbose/print_verbose_fix.dart";
5
+import "package:cw_custom_lints/print_verbose/print_verbose_rule.dart";
6
+import "package:cw_custom_lints/restricted_imports/restricted_imports_rule.dart";
7
+
8
+final plugin = CwCustomLintsPlugin();
9
+
10
+class CwCustomLintsPlugin extends Plugin {
11
+ @override
12
+ String get name => "cw_custom_lints";
13
+
14
+ @override
15
+ void register(PluginRegistry registry) {
16
+ registry.registerWarningRule(PrintVerboseRule());
17
+ registry.registerWarningRule(RestrictedImportsRule());
18
+ registry.registerWarningRule(HttpForceProxyRule());
19
+
20
+ registry.registerFixForRule(PrintVerboseRule.code, ReplaceWithPrintV.new);
21
+ }
22
+}
cw_custom_lints/lib/print_verbose/print_verbose_fix.dart
new
+50
@@ -0,0 +1,50 @@
1
+import "package:analysis_server_plugin/edit/dart/correction_producer.dart";
2
+import "package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart";
3
+import "package:analyzer/dart/ast/ast.dart";
4
+import "package:analyzer_plugin/utilities/change_builder/change_builder_core.dart";
5
+import "package:analyzer_plugin/utilities/fixes/fixes.dart";
6
+import "package:analyzer_plugin/utilities/range_factory.dart";
7
+
8
+class ReplaceWithPrintV extends ResolvedCorrectionProducer {
9
+ ReplaceWithPrintV({required super.context});
10
+
11
+ static const _importString = "package:cw_core/utils/print_verbose.dart";
12
+
13
+ static const _fixKind = FixKind(
14
+ "dart.fix.replaceWithPrintV",
15
+ DartFixKindPriority.standard,
16
+ "Replace with printV()",
17
+ );
18
+
19
+ @override
20
+ CorrectionApplicability get applicability =>
21
+ CorrectionApplicability.singleLocation;
22
+
23
+ @override
24
+ FixKind get fixKind => _fixKind;
25
+
26
+ @override
27
+ Future<void> compute(ChangeBuilder builder) async {
28
+ final invocation = node.thisOrAncestorOfType<MethodInvocation>();
29
+ if (invocation == null) {
30
+ return;
31
+ }
32
+
33
+ final root = invocation.root;
34
+ final hasImport = root is CompilationUnit &&
35
+ root.directives.whereType<ImportDirective>().any(
36
+ (directive) => directive.uri.stringValue == _importString,
37
+ );
38
+
39
+ await builder.addDartFileEdit(file, (builder) {
40
+ builder.addSimpleReplacement(
41
+ range.node(invocation.methodName),
42
+ "printV",
43
+ );
44
+
45
+ if (!hasImport) {
46
+ builder.importLibrary(Uri.parse(_importString));
47
+ }
48
+ });
49
+ }
50
+}
cw_custom_lints/lib/print_verbose/print_verbose_rule.dart
new
+52
@@ -0,0 +1,52 @@
1
+import "package:analyzer/analysis_rule/analysis_rule.dart";
2
+import "package:analyzer/analysis_rule/rule_context.dart";
3
+import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
4
+import "package:analyzer/dart/ast/ast.dart";
5
+import "package:analyzer/dart/ast/visitor.dart";
6
+import "package:analyzer/error/error.dart";
7
+
8
+class PrintVerboseRule extends AnalysisRule {
9
+ PrintVerboseRule()
10
+ : super(
11
+ name: "use_print_v",
12
+ description: "Use printV() from cw_core instead of print().",
13
+ );
14
+
15
+ static const LintCode code = LintCode(
16
+ "use_print_v",
17
+ "Use printV() from cw_core instead",
18
+ correctionMessage: "Replace print with printV",
19
+ severity: DiagnosticSeverity.WARNING,
20
+ );
21
+
22
+ @override
23
+ LintCode get diagnosticCode => code;
24
+
25
+ @override
26
+ void registerNodeProcessors(
27
+ RuleVisitorRegistry registry,
28
+ RuleContext context,
29
+ ) {
30
+ final filePath = context.definingUnit.file.path;
31
+
32
+ if (filePath.contains("/tool/") || filePath.contains("print_verbose.dart")) {
33
+ // tool/ is allowed to use print as it never makes its way into the app
34
+ return;
35
+ }
36
+
37
+ registry.addMethodInvocation(this, _Visitor(this));
38
+ }
39
+}
40
+
41
+class _Visitor extends SimpleAstVisitor<void> {
42
+ _Visitor(this.rule);
43
+
44
+ final AnalysisRule rule;
45
+
46
+ @override
47
+ void visitMethodInvocation(MethodInvocation node) {
48
+ if (node.methodName.name == "print" && node.target == null) {
49
+ rule.reportAtNode(node);
50
+ }
51
+ }
52
+}
cw_custom_lints/lib/restricted_imports/restricted_imports_rule.dart
new
+79
@@ -0,0 +1,79 @@
1
+import "package:analyzer/analysis_rule/analysis_rule.dart";
2
+import "package:analyzer/analysis_rule/rule_context.dart";
3
+import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
4
+import "package:analyzer/dart/ast/ast.dart";
5
+import "package:analyzer/dart/ast/visitor.dart";
6
+import "package:analyzer/error/error.dart";
7
+
8
+class RestrictedImportsRule extends AnalysisRule {
9
+ RestrictedImportsRule()
10
+ : super(
11
+ name: "no_restricted_imports_in_lib",
12
+ description:
13
+ "Do not import from coin-specific packages. If necessary, wire your logic through configure.dart",
14
+ );
15
+
16
+ static const LintCode code = LintCode(
17
+ "no_restricted_imports_in_lib",
18
+ "Do not import from coin-specific packages. If necessary, wire your logic through configure.dart",
19
+ severity: DiagnosticSeverity.ERROR,
20
+ );
21
+
22
+ static const _restrictedPackages = {
23
+ "cw_bitcoin",
24
+ "cw_bitcoin_cash",
25
+ "cw_electrum",
26
+ "cw_litecoin",
27
+ "cw_evm",
28
+ "cw_mweb",
29
+ "cw_nano",
30
+ "cw_solana",
31
+ "cw_tron",
32
+ "cw_zano",
33
+ };
34
+
35
+ @override
36
+ LintCode get diagnosticCode => code;
37
+
38
+ @override
39
+ void registerNodeProcessors(
40
+ RuleVisitorRegistry registry,
41
+ RuleContext context,
42
+ ) {
43
+ final filePath = context.definingUnit.file.path;
44
+ final fileName = filePath.split("/").last;
45
+
46
+ // ignore cw_something folders
47
+ if (filePath.contains("cw_")) {
48
+ return;
49
+ }
50
+
51
+ // allow ex. cw_bitcoin in bitcoin.dart
52
+ if (_restrictedPackages.contains("cw_" + fileName.replaceAll(".dart", ""))) {
53
+ return;
54
+ }
55
+
56
+ registry.addImportDirective(this, _Visitor(this));
57
+ }
58
+}
59
+
60
+class _Visitor extends SimpleAstVisitor<void> {
61
+ _Visitor(this.rule);
62
+
63
+ final AnalysisRule rule;
64
+
65
+ @override
66
+ void visitImportDirective(ImportDirective node) {
67
+ final uriString = node.uri.stringValue;
68
+ if (uriString == null || !uriString.startsWith("package:")) {
69
+ return;
70
+ }
71
+
72
+ final pathWithoutScheme = uriString.replaceFirst("package:", "");
73
+ final packageName = pathWithoutScheme.split("/").first;
74
+
75
+ if (RestrictedImportsRule._restrictedPackages.contains(packageName)) {
76
+ rule.reportAtNode(node);
77
+ }
78
+ }
79
+}
cw_custom_lints/pubspec.yaml
new
+15
@@ -0,0 +1,15 @@
1
+name: cw_custom_lints
2
+version: 0.0.1
3
+environment:
4
+ sdk: '>=3.9.0 <4.0.0'
5
+
6
+
7
+dependencies:
8
+ analysis_server_plugin: 0.3.15
9
+ analyzer: 13.0.0
10
+ analyzer_plugin: 0.14.9
11
+
12
+dev_dependencies:
13
+ analyzer_testing: 0.2.6
14
+ test_reflective_loader: 0.4.0
15
+ test: 1.31.2
cw_decred/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_dogecoin/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_evm/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_monero/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_mweb/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_nano/pubspec.yaml
+1
-1
@@ -6,7 +6,7 @@ author: Cake Wallet
6
homepage: https://cakewallet.com
7
8
environment:
9
- sdk: '>=2.18.2 <3.0.0'
9
+ sdk: '>=3.0.0 <4.0.0'
10
flutter: ">=1.17.0"
11
12
dependencies:
cw_solana/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_tron/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_wownero/analysis_options.yaml
deleted
-4
@@ -1,4 +0,0 @@
1
-include: package:flutter_lints/flutter.yaml
2
-
3
-# Additional information about this file can be found at
4
-# https://dart.dev/guides/language/analysis-options
cw_zano/pubspec.yaml
+1
-1
@@ -5,7 +5,7 @@ publish_to: none
5
homepage: https://cakewallet.com
6
7
environment:
8
- sdk: ">=2.19.0 <3.0.0"
8
+ sdk: ">=3.0.0 <4.0.0"
9
flutter: ">=1.20.0"
10
11
dependencies:
cw_zcash/analysis_options.yaml
deleted
-31
@@ -1,31 +0,0 @@
1
-analyzer:
2
- errors:
3
- implementation_imports: ignore
4
- must_be_immutable: ignore
5
- overridden_fields: ignore
6
- exclude:
7
- - "**.g.dart"
8
- - "external/**"
9
-
10
-linter:
11
- rules:
12
- unawaited_futures: true
13
- avoid_print: false
14
- parameter_assignments: true
15
- prefer_const_declarations: true
16
- prefer_final_fields: true
17
- prefer_final_in_for_each: true
18
- prefer_final_locals: true
19
- prefer_final_parameters: true
20
- avoid_void_async: true
21
- require_trailing_commas: true
22
- sort_child_properties_last: true
23
- sort_constructors_first: true
24
- sort_unnamed_constructors_first: true
25
- unnecessary_async: true
26
- unnecessary_await_in_return: true
27
- unnecessary_string_interpolations: true
28
- always_declare_return_types: true
29
-
30
-formatter:
31
- page_width: 100
lib/bitcoin/cw_bitcoin.dart
+1
-1
@@ -177,7 +177,7 @@ class CWBitcoin extends Bitcoin {
177
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
178
final electrumWallet = wallet as ElectrumWallet;
179
return electrumWallet.walletAddresses.addressesByReceiveType
180
- .map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
180
+ .map<ElectrumSubAddress>((addr) => ElectrumSubAddress(
181
id: addr.index,
182
name: addr.name,
183
address: addr.address,
scripts/lint.sh
+22
-10
@@ -1,17 +1,29 @@
1
#!/bin/bash
2
-set -x -e
3
-cd $(dirname $0)
2
+set -e
3
+cd "$(dirname "$0")"
4
cd ..
5
6
-for i in cw_zcash;
6
+for i in cw_bitcoin cw_bitcoin_cash cw_core cw_decred cw_dogecoin cw_evm cw_monero cw_mweb cw_nano cw_solana cw_tron cw_zano cw_zcash lib;
7
do
8
- dart fix --apply $i/
9
- dart format --line-length=100 $i/
10
- if [[ ! "x$ABORT_ON_CHANGE" == "x" ]];
11
- then
12
- if [[ ! -z "$(git status --porcelain -- $(find $i))" ]];
13
- then
14
- echo "Please run scripts/lint.sh ($i has changes)"
8
+ {
9
+ # files modified after the lints got enabled and committed
10
+ git log --since="2026-07-14 23:59:59" --name-only --diff-filter=d --format="" -- "$i"
11
+ # uncommitted files (assumed to have been modified after the 29th)
12
+ git diff --name-only --diff-filter=d HEAD -- "$i"
13
+ } | grep '\.dart$' | sort -u | while IFS= read -r file; do
14
+ echo $file
15
+ if [[ -f "$file" ]]; then
16
+ dart fix --apply "$file" && dart format --line-length=100 "$file"
17
+ fi
18
+ done
19
+
20
+ if [[ -n "$ABORT_ON_CHANGE" ]]; then
21
+ DART_CHANGES=$(git status --porcelain -- "$i" | grep '\.dart$' || true)
22
+
23
+ if [[ -n "$DART_CHANGES" ]]; then
24
+ echo "Please run scripts/lint.sh"
25
+ echo "changes in $i:"
26
+ echo "$DART_CHANGES"
27
exit 1
28
fi
29
fi
tool/append_translation.dart
+3
-3
@@ -15,7 +15,7 @@ void main(List<String> args) async {
15
final text = args[1];
16
final force = args.last == "--force";
17
18
- printV('Appending "$name": "$text"');
18
+ print('Appending "$name": "$text"');
19
20
// add translation to all languages:
21
for (var lang in langs) {
@@ -25,12 +25,12 @@ void main(List<String> args) async {
25
appendStringToArbFile(fileName, name, translation, force: force);
26
}
27
28
- printV('Alphabetizing all files...');
28
+ print('Alphabetizing all files...');
29
30
for (var lang in langs) {
31
final fileName = getArbFileName(lang);
32
alphabetizeArbFile(fileName);
33
}
34
35
- printV('Done!');
35
+ print('Done!');
36
}
\ No newline at end of file
tool/download_moneroc_prebuilds.dart
+5
-5
@@ -31,7 +31,7 @@ Future<void> main() async {
31
final resp = await _dio.get("https://api.github.com/repos/mrcyjanek/monero_c/releases");
32
final data = resp.data[0];
33
final tagName = data['tag_name'];
34
- printV("Downloading artifacts for: ${tagName}");
34
+ print("Downloading artifacts for: ${tagName}");
35
final assets = data['assets'] as List<dynamic>;
36
for (var i = 0; i < assets.length; i++) {
37
for (var triplet in triplets) {
@@ -42,10 +42,10 @@ Future<void> main() async {
42
String localFilename = filename.replaceAll("${coin}_${triplet}_", "");
43
localFilename = "scripts/monero_c/release/${coin}/${triplet}_${localFilename}";
44
final url = asset["browser_download_url"] as String;
45
- printV("- downloading $localFilename");
45
+ print("- downloading $localFilename");
46
await _dio.download(url, localFilename);
47
if (localFilename.endsWith(".xz")) {
48
- printV(" extracting $localFilename");
48
+ print(" extracting $localFilename");
49
final inputStream = InputFileStream(localFilename);
50
final archive = XZDecoder().decodeBytes(inputStream.toUint8List());
51
final outputStream = OutputFileStream(localFilename.replaceAll(".xz", ""));
@@ -54,11 +54,11 @@ Future<void> main() async {
54
}
55
}
56
if (Platform.isMacOS) {
57
- printV("Generating ios framework");
57
+ print("Generating ios framework");
58
final result = Process.runSync("bash", [
59
"-c",
60
"cd scripts/ios && ./gen_framework.sh && cd ../.."
61
]);
62
- printV((result.stdout+result.stderr).toString().trim());
62
+ print((result.stdout+result.stderr).toString().trim());
63
}
64
}
\ No newline at end of file
tool/fiat_api_check.dart
+7
-7
@@ -76,7 +76,7 @@ void main() {
76
// --- B. Run App in a Zone to Capture Prints ---
77
runZoned(
78
() async {
79
- printV('--- Starting Verified Price Check ---');
79
+ print('--- Starting Verified Price Check ---');
80
81
final Map<String, List<String>> workingPairs = {};
82
final Map<String, List<String>> failedPairs = {};
@@ -130,7 +130,7 @@ void main() {
130
}
131
132
// Print immediate status (Captured by Zone)
133
- printV('$logPrefix $logMessage');
133
+ print('$logPrefix $logMessage');
134
135
// Aggregate
136
if (isSuccess) {
@@ -148,25 +148,25 @@ void main() {
148
}
149
150
// --- FINAL SUMMARY ---
151
- printV('\n\n=== SUMMARY ===\n');
151
+ print('\n\n=== SUMMARY ===\n');
152
153
// Print Successful
154
workingPairs.forEach((crypto, fiats) {
155
if (fiats.isNotEmpty) {
156
- printV('✅ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
156
+ print('✅ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
157
}
158
});
159
160
- printV('\n--------------------------------------------------\n');
160
+ print('\n--------------------------------------------------\n');
161
162
// Print Failed
163
failedPairs.forEach((crypto, fiats) {
164
if (fiats.isNotEmpty) {
165
- printV('❌ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
165
+ print('❌ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
166
}
167
});
168
169
- printV('\n=== DONE ===');
169
+ print('\n=== DONE ===');
170
},
171
172
// --- C. The Interceptor ---
tool/generate_localization.dart
+5
-5
@@ -37,7 +37,7 @@ Future<void> main(List<String> args) async {
37
38
extraInfo.forEach((key, dynamic value) async {
39
if (key != srcDir) {
40
- printV('Wrong key: $key');
40
+ print('Wrong key: $key');
41
return;
42
}
43
@@ -45,7 +45,7 @@ Future<void> main(List<String> args) async {
45
final dir = Directory(dirPath);
46
47
if (!await dir.exists()) {
48
- printV('Wrong directory path: $dirPath');
48
+ print('Wrong directory path: $dirPath');
49
return;
50
}
51
@@ -55,7 +55,7 @@ Future<void> main(List<String> args) async {
55
// normalizing the case so keys match LanguageService.supportedLocales.
56
final fileName = element.uri.pathSegments.last;
57
if (!fileName.startsWith('strings_') || !fileName.endsWith('.arb')) {
58
- printV('Wrong file: ${element.path}');
58
+ print('Wrong file: ${element.path}');
59
return;
60
}
61
final parts =
@@ -67,7 +67,7 @@ Future<void> main(List<String> args) async {
67
});
68
69
if (!localePath.keys.contains(defaultLocale)) {
70
- printV("Locale list doesn't contain $defaultLocale");
70
+ print("Locale list doesn't contain $defaultLocale");
71
return;
72
}
73
@@ -124,7 +124,7 @@ Future<void> main(List<String> args) async {
124
125
await File(outputPath + localeListFileName).writeAsString(locales);
126
} catch (e) {
127
- printV(e.toString());
127
+ print(e.toString());
128
}
129
});
130
}
tool/print_verbose_dummy.dart
-1
@@ -1 +0,0 @@
1
-void printV(dynamic content) => print(content);
\ No newline at end of file
tool/translate_changelog.dart
+1
-1
@@ -46,7 +46,7 @@ void main() async {
46
});
47
48
currentItem++;
49
- printV(
49
+ print(
50
"\r$currentLang/${langs.length} langs done, $currentItem/${defaultLangChangelog.length} items done");
51
}
52
currentLang++;
tool/translate_using_llm.dart
+29
-29
@@ -25,18 +25,18 @@ class TranslationChecker {
25
final sourceMap = json.decode(sourceContent) as Map<String, dynamic>;
26
final destinationMap = json.decode(destinationContent) as Map<String, dynamic>;
27
28
- printV('Found ${sourceMap.length} keys in source, ${destinationMap.length} in destination');
28
+ print('Found ${sourceMap.length} keys in source, ${destinationMap.length} in destination');
29
30
final keysToProcess = specificKey != null
31
? (sourceMap.containsKey(specificKey) ? [specificKey] : <String>[])
32
: sourceMap.keys.toList();
33
34
if (specificKey != null && keysToProcess.isEmpty) {
35
- printV('Error: Key "$specificKey" not found in source file');
35
+ print('Error: Key "$specificKey" not found in source file');
36
return;
37
}
38
39
- printV('Processing ${keysToProcess.length} translations...');
39
+ print('Processing ${keysToProcess.length} translations...');
40
41
int processed = 0;
42
int corrected = 0;
@@ -57,16 +57,16 @@ class TranslationChecker {
57
if (correctedTranslation != destinationValue) {
58
destinationMap[key] = correctedTranslation;
59
corrected++;
60
- printV('Processed: "$key" -> CORRECTED');
61
- printV(' - eng : "$sourceValue"');
62
- printV(' - dst orig: "$destinationValue"');
63
- printV(' - dst new : "$correctedTranslation"');
60
+ print('Processed: "$key" -> CORRECTED');
61
+ print(' - eng : "$sourceValue"');
62
+ print(' - dst orig: "$destinationValue"');
63
+ print(' - dst new : "$correctedTranslation"');
64
await _writeArbFile(destinationArbPath, destinationMap);
65
} else {
66
- printV('Processed: "$key" -> VERIFIED');
66
+ print('Processed: "$key" -> VERIFIED');
67
}
68
} else {
69
- printV('Processed: "$key" -> SKIPPED (non-string)');
69
+ print('Processed: "$key" -> SKIPPED (non-string)');
70
}
71
72
processed++;
@@ -74,11 +74,11 @@ class TranslationChecker {
74
75
await _writeArbFile(destinationArbPath, destinationMap);
76
77
- printV('');
78
- printV('Summary:');
79
- printV('Processed: $processed keys');
80
- printV('Corrected: $corrected keys');
81
- printV('Updated: $destinationArbPath');
77
+ print('');
78
+ print('Summary:');
79
+ print('Processed: $processed keys');
80
+ print('Corrected: $corrected keys');
81
+ print('Updated: $destinationArbPath');
82
}
83
84
Future<String> _checkSingleTranslation({
@@ -108,7 +108,7 @@ class TranslationChecker {
108
109
return currentTranslation;
110
} catch (e) {
111
- printV('Error checking translation for key "$key": $e');
111
+ print('Error checking translation for key "$key": $e');
112
return currentTranslation;
113
}
114
}
@@ -186,7 +186,7 @@ IMPORTANT: Respond with a JSON object in the exact format, you must think about
186
final jsonEnd = response.lastIndexOf('}');
187
188
if (jsonStart == -1 || jsonEnd == -1 || jsonStart >= jsonEnd) {
189
- printV('No valid JSON found in LLM response');
189
+ print('No valid JSON found in LLM response');
190
return null;
191
}
192
@@ -195,7 +195,7 @@ IMPORTANT: Respond with a JSON object in the exact format, you must think about
195
196
return parsed['corrected_translation'] as String?;
197
} catch (e) {
198
- printV('Error extracting JSON from LLM response: $e');
198
+ print('Error extracting JSON from LLM response: $e');
199
return null;
200
}
201
}
@@ -228,7 +228,7 @@ void main(List<String> args) async {
228
final results = parser.parse(args);
229
230
if (results['help'] as bool) {
231
- printV(parser.usage);
231
+ print(parser.usage);
232
return;
233
}
234
@@ -239,24 +239,24 @@ void main(List<String> args) async {
239
final model = results['model'] as String;
240
241
if (!File(sourcePath).existsSync()) {
242
- printV('Error: Source ARB file not found: $sourcePath');
242
+ print('Error: Source ARB file not found: $sourcePath');
243
exit(1);
244
}
245
246
if (!File(destinationPath).existsSync()) {
247
- printV('Error: Destination ARB file not found: $destinationPath');
247
+ print('Error: Destination ARB file not found: $destinationPath');
248
exit(1);
249
}
250
251
- printV('Translation Checker');
252
- printV('Source: $sourcePath');
253
- printV('Destination: $destinationPath');
254
- printV('Ollama URL: $ollamaUrl');
255
- printV('Model: $model');
251
+ print('Translation Checker');
252
+ print('Source: $sourcePath');
253
+ print('Destination: $destinationPath');
254
+ print('Ollama URL: $ollamaUrl');
255
+ print('Model: $model');
256
if (specificKey != null) {
257
- printV('Key: $specificKey');
257
+ print('Key: $specificKey');
258
}
259
- printV('');
259
+ print('');
260
261
final checker = TranslationChecker(
262
ollamaBaseUrl: ollamaUrl,
@@ -270,8 +270,8 @@ void main(List<String> args) async {
270
);
271
272
} catch (e) {
273
- printV('Error: $e');
274
- printV('Run with --help for more information.');
273
+ print('Error: $e');
274
+ print('Run with --help for more information.');
275
exit(1);
276
}
277
}
tool/translation_add_lang.dart
+1
-1
@@ -34,5 +34,5 @@ void main(List<String> args) async {
34
}
35
36
appendStringsToArbFile(targetFileName, translations);
37
- printV("Success! Please add your Language Code to lib/entities/language_service.dart");
37
+ print("Success! Please add your Language Code to lib/entities/language_service.dart");
38
}
tool/translation_consistence.dart
+4
-4
@@ -7,14 +7,14 @@ import 'utils/translation/translation_constants.dart';
7
import 'utils/translation/translation_utils.dart';
8
9
void main(List<String> args) async {
10
- printV('Checking Consistency of all arb-files. Default: $defaultLang');
10
+ print('Checking Consistency of all arb-files. Default: $defaultLang');
11
12
final doFix = args.contains("--fix");
13
14
if (doFix)
15
- printV('Auto fixing enabled!\n');
15
+ print('Auto fixing enabled!\n');
16
else
17
- printV('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
17
+ print('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
18
19
final fileName = getArbFileName(defaultLang);
20
final file = File(fileName);
@@ -27,7 +27,7 @@ void main(List<String> args) async {
27
final missingDefaults = <String, String>{};
28
29
missingKeys.forEach((key) {
30
- printV('Missing in "$lang": "$key"');
30
+ print('Missing in "$lang": "$key"');
31
if (doFix)
32
missingDefaults[key] = arbObj[key] as String;
33
});
tool/utils/translation/arb_file_utils.dart
+1
-1
@@ -8,7 +8,7 @@ void appendStringToArbFile(String fileName, String name, String text, {bool forc
8
final arbObj = readArbFile(file);
9
10
if (arbObj.containsKey(name) && !force) {
11
- printV("String $name already exists in $fileName!");
11
+ print("String $name already exists in $fileName!");
12
return;
13
}
14
tool/utils/translation/translation_utils.dart
+1
-1
@@ -38,7 +38,7 @@ Future<String> getTranslation(String text, String lang) async {
38
try {
39
translation = (await translator.translate(text, from: defaultLang, to: lang)).text;
40
} catch (e) {
41
- printV("$lang, $text, $e");
41
+ print("$lang, $text, $e");
42
}
43
placeholder.forEach((index, value) {
44
final translatedPlaceholder = regExp.allMatches(translation).toList()[index];