Skip to content

Commit 794f8a6

Browse files
authored
Merge pull request #172 from prettier-solidity/bugfix/contract_definition_comments
Contract Definition comments
2 parents 4cd5412 + fb9d154 commit 794f8a6

File tree

14 files changed

+499
-78
lines changed

14 files changed

+499
-78
lines changed

src/clean.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// eslint-disable-next-line no-unused-vars
22
function clean(ast, newObj, parent) {
3-
['code', 'codeStart', 'loc', 'range'].forEach(name => {
3+
['code', 'codeStart', 'loc', 'range', 'raw'].forEach(name => {
44
delete newObj[name]; // eslint-disable-line no-param-reassign
55
});
66
}

src/comments/handler.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
const {
2+
handleOwnLineComment,
3+
handleEndOfLineComment,
4+
handleRemainingComment,
5+
isBlockComment
6+
} = require('../prettier-comments/language-js/comments');
7+
8+
const handleContractDefinitionComments = require('./handlers/ContractDefinition');
9+
10+
function solidityHandleOwnLineComment(
11+
comment,
12+
text,
13+
options,
14+
ast,
15+
isLastComment
16+
) {
17+
const { precedingNode, enclosingNode, followingNode } = comment;
18+
const handlerArguments = [
19+
text,
20+
precedingNode,
21+
enclosingNode,
22+
followingNode,
23+
comment,
24+
options
25+
];
26+
27+
if (
28+
handleContractDefinitionComments(...handlerArguments) ||
29+
handleOwnLineComment(comment, text, options, ast, isLastComment)
30+
) {
31+
return true;
32+
}
33+
return false;
34+
}
35+
36+
function solidityHandleEndOfLineComment(
37+
comment,
38+
text,
39+
options,
40+
ast,
41+
isLastComment
42+
) {
43+
const { precedingNode, enclosingNode, followingNode } = comment;
44+
const handlerArguments = [
45+
text,
46+
precedingNode,
47+
enclosingNode,
48+
followingNode,
49+
comment,
50+
options
51+
];
52+
53+
if (
54+
handleContractDefinitionComments(...handlerArguments) ||
55+
handleEndOfLineComment(comment, text, options, ast, isLastComment)
56+
) {
57+
return true;
58+
}
59+
return false;
60+
}
61+
62+
function solidityHandleRemainingComment(
63+
comment,
64+
text,
65+
options,
66+
ast,
67+
isLastComment
68+
) {
69+
const { precedingNode, enclosingNode, followingNode } = comment;
70+
const handlerArguments = [
71+
text,
72+
precedingNode,
73+
enclosingNode,
74+
followingNode,
75+
comment,
76+
options
77+
];
78+
79+
if (
80+
handleContractDefinitionComments(...handlerArguments) ||
81+
handleRemainingComment(comment, text, options, ast, isLastComment)
82+
) {
83+
return true;
84+
}
85+
return false;
86+
}
87+
88+
module.exports = {
89+
handleOwnLineComment: solidityHandleOwnLineComment,
90+
handleEndOfLineComment: solidityHandleEndOfLineComment,
91+
handleRemainingComment: solidityHandleRemainingComment,
92+
isBlockComment
93+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const {
2+
util: { addLeadingComment, addTrailingComment, addDanglingComment }
3+
} = require('prettier');
4+
const privateUtil = require('../../prettier-comments/common/util');
5+
6+
function handleContractDefinitionComments(
7+
text,
8+
precedingNode,
9+
enclosingNode,
10+
followingNode,
11+
comment,
12+
options
13+
) {
14+
if (!enclosingNode || enclosingNode.type !== 'ContractDefinition') {
15+
return false;
16+
}
17+
18+
// We unfortunately have no way using the AST or location of nodes to know
19+
// if the comment is positioned before the condition parenthesis:
20+
// contract a is abc /* comment */ {}
21+
// The only workaround I found is to look at the next character to see if
22+
// it is a {}.
23+
const nextCharacter = privateUtil.getNextNonSpaceNonCommentCharacter(
24+
text,
25+
comment,
26+
options.locEnd
27+
);
28+
29+
// The comment is behind the start of the Block `{}` or behind a base contract
30+
if (
31+
(followingNode && followingNode.type === 'InheritanceSpecifier') ||
32+
nextCharacter === '{'
33+
) {
34+
// In this scenario the comment belongs to a base contract.
35+
// contract A is B, /* comment for B */ C /* comment for C */ {}
36+
if (precedingNode && precedingNode.type === 'InheritanceSpecifier') {
37+
addTrailingComment(precedingNode, comment);
38+
return true;
39+
}
40+
41+
// In this scenario the comment belongs to the contract's name.
42+
// contract A /* comment for A */ is B, C {}
43+
// TODO: at the moment we prepended it but this should be kept after the name.
44+
addLeadingComment(enclosingNode, comment);
45+
return true;
46+
}
47+
48+
// When the contract is empty and contain comments.
49+
// Most likely disabling a linter rule.
50+
if (enclosingNode.subNodes.length === 0) {
51+
addDanglingComment(enclosingNode, comment);
52+
return true;
53+
}
54+
55+
return false;
56+
}
57+
58+
module.exports = handleContractDefinitionComments;

src/comments/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const handler = require('./handler');
2+
const printer = require('./printer');
3+
4+
module.exports = { handleComments: handler, printComment: printer };

src/comments/printer.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const {
2+
doc: {
3+
builders: { concat, hardline, join }
4+
}
5+
} = require('prettier');
6+
const { hasNewline } = require('../prettier-comments/common/util');
7+
8+
function isIndentableBlockComment(comment) {
9+
// If the comment has multiple lines and every line starts with a star
10+
// we can fix the indentation of each line. The stars in the `/*` and
11+
// `*/` delimiters are not included in the comment value, so add them
12+
// back first.
13+
const lines = `*${comment.raw}*`.split('\n');
14+
return lines.length > 1 && lines.every(line => line.trim()[0] === '*');
15+
}
16+
17+
function printIndentableBlockComment(comment) {
18+
const lines = comment.raw.split('\n');
19+
20+
return concat([
21+
'/*',
22+
join(
23+
hardline,
24+
lines.map((line, index) =>
25+
index === 0
26+
? line.trimRight()
27+
: ` ${index < lines.length - 1 ? line.trim() : line.trimLeft()}`
28+
)
29+
),
30+
'*/'
31+
]);
32+
}
33+
34+
function printComment(commentPath, options) {
35+
const comment = commentPath.getValue();
36+
37+
switch (comment.type) {
38+
case 'BlockComment': {
39+
if (isIndentableBlockComment(comment)) {
40+
const printed = printIndentableBlockComment(comment);
41+
// We need to prevent an edge case of a previous trailing comment
42+
// printed as a `lineSuffix` which causes the comments to be
43+
// interleaved. See https://github.com/prettier/prettier/issues/4412
44+
if (
45+
comment.trailing &&
46+
!hasNewline(options.originalText, options.locStart(comment), {
47+
backwards: true
48+
})
49+
) {
50+
return concat([hardline, printed]);
51+
}
52+
return printed;
53+
}
54+
55+
return `/*${comment.raw}*/`;
56+
}
57+
case 'LineComment':
58+
return `//${comment.raw.trimRight()}`;
59+
default:
60+
throw new Error(`Not a comment: ${JSON.stringify(comment)}`);
61+
}
62+
}
63+
64+
module.exports = printComment;

src/index.js

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const { handleComments } = require('./prettier-comments');
1+
// const { handleComments } = require('./prettier-comments');
2+
// const printComment = require('./comments/printer');
3+
const { handleComments, printComment } = require('./comments');
24

35
const massageAstNode = require('./clean');
46
const loc = require('./loc');
@@ -22,24 +24,8 @@ const parsers = {
2224
'solidity-parse': parser
2325
};
2426

25-
function canAttachComment(node) {
26-
return (
27-
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment'
28-
);
29-
}
30-
31-
function printComment(commentPath) {
32-
const comment = commentPath.getValue();
33-
switch (comment.type) {
34-
case 'BlockComment': {
35-
return `/*${comment.raw}*/`;
36-
}
37-
case 'LineComment':
38-
return `//${comment.raw.trimRight()}`;
39-
default:
40-
throw new Error(`Not a comment: ${JSON.stringify(comment)}`);
41-
}
42-
}
27+
const canAttachComment = node =>
28+
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment';
4329

4430
// https://prettier.io/docs/en/plugins.html#printers
4531
const printers = {

src/nodes/Block.js

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
} = require('prettier/standalone');
66

77
const printPreservingEmptyLines = require('./print-preserving-empty-lines');
8+
const printComments = require('./print-comments');
89

910
const Block = {
1011
print: ({ node, options, path, print }) => {
@@ -13,33 +14,18 @@ const Block = {
1314
return '{}';
1415
}
1516

16-
const parts = [
17+
return concat([
1718
'{',
18-
indent(line),
19-
indent(printPreservingEmptyLines(path, 'statements', options, print))
20-
];
21-
22-
if (node.comments) {
23-
let first = true;
24-
path.each(commentPath => {
25-
if (first) {
26-
first = false;
27-
} else {
28-
parts.push(indent(line));
29-
}
30-
const comment = commentPath.getValue();
31-
if (comment.trailing || comment.leading) {
32-
return;
33-
}
34-
comment.printed = true;
35-
parts.push(options.printer.printComment(commentPath));
36-
}, 'comments');
37-
}
38-
39-
parts.push(line);
40-
parts.push('}');
41-
42-
return concat(parts);
19+
indent(
20+
concat([
21+
line,
22+
printPreservingEmptyLines(path, 'statements', options, print),
23+
printComments(node, path, options)
24+
])
25+
),
26+
line,
27+
'}'
28+
]);
4329
}
4430
};
4531

src/nodes/ContractDefinition.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66

77
const printList = require('./print-list');
88
const printPreservingEmptyLines = require('./print-preserving-empty-lines');
9+
const printComments = require('./print-comments');
910

1011
const inheritance = (node, path, print) =>
1112
node.baseContracts.length > 0
@@ -16,10 +17,15 @@ const inheritance = (node, path, print) =>
1617
: line;
1718

1819
const body = (node, path, options, print) =>
19-
node.subNodes.length > 0
20+
node.subNodes.length > 0 || node.comments
2021
? concat([
21-
indent(line),
22-
indent(printPreservingEmptyLines(path, 'subNodes', options, print)),
22+
indent(
23+
concat([
24+
line,
25+
printPreservingEmptyLines(path, 'subNodes', options, print),
26+
printComments(node, path, options)
27+
])
28+
),
2329
line
2430
])
2531
: '';

src/nodes/print-comments.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const {
2+
doc: {
3+
builders: { join, line }
4+
}
5+
} = require('prettier');
6+
7+
const printComments = (node, path, options) =>
8+
node.comments
9+
? join(
10+
line,
11+
path
12+
.map(commentPath => {
13+
const comment = commentPath.getValue();
14+
if (comment.trailing || comment.leading) {
15+
return null;
16+
}
17+
comment.printed = true;
18+
return options.printer.printComment(commentPath);
19+
}, 'comments')
20+
.filter(element => element)
21+
)
22+
: '';
23+
24+
module.exports = printComments;

0 commit comments

Comments
 (0)