Skip to content

Commit e14a599

Browse files
authored
Render all attributes for Swift symbol declarations on a single line (#633)
A previous change was made so that each attribute was broken onto its own line, but this attempts to collapse all attributes onto their own single line. Example: ```swift // before @discardableResult @objc(bar) func foo() -> Int // after @discardableResult @objc(bar) func foo() -> Int ``` Resolves: rdar://108798969
1 parent 456ea5c commit e14a599

File tree

2 files changed

+71
-89
lines changed

2 files changed

+71
-89
lines changed

src/components/DocumentationTopic/PrimaryContent/DeclarationSource.vue

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ export default {
8484
let indentedParams = false;
8585
const newTokens = [];
8686
let i = 0;
87-
let j = 1;
8887
let openParenTokenIndex = null;
8988
let openParenCharIndex = null;
9089
let closeParenTokenIndex = null;
@@ -96,7 +95,8 @@ export default {
9695
// keep track of the current token and the next one (if any)
9796
const token = tokens[i];
9897
const newToken = { ...token };
99-
const nextToken = j < tokens.length ? tokens[j] : undefined;
98+
const prevToken = tokens[i - 1];
99+
const nextToken = tokens[i + 1];
100100
101101
// loop through the token text to look for "(" and ")" characters
102102
const tokenLength = (token.text || '').length;
@@ -127,15 +127,13 @@ export default {
127127
}
128128
}
129129
130-
// check if this is a text token following an attribute token
131-
// so we can insert a newline here and split each attribute onto its
132-
// own line
133-
//
134-
// we want to avoid doing this when the attribute is encountered
135-
// in a param clause for attributes like `@escaping`
136-
if (token.kind === TokenKind.text && i > 0
137-
&& tokens[i - 1].kind === TokenKind.attribute
138-
&& numUnclosedParens === 0) {
130+
// Find the text following the last attribute preceding the start of a
131+
// declaration by determining if this is the text token in between an
132+
// attribute and a keyword outside of any parameter clause. A newline
133+
// will be added to break these attributes onto their own single line.
134+
if (token.kind === TokenKind.text && numUnclosedParens === 0
135+
&& prevToken && prevToken.kind === TokenKind.attribute
136+
&& nextToken && nextToken.kind === TokenKind.keyword) {
139137
newToken.text = `${token.text.trimEnd()}\n`;
140138
}
141139
@@ -150,7 +148,6 @@ export default {
150148
151149
newTokens.push(newToken);
152150
i += 1;
153-
j += 1;
154151
}
155152
156153
// if we indented some params, we want to find the opening "(" symbol

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

Lines changed: 62 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ describe('Swift function/initializer formatting', () => {
126126
},
127127
});
128128

129+
const getText = tokens => tokens.wrappers.reduce((txt, token) => (
130+
`${txt}${token.props('text')}`
131+
), '');
132+
129133
it('does not add any whitespace for single-param symbols', () => {
130134
// Before:
131135
// init(_ foo: Foo)
@@ -168,14 +172,8 @@ describe('Swift function/initializer formatting', () => {
168172
},
169173
];
170174
const wrapper = mountWithTokens(tokens);
171-
172175
const tokenComponents = wrapper.findAll(Token);
173-
expect(tokenComponents.length).toBe(tokens.length);
174-
tokens.forEach((token, i) => {
175-
const tokenComponent = tokenComponents.at(i);
176-
expect(tokenComponent.props('kind')).toBe(token.kind);
177-
expect(tokenComponent.props('text')).toBe(token.text);
178-
});
176+
expect(getText(tokenComponents)).toBe('init(_ foo: Foo)');
179177
});
180178

181179
it('breaks apart each param onto its own line for multi-param symbols', () => {
@@ -185,7 +183,7 @@ describe('Swift function/initializer formatting', () => {
185183
// After:
186184
// func foo(
187185
// _ a: A,
188-
// _ b: B,
186+
// _ b: B
189187
// ) -> Bar
190188
const tokens = [
191189
{
@@ -263,22 +261,12 @@ describe('Swift function/initializer formatting', () => {
263261
const wrapper = mountWithTokens(tokens);
264262

265263
const tokenComponents = wrapper.findAll(Token);
266-
expect(tokenComponents.length).toBe(tokens.length);
267-
268-
const modifiedTokenIndexes = new Set([3, 9, 15]);
269-
tokens.forEach((token, i) => {
270-
const tokenComponent = tokenComponents.at(i);
271-
expect(tokenComponent.props('kind')).toBe(token.kind);
272-
if (modifiedTokenIndexes.has(i)) {
273-
expect(tokenComponent.props('text')).not.toBe(token.text);
274-
} else {
275-
expect(tokenComponent.props('text')).toBe(token.text);
276-
}
277-
});
278-
279-
expect(tokenComponents.at(3).props('text')).toBe('(\n ');
280-
expect(tokenComponents.at(9).props('text')).toBe(',\n ');
281-
expect(tokenComponents.at(15).props('text')).toBe('\n) -> ');
264+
expect(getText(tokenComponents)).toBe(
265+
`func foo(
266+
_ a: A,
267+
_ b: B
268+
) -> Bar`,
269+
);
282270
});
283271

284272
it('breaks apart each param onto its own line for a tuple return type', () => {
@@ -288,7 +276,7 @@ describe('Swift function/initializer formatting', () => {
288276
// After:
289277
// func foo(
290278
// _ a: A,
291-
// _ b: B,
279+
// _ b: B
292280
// ) -> (A, B)
293281
const tokens = [
294282
{
@@ -379,34 +367,24 @@ describe('Swift function/initializer formatting', () => {
379367
const wrapper = mountWithTokens(tokens);
380368

381369
const tokenComponents = wrapper.findAll(Token);
382-
expect(tokenComponents.length).toBe(tokens.length);
383-
384-
const modifiedTokenIndexes = new Set([3, 9, 15]);
385-
tokens.forEach((token, i) => {
386-
const tokenComponent = tokenComponents.at(i);
387-
expect(tokenComponent.props('kind')).toBe(token.kind);
388-
if (modifiedTokenIndexes.has(i)) {
389-
expect(tokenComponent.props('text')).not.toBe(token.text);
390-
} else {
391-
expect(tokenComponent.props('text')).toBe(token.text);
392-
}
393-
});
394-
395-
expect(tokenComponents.at(3).props('text')).toBe('(\n ');
396-
expect(tokenComponents.at(9).props('text')).toBe(',\n ');
397-
expect(tokenComponents.at(15).props('text')).toBe('\n) -> (');
370+
expect(getText(tokenComponents)).toBe(
371+
`func foo(
372+
_ a: A,
373+
_ b: B
374+
) -> (A, B)`,
375+
);
398376
});
399377

400378
it('breaks apart parameters in functions with generic where clauses', () => {
401379
/* eslint-disable max-len */
402380
// Before:
403-
// public func f(t: T, u: U) where T : Sequence, U : Sequence, T.Iterator.Element : Equatable, T.Iterator.Element == U.Iterator.Element
381+
// public func f(t: T, u: U) where U : Sequence, T : Sequence, T.Element : Equatable, U.Element == T.Element
404382
//
405383
// After:
406384
// public func f(
407385
// t: T,
408-
// u: U,
409-
// ) where T : Sequence, U : Sequence, T.Iterator.Element : Equatable, T.Iterator.Element == U.Iterator.Element
386+
// u: U
387+
// ) where U : Sequence, T : Sequence, T.Element : Equatable, U.Element == T.Element
410388
/* eslint-enable max-len */
411389
const tokens = [
412390
{
@@ -567,22 +545,12 @@ describe('Swift function/initializer formatting', () => {
567545
const wrapper = mountWithTokens(tokens);
568546

569547
const tokenComponents = wrapper.findAll(Token);
570-
expect(tokenComponents.length).toBe(tokens.length);
571-
572-
const modifiedTokenIndexes = new Set([5, 9, 13]);
573-
tokens.forEach((token, i) => {
574-
const tokenComponent = tokenComponents.at(i);
575-
expect(tokenComponent.props('kind')).toBe(token.kind);
576-
if (modifiedTokenIndexes.has(i)) {
577-
expect(tokenComponent.props('text')).not.toBe(token.text);
578-
} else {
579-
expect(tokenComponent.props('text')).toBe(token.text);
580-
}
581-
});
582-
583-
expect(tokenComponents.at(5).props('text')).toBe('(\n ');
584-
expect(tokenComponents.at(9).props('text')).toBe(',\n ');
585-
expect(tokenComponents.at(13).props('text')).toBe('\n) ');
548+
expect(getText(tokenComponents)).toBe(
549+
`public func f(
550+
t: T,
551+
u: U
552+
) where U : Sequence, T : Sequence, U.Element : Equatable, U.Element == T.Element`,
553+
);
586554
});
587555

588556
it('indents parameters using provided/customizable indentation width', () => {
@@ -600,7 +568,7 @@ describe('Swift function/initializer formatting', () => {
600568
// After:
601569
// func foo(
602570
// _ a: A,
603-
// _ b: B,
571+
// _ b: B
604572
// ) -> Bar
605573
const tokens = [
606574
{
@@ -678,22 +646,22 @@ describe('Swift function/initializer formatting', () => {
678646
const wrapper = mountWithTokens(tokens);
679647

680648
const tokenComponents = wrapper.findAll(Token);
681-
expect(tokenComponents.length).toBe(tokens.length);
682-
// should be indented with 2 spaces now instead of the default of 4 spaces
683-
expect(tokenComponents.at(3).props('text')).toBe('(\n ');
684-
expect(tokenComponents.at(9).props('text')).toBe(',\n ');
685-
expect(tokenComponents.at(15).props('text')).toBe('\n) -> ');
649+
expect(getText(tokenComponents)).toBe(
650+
`func foo(
651+
_ a: A,
652+
_ b: B
653+
) -> Bar`,
654+
);
686655

687656
themeSettingsState.theme = originalTheme;
688657
});
689658

690-
it('breaks attributes onto their own lines', () => {
659+
it('breaks attributes onto their own line', () => {
691660
// Before:
692661
// @discardableResult @objc(baz) func foobarbaz() -> Int
693662
//
694663
// After:
695-
// @discardableResult
696-
// @objc(baz)
664+
// @discardableResult @objc(baz)
697665
// func foobarbaz() -> Int
698666
const tokens = [
699667
{
@@ -737,9 +705,10 @@ describe('Swift function/initializer formatting', () => {
737705
const wrapper = mountWithTokens(tokens);
738706

739707
const tokenComponents = wrapper.findAll(Token);
740-
expect(tokenComponents.length).toBe(tokens.length);
741-
expect(tokenComponents.at(1).props('text')).toBe('\n');
742-
expect(tokenComponents.at(3).props('text')).toBe('(baz)\n');
708+
expect(getText(tokenComponents)).toBe(
709+
`@discardableResult @objc(baz)
710+
func foobarbaz() -> Int`,
711+
);
743712
});
744713

745714
it('does not add newlines to attributes within param clause', () => {
@@ -782,11 +751,27 @@ describe('Swift function/initializer formatting', () => {
782751
text: ')',
783752
},
784753
];
785-
const wrapper = mountWithTokens(tokens);
754+
let wrapper = mountWithTokens(tokens);
786755

787-
const tokenComponents = wrapper.findAll(Token);
788-
expect(tokenComponents.length).toBe(tokens.length);
789-
expect(tokenComponents.at(6).props('text')).toBe(tokens[6].text);
790-
expect(tokenComponents.at(7).props('text')).toBe(tokens[7].text);
756+
let tokenComponents = wrapper.findAll(Token);
757+
expect(getText(tokenComponents)).toBe('func foo(bar: @escaping () -> ())');
758+
759+
// @discardableResult func foo(bar: @escaping () -> ()) -> Int
760+
wrapper = mountWithTokens([
761+
{ kind: 'attribute', text: '@discardableResult' },
762+
{ kind: 'text', text: ' ' },
763+
...tokens,
764+
{ kind: 'text', text: ' -> ' },
765+
{
766+
kind: 'typeIdentifier',
767+
identifier: 'doc://com.example/documentation/blah/int',
768+
text: 'Int',
769+
},
770+
]);
771+
tokenComponents = wrapper.findAll(Token);
772+
expect(getText(tokenComponents)).toBe(
773+
`@discardableResult
774+
func foo(bar: @escaping () -> ()) -> Int`,
775+
);
791776
});
792777
});

0 commit comments

Comments
 (0)