From 08b88eff92c9756273526fb595c702d5c7e40c82 Mon Sep 17 00:00:00 2001 From: dev74 Date: Sun, 18 Oct 2026 23:32:41 +0300 Subject: [PATCH 1/3] fix(assets): surgically prune font declarations, weights, families, and empty sections in pubspec.yaml --- lib/src/assets/asset_auditor.dart | 21 +- lib/src/cleaners/asset_cleaner.dart | 367 +++++++++++++++++++++++++--- test/asset_auditor_test.dart | 199 +++++++++++++++ 3 files changed, 552 insertions(+), 35 deletions(-) diff --git a/lib/src/assets/asset_auditor.dart b/lib/src/assets/asset_auditor.dart index e1f8c9a..bc3cb15 100644 --- a/lib/src/assets/asset_auditor.dart +++ b/lib/src/assets/asset_auditor.dart @@ -325,7 +325,7 @@ class AssetAuditor { bool _isFontUsed(File file, PackageDescriptor ownerPkg, String codeCorpus) { final fileName = file.uri.pathSegments.safeLast ?? ''; - // Check if filename is directly referenced + // Check if filename is directly referenced (e.g. 'assets/fonts/Cairo.ttf' or 'Cairo.ttf') if (codeCorpus.contains(fileName)) return true; // Check if any declared font family containing this asset is referenced in code @@ -337,8 +337,9 @@ class AssetAuditor { final assets = entry.value; if (assets.any((a) => a.replaceAll(r'\', '/') == normPath)) { - // If the font family name is referenced in Dart code (e.g. fontFamily: 'MyFont') - if (codeCorpus.contains(family)) { + // If the font family name is referenced as a string literal in Dart code (e.g. fontFamily: 'MyFont' or "MyFont") + final familyPattern = RegExp('[\'"]${RegExp.escape(family)}[\'"]'); + if (familyPattern.hasMatch(codeCorpus)) { return true; } } @@ -360,11 +361,19 @@ class AssetAuditor { return '$first$rest'; } - /// Checks if a file path was explicitly declared by full path in pubspec.yaml. + /// Checks if a file path was explicitly declared by full path in pubspec.yaml (assets or fonts). bool _isExplicitlyDeclared(String relPackage, PackageDescriptor pkg) { final norm = relPackage.replaceAll(r'\', '/'); - return pkg.declaredAssetPatterns - .any((pat) => pat.replaceAll(r'\', '/') == norm); + if (pkg.declaredAssetPatterns + .any((pat) => pat.replaceAll(r'\', '/') == norm)) { + return true; + } + for (final fontList in pkg.declaredFonts.values) { + if (fontList.any((f) => f.replaceAll(r'\', '/') == norm)) { + return true; + } + } + return false; } } diff --git a/lib/src/cleaners/asset_cleaner.dart b/lib/src/cleaners/asset_cleaner.dart index 3a63d21..96f95a4 100644 --- a/lib/src/cleaners/asset_cleaner.dart +++ b/lib/src/cleaners/asset_cleaner.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import '../assets/asset_auditor.dart'; import '../assets/asset_models.dart'; import '../backup/backup_manager.dart'; import '../backup/backup_manifest.dart'; @@ -67,15 +68,13 @@ class AssetCleaner { int deletedCount = 0; int freedBytes = 0; - // 1. Group explicit assets by their owning pubspec file - final pubspecModifications = >{}; + // 1. Group unused assets by their owning package pubspec file + final pubspecModifications = >{}; + final packageRoots = {}; for (final item in unusedAssets) { - if (item.isExplicitInPubspec) { - final pubFile = item.ownerPackage.pubspecFile; - pubspecModifications - .putIfAbsent(pubFile, () => []) - .add(item.packageRelativePath); - } + final pubFile = item.ownerPackage.pubspecFile; + pubspecModifications.putIfAbsent(pubFile, () => []).add(item); + packageRoots[pubFile] = item.ownerPackage.packageRootDir; } // 2. Dry run preview @@ -143,13 +142,14 @@ class AssetCleaner { } catch (_) {} } - // 5. Surgically remove explicit entries from their respective pubspec.yaml + // 5. Surgically remove explicit entries (both assets and font assets) from their respective pubspec.yaml int updatedPubspecs = 0; for (final entry in pubspecModifications.entries) { final pubFile = entry.key; - final assetsToRemove = entry.value; + final itemsToRemove = entry.value; + final pkgRoot = packageRoots[pubFile] ?? workspaceRoot; - if (_prunePubspecAssetLines(pubFile, assetsToRemove)) { + if (_prunePubspecAssetsAndFonts(pubFile, pkgRoot, itemsToRemove)) { updatedPubspecs++; } } @@ -189,47 +189,356 @@ class AssetCleaner { } } - /// Surgically removes explicit lines referencing [assetsToRemove] from [pubFile]. - bool _prunePubspecAssetLines(File pubFile, List assetsToRemove) { + /// Checks if a directory contains no supported media or font files. + bool _isDirEmptyOfAssets(Directory dir) { + if (!dir.existsSync()) return true; + try { + final list = dir.listSync(recursive: true, followLinks: false); + for (final entity in list) { + if (entity is File) { + final ext = entity.uri.pathSegments.safeLast + ?.split('.') + .safeLast + ?.toLowerCase() ?? + ''; + if (AssetAuditor.supportedExtensions.contains(ext)) { + return false; + } + } + } + return true; + } catch (_) { + return true; + } + } + + /// Surgically removes unused asset and font entries from [pubFile]. + bool _prunePubspecAssetsAndFonts( + File pubFile, + Directory packageRootDir, + List unusedItems, + ) { if (!pubFile.existsSync()) return false; try { final lines = pubFile.readAsLinesSync(); - final targets = - assetsToRemove.map((a) => a.replaceAll(r'\', '/')).toSet(); - final newLines = []; + final targetPaths = {}; + for (final item in unusedItems) { + final rel = item.packageRelativePath.replaceAll(r'\', '/'); + targetPaths.add(rel); + targetPaths.add(rel.replaceFirst(RegExp(r'^\.?/'), '')); + final fromPkgRoot = + getRelativePath(item.absolutePath, packageRootDir.path) + .replaceAll(r'\', '/'); + targetPaths.add(fromPkgRoot); + targetPaths.add(fromPkgRoot.replaceFirst(RegExp(r'^\.?/'), '')); + } + bool modified = false; + final newLines = []; + int i = 0; + bool inFlutter = false; + int flutterIndent = 0; - for (int i = 0; i < lines.length; i++) { + while (i < lines.length) { final line = lines[i]; final trimmed = line.trim(); + final indent = line.indexOf(line.trimLeft()); + + // Track top-level flutter: section + if (indent == 0 && trimmed.startsWith('flutter:')) { + inFlutter = true; + flutterIndent = indent; + newLines.add(line); + i++; + continue; + } else if (indent <= flutterIndent && + trimmed.isNotEmpty && + !trimmed.startsWith('#') && + inFlutter) { + inFlutter = false; + } - if (trimmed.startsWith('-')) { - var entry = trimmed.substring(1).trim(); - if (entry.contains('#')) { - entry = entry.substring(0, entry.indexOf('#')).trim(); + if (!inFlutter) { + newLines.add(line); + i++; + continue; + } + + // 1. Process assets: section + if (trimmed.startsWith('assets:')) { + final assetsHeaderLine = line; + final assetsIndent = indent; + i++; + + final rawAssetLines = []; + while (i < lines.length) { + final nextLine = lines[i]; + final nextTrimmed = nextLine.trim(); + final nextIndent = nextLine.indexOf(nextLine.trimLeft()); + + if (nextTrimmed.isEmpty) { + rawAssetLines.add(nextLine); + i++; + continue; + } + + if (nextIndent <= assetsIndent && + !nextTrimmed.startsWith('-') && + !nextTrimmed.startsWith('#')) { + break; + } + + rawAssetLines.add(nextLine); + i++; + } + + final keptAssetLines = []; + for (final aLine in rawAssetLines) { + final aTrimmed = aLine.trim(); + if (aTrimmed.isEmpty || aTrimmed.startsWith('#')) { + keptAssetLines.add(aLine); + continue; + } + + if (aTrimmed.startsWith('-')) { + var rawEntry = aTrimmed.substring(1).trim(); + if (rawEntry.contains('#')) { + rawEntry = rawEntry.substring(0, rawEntry.indexOf('#')).trim(); + } + rawEntry = rawEntry + .replaceAll(RegExp(r'''^["']+|["']+$'''), '') + .trim() + .replaceAll(r'\', '/'); + final cleanEntry = rawEntry.replaceFirst(RegExp(r'^\.?/'), ''); + + // Check if entry matches an unused asset + if (targetPaths.contains(rawEntry) || + targetPaths.contains(cleanEntry)) { + modified = true; + continue; + } + + // Check if directory pattern that is now empty or missing + final isDirPattern = rawEntry.endsWith('/') || + FileSystemEntity.isDirectorySync( + '${packageRootDir.path}/$cleanEntry'); + if (isDirPattern) { + final dir = Directory('${packageRootDir.path}/$cleanEntry'); + if (!dir.existsSync() || _isDirEmptyOfAssets(dir)) { + modified = true; + continue; + } + } + + keptAssetLines.add(aLine); + } else { + keptAssetLines.add(aLine); + } } - entry = entry - .replaceAll(RegExp(r'''^["']+|["']+$'''), '') - .trim() - .replaceAll(r'\', '/'); - if (targets.contains(entry)) { - // Drop this line + final hasItems = + keptAssetLines.any((l) => l.trim().startsWith('-')); + if (hasItems) { + newLines.add(assetsHeaderLine); + newLines.addAll(keptAssetLines); + } else { + // Drop entire assets: section modified = true; - continue; } + continue; + } + + // 2. Process fonts: section + if (trimmed.startsWith('fonts:')) { + final fontsHeaderLine = line; + final fontsIndent = indent; + i++; + + final rawFontLines = []; + while (i < lines.length) { + final nextLine = lines[i]; + final nextTrimmed = nextLine.trim(); + final nextIndent = nextLine.indexOf(nextLine.trimLeft()); + + if (nextTrimmed.isEmpty) { + rawFontLines.add(nextLine); + i++; + continue; + } + + if (nextIndent <= fontsIndent && + !nextTrimmed.startsWith('-') && + !nextTrimmed.startsWith('#')) { + break; + } + + rawFontLines.add(nextLine); + i++; + } + + final families = _parseFontFamilies(rawFontLines); + final keptFamilyLines = []; + + for (final fam in families) { + final keptAssets = fam.fontAssets.where((asset) { + final clean = + asset.normalizedPath.replaceFirst(RegExp(r'^\.?/'), ''); + final isUnused = targetPaths.contains(asset.normalizedPath) || + targetPaths.contains(clean); + if (isUnused) { + modified = true; + return false; + } + return true; + }).toList(); + + if (keptAssets.isNotEmpty) { + keptFamilyLines.addAll(fam.headerLines); + for (final a in keptAssets) { + keptFamilyLines.addAll(a.lines); + } + keptFamilyLines.addAll(fam.trailingLines); + } else { + // Entire family pruned + modified = true; + } + } + + if (keptFamilyLines.isNotEmpty) { + newLines.add(fontsHeaderLine); + newLines.addAll(keptFamilyLines); + } else { + // Entire fonts: section pruned + modified = true; + } + continue; } newLines.add(line); + i++; } if (modified) { - pubFile.writeAsStringSync('${newLines.join('\n')}\n'); + // Clean up duplicate consecutive blank lines + final cleanedLines = []; + bool prevBlank = false; + for (final l in newLines) { + final isBlank = l.trim().isEmpty; + if (isBlank && prevBlank) continue; + cleanedLines.add(l); + prevBlank = isBlank; + } + + pubFile.writeAsStringSync('${cleanedLines.join('\n')}\n'); return true; } } catch (_) {} return false; } + + /// Parses raw lines under `fonts:` into structured font families. + List<_FontFamilySection> _parseFontFamilies(List fontLines) { + final families = <_FontFamilySection>[]; + List? currentHeader; + List<_FontAssetEntry> currentAssets = []; + _FontAssetEntry? currentAsset; + List currentTrailing = []; + + void commitCurrentAsset() { + if (currentAsset != null) { + currentAssets.add(currentAsset!); + currentAsset = null; + } + } + + void commitCurrentFamily() { + commitCurrentAsset(); + if (currentHeader != null) { + families.add(_FontFamilySection( + headerLines: currentHeader!, + fontAssets: currentAssets, + trailingLines: currentTrailing, + )); + currentHeader = null; + currentAssets = []; + currentTrailing = []; + } + } + + for (final line in fontLines) { + final trimmed = line.trim(); + if (trimmed.isEmpty) { + if (currentAsset != null) { + currentAsset!.lines.add(line); + } else if (currentHeader != null && currentAssets.isEmpty) { + currentHeader!.add(line); + } else { + currentTrailing.add(line); + } + continue; + } + + final isNewFamily = trimmed.startsWith('- family:') || + (trimmed.startsWith('family:') && currentHeader == null); + final isNewAsset = trimmed.startsWith('- asset:') || + (trimmed.startsWith('asset:') && currentHeader != null); + + if (isNewFamily) { + commitCurrentFamily(); + currentHeader = [line]; + } else if (isNewAsset) { + commitCurrentAsset(); + final m = RegExp(r'^-?\s*asset:\s*(.*)').firstMatch(trimmed); + var fontPath = m != null ? m.group(1)!.trim() : ''; + if (fontPath.contains('#')) { + fontPath = fontPath.substring(0, fontPath.indexOf('#')).trim(); + } + fontPath = fontPath + .replaceAll(RegExp(r'''^["']+|["']+$'''), '') + .trim() + .replaceAll(r'\', '/'); + currentAsset = _FontAssetEntry(normalizedPath: fontPath, lines: [line]); + } else if (currentAsset != null && + (trimmed.startsWith('weight:') || + trimmed.startsWith('style:') || + trimmed.startsWith('#'))) { + currentAsset!.lines.add(line); + } else if (currentHeader != null && + currentAssets.isEmpty && + currentAsset == null) { + currentHeader!.add(line); + } else if (currentAsset != null) { + currentAsset!.lines.add(line); + } else if (currentHeader != null) { + currentTrailing.add(line); + } + } + + commitCurrentFamily(); + return families; + } +} + +class _FontAssetEntry { + final String normalizedPath; + final List lines; + + _FontAssetEntry({ + required this.normalizedPath, + required this.lines, + }); +} + +class _FontFamilySection { + final List headerLines; + final List<_FontAssetEntry> fontAssets; + final List trailingLines; + + _FontFamilySection({ + required this.headerLines, + required this.fontAssets, + this.trailingLines = const [], + }); } diff --git a/test/asset_auditor_test.dart b/test/asset_auditor_test.dart index 20fc079..919d7e3 100644 --- a/test/asset_auditor_test.dart +++ b/test/asset_auditor_test.dart @@ -216,5 +216,204 @@ flutter: final backupDir = Directory('${tempDir.path}/.detect_unused/backups'); expect(backupDir.existsSync(), isTrue); }); + + test( + 'AssetCleaner: deletes unused font assets and prunes font declarations, weights, and empty families from pubspec.yaml', + () { + final rootPubspec = File('${tempDir.path}/pubspec.yaml'); + rootPubspec.writeAsStringSync(''' +name: font_cleaner_test +flutter: + uses-material-design: true + + fonts: + - family: Cairo + fonts: + - asset: assets/fonts/Cairo-Regular.ttf + - asset: assets/fonts/Cairo-Bold.ttf + weight: 700 + - family: AbandonedFont + fonts: + - asset: assets/fonts/Abandoned.ttf +'''); + + final cairoRegular = + File('${tempDir.path}/assets/fonts/Cairo-Regular.ttf') + ..createSync(recursive: true); + cairoRegular.writeAsStringSync('cairo regular'); + + final cairoBold = File('${tempDir.path}/assets/fonts/Cairo-Bold.ttf') + ..createSync(recursive: true); + cairoBold.writeAsStringSync('cairo bold'); + + final abandoned = File('${tempDir.path}/assets/fonts/Abandoned.ttf') + ..createSync(recursive: true); + abandoned.writeAsStringSync('abandoned'); + + // Only Cairo-Regular is referenced in Dart code + final codeFile = File('${tempDir.path}/lib/main.dart') + ..createSync(recursive: true); + codeFile.writeAsStringSync("const font = 'Cairo-Regular.ttf';"); + + final auditor = AssetAuditor(workspaceRoot: tempDir); + final report = auditor.run(); + + // Cairo-Bold and Abandoned are unused + expect(report.unusedAssets.length, equals(2)); + expect(report.unusedAssets.every((a) => a.isExplicitInPubspec), isTrue); + + final cleaner = AssetCleaner(workspaceRoot: tempDir); + final cleanResult = cleaner.clean(unusedAssets: report.unusedAssets); + + expect(cleanResult.deletedFilesCount, equals(2)); + expect(cairoRegular.existsSync(), isTrue); + expect(cairoBold.existsSync(), isFalse); + expect(abandoned.existsSync(), isFalse); + + final pubspecContent = rootPubspec.readAsStringSync(); + + // Cairo-Regular must remain + expect(pubspecContent, contains('Cairo-Regular.ttf')); + expect(pubspecContent, contains('family: Cairo')); + + // Cairo-Bold and its weight: 700 must be removed + expect(pubspecContent, isNot(contains('Cairo-Bold.ttf'))); + expect(pubspecContent, isNot(contains('weight: 700'))); + + // AbandonedFont family had all assets removed, so the entire family must be gone + expect(pubspecContent, isNot(contains('AbandonedFont'))); + expect(pubspecContent, isNot(contains('Abandoned.ttf'))); + }); + + test( + 'AssetCleaner: completely removes fonts: section when all font families are pruned', + () { + final rootPubspec = File('${tempDir.path}/pubspec.yaml'); + rootPubspec.writeAsStringSync(''' +name: font_all_unused +flutter: + uses-material-design: true + + fonts: + - family: DeadFont + fonts: + - asset: assets/fonts/Dead.ttf +'''); + + final deadFont = File('${tempDir.path}/assets/fonts/Dead.ttf') + ..createSync(recursive: true); + deadFont.writeAsStringSync('dead'); + + final codeFile = File('${tempDir.path}/lib/main.dart') + ..createSync(recursive: true); + codeFile.writeAsStringSync('void main() {}'); + + final auditor = AssetAuditor(workspaceRoot: tempDir); + final report = auditor.run(); + + expect(report.unusedAssets.length, equals(1)); + + final cleaner = AssetCleaner(workspaceRoot: tempDir); + final cleanResult = cleaner.clean(unusedAssets: report.unusedAssets); + + expect(cleanResult.deletedFilesCount, equals(1)); + expect(deadFont.existsSync(), isFalse); + + final pubspecContent = rootPubspec.readAsStringSync(); + expect(pubspecContent, isNot(contains('fonts:'))); + expect(pubspecContent, isNot(contains('DeadFont'))); + expect(pubspecContent, isNot(contains('Dead.ttf'))); + expect(pubspecContent, contains('uses-material-design: true')); + }); + + test( + 'AssetCleaner: removes empty directory asset patterns and empty assets: section', + () { + final rootPubspec = File('${tempDir.path}/pubspec.yaml'); + rootPubspec.writeAsStringSync(''' +name: dir_cleaner_test +flutter: + assets: + - assets/unused_icons/ +'''); + + final icon = File('${tempDir.path}/assets/unused_icons/old_icon.png') + ..createSync(recursive: true); + icon.writeAsStringSync('icon'); + + final codeFile = File('${tempDir.path}/lib/main.dart') + ..createSync(recursive: true); + codeFile.writeAsStringSync('void main() {}'); + + final auditor = AssetAuditor(workspaceRoot: tempDir); + final report = auditor.run(); + + expect(report.unusedAssets.length, equals(1)); + + final cleaner = AssetCleaner(workspaceRoot: tempDir); + final cleanResult = cleaner.clean(unusedAssets: report.unusedAssets); + + expect(cleanResult.deletedFilesCount, equals(1)); + expect(icon.existsSync(), isFalse); + + final pubspecContent = rootPubspec.readAsStringSync(); + expect(pubspecContent, isNot(contains('assets:'))); + expect(pubspecContent, isNot(contains('unused_icons/'))); + }); + + test( + 'AssetCleaner: cleans font asset declared in both flutter.assets and flutter.fonts simultaneously', + () { + final rootPubspec = File('${tempDir.path}/pubspec.yaml'); + rootPubspec.writeAsStringSync(''' +name: dual_declaration_test +flutter: + assets: + - assets/fonts/CustomFont.ttf + - assets/images/app_logo.png + fonts: + - family: CustomFont + fonts: + - asset: assets/fonts/CustomFont.ttf + weight: 500 +'''); + + final font = File('${tempDir.path}/assets/fonts/CustomFont.ttf') + ..createSync(recursive: true); + font.writeAsStringSync('font'); + + final logo = File('${tempDir.path}/assets/images/app_logo.png') + ..createSync(recursive: true); + logo.writeAsStringSync('logo'); + + // Only app_logo.png is used + final codeFile = File('${tempDir.path}/lib/main.dart') + ..createSync(recursive: true); + codeFile.writeAsStringSync("const logo = 'assets/images/app_logo.png';"); + + final auditor = AssetAuditor(workspaceRoot: tempDir); + final report = auditor.run(); + + expect(report.unusedAssets.length, equals(1)); + expect(report.unusedAssets.first.path.replaceAll(r'\', '/'), + contains('CustomFont.ttf')); + + final cleaner = AssetCleaner(workspaceRoot: tempDir); + final cleanResult = cleaner.clean(unusedAssets: report.unusedAssets); + + expect(cleanResult.deletedFilesCount, equals(1)); + expect(font.existsSync(), isFalse); + expect(logo.existsSync(), isTrue); + + final pubspecContent = rootPubspec.readAsStringSync(); + // Must keep logo in assets + expect(pubspecContent, contains('assets/images/app_logo.png')); + // Must remove font from assets: + expect(pubspecContent, isNot(contains('assets/fonts/CustomFont.ttf'))); + // Must remove fonts: and CustomFont family completely + expect(pubspecContent, isNot(contains('fonts:'))); + expect(pubspecContent, isNot(contains('family: CustomFont'))); + expect(pubspecContent, isNot(contains('weight: 500'))); + }); }); } From 92d08826e1eb95204d00b3ec6468a4e47515b11d Mon Sep 17 00:00:00 2001 From: dev74 Date: Sun, 18 Oct 2026 23:38:26 +0300 Subject: [PATCH 2/3] chore(release): bump version to 3.3.1 with font assets pubspec pruning --- CHANGELOG.md | 10 ++++++++++ lib/src/version.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efdd07c..d8b11af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 3.3.1 + +- **Surgical Font Assets & Pubspec Pruning:** + - `AssetAuditor` now explicitly recognizes font assets declared under `flutter -> fonts` in `pubspec.yaml`. + - Improved `_isFontUsed` to accurately match font families as quoted string literals in Dart code (e.g. `fontFamily: 'Cairo'`), preventing false positives when filenames share substrings with family names. + - `AssetCleaner` now surgically prunes unused font declarations (`- asset: ...`) along with their child properties (`weight`, `style`, comments) from `pubspec.yaml`. + - Automatically prunes empty `- family:` blocks when all their font assets are removed. + - Automatically prunes the `fonts:` section when all families are removed, preventing invalid YAML mapping issues. + - Automatically prunes empty directory patterns under `assets:` (e.g. `- assets/fonts/`) when all assets inside the directory are purged. + ## 3.3.0 - **AI Agent Optimization (LLMO / GEO Standard Compliance):** diff --git a/lib/src/version.dart b/lib/src/version.dart index 57e2c5d..9f00536 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -1,2 +1,2 @@ /// Current version of the detect_unused_code package. -const String packageVersion = '3.3.0'; +const String packageVersion = '3.3.1'; diff --git a/pubspec.yaml b/pubspec.yaml index fddd717..462ba7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: detect_unused_code description: Fast, zero-dependency Dart & Flutter dead code, assets, and packages remediation engine. Safely cleans unused classes, imports, assets, and dependencies. -version: 3.3.0 +version: 3.3.1 homepage: https://github.com/mdalhagg/detect_unused_code repository: https://github.com/mdalhagg/detect_unused_code issue_tracker: https://github.com/mdalhagg/detect_unused_code/issues From ddeb9dfc00dc259c4d2d7eb8d5d9d58eb43f98ea Mon Sep 17 00:00:00 2001 From: dev74 Date: Mon, 19 Oct 2026 00:19:34 +0300 Subject: [PATCH 3/3] style: format asset_cleaner.dart to pass CI formatting check --- lib/src/cleaners/asset_cleaner.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/cleaners/asset_cleaner.dart b/lib/src/cleaners/asset_cleaner.dart index 96f95a4..68fae22 100644 --- a/lib/src/cleaners/asset_cleaner.dart +++ b/lib/src/cleaners/asset_cleaner.dart @@ -337,8 +337,7 @@ class AssetCleaner { } } - final hasItems = - keptAssetLines.any((l) => l.trim().startsWith('-')); + final hasItems = keptAssetLines.any((l) => l.trim().startsWith('-')); if (hasItems) { newLines.add(assetsHeaderLine); newLines.addAll(keptAssetLines);