blob: 4cbce98ee0e82ce4b826cc0e73839bbbcd587056 [file] [edit]
import 'package:analyzer/dart/ast/token.dart';
import 'package:pub_semver/pub_semver.dart';
import '../ast_extensions.dart';
import '../dart_formatter.dart';
/// The formatting style that should be applied to code.
///
/// This is sort of the internal version of [DartFormatter]. The former is
/// public API so is limited in what it exposes. This contains getters for
/// internal use to determine what style rules to apply.
///
/// This also tracks how language version affects the style rules. From Dart 3.7
/// and forward, most changes to the formatting style are language versioned:
/// code whose language version is older than a style change will retain the
/// older style.
final class FormattingStyle {
static final _version3Dot7 = Version(3, 7, 0);
static final _version3Dot10 = Version(3, 10, 0);
static final _version3Dot13 = Version(3, 13, 0);
/// The [DartFormatter] the style was created from.
final DartFormatter _formatter;
/// The language version of the style.
///
/// Usually the same version as [formatter], but may be different if the file
/// being formatted has an `@dart=` comment.
final Version _languageVersion;
/// The number of characters allowed in a single line.
///
/// Usually the same as [formatter]'s but may be different if the file being
/// formatted has a `// dart format width = ` comment.
final int pageWidth;
FormattingStyle(
this._formatter, {
required this.lineEnding,
Version? languageVersion,
int? pageWidth,
}) : _languageVersion = languageVersion ?? _formatter.languageVersion,
pageWidth = pageWidth ?? _formatter.pageWidth;
final String? lineEnding;
/// The number of characters of indentation to prefix the output lines with.
int get leadingIndent => _formatter.indent;
/// Whether the code being formatted is at language version 3.7 and doesn't
/// include the sweeping style changes in 3.8.
bool get is3Dot7 => _languageVersion == _version3Dot7;
/// Whether a trailing comma should be preserved after for-loop updaters.
bool get preserveTrailingCommaAfterForUpdaters =>
_formatter.trailingCommas == TrailingCommas.preserve;
/// Whether a trailing comma should be preserved after enum values.
bool get preserveTrailingCommaAfterEnumValues =>
_formatter.trailingCommas == TrailingCommas.preserve &&
_languageVersion >= _version3Dot10;
/// Whether the formatter should penalize splitting in the target of a call
/// chain if the target is an argument list with only one argument or a
/// collection literal with only one element.
bool get avoidSplittingSingleElementCallChainTargets =>
_languageVersion >= _version3Dot13;
/// Whether mixin declarations and extension types with brace bodies should
/// always get a blank line above and below them.
///
/// They always should have, but they were overlooked. We already do this for
/// classes, enums, and extensions.
bool get blankLineAroundMixinAndExtensionTypes =>
_languageVersion >= _version3Dot13;
/// Whether parameter lists should be block formatted in things like typedefs.
bool get blockFormatParameterLists => _languageVersion >= _version3Dot13;
/// Whether the LHS of an `as`, `is`, or `is!` expression can be block
/// formatted.
bool get blockFormatTypeTest => _languageVersion >= _version3Dot13;
/// Whether an if-case pattern can be block-formatted when there is a guard
/// clause as well.
bool get blockFormatIfCaseWithGuard => _languageVersion < _version3Dot13;
/// Whether the formatter should prefer overflow from "soft" characters versus
/// others when no solution fits the page width and an overflowing solution
/// must be chosen.
///
/// Sometimes the formatter does it's best, but no solution fits in the page
/// width. Usually, this is because the code has some long string literals or
/// comments that the user should split manually. If the formatter treats all
/// overflowing characters uniformly, then it will try to pick a solution that
/// minimizes those overhanging strings and comments at the expense of
/// choosing weird formatting for other code. In particularly bad cases, the
/// strings or comments end up completely fitting and some other code runs
/// over. When that happens, it's confusing to the user because it looks like
/// the formatter just picked a weird solution for no reason. They don't see
/// that the strings or comments were the problem.
///
/// What we want is for the formatter to leave those strings or comments
/// hanging past the page width so it's clear to the user where they need to
/// split things to get everything to fit. Also, this minimizes the format
/// churn when they do fix those strings or comments.
///
/// To do that, the formatter distinguishes "soft" characters from other
/// kinds of code. "Soft" code is string literals, comments, or a few other
/// things that often follow a string literal or comment: `,`, `;`, or `() {`
/// (including `async` or other modifiers that can appear in a function
/// header) for a trailing block-formatted lambda. When an overflowing line of
/// code ends in soft characters, the overflow cost of all of those characters
/// is collapsed to a single point of penalty instead of one per character.
/// (We don't go all the way to zero because it is still overflow and we still
/// want to avoid it completely if possible.)
///
/// That leads the formatter to prefer solutions where the overflow is mostly
/// strings or comments, which is likely where the user needs to take action.
/// This feature is only a heuristic and doesn't always highlight the reason
/// no solution fits, but it's fairly simple one and does the right thing in
/// common cases.
///
/// This feature has no effect on code that does fit in the page width.
bool get useSoftOverflow => _languageVersion >= _version3Dot13;
/// Whether to force a blank line between imports and exports whose URIs are
/// different categories: `dart:`, `package:`, or relative.
bool get separateDirectiveSections => _languageVersion >= _version3Dot13;
/// Whether to try to figure out a piece's state based on the page width
/// before running the solver or during.
///
/// Initially, this ran during solving but that leads to some subtle bugs in
/// the solver. Performing it before solving is less effective for performance
/// but avoids those bugs.
///
/// We language version this even though the old logic was never correct to
/// minimize unexpected churn.
bool get pinStateByPageWidthBeforeSolving =>
_languageVersion >= _version3Dot13;
/// Whether an extension type's representation clause allows a trailing
/// comma.
///
/// When primary constructors were added in Dart 3.13, the grammar was
/// adjusted to define extension types in terms of them which also means that
/// a trailing comma is now permitted.
bool get allowTrailingCommaInRepresentationClause =>
_languageVersion >= _version3Dot13;
/// Whether there is a trailing comma at the end of the list delimited by
/// [rightBracket] which should be preserved by this style.
bool preserveTrailingCommaBefore(Token rightBracket) =>
_formatter.trailingCommas == TrailingCommas.preserve &&
rightBracket.hasCommaBefore;
}