| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | import 'dart:async'; | 
 |  | 
 | import 'package:analysis_server/src/handler/legacy/legacy_handler.dart'; | 
 | import 'package:analysis_server/src/legacy_analysis_server.dart'; | 
 | import 'package:analysis_server/src/plugin/result_converter.dart'; | 
 | import 'package:analysis_server/src/protocol_server.dart'; | 
 | import 'package:analysis_server/src/request_handler_mixin.dart'; | 
 | import 'package:analysis_server/src/services/correction/fix/analysis_options/fix_generator.dart'; | 
 | import 'package:analysis_server/src/services/correction/fix/pubspec/fix_generator.dart'; | 
 | import 'package:analysis_server/src/services/correction/fix_performance.dart'; | 
 | import 'package:analysis_server_plugin/edit/fix/dart_fix_context.dart'; | 
 | import 'package:analysis_server_plugin/edit/fix/fix.dart'; | 
 | import 'package:analysis_server_plugin/src/correction/dart_change_workspace.dart'; | 
 | import 'package:analysis_server_plugin/src/correction/fix_processor.dart'; | 
 | import 'package:analyzer/dart/analysis/results.dart'; | 
 | import 'package:analyzer/dart/analysis/session.dart'; | 
 | import 'package:analyzer/file_system/file_system.dart'; | 
 | import 'package:analyzer/source/file_source.dart'; | 
 | import 'package:analyzer/source/line_info.dart'; | 
 | import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; | 
 | import 'package:analyzer/src/analysis_options/options_file_validator.dart'; | 
 | import 'package:analyzer/src/dart/analysis/analysis_options.dart'; | 
 | import 'package:analyzer/src/dart/analysis/results.dart' as engine; | 
 | import 'package:analyzer/src/exception/exception.dart'; | 
 | import 'package:analyzer/src/generated/source.dart' show SourceFactory; | 
 | import 'package:analyzer/src/pubspec/pubspec_validator.dart'; | 
 | import 'package:analyzer/src/util/file_paths.dart' as file_paths; | 
 | import 'package:analyzer/src/workspace/pub.dart'; | 
 | import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; | 
 | import 'package:yaml/yaml.dart'; | 
 |  | 
 | /// The handler for the `edit.getFixes` request. | 
 | class EditGetFixesHandler extends LegacyHandler | 
 |     with RequestHandlerMixin<LegacyAnalysisServer> { | 
 |   /// Initialize a newly created handler to be able to service requests for the | 
 |   /// [server]. | 
 |   EditGetFixesHandler( | 
 |     super.server, | 
 |     super.request, | 
 |     super.cancellationToken, | 
 |     super.performance, | 
 |   ); | 
 |  | 
 |   @override | 
 |   Future<void> handle() async { | 
 |     var params = EditGetFixesParams.fromRequest( | 
 |       request, | 
 |       clientUriConverter: server.uriConverter, | 
 |     ); | 
 |     var file = params.file; | 
 |     var offset = params.offset; | 
 |  | 
 |     if (server.sendResponseErrorIfInvalidFilePath(request, file)) { | 
 |       return; | 
 |     } | 
 |  | 
 |     if (!server.isAnalyzed(file)) { | 
 |       server.sendResponse(Response.getFixesInvalidFile(request)); | 
 |       return; | 
 |     } | 
 |  | 
 |     // | 
 |     // Allow plugins to start computing fixes. | 
 |     // | 
 |     var requestParams = plugin.EditGetFixesParams(file, offset); | 
 |     var driver = server.getAnalysisDriver(file); | 
 |     var pluginFutures = server.broadcastRequestToPlugins(requestParams, driver); | 
 |     // | 
 |     // Compute fixes associated with server-generated errors. | 
 |     // | 
 |     List<AnalysisErrorFixes>? errorFixesList; | 
 |     while (errorFixesList == null) { | 
 |       try { | 
 |         errorFixesList = await _computeServerErrorFixes(request, file, offset); | 
 |       } on InconsistentAnalysisException { | 
 |         // Loop around to try again to compute the fixes. | 
 |       } | 
 |     } | 
 |     // | 
 |     // Add the fixes produced by plugins to the server-generated fixes. | 
 |     // | 
 |     var responses = await waitForResponses( | 
 |       pluginFutures, | 
 |       requestParameters: requestParams, | 
 |     ); | 
 |     server.requestStatistics?.addItemTimeNow(request, 'pluginResponses'); | 
 |     var converter = ResultConverter(); | 
 |     for (var response in responses) { | 
 |       var result = plugin.EditGetFixesResult.fromResponse(response); | 
 |       errorFixesList.addAll( | 
 |         result.fixes.map(converter.convertAnalysisErrorFixes), | 
 |       ); | 
 |     } | 
 |     // | 
 |     // Send the response. | 
 |     // | 
 |     sendResult(EditGetFixesResult(errorFixesList)); | 
 |   } | 
 |  | 
 |   /// Compute and return the fixes associated with server-generated errors in | 
 |   /// analysis options files. | 
 |   Future<List<AnalysisErrorFixes>> _computeAnalysisOptionsFixes( | 
 |     String file, | 
 |     int offset, | 
 |   ) async { | 
 |     var errorFixesList = <AnalysisErrorFixes>[]; | 
 |     var resourceProvider = server.resourceProvider; | 
 |     var optionsFile = resourceProvider.getFile(file); | 
 |     var content = _safelyRead(optionsFile); | 
 |     if (content == null) { | 
 |       return errorFixesList; | 
 |     } | 
 |     var driver = server.getAnalysisDriver(file); | 
 |     if (driver == null) { | 
 |       return errorFixesList; | 
 |     } | 
 |     await driver.applyPendingFileChanges(); | 
 |     var session = driver.currentSession; | 
 |     var sourceFactory = driver.sourceFactory; | 
 |     var analysisContext = session.analysisContext; | 
 |     var package = analysisContext.contextRoot.workspace.findPackageFor( | 
 |       optionsFile.path, | 
 |     ); | 
 |     var sdkVersionConstraint = (package is PubPackage) | 
 |         ? package.sdkVersionConstraint | 
 |         : null; | 
 |     var diagnostics = analyzeAnalysisOptions( | 
 |       FileSource(optionsFile), | 
 |       content, | 
 |       sourceFactory, | 
 |       analysisContext.contextRoot.root.path, | 
 |       sdkVersionConstraint, | 
 |       resourceProvider, | 
 |     ); | 
 |     var options = _getOptions(sourceFactory, content); | 
 |     if (options == null) { | 
 |       return errorFixesList; | 
 |     } | 
 |     for (var diagnostic in diagnostics) { | 
 |       var generator = AnalysisOptionsFixGenerator( | 
 |         resourceProvider, | 
 |         diagnostic, | 
 |         content, | 
 |         options, | 
 |       ); | 
 |       var fixes = await generator.computeFixes(); | 
 |       if (fixes.isNotEmpty) { | 
 |         fixes.sort(Fix.compareFixes); | 
 |         var lineInfo = LineInfo.fromContent(content); | 
 |         // Options are not used in the analysis *of options* so associating | 
 |         // an empty set is accurate if not ideal. | 
 |         var analysisOptions = AnalysisOptionsImpl(); | 
 |         var result = engine.ErrorsResultImpl( | 
 |           session: session, | 
 |           file: optionsFile, | 
 |           content: content, | 
 |           uri: optionsFile.toUri(), | 
 |           lineInfo: lineInfo, | 
 |           isLibrary: true, | 
 |           isPart: false, | 
 |           diagnostics: diagnostics, | 
 |           analysisOptions: analysisOptions, | 
 |         ); | 
 |         var serverError = newAnalysisError_fromEngine(result, diagnostic); | 
 |         var errorFixes = AnalysisErrorFixes(serverError); | 
 |         errorFixesList.add(errorFixes); | 
 |         for (var fix in fixes) { | 
 |           errorFixes.fixes.add(fix.change); | 
 |         } | 
 |       } | 
 |     } | 
 |     return errorFixesList; | 
 |   } | 
 |  | 
 |   /// Compute and return the fixes associated with server-generated errors in | 
 |   /// Dart files. | 
 |   Future<List<AnalysisErrorFixes>> _computeDartFixes( | 
 |     Request request, | 
 |     String file, | 
 |     int offset, | 
 |   ) async { | 
 |     var errorFixesList = <AnalysisErrorFixes>[]; | 
 |     var libraryResult = await server.getResolvedLibrary(file); | 
 |     server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit'); | 
 |     if (libraryResult != null) { | 
 |       var unitResult = libraryResult.unitWithPath(file)!; | 
 |       var lineInfo = unitResult.lineInfo; | 
 |       var requestLine = lineInfo.getLocation(offset).lineNumber; | 
 |       for (var diagnostic in unitResult.diagnostics) { | 
 |         var errorLine = lineInfo.getLocation(diagnostic.offset).lineNumber; | 
 |         if (errorLine == requestLine) { | 
 |           var workspace = DartChangeWorkspace(await server.currentSessions); | 
 |           var context = DartFixContext( | 
 |             instrumentationService: server.instrumentationService, | 
 |             workspace: workspace, | 
 |             libraryResult: libraryResult, | 
 |             unitResult: unitResult, | 
 |             error: diagnostic, | 
 |           ); | 
 |  | 
 |           List<Fix> fixes; | 
 |           try { | 
 |             var performanceTracker = FixPerformance(); | 
 |             fixes = await computeFixes( | 
 |               context, | 
 |               performance: performanceTracker, | 
 |             ); | 
 |  | 
 |             server.recentPerformance.getFixes.add( | 
 |               GetFixesPerformance( | 
 |                 performance: performance, | 
 |                 path: file, | 
 |                 content: unitResult.content, | 
 |                 offset: offset, | 
 |                 requestLatency: performanceTracker.computeTime!.inMilliseconds, | 
 |                 producerTimings: performanceTracker.producerTimings, | 
 |               ), | 
 |             ); | 
 |           } on InconsistentAnalysisException { | 
 |             fixes = []; | 
 |           } catch (exception, stackTrace) { | 
 |             var parametersFile = | 
 |                 ''' | 
 | offset: $offset | 
 | error: $diagnostic | 
 | error.errorCode: ${diagnostic.diagnosticCode} | 
 | '''; | 
 |             throw CaughtExceptionWithFiles(exception, stackTrace, { | 
 |               file: unitResult.content, | 
 |               'parameters': parametersFile, | 
 |             }); | 
 |           } | 
 |  | 
 |           if (fixes.isNotEmpty) { | 
 |             fixes.sort(Fix.compareFixes); | 
 |             var serverError = newAnalysisError_fromEngine( | 
 |               unitResult, | 
 |               diagnostic, | 
 |             ); | 
 |             var errorFixes = AnalysisErrorFixes(serverError); | 
 |             errorFixesList.add(errorFixes); | 
 |             for (var fix in fixes) { | 
 |               errorFixes.fixes.add(fix.change); | 
 |             } | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |     server.requestStatistics?.addItemTimeNow(request, 'computedFixes'); | 
 |     return errorFixesList; | 
 |   } | 
 |  | 
 |   /// Compute and return the fixes associated with server-generated errors in | 
 |   /// pubspec.yaml files. | 
 |   Future<List<AnalysisErrorFixes>> _computePubspecFixes( | 
 |     String file, | 
 |     int offset, | 
 |   ) async { | 
 |     var errorFixesList = <AnalysisErrorFixes>[]; | 
 |     var resourceProvider = server.resourceProvider; | 
 |     var pubspecFile = resourceProvider.getFile(file); | 
 |     var content = _safelyRead(pubspecFile); | 
 |     if (content == null) { | 
 |       return errorFixesList; | 
 |     } | 
 |     var session = await server.getAnalysisSession(file); | 
 |     if (session == null) { | 
 |       return errorFixesList; | 
 |     } | 
 |  | 
 |     YamlNode node; | 
 |     try { | 
 |       node = loadYamlNode(content); | 
 |     } catch (exception) { | 
 |       return errorFixesList; | 
 |     } | 
 |     if (node is! YamlMap) { | 
 |       return errorFixesList; | 
 |     } | 
 |  | 
 |     var fileResult = session.getFile(pubspecFile.path); | 
 |     if (fileResult is! FileResult) { | 
 |       return errorFixesList; | 
 |     } | 
 |  | 
 |     var analysisOptions = fileResult.analysisOptions; | 
 |     var diagnostics = validatePubspec( | 
 |       contents: node, | 
 |       source: FileSource(pubspecFile), | 
 |       provider: resourceProvider, | 
 |       analysisOptions: analysisOptions, | 
 |     ); | 
 |     for (var diagnostic in diagnostics) { | 
 |       var generator = PubspecFixGenerator( | 
 |         resourceProvider, | 
 |         diagnostic, | 
 |         content, | 
 |         node, | 
 |         defaultEol: server.defaultEol, | 
 |       ); | 
 |       var fixes = await generator.computeFixes(); | 
 |       if (fixes.isNotEmpty) { | 
 |         fixes.sort(Fix.compareFixes); | 
 |         var lineInfo = LineInfo.fromContent(content); | 
 |         // TODO(pq): package:analyzer results are specific to *.dart files and we | 
 |         // shouldn't use them to represent errors in non-Dart files. | 
 |         // see: https://dart-review.googlesource.com/c/sdk/+/333588 | 
 |         var result = engine.ErrorsResultImpl( | 
 |           session: session, | 
 |           file: pubspecFile, | 
 |           content: content, | 
 |           uri: pubspecFile.toUri(), | 
 |           lineInfo: lineInfo, | 
 |           isLibrary: true, | 
 |           isPart: false, | 
 |           diagnostics: diagnostics, | 
 |           analysisOptions: analysisOptions, | 
 |         ); | 
 |         var serverError = newAnalysisError_fromEngine(result, diagnostic); | 
 |         var errorFixes = AnalysisErrorFixes(serverError); | 
 |         errorFixesList.add(errorFixes); | 
 |         for (var fix in fixes) { | 
 |           errorFixes.fixes.add(fix.change); | 
 |         } | 
 |       } | 
 |     } | 
 |     return errorFixesList; | 
 |   } | 
 |  | 
 |   /// Compute and return the fixes associated with server-generated errors. | 
 |   Future<List<AnalysisErrorFixes>> _computeServerErrorFixes( | 
 |     Request request, | 
 |     String file, | 
 |     int offset, | 
 |   ) async { | 
 |     var pathContext = server.resourceProvider.pathContext; | 
 |     if (file_paths.isDart(pathContext, file)) { | 
 |       return _computeDartFixes(request, file, offset); | 
 |     } else if (file_paths.isAnalysisOptionsYaml(pathContext, file)) { | 
 |       return _computeAnalysisOptionsFixes(file, offset); | 
 |     } else if (file_paths.isPubspecYaml(pathContext, file)) { | 
 |       return _computePubspecFixes(file, offset); | 
 |     } | 
 |     return <AnalysisErrorFixes>[]; | 
 |   } | 
 |  | 
 |   YamlMap? _getOptions(SourceFactory sourceFactory, String content) { | 
 |     var optionsProvider = AnalysisOptionsProvider(sourceFactory); | 
 |     try { | 
 |       return optionsProvider.getOptionsFromString(content); | 
 |     } on OptionsFormatException { | 
 |       return null; | 
 |     } | 
 |   } | 
 |  | 
 |   /// Return the contents of the [file], or `null` if the file does not exist or | 
 |   /// cannot be read. | 
 |   String? _safelyRead(File file) { | 
 |     try { | 
 |       return file.readAsStringSync(); | 
 |     } on FileSystemException { | 
 |       return null; | 
 |     } | 
 |   } | 
 | } |