Skip to content

Commit 1c9da7e

Browse files
committed
prevent prettier from moving // [!code ++] to different lines
1 parent 735a4f3 commit 1c9da7e

3 files changed

Lines changed: 73 additions & 17 deletions

File tree

config/prettier/format-mdx3.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/** @import { Plugin } from 'prettier' */
22

3+
import * as prettier from "prettier";
34
import markdown from "prettier/plugins/markdown.js";
45

56
/** @type {Plugin["languages"]} */
@@ -17,12 +18,77 @@ export const parsers = {
1718
astFormat: "mdx3",
1819
},
1920
};
21+
22+
/**
23+
* Applied to code blocks to preserve special comments like `// [!code ...]` on the same line.
24+
*/
25+
function handleCodeBlockWithSpecialComments(node, path, options) {
26+
// Check if this code block has special comments
27+
const specialCommentPattern = /\/\/\s*\[!code\s+[^\]]*\]/g;
28+
const commentMatches = [...node.value.matchAll(specialCommentPattern)];
29+
30+
// Nothing to do.
31+
if (commentMatches.length == 0) return null;
32+
33+
// Analyze comment positions - only track if comment should be inline
34+
const commentPositions = commentMatches.map((commentMatch) => {
35+
const beforeComment = node.value.substring(0, commentMatch.index);
36+
const lineStart = beforeComment.lastIndexOf("\n") + 1;
37+
const codeOnLine = node.value.substring(lineStart, commentMatch.index);
38+
39+
return {
40+
comment: commentMatch[0],
41+
shouldBeInline: codeOnLine.trim() !== "",
42+
};
43+
});
44+
45+
// Use default formatting first
46+
const defaultEmbed = markdown.printers.mdast.embed(path, options);
47+
return async (textToDoc) => {
48+
let stringResult = prettier.doc.printer.printDocToString(
49+
await defaultEmbed(textToDoc),
50+
options
51+
).formatted;
52+
53+
const specialCommentPattern = /\/\/\s*\[!code\s+[^\]]*\]/g;
54+
const formattedCommentMatches = [
55+
...stringResult.matchAll(specialCommentPattern),
56+
];
57+
58+
// Process each comment in reverse order to avoid index shifting
59+
for (let i = formattedCommentMatches.length - 1; i >= 0; i--) {
60+
const commentMatch = formattedCommentMatches[i];
61+
const pos = commentPositions[i];
62+
if (!pos || !pos.shouldBeInline) continue;
63+
64+
// Find the preceding non-whitespace character, starting from the comment position
65+
let insertPos = commentMatch.index - 1;
66+
while (insertPos >= 0 && /\s/.test(stringResult[insertPos])) {
67+
insertPos--;
68+
}
69+
70+
stringResult =
71+
stringResult.substring(0, insertPos + 1) +
72+
" " +
73+
stringResult.substring(commentMatch.index);
74+
}
75+
76+
return stringResult;
77+
};
78+
}
79+
2080
/** @type {Plugin["printers"]} */
2181
export const printers = {
2282
mdx3: {
2383
...markdown.printers.mdast,
2484
embed(path, options) {
2585
const node = path.node;
86+
87+
if (node.type === "code" && node.lang !== null) {
88+
const result = handleCodeBlockWithSpecialComments(node, path, options);
89+
if (result) return result;
90+
}
91+
2692
if (node.type === "jsx") {
2793
// If the node was parsed incorrectly because it followed the MDX3 format (no spacing around JSX tags),
2894
// we will not try to format it as MDX, but instead return the original value.

config/prettier/test.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
import * as prettier from "prettier";
22
const code = `
3-
Notice that the \`Trail\` component isn't receiving the entire \`trail\` object via props, only the \`id\` which is used along with the fragment document to create a live binding for each trail item in the cache. This allows each \`Trail\` component to react to the cache updates for a single trail independently. Updates to a trail's \`status\` will not cause the parent \`App\` component to rerender since the \`@nonreactive\` directive is applied to the \`TrailFragment\` spread, a fragment that includes the \`status\` field.
4-
5-
<MinVersion version="3.12.0">
6-
## \`@unmask\`
7-
</MinVersion>
8-
9-
The \`@unmask\` directive is used to make fragment data available when using [data masking](./fragments#data-masking). It is primarily used to [incrementally adopt data masking in an existing application](./fragments#incremental-adoption-in-an-existing-application). It is considered an escape hatch for all other cases where working with masked data would otherwise be difficult.
10-
11-
\`\`\`graphql
12-
query GetPosts {
13-
posts {
14-
id
15-
...PostDetails @unmask
16-
}
17-
}
3+
\`\`\`ts
4+
const client = new ApolloClient({
5+
link: new HttpLink({ // [!code ++]
6+
uri: "https://example.com/graphql",
7+
}),
8+
});
189
\`\`\`
19-
2010
`;
2111

2212
const result = await prettier.format(code, {

docs/source/migrating/apollo-client-4-migration.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ import {
420420
const client = new ApolloClient({
421421
// ...
422422
uri: "https://example.com/graphql",
423-
link: new HttpLink({ <!--[!code --]-->
423+
link: new HttpLink({ // [!code ++]
424424
uri: "https://example.com/graphql",
425425
}), // [!code ++]
426426
// ...

0 commit comments

Comments
 (0)