Skip to content

Commit 50a8807

Browse files
authored
Fix param formatting with complex attributes (#656)
Fix issue where attributes are being incorrectly indented. This happens because the existing logic for indenting parameters simply looks for the first opening "(" character to indent the first param of a multi-param symbol—this doesn't work when a symbol is prefixed with an attribute that takes arguments, since it also uses parenthesis. To fix it—the logic has been updated to look for the first "(" character _that follows the first keyword_. Example: ```swift // original declaration @objc(quxA:B:) public func quxqux(a: Int, b: Int) -> Int // formatted declaration before this commit @objc(quxA:B: ) public func quxqux(a: Int, b: Int) -> Int // formatted declaration after this commit @objc(quxA:B:) public func quxqux( a: Int, b: Int ) -> Int ``` Resolves: rdar://108167163
1 parent d6f8bfb commit 50a8807

File tree

2 files changed

+188
-23
lines changed

2 files changed

+188
-23
lines changed

src/components/DocumentationTopic/PrimaryContent/DeclarationSource.vue

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export default {
8989
let closeParenTokenIndex = null;
9090
let closeParenCharIndex = null;
9191
let numUnclosedParens = 0;
92+
let firstKeywordTokenIndex = null;
9293
9394
// loop through every declaration token
9495
while (i < tokens.length) {
@@ -98,31 +99,39 @@ export default {
9899
const prevToken = tokens[i - 1];
99100
const nextToken = tokens[i + 1];
100101
101-
// loop through the token text to look for "(" and ")" characters
102-
const tokenLength = (token.text || '').length;
103-
// eslint-disable-next-line no-plusplus
104-
for (let k = 0; k < tokenLength; k++) {
105-
if (token.text.charAt(k) === '(') {
106-
numUnclosedParens += 1;
107-
// keep track of the token/character position of the first "("
108-
if (openParenCharIndex == null) {
109-
openParenCharIndex = k;
110-
openParenTokenIndex = i;
102+
// keep track of the index of the first keyword token
103+
if (!firstKeywordTokenIndex && token.kind === TokenKind.keyword) {
104+
firstKeywordTokenIndex = i;
105+
}
106+
107+
// loop through the token text to look for "(" and ")" characters after
108+
// we've already encountered the first keyword
109+
if (firstKeywordTokenIndex !== null) {
110+
const tokenLength = (token.text || '').length;
111+
// eslint-disable-next-line no-plusplus
112+
for (let k = 0; k < tokenLength; k++) {
113+
if (token.text.charAt(k) === '(') {
114+
numUnclosedParens += 1;
115+
// keep track of the token/character position of the first "("
116+
if (openParenCharIndex == null) {
117+
openParenCharIndex = k;
118+
openParenTokenIndex = i;
119+
}
111120
}
112-
}
113121
114-
if (token.text.charAt(k) === ')') {
115-
numUnclosedParens -= 1;
116-
// if this ")" balances out the number of "(" characters that have
117-
// been seen, this is the one that pairs up with the first one
118-
if (
119-
openParenTokenIndex !== null
120-
&& closeParenTokenIndex == null
121-
&& numUnclosedParens === 0
122-
) {
123-
closeParenCharIndex = k;
124-
closeParenTokenIndex = i;
125-
break;
122+
if (token.text.charAt(k) === ')') {
123+
numUnclosedParens -= 1;
124+
// if this ")" balances out the number of "(" characters that have
125+
// been seen, this is the one that pairs up with the first one
126+
if (
127+
openParenTokenIndex !== null
128+
&& closeParenTokenIndex == null
129+
&& numUnclosedParens === 0
130+
) {
131+
closeParenCharIndex = k;
132+
closeParenTokenIndex = i;
133+
break;
134+
}
126135
}
127136
}
128137
}

tests/unit/components/DocumentationTopic/PrimaryContent/DeclarationSource.spec.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,162 @@ func foo(bar: @escaping () -> ()) -> Int`,
872872
`func qux(
873873
@StringBuilder a: () -> String,
874874
b: Int
875+
)`,
876+
);
877+
});
878+
879+
it('indents params properly for functions prefixed with attributes which take arguments', () => {
880+
const tokens = [
881+
{
882+
kind: TokenKind.attribute,
883+
text: '@objc',
884+
},
885+
{
886+
kind: TokenKind.text,
887+
text: '(quxA:B:) ',
888+
},
889+
{
890+
kind: TokenKind.keyword,
891+
text: 'func',
892+
},
893+
{
894+
kind: TokenKind.text,
895+
text: ' ',
896+
},
897+
{
898+
kind: TokenKind.identifier,
899+
text: 'quxqux',
900+
},
901+
{
902+
kind: TokenKind.text,
903+
text: '(',
904+
},
905+
{
906+
kind: TokenKind.externalParam,
907+
text: 'a',
908+
},
909+
{
910+
kind: TokenKind.text,
911+
text: ': ',
912+
},
913+
{
914+
kind: TokenKind.typeIdentifier,
915+
text: 'Int',
916+
},
917+
{
918+
kind: TokenKind.text,
919+
text: ', ',
920+
},
921+
{
922+
kind: TokenKind.externalParam,
923+
text: 'b',
924+
},
925+
{
926+
kind: TokenKind.text,
927+
text: ': ',
928+
},
929+
{
930+
kind: TokenKind.typeIdentifier,
931+
text: 'Int',
932+
},
933+
{
934+
kind: TokenKind.text,
935+
text: ') -> ',
936+
},
937+
{
938+
kind: TokenKind.typeIdentifier,
939+
text: 'Int',
940+
},
941+
];
942+
943+
// Before:
944+
// @objc(quxA:B:) func quxqux(a: Int, b: Int) -> Int
945+
//
946+
// After:
947+
// @objc(quxA:B:)
948+
// func quxqux(
949+
// a: Int,
950+
// b: Int
951+
// ) -> Int
952+
const wrapper = mountWithTokens(tokens);
953+
const tokenComponents = wrapper.findAll(Token);
954+
expect(getText(tokenComponents)).toBe(
955+
`@objc(quxA:B:)
956+
func quxqux(
957+
a: Int,
958+
b: Int
959+
) -> Int`,
960+
);
961+
});
962+
963+
it('indents params properly for initializers prefixed with attributes which take arguments', () => {
964+
const tokens = [
965+
{
966+
kind: TokenKind.attribute,
967+
text: '@objc',
968+
},
969+
{
970+
kind: TokenKind.text,
971+
text: '(initWithA:B:) ',
972+
},
973+
{
974+
kind: TokenKind.keyword,
975+
text: 'init',
976+
},
977+
{
978+
kind: TokenKind.text,
979+
text: '(',
980+
},
981+
{
982+
kind: TokenKind.externalParam,
983+
text: 'a',
984+
},
985+
{
986+
kind: TokenKind.text,
987+
text: ': ',
988+
},
989+
{
990+
kind: TokenKind.typeIdentifier,
991+
text: 'Int',
992+
},
993+
{
994+
kind: TokenKind.text,
995+
text: ', ',
996+
},
997+
{
998+
kind: TokenKind.externalParam,
999+
text: 'b',
1000+
},
1001+
{
1002+
kind: TokenKind.text,
1003+
text: ': ',
1004+
},
1005+
{
1006+
kind: TokenKind.typeIdentifier,
1007+
text: 'Int',
1008+
},
1009+
{
1010+
kind: TokenKind.text,
1011+
text: ')',
1012+
},
1013+
];
1014+
1015+
// Before:
1016+
// @objc(initWithA:B:) init(a: Int, b: Int)
1017+
//
1018+
// After:
1019+
// @objc(initWithA:B:)
1020+
// init(
1021+
// a: Int,
1022+
// b: Int
1023+
// )
1024+
const wrapper = mountWithTokens(tokens);
1025+
const tokenComponents = wrapper.findAll(Token);
1026+
expect(getText(tokenComponents)).toBe(
1027+
`@objc(initWithA:B:)
1028+
init(
1029+
a: Int,
1030+
b: Int
8751031
)`,
8761032
);
8771033
});

0 commit comments

Comments
 (0)