Repository: felangel/bloc

Issue:
fix: Bloc Analysis Server crash with FileSystemException
**Description**

I've enabled the new bloc lint server in VSCode and in crashes with exception FileSystemException

**Steps To Reproduce**

1. install version 6.8.0 of the Bloc extension for VSCode
2. Enable the recommended lint rules(from doc site) in your analysis_options.yaml
3. View Output tab filtered with component "Bloc Analysis Server" in VSCode bottom panel




**Screenshots**

![Image](https://github.com/user-attachments/assets/2972166e-0456-4410-867d-140195736293)

**Additional Context**
[Bloc Analysis Server.log](https://github.com/user-attachments/files/19947087/Bloc.Analysis.Server.log)

* OS: windows 11
* flutter: 3.29.3 (dart: 3.7.2). managed by FVM
* global dart: 3.7.3

[On discord](https://discord.com/channels/649708778631200778/649708778631200784/1366453993764683916) I noted that my code was in a separate drive(E:) but I moved to main drive(C:) and can still reproduce this issue. Seeing "Can't load Kernel binary: Invalid SDK hash." in the errors reminded me of https://fvm.app/documentation/getting-started/faq#invalid-kernel-binary-or-invalid-sdk-hash-when-running-fvm in FVM.



Discussion: I was able to reproduce this and should have a fix tomorrow. Sorry for the inconvenience!

Fix patch:
diff --git a/packages/bloc_lint/lib/src/analysis_options.dart b/packages/bloc_lint/lib/src/analysis_options.dart
index 723d9a06e27..7bf08110233 100644
--- a/packages/bloc_lint/lib/src/analysis_options.dart
+++ b/packages/bloc_lint/lib/src/analysis_options.dart
@@ -84,7 +84,7 @@ class AnalysisOptions {
 
   /// Try to parse an analysis_options yaml referenced by the [include].
   static AnalysisOptions? tryInclude(String include, {required Directory cwd}) {
-    final packagePrefix = include.split(p.separator).first;
+    final packagePrefix = include.split('/').first;
     final packageName = packagePrefix.split('package:').last;
     final packageConfigFile = File(
       p.join(cwd.path, '.dart_tool', 'package_config.json'),
diff --git a/packages/bloc_lint/lib/src/linter.dart b/packages/bloc_lint/lib/src/linter.dart
index 441eaee5eba..909f37cee42 100644
--- a/packages/bloc_lint/lib/src/linter.dart
+++ b/packages/bloc_lint/lib/src/linter.dart
@@ -36,9 +36,10 @@ class Linter {
   /// will be analyzed (both single files and directories are supported).
   Map<String, List<Diagnostic>> analyze({required Uri uri, String? content}) {
     if (content != null) return _analyzeContent(uri, content);
-    final directory = Directory(uri.path);
+    final path = uri.isScheme('file') ? p.fromUri(uri) : uri.path;
+    final directory = Directory(path);
     if (directory.existsSync()) return _analyzeDirectory(directory);
-    final file = File(uri.path);
+    final file = File(path);
     if (file.existsSync()) return _analyzeFile(file);
     return {};
   }
@@ -61,14 +62,18 @@ class Linter {
 
   Map<String, List<Diagnostic>> _analyzeContent(Uri uri, String content) {
     final diagnostics = <Diagnostic>[];
-    final results = {uri.path: diagnostics};
-    final cwd = File(uri.path).parent;
+    final path = uri.isScheme('file') ? p.fromUri(uri) : uri.path;
+    final results = {path: diagnostics};
+    final cwd = File(path).parent;
     final pubspecLock = findPubspecLock(cwd);
     if (pubspecLock == null) return results;
     if (!pubspecLock.packages.keys.contains('bloc')) return results;
     final analysisOptions = findAnalysisOptions(cwd);
     if (analysisOptions == null) return results;
-    if (analysisOptions.excludes.any((e) => e.matches(uri.path))) {
+    final relativePath = p
+        .relative(path, from: analysisOptions.file.parent.path)
+        .replaceAll(r'\', '/');
+    if (analysisOptions.excludes.any((e) => e.matches(relativePath))) {
       return results;
     }
     final document = TextDocument(uri: uri, content: content);
diff --git a/packages/bloc_tools/lib/src/lsp/language_server.dart b/packages/bloc_tools/lib/src/lsp/language_server.dart
index 7d4244e1326..2955d8848c7 100644
--- a/packages/bloc_tools/lib/src/lsp/language_server.dart
+++ b/packages/bloc_tools/lib/src/lsp/language_server.dart
@@ -26,7 +26,7 @@ class LanguageServer {
       _connection.sendDiagnostics(
         lsp.PublishDiagnosticsParams(
           diagnostics: entry.value.map((d) => d.toLsp()).toList(),
-          uri: Uri.parse(entry.key),
+          uri: p.toUri(entry.key),
         ),
       );
     }
@@ -57,7 +57,9 @@ class LanguageServer {
       },
       onDidSave: (params) async {
         if (p.basename(params.document.uri.path) == 'analysis_options.yaml') {
-          _reportDiagnostics(uri: File(params.document.uri.path).parent.uri);
+          _reportDiagnostics(
+            uri: File(p.fromUri(params.document.uri)).parent.uri,
+          );
         }
       },
     );


Test patch:
diff --git a/packages/bloc_tools/test/src/lsp/language_server_test.dart b/packages/bloc_tools/test/src/lsp/language_server_test.dart
index b1666dd21a7..fe4105047a1 100644
--- a/packages/bloc_tools/test/src/lsp/language_server_test.dart
+++ b/packages/bloc_tools/test/src/lsp/language_server_test.dart
@@ -7,6 +7,7 @@ import 'package:bloc_tools/src/lsp/language_server.dart';
 import 'package:lsp_server/lsp_server.dart' hide Diagnostic, Position, Range;
 import 'package:lsp_server/lsp_server.dart' as lsp;
 import 'package:mocktail/mocktail.dart';
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 
 class _MockLinter extends Mock implements Linter {}
@@ -32,7 +33,7 @@ typedef _OnDidSaveTextDocumentHandler =
 
 void main() {
   group(LanguageServer, () {
-    final rootUri = Uri.parse('file://main.dart');
+    final rootUri = Uri.parse('file:///c:/users/dash/main.dart');
     late Connection connection;
     late Linter linter;
     late LanguageServer languageServer;
@@ -78,7 +79,7 @@ void main() {
         ).thenAnswer((invocation) {
           final uri = invocation.namedArguments[#uri] as Uri;
           return <String, List<Diagnostic>>{
-            '$uri': [diagnostic],
+            p.fromUri(uri): [diagnostic],
           };
         });
       });
@@ -148,7 +149,7 @@ void main() {
           () => connection.sendDiagnostics(
             any(
               that: isA<PublishDiagnosticsParams>()
-                  .having((p) => p.uri, 'uri', rootUri)
+                  .having((p) => p.uri, 'uri', p.toUri(p.fromUri(rootUri)))
                   .having((p) => p.diagnostics, 'diagnostics', [
                     lsp.Diagnostic(
                       range: lsp.Range(
@@ -180,7 +181,7 @@ void main() {
           textDocument: TextDocumentItem(
             languageId: 'dart',
             text: 'void main() {}',
-            uri: Uri.parse('file://main.dart'),
+            uri: p.toUri(p.fromUri(rootUri)),
             version: 1,
           ),
         );
@@ -222,7 +223,7 @@ void main() {
                 as _OnDidChangeTextDocumentHandler;
         final didChangeTextDocumentParams = DidChangeTextDocumentParams(
           textDocument: VersionedTextDocumentIdentifier(
-            uri: Uri.parse('file://main.dart'),
+            uri: p.toUri(p.fromUri(rootUri)),
             version: 1,
           ),
           contentChanges: [