From 6b5a7e29f1956194e1dac73fe8f1469edd452a72 Mon Sep 17 00:00:00 2001 From: DevFullStack-Franklyn-R-Silva Date: Thu, 11 Dec 2025 08:57:59 -0300 Subject: [PATCH 1/5] feat: add flutter lldb helper and initialization scripts --- .../Flutter/ephemeral/flutter_lldb_helper.py | 32 +++++++++++++++++++ .../ios/Flutter/ephemeral/flutter_lldbinit | 5 +++ pubspec.yaml | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 example/example_app/ios/Flutter/ephemeral/flutter_lldb_helper.py create mode 100644 example/example_app/ios/Flutter/ephemeral/flutter_lldbinit diff --git a/example/example_app/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/example_app/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/example/example_app/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/example/example_app/ios/Flutter/ephemeral/flutter_lldbinit b/example/example_app/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/example/example_app/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/pubspec.yaml b/pubspec.yaml index 2c50b83..4135715 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,11 +12,11 @@ dependencies: args: ^2.4.2 tint: ^2.0.1 yaml: ^3.1.2 + yaml_edit: ^2.2.3 dev_dependencies: lints: ^3.0.0 test: ^1.16.7 - import_sorter: emojis: true From 631be450c51302e9c76418bbe62bdc68c33374b5 Mon Sep 17 00:00:00 2001 From: DevFullStack-Franklyn-R-Silva Date: Thu, 11 Dec 2025 09:13:35 -0300 Subject: [PATCH 2/5] feat: add automatic sorting for pubspec.yaml dependencies --- README.md | 17 +++++++++-- lib/pubspec_sort.dart | 66 +++++++++++++++++++++++++++++++++++++++++++ lib/sort.dart | 51 ++++++++++++++++++++++++--------- 3 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 lib/pubspec_sort.dart diff --git a/README.md b/README.md index a102a26..c6c1967 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,18 @@ import_sorter: If you need another example check the [example app's import_sorter configuration](https://github.com/fluttercommunity/import_sorter/blob/master/example/example_app/pubspec.yaml#L76). +## Pubspec Dependency Sorting 📦 + +The tool now automatically organizes your `pubspec.yaml` dependencies, ensuring cleaner and more readable project configurations. + +When running `import_sorter`, it will alphabetically sort package entries within the following sections, preserving comments and formatting: + +- `dependencies` +- `dev_dependencies` +- `dependency_overrides` + +This feature is enabled by default. + ## 🚨 [`pre-commit`](https://pre-commit.com/) Hook There are two pre-commit hooks available: `dart-import-sorter` and `flutter-import-sorter`. They use `pub run` and `flutter pub run` respectively. Use the former for a generic Dart project and the latter for a Flutter project. @@ -124,7 +136,7 @@ Using pre-commit hooks in your project: ```yaml - repo: https://github.com/fluttercommunity/import_sorter - rev: 'master' + rev: "master" hooks: - id: dart-import-sorter # use `flutter-import-sorter` for a Flutter project ``` @@ -140,8 +152,8 @@ pre-commit run --all-files All contributions are welcome! Just make sure that it's not an already existing issue or pull request. -## 👥 Contributors +## 👥 Contributors - **[@gleich](https://github.com/gleich)** @@ -160,5 +172,6 @@ All contributions are welcome! Just make sure that it's not an already existing - **[@jlnrrg](https://github.com/jlnrrg)** - **[@vHanda](https://github.com/vHanda)** +- **[@vHanda](https://github.com/Franklyn-R-Silva)** diff --git a/lib/pubspec_sort.dart b/lib/pubspec_sort.dart new file mode 100644 index 0000000..0e8ace1 --- /dev/null +++ b/lib/pubspec_sort.dart @@ -0,0 +1,66 @@ +// 🎯 Dart imports: +import 'dart:io'; + +// 📦 Package imports: +import 'package:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +/// Sorts the dependencies (dependencies, dev_dependencies, dependency_overrides) +/// in pubspec.yaml alphabetically. +/// +/// Returns true if changes were made to the file, and false otherwise +/// (if the file doesn't exist or is already sorted). +bool sortPubspec() { + final file = File('pubspec.yaml'); + + // 1. Check if the file exists. + if (!file.existsSync()) { + return false; + } + + final content = file.readAsStringSync(); + final editor = YamlEditor(content); + final yaml = loadYaml(content); + + // 2. Define the sections that should be sorted. + final sectionsToCheck = [ + 'dependencies', + 'dev_dependencies', + 'dependency_overrides' + ]; + + var changed = false; + + // 3. Iterate over sections and apply sorting logic. + for (final section in sectionsToCheck) { + if (yaml is Map && yaml.containsKey(section)) { + final sectionMap = yaml[section]; + + // Skip if the section is not a Map or is empty + if (sectionMap is! Map || sectionMap.isEmpty) continue; + + // Get the keys (package names) + final keys = sectionMap.keys.map((e) => e.toString()).toList(); + + // Create a list of sorted keys + final sortedKeys = List.from(keys)..sort(); + + // If the current order is different from the sorted order, apply the change + if (keys.toString() != sortedKeys.toString()) { + // Recreate the map with the new order + final sortedMap = {for (var key in sortedKeys) key: sectionMap[key]}; + + // Update the file virtually (using yaml_edit to preserve comments and structure) + editor.update([section], sortedMap); + changed = true; + } + } + } + + // 4. If any change was made, write the new content to the file. + if (changed) { + file.writeAsStringSync(editor.toString()); + } + + return changed; +} diff --git a/lib/sort.dart b/lib/sort.dart index ba44e4e..af9a886 100644 --- a/lib/sort.dart +++ b/lib/sort.dart @@ -1,10 +1,9 @@ // 🎯 Dart imports: import 'dart:io'; -/// Sort the imports -/// Returns the sorted file as a string at -/// index 0 and the number of sorted imports -/// at index 1 +/// Sorts the imports in a given list of lines. +/// Returns an [ImportSortData] object containing the sorted file +/// as a string and a boolean indicating if the file was updated. ImportSortData sortImports( List lines, String packageName, @@ -13,6 +12,7 @@ ImportSortData sortImports( bool noComments, { String? filePath, }) { + // Functions to generate the categorized import comments String dartImportComment(bool emojis) => '//${emojis ? ' 🎯 ' : ' '}Dart imports:'; String flutterImportComment(bool emojis) => @@ -41,7 +41,7 @@ ImportSortData sortImports( var isMultiLineString = false; for (var i = 0; i < lines.length; i++) { - // Check if line is in multiline string + // Check if line is inside a multiline string literal if (_timesContained(lines[i], "'''") == 1 || _timesContained(lines[i], '"""') == 1) { isMultiLineString = !isMultiLineString; @@ -62,7 +62,9 @@ ImportSortData sortImports( } else { projectRelativeImports.add(lines[i]); } - } else if (i != lines.length - 1 && + } + // Check for existing import comments that should be ignored + else if (i != lines.length - 1 && (lines[i] == dartImportComment(false) || lines[i] == flutterImportComment(false) || lines[i] == packageImportComment(false) || @@ -74,16 +76,21 @@ ImportSortData sortImports( lines[i] == '// 📱 Flutter imports:') && lines[i + 1].startsWith('import ') && lines[i + 1].endsWith(';')) { - } else if (noImports()) { + } + // If no imports have been found yet, the line belongs to the file header + else if (noImports()) { beforeImportLines.add(lines[i]); - } else { + } + // Otherwise, the line belongs to the code after the imports + else { afterImportLines.add(lines[i]); } } - // If no imports return original string of lines + // If no imports were found, return the original string of lines if (noImports()) { var joinedLines = lines.join('\n'); + // Ensure the file ends with a single newline character if (joinedLines.endsWith('\n') && !joinedLines.endsWith('\n\n')) { joinedLines += '\n'; } else if (!joinedLines.endsWith('\n')) { @@ -92,7 +99,7 @@ ImportSortData sortImports( return ImportSortData(joinedLines, false); } - // Remove spaces + // Remove trailing empty lines from the header if (beforeImportLines.isNotEmpty) { if (beforeImportLines.last.trim() == '') { beforeImportLines.removeLast(); @@ -101,21 +108,27 @@ ImportSortData sortImports( final sortedLines = [...beforeImportLines]; - // Adding content conditionally + // Add a blank line if the header is not empty if (beforeImportLines.isNotEmpty) { sortedLines.add(''); } + + // Adding Dart imports if (dartImports.isNotEmpty) { if (!noComments) sortedLines.add(dartImportComment(emojis)); dartImports.sort(); sortedLines.addAll(dartImports); } + + // Adding Flutter imports if (flutterImports.isNotEmpty) { if (dartImports.isNotEmpty) sortedLines.add(''); if (!noComments) sortedLines.add(flutterImportComment(emojis)); flutterImports.sort(); sortedLines.addAll(flutterImports); } + + // Adding Package imports if (packageImports.isNotEmpty) { if (dartImports.isNotEmpty || flutterImports.isNotEmpty) { sortedLines.add(''); @@ -124,6 +137,8 @@ ImportSortData sortImports( packageImports.sort(); sortedLines.addAll(packageImports); } + + // Adding Project imports (relative and absolute paths) if (projectImports.isNotEmpty || projectRelativeImports.isNotEmpty) { if (dartImports.isNotEmpty || flutterImports.isNotEmpty || @@ -137,22 +152,28 @@ ImportSortData sortImports( sortedLines.addAll(projectRelativeImports); } + // Add separation line before the rest of the code sortedLines.add(''); + // Add the remaining code lines var addedCode = false; for (var j = 0; j < afterImportLines.length; j++) { if (afterImportLines[j] != '') { sortedLines.add(afterImportLines[j]); addedCode = true; } + // Only keep internal blank lines if code (non-blank line) has been added if (addedCode && afterImportLines[j] == '') { sortedLines.add(afterImportLines[j]); } } + // Add a final newline for standard file format compliance sortedLines.add(''); final sortedFile = sortedLines.join('\n'); final original = '${lines.join('\n')}\n'; + + // Check if the file changed and exit if required by the flag if (exitIfChanged && original != sortedFile) { if (filePath != null) { stdout @@ -160,19 +181,21 @@ ImportSortData sortImports( } exit(1); } + + // If no changes were made, return the original content if (original == sortedFile) { return ImportSortData(original, false); } + // Return the sorted content return ImportSortData(sortedFile, true); } -/// Get the number of times a string contains another -/// string +/// Gets the number of times a string contains another string int _timesContained(String string, String looking) => string.split(looking).length - 1; -/// Data to return from a sort +/// Data structure to return from a sort operation class ImportSortData { final String sortedFile; final bool updated; From aa811a9d09f691359da31cd91d0cec6dab806fa0 Mon Sep 17 00:00:00 2001 From: DevFullStack-Franklyn-R-Silva Date: Thu, 11 Dec 2025 09:18:01 -0300 Subject: [PATCH 3/5] feat: add sorting feedback for pubspec.yaml dependencies --- bin/import_sorter.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bin/import_sorter.dart b/bin/import_sorter.dart index ce34ccc..3b5dbb8 100644 --- a/bin/import_sorter.dart +++ b/bin/import_sorter.dart @@ -3,6 +3,7 @@ import 'dart:io'; // 📦 Package imports: import 'package:args/args.dart'; +import 'package:import_sorter/pubspec_sort.dart' as pubspec_sort; import 'package:tint/tint.dart'; import 'package:yaml/yaml.dart'; @@ -104,6 +105,14 @@ void main(List args) { sortedFiles.add(filePath); } + stdout.write('\n┏━━ Sorting pubspec.yaml dependencies...\n'); + final pubspecSorted = pubspec_sort.sortPubspec(); + if (pubspecSorted) { + stdout.write('┃ ┗━━ $success Sorted dependencies in pubspec.yaml\n'); + } else { + stdout.write('┃ ┗━━ pubspec.yaml is already sorted or not found\n'); + } + stopwatch.stop(); // Outputting results From 35823578f1827d26e67f8b5037cf9379f6ba6b9e Mon Sep 17 00:00:00 2001 From: DevFullStack-Franklyn-R-Silva Date: Thu, 11 Dec 2025 09:24:35 -0300 Subject: [PATCH 4/5] feat: Allow custom path to the pubspec.yaml file and add tests for classification. --- lib/pubspec_sort.dart | 4 +- test/pubspec_test.dart | 85 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 test/pubspec_test.dart diff --git a/lib/pubspec_sort.dart b/lib/pubspec_sort.dart index 0e8ace1..6846a95 100644 --- a/lib/pubspec_sort.dart +++ b/lib/pubspec_sort.dart @@ -10,8 +10,8 @@ import 'package:yaml_edit/yaml_edit.dart'; /// /// Returns true if changes were made to the file, and false otherwise /// (if the file doesn't exist or is already sorted). -bool sortPubspec() { - final file = File('pubspec.yaml'); +bool sortPubspec({String path = 'pubspec.yaml'}) { + final file = File(path); // 1. Check if the file exists. if (!file.existsSync()) { diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart new file mode 100644 index 0000000..64919d2 --- /dev/null +++ b/test/pubspec_test.dart @@ -0,0 +1,85 @@ +// 🎯 Dart imports: +import 'dart:io'; + +// 📦 Package imports: +import 'package:test/test.dart'; + +// 🌎 Project imports: +import 'package:import_sorter/pubspec_sort.dart'; + +void main() { + group('Pubspec Sorting', () { + final testFilePath = 'pubspec_test_temp.yaml'; + late File testFile; + + setUp(() { + testFile = File(testFilePath); + }); + + tearDown(() { + // Limpa o arquivo de teste após cada execução + if (testFile.existsSync()) { + testFile.deleteSync(); + } + }); + + test('Sorts dependencies alphabetically', () { + // 1. Cria um arquivo yaml bagunçado + const content = ''' +name: testing +dependencies: + yaml: ^3.1.0 + args: ^2.0.0 + tint: ^2.0.0 +'''; + testFile.writeAsStringSync(content); + + // 2. Roda sua função no arquivo de teste + final changed = sortPubspec(path: testFilePath); + + // 3. Verifica se houve mudança e se está ordenado + expect(changed, isTrue); + + final sortedContent = testFile.readAsStringSync(); + // args vem antes de tint, que vem antes de yaml + expect(sortedContent, contains('args: ^2.0.0')); + expect(sortedContent.indexOf('args:'), + lessThan(sortedContent.indexOf('tint:'))); + expect(sortedContent.indexOf('tint:'), + lessThan(sortedContent.indexOf('yaml:'))); + }); + + test('Sorts dev_dependencies alphabetically', () { + const content = ''' +name: testing +dev_dependencies: + test: ^1.0.0 + lints: ^2.0.0 +'''; + testFile.writeAsStringSync(content); + + sortPubspec(path: testFilePath); + + final sortedContent = testFile.readAsStringSync(); + // lints deve vir antes de test + expect(sortedContent.indexOf('lints:'), + lessThan(sortedContent.indexOf('test:'))); + }); + + test('Does nothing if already sorted', () { + const content = ''' +name: testing +dependencies: + args: ^2.0.0 + yaml: ^3.1.0 +'''; + testFile.writeAsStringSync(content); + + // Roda a função + final changed = sortPubspec(path: testFilePath); + + // Não deve ter alterado nada + expect(changed, isFalse); + }); + }); +} From 76216b801ea4b60e1ba3d72f2811a4c6dd4fc30b Mon Sep 17 00:00:00 2001 From: DevFullStack-Franklyn-R-Silva Date: Thu, 11 Dec 2025 10:08:27 -0300 Subject: [PATCH 5/5] docs: Update contributor list and improve test comments for clarity --- README.md | 3 ++- test/pubspec_test.dart | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c6c1967..8a6e6ba 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ All contributions are welcome! Just make sure that it's not an already existing - **[@jlnrrg](https://github.com/jlnrrg)** - **[@vHanda](https://github.com/vHanda)** -- **[@vHanda](https://github.com/Franklyn-R-Silva)** + +- **[@Franklyn-R-Silva](https://github.com/Franklyn-R-Silva)** diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart index 64919d2..eaddb1f 100644 --- a/test/pubspec_test.dart +++ b/test/pubspec_test.dart @@ -17,14 +17,14 @@ void main() { }); tearDown(() { - // Limpa o arquivo de teste após cada execução + // Cleans up the test file after each execution if (testFile.existsSync()) { testFile.deleteSync(); } }); test('Sorts dependencies alphabetically', () { - // 1. Cria um arquivo yaml bagunçado + // 1. Create a messy yaml file const content = ''' name: testing dependencies: @@ -34,14 +34,14 @@ dependencies: '''; testFile.writeAsStringSync(content); - // 2. Roda sua função no arquivo de teste + // 2. Run your function on the test file final changed = sortPubspec(path: testFilePath); - // 3. Verifica se houve mudança e se está ordenado + // 3. Check if there was a change and if it is sorted expect(changed, isTrue); final sortedContent = testFile.readAsStringSync(); - // args vem antes de tint, que vem antes de yaml + // args comes before tint, which comes before yaml expect(sortedContent, contains('args: ^2.0.0')); expect(sortedContent.indexOf('args:'), lessThan(sortedContent.indexOf('tint:'))); @@ -61,7 +61,7 @@ dev_dependencies: sortPubspec(path: testFilePath); final sortedContent = testFile.readAsStringSync(); - // lints deve vir antes de test + // lints should come before test expect(sortedContent.indexOf('lints:'), lessThan(sortedContent.indexOf('test:'))); }); @@ -75,10 +75,10 @@ dependencies: '''; testFile.writeAsStringSync(content); - // Roda a função + // Run the function final changed = sortPubspec(path: testFilePath); - // Não deve ter alterado nada + // Should not have changed anything expect(changed, isFalse); }); });