blob: 3a3c9ee4099fee20fb306434411d3f531ab5b4ba [file] [log] [blame]
// 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/protocol_server.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:dart_style/src/dart_formatter.dart';
import 'package:dart_style/src/exceptions.dart';
import 'package:dart_style/src/source_code.dart';
import 'package:pub_semver/pub_semver.dart';
/// The handler for the `edit.formatIfEnabled` request.
class EditFormatIfEnabledHandler extends LegacyHandler {
/// Initialize a newly created handler to be able to service requests for the
/// [server].
EditFormatIfEnabledHandler(
super.server, super.request, super.cancellationToken, super.performance);
/// Format the given [file] with the given [languageVersion].
///
/// Throws a [FileSystemException] if the file doesn't exist or can't be read.
/// Throws a [FormatterException] if the code could not be formatted.
List<SourceEdit> formatFile(File file, {Version? languageVersion}) {
// TODO(brianwilkerson): Move this to a superclass when `edit.format` is
// implemented by a handler class so the code can be shared.
var originalContent = file.readAsStringSync();
var code = SourceCode(originalContent);
var formatter = DartFormatter(
languageVersion:
languageVersion ?? DartFormatter.latestLanguageVersion);
var formatResult = formatter.formatSource(code);
var formattedContent = formatResult.text;
var edits = <SourceEdit>[];
if (formattedContent != originalContent) {
// TODO(brianwilkerson): Replace full replacements with smaller, more
// targeted edits.
var edit = SourceEdit(0, originalContent.length, formattedContent);
edits.add(edit);
}
return edits;
}
@override
Future<void> handle() async {
var params = EditFormatIfEnabledParams.fromRequest(request,
clientUriConverter: server.uriConverter);
var collection = AnalysisContextCollectionImpl(
includedPaths: params.directories,
resourceProvider: server.resourceProvider,
sdkPath: server.sdkPath,
);
var sourceFileEdits = <SourceFileEdit>[];
for (var context in collection.contexts) {
await _formatInContext(context, sourceFileEdits);
}
sendResult(EditFormatIfEnabledResult(sourceFileEdits));
}
/// Format all of the Dart files in the given [context] whose associated
/// `codeStyleOptions` enable formatting, adding the edits to the list of
/// [sourceFileEdits].
Future<void> _formatInContext(DriverBasedAnalysisContext context,
List<SourceFileEdit> sourceFileEdits) async {
var pathContext = context.resourceProvider.pathContext;
for (var filePath in context.contextRoot.analyzedFiles()) {
// Skip anything but .dart files.
if (!file_paths.isDart(pathContext, filePath)) continue;
// TODO(pq): consider optimizing this file (re)creation
// `analyzedFiles()` creates and disposes of a File object only for us
// to recreate it here. It would be faster if we could get the files
// directly from the context root.
var resource = context.resourceProvider.getResource(filePath);
if (resource is! File) continue;
var options = context.getAnalysisOptionsForFile(resource);
if (options.codeStyleOptions.useFormatter) {
try {
var library = await context.driver.getResolvedLibrary(filePath);
var languageVersion = library.effectiveLanguageVersion;
var sourceEdits =
formatFile(resource, languageVersion: languageVersion);
if (sourceEdits.isNotEmpty) {
sourceFileEdits
.add(SourceFileEdit(filePath, 0, edits: sourceEdits));
}
} catch (exception) {
// Ignore files that can't be formatted.
}
}
}
}
}