Skip to content

Commit 5b577be

Browse files
authored
Placing comments in the middle of a member access chained expression (#1461)
1 parent 6e10f9c commit 5b577be

9 files changed

Lines changed: 132 additions & 48 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { NonterminalKind } from '@nomicfoundation/slang/cst';
2+
import { util } from 'prettier';
3+
4+
import type { HandlerParams } from './types.d.ts';
5+
6+
const { addLeadingComment, addTrailingComment } = util;
7+
8+
export default function handleMemberAccessExpressionComments({
9+
precedingNode,
10+
enclosingNode,
11+
followingNode,
12+
comment
13+
}: HandlerParams): boolean {
14+
if (enclosingNode?.kind !== NonterminalKind.MemberAccessExpression) {
15+
return false;
16+
}
17+
18+
if (followingNode === enclosingNode.operand) {
19+
addLeadingComment(followingNode, comment);
20+
return true;
21+
}
22+
23+
if (
24+
precedingNode === enclosingNode.operand &&
25+
followingNode === enclosingNode.member
26+
) {
27+
addTrailingComment(precedingNode, comment);
28+
return true;
29+
}
30+
31+
if (precedingNode === enclosingNode.member) {
32+
addTrailingComment(precedingNode, comment);
33+
return true;
34+
}
35+
36+
return false;
37+
}

src/slang-comments/handlers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import handleElseBranchComments from './handle-else-branch-comments.js';
55
import handleIfStatementComments from './handle-if-statement-comments.js';
66
import handleInterfaceDefinitionComments from './handle-interface-definition-comments.js';
77
import handleLibraryDefinitionComments from './handle-library-definition-comments.js';
8+
import handleMemberAccessExpressionComments from './handle-member-access-expression-comments.js';
89
import handleModifierInvocationComments from './handle-modifier-invocation-comments.js';
910
import handleParametersDeclarationComments from './handle-parameters-declaration-comments.js';
1011
import handlePositionalArgumentsDeclarationComments from './handle-positional-arguments-declaration-comments.js';
@@ -22,6 +23,7 @@ export default [
2223
handleIfStatementComments,
2324
handleInterfaceDefinitionComments,
2425
handleLibraryDefinitionComments,
26+
handleMemberAccessExpressionComments,
2527
handleModifierInvocationComments,
2628
handleParametersDeclarationComments,
2729
handlePositionalArgumentsDeclarationComments,
Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { NonterminalKind } from '@nomicfoundation/slang/cst';
2-
import { doc } from 'prettier';
3-
import { isLabel } from '../slang-utils/is-label.js';
4-
import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js';
52
import { extractVariant } from '../slang-utils/extract-variant.js';
3+
import { printPossibleMemberAccessChainItem } from '../slang-printers/print-member-access-chain-item.js';
64
import { SlangNode } from './SlangNode.js';
75
import { Expression } from './Expression.js';
86
import { ArgumentsDeclaration } from './ArgumentsDeclaration.js';
@@ -12,8 +10,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier';
1210
import type { CollectedMetadata, PrintFunction } from '../types.d.ts';
1311
import type { AstNode } from './types.d.ts';
1412

15-
const { label } = doc.builders;
16-
1713
export class FunctionCallExpression extends SlangNode {
1814
readonly kind = NonterminalKind.FunctionCallExpression;
1915

@@ -39,20 +35,9 @@ export class FunctionCallExpression extends SlangNode {
3935
}
4036

4137
print(path: AstPath<FunctionCallExpression>, print: PrintFunction): Doc {
42-
const operand = path.call(print, 'operand');
43-
const argumentsDoc = path.call(print, 'arguments');
44-
45-
// If we are at the end of a MemberAccessChain we should indent the
46-
// arguments accordingly.
47-
if (isLabel(operand) && operand.label === 'MemberAccessChain') {
48-
// We wrap the expression in a label in case there is an IndexAccess or
49-
// a FunctionCall following this IndexAccess.
50-
return label(
51-
'MemberAccessChain',
52-
printGroupAndIndentIfBreakPair(operand.contents, argumentsDoc)
53-
);
54-
}
55-
56-
return [operand, argumentsDoc].flat();
38+
return printPossibleMemberAccessChainItem(
39+
path.call(print, 'operand'),
40+
path.call(print, 'arguments')
41+
);
5742
}
5843
}

src/slang-nodes/IndexAccessExpression.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { NonterminalKind } from '@nomicfoundation/slang/cst';
2-
import { doc } from 'prettier';
32
import { printSeparatedItem } from '../slang-printers/print-separated-item.js';
4-
import { printGroupAndIndentIfBreakPair } from '../slang-printers/print-group-and-indent-if-break-pair.js';
5-
import { isLabel } from '../slang-utils/is-label.js';
63
import { extractVariant } from '../slang-utils/extract-variant.js';
4+
import { printPossibleMemberAccessChainItem } from '../slang-printers/print-member-access-chain-item.js';
75
import { SlangNode } from './SlangNode.js';
86
import { Expression } from './Expression.js';
97
import { IndexAccessEnd } from './IndexAccessEnd.js';
@@ -13,8 +11,6 @@ import type { AstPath, Doc, ParserOptions } from 'prettier';
1311
import type { CollectedMetadata, PrintFunction } from '../types.d.ts';
1412
import type { AstNode } from './types.d.ts';
1513

16-
const { label } = doc.builders;
17-
1814
export class IndexAccessExpression extends SlangNode {
1915
readonly kind = NonterminalKind.IndexAccessExpression;
2016

@@ -47,24 +43,10 @@ export class IndexAccessExpression extends SlangNode {
4743
}
4844

4945
print(path: AstPath<IndexAccessExpression>, print: PrintFunction): Doc {
50-
const operand = path.call(print, 'operand');
51-
const indexDoc = [
46+
return printPossibleMemberAccessChainItem(path.call(print, 'operand'), [
5247
'[',
5348
printSeparatedItem([path.call(print, 'start'), path.call(print, 'end')]),
5449
']'
55-
];
56-
57-
// If we are at the end of a MemberAccessChain we should indent the
58-
// arguments accordingly.
59-
if (isLabel(operand) && operand.label === 'MemberAccessChain') {
60-
// We wrap the expression in a label in case there is an IndexAccess or
61-
// a FunctionCall following this IndexAccess.
62-
return label(
63-
'MemberAccessChain',
64-
printGroupAndIndentIfBreakPair(operand.contents, indexDoc)
65-
);
66-
}
67-
68-
return [operand, indexDoc].flat();
50+
]);
6951
}
7052
}

src/slang-nodes/MemberAccessExpression.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { doc } from 'prettier';
33
import { isLabel } from '../slang-utils/is-label.js';
44
import { extractVariant } from '../slang-utils/extract-variant.js';
55
import { isChainableExpression } from '../slang-utils/is-chainable-expression.js';
6+
import { memberAccessChainLabel } from '../slang-printers/print-member-access-chain-item.js';
67
import { SlangNode } from './SlangNode.js';
78
import { Expression } from './Expression.js';
89
import { TerminalNode } from './TerminalNode.js';
@@ -14,6 +15,8 @@ import type { AstNode, ChainableExpression, StrictAstNode } from './types.d.ts';
1415

1516
const { group, indent, label, softline } = doc.builders;
1617

18+
const separatorLabel = Symbol('separator');
19+
1720
function isEndOfChain(
1821
node: ChainableExpression,
1922
path: AstPath<StrictAstNode>
@@ -91,13 +94,13 @@ function isEndOfChain(
9194
* be printed.
9295
*/
9396
function processChain(chain: Doc[]): Doc {
94-
const firstSeparatorIndex = chain.findIndex(
95-
(element) => isLabel(element) && element.label === 'separator'
97+
const firstSeparatorIndex = chain.findIndex((element) =>
98+
isLabel(element, separatorLabel)
9699
);
97100

98101
// We wrap the expression in a label in case there is an IndexAccess or
99102
// a FunctionCall following this MemberAccess.
100-
return label('MemberAccessChain', [
103+
return label(memberAccessChainLabel, [
101104
// The doc[] before the first separator
102105
chain.slice(0, firstSeparatorIndex),
103106
// The doc[] containing the rest of the chain
@@ -128,9 +131,14 @@ export class MemberAccessExpression extends SlangNode {
128131
}
129132

130133
print(path: AstPath<MemberAccessExpression>, print: PrintFunction): Doc {
134+
let operandDoc = path.call(print, 'operand');
135+
if (Array.isArray(operandDoc)) {
136+
operandDoc = operandDoc.flat();
137+
}
138+
131139
const document = [
132-
path.call(print, 'operand'),
133-
label('separator', [softline, '.']),
140+
operandDoc,
141+
label(separatorLabel, [softline, '.']),
134142
path.call(print, 'member')
135143
].flat();
136144

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { doc } from 'prettier';
2+
import { isLabel } from '../slang-utils/is-label.js';
3+
import { printGroupAndIndentIfBreakPair } from './print-group-and-indent-if-break-pair.js';
4+
5+
import type { Doc } from 'prettier';
6+
7+
const { label } = doc.builders;
8+
9+
export const memberAccessChainLabel = Symbol('MemberAccessChain');
10+
11+
export function printPossibleMemberAccessChainItem(
12+
operand: Doc,
13+
predicate: Doc
14+
): Doc {
15+
// If we are at the end of a MemberAccessChain we should indent the
16+
// arguments accordingly.
17+
if (isLabel(operand, memberAccessChainLabel)) {
18+
// We wrap the expression in a label in case there is an IndexAccess or
19+
// a FunctionCall following Node.
20+
return label(
21+
memberAccessChainLabel,
22+
printGroupAndIndentIfBreakPair(operand.contents, predicate)
23+
);
24+
}
25+
26+
return [operand, predicate].flat();
27+
}

src/slang-utils/is-label.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import type { Doc, doc } from 'prettier';
22

3-
export function isLabel(document: Doc): document is doc.builders.Label {
4-
return (document as doc.builders.DocCommand).type === 'label';
3+
export function isLabel(
4+
document: Doc,
5+
value: symbol
6+
): document is doc.builders.Label {
7+
return (
8+
(document as doc.builders.DocCommand).type === 'label' &&
9+
(document as doc.builders.Label).label === value
10+
);
511
}

tests/format/MemberAccess/MemberAccess.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ contract MemberAccess {
6262
abi.encodeWithSelector(f.selector),
6363
abi.encode(returned1)
6464
);
65+
// Comments in between the chain
66+
game.
67+
// CONFIG
68+
// Do not touch
69+
config. // Window
70+
// Resolution 1000
71+
resolveWindow = 1000;
72+
config.defaultDelay = 0;
73+
begin
74+
.// Comment 1
75+
functionCall(/* Comment about parameter */ parameter)
76+
.// Comment 2
77+
end;
6578
}
6679
}
6780

tests/format/MemberAccess/__snapshots__/format.test.js.snap

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ contract MemberAccess {
7070
abi.encodeWithSelector(f.selector),
7171
abi.encode(returned1)
7272
);
73+
// Comments in between the chain
74+
game.
75+
// CONFIG
76+
// Do not touch
77+
config. // Window
78+
// Resolution 1000
79+
resolveWindow = 1000;
80+
config.defaultDelay = 0;
81+
begin
82+
.// Comment 1
83+
functionCall(/* Comment about parameter */ parameter)
84+
.// Comment 2
85+
end;
7386
}
7487
}
7588
@@ -206,6 +219,17 @@ contract MemberAccess {
206219
abi.encodeWithSelector(f.selector),
207220
abi.encode(returned1)
208221
);
222+
// Comments in between the chain
223+
game
224+
// CONFIG
225+
// Do not touch
226+
.config // Window
227+
// Resolution 1000
228+
.resolveWindow = 1000;
229+
config.defaultDelay = 0;
230+
begin // Comment 1
231+
.functionCall(/* Comment about parameter */ parameter) // Comment 2
232+
.end;
209233
}
210234
}
211235

0 commit comments

Comments
 (0)