Skip to content

Commit 8a3a295

Browse files
committed
[Omit needless words] Improve our handling of plural acronyms.
1 parent 5963cfd commit 8a3a295

File tree

5 files changed

+157
-16
lines changed

5 files changed

+157
-16
lines changed

lib/Basic/StringExtras.cpp

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ PartOfSpeech swift::getPartOfSpeech(StringRef word) {
9696
return PartOfSpeech::Unknown;
9797
}
9898

99+
/// Whether the given word is a plural s
100+
static bool isPluralSuffix(StringRef word) {
101+
return word == "s" || word == "es" || word == "ies";
102+
}
103+
99104
void WordIterator::computeNextPosition() const {
100105
assert(Position < String.size() && "Already at end of string");
101106

@@ -118,7 +123,20 @@ void WordIterator::computeNextPosition() const {
118123
// If we hit the end of the string, that's it. Otherwise, this
119124
// word ends before the last uppercase letter if the next word is alphabetic
120125
// (URL_Loader) or after the last uppercase letter if it's not (UTF_8).
121-
NextPosition = (i == n || !clang::isLowercase(String[i])) ? i : i-1;
126+
127+
// Collect the lowercase letters up to the next word.
128+
unsigned endOfNext = i;
129+
while (endOfNext < n && clang::isLowercase(String[endOfNext]))
130+
++endOfNext;
131+
132+
// If the next word is a plural suffix, add it on.
133+
if (i == n || isPluralSuffix(String.slice(i, endOfNext)))
134+
NextPosition = endOfNext;
135+
else if (clang::isLowercase(String[i]))
136+
NextPosition = i-1;
137+
else
138+
NextPosition = i;
139+
122140
NextPositionValid = true;
123141
return;
124142
}
@@ -140,9 +158,19 @@ void WordIterator::computePrevPosition() const {
140158
while (i > 0 && !clang::isUppercase(String[i-1]) && String[i-1] != '_')
141159
--i;
142160

161+
// If what we found is a plural suffix, keep going.
162+
bool skippedPluralSuffix = false;
163+
unsigned effectiveEndPosition = Position;
164+
if (i > 0 && isPluralSuffix(String.slice(i, Position))) {
165+
skippedPluralSuffix = true;
166+
effectiveEndPosition = i;
167+
while (i > 0 && !clang::isUppercase(String[i-1]) && String[i-1] != '_')
168+
--i;
169+
}
170+
143171
// If we found any lowercase letters, this was a normal camel case
144172
// word (not an acronym).
145-
if (i < Position) {
173+
if (i < effectiveEndPosition) {
146174
// If we hit the beginning of the string, that's it. Otherwise, this
147175
// word starts with an uppercase letter if the next word is alphabetic
148176
// (URL_Loader) or after the last uppercase letter if it's not (UTF_8).
@@ -577,6 +605,14 @@ static StringRef omitNeedlessWords(StringRef name,
577605
auto newShortenedNameWord
578606
= omitNeedlessWords(shortenedNameWord, typeName.CollectionElement,
579607
NameRole::Partial, allPropertyNames, scratch);
608+
if (shortenedNameWord == newShortenedNameWord &&
609+
shortenedNameWord.back() == 'e') {
610+
shortenedNameWord.drop_back();
611+
newShortenedNameWord =
612+
omitNeedlessWords(shortenedNameWord, typeName.CollectionElement,
613+
NameRole::Partial, allPropertyNames, scratch);
614+
}
615+
580616
if (shortenedNameWord != newShortenedNameWord) {
581617
matched();
582618
unsigned targetSize = newShortenedNameWord.size();
@@ -749,11 +785,6 @@ static StringRef omitNeedlessWords(StringRef name,
749785
return name;
750786
}
751787

752-
/// Whether the given word is a plural s
753-
static bool isPluralSuffix(StringRef word) {
754-
return word == "s" || word == "es" || word == "ies";
755-
}
756-
757788
StringRef camel_case::toLowercaseInitialisms(StringRef string,
758789
StringScratchSpace &scratch) {
759790
if (string.empty())
@@ -834,8 +865,7 @@ static bool wordConflictsAfterPreposition(StringRef word,
834865
/// Split the base name after the last preposition, if there is one.
835866
static bool splitBaseNameAfterLastPreposition(StringRef &baseName,
836867
StringRef &argName,
837-
const OmissionTypeName &paramType,
838-
StringScratchSpace &scratch) {
868+
const OmissionTypeName &paramType) {
839869
// Scan backwards for a preposition.
840870
auto nameWords = camel_case::getWords(baseName);
841871
auto nameWordRevIter = nameWords.rbegin(),
@@ -928,8 +958,7 @@ static bool splitBaseNameAfterLastPreposition(StringRef &baseName,
928958
}
929959

930960
// Update the argument label and base name.
931-
argName = toLowercaseInitialisms(baseName.substr(startOfArgumentLabel),
932-
scratch);
961+
argName = baseName.substr(startOfArgumentLabel);
933962
baseName = newBaseName;
934963

935964
return true;
@@ -938,8 +967,7 @@ static bool splitBaseNameAfterLastPreposition(StringRef &baseName,
938967
/// Split the base name, if it makes sense.
939968
static bool splitBaseName(StringRef &baseName, StringRef &argName,
940969
const OmissionTypeName &paramType,
941-
StringRef paramName,
942-
StringScratchSpace &scratch) {
970+
StringRef paramName) {
943971
// If there is already an argument label, do nothing.
944972
if (!argName.empty()) return false;
945973

@@ -962,7 +990,7 @@ static bool splitBaseName(StringRef &baseName, StringRef &argName,
962990
return false;
963991

964992
// Try splitting after the last preposition.
965-
if (splitBaseNameAfterLastPreposition(baseName, argName, paramType, scratch))
993+
if (splitBaseNameAfterLastPreposition(baseName, argName, paramType))
966994
return true;
967995

968996
return false;
@@ -1042,8 +1070,7 @@ bool swift::omitNeedlessWords(StringRef &baseName,
10421070

10431071
// If needed, split the base name.
10441072
if (!argNames.empty() &&
1045-
splitBaseName(baseName, argNames[0], paramTypes[0], firstParamName,
1046-
scratch))
1073+
splitBaseName(baseName, argNames[0], paramTypes[0], firstParamName))
10471074
anyChanges = true;
10481075

10491076
// Omit needless words based on parameter types.

test/IDE/Inputs/custom-modules/OmitNeedlessWords.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
-(void)setIndirectlyToValue:(nonnull id)object;
88
-(void)jumpToTop:(nonnull id)sender;
99
-(void)removeWithNoRemorse:(nonnull id)object;
10+
-(void)bookmarkWithURLs:(nonnull NSArray<NSURL *> *)urls;
1011
@end

test/IDE/print_omit_needless_words.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@
273273
// CHECK-OMIT-NEEDLESS-WORDS: func setIndirectlyToValue(_: AnyObject)
274274
// CHECK-OMIT-NEEDLESS-WORDS: func jumpToTop(_: AnyObject)
275275
// CHECK-OMIT-NEEDLESS-WORDS: func removeWithNoRemorse(_: AnyObject)
276+
// CHECK-OMIT-NEEDLESS-WORDS: func bookmarkWith(_: [URL])
276277

277278
// Don't drop the 'error'.
278279
// CHECK-ERRORS: func tryAndReturnError(_: ()) throws

unittests/Basic/StringExtrasTest.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,102 @@ TEST(CamelCaseWordsTest, Iteration) {
5656
EXPECT_EQ(iter, words.end());
5757
}
5858

59+
TEST(CamelCaseWordsTest, PluralAcronyms) {
60+
auto words = camel_case::getWords("CreateURLsFromPIXies");
61+
62+
// Forward iteration count.
63+
EXPECT_EQ(4, std::distance(words.begin(), words.end()));
64+
65+
// Reverse iteration count.
66+
EXPECT_EQ(4, std::distance(words.rbegin(), words.rend()));
67+
68+
// Iteration contents.
69+
auto iter = words.begin();
70+
EXPECT_EQ(*iter, "Create");
71+
72+
// Stepping forward.
73+
++iter;
74+
EXPECT_EQ(*iter, "URLs");
75+
76+
// Immediately stepping back.
77+
--iter;
78+
EXPECT_EQ(*iter, "Create");
79+
80+
// Immediately stepping forward.
81+
++iter;
82+
EXPECT_EQ(*iter, "URLs");
83+
84+
// Stepping forward.
85+
++iter;
86+
EXPECT_EQ(*iter, "From");
87+
88+
// Stepping back twice (slow path).
89+
--iter;
90+
--iter;
91+
EXPECT_EQ(*iter, "Create");
92+
93+
// Stepping forward to visit the remaining elements.
94+
++iter;
95+
EXPECT_EQ(*iter, "URLs");
96+
++iter;
97+
EXPECT_EQ(*iter, "From");
98+
++iter;
99+
EXPECT_EQ(*iter, "PIXies");
100+
101+
// We're done.
102+
++iter;
103+
EXPECT_EQ(iter, words.end());
104+
}
105+
106+
TEST(CamelCaseWordsTest, MorePluralAcronyms) {
107+
auto words = camel_case::getWords("inSameDayAsDate");
108+
109+
// Forward iteration count.
110+
EXPECT_EQ(5, std::distance(words.begin(), words.end()));
111+
112+
// Reverse iteration count.
113+
EXPECT_EQ(5, std::distance(words.rbegin(), words.rend()));
114+
115+
// Iteration contents.
116+
auto iter = words.begin();
117+
EXPECT_EQ(*iter, "in");
118+
119+
// Stepping forward.
120+
++iter;
121+
EXPECT_EQ(*iter, "Same");
122+
123+
// Immediately stepping back (fast path).
124+
--iter;
125+
EXPECT_EQ(*iter, "in");
126+
127+
// Immediately stepping forward (fast path).
128+
++iter;
129+
EXPECT_EQ(*iter, "Same");
130+
131+
// Stepping forward.
132+
++iter;
133+
EXPECT_EQ(*iter, "Day");
134+
135+
// Stepping back twice (slow path).
136+
--iter;
137+
--iter;
138+
EXPECT_EQ(*iter, "in");
139+
140+
// Stepping forward to visit the remaining elements.
141+
++iter;
142+
EXPECT_EQ(*iter, "Same");
143+
++iter;
144+
EXPECT_EQ(*iter, "Day");
145+
++iter;
146+
EXPECT_EQ(*iter, "As");
147+
++iter;
148+
EXPECT_EQ(*iter, "Date");
149+
150+
// We're done.
151+
++iter;
152+
EXPECT_EQ(iter, words.end());
153+
}
154+
59155
TEST(CamelCaseWordsTest, WordsWithUnderscores) {
60156
auto words = camel_case::getWords("CF_Flags_789");
61157
EXPECT_EQ(5, std::distance(words.begin(), words.end()));

utils/omit-needless-words.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ def dump_module_api(cmd, extra_dump_args, output_dir, module, quiet):
9191

9292
return
9393

94+
# Collect the set of frameworks we should dump
95+
def collect_frameworks(sdk):
96+
(exitcode, out, err) = run_command(["xcrun", "--show-sdk-path", "-sdk", sdk])
97+
if exitcode != 0:
98+
print('error: framework collection failed with error %d' % (exitcode))
99+
return ()
100+
sdk_path = out
101+
102+
(exitcode, out, err) = run_command(["xcrun", "--show-sdk-version", "-sdk", sdk])
103+
if exitcode != 0:
104+
print('error: framework collection failed with error %d' % (exitcode))
105+
return ()
106+
107+
108+
109+
94110
def main():
95111
source_filename = 'omit-needless-words.swift'
96112
parser = create_parser()

0 commit comments

Comments
 (0)