[analysis_server] Pass experiments through to formatter

This passes any experiments that are both enabled, and marked as "future" through to the formatter.

I extracted most uses of the formatter to use the same shared `createFormatter()` helper that sets the appropriate values. Those that didn't already have a Result to pass I added TODOs to (though one is g3 so probably would require some internal migration).

See https://github.com/dart-lang/sdk/issues/55125

Change-Id: I8f4ef4242614dc240e217cdaf99105f2a5b49dc9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/486840
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Danny Tuppeny
2026-03-10 12:46:31 -07:00
committed by Commit Queue
parent 38743dd774
commit 4a4658e014
8 changed files with 128 additions and 47 deletions
@@ -7,9 +7,9 @@ import 'dart:async';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
import 'package:analysis_server/src/utilities/extensions/formatter_options.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/src/utilities/formatter.dart';
import 'package:dart_style/dart_style.dart' hide TrailingCommas;
/// The handler for the `edit.format` request.
@@ -47,25 +47,17 @@ class EditFormatHandler extends LegacyHandler {
length = null;
}
var formatter = createFormatter(unit, defaultPageWidth: params.lineLength);
var unformattedCode = unit.content;
var code = SourceCode(
unformattedCode,
selectionStart: start,
selectionLength: length,
);
var formatterOptions = unit.analysisOptions.formatterOptions;
var effectivePageWidth = formatterOptions.pageWidth ?? params.lineLength;
var effectiveTrailingCommas = formatterOptions.dartStyleTrailingCommas;
var effectiveLanguageVersion = unit.unit.languageVersion.effective;
var formatter = DartFormatter(
pageWidth: effectivePageWidth,
trailingCommas: effectiveTrailingCommas,
languageVersion: effectiveLanguageVersion,
);
SourceCode formattedResult;
try {
formattedResult = formatter.formatSource(code);
formattedResult = formatter.formatSource(
SourceCode(
unformattedCode,
selectionStart: start,
selectionLength: length,
),
);
} on FormatterException {
sendResponse(Response.formatWithErrors(request));
return;
@@ -36,6 +36,9 @@ class EditFormatIfEnabledHandler extends LegacyHandler {
var originalContent = file.readAsStringSync();
var code = SourceCode(originalContent);
// TODO(dantup): Consider using createFormatter() which takes a result and
// correctly applies settings like page_width, trailing_commas, and enabled
// experiments.
var formatter = DartFormatter(
languageVersion: languageVersion ?? DartFormatter.latestLanguageVersion,
);
@@ -8,7 +8,6 @@ import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/protocol_server.dart'
as server
show SourceEdit;
import 'package:analysis_server/src/utilities/extensions/formatter_options.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/token.dart';
@@ -17,6 +16,7 @@ import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
import 'package:analyzer_plugin/src/utilities/formatter.dart';
import 'package:dart_style/dart_style.dart' hide TrailingCommas;
/// Checks whether a string contains only characters that are allowed to differ
@@ -130,25 +130,10 @@ ErrorOr<List<TextEdit>?> generateEditsForFormatting(
}) {
var unformattedSource = result.content;
var formatterOptions = result.analysisOptions.formatterOptions;
// The analysis options page width always takes priority over the default from
// the LSP configuration.
var effectivePageWidth = formatterOptions.pageWidth ?? defaultPageWidth;
var effectiveTrailingCommas = formatterOptions.dartStyleTrailingCommas;
var effectiveLanguageVersion = result.unit.languageVersion.effective;
var code = SourceCode(unformattedSource);
SourceCode formattedResult;
var formatter = createFormatter(result, defaultPageWidth: defaultPageWidth);
String formattedSource;
try {
// Create a new formatter on every request because it may contain state that
// affects repeated formats.
// https://github.com/dart-lang/dart_style/issues/1337
var formatter = DartFormatter(
pageWidth: effectivePageWidth,
trailingCommas: effectiveTrailingCommas,
languageVersion: effectiveLanguageVersion,
);
formattedResult = formatter.formatSource(code);
formattedSource = formatter.format(unformattedSource);
} on FormatterException {
// If the document fails to parse, just return no edits to avoid the
// use seeing edits on every save with invalid code (if LSP gains the
@@ -156,7 +141,6 @@ ErrorOr<List<TextEdit>?> generateEditsForFormatting(
// we may wish to change this to return an error for that case).
return success(null);
}
var formattedSource = formattedResult.text;
if (formattedSource == unformattedSource) {
return success(null);
@@ -23,6 +23,7 @@ import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core
import 'package:analyzer_plugin/src/utilities/charcodes.dart';
import 'package:analyzer_plugin/src/utilities/directive_sort.dart';
import 'package:analyzer_plugin/src/utilities/extensions/resolved_unit_result.dart';
import 'package:analyzer_plugin/src/utilities/formatter.dart';
import 'package:analyzer_plugin/src/utilities/library.dart';
import 'package:analyzer_plugin/src/utilities/string_utilities.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -1895,16 +1896,14 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
}
}
var languageVersion = resolvedUnit.libraryElement.languageVersion.effective;
var formattedResult = DartFormatter(languageVersion: languageVersion)
.formatSource(
SourceCode(
newContent,
isCompilationUnit: true,
selectionStart: newRangeOffset,
selectionLength: newRangeLength,
),
);
var formatter = createFormatter(resolvedUnit);
var formattedResult = formatter.formatSource(
SourceCode(
newContent,
selectionStart: newRangeOffset,
selectionLength: newRangeLength,
),
);
replaceEdits(
range,
@@ -0,0 +1,42 @@
// Copyright (c) 2026, 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 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart'
show ExperimentStatus;
import 'package:analyzer_plugin/src/utilities/extensions/formatter_options.dart';
import 'package:dart_style/dart_style.dart';
/// A list of all features that are currently enabled by an experiment flag.
final _allowedExperiments = ExperimentStatus.knownFeatures.values
.where((feature) => feature.status == FeatureStatus.future)
.toList();
/// Creates a formatter with the appropriate settings for [result].
DartFormatter createFormatter(
ParsedUnitResult result, {
int? defaultPageWidth,
}) {
var featureSet = result.unit.featureSet;
var formatterOptions = result.analysisOptions.formatterOptions;
var effectivePageWidth = formatterOptions.pageWidth ?? defaultPageWidth;
var effectiveTrailingCommas = formatterOptions.dartStyleTrailingCommas;
var effectiveLanguageVersion = result.unit.languageVersion.effective;
return DartFormatter(
pageWidth: effectivePageWidth,
trailingCommas: effectiveTrailingCommas,
languageVersion: effectiveLanguageVersion,
experimentFlags: _getExperiments(featureSet),
);
}
/// Gets the list of experiment strings enabled by [featureSet] that are
/// required for future features.
List<String> _getExperiments(FeatureSet featureSet) {
return _allowedExperiments
.where(featureSet.isEnabled)
.map((feature) => feature.enableString)
.toList();
}
@@ -0,0 +1,59 @@
// Copyright (c) 2026, 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 'package:analyzer_plugin/src/utilities/formatter.dart';
import 'package:analyzer_testing/experiments/experiments.dart';
import 'package:dart_style/dart_style.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../support/abstract_single_unit.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(FormatterTest);
});
}
@reflectiveTest
class FormatterTest extends AbstractSingleUnitTest {
Future<void> test_experiments() async {
await resolveTestCode('');
var formatter = createFormatter(result);
expect(formatter.experimentFlags, experimentsForTests);
}
Future<void> test_languageVersion_default() async {
await resolveTestCode('');
var formatter = createFormatter(result);
expect(formatter.languageVersion, DartFormatter.latestLanguageVersion);
}
Future<void> test_languageVersion_override() async {
await resolveTestCode('// @dart=2.12');
var formatter = createFormatter(result);
expect(formatter.languageVersion, Version(2, 12, 0));
}
Future<void> test_pageWidth() async {
newFile(convertPath('$testPackageRootPath/analysis_options.yaml'), '''
formatter:
page_width: 123
''');
await resolveTestCode('');
var formatter = createFormatter(result);
expect(formatter.pageWidth, 123);
}
Future<void> test_trailingCommas() async {
newFile(convertPath('$testPackageRootPath/analysis_options.yaml'), '''
formatter:
trailing_commas: preserve
''');
await resolveTestCode('');
var formatter = createFormatter(result);
expect(formatter.trailingCommas, TrailingCommas.preserve);
}
}
@@ -7,6 +7,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'change_builder/test_all.dart' as change_builder;
import 'client_uri_converter_test.dart' as client_uri_converter;
import 'completion/test_all.dart' as completion;
import 'formatter_test.dart' as formatter;
import 'navigation/test_all.dart' as navigation;
import 'string_utilities_test.dart' as string_utilities;
import 'visitors/test_all.dart' as visitors;
@@ -16,6 +17,7 @@ void main() {
change_builder.main();
client_uri_converter.main();
completion.main();
formatter.main();
navigation.main();
string_utilities.main();
visitors.main();