Skip to content

Commit 39f002f

Browse files
authored
Proposal to avoid separating function definitions without an implementation (#941)
* Proposal for avoiding separation between function definitions without an implementation * compatibility with Prettier 2 * refactor backward compatibility utils * moving isPrettier2 to the backwards compatibility module * reorganising the logic for clarity and to avoid having to check for extra lines added by mistake at the end * to avoid conflicts with #912 we changed isPrettier2 from a function to a constant checked at the loading of the module * Adding documentation * only check for leading comments
1 parent 71ba936 commit 39f002f

File tree

10 files changed

+157
-184
lines changed

10 files changed

+157
-184
lines changed

.c8rc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
"functions": 100,
66
"statements": 100,
77
"exclude": ["/node_modules/", "/src/prettier-comments/"],
8-
"include": ["src/**/*.js", "!src/prettier-comments/**/*.js"],
8+
"include": [
9+
"src/**/*.js",
10+
"!src/prettier-comments/**/*.js",
11+
"!src/common/backward-compatibility.js"
12+
],
913
"reporter": ["lcov", "text"],
1014
"temp-dir": "./coverage/"
1115
}

src/comments/handlers/handleContractDefinitionComments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { util } from 'prettier';
2-
import { getNextNonSpaceNonCommentCharacter } from '../../common/util.js';
2+
import { getNextNonSpaceNonCommentCharacter } from '../../common/backward-compatibility.js';
33

44
const { addLeadingComment, addTrailingComment, addDanglingComment } = util;
55

src/comments/handlers/handleModifierInvocationComments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { util } from 'prettier';
2-
import { getNextNonSpaceNonCommentCharacter } from '../../common/util.js';
2+
import { getNextNonSpaceNonCommentCharacter } from '../../common/backward-compatibility.js';
33

44
const { addLeadingComment, addTrailingComment, addDanglingComment } = util;
55

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { util } from 'prettier';
2+
import { prettierVersionSatisfies } from './util.js';
3+
4+
export const isPrettier2 = prettierVersionSatisfies('^2.3.0');
5+
6+
// The functions in this file will never be 100% covered in a single run
7+
// since it depends on the version of Prettier being used.
8+
// Mocking the behaviour will introduce a lot of maintenance in the tests.
9+
10+
export function isNextLineEmpty(text, startIndex) {
11+
return isPrettier2
12+
? util.isNextLineEmptyAfterIndex(text, startIndex)
13+
: util.isNextLineEmpty(text, startIndex); // V3 deprecated `isNextLineEmptyAfterIndex`
14+
}
15+
16+
export function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) {
17+
return isPrettier2
18+
? util.getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd)
19+
: util.getNextNonSpaceNonCommentCharacterIndex(text, locEnd(node)); // V3 signature changed
20+
}
21+
22+
export function getNextNonSpaceNonCommentCharacter(text, node, locEnd) {
23+
return isPrettier2
24+
? text.charAt(
25+
util.getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd)
26+
)
27+
: util.getNextNonSpaceNonCommentCharacter(text, locEnd(node)); // V3 exposes this function directly
28+
}
29+
30+
export function isFirst(path, _, index) {
31+
return isPrettier2 ? index === 0 : path.isFirst;
32+
}
33+
34+
export function isLast(path, key, index) {
35+
return isPrettier2
36+
? index === path.getParentNode()[key].length - 1
37+
: path.isLast;
38+
}
39+
40+
export function previous(path, key, index) {
41+
return isPrettier2 ? path.getParentNode()[key][index - 1] : path.previous;
42+
}
43+
44+
export function next(path, key, index) {
45+
return isPrettier2 ? path.getParentNode()[key][index + 1] : path.next;
46+
}

src/common/printer-helpers.js

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { doc } from 'prettier';
2-
import { isNextLineEmpty, isPrettier2 } from './util.js';
2+
import {
3+
isFirst,
4+
isLast,
5+
isNextLineEmpty,
6+
isPrettier2,
7+
next,
8+
previous
9+
} from './backward-compatibility.js';
310

411
const { group, indent, join, line, softline, hardline } = doc.builders;
512

@@ -26,12 +33,28 @@ export const printComments = (node, path, options, filter = () => true) => {
2633
// since it depends on the version of Prettier being used.
2734
// Mocking the behaviour will introduce a lot of maintenance in the tests.
2835
/* c8 ignore start */
29-
return isPrettier2()
36+
return isPrettier2
3037
? document.parts // Prettier V2
3138
: document; // Prettier V3
3239
/* c8 ignore stop */
3340
};
3441

