What changed, and why it matters
This commit is a routine code-quality and CI housekeeping change. It adds and reorganizes Dart lint rules, creates a custom lint plugin to enforce coding standards (e.g., prefer a verbose print helper, avoid direct HTTP imports, restrict cross-package imports), and updates several tool scripts to use plain print() instead of the app's verbose logger. There is no user-facing security fix or vulnerability patch in the diff.
No security action required. Treat as normal code-quality/CI maintenance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces a new cw_custom_lints Dart plugin with three rules: use_print_v, no_restricted_imports_in_lib, and no_http_imports. It wires the plugin into analysis_options.yaml, expands the enabled linter rules, deletes per-package analysis_options.yaml files so the root config applies, and adds a dedicated GitHub Actions lint workflow. It also removes the lint step from the Linux PR build workflow and changes tool/ scripts to call print() directly because the printV rule intentionally excludes the tool/ directory. A single production code change in lib/bitcoin/cw_bitcoin.dart is a type-inference simplification. No vulnerability, bug fix, or security-relevant behavior change is present.
Changed components
CI/CD workflows (.github/workflows/lint.yml, no_http_imports.yaml, no_print_in_dart.yaml, pr_test_build_linux.yml)Dart analysis configuration (analysis_options.yaml, deleted per-package analysis_options.yaml files)Custom lint plugin (cw_custom_lints)Developer tooling scripts (tool/*.dart)lib/bitcoin/cw_bitcoin.dartInspect captured patch +587 / −199
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..bdcd36a4
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,109 @@
+name: Lint
+
+on: [pull_request]
+
+defaults:
+ run:
+ shell: bash
+jobs:
+ lint:
+ runs-on: [Linux, amd64, android]
+ container:
+ image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly
+ env:
+ STORE_PASS: test@cake_wallet
+ KEY_PASS: test@cake_wallet
+ MONEROC_CACHE_DIR_ROOT: /opt/generic_cache
+ BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
+ ANDROID_AVD_HOME: /root/.android/avd
+ volumes:
+ - /opt/cw_cache_android/root/.cache:/root/.cache
+ - /opt/cw_cache_android/root/.android/avd/:/root/.android/avd
+ - /opt/cw_cache_android/root/.ccache:/root/.ccache
+ - /opt/cw_cache_android/root/.pub-cache/:/root/.pub-cache
+ - /opt/cw_cache_android/root/.gradle/:/root/.gradle
+ - /opt/cw_cache_android/root/.android/:/root/.android
+ - /opt/cw_cache_android/root/go/pkg:/root/go/pkg
+ - /opt/cw_cache_android/opt/generic_cache:/opt/generic_cache
+ - /dev/kvm:/dev/kvm
+ - /var/run/docker.sock:/var/run/docker.sock
+ strategy:
+ matrix:
+ api-level: [ 29 ]
+
+ steps:
+ - name: configure git
+ run: |
+ git config --global --add safe.directory '*'
+ git config --global user.email "ci@cakewallet.com"
+ git config --global user.name "CakeWallet CI"
+
+ - name: Fetch prebuilt Torch
+ run: |
+ set -x -e
+ pushd scripts
+ wget https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz -O torch_dart.tar.gz
+ rm -rf torch_dart
+ mkdir torch_dart
+ pushd torch_dart
+ tar -xzf ../torch_dart.tar.gz
+ popd
+ rm ./torch_dart.tar.gz
+ popd
+
+ - name: Fetch prebuilt Reown
+ run: |
+ set -x -e
+ pushd scripts
+ # cleaning
+ rm -rf reown_flutter
+ rm -f reown_flutter.tar.gz
+
+ 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
+ mkdir reown_flutter
+ pushd reown_flutter
+ tar -xzf ../reown_flutter.tar.gz
+ popd
+ rm ./reown_flutter.tar.gz
+ popd
+
+ - name: Build Bitbox Flutter
+ run: |
+ set -x -e
+ pushd scripts
+ ./build_bitbox_flutter.sh
+ popd
+
+ - name: Prepare Zcash
+ run: |
+ set -x -e
+ scripts/prepare_zcash.sh
+
+ - name: Execute Build and Setup Commands
+ run: |
+ pushd scripts/android
+ source ./app_env.sh cakewallet
+ export GITHUB_HUH=yeah
+ ./app_config.sh
+ popd
+
+ - name: Install Flutter dependencies
+ run: |
+ flutter pub get
+
+ - name: Build generated code
+ run: |
+ flutter --version
+ flutter clean
+ rm -rf .dart_tool
+ rm pubspec.lock
+ flutter pub get
+ ./model_generator.sh async
+
+ - name: Generate localization
+ run: |
+ dart run tool/generate_localization.dart
+
+ - name: Lint
+ run: |
+ ABORT_ON_CHANGE=yeah scripts/lint.sh
\ No newline at end of file
diff --git a/.github/workflows/no_http_imports.yaml b/.github/workflows/no_http_imports.yaml
index dad6821a..0bfca1d0 100644
--- a/.github/workflows/no_http_imports.yaml
+++ b/.github/workflows/no_http_imports.yaml
@@ -11,11 +11,10 @@ jobs:
- name: Check for http package usage
if: github.event_name == 'pull_request'
run: |
- 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)"
+ 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)"
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use http imports"
echo "Using http package breaks proxy integration"
echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart"
exit 1
-
\ No newline at end of file
diff --git a/.github/workflows/no_print_in_dart.yaml b/.github/workflows/no_print_in_dart.yaml
index 507793bd..69885c9b 100644
--- a/.github/workflows/no_print_in_dart.yaml
+++ b/.github/workflows/no_print_in_dart.yaml
@@ -11,7 +11,7 @@ jobs:
- name: Check for print() statements in dart code (use printV() instead)
if: github.event_name == 'pull_request'
run: |
- 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)"
+ 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)"
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use print() statements"
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 8cb44244..879ea247 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -179,9 +179,6 @@ jobs:
with:
path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip
name: cakewallet_linux
- - name: Verify lint
- run: |
- exec env ABORT_ON_CHANGE=true ./scripts/lint.sh
- name: Prepare virtual desktop
if: ${{ contains(env.message, 'run tests') }}
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 98dfe6ae..70e87faf 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,4 +1,3 @@
-include: package:lints/recommended.yaml
analyzer:
@@ -9,6 +8,7 @@ analyzer:
cw_core/lib/**.g.dart,
cw_haven/lib/**.g.dart,
cw_monero/lib/**.g.dart,
+ cw_mweb/lib/**.g.dart,
lib/generated/*.dart,
cw_monero/ios/External/**,
cw_shared_external/**,
@@ -18,65 +18,124 @@ analyzer:
strict-casts: true
strict-raw-types: true
+plugins:
+ cw_custom_lints:
+ path: cw_custom_lints
+
linter:
rules:
- cancel_subscriptions
- always_declare_return_types
- prefer_final_fields
+ - always_put_control_body_on_new_line
+ - always_put_required_named_parameters_first
+ - always_use_package_imports
+ - annotate_overrides
+ - annotate_redeclares
+ - avoid_empty_else
+ - avoid_function_literals_in_foreach_calls
+ - avoid_init_to_null
+ - avoid_multiple_declarations_per_line
+ - avoid_positional_boolean_parameters
+ - avoid_relative_lib_imports
+ - avoid_renaming_method_parameters
+ - avoid_return_types_on_setters
+ - avoid_returning_null_for_void
+ - avoid_slow_async_io
+ - avoid_types_as_parameter_names
+ - avoid_types_on_closure_parameters
+ - avoid_unnecessary_containers
+ - avoid_unused_constructor_parameters
+ - avoid_void_async
+ - await_only_futures
+ - camel_case_extensions
+ - camel_case_types
+ - cast_nullable_to_non_nullable
+ - collection_methods_unrelated_type
+ - constant_identifier_names
+ - curly_braces_in_flow_control_structures
+ - directives_ordering
+ - empty_constructor_bodies
+ - empty_statements
+ - eol_at_end_of_file
+ - flutter_style_todos
+ - hash_and_equals
+ - implicit_reopen
+ - library_private_types_in_public_api
+ - non_constant_identifier_names
+ - only_throw_errors
+ - package_names
+ - prefer_asserts_with_message
+ - prefer_conditional_assignment
+ - prefer_const_constructors
+ - prefer_const_constructors_in_immutables
+ - prefer_const_declarations
+ - prefer_constructors_over_static_methods
+ - prefer_contains
+ - prefer_double_quotes
+ - prefer_expression_function_bodies
+ - prefer_final_in_for_each
+ - prefer_function_declarations_over_variables
+ - prefer_if_null_operators
+ - prefer_initializing_formals
+ - prefer_inlined_adds
+ - prefer_int_literals
+ - prefer_is_empty
+ - prefer_is_not_empty
+ - prefer_is_not_operator
+ - prefer_iterable_wheretype
+ - prefer_null_aware_operators
+ - prefer_spread_collections
+ - prefer_typing_uninitialized_variables
+ - recursive_getters
+ - require_trailing_commas
+ - sized_box_for_whitespace
+ - sort_child_properties_last
+ - sort_constructors_first
+ - sort_unnamed_constructors_first
+ - test_types_in_equals
+ - unawaited_futures
+ - unnecessary_async
+ - unnecessary_await_in_return
+ - unnecessary_const
+ - unnecessary_constructor_name
+ - unnecessary_lambdas
+ - unnecessary_late
+ - unnecessary_new
+ - unnecessary_null_aware_assignments
+ - unnecessary_null_checks
+ - unnecessary_null_in_if_null_operators
+ - unnecessary_nullable_for_final_variable_declarations
+ - unnecessary_overrides
+ - unnecessary_parenthesis
+ - unnecessary_string_escapes
+ - unnecessary_string_interpolations
+ - unnecessary_this
+ - unnecessary_to_list_in_spreads
+ - unnecessary_underscores
+ - unreachable_from_main
+ - unrelated_type_equality_checks
+ - use_build_context_synchronously
+ - use_colored_box
+ - use_full_hex_values_for_flutter_colors
+ - use_function_type_syntax_for_parameters
+ - use_is_even_rather_than_modulo
+ - use_named_constants
+ - use_null_aware_elements
+ - use_raw_strings
+ - use_rethrow_when_possible
+ - use_string_buffers
+ - use_super_parameters
+ - use_truncating_division
+ - valid_regexps
+ - void_checks
+ - prefer_final_locals
+ - prefer_interpolation_to_compose_strings
+ - null_closures
+ - no_wildcard_variable_uses
-formatter:
- page_width: 100
-
-# analyzer:
-# strong-mode:
-# implicit-casts: false
-# implicit-dynamic: false
-# exclude: [build/**, lib/generated/*.dart, lib/**.g.dart, cw_monero/ios/External/**, cw_shared_external/**, shared_external/**]
-# linter:
-# rules:
-# - always_declare_return_types
-# - annotate_overrides
-# - avoid_empty_else
-# - avoid_init_to_null
-# - avoid_return_types_on_setters
-# - await_only_futures
-# - camel_case_types
-# - cancel_subscriptions
-# - close_sinks
-# - comment_references
-# - constant_identifier_names
-# - control_flow_in_finally
-# - empty_catches
-# - empty_constructor_bodies
-# - empty_statements
-# - hash_and_equals
-# - invariant_booleans
-# - iterable_contains_unrelated_type
-# - library_names
-# - library_prefixes
-# - list_remove_unrelated_type
-# - literal_only_boolean_expressions
-# - non_constant_identifier_names
-# - one_member_abstracts
-# - only_throw_errors
-# - overridden_fields
-# - package_api_docs
-# - package_names
-# - package_prefixed_library_names
-# - parameter_assignments
-# - prefer_final_fields
-# - prefer_final_locals
-# - prefer_is_not_empty
-# - slash_for_doc_comments
-# - sort_constructors_first
-# - sort_unnamed_constructors_first
-# - test_types_in_equals
-# - throw_in_finally
-# - type_init_formals
-# - unawaited_futures
-# - unnecessary_getters_setters
-# - unrelated_type_equality_checks
-# - valid_regexps
+formatter:
+ page_width: 100
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index b0752cf7..98e1ec46 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -6,7 +6,7 @@ author: Cake Wallet
homepage: https://cakewallet.com
environment:
- sdk: ">=2.17.5 <3.0.0"
+ sdk: ">=3.0.0 <4.0.0"
flutter: ">=1.20.0"
dependencies:
diff --git a/cw_bitcoin_cash/analysis_options.yaml b/cw_bitcoin_cash/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_bitcoin_cash/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_custom_lints/lib/http_force_proxy/http_force_proxy_rule.dart b/cw_custom_lints/lib/http_force_proxy/http_force_proxy_rule.dart
new file mode 100644
index 00000000..69d0ea7f
--- /dev/null
+++ b/cw_custom_lints/lib/http_force_proxy/http_force_proxy_rule.dart
@@ -0,0 +1,62 @@
+import "package:analyzer/analysis_rule/analysis_rule.dart";
+import "package:analyzer/analysis_rule/rule_context.dart";
+import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
+import "package:analyzer/dart/ast/ast.dart";
+import "package:analyzer/dart/ast/visitor.dart";
+import "package:analyzer/error/error.dart";
+
+class HttpForceProxyRule extends AnalysisRule {
+ HttpForceProxyRule()
+ : super(
+ name: "no_http_imports",
+ description:
+ "Using the http package breaks proxy integration. Please use ProxyWrapper or alias it as \"very_insecure_http_do_not_use\".",
+ );
+
+ static const LintCode code = LintCode(
+ "no_http_imports",
+ "Using the http package breaks proxy integration. Please use ProxyWrapper or alias it as \"very_insecure_http_do_not_use\".",
+ severity: DiagnosticSeverity.WARNING,
+ );
+
+ @override
+ LintCode get diagnosticCode => code;
+
+ @override
+ void registerNodeProcessors(
+ RuleVisitorRegistry registry,
+ RuleContext context,
+ ) {
+ final filePath = context.definingUnit.file.path;
+
+ if (filePath.endsWith("proxy_wrapper.dart")) {
+ return;
+ }
+
+ registry.addImportDirective(this, _Visitor(this));
+ }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+ _Visitor(this.rule);
+
+ final AnalysisRule rule;
+
+ @override
+ void visitImportDirective(ImportDirective node) {
+ final uriString = node.uri.stringValue;
+ if (uriString == null) {
+ return;
+ }
+
+ if (uriString.startsWith("package:http")) {
+ final prefixNode = node.prefix;
+ if (prefixNode != null &&
+ prefixNode.name == "very_insecure_http_do_not_use") {
+ return;
+ }
+
+ rule.reportAtNode(node);
+ }
+ }
+}
diff --git a/cw_custom_lints/lib/main.dart b/cw_custom_lints/lib/main.dart
new file mode 100644
index 00000000..39e07ba3
--- /dev/null
+++ b/cw_custom_lints/lib/main.dart
@@ -0,0 +1,22 @@
+import "package:analysis_server_plugin/plugin.dart";
+import "package:analysis_server_plugin/registry.dart";
+import "package:cw_custom_lints/http_force_proxy/http_force_proxy_rule.dart";
+import "package:cw_custom_lints/print_verbose/print_verbose_fix.dart";
+import "package:cw_custom_lints/print_verbose/print_verbose_rule.dart";
+import "package:cw_custom_lints/restricted_imports/restricted_imports_rule.dart";
+
+final plugin = CwCustomLintsPlugin();
+
+class CwCustomLintsPlugin extends Plugin {
+ @override
+ String get name => "cw_custom_lints";
+
+ @override
+ void register(PluginRegistry registry) {
+ registry.registerWarningRule(PrintVerboseRule());
+ registry.registerWarningRule(RestrictedImportsRule());
+ registry.registerWarningRule(HttpForceProxyRule());
+
+ registry.registerFixForRule(PrintVerboseRule.code, ReplaceWithPrintV.new);
+ }
+}
diff --git a/cw_custom_lints/lib/print_verbose/print_verbose_fix.dart b/cw_custom_lints/lib/print_verbose/print_verbose_fix.dart
new file mode 100644
index 00000000..b7a2daf5
--- /dev/null
+++ b/cw_custom_lints/lib/print_verbose/print_verbose_fix.dart
@@ -0,0 +1,50 @@
+import "package:analysis_server_plugin/edit/dart/correction_producer.dart";
+import "package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart";
+import "package:analyzer/dart/ast/ast.dart";
+import "package:analyzer_plugin/utilities/change_builder/change_builder_core.dart";
+import "package:analyzer_plugin/utilities/fixes/fixes.dart";
+import "package:analyzer_plugin/utilities/range_factory.dart";
+
+class ReplaceWithPrintV extends ResolvedCorrectionProducer {
+ ReplaceWithPrintV({required super.context});
+
+ static const _importString = "package:cw_core/utils/print_verbose.dart";
+
+ static const _fixKind = FixKind(
+ "dart.fix.replaceWithPrintV",
+ DartFixKindPriority.standard,
+ "Replace with printV()",
+ );
+
+ @override
+ CorrectionApplicability get applicability =>
+ CorrectionApplicability.singleLocation;
+
+ @override
+ FixKind get fixKind => _fixKind;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ final invocation = node.thisOrAncestorOfType<MethodInvocation>();
+ if (invocation == null) {
+ return;
+ }
+
+ final root = invocation.root;
+ final hasImport = root is CompilationUnit &&
+ root.directives.whereType<ImportDirective>().any(
+ (directive) => directive.uri.stringValue == _importString,
+ );
+
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addSimpleReplacement(
+ range.node(invocation.methodName),
+ "printV",
+ );
+
+ if (!hasImport) {
+ builder.importLibrary(Uri.parse(_importString));
+ }
+ });
+ }
+}
diff --git a/cw_custom_lints/lib/print_verbose/print_verbose_rule.dart b/cw_custom_lints/lib/print_verbose/print_verbose_rule.dart
new file mode 100644
index 00000000..0fee39bc
--- /dev/null
+++ b/cw_custom_lints/lib/print_verbose/print_verbose_rule.dart
@@ -0,0 +1,52 @@
+import "package:analyzer/analysis_rule/analysis_rule.dart";
+import "package:analyzer/analysis_rule/rule_context.dart";
+import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
+import "package:analyzer/dart/ast/ast.dart";
+import "package:analyzer/dart/ast/visitor.dart";
+import "package:analyzer/error/error.dart";
+
+class PrintVerboseRule extends AnalysisRule {
+ PrintVerboseRule()
+ : super(
+ name: "use_print_v",
+ description: "Use printV() from cw_core instead of print().",
+ );
+
+ static const LintCode code = LintCode(
+ "use_print_v",
+ "Use printV() from cw_core instead",
+ correctionMessage: "Replace print with printV",
+ severity: DiagnosticSeverity.WARNING,
+ );
+
+ @override
+ LintCode get diagnosticCode => code;
+
+ @override
+ void registerNodeProcessors(
+ RuleVisitorRegistry registry,
+ RuleContext context,
+ ) {
+ final filePath = context.definingUnit.file.path;
+
+ if (filePath.contains("/tool/") || filePath.contains("print_verbose.dart")) {
+ // tool/ is allowed to use print as it never makes its way into the app
+ return;
+ }
+
+ registry.addMethodInvocation(this, _Visitor(this));
+ }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+ _Visitor(this.rule);
+
+ final AnalysisRule rule;
+
+ @override
+ void visitMethodInvocation(MethodInvocation node) {
+ if (node.methodName.name == "print" && node.target == null) {
+ rule.reportAtNode(node);
+ }
+ }
+}
diff --git a/cw_custom_lints/lib/restricted_imports/restricted_imports_rule.dart b/cw_custom_lints/lib/restricted_imports/restricted_imports_rule.dart
new file mode 100644
index 00000000..80680af8
--- /dev/null
+++ b/cw_custom_lints/lib/restricted_imports/restricted_imports_rule.dart
@@ -0,0 +1,79 @@
+import "package:analyzer/analysis_rule/analysis_rule.dart";
+import "package:analyzer/analysis_rule/rule_context.dart";
+import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
+import "package:analyzer/dart/ast/ast.dart";
+import "package:analyzer/dart/ast/visitor.dart";
+import "package:analyzer/error/error.dart";
+
+class RestrictedImportsRule extends AnalysisRule {
+ RestrictedImportsRule()
+ : super(
+ name: "no_restricted_imports_in_lib",
+ description:
+ "Do not import from coin-specific packages. If necessary, wire your logic through configure.dart",
+ );
+
+ static const LintCode code = LintCode(
+ "no_restricted_imports_in_lib",
+ "Do not import from coin-specific packages. If necessary, wire your logic through configure.dart",
+ severity: DiagnosticSeverity.ERROR,
+ );
+
+ static const _restrictedPackages = {
+ "cw_bitcoin",
+ "cw_bitcoin_cash",
+ "cw_electrum",
+ "cw_litecoin",
+ "cw_evm",
+ "cw_mweb",
+ "cw_nano",
+ "cw_solana",
+ "cw_tron",
+ "cw_zano",
+ };
+
+ @override
+ LintCode get diagnosticCode => code;
+
+ @override
+ void registerNodeProcessors(
+ RuleVisitorRegistry registry,
+ RuleContext context,
+ ) {
+ final filePath = context.definingUnit.file.path;
+ final fileName = filePath.split("/").last;
+
+ // ignore cw_something folders
+ if (filePath.contains("cw_")) {
+ return;
+ }
+
+ // allow ex. cw_bitcoin in bitcoin.dart
+ if (_restrictedPackages.contains("cw_" + fileName.replaceAll(".dart", ""))) {
+ return;
+ }
+
+ registry.addImportDirective(this, _Visitor(this));
+ }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+ _Visitor(this.rule);
+
+ final AnalysisRule rule;
+
+ @override
+ void visitImportDirective(ImportDirective node) {
+ final uriString = node.uri.stringValue;
+ if (uriString == null || !uriString.startsWith("package:")) {
+ return;
+ }
+
+ final pathWithoutScheme = uriString.replaceFirst("package:", "");
+ final packageName = pathWithoutScheme.split("/").first;
+
+ if (RestrictedImportsRule._restrictedPackages.contains(packageName)) {
+ rule.reportAtNode(node);
+ }
+ }
+}
diff --git a/cw_custom_lints/pubspec.yaml b/cw_custom_lints/pubspec.yaml
new file mode 100644
index 00000000..399fa59c
--- /dev/null
+++ b/cw_custom_lints/pubspec.yaml
@@ -0,0 +1,15 @@
+name: cw_custom_lints
+version: 0.0.1
+environment:
+ sdk: '>=3.9.0 <4.0.0'
+
+
+dependencies:
+ analysis_server_plugin: 0.3.15
+ analyzer: 13.0.0
+ analyzer_plugin: 0.14.9
+
+dev_dependencies:
+ analyzer_testing: 0.2.6
+ test_reflective_loader: 0.4.0
+ test: 1.31.2
diff --git a/cw_decred/analysis_options.yaml b/cw_decred/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_decred/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_dogecoin/analysis_options.yaml b/cw_dogecoin/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_dogecoin/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_evm/analysis_options.yaml b/cw_evm/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_evm/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_monero/analysis_options.yaml b/cw_monero/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_monero/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_mweb/analysis_options.yaml b/cw_mweb/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_mweb/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml
index a3ef2ea6..449b1b8f 100644
--- a/cw_nano/pubspec.yaml
+++ b/cw_nano/pubspec.yaml
@@ -6,7 +6,7 @@ author: Cake Wallet
homepage: https://cakewallet.com
environment:
- sdk: '>=2.18.2 <3.0.0'
+ sdk: '>=3.0.0 <4.0.0'
flutter: ">=1.17.0"
dependencies:
diff --git a/cw_solana/analysis_options.yaml b/cw_solana/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_solana/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_tron/analysis_options.yaml b/cw_tron/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_tron/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_wownero/analysis_options.yaml b/cw_wownero/analysis_options.yaml
deleted file mode 100644
index a5744c1c..00000000
--- a/cw_wownero/analysis_options.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-include: package:flutter_lints/flutter.yaml
-
-# Additional information about this file can be found at
-# https://dart.dev/guides/language/analysis-options
diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml
index 7aa32af6..7e8ede8d 100644
--- a/cw_zano/pubspec.yaml
+++ b/cw_zano/pubspec.yaml
@@ -5,7 +5,7 @@ publish_to: none
homepage: https://cakewallet.com
environment:
- sdk: ">=2.19.0 <3.0.0"
+ sdk: ">=3.0.0 <4.0.0"
flutter: ">=1.20.0"
dependencies:
diff --git a/cw_zcash/analysis_options.yaml b/cw_zcash/analysis_options.yaml
deleted file mode 100644
index f43bd483..00000000
--- a/cw_zcash/analysis_options.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-analyzer:
- errors:
- implementation_imports: ignore
- must_be_immutable: ignore
- overridden_fields: ignore
- exclude:
- - "**.g.dart"
- - "external/**"
-
-linter:
- rules:
- unawaited_futures: true
- avoid_print: false
- parameter_assignments: true
- prefer_const_declarations: true
- prefer_final_fields: true
- prefer_final_in_for_each: true
- prefer_final_locals: true
- prefer_final_parameters: true
- avoid_void_async: true
- require_trailing_commas: true
- sort_child_properties_last: true
- sort_constructors_first: true
- sort_unnamed_constructors_first: true
- unnecessary_async: true
- unnecessary_await_in_return: true
- unnecessary_string_interpolations: true
- always_declare_return_types: true
-
-formatter:
- page_width: 100
diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart
index a5d11832..724c9197 100644
--- a/lib/bitcoin/cw_bitcoin.dart
+++ b/lib/bitcoin/cw_bitcoin.dart
@@ -177,7 +177,7 @@ class CWBitcoin extends Bitcoin {
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addressesByReceiveType
- .map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
+ .map<ElectrumSubAddress>((addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
diff --git a/scripts/lint.sh b/scripts/lint.sh
index c4b1b4e4..cfffdb80 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -1,17 +1,29 @@
#!/bin/bash
-set -x -e
-cd $(dirname $0)
+set -e
+cd "$(dirname "$0")"
cd ..
-for i in cw_zcash;
+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;
do
- dart fix --apply $i/
- dart format --line-length=100 $i/
- if [[ ! "x$ABORT_ON_CHANGE" == "x" ]];
- then
- if [[ ! -z "$(git status --porcelain -- $(find $i))" ]];
- then
- echo "Please run scripts/lint.sh ($i has changes)"
+ {
+ # files modified after the lints got enabled and committed
+ git log --since="2026-07-14 23:59:59" --name-only --diff-filter=d --format="" -- "$i"
+ # uncommitted files (assumed to have been modified after the 29th)
+ git diff --name-only --diff-filter=d HEAD -- "$i"
+ } | grep '\.dart$' | sort -u | while IFS= read -r file; do
+ echo $file
+ if [[ -f "$file" ]]; then
+ dart fix --apply "$file" && dart format --line-length=100 "$file"
+ fi
+ done
+
+ if [[ -n "$ABORT_ON_CHANGE" ]]; then
+ DART_CHANGES=$(git status --porcelain -- "$i" | grep '\.dart$' || true)
+
+ if [[ -n "$DART_CHANGES" ]]; then
+ echo "Please run scripts/lint.sh"
+ echo "changes in $i:"
+ echo "$DART_CHANGES"
exit 1
fi
fi
diff --git a/tool/append_translation.dart b/tool/append_translation.dart
index 7232f308..1cc6336b 100644
--- a/tool/append_translation.dart
+++ b/tool/append_translation.dart
@@ -15,7 +15,7 @@ void main(List<String> args) async {
final text = args[1];
final force = args.last == "--force";
- printV('Appending "$name": "$text"');
+ print('Appending "$name": "$text"');
// add translation to all languages:
for (var lang in langs) {
@@ -25,12 +25,12 @@ void main(List<String> args) async {
appendStringToArbFile(fileName, name, translation, force: force);
}
- printV('Alphabetizing all files...');
+ print('Alphabetizing all files...');
for (var lang in langs) {
final fileName = getArbFileName(lang);
alphabetizeArbFile(fileName);
}
- printV('Done!');
+ print('Done!');
}
\ No newline at end of file
diff --git a/tool/download_moneroc_prebuilds.dart b/tool/download_moneroc_prebuilds.dart
index a12777ad..dee04612 100644
--- a/tool/download_moneroc_prebuilds.dart
+++ b/tool/download_moneroc_prebuilds.dart
@@ -31,7 +31,7 @@ Future<void> main() async {
final resp = await _dio.get("https://api.github.com/repos/mrcyjanek/monero_c/releases");
final data = resp.data[0];
final tagName = data['tag_name'];
- printV("Downloading artifacts for: ${tagName}");
+ print("Downloading artifacts for: ${tagName}");
final assets = data['assets'] as List<dynamic>;
for (var i = 0; i < assets.length; i++) {
for (var triplet in triplets) {
@@ -42,10 +42,10 @@ Future<void> main() async {
String localFilename = filename.replaceAll("${coin}_${triplet}_", "");
localFilename = "scripts/monero_c/release/${coin}/${triplet}_${localFilename}";
final url = asset["browser_download_url"] as String;
- printV("- downloading $localFilename");
+ print("- downloading $localFilename");
await _dio.download(url, localFilename);
if (localFilename.endsWith(".xz")) {
- printV(" extracting $localFilename");
+ print(" extracting $localFilename");
final inputStream = InputFileStream(localFilename);
final archive = XZDecoder().decodeBytes(inputStream.toUint8List());
final outputStream = OutputFileStream(localFilename.replaceAll(".xz", ""));
@@ -54,11 +54,11 @@ Future<void> main() async {
}
}
if (Platform.isMacOS) {
- printV("Generating ios framework");
+ print("Generating ios framework");
final result = Process.runSync("bash", [
"-c",
"cd scripts/ios && ./gen_framework.sh && cd ../.."
]);
- printV((result.stdout+result.stderr).toString().trim());
+ print((result.stdout+result.stderr).toString().trim());
}
}
\ No newline at end of file
diff --git a/tool/fiat_api_check.dart b/tool/fiat_api_check.dart
index 5f3fe490..c70fa337 100755
--- a/tool/fiat_api_check.dart
+++ b/tool/fiat_api_check.dart
@@ -76,7 +76,7 @@ void main() {
// --- B. Run App in a Zone to Capture Prints ---
runZoned(
() async {
- printV('--- Starting Verified Price Check ---');
+ print('--- Starting Verified Price Check ---');
final Map<String, List<String>> workingPairs = {};
final Map<String, List<String>> failedPairs = {};
@@ -130,7 +130,7 @@ void main() {
}
// Print immediate status (Captured by Zone)
- printV('$logPrefix $logMessage');
+ print('$logPrefix $logMessage');
// Aggregate
if (isSuccess) {
@@ -148,25 +148,25 @@ void main() {
}
// --- FINAL SUMMARY ---
- printV('\n\n=== SUMMARY ===\n');
+ print('\n\n=== SUMMARY ===\n');
// Print Successful
workingPairs.forEach((crypto, fiats) {
if (fiats.isNotEmpty) {
- printV('✅ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
+ print('✅ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
}
});
- printV('\n--------------------------------------------------\n');
+ print('\n--------------------------------------------------\n');
// Print Failed
failedPairs.forEach((crypto, fiats) {
if (fiats.isNotEmpty) {
- printV('❌ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
+ print('❌ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
}
});
- printV('\n=== DONE ===');
+ print('\n=== DONE ===');
},
// --- C. The Interceptor ---
diff --git a/tool/generate_localization.dart b/tool/generate_localization.dart
index 41953956..4b0927a8 100644
--- a/tool/generate_localization.dart
+++ b/tool/generate_localization.dart
@@ -37,7 +37,7 @@ Future<void> main(List<String> args) async {
extraInfo.forEach((key, dynamic value) async {
if (key != srcDir) {
- printV('Wrong key: $key');
+ print('Wrong key: $key');
return;
}
@@ -45,7 +45,7 @@ Future<void> main(List<String> args) async {
final dir = Directory(dirPath);
if (!await dir.exists()) {
- printV('Wrong directory path: $dirPath');
+ print('Wrong directory path: $dirPath');
return;
}
@@ -55,7 +55,7 @@ Future<void> main(List<String> args) async {
// normalizing the case so keys match LanguageService.supportedLocales.
final fileName = element.uri.pathSegments.last;
if (!fileName.startsWith('strings_') || !fileName.endsWith('.arb')) {
- printV('Wrong file: ${element.path}');
+ print('Wrong file: ${element.path}');
return;
}
final parts =
@@ -67,7 +67,7 @@ Future<void> main(List<String> args) async {
});
if (!localePath.keys.contains(defaultLocale)) {
- printV("Locale list doesn't contain $defaultLocale");
+ print("Locale list doesn't contain $defaultLocale");
return;
}
@@ -124,7 +124,7 @@ Future<void> main(List<String> args) async {
await File(outputPath + localeListFileName).writeAsString(locales);
} catch (e) {
- printV(e.toString());
+ print(e.toString());
}
});
}
diff --git a/tool/print_verbose_dummy.dart b/tool/print_verbose_dummy.dart
index f7286e1f..e69de29b 100644
--- a/tool/print_verbose_dummy.dart
+++ b/tool/print_verbose_dummy.dart
@@ -1 +0,0 @@
-void printV(dynamic content) => print(content);
\ No newline at end of file
diff --git a/tool/translate_changelog.dart b/tool/translate_changelog.dart
index e044cc20..609c969a 100644
--- a/tool/translate_changelog.dart
+++ b/tool/translate_changelog.dart
@@ -46,7 +46,7 @@ void main() async {
});
currentItem++;
- printV(
+ print(
"\r$currentLang/${langs.length} langs done, $currentItem/${defaultLangChangelog.length} items done");
}
currentLang++;
diff --git a/tool/translate_using_llm.dart b/tool/translate_using_llm.dart
index 3ca92d78..e5c22efe 100644
--- a/tool/translate_using_llm.dart
+++ b/tool/translate_using_llm.dart
@@ -25,18 +25,18 @@ class TranslationChecker {
final sourceMap = json.decode(sourceContent) as Map<String, dynamic>;
final destinationMap = json.decode(destinationContent) as Map<String, dynamic>;
- printV('Found ${sourceMap.length} keys in source, ${destinationMap.length} in destination');
+ print('Found ${sourceMap.length} keys in source, ${destinationMap.length} in destination');
final keysToProcess = specificKey != null
? (sourceMap.containsKey(specificKey) ? [specificKey] : <String>[])
: sourceMap.keys.toList();
if (specificKey != null && keysToProcess.isEmpty) {
- printV('Error: Key "$specificKey" not found in source file');
+ print('Error: Key "$specificKey" not found in source file');
return;
}
- printV('Processing ${keysToProcess.length} translations...');
+ print('Processing ${keysToProcess.length} translations...');
int processed = 0;
int corrected = 0;
@@ -57,16 +57,16 @@ class TranslationChecker {
if (correctedTranslation != destinationValue) {
destinationMap[key] = correctedTranslation;
corrected++;
- printV('Processed: "$key" -> CORRECTED');
- printV(' - eng : "$sourceValue"');
- printV(' - dst orig: "$destinationValue"');
- printV(' - dst new : "$correctedTranslation"');
+ print('Processed: "$key" -> CORRECTED');
+ print(' - eng : "$sourceValue"');
+ print(' - dst orig: "$destinationValue"');
+ print(' - dst new : "$correctedTranslation"');
await _writeArbFile(destinationArbPath, destinationMap);
} else {
- printV('Processed: "$key" -> VERIFIED');
+ print('Processed: "$key" -> VERIFIED');
}
} else {
- printV('Processed: "$key" -> SKIPPED (non-string)');
+ print('Processed: "$key" -> SKIPPED (non-string)');
}
processed++;
@@ -74,11 +74,11 @@ class TranslationChecker {
await _writeArbFile(destinationArbPath, destinationMap);
- printV('');
- printV('Summary:');
- printV('Processed: $processed keys');
- printV('Corrected: $corrected keys');
- printV('Updated: $destinationArbPath');
+ print('');
+ print('Summary:');
+ print('Processed: $processed keys');
+ print('Corrected: $corrected keys');
+ print('Updated: $destinationArbPath');
}
Future<String> _checkSingleTranslation({
@@ -108,7 +108,7 @@ class TranslationChecker {
return currentTranslation;
} catch (e) {
- printV('Error checking translation for key "$key": $e');
+ print('Error checking translation for key "$key": $e');
return currentTranslation;
}
}
@@ -186,7 +186,7 @@ IMPORTANT: Respond with a JSON object in the exact format, you must think about
final jsonEnd = response.lastIndexOf('}');
if (jsonStart == -1 || jsonEnd == -1 || jsonStart >= jsonEnd) {
- printV('No valid JSON found in LLM response');
+ print('No valid JSON found in LLM response');
return null;
}
@@ -195,7 +195,7 @@ IMPORTANT: Respond with a JSON object in the exact format, you must think about
return parsed['corrected_translation'] as String?;
} catch (e) {
- printV('Error extracting JSON from LLM response: $e');
+ print('Error extracting JSON from LLM response: $e');
return null;
}
}
@@ -228,7 +228,7 @@ void main(List<String> args) async {
final results = parser.parse(args);
if (results['help'] as bool) {
- printV(parser.usage);
+ print(parser.usage);
return;
}
@@ -239,24 +239,24 @@ void main(List<String> args) async {
final model = results['model'] as String;
if (!File(sourcePath).existsSync()) {
- printV('Error: Source ARB file not found: $sourcePath');
+ print('Error: Source ARB file not found: $sourcePath');
exit(1);
}
if (!File(destinationPath).existsSync()) {
- printV('Error: Destination ARB file not found: $destinationPath');
+ print('Error: Destination ARB file not found: $destinationPath');
exit(1);
}
- printV('Translation Checker');
- printV('Source: $sourcePath');
- printV('Destination: $destinationPath');
- printV('Ollama URL: $ollamaUrl');
- printV('Model: $model');
+ print('Translation Checker');
+ print('Source: $sourcePath');
+ print('Destination: $destinationPath');
+ print('Ollama URL: $ollamaUrl');
+ print('Model: $model');
if (specificKey != null) {
- printV('Key: $specificKey');
+ print('Key: $specificKey');
}
- printV('');
+ print('');
final checker = TranslationChecker(
ollamaBaseUrl: ollamaUrl,
@@ -270,8 +270,8 @@ void main(List<String> args) async {
);
} catch (e) {
- printV('Error: $e');
- printV('Run with --help for more information.');
+ print('Error: $e');
+ print('Run with --help for more information.');
exit(1);
}
}
diff --git a/tool/translation_add_lang.dart b/tool/translation_add_lang.dart
index e3c372a8..3f509090 100644
--- a/tool/translation_add_lang.dart
+++ b/tool/translation_add_lang.dart
@@ -34,5 +34,5 @@ void main(List<String> args) async {
}
appendStringsToArbFile(targetFileName, translations);
- printV("Success! Please add your Language Code to lib/entities/language_service.dart");
+ print("Success! Please add your Language Code to lib/entities/language_service.dart");
}
diff --git a/tool/translation_consistence.dart b/tool/translation_consistence.dart
index 2cdd358c..f3d3120b 100644
--- a/tool/translation_consistence.dart
+++ b/tool/translation_consistence.dart
@@ -7,14 +7,14 @@ import 'utils/translation/translation_constants.dart';
import 'utils/translation/translation_utils.dart';
void main(List<String> args) async {
- printV('Checking Consistency of all arb-files. Default: $defaultLang');
+ print('Checking Consistency of all arb-files. Default: $defaultLang');
final doFix = args.contains("--fix");
if (doFix)
- printV('Auto fixing enabled!\n');
+ print('Auto fixing enabled!\n');
else
- printV('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
+ print('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
final fileName = getArbFileName(defaultLang);
final file = File(fileName);
@@ -27,7 +27,7 @@ void main(List<String> args) async {
final missingDefaults = <String, String>{};
missingKeys.forEach((key) {
- printV('Missing in "$lang": "$key"');
+ print('Missing in "$lang": "$key"');
if (doFix)
missingDefaults[key] = arbObj[key] as String;
});
diff --git a/tool/utils/translation/arb_file_utils.dart b/tool/utils/translation/arb_file_utils.dart
index 92a8f6e2..c8633c09 100644
--- a/tool/utils/translation/arb_file_utils.dart
+++ b/tool/utils/translation/arb_file_utils.dart
@@ -8,7 +8,7 @@ void appendStringToArbFile(String fileName, String name, String text, {bool forc
final arbObj = readArbFile(file);
if (arbObj.containsKey(name) && !force) {
- printV("String $name already exists in $fileName!");
+ print("String $name already exists in $fileName!");
return;
}
diff --git a/tool/utils/translation/translation_utils.dart b/tool/utils/translation/translation_utils.dart
index f53a8d13..4498e279 100644
--- a/tool/utils/translation/translation_utils.dart
+++ b/tool/utils/translation/translation_utils.dart
@@ -38,7 +38,7 @@ Future<String> getTranslation(String text, String lang) async {
try {
translation = (await translator.translate(text, from: defaultLang, to: lang)).text;
} catch (e) {
- printV("$lang, $text, $e");
+ print("$lang, $text, $e");
}
placeholder.forEach((index, value) {
final translatedPlaceholder = regExp.allMatches(translation).toList()[index];
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.