Skip to content

Commit f27ccab

Browse files
committed
SourceKit/Formatting: avoid indenting for consecutive dot-member calls
rdar://52392291
1 parent d0f0069 commit f27ccab

File tree

4 files changed

+107
-44
lines changed

4 files changed

+107
-44
lines changed

include/swift/Basic/SourceManager.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,14 @@ class SourceManager {
231231
void verifyAllBuffers() const;
232232

233233
/// Translate line and column pair to the offset.
234+
/// If the column number is the maximum unsinged int, return the offset of the end of the line.
234235
llvm::Optional<unsigned> resolveFromLineCol(unsigned BufferId, unsigned Line,
235236
unsigned Col) const;
236237

238+
/// Translate the end position of the given line to the offset.
239+
llvm::Optional<unsigned> resolveOffsetForEndOfLine(unsigned BufferId,
240+
unsigned Line) const;
241+
237242
SourceLoc getLocForLineCol(unsigned BufferId, unsigned Line, unsigned Col) const {
238243
auto Offset = resolveFromLineCol(BufferId, Line, Col);
239244
return Offset.hasValue() ? getLocForOffset(BufferId, Offset.getValue()) :

lib/Basic/SourceLoc.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,19 @@ void CharSourceRange::dump(const SourceManager &SM) const {
310310
print(llvm::errs(), SM);
311311
}
312312

313+
llvm::Optional<unsigned>
314+
SourceManager::resolveOffsetForEndOfLine(unsigned BufferId,
315+
unsigned Line) const {
316+
return resolveFromLineCol(BufferId, Line, ~0u);
317+
}
318+
313319
llvm::Optional<unsigned> SourceManager::resolveFromLineCol(unsigned BufferId,
314320
unsigned Line,
315321
unsigned Col) const {
316322
if (Line == 0 || Col == 0) {
317323
return None;
318324
}
325+
const bool LineEnd = Col == ~0u;
319326
auto InputBuf = getLLVMSourceMgr().getMemoryBuffer(BufferId);
320327
const char *Ptr = InputBuf->getBufferStart();
321328
const char *End = InputBuf->getBufferEnd();
@@ -331,14 +338,18 @@ llvm::Optional<unsigned> SourceManager::resolveFromLineCol(unsigned BufferId,
331338
return None;
332339
}
333340
Ptr = LineStart;
334-
335341
// The <= here is to allow for non-inclusive range end positions at EOF
336-
for (; Ptr <= End; ++Ptr) {
342+
for (; ; ++Ptr) {
337343
--Col;
338344
if (Col == 0)
339345
return Ptr - InputBuf->getBufferStart();
340-
if (*Ptr == '\n')
341-
break;
346+
if (*Ptr == '\n' || Ptr == End) {
347+
if (LineEnd) {
348+
return Ptr - InputBuf->getBufferStart();
349+
} else {
350+
break;
351+
}
352+
}
342353
}
343354
return None;
344355
}

lib/IDE/Formatting.cpp

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,35 @@ struct SiblingAlignInfo {
2929
};
3030

3131
struct TokenInfo {
32-
const Token *StartOfLineTarget;
33-
const Token *StartOfLineBeforeTarget;
34-
TokenInfo(const Token *StartOfLineTarget,
35-
const Token *StartOfLineBeforeTarget) :
36-
StartOfLineTarget(StartOfLineTarget),
37-
StartOfLineBeforeTarget(StartOfLineBeforeTarget) {}
38-
TokenInfo() : TokenInfo(nullptr, nullptr) {}
39-
operator bool() { return StartOfLineTarget && StartOfLineBeforeTarget; }
32+
// The tokens appearing at the start of lines, from the first line to the
33+
// line under indentation.
34+
std::vector<const Token*> LineStarts;
35+
const Token *getLineStarter(unsigned idx = 0) const {
36+
auto it = LineStarts.rbegin() + idx;
37+
return it < LineStarts.rend() ? *it : nullptr;
38+
}
39+
operator bool() { return LineStarts.size() > 1; }
40+
bool isRBraceDotsPattern() const {
41+
for (auto It = LineStarts.rbegin(), end = LineStarts.rend();
42+
It + 1 < end; ++It) {
43+
auto* CurrentLine = *It;
44+
auto* PreviousLine = *(It + 1);
45+
if (CurrentLine->getKind() == tok::period &&
46+
PreviousLine->getKind() == tok::period) {
47+
// If the previous line starts with dot too, move further up.
48+
continue;
49+
} else if (CurrentLine->getKind() == tok::period &&
50+
PreviousLine->getKind() == tok::r_brace &&
51+
PreviousLine + 1 == CurrentLine) {
52+
// Check if the previous line starts with '}' and the period of the
53+
// current line is immediately after the '}'
54+
return true;
55+
} else {
56+
return false;
57+
}
58+
}
59+
return false;
60+
}
4061
};
4162

4263
using StringBuilder = llvm::SmallString<64>;
@@ -285,17 +306,16 @@ class FormatContext {
285306
return false;
286307

287308
if (TInfo) {
288-
if (TInfo.StartOfLineTarget->getKind() == tok::l_brace &&
289-
isKeywordPossibleDeclStart(*TInfo.StartOfLineBeforeTarget) &&
290-
TInfo.StartOfLineBeforeTarget->isKeyword())
309+
if (TInfo.getLineStarter()->getKind() == tok::l_brace &&
310+
isKeywordPossibleDeclStart(*TInfo.getLineStarter(1)) &&
311+
TInfo.getLineStarter(1)->isKeyword())
291312
return false;
292313
// VStack {
293314
// ...
294315
// }
295316
// .onAppear { <---- No indentation here.
296-
if (TInfo.StartOfLineTarget->getKind() == tok::period &&
297-
TInfo.StartOfLineBeforeTarget->getKind() == tok::r_brace &&
298-
TInfo.StartOfLineBeforeTarget + 1 == TInfo.StartOfLineTarget) {
317+
// .onAppear1 { <---- No indentation here.
318+
if (TInfo.isRBraceDotsPattern()) {
299319
return false;
300320
}
301321
}
@@ -365,7 +385,7 @@ class FormatContext {
365385
SignatureEnd = SD->getSignatureSourceRange().End;
366386
}
367387
if (SignatureEnd.isValid() && TInfo &&
368-
TInfo.StartOfLineTarget->getLoc() == SignatureEnd) {
388+
TInfo.getLineStarter()->getLoc() == SignatureEnd) {
369389
return false;
370390
}
371391

@@ -862,7 +882,7 @@ class CodeFormatter {
862882

863883
// If having sibling locs to align with, respect siblings.
864884
auto isClosingSquare =
865-
ToInfo && ToInfo.StartOfLineTarget->getKind() == tok::r_square;
885+
ToInfo && ToInfo.getLineStarter()->getKind() == tok::r_square;
866886
if (!isClosingSquare && FC.HasSibling()) {
867887
StringRef Line = swift::ide::getTextForLine(LineIndex, Text, /*Trim*/true);
868888
StringBuilder Builder;
@@ -940,33 +960,37 @@ class TokenInfoCollector {
940960
SourceManager &SM;
941961
ArrayRef<Token> Tokens;
942962
unsigned Line;
943-
944-
struct Comparator {
945-
SourceManager &SM;
946-
Comparator(SourceManager &SM) : SM(SM) {}
947-
bool operator()(const Token &T, unsigned Line) const {
948-
return SM.getLineNumber(T.getLoc()) < Line;
949-
}
950-
bool operator()(unsigned Line, const Token &T) const {
951-
return Line < SM.getLineNumber(T.getLoc());
952-
}
953-
};
954-
963+
// The location of the end of the line under indentation, we don't need to
964+
// collect tokens after this location.
965+
SourceLoc EndLimit;
955966
public:
956-
TokenInfoCollector(SourceManager &SM, ArrayRef<Token> Tokens,
957-
unsigned Line) : SM(SM), Tokens(Tokens), Line(Line) {}
967+
TokenInfoCollector(SourceManager &SM,
968+
unsigned BufferId,
969+
ArrayRef<Token> Tokens, unsigned Line):
970+
SM(SM), Tokens(Tokens), Line(Line) {
971+
if (auto Offset = SM.resolveOffsetForEndOfLine(BufferId, Line)) {
972+
EndLimit = SM.getLocForOffset(BufferId, *Offset);
973+
}
974+
}
958975

959976
TokenInfo collect() {
960-
if (Line == 0)
977+
if (EndLimit.isInvalid()) {
961978
return TokenInfo();
962-
Comparator Comp(SM);
963-
auto LineMatch = [this] (const Token* T, unsigned Line) {
964-
return T != Tokens.end() && SM.getLineNumber(T->getLoc()) == Line;
965-
};
966-
auto TargetIt = std::lower_bound(Tokens.begin(), Tokens.end(), Line, Comp);
967-
auto LineBefore = std::lower_bound(Tokens.begin(), TargetIt, Line - 1, Comp);
968-
if (LineMatch(TargetIt, Line) && LineMatch(LineBefore, Line - 1))
969-
return TokenInfo(TargetIt, LineBefore);
979+
}
980+
TokenInfo Result;
981+
for(auto &T: Tokens) {
982+
if (!T.isAtStartOfLine())
983+
continue;
984+
if (SM.isBeforeInBuffer(EndLimit, T.getLoc())) {
985+
if (!Result.LineStarts.empty()) {
986+
if (SM.getLineNumber(Result.getLineStarter()->getLoc()) == Line) {
987+
return Result;
988+
}
989+
}
990+
return TokenInfo();
991+
}
992+
Result.LineStarts.push_back(&T);
993+
}
970994
return TokenInfo();
971995
}
972996
};
@@ -1051,7 +1075,8 @@ std::pair<LineRange, std::string> swift::ide::reformat(LineRange Range,
10511075
FormatContext FC = walker.walkToLocation(Loc);
10521076
CodeFormatter CF(Options);
10531077
unsigned Line = Range.startLine();
1054-
return CF.indent(Line, FC, Text, TokenInfoCollector(SM, walker.getTokens(),
1078+
return CF.indent(Line, FC, Text, TokenInfoCollector(SM, SourceBufferID,
1079+
walker.getTokens(),
10551080
Line).collect());
10561081
}
10571082

test/SourceKit/CodeFormat/indent-closure.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ func foo11() {
7474
}
7575
}
7676

77+
func foo12() {
78+
VStack {
79+
}
80+
.onAppear1()
81+
.onAppear2() {}
82+
.onAppear3() {
83+
}
84+
.onAppear4() {}
85+
}
86+
7787
// RUN: %sourcekitd-test -req=format -line=3 -length=1 %s >%t.response
7888
// RUN: %sourcekitd-test -req=format -line=4 -length=1 %s >>%t.response
7989
// RUN: %sourcekitd-test -req=format -line=5 -length=1 %s >>%t.response
@@ -105,6 +115,12 @@ func foo11() {
105115
// RUN: %sourcekitd-test -req=format -line=67 -length=1 %s >>%t.response
106116
// RUN: %sourcekitd-test -req=format -line=73 -length=1 %s >>%t.response
107117

118+
// RUN: %sourcekitd-test -req=format -line=80 -length=1 %s >>%t.response
119+
// RUN: %sourcekitd-test -req=format -line=81 -length=1 %s >>%t.response
120+
// RUN: %sourcekitd-test -req=format -line=82 -length=1 %s >>%t.response
121+
// RUN: %sourcekitd-test -req=format -line=83 -length=1 %s >>%t.response
122+
// RUN: %sourcekitd-test -req=format -line=84 -length=1 %s >>%t.response
123+
108124
// RUN: %FileCheck --strict-whitespace %s <%t.response
109125

110126
// CHECK: key.sourcetext: " var abc = 1"
@@ -145,3 +161,9 @@ func foo11() {
145161
// CHECK: key.sourcetext: " Something() ["
146162
// CHECK: key.sourcetext: " ].whatever"
147163
// CHECK: key.sourcetext: " .onAppear {"
164+
165+
// CHECK: key.sourcetext: " .onAppear1()"
166+
// CHECK: key.sourcetext: " .onAppear2() {}"
167+
// CHECK: key.sourcetext: " .onAppear3() {"
168+
// CHECK: key.sourcetext: " }"
169+
// CHECK: key.sourcetext: " .onAppear4() {}"

0 commit comments

Comments
 (0)