mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
We set an original expression on ErrorExpr for cases where we have something semantically invalid that doesn't fit into the AST, but is still something that the user has explicitly written. For example this is how we represent unresolved dots without member names (`x.`). We still want to type-check the underlying expression though since it can provide useful diagnostics and allows semantic functionality such as completion and cursor info to work correctly. rdar://130771574
3037 lines
108 KiB
C++
3037 lines
108 KiB
C++
//===--- Formatting.cpp ---------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/ASTWalker.h"
|
|
#include "swift/AST/AvailabilitySpec.h"
|
|
#include "swift/AST/GenericParamList.h"
|
|
#include "swift/AST/TypeRepr.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/Frontend/Frontend.h"
|
|
#include "swift/IDE/Indenting.h"
|
|
#include "swift/IDE/SourceEntityWalker.h"
|
|
#include "swift/Parse/Lexer.h"
|
|
#include "swift/Subsystems.h"
|
|
|
|
using namespace swift;
|
|
using namespace ide;
|
|
|
|
namespace {
|
|
|
|
using StringBuilder = llvm::SmallString<64>;
|
|
|
|
static bool isOnSameLine(SourceManager &SM, SourceLoc L, SourceLoc R) {
|
|
return Lexer::getLocForStartOfLine(SM, L) ==
|
|
Lexer::getLocForStartOfLine(SM, R);
|
|
}
|
|
|
|
static void widenOrSet(SourceRange &First, SourceRange Second) {
|
|
if (Second.isInvalid())
|
|
return;
|
|
if (First.isValid()) {
|
|
First.widen(Second);
|
|
} else {
|
|
First = Second;
|
|
}
|
|
}
|
|
|
|
/// \returns true if \c Loc is the location of the first non-comment token on
|
|
/// its line.
|
|
static bool isFirstTokenOnLine(SourceManager &SM, SourceLoc Loc) {
|
|
assert(Loc.isValid());
|
|
SourceLoc LineStart = Lexer::getLocForStartOfLine(SM, Loc);
|
|
CommentRetentionMode SkipComments = CommentRetentionMode::None;
|
|
Token First = Lexer::getTokenAtLocation(SM, LineStart, SkipComments);
|
|
return First.getLoc() == Loc;
|
|
}
|
|
|
|
/// \returns the location of the first non-whitespace character on the line
|
|
/// containing \c Loc.
|
|
static SourceLoc
|
|
getLocForContentStartOnSameLine(SourceManager &SM, SourceLoc Loc) {
|
|
assert(Loc.isValid());
|
|
SourceLoc LineStart = Lexer::getLocForStartOfLine(SM, Loc);
|
|
StringRef Indentation = Lexer::getIndentationForLine(SM, LineStart);
|
|
return LineStart.getAdvancedLoc(Indentation.size());
|
|
}
|
|
|
|
/// \returns true if the line at \c Loc is either empty or only contains whitespace
|
|
static bool isLineAtLocEmpty(SourceManager &SM, SourceLoc Loc) {
|
|
SourceLoc LineStart = Lexer::getLocForStartOfLine(SM, Loc);
|
|
SourceLoc Next = Lexer::getTokenAtLocation(SM, LineStart, CommentRetentionMode::ReturnAsTokens).getLoc();
|
|
return Next.isInvalid() || !isOnSameLine(SM, Loc, Next);
|
|
}
|
|
|
|
/// \returns the first token after the token at \c Loc.
|
|
static std::optional<Token> getTokenAfter(SourceManager &SM, SourceLoc Loc,
|
|
bool SkipComments = true) {
|
|
assert(Loc.isValid());
|
|
CommentRetentionMode Mode = SkipComments
|
|
? CommentRetentionMode::None
|
|
: CommentRetentionMode::ReturnAsTokens;
|
|
assert(Lexer::getTokenAtLocation(SM, Loc, Mode).getLoc() == Loc);
|
|
SourceLoc End = Lexer::getLocForEndOfToken(SM, Loc);
|
|
Token Next = Lexer::getTokenAtLocation(SM, End, Mode);
|
|
if (Next.getKind() == tok::NUM_TOKENS)
|
|
return std::nullopt;
|
|
return Next;
|
|
}
|
|
|
|
/// \returns the last token of the given kind in the open range between \c From
|
|
/// and \c To.
|
|
static std::optional<Token> getLastTokenOfKindInOpenRange(SourceManager &SM,
|
|
tok Kind,
|
|
SourceLoc From,
|
|
SourceLoc To) {
|
|
std::optional<Token> Match;
|
|
while (auto Next = getTokenAfter(SM, From)) {
|
|
if (!Next || !SM.isBeforeInBuffer(Next->getLoc(), To))
|
|
break;
|
|
if (Next->getKind() == Kind)
|
|
Match = Next;
|
|
From = Next->getLoc();
|
|
}
|
|
return Match;
|
|
}
|
|
|
|
/// \returns true if the token at \c Loc is one of the given \c Kinds.
|
|
static bool locIsKind(SourceManager &SM, SourceLoc Loc, ArrayRef<tok> Kinds) {
|
|
Token Tok = Lexer::getTokenAtLocation(SM, Loc);
|
|
return Tok.getLoc() == Loc &&
|
|
std::find(Kinds.begin(), Kinds.end(), Tok.getKind()) != Kinds.end();
|
|
}
|
|
|
|
/// \returns the given \c Loc if there is a token at that location that is one
|
|
/// of the given \c Kinds and an invalid \c SourceLocation otherwise.
|
|
static SourceLoc getLocIfKind(SourceManager &SM, SourceLoc Loc,
|
|
ArrayRef<tok> Kinds) {
|
|
if (!locIsKind(SM, Loc, Kinds))
|
|
return SourceLoc();
|
|
return Loc;
|
|
}
|
|
|
|
/// \returns the given \c Loc if there is a token at that location that is
|
|
/// spelled with the given \c Text and an invalid \c SourceLocation otherwise.
|
|
static SourceLoc
|
|
getLocIfTokenTextMatches(SourceManager &SM, SourceLoc Loc, StringRef Text) {
|
|
Token Tok = Lexer::getTokenAtLocation(SM, Loc);
|
|
if (Tok.getLoc() != Loc || Tok.getKind() == tok::NUM_TOKENS ||
|
|
Tok.getRawText() != Text)
|
|
return SourceLoc();
|
|
return Loc;
|
|
}
|
|
|
|
/// \returns true if the given \c VarDecl has grouping accessor braces containing
|
|
/// one or more explicit accessors.
|
|
static bool hasExplicitAccessors(VarDecl *VD) {
|
|
auto Getter = VD->getParsedAccessor(AccessorKind::Get);
|
|
SourceRange Braces = VD->getBracesRange();
|
|
return Braces.isValid() && (!Getter ||
|
|
Getter->getAccessorKeywordLoc().isValid());
|
|
}
|
|
|
|
static ClosureExpr *findTrailingClosureFromArgument(Expr *arg) {
|
|
if (auto TC = dyn_cast_or_null<ClosureExpr>(arg))
|
|
return TC;
|
|
if (auto TCL = dyn_cast_or_null<CaptureListExpr>(arg))
|
|
return dyn_cast_or_null<ClosureExpr>(TCL->getClosureBody());
|
|
return nullptr;
|
|
}
|
|
|
|
static size_t calcVisibleWhitespacePrefix(StringRef Line,
|
|
CodeFormatOptions Options) {
|
|
size_t Indent = 0;
|
|
for (auto Char : Line) {
|
|
if (Char == '\t') {
|
|
Indent += Options.TabWidth;
|
|
} else if (Char == ' ' || Char == '\v' || Char == '\f') {
|
|
Indent++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return Indent;
|
|
}
|
|
|
|
/// An indentation context of the target location
|
|
struct IndentContext {
|
|
enum ContextKind { Exact, LineStart };
|
|
|
|
/// The location to indent relative to.
|
|
SourceLoc ContextLoc;
|
|
|
|
/// Indicates whether to indent relative to the extact column of ContextLoc
|
|
/// (Exact) or to the start of the content of the line it appears on (LineStart).
|
|
ContextKind Kind;
|
|
|
|
/// The number of levels to indent by.
|
|
unsigned IndentLevel;
|
|
|
|
IndentContext(SourceLoc Context, bool AddsIndent,
|
|
ContextKind Kind = LineStart)
|
|
: ContextLoc(Context), Kind(Kind), IndentLevel(AddsIndent ? 1 : 0) {
|
|
assert(Context.isValid());
|
|
}
|
|
};
|
|
|
|
|
|
/// A helper class used to optionally override the ContextLoc and Kind of an
|
|
/// IndentContext.
|
|
class ContextOverride {
|
|
struct Override {
|
|
/// The overriding ContextLoc.
|
|
SourceLoc ContextLoc;
|
|
/// The overriding Kind.
|
|
IndentContext::ContextKind Kind;
|
|
/// The location after which this override takes effect.
|
|
SourceLoc ApplicableFrom;
|
|
};
|
|
|
|
/// The current override, if set.
|
|
std::optional<Override> Value;
|
|
|
|
public:
|
|
/// Clears this override.
|
|
void clear() { Value = std::nullopt; }
|
|
|
|
/// Sets this override to make an IndentContext indent relative to the exact
|
|
/// column of AlignLoc if the IndentContext's ContextLoc is >= AlignLoc and
|
|
/// on the same line.
|
|
void setExact(SourceManager &SM, SourceLoc AlignLoc) {
|
|
Value = {AlignLoc, IndentContext::Exact, AlignLoc};
|
|
}
|
|
|
|
/// Sets this override to propagate the given ContextLoc and Kind along to any
|
|
/// IndentContext with a ContextLoc >= L and on the same line. If this
|
|
/// override's existing value applies to the provided ContextLoc, its
|
|
/// ContextLoc and Kind are propagated instead.
|
|
///
|
|
/// This propagation is necessary for cases like the trailing closure of 'bar'
|
|
/// in the example below. It's direct ContextLoc is 'bar', but we want
|
|
/// it to be 'foo' (the ContextLoc of its parent tuple expression):
|
|
///
|
|
/// \code
|
|
/// foo(a: 1,
|
|
/// b: 2)(45, bar(c: 1,
|
|
/// d: 2) {
|
|
/// fatalError()
|
|
/// })
|
|
/// \endcode
|
|
SourceLoc propagateContext(SourceManager &SM, SourceLoc ContextLoc,
|
|
IndentContext::ContextKind Kind,
|
|
SourceLoc L, SourceLoc R) {
|
|
// If the range ends on the same line as it starts, we know up front that
|
|
// no child range can span multiple lines, so there's no need to propagate
|
|
// ContextLoc via this override.
|
|
if (R.isValid() && isOnSameLine(SM, L, R))
|
|
return ContextLoc;
|
|
|
|
// Similarly if the ContextLoc and L are on the same line, there's no need
|
|
// to propagate. Overrides applicable to ContextLoc will already apply
|
|
// to child ranges on the same line as L.
|
|
if (isOnSameLine(SM, ContextLoc, L))
|
|
return ContextLoc;
|
|
|
|
applyIfNeeded(SM, ContextLoc, Kind);
|
|
Value = {ContextLoc, Kind, L};
|
|
return ContextLoc;
|
|
}
|
|
|
|
/// Applies the overriding ContextLoc and Kind to the given IndentContext if it
|
|
/// starts after ApplicableFrom and on the same line.
|
|
void applyIfNeeded(SourceManager &SM, IndentContext &Ctx) {
|
|
// Exactly aligned indent contexts should always set a matching exact
|
|
// alignment context override so child braces/parens/brackets are indented
|
|
// correctly. If the given innermost indent context is Exact and the
|
|
// override doesn't match its Kind and ContextLoc, something is wrong.
|
|
assert((Ctx.Kind != IndentContext::Exact ||
|
|
(Value && Value->Kind == IndentContext::Exact &&
|
|
Value->ContextLoc == Ctx.ContextLoc)) &&
|
|
"didn't set override ctx when exact innermost context was set?");
|
|
|
|
applyIfNeeded(SM, Ctx.ContextLoc, Ctx.Kind);
|
|
}
|
|
|
|
/// Applies the overriding ContextLoc and Kind to the given Override if its
|
|
/// ContextLoc starts after ApplicableFrom and on the same line.
|
|
void applyIfNeeded(SourceManager &SM, SourceLoc &ContextLoc,
|
|
IndentContext::ContextKind &Kind) {
|
|
if (!isApplicableTo(SM, ContextLoc))
|
|
return;
|
|
ContextLoc = Value->ContextLoc;
|
|
Kind = Value->Kind;
|
|
}
|
|
|
|
private:
|
|
bool isApplicableTo(SourceManager &SM, SourceLoc Loc) const {
|
|
return Value && isOnSameLine(SM, Loc, Value->ApplicableFrom) &&
|
|
!SM.isBeforeInBuffer(Loc, Value->ApplicableFrom);
|
|
}
|
|
};
|
|
|
|
|
|
class FormatContext {
|
|
SourceManager &SM;
|
|
std::optional<IndentContext> InnermostCtx;
|
|
bool InDocCommentBlock;
|
|
bool InCommentLine;
|
|
|
|
public:
|
|
FormatContext(SourceManager &SM, std::optional<IndentContext> IndentCtx,
|
|
bool InDocCommentBlock = false, bool InCommentLine = false)
|
|
: SM(SM), InnermostCtx(IndentCtx), InDocCommentBlock(InDocCommentBlock),
|
|
InCommentLine(InCommentLine) {}
|
|
|
|
bool IsInDocCommentBlock() {
|
|
return InDocCommentBlock;
|
|
}
|
|
|
|
bool IsInCommentLine() {
|
|
return InCommentLine;
|
|
}
|
|
|
|
void padToExactColumn(StringBuilder &Builder,
|
|
const CodeFormatOptions &FmtOptions) {
|
|
assert(isExact() && "Context is not exact?");
|
|
SourceLoc AlignLoc = InnermostCtx->ContextLoc;
|
|
CharSourceRange Range(SM, Lexer::getLocForStartOfLine(SM, AlignLoc),
|
|
AlignLoc);
|
|
unsigned SpaceLength = 0;
|
|
unsigned TabLength = 0;
|
|
|
|
// Calculating space length
|
|
for (auto C: Range.str())
|
|
SpaceLength += C == '\t' ? FmtOptions.TabWidth : 1;
|
|
SpaceLength += InnermostCtx->IndentLevel * FmtOptions.TabWidth;
|
|
|
|
// If we're indenting past the exact column, round down to the next tab.
|
|
if (InnermostCtx->IndentLevel)
|
|
SpaceLength -= SpaceLength % FmtOptions.TabWidth;
|
|
|
|
// If we are using tabs, calculating the number of tabs and spaces we need
|
|
// to insert.
|
|
if (FmtOptions.UseTabs) {
|
|
TabLength = SpaceLength / FmtOptions.TabWidth;
|
|
SpaceLength = SpaceLength % FmtOptions.TabWidth;
|
|
}
|
|
Builder.append(TabLength, '\t');
|
|
Builder.append(SpaceLength, ' ');
|
|
}
|
|
|
|
bool isExact() {
|
|
return InnermostCtx.has_value() &&
|
|
InnermostCtx->Kind == IndentContext::Exact;
|
|
}
|
|
|
|
std::pair<unsigned, unsigned> indentLineAndColumn() {
|
|
if (InnermostCtx)
|
|
return SM.getLineAndColumnInBuffer(InnermostCtx->ContextLoc);
|
|
return std::make_pair(0, 0);
|
|
}
|
|
|
|
bool shouldAddIndentForLine() const {
|
|
return InnermostCtx.has_value() && InnermostCtx->IndentLevel > 0;
|
|
}
|
|
|
|
unsigned numIndentLevels() const {
|
|
if (InnermostCtx)
|
|
return InnermostCtx->IndentLevel;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
/// Recursively strips any trailing arguments, subscripts, generic
|
|
/// specializations, or optional bindings from the given expression.
|
|
static Expr *getContextExprOf(SourceManager &SM, Expr *E) {
|
|
assert(E);
|
|
if (auto *USE = dyn_cast<UnresolvedSpecializeExpr>(E)) {
|
|
if (auto *Sub = USE->getSubExpr())
|
|
return getContextExprOf(SM, Sub);
|
|
} else if (auto *CE = dyn_cast<CallExpr>(E)) {
|
|
if (auto *Fn = CE->getFn())
|
|
return getContextExprOf(SM, Fn);
|
|
} else if (auto *SE = dyn_cast<SubscriptExpr>(E)) {
|
|
if (auto *B = SE->getBase())
|
|
return getContextExprOf(SM, B);
|
|
} else if (auto *OBE = dyn_cast<BindOptionalExpr>(E)) {
|
|
if (auto *B = OBE->getSubExpr())
|
|
return getContextExprOf(SM, B);
|
|
} else if (auto *PUE = dyn_cast<PostfixUnaryExpr>(E)) {
|
|
if (auto *B = PUE->getOperand())
|
|
return getContextExprOf(SM, B);
|
|
}
|
|
return E;
|
|
}
|
|
|
|
/// Finds the ContextLoc to use for the argument of the given SubscriptExpr,
|
|
/// ApplyExpr, or UnresolvedSpecializeExpr. This is needed as the ContextLoc to
|
|
/// align their arguments with (including trailing closures) may be neither the
|
|
/// start or end of their function or base expression, as in the SubscriptExpr
|
|
/// in the example below, where 'select' is the desired ContextLoc to use.
|
|
///
|
|
/// \code
|
|
/// Base()
|
|
/// .select(x: 10
|
|
/// y: 20)[10] {
|
|
/// print($0)
|
|
/// }
|
|
/// .count
|
|
/// \endcode
|
|
static SourceLoc getContextLocForArgs(SourceManager &SM, Expr *E) {
|
|
assert(E->getArgs() || isa<UnresolvedSpecializeExpr>(E));
|
|
Expr *Base = getContextExprOf(SM, E);
|
|
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(Base))
|
|
return UDE->getDotLoc();
|
|
if (auto *UDRE = dyn_cast<UnresolvedDeclRefExpr>(Base))
|
|
return UDRE->getLoc();
|
|
return Base->getStartLoc();
|
|
}
|
|
|
|
/// This is a helper class intended to report every pair of matching parens,
|
|
/// braces, angle brackets, and square brackets in a given AST node, along with their ContextLoc.
|
|
class RangeWalker: protected ASTWalker {
|
|
protected:
|
|
SourceManager &SM;
|
|
|
|
public:
|
|
explicit RangeWalker(SourceManager &SM) : SM(SM) {}
|
|
|
|
/// Called for every range bounded by a pair of parens, braces, square
|
|
/// brackets, or angle brackets.
|
|
///
|
|
/// \returns true to continue walking.
|
|
virtual bool handleRange(SourceLoc L, SourceLoc R, SourceLoc ContextLoc) = 0;
|
|
|
|
/// Called for ranges that have a separate ContextLoc but no bounding tokens.
|
|
virtual void handleImplicitRange(SourceRange Range, SourceLoc ContextLoc) = 0;
|
|
|
|
private:
|
|
bool handleBraces(SourceLoc L, SourceLoc R, SourceLoc ContextLoc) {
|
|
L = getLocIfKind(SM, L, tok::l_brace);
|
|
R = getLocIfKind(SM, R, tok::r_brace);
|
|
return L.isInvalid() || handleRange(L, R, ContextLoc);
|
|
}
|
|
|
|
bool handleBraces(SourceRange Braces, SourceLoc ContextLoc) {
|
|
return handleBraces(Braces.Start, Braces.End, ContextLoc);
|
|
}
|
|
|
|
bool handleParens(SourceLoc L, SourceLoc R, SourceLoc ContextLoc) {
|
|
L = getLocIfKind(SM, L, tok::l_paren);
|
|
R = getLocIfKind(SM, R, tok::r_paren);
|
|
return L.isInvalid() || handleRange(L, R, ContextLoc);
|
|
}
|
|
|
|
bool handleSquares(SourceLoc L, SourceLoc R, SourceLoc ContextLoc) {
|
|
L = getLocIfKind(SM, L, tok::l_square);
|
|
R = getLocIfKind(SM, R, tok::r_square);
|
|
return L.isInvalid() || handleRange(L, R, ContextLoc);
|
|
}
|
|
|
|
bool handleAngles(SourceLoc L, SourceLoc R, SourceLoc ContextLoc) {
|
|
L = getLocIfTokenTextMatches(SM, L, "<");
|
|
R = getLocIfTokenTextMatches(SM, R, ">");
|
|
return L.isInvalid() || handleRange(L, R, ContextLoc);
|
|
}
|
|
|
|
bool handleBraceStmt(Stmt *S, SourceLoc ContextLoc) {
|
|
if (auto *BS = dyn_cast_or_null<BraceStmt>(S))
|
|
return handleBraces({BS->getLBraceLoc(), BS->getRBraceLoc()}, ContextLoc);
|
|
return true;
|
|
}
|
|
|
|
bool walkCustomAttributes(Decl *D) {
|
|
// CustomAttrs of non-param VarDecls are handled when this method is called
|
|
// on their containing PatternBindingDecls (below).
|
|
if (isa<VarDecl>(D) && !isa<ParamDecl>(D))
|
|
return true;
|
|
|
|
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
|
|
if (auto *SingleVar = PBD->getSingleVar()) {
|
|
D = SingleVar;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
for (auto *customAttr :
|
|
D->getParsedAttrs().getAttributes<CustomAttr, true>()) {
|
|
if (auto *Repr = customAttr->getTypeRepr()) {
|
|
if (!Repr->walk(*this))
|
|
return false;
|
|
}
|
|
if (auto *Args = customAttr->getArgs()) {
|
|
if (!Args->walk(*this))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MacroWalking getMacroWalkingBehavior() const override {
|
|
return MacroWalking::Arguments;
|
|
}
|
|
|
|
PreWalkAction walkToDeclPre(Decl *D) override {
|
|
if (!walkCustomAttributes(D))
|
|
return Action::SkipNode();
|
|
|
|
if (D->isImplicit())
|
|
return Action::Continue();
|
|
|
|
SourceLoc ContextLoc = D->getStartLoc();
|
|
|
|
if (auto *GC = D->getAsGenericContext()) {
|
|
// Asking for generic parameters on decls where they are computed, rather
|
|
// than explicitly defined will trigger an assertion when semantic queries
|
|
// and name lookup are disabled.
|
|
bool SafeToAskForGenerics = !isa<ExtensionDecl>(D) &&
|
|
!isa<ProtocolDecl>(D);
|
|
if (SafeToAskForGenerics) {
|
|
if (auto *GP = GC->getParsedGenericParams()) {
|
|
if (!handleAngles(GP->getLAngleLoc(), GP->getRAngleLoc(), ContextLoc))
|
|
return Action::Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto *NTD = dyn_cast<NominalTypeDecl>(D)) {
|
|
if (!handleBraces(NTD->getBraces(), ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
|
|
if (!handleBraces(ED->getBraces(), ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *VD = dyn_cast<VarDecl>(D)) {
|
|
if (!handleBraces(VD->getBracesRange(), VD->getNameLoc()))
|
|
return Action::Stop();
|
|
} else if (isa<AbstractFunctionDecl>(D) || isa<SubscriptDecl>(D)) {
|
|
if (isa<SubscriptDecl>(D)) {
|
|
if (!handleBraces(cast<SubscriptDecl>(D)->getBracesRange(), ContextLoc))
|
|
return Action::Stop();
|
|
}
|
|
auto *PL = cast<ValueDecl>(D)->getParameterList();
|
|
if (!handleParens(PL->getLParenLoc(), PL->getRParenLoc(), ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *PGD = dyn_cast<PrecedenceGroupDecl>(D)) {
|
|
SourceRange Braces(PGD->getLBraceLoc(), PGD->getRBraceLoc());
|
|
if (!handleBraces(Braces, ContextLoc))
|
|
return Action::Stop();
|
|
}
|
|
|
|
return Action::Continue();
|
|
}
|
|
|
|
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
|
|
if (S->isImplicit())
|
|
return Action::Continue(S);
|
|
|
|
if (auto *LCS = dyn_cast<LabeledConditionalStmt>(S)) {
|
|
for (auto &Elem: LCS->getCond()) {
|
|
if (Elem.getKind() == StmtConditionElement::CK_Availability) {
|
|
PoundAvailableInfo *PA = Elem.getAvailability();
|
|
if (!handleParens(PA->getLParenLoc(), PA->getRParenLoc(),
|
|
PA->getStartLoc()))
|
|
return Action::Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
SourceLoc ContextLoc = S->getStartLoc();
|
|
if (auto *BS = dyn_cast<BraceStmt>(S)) {
|
|
if (!handleBraceStmt(BS, ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *IS = dyn_cast<IfStmt>(S)) {
|
|
if (!handleBraceStmt(IS->getThenStmt(), IS->getIfLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *GS = dyn_cast<GuardStmt>(S)) {
|
|
if (!handleBraceStmt(GS->getBody(), GS->getGuardLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *FS = dyn_cast<ForEachStmt>(S)) {
|
|
if (!handleBraceStmt(FS->getBody(), FS->getForLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *SS = dyn_cast<SwitchStmt>(S)) {
|
|
SourceRange Braces(SS->getLBraceLoc(), SS->getRBraceLoc());
|
|
if (!handleBraces(Braces, SS->getSwitchLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *DS = dyn_cast<DoStmt>(S)) {
|
|
if (!handleBraceStmt(DS->getBody(), DS->getDoLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *DCS = dyn_cast<DoCatchStmt>(S)) {
|
|
if (!handleBraceStmt(DCS->getBody(), DCS->getDoLoc()))
|
|
return Action::Stop();
|
|
} else if (isa<CaseStmt>(S) &&
|
|
cast<CaseStmt>(S)->getParentKind() == CaseParentKind::DoCatch) {
|
|
auto CS = cast<CaseStmt>(S);
|
|
if (!handleBraceStmt(CS->getBody(), CS->getLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *RWS = dyn_cast<RepeatWhileStmt>(S)) {
|
|
if (!handleBraceStmt(RWS->getBody(), RWS->getRepeatLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *WS = dyn_cast<WhileStmt>(S)) {
|
|
if (!handleBraceStmt(WS->getBody(), WS->getWhileLoc()))
|
|
return Action::Stop();
|
|
} else if (isa<PoundAssertStmt>(S)) {
|
|
// TODO: add paren locations to PoundAssertStmt
|
|
}
|
|
|
|
return Action::Continue(S);
|
|
}
|
|
|
|
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
|
|
if (E->isImplicit())
|
|
return Action::Continue(E);
|
|
|
|
SourceLoc ContextLoc = E->getStartLoc();
|
|
if (auto *PE = dyn_cast<ParenExpr>(E)) {
|
|
SourceLoc L = getLocIfKind(SM, PE->getLParenLoc(),
|
|
{tok::l_paren, tok::l_square});
|
|
SourceLoc R = getLocIfKind(SM, PE->getRParenLoc(),
|
|
{tok::r_paren, tok::r_square});
|
|
if (L.isValid() && !handleRange(L, R, ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *TE = dyn_cast<TupleExpr>(E)) {
|
|
SourceLoc L = getLocIfKind(SM, TE->getLParenLoc(),
|
|
{tok::l_paren, tok::l_square});
|
|
SourceLoc R = getLocIfKind(SM, TE->getRParenLoc(),
|
|
{tok::r_paren, tok::r_square});
|
|
if (L.isValid() && !handleRange(L, R, ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *CE = dyn_cast<CollectionExpr>(E)) {
|
|
if (!handleSquares(CE->getLBracketLoc(), CE->getRBracketLoc(),
|
|
ContextLoc))
|
|
return Action::Stop();
|
|
} else if (auto *CE = dyn_cast<ClosureExpr>(E)) {
|
|
if (!handleBraceStmt(CE->getBody(), ContextLoc))
|
|
return Action::Stop();
|
|
SourceRange Capture = CE->getBracketRange();
|
|
if (!handleSquares(Capture.Start, Capture.End, Capture.Start))
|
|
return Action::Stop();
|
|
if (auto *PL = CE->getParameters()) {
|
|
if (!handleParens(PL->getLParenLoc(), PL->getRParenLoc(),
|
|
PL->getStartLoc()))
|
|
return Action::Stop();
|
|
}
|
|
} else if (auto *USE = dyn_cast<UnresolvedSpecializeExpr>(E)) {
|
|
SourceLoc ContextLoc = getContextLocForArgs(SM, E);
|
|
if (!handleAngles(USE->getLAngleLoc(), USE->getRAngleLoc(), ContextLoc))
|
|
return Action::Stop();
|
|
} else if (isa<CallExpr>(E) || isa<SubscriptExpr>(E)) {
|
|
SourceLoc ContextLoc = getContextLocForArgs(SM, E);
|
|
auto *Args = E->getArgs();
|
|
|
|
auto lParenLoc = Args->getLParenLoc();
|
|
auto rParenLoc = Args->getRParenLoc();
|
|
|
|
if (isa<SubscriptExpr>(E)) {
|
|
if (!handleSquares(lParenLoc, rParenLoc, ContextLoc))
|
|
return Action::Stop();
|
|
} else {
|
|
if (!handleParens(lParenLoc, rParenLoc, ContextLoc))
|
|
return Action::Stop();
|
|
}
|
|
|
|
if (Args->hasAnyTrailingClosures()) {
|
|
if (auto *unaryArg = Args->getUnaryExpr()) {
|
|
if (auto CE = findTrailingClosureFromArgument(unaryArg)) {
|
|
if (!handleBraceStmt(CE->getBody(), ContextLoc))
|
|
return Action::Stop();
|
|
}
|
|
} else {
|
|
handleImplicitRange(Args->getOriginalArgs()->getTrailingSourceRange(),
|
|
ContextLoc);
|
|
}
|
|
}
|
|
}
|
|
return Action::Continue(E);
|
|
}
|
|
|
|
PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
|
|
if (P->isImplicit())
|
|
return Action::Continue(P);
|
|
|
|
if (isa<TuplePattern>(P) || isa<ParenPattern>(P)) {
|
|
if (!handleParens(P->getStartLoc(), P->getEndLoc(), P->getStartLoc()))
|
|
return Action::Stop();
|
|
}
|
|
|
|
return Action::Continue(P);
|
|
}
|
|
|
|
PreWalkAction walkToTypeReprPre(TypeRepr *T) override {
|
|
if (auto *TT = dyn_cast<TupleTypeRepr>(T)) {
|
|
SourceRange Parens = TT->getParens();
|
|
if (!handleParens(Parens.Start, Parens.End, Parens.Start))
|
|
return Action::Stop();
|
|
} else if (isa<ArrayTypeRepr>(T) || isa<DictionaryTypeRepr>(T)) {
|
|
if (!handleSquares(T->getStartLoc(), T->getEndLoc(), T->getStartLoc()))
|
|
return Action::Stop();
|
|
} else if (auto *DRTR = dyn_cast<DeclRefTypeRepr>(T)) {
|
|
SourceLoc ContextLoc = DRTR->getNameLoc().getBaseNameLoc();
|
|
auto Brackets = DRTR->getAngleBrackets();
|
|
if (Brackets.isValid() &&
|
|
!handleAngles(Brackets.Start, Brackets.End, ContextLoc))
|
|
return Action::Stop();
|
|
}
|
|
return Action::Continue();
|
|
}
|
|
|
|
bool shouldWalkIntoGenericParams() override { return true; }
|
|
};
|
|
|
|
/// Indicates whether a range is an open or closed range.
|
|
enum class RangeKind {Closed, Open};
|
|
|
|
/// A helper class that determines whether a given node, or subrage of a node
|
|
/// should indent or not when it spans multiple lines.
|
|
class OutdentChecker: protected RangeWalker {
|
|
SourceRange CheckRange; ///< The source range to consider.
|
|
RangeKind CheckRangeKind; ///< Whether \c CheckRange is open or closed.
|
|
bool IsOutdenting = false; ///< Tracks whether a seen range prevents indenting.
|
|
llvm::DenseMap<SourceLoc, ContextOverride> LineStartToOverride;
|
|
|
|
explicit OutdentChecker(SourceManager &SM,
|
|
SourceRange CheckRange, RangeKind CheckRangeKind)
|
|
: RangeWalker(SM), CheckRange(CheckRange), CheckRangeKind(CheckRangeKind) {
|
|
assert(CheckRange.isValid());
|
|
}
|
|
|
|
void handleImplicitRange(SourceRange Range, SourceLoc ContextLoc) override {
|
|
assert(Range.isValid() && ContextLoc.isValid());
|
|
|
|
// Ignore ranges outside of the open/closed check range.
|
|
if (!isInCheckRange(Range.Start, Range.End))
|
|
return;
|
|
|
|
propagateContextLocs(ContextLoc, Range.Start, Range.End);
|
|
}
|
|
|
|
bool handleRange(SourceLoc L, SourceLoc R, SourceLoc ContextLoc) override {
|
|
assert(L.isValid() && ContextLoc.isValid());
|
|
|
|
// Ignore parens/braces/brackets outside of the open/closed check range.
|
|
if (!isInCheckRange(L, R))
|
|
return true;
|
|
|
|
// The CheckRange is made outdenting by any parens/braces/brackets with:
|
|
// 1) a ContextLoc starts on the same line as the start of CheckRange, and
|
|
// 2) either:
|
|
// a) an R token that starts its containing line, or
|
|
// b) an L token that isn't the ContextLoc and starts its containing line.
|
|
//
|
|
// E.g. for an open CheckRange covering the contents of an array literal
|
|
// with various line bresk positions:
|
|
//
|
|
// // This doesn't outdent because:
|
|
// [ // The array brackets are outside the open CheckRange so ignored.
|
|
// (1, (2, 3)), // both parens fail conditions 1 and 2.
|
|
// (4, (5, 6)) // both parens fail conditions 1 and 2.
|
|
// ]
|
|
//
|
|
// // This doesn't outdent because:
|
|
// [(1, (2, 3)), // both parens fail condition 2.
|
|
// ( // these parens fail condition 1.
|
|
// 4, (5, 6) // these parens fail conditions 1 and 2.
|
|
// )]
|
|
//
|
|
// This outdents because:
|
|
// [( // These parens meet conditions 1 and 2a.
|
|
// 1, (2, 3)
|
|
// ), (
|
|
// 4, (5, 6)
|
|
// )]
|
|
//
|
|
// This outdents because:
|
|
// [(1, ( // The inner parens meet conditions 1 and 2a.
|
|
// 2, 3
|
|
// ), (4, (5, 6)]
|
|
//
|
|
// This outdents because:
|
|
// [(1, (2, 3), ( // The inner parens meet conditions 1 and 2a.
|
|
// 4, (5, 6)
|
|
// )]
|
|
//
|
|
// For a closed CheckRange covering the variable declaration below:
|
|
//
|
|
// This doesn't outdent because:
|
|
// var x = foo(1) { // The parens and braces fail condition 2
|
|
// return 42 }
|
|
//
|
|
// This outdents because:
|
|
// var x = foo(1) { // These braces meet conditions 1 and 2a.
|
|
// return 42
|
|
// }
|
|
//
|
|
// This outdents because:
|
|
// var x = foo(1)
|
|
// { // These braces meet conditions 1 and 2b (their ContextLoc is 'foo').
|
|
// return 42
|
|
// }
|
|
//
|
|
// And for a closed CheckRange covering the call expression below:
|
|
//
|
|
// This doesn't outdent because:
|
|
// foo(1, // These parens fail condition 2.
|
|
// 2, 3) { return 42 } // These braces fail condition 1 and 2.
|
|
//
|
|
// This outdents because:
|
|
// foo(1, 2, 3)
|
|
// { // These braces meet conditions 1 and 2b (their ContextLoc is 'foo').
|
|
// return 42
|
|
// }
|
|
|
|
// The above conditions are not sufficient to handle cases like the below,
|
|
// which we would like to be considered outdenting:
|
|
// foo(a: 1,
|
|
// b: 2)[x: bar { // These braces fail condition 1.
|
|
// return 42
|
|
// }]
|
|
// To handle them, we propagate the ContextLoc of each parent range down to
|
|
// any child ranges that start on the same line as the parent. The braces
|
|
// above then 'inherit' the ContextLoc of their parent brackets ('foo'), and
|
|
// pass condition 1.
|
|
ContextLoc = propagateContextLocs(ContextLoc, L, R);
|
|
|
|
// Ignore parens/braces/brackets that fail Condition 1.
|
|
if (!isOnSameLine(SM, ContextLoc, CheckRange.Start))
|
|
return true;
|
|
|
|
// Ignore parens/braces/brackets that can't meet Condition 2.
|
|
if (R.isValid() && isOnSameLine(SM, ContextLoc, R))
|
|
return true;
|
|
|
|
// Check condition 2b.
|
|
if (ContextLoc != L && isFirstTokenOnLine(SM, L)) {
|
|
IsOutdenting = true;
|
|
} else if (R.isValid()) {
|
|
// Check condition 2a.
|
|
SourceLoc LineStart = Lexer::getLocForStartOfLine(SM, R);
|
|
Token First = Lexer::getTokenAtLocation(SM, LineStart,
|
|
CommentRetentionMode::None);
|
|
IsOutdenting |= First.getLoc() == R;
|
|
}
|
|
|
|
// We only need to continue checking if it's not already outdenting.
|
|
return !IsOutdenting;
|
|
}
|
|
|
|
SourceLoc propagateContextLocs(SourceLoc ContextLoc, SourceLoc L, SourceLoc R) {
|
|
bool HasSeparateContext = !isOnSameLine(SM, L, ContextLoc);
|
|
|
|
// Update ContextLoc for the currently active override on its line.
|
|
ContextOverride &Upstream = getOverrideForLineContaining(ContextLoc);
|
|
IndentContext::ContextKind Kind = IndentContext::LineStart;
|
|
Upstream.applyIfNeeded(SM, ContextLoc, Kind);
|
|
|
|
// If the original ContextLoc and L were on the same line, there's no need
|
|
// to propagate anything. Child ranges later on the same line will pick up
|
|
// whatever override we picked up above anyway, and if there wasn't
|
|
// one, their normal ContextLoc should already be correct.
|
|
if (!HasSeparateContext)
|
|
return ContextLoc;
|
|
|
|
// Set an override to propagate the context loc onto the line of L.
|
|
ContextOverride &Downstream = getOverrideForLineContaining(L);
|
|
ContextLoc = Downstream.propagateContext(SM, ContextLoc,
|
|
Kind, L, R);
|
|
return ContextLoc;
|
|
}
|
|
|
|
bool isInCheckRange(SourceLoc L, SourceLoc R) const {
|
|
switch (CheckRangeKind) {
|
|
case RangeKind::Open:
|
|
return SM.isBeforeInBuffer(CheckRange.Start, L) &&
|
|
(R.isInvalid() || SM.isBeforeInBuffer(R, CheckRange.End));
|
|
case RangeKind::Closed:
|
|
return !SM.isBeforeInBuffer(L, CheckRange.Start) &&
|
|
(R.isInvalid() || !SM.isBeforeInBuffer(CheckRange.End, R));
|
|
}
|
|
llvm_unreachable("invalid range kind");
|
|
}
|
|
|
|
public:
|
|
/// Checks if a source range shouldn't indent when it crosses multiple lines.
|
|
///
|
|
/// \param SM
|
|
/// The SourceManager managing the given source range.
|
|
/// \param Range
|
|
/// The range to check.
|
|
/// \param WalkableParent
|
|
/// A parent AST node that when walked covers all relevant nodes in the
|
|
/// given source range.
|
|
/// \param RangeKind
|
|
/// Whether the given range to check is closed (the default) or open.
|
|
template <typename T>
|
|
static bool hasOutdent(SourceManager &SM, SourceRange Range, T *WalkableParent,
|
|
RangeKind RangeKind = RangeKind::Closed) {
|
|
assert(Range.isValid());
|
|
if (isOnSameLine(SM, Range.Start, Range.End))
|
|
return false;
|
|
OutdentChecker Checker(SM, Range, RangeKind);
|
|
WalkableParent->walk(Checker);
|
|
return Checker.IsOutdenting;
|
|
}
|
|
|
|
/// Checks if an AST node shouldn't indent when it crosses multiple lines.
|
|
///
|
|
/// \param SM
|
|
/// The SourceManager managing the given source range.
|
|
/// \param WalkableNode
|
|
/// The AST node to check.
|
|
/// \param RangeKind
|
|
/// Whether to check the source range of \c WalkableNode as a closed (the
|
|
/// default) or open range.
|
|
template <typename T>
|
|
static bool hasOutdent(SourceManager &SM, T *WalkableNode,
|
|
RangeKind RangeKind = RangeKind::Closed) {
|
|
return hasOutdent(SM, WalkableNode->getSourceRange(), WalkableNode,
|
|
RangeKind);
|
|
}
|
|
|
|
private:
|
|
ContextOverride &getOverrideForLineContaining(SourceLoc Loc) {
|
|
SourceLoc LineStart = Lexer::getLocForStartOfLine(SM, Loc);
|
|
auto Ret = LineStartToOverride.insert({LineStart, ContextOverride()});
|
|
return Ret.first->getSecond();
|
|
}
|
|
};
|
|
|
|
|
|
/// Information about an indent target that immediately follows a node being walked by
|
|
/// a \c FormatWalker instance, or optionally, that follows a trailing comma
|
|
/// after such a node.
|
|
class TrailingInfo {
|
|
std::optional<Token> TrailingToken;
|
|
TrailingInfo(std::optional<Token> TrailingToken)
|
|
: TrailingToken(TrailingToken) {}
|
|
|
|
public:
|
|
/// Whether the trailing target is on an empty line.
|
|
bool isEmpty() const { return !TrailingToken.has_value(); }
|
|
|
|
/// Whether the trailing target is a token with one of the given kinds.
|
|
bool hasKind(ArrayRef<tok> Kinds) const {
|
|
if (TrailingToken) {
|
|
tok Kind = TrailingToken->getKind();
|
|
return std::find(Kinds.begin(), Kinds.end(), Kind) != Kinds.end();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Checks if the target location immediately follows the provided \p EndLoc,
|
|
/// optionally allowing for a single comma in between.
|
|
static std::optional<TrailingInfo> find(SourceManager &SM, SourceLoc EndLoc,
|
|
SourceLoc TargetLoc,
|
|
bool LookPastTrailingComma = true) {
|
|
// If the target is before the end of the end token, it's not trailing.
|
|
SourceLoc TokenEndLoc = Lexer::getLocForEndOfToken(SM, EndLoc);
|
|
if (SM.isBeforeInBuffer(TargetLoc, TokenEndLoc))
|
|
return std::nullopt;
|
|
|
|
// If there is no next token, the target directly trails the end token.
|
|
auto Next = getTokenAfter(SM, EndLoc, /*SkipComments=*/false);
|
|
if (!Next)
|
|
return TrailingInfo{std::nullopt};
|
|
|
|
// If the target is before or at the next token's locations, it directly
|
|
// trails the end token.
|
|
SourceLoc NextTokLoc = Next->getLoc();
|
|
if (NextTokLoc == TargetLoc)
|
|
return TrailingInfo {Next};
|
|
if (SM.isBeforeInBuffer(TargetLoc, Next->getLoc()))
|
|
return TrailingInfo{std::nullopt};
|
|
|
|
// The target does not directly trail the end token. If we should look past
|
|
// trailing commas, do so.
|
|
if (LookPastTrailingComma && Next->getKind() == tok::comma)
|
|
return find(SM, Next->getLoc(), TargetLoc, false);
|
|
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
|
|
|
|
/// A helper class for aligning list elements and their bounding tokens.
|
|
class ListAligner {
|
|
SourceManager &SM;
|
|
SourceLoc TargetLoc; ///< The indent location.
|
|
SourceLoc ContextLoc; ///< The owning indent context's location.
|
|
SourceLoc IntroducerLoc; ///< The opening token before the first list element.
|
|
SourceLoc CloseLoc; ///< The token that closes the list (optional).
|
|
bool CloseRequired; ///< Whether a closing token is expected.
|
|
bool AllowsTrailingSeparator; ///< Whether a final trailing comma is legal.
|
|
bool ElementExpected; ///<Whether at least one element is expected.
|
|
|
|
SourceLoc AlignLoc;
|
|
SourceLoc LastEndLoc;
|
|
bool HasOutdent = false;
|
|
bool BreakAlignment = false;
|
|
|
|
public:
|
|
|
|
/// Don't column-align if any element starts on the same line as IntroducerLoc
|
|
/// but ends on a later line.
|
|
bool BreakAlignmentIfSpanning = false;
|
|
|
|
/// Constructs a new \c ListAligner for a list bounded by separate opening and
|
|
/// closing tokens, e.g. tuples, array literals, parameter lists, etc.
|
|
///
|
|
/// \param SM
|
|
/// The source manager to use.
|
|
/// \param TargetLoc
|
|
/// The indent target location.
|
|
/// \param ContextLoc
|
|
/// The location list items should indent relative to.
|
|
/// \param L
|
|
/// The location of the token before the first item in the list, e.g. '(',
|
|
/// or \c case.
|
|
/// \param R
|
|
/// The location of the closing token of the list, e.g. ')', if present.
|
|
/// \param AllowsTrailingSeparator
|
|
/// Whether a trailing comma is legal, or indicates an incomplete list.
|
|
ListAligner(SourceManager &SM, SourceLoc TargetLoc, SourceLoc ContextLoc,
|
|
SourceLoc L, SourceLoc R, bool AllowsTrailingSeparator = false)
|
|
: SM(SM), TargetLoc(TargetLoc), ContextLoc(ContextLoc),
|
|
IntroducerLoc(L), CloseLoc(R), CloseRequired(true),
|
|
AllowsTrailingSeparator(AllowsTrailingSeparator), ElementExpected(true) {
|
|
assert(ContextLoc.isValid() && IntroducerLoc.isValid());
|
|
}
|
|
|
|
/// Constructs a new \c ListAligner for a list with only an introducing token,
|
|
/// e.g. enum case element lists, guard/if condition pattern lists, and
|
|
/// pattern binding declaration lists.
|
|
///
|
|
/// \param SM
|
|
/// The source manager to use.
|
|
/// \param TargetLoc
|
|
/// The indent target location.
|
|
/// \param ContextLoc
|
|
/// The location list items should indent relative to.
|
|
/// \param IntroducerLoc
|
|
/// The location of the token before the first item in the list, e.g. '(',
|
|
/// or \c case.
|
|
/// \param ElementExpected
|
|
/// Whether at least one item is expected. Currently not true of 'catch'
|
|
/// pattern lists only.
|
|
ListAligner(SourceManager &SM, SourceLoc TargetLoc, SourceLoc ContextLoc,
|
|
SourceLoc IntroducerLoc, bool ElementExpected = true)
|
|
: SM(SM), TargetLoc(TargetLoc), ContextLoc(ContextLoc),
|
|
IntroducerLoc(IntroducerLoc), CloseLoc(SourceLoc()), CloseRequired(false),
|
|
AllowsTrailingSeparator(false), ElementExpected(ElementExpected) {
|
|
assert(ContextLoc.isValid() && IntroducerLoc.isValid());
|
|
}
|
|
|
|
/// Update the alignment for a list element.
|
|
///
|
|
/// \param Start
|
|
/// The start location of the element.
|
|
/// \param End
|
|
/// The end location of the element
|
|
/// \param WalkableParent
|
|
/// An AST node that is, or contains the element, and is walkable.
|
|
template <typename T>
|
|
void updateAlignment(SourceLoc Start, SourceLoc End, T *WalkableParent) {
|
|
updateAlignment(SourceRange(Start, End), WalkableParent);
|
|
}
|
|
|
|
/// Update the alignment for a list element.
|
|
///
|
|
/// \param Range
|
|
/// The source range of the element.
|
|
/// \param WalkableParent
|
|
/// An AST node that is, or contains the element, and is walkable.
|
|
template <typename T>
|
|
void updateAlignment(SourceRange Range, T *WalkableParent) {
|
|
assert(Range.isValid());
|
|
LastEndLoc = Range.End;
|
|
|
|
if (isOnSameLine(SM, IntroducerLoc, Range.Start)) {
|
|
HasOutdent |= OutdentChecker::hasOutdent(SM, Range, WalkableParent);
|
|
if (BreakAlignmentIfSpanning)
|
|
BreakAlignment |= !isOnSameLine(SM, IntroducerLoc, Range.End);
|
|
}
|
|
|
|
if (HasOutdent || !SM.isBeforeInBuffer(Range.Start, TargetLoc))
|
|
return;
|
|
if (AlignLoc.isValid()) {
|
|
if (isOnSameLine(SM, Range.Start, AlignLoc) ||
|
|
!isFirstTokenOnLine(SM, Range.Start))
|
|
return;
|
|
AlignLoc = getLocForContentStartOnSameLine(SM, Range.Start);
|
|
} else if (isOnSameLine(SM, IntroducerLoc, Range.Start)) {
|
|
AlignLoc = Range.Start;
|
|
}
|
|
}
|
|
|
|
/// Returns the list's IndentContext (if applicable to the TargetLoc) and sets
|
|
/// an exact alignment override if needed.
|
|
///
|
|
/// \note This should only be called after calling \c updateAlignment on every
|
|
/// element range.
|
|
/// \param Override
|
|
/// A ContextOverride object to set
|
|
std::optional<IndentContext>
|
|
getContextAndSetAlignment(ContextOverride &Override) {
|
|
// If the target is before the introducer token, or on it and it is also
|
|
// the context loc, the list shouldn't be an indent context.
|
|
if (SM.isBeforeInBuffer(TargetLoc, IntroducerLoc))
|
|
return std::nullopt;
|
|
if (TargetLoc == IntroducerLoc && ContextLoc == IntroducerLoc)
|
|
return std::nullopt;
|
|
|
|
// Get the end location of the (possibly incomplete) list.
|
|
bool HasTrailingComma = false;
|
|
SourceLoc End = getEffectiveEndLoc(HasTrailingComma);
|
|
assert(End.isValid());
|
|
|
|
// If the target is past the end of the list we may still be an indent
|
|
// context.
|
|
bool TargetIsTrailing = false;
|
|
if (!SM.isBeforeInBuffer(TargetLoc, Lexer::getLocForEndOfToken(SM, End))) {
|
|
// If the close token is present, we're not.
|
|
if (CloseLoc.isValid())
|
|
return std::nullopt;
|
|
|
|
// If there's no trailing comma and a close token isn't required, we're
|
|
// only a context if there no elements yet but at least one is expected,
|
|
// e.g. in an if condition list.
|
|
if (!HasTrailingComma && !CloseRequired &&
|
|
(LastEndLoc.isValid() || !ElementExpected))
|
|
return std::nullopt;
|
|
|
|
// If the target isn't immediately trailing the end loc, we're not.
|
|
if (!TrailingInfo::find(SM, End, TargetLoc,
|
|
/*LookPastTrailingComma=*/!HasTrailingComma)) {
|
|
return std::nullopt;
|
|
}
|
|
TargetIsTrailing = true;
|
|
}
|
|
|
|
bool ShouldIndent = shouldIndent(HasTrailingComma, TargetIsTrailing);
|
|
if (ShouldIndent && !BreakAlignment && AlignLoc.isValid()) {
|
|
setAlignmentIfNeeded(Override);
|
|
return IndentContext {AlignLoc, false, IndentContext::Exact};
|
|
}
|
|
return IndentContext {ContextLoc, ShouldIndent};
|
|
}
|
|
|
|
/// Sets an exact alignment override for child indent contexts, if needed.
|
|
///
|
|
/// This should be called before returning an \c IndentContext for a subrange
|
|
/// of the list.
|
|
void setAlignmentIfNeeded(ContextOverride &Override) {
|
|
if (HasOutdent || BreakAlignment || AlignLoc.isInvalid())
|
|
return;
|
|
Override.setExact(SM, AlignLoc);
|
|
}
|
|
|
|
private:
|
|
bool shouldIndent(bool HasTrailingComma, bool TargetIsTrailing) const {
|
|
if (HasOutdent || TargetLoc == IntroducerLoc)
|
|
return false;
|
|
if (TargetLoc == CloseLoc)
|
|
return !AllowsTrailingSeparator && HasTrailingComma;
|
|
if (TargetIsTrailing) {
|
|
return CloseLoc.isInvalid() &&
|
|
((LastEndLoc.isInvalid() && ElementExpected) ||
|
|
HasTrailingComma);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SourceLoc getEffectiveEndLoc(bool &HasTrailingComma) const {
|
|
if (LastEndLoc.isInvalid())
|
|
return CloseLoc.isValid() ? CloseLoc : IntroducerLoc;
|
|
|
|
SourceLoc EffectiveEnd = LastEndLoc;
|
|
if (locIsKind(SM, LastEndLoc, tok::comma)) {
|
|
HasTrailingComma = true;
|
|
} else {
|
|
std::optional<Token> AfterLast = getTokenAfter(SM, LastEndLoc);
|
|
if (AfterLast && AfterLast->is(tok::comma)) {
|
|
HasTrailingComma = true;
|
|
EffectiveEnd = AfterLast->getLoc();
|
|
}
|
|
}
|
|
|
|
if (CloseLoc.isValid())
|
|
return CloseLoc;
|
|
return EffectiveEnd;
|
|
}
|
|
};
|
|
|
|
|
|
/// Walks an AST Node to determine the \c FormatContext of the target indent location.
|
|
///
|
|
/// It only walks into nodes whose source range overlaps, or immediately
|
|
/// precedes the target indent location.
|
|
class FormatWalker : public ASTWalker {
|
|
SourceFile &SF;
|
|
SourceManager &SM;
|
|
CodeFormatOptions &FmtOptions;
|
|
ArrayRef<Token> TokenList;
|
|
|
|
SourceLoc TargetLocation;
|
|
SourceLoc TargetLineLoc;
|
|
llvm::SmallPtrSet<void *, 16> NodesToSkip;
|
|
ArrayRef<Token>::iterator CurrentTokIt;
|
|
|
|
/// The innermost indent context of the target location.
|
|
std::optional<IndentContext> InnermostCtx;
|
|
/// A conditionally applicable indent context override.
|
|
ContextOverride CtxOverride;
|
|
/// Whether the target location appears within a doc comment block.
|
|
bool InDocCommentBlock = false;
|
|
/// Whether the target location appears within a line comment.
|
|
bool InCommentLine = false;
|
|
|
|
/// The range of the string literal the target is inside of (if any, invalid otherwise).
|
|
CharSourceRange StringLiteralRange;
|
|
|
|
public:
|
|
explicit FormatWalker(SourceFile &SF, SourceManager &SM, CodeFormatOptions &Options)
|
|
: SF(SF), SM(SM), FmtOptions(Options), TokenList(SF.getAllTokens()),
|
|
CurrentTokIt(TokenList.begin()) {}
|
|
|
|
/// Compute the \c FormatContext of the given source location.
|
|
///
|
|
/// \note The given location should point to the content start of its line.
|
|
FormatContext walkToLocation(SourceLoc Loc) {
|
|
InnermostCtx = std::nullopt;
|
|
CtxOverride.clear();
|
|
TargetLocation = Loc;
|
|
TargetLineLoc = Lexer::getLocForStartOfLine(SM, TargetLocation);
|
|
InDocCommentBlock = InCommentLine = false;
|
|
StringLiteralRange = CharSourceRange();
|
|
NodesToSkip.clear();
|
|
CurrentTokIt = TokenList.begin();
|
|
|
|
SF.walk(*this);
|
|
scanTokensUntil(SourceLoc());
|
|
|
|
if (InnermostCtx)
|
|
CtxOverride.applyIfNeeded(SM, *InnermostCtx);
|
|
|
|
if (StringLiteralRange.isValid()) {
|
|
assert(!InDocCommentBlock && !InCommentLine &&
|
|
"Target is in both a string and comment");
|
|
InnermostCtx = indentWithinStringLiteral();
|
|
} else {
|
|
assert(!InDocCommentBlock || !InCommentLine &&
|
|
"Target is in both a doc comment block and comment line");
|
|
}
|
|
|
|
return FormatContext(SM, InnermostCtx, InDocCommentBlock,
|
|
InCommentLine);
|
|
}
|
|
|
|
private:
|
|
std::optional<IndentContext> indentWithinStringLiteral() {
|
|
assert(StringLiteralRange.isValid() && "Target is not within a string literal");
|
|
|
|
// This isn't ideal since if the user types """""" and then an enter
|
|
// inside after, we won't indent the end quotes. But indenting the end
|
|
// quotes could lead to an error in the rest of the string, so best to
|
|
// avoid it entirely for now.
|
|
if (isOnSameLine(SM, TargetLineLoc, StringLiteralRange.getEnd()))
|
|
return IndentContext {TargetLocation, false, IndentContext::Exact};
|
|
|
|
// If there's contents before the end quotes then it's likely the quotes
|
|
// are actually the start quotes of the next string in the file. Pretend
|
|
// they don't exist so their indent doesn't affect the indenting.
|
|
SourceLoc EndLineContentLoc =
|
|
getLocForContentStartOnSameLine(SM, StringLiteralRange.getEnd());
|
|
bool HaveEndQuotes = CharSourceRange(SM, EndLineContentLoc,
|
|
StringLiteralRange.getEnd())
|
|
.str() == "\"\"\"";
|
|
|
|
if (!HaveEndQuotes) {
|
|
// Indent to the same indentation level as the first non-empty line
|
|
// before the target. If that line is the start line then either use
|
|
// the same indentation of the start quotes if they are on their own
|
|
// line, or an extra indentation otherwise.
|
|
//
|
|
// This will indent lines with content on it as well, which should be
|
|
// fine since it is quite unlikely anyone would format a range that
|
|
// includes an unterminated string.
|
|
|
|
SourceLoc AlignLoc = TargetLineLoc;
|
|
while (SM.isBeforeInBuffer(StringLiteralRange.getStart(), AlignLoc)) {
|
|
AlignLoc = Lexer::getLocForStartOfLine(SM, AlignLoc.getAdvancedLoc(-1));
|
|
if (!isLineAtLocEmpty(SM, AlignLoc)) {
|
|
AlignLoc = getLocForContentStartOnSameLine(SM, AlignLoc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isOnSameLine(SM, AlignLoc, StringLiteralRange.getStart())) {
|
|
SourceLoc StartLineContentLoc =
|
|
getLocForContentStartOnSameLine(SM, StringLiteralRange.getStart());
|
|
bool StartLineOnlyQuotes = CharSourceRange(SM, StartLineContentLoc,
|
|
StringLiteralRange.getEnd())
|
|
.str().starts_with(StringRef("\"\"\""));
|
|
if (!StartLineOnlyQuotes)
|
|
return IndentContext {StringLiteralRange.getStart(), true};
|
|
|
|
AlignLoc = StringLiteralRange.getStart();
|
|
}
|
|
|
|
return IndentContext {AlignLoc, false, IndentContext::Exact};
|
|
}
|
|
|
|
// If there are end quotes, only enforce a minimum indentation. We don't
|
|
// want to add any other indentation since that could add unintended
|
|
// whitespace to existing strings. Could change this if the full range
|
|
// was passed rather than a single line - in that case we *would* indent
|
|
// if the range was a single empty line.
|
|
|
|
CharSourceRange TargetIndentRange =
|
|
CharSourceRange(SM, Lexer::getLocForStartOfLine(SM, TargetLocation),
|
|
TargetLocation);
|
|
CharSourceRange EndIndentRange =
|
|
CharSourceRange(SM, Lexer::getLocForStartOfLine(SM, EndLineContentLoc),
|
|
EndLineContentLoc);
|
|
|
|
size_t TargetIndent =
|
|
calcVisibleWhitespacePrefix(TargetIndentRange.str(), FmtOptions);
|
|
size_t EndIndent =
|
|
calcVisibleWhitespacePrefix(EndIndentRange.str(), FmtOptions);
|
|
if (TargetIndent >= EndIndent)
|
|
return IndentContext {TargetLocation, false, IndentContext::Exact};
|
|
|
|
return IndentContext {EndLineContentLoc, false, IndentContext::Exact};
|
|
}
|
|
|
|
#pragma mark ASTWalker overrides and helpers
|
|
|
|
bool walkCustomAttributes(Decl *D) {
|
|
// CustomAttrs of non-param VarDecls are handled when this method is called
|
|
// on their containing PatternBindingDecls (below).
|
|
if (isa<VarDecl>(D) && !isa<ParamDecl>(D))
|
|
return true;
|
|
|
|
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
|
|
if (auto *SingleVar = PBD->getSingleVar()) {
|
|
D = SingleVar;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
for (auto *customAttr :
|
|
D->getParsedAttrs().getAttributes<CustomAttr, true>()) {
|
|
if (auto *Repr = customAttr->getTypeRepr()) {
|
|
if (!Repr->walk(*this))
|
|
return false;
|
|
}
|
|
if (auto *Args = customAttr->getArgs()) {
|
|
if (!Args->walk(*this))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MacroWalking getMacroWalkingBehavior() const override {
|
|
return MacroWalking::Arguments;
|
|
}
|
|
|
|
PreWalkAction walkToDeclPre(Decl *D) override {
|
|
if (!walkCustomAttributes(D))
|
|
return Action::SkipNode();
|
|
|
|
auto Action = HandlePre(D, D->isImplicit());
|
|
if (Action.shouldGenerateIndentContext()) {
|
|
if (auto IndentCtx = getIndentContextFrom(D, Action.Trailing))
|
|
InnermostCtx = IndentCtx;
|
|
}
|
|
|
|
// Walk accessors via their pattern binding decl. They aren't walked via
|
|
// their VarDecls due to their non-overlapping range, so they'd be skipped
|
|
// otherwise.
|
|
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
|
|
if (Action.shouldVisitChildren()) {
|
|
for (auto I: range(PBD->getNumPatternEntries())) {
|
|
auto *P = PBD->getPattern(I);
|
|
if (!P)
|
|
continue;
|
|
bool Cancelled = false;
|
|
P->forEachVariable([&](VarDecl *VD) {
|
|
if (Cancelled || VD->getBracesRange().isInvalid())
|
|
return;
|
|
for (auto *AD : VD->getAllAccessors()) {
|
|
if (AD->walk(*this)) {
|
|
Cancelled = true;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: We ought to be able to use Action::VisitChildrenIf here, but we'd
|
|
// need to ensure the AST is walked in source order (currently not the case
|
|
// for things like postfix operators).
|
|
return Action::VisitNodeIf(Action.shouldVisitChildren());
|
|
}
|
|
|
|
PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
|
|
auto Action = HandlePre(S, S->isImplicit());
|
|
if (Action.shouldGenerateIndentContext()) {
|
|
if (auto IndentCtx = getIndentContextFrom(S, Action.Trailing))
|
|
InnermostCtx = IndentCtx;
|
|
}
|
|
// FIXME: We ought to be able to use Action::VisitChildrenIf here, but we'd
|
|
// need to ensure the AST is walked in source order (currently not the case
|
|
// for things like postfix operators).
|
|
return Action::VisitNodeIf(Action.shouldVisitChildren(), S);
|
|
}
|
|
|
|
PreWalkResult<ArgumentList *>
|
|
walkToArgumentListPre(ArgumentList *Args) override {
|
|
SourceLoc ContextLoc;
|
|
if (auto *E = Parent.getAsExpr()) {
|
|
// Retrieve the context loc from the parent call. Note that we don't
|
|
// perform this if we're walking the ArgumentList directly for e.g an
|
|
// interpolated string literal.
|
|
if (E->getArgs() == Args)
|
|
ContextLoc = getContextLocForArgs(SM, E);
|
|
} else if (auto *D = Parent.getAsDecl()) {
|
|
if (auto *MED = dyn_cast<MacroExpansionDecl>(D)) {
|
|
if (MED->getArgs() == Args) {
|
|
ContextLoc = MED->getStartLoc();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto Action = HandlePre(Args, Args->isImplicit());
|
|
if (Action.shouldGenerateIndentContext()) {
|
|
if (auto Ctx = getIndentContextFrom(Args, Action.Trailing, ContextLoc))
|
|
InnermostCtx = Ctx;
|
|
}
|
|
// FIXME: We ought to be able to use Action::VisitChildrenIf here, but we'd
|
|
// need to ensure the AST is walked in source order (currently not the case
|
|
// for things like postfix operators).
|
|
return Action::VisitNodeIf(Action.shouldVisitChildren(), Args);
|
|
}
|
|
|
|
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
|
|
if (E->getKind() == ExprKind::StringLiteral &&
|
|
SM.isBeforeInBuffer(E->getStartLoc(), TargetLocation) &&
|
|
SM.isBeforeInBuffer(TargetLocation,
|
|
Lexer::getLocForEndOfToken(SM, E->getEndLoc()))) {
|
|
StringLiteralRange = Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange());
|
|
}
|
|
|
|
// Create a default indent context for all top-level expressions
|
|
if (isStatementListItem()) {
|
|
SourceRange Range = E->getSourceRange();
|
|
if (Range.isValid() && isTargetContext(Range)) {
|
|
InnermostCtx = IndentContext {
|
|
E->getStartLoc(),
|
|
!OutdentChecker::hasOutdent(SM, E)
|
|
};
|
|
}
|
|
}
|
|
|
|
auto Action = HandlePre(E, E->isImplicit());
|
|
if (Action.shouldGenerateIndentContext()) {
|
|
if (auto IndentCtx = getIndentContextFrom(E, Action.Trailing))
|
|
InnermostCtx = IndentCtx;
|
|
}
|
|
|
|
// Don't visit the child expressions of interpolated strings directly -
|
|
// visit only the argument of each appendInterpolation call instead, and
|
|
// set StringLiteralRange if needed for each segment.
|
|
if (auto *ISL = dyn_cast<InterpolatedStringLiteralExpr>(E)) {
|
|
if (Action.shouldVisitChildren()) {
|
|
llvm::SaveAndRestore<ASTWalker::ParentTy>(Parent, ISL);
|
|
SourceLoc PrevStringStart = ISL->getStartLoc();
|
|
ISL->forEachSegment(SF.getASTContext(),
|
|
[&](bool IsInterpolation, CallExpr *CE) {
|
|
if (IsInterpolation) {
|
|
// Handle the preceeding string segment.
|
|
CharSourceRange StringRange(SM, PrevStringStart, CE->getStartLoc());
|
|
if (StringRange.contains(TargetLocation)) {
|
|
StringLiteralRange =
|
|
Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange());
|
|
return;
|
|
}
|
|
// Walk into the interpolation segment.
|
|
CE->getArgs()->walk(*this);
|
|
} else {
|
|
PrevStringStart = CE->getStartLoc();
|
|
}
|
|
});
|
|
// Handle the trailing string segment.
|
|
SourceLoc End = Lexer::getLocForEndOfToken(SM, ISL->getStartLoc());
|
|
CharSourceRange StringRange(SM, PrevStringStart, End);
|
|
if (StringRange.contains(TargetLocation))
|
|
StringLiteralRange =
|
|
Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange());
|
|
|
|
return Action::SkipNode(E);
|
|
}
|
|
}
|
|
|
|
// FIXME: We ought to be able to use Action::VisitChildrenIf here, but we'd
|
|
// need to ensure the AST is walked in source order (currently not the case
|
|
// for things like postfix operators).
|
|
return Action::VisitNodeIf(Action.shouldVisitChildren(), E);
|
|
}
|
|
|
|
PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
|
|
auto Action = HandlePre(P, P->isImplicit());
|
|
if (Action.shouldGenerateIndentContext()) {
|
|
if (auto IndentCtx = getIndentContextFrom(P, Action.Trailing))
|
|
InnermostCtx = IndentCtx;
|
|
}
|
|
// FIXME: We ought to be able to use Action::VisitChildrenIf here, but we'd
|
|
// need to ensure the AST is walked in source order (currently not the case
|
|
// for things like postfix operators).
|
|
return Action::VisitNodeIf(Action.shouldVisitChildren(), P);
|
|
}
|
|
|
|
PreWalkAction walkToTypeReprPre(TypeRepr *T) override {
|
|
auto Action = HandlePre(T, false);
|
|
if (Action.shouldGenerateIndentContext()) {
|
|
if (auto IndentCtx = getIndentContextFrom(T, Action.Trailing))
|
|
InnermostCtx = IndentCtx;
|
|
}
|
|
// FIXME: We ought to be able to use Action::VisitChildrenIf here, but we'd
|
|
// need to ensure the AST is walked in source order (currently not the case
|
|
// for things like postfix operators).
|
|
return Action::VisitNodeIf(Action.shouldVisitChildren());
|
|
}
|
|
|
|
PostWalkAction walkToDeclPost(Decl *D) override {
|
|
return HandlePost(D).Action;
|
|
}
|
|
PostWalkAction walkToTypeReprPost(TypeRepr *T) override {
|
|
return HandlePost(T).Action;
|
|
}
|
|
|
|
PostWalkResult<Stmt *> walkToStmtPost(Stmt *S) override {
|
|
return HandlePost(S);
|
|
}
|
|
|
|
PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
|
|
return HandlePost(E);
|
|
}
|
|
|
|
PostWalkResult<Pattern *> walkToPatternPost(Pattern *P) override {
|
|
return HandlePost(P);
|
|
}
|
|
|
|
bool shouldWalkIntoGenericParams() override { return true; }
|
|
|
|
|
|
#pragma mark Visitation helpers
|
|
|
|
struct VisitAction {
|
|
enum : unsigned { Skip, VisitChildren, GetContext } action;
|
|
std::optional<TrailingInfo> Trailing;
|
|
|
|
bool shouldVisitChildren() const { return action >= VisitChildren; }
|
|
bool shouldGenerateIndentContext() const { return action >= GetContext; }
|
|
};
|
|
|
|
template <class T>
|
|
VisitAction HandlePre(T* Node, bool IsImplicit) {
|
|
SourceLoc Start = Node->getStartLoc(), End = Node->getEndLoc();
|
|
|
|
if (Start.isInvalid())
|
|
return {VisitAction::VisitChildren, std::nullopt};
|
|
|
|
std::optional<TrailingInfo> Trailing =
|
|
TrailingInfo::find(SM, End, TargetLocation);
|
|
scanTokensUntil(Start);
|
|
|
|
if (!isTargetContext(Start, End) && !Trailing)
|
|
return {VisitAction::Skip, std::nullopt};
|
|
if (!NodesToSkip.count(Node) && !IsImplicit)
|
|
return {VisitAction::GetContext, Trailing};
|
|
return {VisitAction::VisitChildren, Trailing};
|
|
}
|
|
|
|
template <typename T>
|
|
PostWalkResult<T *> HandlePost(T *Node) {
|
|
if (SM.isBeforeInBuffer(TargetLocation, Node->getStartLoc()))
|
|
return Action::Stop();
|
|
|
|
return Action::Continue(Node);
|
|
}
|
|
|
|
void scanTokensUntil(SourceLoc Loc) {
|
|
if (InDocCommentBlock || InCommentLine || StringLiteralRange.isValid())
|
|
return;
|
|
for (auto Invalid = Loc.isInvalid(); CurrentTokIt != TokenList.end() &&
|
|
(Invalid || SM.isBeforeInBuffer(CurrentTokIt->getLoc(), Loc));
|
|
++CurrentTokIt) {
|
|
if (CurrentTokIt->getKind() == tok::comment) {
|
|
CharSourceRange CommentRange = CurrentTokIt->getRange();
|
|
SourceLoc StartLineLoc = Lexer::getLocForStartOfLine(
|
|
SM, CommentRange.getStart());
|
|
|
|
SourceLoc EndLineLoc = Lexer::getLocForStartOfLine(
|
|
SM, CommentRange.getEnd());
|
|
auto TokenStr = CurrentTokIt->getRange().str();
|
|
InDocCommentBlock |= SM.isBeforeInBuffer(StartLineLoc, TargetLineLoc) && !SM.isBeforeInBuffer(EndLineLoc, TargetLineLoc) &&
|
|
TokenStr.starts_with("/*");
|
|
InCommentLine |= StartLineLoc == TargetLineLoc &&
|
|
TokenStr.starts_with("//");
|
|
} else if (CurrentTokIt->getKind() == tok::unknown &&
|
|
CurrentTokIt->getRange().str().starts_with("\"\"\"")) {
|
|
StringLiteralRange = CurrentTokIt->getRange();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// When visiting an expression, returns true if it's a stement level
|
|
/// expression.
|
|
bool isStatementListItem() {
|
|
if (auto *S = Parent.getAsStmt()) {
|
|
if (auto *RS = dyn_cast<ReturnStmt>(S))
|
|
return RS->isImplicit();
|
|
return isa<BraceStmt>(S);
|
|
}
|
|
if (auto *E = Parent.getAsExpr()) {
|
|
return isa<ClosureExpr>(E);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Checks whether the given range is an indent context of the target location.
|
|
///
|
|
/// \return \c Start < \c TargetLocation <= \c End.
|
|
bool isTargetContext(SourceLoc Start, SourceLoc End) const {
|
|
assert(Start.isValid());
|
|
// Start < Target <= End
|
|
return SM.isBeforeInBuffer(Start, TargetLocation) &&
|
|
(End.isInvalid() ||
|
|
SM.isBeforeInBuffer(TargetLocation,
|
|
Lexer::getLocForEndOfToken(SM, End)));
|
|
}
|
|
|
|
/// Checks whether the given range is an indent context of the target location.
|
|
///
|
|
/// \return \c Range.Start < \c TargetLocation <= \c Range.End.
|
|
bool isTargetContext(SourceRange Range) const {
|
|
return isTargetContext(Range.Start, Range.End);
|
|
}
|
|
|
|
/// Checks whether the given range overlaps the target location.
|
|
///
|
|
/// \return \c Start <= \c TargetLocation <= \c End
|
|
bool overlapsTarget(SourceLoc Start, SourceLoc End) const {
|
|
assert(Start.isValid());
|
|
return !SM.isBeforeInBuffer(TargetLocation, Start) &&
|
|
(End.isInvalid() ||
|
|
SM.isBeforeInBuffer(TargetLocation,
|
|
Lexer::getLocForEndOfToken(SM, End)));
|
|
}
|
|
|
|
/// Checks whether the given range overlaps the target location.
|
|
///
|
|
/// \return \c Range.Start <= \c TargetLocation <= \c Range.End
|
|
bool overlapsTarget(SourceRange Range) const {
|
|
assert(Range.isValid());
|
|
return overlapsTarget(Range.Start, Range.End);
|
|
}
|
|
|
|
/// Checks whether the given range contains the target location.
|
|
///
|
|
/// \return \c Start < \c TargetLocation < \c End
|
|
bool containsTarget(SourceLoc Start, SourceLoc End) const {
|
|
assert(Start.isValid());
|
|
return SM.isBeforeInBuffer(Start, TargetLocation) &&
|
|
(End.isInvalid() || SM.isBeforeInBuffer(TargetLocation, End));
|
|
}
|
|
|
|
/// Checks whether the given range contains the target location.
|
|
///
|
|
/// \return \c Range.Start < \c TargetLocation < \c Range.End
|
|
bool containsTarget(SourceRange Range) const {
|
|
assert(Range.isValid());
|
|
return containsTarget(Range.Start, Range.End);
|
|
}
|
|
|
|
#pragma mark Declaration indent contexts
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(Decl *D, std::optional<TrailingInfo> TrailingTarget) {
|
|
|
|
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(D)) {
|
|
SourceLoc ContextLoc = AFD->getStartLoc();
|
|
// If this is a getter without a 'get' loc, the context loc is the start
|
|
// of its storage.
|
|
if (auto *AD = dyn_cast<AccessorDecl>(AFD)) {
|
|
if (AD->isGetter() && AD->getAccessorKeywordLoc().isInvalid()) {
|
|
auto *ASD = AD->getStorage();
|
|
if (auto *VD = dyn_cast_or_null<VarDecl>(ASD)) {
|
|
ContextLoc = VD->getStartLoc();
|
|
} else if (auto *SD = dyn_cast_or_null<SubscriptDecl>(ASD)) {
|
|
ContextLoc = SD->getStartLoc();
|
|
}
|
|
}
|
|
}
|
|
if (auto Ctx = getIndentContextFrom(AFD->getBody(), ContextLoc))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(AFD->getParameters(), ContextLoc))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(AFD->getParsedGenericParams(), ContextLoc, D))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(AFD->getTrailingWhereClause(), ContextLoc, D))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
if (auto *NTD = dyn_cast<NominalTypeDecl>(D)) {
|
|
SourceLoc ContextLoc = NTD->getStartLoc();
|
|
|
|
if (auto Ctx = getIndentContextFromInherits(NTD->getInherited(), ContextLoc))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFromBraces(NTD->getBraces(), ContextLoc, NTD))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(NTD->getParsedGenericParams(), ContextLoc, D))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(NTD->getTrailingWhereClause(), ContextLoc, D))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
|
|
SourceLoc ContextLoc = ED->getStartLoc();
|
|
|
|
if (auto Ctx = getIndentContextFromInherits(ED->getInherited(), ContextLoc))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFromBraces(ED->getBraces(), ContextLoc, ED))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(ED->getTrailingWhereClause(), ContextLoc, D))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
if (auto *ECD = dyn_cast<EnumCaseDecl>(D)) {
|
|
SourceLoc CaseLoc = ECD->getLoc();
|
|
ListAligner Aligner(SM, TargetLocation, CaseLoc, CaseLoc);
|
|
for (auto *Elem: ECD->getElements()) {
|
|
if (Elem->isImplicit())
|
|
continue;
|
|
SourceRange ElemRange = Elem->getSourceRange();
|
|
Aligner.updateAlignment(ElemRange, Elem);
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *EED = dyn_cast<EnumElementDecl>(D)) {
|
|
SourceLoc ContextLoc = EED->getStartLoc();
|
|
if (auto Ctx = getIndentContextFrom(EED->getParameterList()))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
return IndentContext {
|
|
ContextLoc,
|
|
!OutdentChecker::hasOutdent(SM, EED)
|
|
};
|
|
}
|
|
|
|
if (auto *SD = dyn_cast<SubscriptDecl>(D)) {
|
|
SourceLoc ContextLoc = SD->getStartLoc();
|
|
|
|
if (auto Ctx = getIndentContextFromBraces(SD->getBracesRange(), ContextLoc, SD))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(SD->getIndices(), ContextLoc))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(SD->getParsedGenericParams(), ContextLoc, D))
|
|
return Ctx;
|
|
if (auto Ctx = getIndentContextFrom(SD->getTrailingWhereClause(), ContextLoc, D))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
if (auto *PGD = dyn_cast<PrecedenceGroupDecl>(D)) {
|
|
SourceLoc ContextLoc = PGD->getStartLoc();
|
|
SourceLoc L = PGD->getLBraceLoc(), R = PGD->getRBraceLoc();
|
|
|
|
if (auto Ctx = getIndentContextFromBraces(L, R, ContextLoc, PGD))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {PGD->getStartLoc(), false};
|
|
}
|
|
|
|
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
|
|
SourceLoc ContextLoc = PBD->getStartLoc(), IntroducerLoc = PBD->getLoc();
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, IntroducerLoc);
|
|
|
|
// Don't column align PBD entries if any entry spans from the same line as
|
|
// the IntroducerLoc (var/let) to another line. E.g.
|
|
//
|
|
// let foo = someItem
|
|
// .getValue(), // Column-alignment looks ok here, but...
|
|
// bar = otherItem
|
|
// .getValue()
|
|
//
|
|
// getAThing()
|
|
// .andDoStuffWithIt()
|
|
// let foo = someItem
|
|
// .getValue() // looks over-indented here, which is more common...
|
|
// getOtherThing()
|
|
// .andDoStuffWithIt()
|
|
//
|
|
// getAThing()
|
|
// .andDoStuffWithIt()
|
|
// let foo = someItem
|
|
// .getValue() // so break column alignment in this case...
|
|
// doOtherThing()
|
|
//
|
|
// let foo = someItem.getValue(),
|
|
// bar = otherItem.getValue() // but not in this case.
|
|
//
|
|
// Using this rule, rather than handling single and multi-entry PBDs
|
|
// differently, ensures that the as-typed-out indentation matches the
|
|
// re-indented indentation for multi-entry PBDs.
|
|
Aligner.BreakAlignmentIfSpanning = true;
|
|
|
|
for (auto I: range(PBD->getNumPatternEntries())) {
|
|
SourceRange EntryRange = PBD->getEqualLoc(I);
|
|
VarDecl *SingleVar = nullptr;
|
|
|
|
if (auto *E = PBD->getOriginalInit(I))
|
|
widenOrSet(EntryRange, E->getSourceRange());
|
|
if (auto *P = PBD->getPattern(I)) {
|
|
widenOrSet(EntryRange, P->getSourceRange());
|
|
if ((SingleVar = P->getSingleVar()))
|
|
widenOrSet(EntryRange, SingleVar->getBracesRange());
|
|
}
|
|
assert(EntryRange.isValid());
|
|
Aligner.updateAlignment(EntryRange, PBD);
|
|
|
|
// If the var has explicit accessors, the braces are an indent context.
|
|
if (SingleVar && hasExplicitAccessors(SingleVar)) {
|
|
SourceRange Braces = SingleVar->getBracesRange();
|
|
if (auto Ctx = getIndentContextFromBraces(Braces, EntryRange.Start, PBD)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return Ctx;
|
|
}
|
|
}
|
|
|
|
// The pattern entry as whole is also an indent context.
|
|
if (isTargetContext(EntryRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
EntryRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, EntryRange, PBD)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
// std::nullopt of the below declarations can claim trailing targets.
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
if (auto *TAD = dyn_cast<TypeAliasDecl>(D)) {
|
|
SourceLoc ContextLoc = TAD->getStartLoc();
|
|
|
|
if (auto Ctx = getIndentContextFrom(TAD->getParsedGenericParams(), ContextLoc,
|
|
D)) {
|
|
return Ctx;
|
|
}
|
|
if (auto Ctx = getIndentContextFrom(TAD->getTrailingWhereClause(), ContextLoc,
|
|
D)) {
|
|
return Ctx;
|
|
}
|
|
|
|
return IndentContext {ContextLoc, !OutdentChecker::hasOutdent(SM, D)};
|
|
}
|
|
|
|
if (auto *ATD = dyn_cast<AssociatedTypeDecl>(D)) {
|
|
SourceLoc ContextLoc = ATD->getStartLoc();
|
|
|
|
if (auto Ctx = getIndentContextFromInherits(ATD->getInherited(),
|
|
ContextLoc)) {
|
|
return Ctx;
|
|
}
|
|
if (auto Ctx = getIndentContextFrom(ATD->getTrailingWhereClause(),
|
|
ContextLoc, D)) {
|
|
return Ctx;
|
|
}
|
|
|
|
return IndentContext {ContextLoc, !OutdentChecker::hasOutdent(SM, D)};
|
|
}
|
|
|
|
switch (D->getKind()) {
|
|
case DeclKind::InfixOperator:
|
|
case DeclKind::PostfixOperator:
|
|
case DeclKind::PrefixOperator:
|
|
case DeclKind::Import:
|
|
case DeclKind::Param:
|
|
return IndentContext {
|
|
D->getStartLoc(),
|
|
!OutdentChecker::hasOutdent(SM, D)
|
|
};
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFromWhereClause(ArrayRef<RequirementRepr> Requirements,
|
|
SourceRange Range, SourceLoc ContextLoc,
|
|
Decl *WalkableParent) {
|
|
if (Range.isInvalid())
|
|
return std::nullopt;
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, Range.Start);
|
|
for (auto &Req: Requirements) {
|
|
SourceRange ReqRange = Req.getSourceRange();
|
|
if (ReqRange.isInvalid())
|
|
continue;
|
|
Aligner.updateAlignment(ReqRange, WalkableParent);
|
|
if (isTargetContext(ReqRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ReqRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ReqRange, WalkableParent)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
std::optional<IndentContext> getIndentContextFrom(TrailingWhereClause *TWC,
|
|
SourceLoc ContextLoc,
|
|
Decl *WalkableParent) {
|
|
if (!TWC)
|
|
return std::nullopt;
|
|
return getIndentContextFromWhereClause(TWC->getRequirements(),
|
|
TWC->getSourceRange(),
|
|
ContextLoc, WalkableParent);
|
|
}
|
|
|
|
std::optional<IndentContext> getIndentContextFrom(GenericParamList *GP,
|
|
SourceLoc ContextLoc,
|
|
Decl *WalkableParent) {
|
|
if (!GP)
|
|
return std::nullopt;
|
|
|
|
SourceLoc L = GP->getLAngleLoc();
|
|
SourceLoc R = getLocIfTokenTextMatches(SM, GP->getRAngleLoc(), ">");
|
|
|
|
if (L.isValid() && overlapsTarget(L, R)) {
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto *P: GP->getParams()) {
|
|
SourceRange ParamRange = P->getSourceRange();
|
|
Aligner.updateAlignment(ParamRange, WalkableParent);
|
|
if (isTargetContext(ParamRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ParamRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, P)
|
|
};
|
|
}
|
|
}
|
|
if (auto Ctx = Aligner.getContextAndSetAlignment(CtxOverride))
|
|
return Ctx;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(ParameterList *PL, SourceLoc ContextLoc = SourceLoc()) {
|
|
if (!PL)
|
|
return std::nullopt;
|
|
|
|
SourceRange Range = PL->getSourceRange();
|
|
if (Range.isInvalid() || locIsKind(SM, Range.Start, tok::l_brace))
|
|
return std::nullopt;
|
|
|
|
SourceLoc L = getLocIfKind(SM, PL->getLParenLoc(), tok::l_paren);
|
|
SourceLoc R = getLocIfKind(SM, PL->getRParenLoc(), tok::r_paren);
|
|
if (ContextLoc.isInvalid())
|
|
ContextLoc = Range.Start;
|
|
|
|
if (L.isValid()) {
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto *PD: *PL)
|
|
Aligner.updateAlignment(PD->getSourceRange(), PD);
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
// There are no parens at this point, so if there are no parameters either,
|
|
// this shouldn't be a context (it's an implicit parameter list).
|
|
if (!PL->size())
|
|
return std::nullopt;
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, Range.Start);
|
|
for (auto *PD: *PL)
|
|
Aligner.updateAlignment(PD->getSourceRange(), PD);
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<IndentContext>
|
|
getIndentContextFromBraces(SourceLoc Open, SourceLoc Close,
|
|
SourceLoc ContextLoc, T *WalkableParent) {
|
|
SourceLoc L = getLocIfKind(SM, Open, tok::l_brace);
|
|
SourceLoc R = getLocIfKind(SM, Close, tok::r_brace);
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
return IndentContext {
|
|
ContextLoc,
|
|
containsTarget(L, R) &&
|
|
!OutdentChecker::hasOutdent(SM, SourceRange(Open, Close), WalkableParent,
|
|
RangeKind::Open)
|
|
};
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<IndentContext> getIndentContextFromBraces(SourceRange Braces,
|
|
SourceLoc ContextLoc,
|
|
T *WalkableParent) {
|
|
return getIndentContextFromBraces(Braces.Start, Braces.End, ContextLoc,
|
|
WalkableParent);
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFromInherits(InheritedTypes Inherits, SourceLoc ContextLoc) {
|
|
if (Inherits.empty())
|
|
return std::nullopt;
|
|
|
|
SourceLoc StartLoc = Inherits.getStartLoc();
|
|
if (StartLoc.isInvalid())
|
|
return std::nullopt;
|
|
|
|
// FIXME: Add the colon location to the AST.
|
|
auto ColonLoc = getLastTokenOfKindInOpenRange(SM, tok::colon, ContextLoc,
|
|
StartLoc);
|
|
assert(ColonLoc.has_value() && "inherits list without leading colon?");
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, ColonLoc->getLoc());
|
|
for (auto TL : Inherits.getEntries())
|
|
Aligner.updateAlignment(TL.getSourceRange(), TL.getTypeRepr());
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
#pragma mark Statement indent contexts
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(Stmt *S, std::optional<TrailingInfo> TrailingTarget) {
|
|
|
|
if (auto *BS = dyn_cast<BraceStmt>(S))
|
|
return getIndentContextFrom(BS);
|
|
|
|
if (auto *SS = dyn_cast<SwitchStmt>(S)) {
|
|
SourceLoc ContextLoc = SS->getSwitchLoc();
|
|
if (!SM.isBeforeInBuffer(ContextLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto *E = SS->getSubjectExpr()) {
|
|
SourceRange Range = E->getSourceRange();
|
|
widenOrSet(Range, ContextLoc);
|
|
if (isTargetContext(Range)) {
|
|
return IndentContext {
|
|
ContextLoc,
|
|
!OutdentChecker::hasOutdent(SM, Range, E)
|
|
};
|
|
}
|
|
}
|
|
SourceLoc L = SS->getLBraceLoc(), R = SS->getRBraceLoc();
|
|
if (FmtOptions.IndentSwitchCase) {
|
|
if (auto Ctx = getIndentContextFromBraces(L, R, ContextLoc, SS))
|
|
return Ctx;
|
|
}
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
auto *CS = dyn_cast<CaseStmt>(S);
|
|
if (CS && CS->getParentKind() == CaseParentKind::Switch) {
|
|
SourceLoc CaseLoc = CS->getLoc();
|
|
if (!SM.isBeforeInBuffer(CaseLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
SourceRange LabelItemsRange = CS->getLabelItemsRange();
|
|
SourceLoc ColonLoc = getLocIfKind(SM, LabelItemsRange.End, tok::colon);
|
|
|
|
if (auto Ctx = getIndentContextFromCaseItems(CS, true))
|
|
return Ctx;
|
|
|
|
if (ColonLoc.isValid() && isTargetContext(ColonLoc, SourceLoc()) &&
|
|
(!TrailingTarget || TrailingTarget->isEmpty())) {
|
|
SourceRange ColonToEnd = SourceRange(ColonLoc, CS->getEndLoc());
|
|
return IndentContext {
|
|
CaseLoc,
|
|
!OutdentChecker::hasOutdent(SM, ColonToEnd, CS)
|
|
};
|
|
}
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {CaseLoc, false};
|
|
}
|
|
|
|
if (auto *DS = dyn_cast<DoStmt>(S)) {
|
|
if (!SM.isBeforeInBuffer(DS->getDoLoc(), TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto *BS = dyn_cast<BraceStmt>(DS->getBody())) {
|
|
if (auto Ctx = getIndentContextFrom(BS, DS->getStartLoc()))
|
|
return Ctx;
|
|
}
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {DS->getStartLoc(), false};
|
|
}
|
|
|
|
if (CS && CS->getParentKind() == CaseParentKind::DoCatch) {
|
|
SourceLoc CatchLoc = CS->getLoc();
|
|
SourceLoc L;
|
|
|
|
auto *BS = dyn_cast<BraceStmt>(CS->getBody());
|
|
if (BS) L = getLocIfKind(SM, BS->getLBraceLoc(), tok::l_brace);
|
|
|
|
if (auto Ctx = getIndentContextFromCaseItems(CS, false))
|
|
return Ctx;
|
|
|
|
if (auto Ctx = getIndentContextFrom(BS, CS->getStartLoc()))
|
|
return Ctx;
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {CatchLoc, false};
|
|
}
|
|
|
|
if (auto *IS = dyn_cast<IfStmt>(S)) {
|
|
SourceLoc ContextLoc = IS->getIfLoc();
|
|
if (!SM.isBeforeInBuffer(ContextLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto Ctx = getIndentContextFrom(IS->getCond(), ContextLoc, IS))
|
|
return Ctx;
|
|
if (auto *BS = dyn_cast_or_null<BraceStmt>(IS->getThenStmt())) {
|
|
if (auto Ctx = getIndentContextFrom(BS, IS->getStartLoc()))
|
|
return Ctx;
|
|
}
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
if (auto *GS = dyn_cast<GuardStmt>(S)) {
|
|
SourceLoc ContextLoc = GS->getGuardLoc();
|
|
if (!SM.isBeforeInBuffer(ContextLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto Ctx = getIndentContextFrom(GS->getCond(), ContextLoc, GS))
|
|
return Ctx;
|
|
if (auto *BS = dyn_cast_or_null<BraceStmt>(GS->getBody())) {
|
|
if (auto Ctx = getIndentContextFrom(BS, GS->getStartLoc()))
|
|
return Ctx;
|
|
}
|
|
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {GS->getGuardLoc(), false};
|
|
}
|
|
|
|
if (auto *RWS = dyn_cast<RepeatWhileStmt>(S)) {
|
|
SourceLoc ContextLoc = RWS->getRepeatLoc();
|
|
if (!SM.isBeforeInBuffer(ContextLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto *E = RWS->getCond()) {
|
|
if (overlapsTarget(E->getSourceRange()))
|
|
return IndentContext {RWS->getRepeatLoc(), true};
|
|
}
|
|
|
|
if (auto *BS = dyn_cast_or_null<BraceStmt>(RWS->getBody())) {
|
|
if (auto Ctx = getIndentContextFrom(BS, ContextLoc))
|
|
return Ctx;
|
|
}
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {RWS->getRepeatLoc(), false};
|
|
}
|
|
|
|
if (auto *WS = dyn_cast<WhileStmt>(S)) {
|
|
SourceLoc ContextLoc = WS->getWhileLoc();
|
|
if (!SM.isBeforeInBuffer(ContextLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto Ctx = getIndentContextFrom(WS->getCond(), ContextLoc, WS))
|
|
return Ctx;
|
|
|
|
if (auto *BS = dyn_cast_or_null<BraceStmt>(WS->getBody())) {
|
|
if (auto Ctx = getIndentContextFrom(BS, ContextLoc))
|
|
return Ctx;
|
|
}
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
if (auto *FS = dyn_cast<ForEachStmt>(S)) {
|
|
SourceLoc ContextLoc = FS->getStartLoc();
|
|
SourceLoc ForLoc = FS->getForLoc();
|
|
if (!SM.isBeforeInBuffer(ForLoc, TargetLocation))
|
|
return std::nullopt;
|
|
|
|
if (auto *P = FS->getPattern()) {
|
|
SourceRange Range = P->getSourceRange();
|
|
if (Range.isValid() && overlapsTarget(Range))
|
|
return IndentContext {ForLoc, !OutdentChecker::hasOutdent(SM, P)};
|
|
}
|
|
if (auto *E = FS->getParsedSequence()) {
|
|
SourceRange Range = FS->getInLoc();
|
|
widenOrSet(Range, E->getSourceRange());
|
|
if (Range.isValid() && isTargetContext(Range)) {
|
|
return IndentContext {
|
|
Range.Start,
|
|
!OutdentChecker::hasOutdent(SM, E)
|
|
};
|
|
}
|
|
}
|
|
if (auto *WE = FS->getWhere()) {
|
|
SourceLoc WhereLoc = FS->getWhereLoc();
|
|
SourceRange Range = WE->getSourceRange();
|
|
if (Range.isValid() && overlapsTarget(Range))
|
|
return IndentContext {WhereLoc, !OutdentChecker::hasOutdent(SM, WE)};
|
|
}
|
|
if (auto *BS = dyn_cast_or_null<BraceStmt>(FS->getBody())) {
|
|
if (auto Ctx = getIndentContextFrom(BS, FS->getStartLoc()))
|
|
return Ctx;
|
|
}
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
return IndentContext {ContextLoc, false};
|
|
}
|
|
|
|
// None of the below statements ever claim trailing targets.
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
if (auto *RS = dyn_cast<ReturnStmt>(S)) {
|
|
SourceLoc ContextLoc = RS->getReturnLoc();
|
|
SourceRange Range = RS->getSourceRange();
|
|
Expr *Result = RS->getResult();
|
|
return IndentContext {
|
|
ContextLoc,
|
|
Result && !OutdentChecker::hasOutdent(SM, Range, Result)
|
|
};
|
|
}
|
|
|
|
if (auto *DCS = dyn_cast<DoCatchStmt>(S)) {
|
|
if (!SM.isBeforeInBuffer(DCS->getDoLoc(), TargetLocation))
|
|
return std::nullopt;
|
|
if (auto *BS = dyn_cast<BraceStmt>(DCS->getBody())) {
|
|
if (auto Ctx = getIndentContextFrom(BS))
|
|
return Ctx;
|
|
}
|
|
return IndentContext {DCS->getStartLoc(), false};
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(BraceStmt *BS, SourceLoc ContextLoc = SourceLoc()) {
|
|
if (!BS)
|
|
return std::nullopt;
|
|
|
|
SourceLoc L = getLocIfKind(SM, BS->getLBraceLoc(), tok::l_brace);
|
|
SourceLoc R = getLocIfKind(SM, BS->getRBraceLoc(), tok::r_brace);
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
if (ContextLoc.isInvalid()) {
|
|
ContextLoc = L;
|
|
} else {
|
|
NodesToSkip.insert(static_cast<Stmt*>(BS));
|
|
}
|
|
bool shouldIndent = containsTarget(L, R) &&
|
|
!OutdentChecker::hasOutdent(SM, BS, RangeKind::Open);
|
|
return IndentContext {ContextLoc, shouldIndent};
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<IndentContext> getIndentContextFrom(PoundAvailableInfo *A,
|
|
T *WalkableParent) {
|
|
SourceLoc ContextLoc = A->getStartLoc();
|
|
SourceLoc L = A->getLParenLoc();
|
|
SourceLoc R = getLocIfKind(SM, A->getRParenLoc(), tok::r_paren);
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto *Spec: A->getQueries()) {
|
|
SourceRange Range = Spec->getSourceRange();
|
|
if (Range.isValid()) {
|
|
Aligner.updateAlignment(Range, WalkableParent);
|
|
if (isTargetContext(Range)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {Range.Start, true};
|
|
}
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(const StmtCondition &Condition, SourceLoc ContextLoc,
|
|
T *WalkableParent) {
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, ContextLoc);
|
|
for (auto &Elem: Condition) {
|
|
// Skip implicit condition created when there are no explicit ones.
|
|
Expr *BoolExpr = Elem.getBooleanOrNull();
|
|
if (BoolExpr && BoolExpr->isImplicit())
|
|
continue;
|
|
|
|
SourceRange ElemRange = Elem.getSourceRange();
|
|
Aligner.updateAlignment(ElemRange, WalkableParent);
|
|
|
|
if (Elem.getKind() == StmtConditionElement::CK_Availability) {
|
|
if (auto Ctx = getIndentContextFrom(Elem.getAvailability(),
|
|
WalkableParent)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return Ctx;
|
|
}
|
|
}
|
|
if (ElemRange.isValid() && isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, WalkableParent)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
SourceRange getConditionRange(const StmtCondition &Condition) {
|
|
if (Condition.empty())
|
|
return SourceRange();
|
|
|
|
SourceRange Bounds = SourceRange(Condition.front().getStartLoc(),
|
|
Condition.back().getEndLoc());
|
|
if (auto Next = getTokenAfter(SM, Bounds.End)) {
|
|
if (Next->getKind() == tok::comma)
|
|
Bounds.widen(Next->getLoc());
|
|
}
|
|
return Bounds;
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFromCaseItems(CaseStmt *CS, bool ElementExpected) {
|
|
SourceLoc IntroducerLoc = CS->getLoc();
|
|
ListAligner Aligner(SM, TargetLocation, IntroducerLoc, IntroducerLoc,
|
|
ElementExpected);
|
|
for (auto &Elem: CS->getCaseLabelItems()) {
|
|
// Skip the implicit 'error' pattern for empty catch CaseStmts.
|
|
if ((!Elem.getPattern() || Elem.getPattern()->isImplicit()) &&
|
|
Elem.getWhereLoc().isInvalid())
|
|
continue;
|
|
|
|
SourceRange ElemRange = Elem.getSourceRange();
|
|
Aligner.updateAlignment(ElemRange, CS);
|
|
if (isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, CS)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
#pragma mark Expression indent contexts
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(Expr *E, std::optional<TrailingInfo> TrailingTarget) {
|
|
|
|
// All handled expressions may claim a trailing target.
|
|
|
|
if (auto *TE = dyn_cast<TupleExpr>(E))
|
|
return getIndentContextFrom(TE);
|
|
|
|
if (auto *PE = dyn_cast<ParenExpr>(E))
|
|
return getIndentContextFrom(PE);
|
|
|
|
if (auto *DE = dyn_cast<DictionaryExpr>(E)) {
|
|
SourceLoc L = DE->getLBracketLoc();
|
|
SourceLoc R = getLocIfKind(SM, DE->getRBracketLoc(), tok::r_square);
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
ListAligner Aligner(SM, TargetLocation, L, L, R, true);
|
|
for (Expr *Elem: DE->getElements()) {
|
|
auto *TE = dyn_cast<TupleExpr>(Elem);
|
|
Aligner.updateAlignment(TE->getSourceRange(), TE);
|
|
if (auto Ctx = getIndentContextFromDictionaryElem(TE)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return Ctx;
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *AE = dyn_cast<ArrayExpr>(E)) {
|
|
SourceLoc L = AE->getLBracketLoc();
|
|
SourceLoc R = getLocIfKind(SM, AE->getRBracketLoc(), tok::r_square);
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
ListAligner Aligner(SM, TargetLocation, L, L, R, true);
|
|
for (auto *Elem: AE->getElements()) {
|
|
SourceRange ElemRange = Elem->getSourceRange();
|
|
Aligner.updateAlignment(ElemRange, Elem);
|
|
if (isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, Elem)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *USE = dyn_cast<UnresolvedSpecializeExpr>(E)) {
|
|
SourceLoc L = USE->getLAngleLoc();
|
|
SourceLoc R = getLocIfTokenTextMatches(SM, USE->getRAngleLoc(), ">");
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
SourceLoc ContextLoc = getContextLocForArgs(SM, USE);
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto *T : USE->getUnresolvedParams()) {
|
|
Aligner.updateAlignment(T->getSourceRange(), T);
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *CLE = dyn_cast<CaptureListExpr>(E))
|
|
return getIndentContextFrom(CLE);
|
|
|
|
if (auto *CE = dyn_cast<ClosureExpr>(E))
|
|
return getIndentContextFrom(CE);
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(CaptureListExpr *CL,
|
|
SourceLoc ContextLoc = SourceLoc()) {
|
|
AbstractClosureExpr *CE = CL->getClosureBody();
|
|
BraceStmt *BS = CE->getBody();
|
|
if (!BS)
|
|
return std::nullopt;
|
|
|
|
if (ContextLoc.isValid()) {
|
|
NodesToSkip.insert(static_cast<Expr*>(CL));
|
|
} else {
|
|
NodesToSkip.insert(static_cast<Expr*>(CE));
|
|
}
|
|
|
|
return getIndentContextFrom(CE, ContextLoc, CL);
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(AbstractClosureExpr *ACE,
|
|
SourceLoc ContextLoc = SourceLoc(),
|
|
CaptureListExpr *ParentCapture = nullptr) {
|
|
// Explicit capture lists should always have an explicit ClosureExpr as
|
|
// their subexpression.
|
|
auto CE = dyn_cast<ClosureExpr>(ACE);
|
|
if (!CE) {
|
|
return std::nullopt;
|
|
}
|
|
BraceStmt *BS = CE->getBody();
|
|
if (!BS)
|
|
return std::nullopt;
|
|
NodesToSkip.insert(static_cast<Stmt*>(BS));
|
|
|
|
SourceLoc L = BS->getLBraceLoc();
|
|
SourceLoc R = getLocIfKind(SM, BS->getRBraceLoc(), tok::r_brace);
|
|
|
|
if (ContextLoc.isValid()) {
|
|
NodesToSkip.insert(static_cast<Expr*>(CE));
|
|
if (isTargetContext(L, R))
|
|
ContextLoc = CtxOverride.propagateContext(SM, ContextLoc,
|
|
IndentContext::LineStart,
|
|
L, R);
|
|
}
|
|
|
|
// Handle the capture list.
|
|
SourceRange CL = CE->getBracketRange();
|
|
if (CL.isValid()) {
|
|
SourceLoc L = CL.Start;
|
|
SourceLoc R = getLocIfKind(SM, CL.End, tok::r_square);
|
|
if (isTargetContext(L, R)) {
|
|
ContextLoc = L;
|
|
if (!ParentCapture) // empty capture list
|
|
return IndentContext {ContextLoc, containsTarget(L, R)};
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto &Entry: ParentCapture->getCaptureList()) {
|
|
auto *PBD = Entry.PBD;
|
|
NodesToSkip.insert(PBD);
|
|
SourceRange Range = PBD->getSourceRangeIncludingAttrs();
|
|
Aligner.updateAlignment(Range, PBD);
|
|
|
|
if (isTargetContext(Range)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
Range.Start,
|
|
!OutdentChecker::hasOutdent(SM, Range, PBD)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
}
|
|
|
|
// Handle parameter list
|
|
if (auto Ctx = getIndentContextFrom(CE->getParameters()))
|
|
return Ctx;
|
|
|
|
// Handle outer braces.
|
|
if (L.isInvalid() || !isTargetContext(L, R))
|
|
return std::nullopt;
|
|
|
|
if (ContextLoc.isInvalid())
|
|
ContextLoc = L;
|
|
Expr *WalkableParent = CE;
|
|
if (ParentCapture)
|
|
WalkableParent = ParentCapture;
|
|
|
|
auto InLoc = CE->getInLoc();
|
|
if (InLoc.isValid()) {
|
|
if (containsTarget(InLoc, R)) {
|
|
SourceRange InToEnd = SourceRange(InLoc, BS->getEndLoc());
|
|
return IndentContext {
|
|
ContextLoc,
|
|
!OutdentChecker::hasOutdent(SM, InToEnd, WalkableParent)
|
|
};
|
|
}
|
|
}
|
|
|
|
bool shouldIndent = containsTarget(L, R) &&
|
|
!OutdentChecker::hasOutdent(SM, WalkableParent, RangeKind::Open);
|
|
return IndentContext {ContextLoc, shouldIndent};
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFromDictionaryElem(TupleExpr *TE) {
|
|
SourceLoc Start = TE->getStartLoc(), End = TE->getEndLoc();
|
|
if (!TE->getNumElements() || !isTargetContext(Start, End))
|
|
return std::nullopt;
|
|
Expr *Key = TE->getElement(0);
|
|
SourceLoc ColonLoc;
|
|
if (auto Next = getTokenAfter(SM, Key->getEndLoc())) {
|
|
if (Next && Next->getKind() == tok::colon)
|
|
ColonLoc = Next->getLoc();
|
|
}
|
|
if (ColonLoc.isValid() && isTargetContext(ColonLoc, End))
|
|
return IndentContext {
|
|
Start,
|
|
!OutdentChecker::hasOutdent(SM, SourceRange(ColonLoc, End), TE)
|
|
};
|
|
return IndentContext {Start, !OutdentChecker::hasOutdent(SM, Key)};
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(TupleExpr *TE, SourceLoc ContextLoc = SourceLoc()) {
|
|
if (ContextLoc.isValid())
|
|
NodesToSkip.insert(static_cast<Expr*>(TE));
|
|
SourceLoc L = TE->getLParenLoc();
|
|
SourceLoc R = getLocIfKind(SM, TE->getRParenLoc(),
|
|
{tok::r_paren, tok::r_square});
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
if (ContextLoc.isValid()) {
|
|
ContextLoc = CtxOverride.propagateContext(SM, ContextLoc,
|
|
IndentContext::LineStart,
|
|
L, R);
|
|
} else {
|
|
ContextLoc = L;
|
|
}
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
auto NumElems = TE->getNumElements();
|
|
for (auto I : range(NumElems)) {
|
|
SourceRange ElemRange = TE->getElementNameLoc(I);
|
|
if (Expr *Elem = TE->getElement(I))
|
|
widenOrSet(ElemRange, Elem->getSourceRange());
|
|
assert(ElemRange.isValid());
|
|
|
|
Aligner.updateAlignment(ElemRange, TE);
|
|
if (isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, TE)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(ParenExpr *PE, SourceLoc ContextLoc = SourceLoc()) {
|
|
if (ContextLoc.isValid())
|
|
NodesToSkip.insert(static_cast<Expr*>(PE));
|
|
SourceLoc L = PE->getLParenLoc();
|
|
SourceLoc R = getLocIfKind(SM, PE->getRParenLoc(),
|
|
{tok::r_paren, tok::r_square});
|
|
if (L.isInvalid() || !overlapsTarget(L, R))
|
|
return std::nullopt;
|
|
|
|
if (ContextLoc.isValid()) {
|
|
ContextLoc = CtxOverride.propagateContext(SM, ContextLoc,
|
|
IndentContext::LineStart,
|
|
L, R);
|
|
} else {
|
|
ContextLoc = L;
|
|
}
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
Expr *Elem = PE->getSubExpr();
|
|
SourceRange Range = Elem->getSourceRange();
|
|
Aligner.updateAlignment(Range, Elem);
|
|
|
|
if (isTargetContext(Range)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
Range.Start,
|
|
!OutdentChecker::hasOutdent(SM, Elem)
|
|
};
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
std::optional<IndentContext> getIndentContextFromTrailingClosure(
|
|
ArgumentList *Args, std::optional<TrailingInfo> TrailingTarget,
|
|
SourceLoc ContextLoc) {
|
|
if (!Args->hasAnyTrailingClosures())
|
|
return std::nullopt;
|
|
|
|
if (auto *arg = Args->getUnaryExpr()) {
|
|
auto *CE = findTrailingClosureFromArgument(arg);
|
|
if (!CE)
|
|
return std::nullopt;
|
|
|
|
auto Range = CE->getSourceRange();
|
|
if (Range.isInvalid() || (!TrailingTarget && !overlapsTarget(Range)))
|
|
return std::nullopt;
|
|
|
|
if (auto *CLE = dyn_cast<CaptureListExpr>(arg))
|
|
return getIndentContextFrom(CLE, ContextLoc);
|
|
|
|
return getIndentContextFrom(CE, ContextLoc);
|
|
}
|
|
auto ClosuresRange = Args->getOriginalArgs()->getTrailingSourceRange();
|
|
if (!overlapsTarget(ClosuresRange) && !TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
SourceRange ContextToEnd(ContextLoc, ClosuresRange.End);
|
|
ContextLoc =
|
|
CtxOverride.propagateContext(SM, ContextLoc, IndentContext::LineStart,
|
|
ClosuresRange.Start, SourceLoc());
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
bool hasOutdent;
|
|
if (auto *ParentE = Parent.getAsExpr()) {
|
|
hasOutdent = OutdentChecker::hasOutdent(SM, ContextToEnd, ParentE);
|
|
} else if (auto *ParentD = Parent.getAsDecl()) {
|
|
assert(isa<MacroExpansionDecl>(ParentD) && "Trailing closures in decls can only occur in macro expansions");
|
|
hasOutdent = OutdentChecker::hasOutdent(SM, ContextToEnd, ParentD);
|
|
} else {
|
|
assert(false && "Trailing closures can only occur in expr contexts and macro expansions");
|
|
return std::nullopt;
|
|
}
|
|
return IndentContext{ContextLoc, !hasOutdent};
|
|
}
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(ArgumentList *Args,
|
|
std::optional<TrailingInfo> TrailingTarget,
|
|
SourceLoc ContextLoc = SourceLoc()) {
|
|
if (ContextLoc.isValid())
|
|
NodesToSkip.insert(static_cast<ArgumentList *>(Args));
|
|
SourceLoc L = Args->getLParenLoc();
|
|
SourceLoc R = getLocIfKind(SM, Args->getRParenLoc(),
|
|
{tok::r_paren, tok::r_square});
|
|
if (L.isInvalid() || !overlapsTarget(L, R)) {
|
|
return getIndentContextFromTrailingClosure(Args, TrailingTarget,
|
|
ContextLoc);
|
|
}
|
|
|
|
if (ContextLoc.isValid()) {
|
|
ContextLoc = CtxOverride.propagateContext(SM, ContextLoc,
|
|
IndentContext::LineStart, L, R);
|
|
} else {
|
|
ContextLoc = L;
|
|
}
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto Arg : Args->getOriginalArgs()->getNonTrailingArgs()) {
|
|
SourceRange ElemRange = Arg.getLabelLoc();
|
|
auto *Elem = Arg.getExpr();
|
|
assert(Elem);
|
|
widenOrSet(ElemRange, Elem->getSourceRange());
|
|
assert(ElemRange.isValid());
|
|
|
|
Aligner.updateAlignment(ElemRange, Args);
|
|
if (isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext{ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, Args)};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
#pragma mark TypeRepr indent contexts
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(TypeRepr *T,
|
|
std::optional<TrailingInfo> TrailingTarget) {
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
if (auto *DRTR = dyn_cast<DeclRefTypeRepr>(T)) {
|
|
SourceLoc ContextLoc = DRTR->getNameLoc().getBaseNameLoc();
|
|
SourceRange Brackets = DRTR->getAngleBrackets();
|
|
if (Brackets.isInvalid())
|
|
return std::nullopt;
|
|
|
|
SourceLoc L = Brackets.Start;
|
|
SourceLoc R = getLocIfTokenTextMatches(SM, Brackets.End, ">");
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto *Arg: DRTR->getGenericArgs())
|
|
Aligner.updateAlignment(Arg->getSourceRange(), DRTR);
|
|
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *TT = dyn_cast<TupleTypeRepr>(T)) {
|
|
SourceLoc ContextLoc = TT->getStartLoc();
|
|
SourceRange Parens = TT->getParens();
|
|
if (Parens.isInvalid())
|
|
return std::nullopt;
|
|
|
|
SourceLoc L = Parens.Start;
|
|
SourceLoc R = getLocIfKind(SM, Parens.End, tok::r_paren);
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto &Elem: TT->getElements()) {
|
|
SourceRange ElemRange = Elem.NameLoc;
|
|
widenOrSet(ElemRange, Elem.UnderscoreLoc);
|
|
if (auto *T = Elem.Type)
|
|
widenOrSet(ElemRange, T->getSourceRange());
|
|
|
|
Aligner.updateAlignment(ElemRange, TT);
|
|
if (Elem.ColonLoc.isValid()) {
|
|
SourceRange FromColonToEnd = SourceRange(Elem.ColonLoc, ElemRange.End);
|
|
if (isTargetContext(FromColonToEnd)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, FromColonToEnd, TT)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *AT = dyn_cast<ArrayTypeRepr>(T)) {
|
|
SourceLoc ContextLoc = AT->getStartLoc();
|
|
SourceRange Brackets = AT->getBrackets();
|
|
if (Brackets.isInvalid())
|
|
return std::nullopt;
|
|
return IndentContext {
|
|
ContextLoc,
|
|
containsTarget(Brackets.Start, Brackets.End) &&
|
|
!OutdentChecker::hasOutdent(SM, AT, RangeKind::Open)
|
|
};
|
|
}
|
|
|
|
if (auto *DT = dyn_cast<DictionaryTypeRepr>(T)) {
|
|
SourceLoc ContextLoc = DT->getStartLoc();
|
|
SourceRange Brackets = DT->getBrackets();
|
|
if (Brackets.isInvalid())
|
|
return std::nullopt;
|
|
|
|
SourceLoc KeyLoc = DT->getKey()->getStartLoc();
|
|
SourceLoc ColonLoc = DT->getColonLoc();
|
|
if (ColonLoc.isValid()) {
|
|
SourceRange ColonToEnd = SourceRange(ColonLoc, Brackets.End);
|
|
if (isTargetContext(ColonToEnd))
|
|
return IndentContext {
|
|
KeyLoc,
|
|
containsTarget(Brackets) &&
|
|
!OutdentChecker::hasOutdent(SM, ColonToEnd, DT)
|
|
};
|
|
}
|
|
return IndentContext {
|
|
ContextLoc,
|
|
containsTarget(Brackets) &&
|
|
!OutdentChecker::hasOutdent(SM, DT, RangeKind::Open)
|
|
};
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
#pragma mark Pattern indent contexts
|
|
|
|
std::optional<IndentContext>
|
|
getIndentContextFrom(Pattern *P, std::optional<TrailingInfo> TrailingTarget) {
|
|
if (TrailingTarget)
|
|
return std::nullopt;
|
|
|
|
if (auto *TP = dyn_cast<TypedPattern>(P)) {
|
|
SourceLoc ContextLoc = TP->getStartLoc();
|
|
auto *LHS = TP->getSubPattern();
|
|
|
|
SourceLoc ColonLoc;
|
|
if (auto Next = getTokenAfter(SM, LHS->getEndLoc())) {
|
|
if (Next->getKind() == tok::colon)
|
|
ColonLoc = Next->getLoc();
|
|
}
|
|
if (ColonLoc.isValid()) {
|
|
SourceRange ColonToEnd = SourceRange(ColonLoc, TP->getEndLoc());
|
|
if (isTargetContext(ColonToEnd))
|
|
return IndentContext {
|
|
ContextLoc,
|
|
!OutdentChecker::hasOutdent(SM, ColonToEnd, TP)
|
|
};
|
|
}
|
|
return IndentContext {ContextLoc, !OutdentChecker::hasOutdent(SM, TP)};
|
|
}
|
|
|
|
if (auto *PP = dyn_cast<ParenPattern>(P)) {
|
|
SourceLoc ContextLoc = PP->getStartLoc();
|
|
SourceLoc L = PP->getLParenLoc();
|
|
SourceLoc R = getLocIfKind(SM, PP->getRParenLoc(), tok::r_paren);
|
|
if (L.isInvalid())
|
|
return std::nullopt;
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
if (auto *Elem = PP->getSubPattern()) {
|
|
SourceRange ElemRange = Elem->getSourceRange();
|
|
Aligner.updateAlignment(ElemRange, Elem);
|
|
|
|
if (isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, Elem)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
if (auto *TP = dyn_cast<TuplePattern>(P)) {
|
|
SourceLoc ContextLoc = TP->getStartLoc();
|
|
SourceLoc L = TP->getLParenLoc(), R = TP->getRParenLoc();
|
|
if (L.isInvalid())
|
|
return std::nullopt;
|
|
|
|
ListAligner Aligner(SM, TargetLocation, ContextLoc, L, R);
|
|
for (auto &Elem: TP->getElements()) {
|
|
SourceRange ElemRange = Elem.getLabelLoc();
|
|
if (auto *P = Elem.getPattern())
|
|
widenOrSet(ElemRange, P->getSourceRange());
|
|
Aligner.updateAlignment(ElemRange, TP);
|
|
|
|
if (isTargetContext(ElemRange)) {
|
|
Aligner.setAlignmentIfNeeded(CtxOverride);
|
|
return IndentContext {
|
|
ElemRange.Start,
|
|
!OutdentChecker::hasOutdent(SM, ElemRange, TP)
|
|
};
|
|
}
|
|
}
|
|
return Aligner.getContextAndSetAlignment(CtxOverride);
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
};
|
|
|
|
class CodeFormatter {
|
|
CodeFormatOptions &FmtOptions;
|
|
public:
|
|
CodeFormatter(CodeFormatOptions &Options)
|
|
:FmtOptions(Options) { }
|
|
|
|
std::pair<LineRange, std::string> indent(unsigned LineIndex,
|
|
FormatContext &FC,
|
|
StringRef Text) {
|
|
if (FC.isExact()) {
|
|
StringRef Line = swift::ide::getTextForLine(LineIndex, Text, /*Trim*/true);
|
|
StringBuilder Builder;
|
|
FC.padToExactColumn(Builder, FmtOptions);
|
|
Builder.append(Line);
|
|
return std::make_pair(LineRange(LineIndex, 1), Builder.str().str());
|
|
}
|
|
|
|
// Take the current indent position of the context, then add the number of
|
|
// indents specified.
|
|
auto LineAndColumn = FC.indentLineAndColumn();
|
|
size_t ExpandedIndent = swift::ide::getExpandedIndentForLine(LineAndColumn.first,
|
|
FmtOptions, Text);
|
|
|
|
if (FC.shouldAddIndentForLine()) {
|
|
auto Width = FmtOptions.UseTabs ? FmtOptions.TabWidth
|
|
: FmtOptions.IndentWidth;
|
|
// We don't need to add additional indentation if Width is zero.
|
|
if (Width) {
|
|
// Increment indent.
|
|
ExpandedIndent += Width * FC.numIndentLevels();
|
|
|
|
// Normalize indent to align on proper column indent width.
|
|
ExpandedIndent -= ExpandedIndent % Width;
|
|
}
|
|
}
|
|
|
|
if (FC.IsInDocCommentBlock()) {
|
|
// Inside doc comment block, the indent is one space, e.g.
|
|
// /**
|
|
// * <---Indent to align with the first star.
|
|
// */
|
|
ExpandedIndent += 1;
|
|
}
|
|
|
|
// Reformat the specified line with the calculated indent.
|
|
StringRef Line = swift::ide::getTextForLine(LineIndex, Text, /*Trim*/true);
|
|
std::string IndentedLine;
|
|
if (FmtOptions.UseTabs)
|
|
IndentedLine.assign(ExpandedIndent / FmtOptions.TabWidth, '\t');
|
|
else
|
|
IndentedLine.assign(ExpandedIndent, ' ');
|
|
IndentedLine.append(Line.str());
|
|
|
|
// Return affected line range, which can later be more than one line.
|
|
LineRange range = LineRange(LineIndex, 1);
|
|
return std::make_pair(range, IndentedLine);
|
|
}
|
|
};
|
|
} //anonymous namespace
|
|
|
|
size_t swift::ide::getOffsetOfLine(unsigned LineIndex, StringRef Text) {
|
|
// SourceLoc start = SourceLoc(llvm::SMLoc::getFromPointer(Text.begin()));
|
|
// FIXME: We should have a cached line map in EditableTextBuffer, for now
|
|
// we just do the slow naive thing here.
|
|
size_t LineOffset = 0;
|
|
unsigned CurrentLine = 0;
|
|
while (LineOffset < Text.size() && ++CurrentLine < LineIndex) {
|
|
LineOffset = Text.find_first_of("\r\n", LineOffset);
|
|
if (LineOffset != std::string::npos) {
|
|
++LineOffset;
|
|
if (LineOffset < Text.size() &&
|
|
Text[LineOffset - 1] == '\r' && Text[LineOffset] == '\n')
|
|
++LineOffset;
|
|
}
|
|
|
|
}
|
|
if (LineOffset == std::string::npos)
|
|
LineOffset = 0;
|
|
return LineOffset;
|
|
}
|
|
|
|
size_t swift::ide::getOffsetOfLine(unsigned LineIndex, StringRef Text, bool Trim) {
|
|
size_t LineOffset = swift::ide::getOffsetOfLine(LineIndex, Text);
|
|
if (!Trim)
|
|
return LineOffset;
|
|
// Skip leading whitespace.
|
|
size_t FirstNonWSOnLine = Text.find_first_not_of(" \t\v\f", LineOffset);
|
|
if (FirstNonWSOnLine != std::string::npos)
|
|
LineOffset = FirstNonWSOnLine;
|
|
return LineOffset;
|
|
}
|
|
|
|
llvm::StringRef swift::ide::getTextForLine(unsigned LineIndex, StringRef Text,
|
|
bool Trim) {
|
|
size_t LineOffset = getOffsetOfLine(LineIndex, Text, Trim);
|
|
size_t LineEnd = Text.find_first_of("\r\n", LineOffset);
|
|
return Text.slice(LineOffset, LineEnd);
|
|
}
|
|
|
|
size_t swift::ide::getExpandedIndentForLine(unsigned LineIndex,
|
|
CodeFormatOptions Options,
|
|
StringRef Text) {
|
|
size_t LineOffset = getOffsetOfLine(LineIndex, Text);
|
|
|
|
// Tab-expand all leading whitespace
|
|
size_t FirstNonWSOnLine = Text.find_first_not_of(" \t\v\f", LineOffset);
|
|
size_t Indent = 0;
|
|
while (LineOffset < Text.size() && LineOffset < FirstNonWSOnLine) {
|
|
if (Text[LineOffset++] == '\t')
|
|
Indent += Options.TabWidth;
|
|
else
|
|
Indent += 1;
|
|
}
|
|
return Indent;
|
|
}
|
|
|
|
std::pair<LineRange, std::string> swift::ide::reformat(LineRange Range,
|
|
CodeFormatOptions Options,
|
|
SourceManager &SM,
|
|
SourceFile &SF) {
|
|
// Sanitize 0-width tab
|
|
if (Options.UseTabs && !Options.TabWidth) {
|
|
// If IndentWidth is specified, use it as the tab width. Otherwise, use the
|
|
// default value.
|
|
Options.TabWidth = Options.IndentWidth ? Options.IndentWidth : 4;
|
|
}
|
|
auto SourceBufferID = SF.getBufferID();
|
|
StringRef Text = SM.getLLVMSourceMgr()
|
|
.getMemoryBuffer(SourceBufferID)->getBuffer();
|
|
size_t Offset = getOffsetOfLine(Range.startLine(), Text, /*Trim*/true);
|
|
SourceLoc Loc = SM.getLocForBufferStart(SourceBufferID)
|
|
.getAdvancedLoc(Offset);
|
|
|
|
FormatWalker walker(SF, SM, Options);
|
|
FormatContext FC = walker.walkToLocation(Loc);
|
|
CodeFormatter CF(Options);
|
|
return CF.indent(Range.startLine(), FC, Text);
|
|
}
|
|
|