[analyzer]/[analysis_server] Handle parse errors in analysis_options.yaml better + tweak unhandled exception text

Fixes the main issues at https://github.com/dart-lang/sdk/issues/55987

Change-Id: I5b86f7c0df4017c02b96e67d8d8d03b71933b318
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/371861
Reviewed-by: Sam Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2024-06-17 23:15:51 +00:00 committed by Commit Queue
parent 3a91bcb39e
commit d4c2b91a14
3 changed files with 44 additions and 14 deletions

View file

@ -135,6 +135,26 @@ include: package:pedantic/analysis_options.yaml
// expect(updatedDiagnostics, hasLength(0)); // expect(updatedDiagnostics, hasLength(0));
} }
/// Ensure the server can initialize correctly and send diagnostics when the
/// analysis_options file throws errors during parsing.
///
/// https://github.com/dart-lang/sdk/issues/55987
Future<void> test_analysisOptionsFile_parseError() async {
newFile(analysisOptionsPath, '''
include: package:lints/recommended.yaml
f
''');
var firstDiagnosticsUpdate = waitForDiagnostics(analysisOptionsUri);
await initialize();
var initialDiagnostics = await firstDiagnosticsUpdate;
var diagnostic = initialDiagnostics!.first;
expect(diagnostic.severity, DiagnosticSeverity.Error);
expect(diagnostic.code, 'parse_error');
expect(diagnostic.message, "Expected ':'.");
}
Future<void> test_contextMessage() async { Future<void> test_contextMessage() async {
newFile(mainFilePath, ''' newFile(mainFilePath, '''
void f() { void f() {

View file

@ -24,7 +24,8 @@ class AnalysisOptionsProvider {
/// [root]/[file_paths.analysisOptionsYaml]. /// [root]/[file_paths.analysisOptionsYaml].
/// Recursively merge options referenced by an include directive /// Recursively merge options referenced by an include directive
/// and remove the include directive from the resulting options map. /// and remove the include directive from the resulting options map.
/// Return an empty options map if the file does not exist. /// Return an empty options map if the file does not exist or cannot be
/// parsed.
YamlMap getOptions(Folder root) { YamlMap getOptions(Folder root) {
File? optionsFile = getOptionsFile(root); File? optionsFile = getOptionsFile(root);
if (optionsFile == null) { if (optionsFile == null) {
@ -51,7 +52,8 @@ class AnalysisOptionsProvider {
/// Provide the options found in [file]. /// Provide the options found in [file].
/// Recursively merge options referenced by an include directive /// Recursively merge options referenced by an include directive
/// and remove the include directive from the resulting options map. /// and remove the include directive from the resulting options map.
/// Return an empty options map if the file does not exist. /// Return an empty options map if the file does not exist or cannot be
/// parsed.
YamlMap getOptionsFromFile(File file) { YamlMap getOptionsFromFile(File file) {
return getOptionsFromSource(FileSource(file)); return getOptionsFromSource(FileSource(file));
} }
@ -60,21 +62,25 @@ class AnalysisOptionsProvider {
/// ///
/// Recursively merge options referenced by an `include` directive and remove /// Recursively merge options referenced by an `include` directive and remove
/// the `include` directive from the resulting options map. Return an empty /// the `include` directive from the resulting options map. Return an empty
/// options map if the file does not exist. /// options map if the file does not exist or cannot be parsed.
YamlMap getOptionsFromSource(Source source) { YamlMap getOptionsFromSource(Source source) {
YamlMap options = getOptionsFromString(_readAnalysisOptions(source)); try {
var node = options.valueAt(AnalyzerOptions.include); YamlMap options = getOptionsFromString(_readAnalysisOptions(source));
var sourceFactory = this.sourceFactory; var node = options.valueAt(AnalyzerOptions.include);
if (sourceFactory != null && node is YamlScalar) { var sourceFactory = this.sourceFactory;
var path = node.value; if (sourceFactory != null && node is YamlScalar) {
if (path is String) { var path = node.value;
var parent = sourceFactory.resolveUri(source, path); if (path is String) {
if (parent != null) { var parent = sourceFactory.resolveUri(source, path);
options = merge(getOptionsFromSource(parent), options); if (parent != null) {
options = merge(getOptionsFromSource(parent), options);
}
} }
} }
return options;
} on OptionsFormatException {
return YamlMap();
} }
return options;
} }
/// Provide the options found in [content]. /// Provide the options found in [content].

View file

@ -295,8 +295,12 @@ class AnalysisServer {
void _handleServerError(Map<String, dynamic>? error) { void _handleServerError(Map<String, dynamic>? error) {
_serverErrorReceived = true; _serverErrorReceived = true;
final err = error!; final err = error!;
log.stderr('An unexpected error was encountered by the Analysis Server.');
log.stderr('Please file an issue at '
'https://github.com/dart-lang/sdk/issues/new/choose with the following '
'details:\n');
// Fields are 'isFatal', 'message', and 'stackTrace'. // Fields are 'isFatal', 'message', and 'stackTrace'.
log.stderr('Error from the analysis server: ${err['message']}'); log.stderr(err['message']);
if (err['stackTrace'] != null) { if (err['stackTrace'] != null) {
log.stderr(err['stackTrace'] as String); log.stderr(err['stackTrace'] as String);
} }