Skip to content

Commit 1f0979b

Browse files
authored
Chained member access (#557)
1 parent 5c99a28 commit 1f0979b

File tree

7 files changed

+145
-51
lines changed

7 files changed

+145
-51
lines changed

src/nodes/IndexAccess.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
const {
22
doc: {
3-
builders: { group, indent, softline }
3+
builders: { group, indent, label, softline }
44
}
55
} = require('prettier');
66

7+
let indexAccessId = 0;
8+
79
const IndexAccess = {
8-
print: ({ path, print }) => [
9-
path.call(print, 'base'),
10-
'[',
11-
group([indent([softline, path.call(print, 'index')]), softline]),
12-
']'
13-
]
10+
print: ({ path, print }) => {
11+
const indexAccessLabel = {
12+
type: 'IndexAccess',
13+
groupId: `IndexAccess-${indexAccessId}`
14+
};
15+
indexAccessId += 1;
16+
17+
return label(JSON.stringify(indexAccessLabel), [
18+
path.call(print, 'base'),
19+
'[',
20+
group([indent([softline, path.call(print, 'index')]), softline], {
21+
id: indexAccessLabel.groupId
22+
}),
23+
']'
24+
]);
25+
}
1426
};
1527

1628
module.exports = IndexAccess;

src/nodes/MemberAccess.js

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const {
22
doc: {
3-
builders: { group, ifBreak, indent, softline }
3+
builders: { group, ifBreak, indent, label, softline }
44
}
55
} = require('prettier');
66

@@ -47,43 +47,106 @@ const isEndOfChain = (node, path) => {
4747
return true;
4848
};
4949

50-
const shallIndent = (path) => {
51-
let i = 0;
52-
let elderNode = path.getParentNode(i);
53-
while (elderNode) {
54-
if (
55-
elderNode.type === 'VariableDeclarationStatement' ||
56-
elderNode.type === 'BinaryOperation'
57-
)
58-
return false;
59-
i += 1;
60-
elderNode = path.getParentNode(i);
61-
}
62-
return true;
50+
/**
51+
* processChain expects the doc[] of the full chain of MemberAccess.
52+
*
53+
* It uses the separator label to split the chain into 2 arrays.
54+
* The first array is the doc[] corresponding to the first element before the
55+
* first separator.
56+
* The second array contains the rest of the chain.
57+
*
58+
* The indentation of the whole chain depends on the result of the first
59+
* element.
60+
*
61+
* If the first element breaks into multiple lines, we won't indent the rest of
62+
* the chain as the last line (most likely a closing parentheses) won't be
63+
* indented.
64+
*
65+
* i.e.
66+
* ```
67+
* functionCall(
68+
* arg1,
69+
* arg2
70+
* ).rest.of.chain
71+
*
72+
* functionCall(
73+
* arg1,
74+
* arg2
75+
* )
76+
* .long
77+
* .rest
78+
* .of
79+
* .chain
80+
* ```
81+
*
82+
* If the first element doesn't break into multiple lines we treat the rest of
83+
* the chain as a normal chain and proceed to indent it.
84+
*
85+
*
86+
* i.e.
87+
* ```
88+
* a = functionCall(arg1, arg2).rest.of.chain
89+
*
90+
* b = functionCall(arg1, arg2)
91+
* .long
92+
* .rest
93+
* .of
94+
* .chain
95+
* ```
96+
*
97+
* NOTE: As described in the examples above, the rest of the chain will be grouped
98+
* and try to stay in the same line as the end of the first element.
99+
*
100+
* @param {doc[]} chain is the full chain of MemberAccess
101+
* @returns a processed doc[] with the proper grouping and indentation ready to
102+
* be printed.
103+
*/
104+
const processChain = (chain) => {
105+
const firstSeparatorIndex = chain.findIndex((element) => {
106+
if (element.label) {
107+
return JSON.parse(element.label).type === 'separator';
108+
}
109+
return false;
110+
});
111+
// We fetch the groupId from the firstSeparator
112+
const { groupId } = JSON.parse(chain[firstSeparatorIndex].label);
113+
// The doc[] before the first separator
114+
const firstExpression = chain.slice(0, firstSeparatorIndex);
115+
// The doc[] containing the rest of the chain
116+
const restOfChain = group(chain.slice(firstSeparatorIndex));
117+
118+
return groupId
119+
? [
120+
...firstExpression,
121+
ifBreak(restOfChain, indent(restOfChain), { groupId })
122+
]
123+
: [...firstExpression, indent(restOfChain)];
63124
};
64125

65126
const MemberAccess = {
66127
print: ({ node, path, print }) => {
67128
let expressionDoc = path.call(print, 'expression');
68-
let separator = [softline, '.'];
69-
let labelData;
129+
const separatorLabel = {
130+
type: 'separator'
131+
};
132+
70133
if (expressionDoc.label) {
71-
labelData = JSON.parse(expressionDoc.label);
134+
const labelData = JSON.parse(expressionDoc.label);
135+
if (labelData && labelData.groupId) {
136+
// if there's a groupId in the data, we pass it to the separator as
137+
// this doc[] is going to be stripped of it's metadata
138+
separatorLabel.groupId = labelData.groupId;
139+
}
72140
expressionDoc = expressionDoc.contents.flat();
73141
}
74-
if (labelData && labelData.groupId) {
75-
separator = ifBreak('.', [softline, '.'], {
76-
groupId: labelData.groupId
77-
});
78-
}
79142

80143
const doc = [
81144
expressionDoc,
82-
shallIndent(path) ? indent(separator) : separator,
145+
label(JSON.stringify(separatorLabel), [softline, '.']),
83146
node.memberName
84147
].flat();
85148

86-
return isEndOfChain(node, path) ? group(doc) : doc;
149+
return isEndOfChain(node, path) ? group(processChain(doc)) : doc;
87150
}
88151
};
89152

tests/format/FunctionCalls/__snapshots__/jsfmt.spec.js.snap

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ contract FunctionCalls {
5252
contract FunctionCalls {
5353
function foo() {
5454
address veryLongValidatorAddress = veryVeryVeryLongSignature
55-
.popLast20Bytes();
55+
.popLast20Bytes();
5656
}
5757
5858
function foo() {
@@ -80,7 +80,8 @@ contract FunctionCalls {
8080
keccak256(req.data)
8181
)
8282
)
83-
)._hashTypedDataV2(
83+
)
84+
._hashTypedDataV2(
8485
keccak256(
8586
abi.encode(
8687
TYPEHASH,
@@ -92,7 +93,8 @@ contract FunctionCalls {
9293
keccak256(req.data)
9394
)
9495
)
95-
)._hashTypedDataV3(
96+
)
97+
._hashTypedDataV3(
9698
keccak256(
9799
abi.encode(
98100
TYPEHASH,
@@ -104,7 +106,8 @@ contract FunctionCalls {
104106
keccak256(req.data)
105107
)
106108
)
107-
).recover(signature);
109+
)
110+
.recover(signature);
108111
signer = _hashTypedDataV1(
109112
keccak256(
110113
abi.encode(
@@ -117,7 +120,8 @@ contract FunctionCalls {
117120
keccak256(req.data)
118121
)
119122
)
120-
)._hashTypedDataV2(
123+
)
124+
._hashTypedDataV2(
121125
keccak256(
122126
abi.encode(
123127
TYPEHASH,
@@ -129,7 +133,8 @@ contract FunctionCalls {
129133
keccak256(req.data)
130134
)
131135
)
132-
)._hashTypedDataV3(
136+
)
137+
._hashTypedDataV3(
133138
keccak256(
134139
abi.encode(
135140
TYPEHASH,
@@ -141,7 +146,8 @@ contract FunctionCalls {
141146
keccak256(req.data)
142147
)
143148
)
144-
).recover(signature);
149+
)
150+
.recover(signature);
145151
return _nonces[req.from] == req.nonce && signer == req.from;
146152
}
147153
}
@@ -200,7 +206,7 @@ contract FunctionCalls {
200206
contract FunctionCalls {
201207
function foo() {
202208
address veryLongValidatorAddress = veryVeryVeryLongSignature
203-
.popLast20Bytes();
209+
.popLast20Bytes();
204210
}
205211
206212
function foo() {
@@ -228,7 +234,8 @@ contract FunctionCalls {
228234
keccak256(req.data)
229235
)
230236
)
231-
)._hashTypedDataV2(
237+
)
238+
._hashTypedDataV2(
232239
keccak256(
233240
abi.encode(
234241
TYPEHASH,
@@ -240,7 +247,8 @@ contract FunctionCalls {
240247
keccak256(req.data)
241248
)
242249
)
243-
)._hashTypedDataV3(
250+
)
251+
._hashTypedDataV3(
244252
keccak256(
245253
abi.encode(
246254
TYPEHASH,
@@ -252,7 +260,8 @@ contract FunctionCalls {
252260
keccak256(req.data)
253261
)
254262
)
255-
).recover(signature);
263+
)
264+
.recover(signature);
256265
signer = _hashTypedDataV1(
257266
keccak256(
258267
abi.encode(
@@ -265,7 +274,8 @@ contract FunctionCalls {
265274
keccak256(req.data)
266275
)
267276
)
268-
)._hashTypedDataV2(
277+
)
278+
._hashTypedDataV2(
269279
keccak256(
270280
abi.encode(
271281
TYPEHASH,
@@ -277,7 +287,8 @@ contract FunctionCalls {
277287
keccak256(req.data)
278288
)
279289
)
280-
)._hashTypedDataV3(
290+
)
291+
._hashTypedDataV3(
281292
keccak256(
282293
abi.encode(
283294
TYPEHASH,
@@ -289,7 +300,8 @@ contract FunctionCalls {
289300
keccak256(req.data)
290301
)
291302
)
292-
).recover(signature);
303+
)
304+
.recover(signature);
293305
return _nonces[req.from] == req.nonce && signer == req.from;
294306
}
295307
}

tests/format/Issues/__snapshots__/jsfmt.spec.js.snap

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ contract Example {
3232
function example(address token, uint256 amount) public {
3333
balanceStates[msg.sender][token].balance = balanceStates[msg.sender][
3434
token
35-
]
36-
.balance
37-
.sub(amount);
35+
].balance.sub(amount);
3836
}
3937
}
4038
@@ -59,8 +57,7 @@ contract Issue289 {
5957
function f() {
6058
address[] storage proposalValidators = ethProposals[_blockNumber][
6159
_proposalId
62-
]
63-
.proposalValidators;
60+
].proposalValidators;
6461
}
6562
}
6663

tests/format/MemberAccess/MemberAccess.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pragma solidity ^0.5.0;
22

33
contract MemberAccess {
44
function() {
5+
int256 amount = SafeCast.toInt256(10**(18 - underlyingAssetDecimals)).neg();
6+
longNameAmount = SafeCast.toInt256(10**(18 - underlyingAssetDecimals)).neg();
57
a.b.c.d;
68
a.b().c.d;
79
a.b.c.d();

tests/format/MemberAccess/__snapshots__/jsfmt.spec.js.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pragma solidity ^0.5.0;
1010
1111
contract MemberAccess {
1212
function() {
13+
int256 amount = SafeCast.toInt256(10**(18 - underlyingAssetDecimals)).neg();
14+
longNameAmount = SafeCast.toInt256(10**(18 - underlyingAssetDecimals)).neg();
1315
a.b.c.d;
1416
a.b().c.d;
1517
a.b.c.d();
@@ -67,6 +69,12 @@ pragma solidity ^0.5.0;
6769
6870
contract MemberAccess {
6971
function() {
72+
int256 amount = SafeCast
73+
.toInt256(10**(18 - underlyingAssetDecimals))
74+
.neg();
75+
longNameAmount = SafeCast
76+
.toInt256(10**(18 - underlyingAssetDecimals))
77+
.neg();
7078
a.b.c.d;
7179
a.b().c.d;
7280
a.b.c.d();

tests/format/SplittableCommodity/__snapshots__/jsfmt.spec.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ contract SplittableCommodity is MintableCommodity {
8888
uint256 _amount
8989
) public whenNotPaused {
9090
address supplierProxy = IContractRegistry(contractRegistry)
91-
.getLatestProxyAddr("Supplier");
91+
.getLatestProxyAddr("Supplier");
9292
require(
9393
msg.sender ==
9494
IContractRegistry(contractRegistry).getLatestProxyAddr(

0 commit comments

Comments
 (0)