42+
const shouldHaveEmptyLine = (node, checkForLeading) =>
43+
Boolean(
44+
// if node is not FunctionDefinition, it should have an empty line
45+
node.type !== 'FunctionDefinition' ||
46+
// if FunctionDefinition is not abstract, it should have an empty line
47+
node.body ||
48+
// if FunctionDefinition has the comment we are looking for (trailing or
49+
// leading), it should have an empty line
50+
node.comments?.some((comment) => checkForLeading && comment.leading)
51+
);
52+
53+
const separatingLine = (firstNode, secondNode) =>
54+
shouldHaveEmptyLine(firstNode, false) || shouldHaveEmptyLine(secondNode, true)
55+
? hardline
56+
: '';
57+
3558
export function printPreservingEmptyLines(path, key, options, print) {
3659
const parts = [];
3760
path.each((childPath, index) => {
@@ -48,29 +71,41 @@ export function printPreservingEmptyLines(path, key, options, print) {
4871
parts.push(hardline);
4972
}
5073

51-
if (index > 0) {
52-
if (
53-
['ContractDefinition', 'FunctionDefinition'].includes(nodeType) &&
54-
parts[parts.length - 2] !== hardline
55-
) {
74+
// Only attempt to prepend an empty line if `node` is not the first item
75+
// and an empty line hasn't already been appended after the previous `node`
76+
if (
77+
!isFirst(childPath, key, index) &&
78+
parts[parts.length - 2] !== hardline
79+
) {
80+
if (nodeType === 'FunctionDefinition') {
81+
// Prepend FunctionDefinition with an empty line if there should be a
82+
// separation with the previous `node`
83+
parts.push(separatingLine(previous(childPath, key, index), node));
84+
} else if (nodeType === 'ContractDefinition') {
85+
// Prepend ContractDefinition with an empty line
5686
parts.push(hardline);
5787
}
5888
}
5989

6090
parts.push(print(childPath));
6191

62-
if (
63-
isNextLineEmpty(options.originalText, options.locEnd(node) + 1) ||
64-
nodeType === 'FunctionDefinition'
65-
) {
66-
parts.push(hardline);
92+
// Only attempt to append an empty line if `node` is not the last item
93+
if (!isLast(childPath, key, index)) {
94+
if (isNextLineEmpty(options.originalText, options.locEnd(node) + 1)) {
95+
// Append an empty line if the original text already had an one after
96+
// the current `node`
97+
parts.push(hardline);
98+
} else if (nodeType === 'FunctionDefinition') {
99+
// Append FunctionDefinition with an empty line if there should be a
100+
// separation with the next `node`
101+
parts.push(separatingLine(node, next(childPath, key, index)));
102+
} else if (nodeType === 'ContractDefinition') {
103+
// Append ContractDefinition with an empty line
104+
parts.push(hardline);
105+
}
67106
}
68107
}, key);
69108

70-
if (parts.length > 1 && parts[parts.length - 1] === hardline) {
71-
parts.pop();
72-
}
73-
74109
return parts;
75110
}
76111

src/common/util.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,6 @@ import { util, version } from 'prettier';
22
import satisfies from 'semver/functions/satisfies.js';
33

44
export const prettierVersionSatisfies = (range) => satisfies(version, range);
5-
export const isPrettier2 = () => prettierVersionSatisfies('^2.3.0');
6-
7-
// The following functions will never be 100% covered in a single run
8-
// since it depends on the version of Prettier being used.
9-
// Mocking the behaviour will introduce a lot of maintenance in the tests.
10-
/* c8 ignore start */
11-
export function isNextLineEmpty(text, startIndex) {
12-
return isPrettier2()
13-
? util.isNextLineEmptyAfterIndex(text, startIndex)
14-
: util.isNextLineEmpty(text, startIndex); // V3 deprecated `isNextLineEmptyAfterIndex`
15-
}
16-
17-
export function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) {
18-
return isPrettier2()
19-
? util.getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd)
20-
: util.getNextNonSpaceNonCommentCharacterIndex(text, locEnd(node)); // V3 signature changed
21-
}
22-
23-
export function getNextNonSpaceNonCommentCharacter(text, node, locEnd) {
24-
return isPrettier2()
25-
? text.charAt(
26-
util.getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd)
27-
)
28-
: util.getNextNonSpaceNonCommentCharacter(text, locEnd(node)); // V3 exposes this function directly
29-
}
30-
/* c8 ignore stop */
315

326
export function printString(rawContent, options) {
337
const double = { quote: '"', regex: /"/g };

src/nodes/FunctionDefinition.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { doc } from 'prettier';
2-
import { getNextNonSpaceNonCommentCharacter } from '../common/util.js';
2+
import { getNextNonSpaceNonCommentCharacter } from '../common/backward-compatibility.js';
33
import {
44
printComments,
55
printSeparatedItem,

src/prettier-comments/language-js/comments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { util } from "prettier";
2-
import { getNextNonSpaceNonCommentCharacter } from "../../common/util.js";
2+
import { getNextNonSpaceNonCommentCharacter } from "../../common/backward-compatibility.js";
33

44
const {
55
addLeadingComment,

0 commit comments

Comments
 (0)