Skip to content

Commit bc9e741

Browse files
committed
feat: allow multiple comments atop of script/style tags
closes sveltejs#291
1 parent 4ce3dd3 commit bc9e741

File tree

8 files changed

+111
-18
lines changed

8 files changed

+111
-18
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- (feat) support `requirePragma` and `insertPragma` options ([#350](https://github.com/sveltejs/prettier-plugin-svelte/issues/350))
66
- (feat) support `<svelte:document>`
77
- (feat) trim whitespace in `class` attributes ([#339](https://github.com/sveltejs/prettier-plugin-svelte/issues/339))
8+
- (feat) allow multiple comments atop of script/style tags ([#291](https://github.com/sveltejs/prettier-plugin-svelte/issues/291))
89
- (fix) handle script/style attributes without quotes ([#344](https://github.com/sveltejs/prettier-plugin-svelte/issues/344))
910

1011
## 2.9.0

src/embed.ts

+20-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
isTypeScript,
1515
printRaw,
1616
} from './print/node-helpers';
17-
import { ElementNode, Node, ScriptNode, StyleNode } from './print/nodes';
17+
import { CommentNode, ElementNode, Node, ScriptNode, StyleNode } from './print/nodes';
1818

1919
const {
2020
builders: { concat, hardline, softline, indent, dedent, literalline },
@@ -193,11 +193,16 @@ function embedTag(
193193
const node: ScriptNode | StyleNode | ElementNode = path.getNode();
194194
const content =
195195
tag === 'template' ? printRaw(node as ElementNode, text) : getSnippedContent(node);
196-
const previousComment = getLeadingComment(path);
196+
const previousComments =
197+
node.type === 'Script' || node.type === 'Style'
198+
? node.comments
199+
: [getLeadingComment(path)]
200+
.filter(Boolean)
201+
.map((comment) => ({ comment: comment as CommentNode, emptyLineAfter: false }));
197202

198203
const canFormat =
199204
isNodeSupportedLanguage(node) &&
200-
!isIgnoreDirective(previousComment) &&
205+
!isIgnoreDirective(previousComments[previousComments.length - 1]?.comment) &&
201206
(tag !== 'template' ||
202207
options.plugins.some(
203208
(plugin) => typeof plugin !== 'string' && plugin.parsers && plugin.parsers.pug,
@@ -223,16 +228,21 @@ function embedTag(
223228
]);
224229
let result = groupConcat([openingTag, body, '</', tag, '>']);
225230

231+
const comments = [];
232+
for (const comment of previousComments) {
233+
comments.push('<!--', comment.comment.data, '-->');
234+
comments.push(hardline);
235+
if (comment.emptyLineAfter) {
236+
comments.push(hardline);
237+
}
238+
}
239+
226240
if (isTopLevel && options.svelteSortOrder !== 'none') {
227241
// top level embedded nodes have been moved from their normal position in the
228242
// node tree. if there is a comment referring to it, it must be recreated at
229243
// the new position.
230-
if (previousComment) {
231-
result = concat(['<!--', previousComment.data, '-->', hardline, result, hardline]);
232-
} else {
233-
result = concat([result, hardline]);
234-
}
244+
return concat([...comments, result, hardline]);
245+
} else {
246+
return comments.length ? concat([...comments, result]) : result;
235247
}
236-
237-
return result;
238248
}

src/print/index.ts

+66
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
import {
5050
ASTNode,
5151
AttributeNode,
52+
CommentInfo,
5253
CommentNode,
5354
IfBlockNode,
5455
Node,
@@ -100,6 +101,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
100101
}
101102

102103
if (isASTNode(n)) {
104+
assignCommentsToNodes(n);
103105
return printTopLevelParts(n, options, path, print);
104106
}
105107

@@ -786,6 +788,70 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
786788
throw new Error('unknown node type: ' + node.type);
787789
}
788790

791+
function assignCommentsToNodes(ast: ASTNode) {
792+
if (ast.module) {
793+
ast.module.comments = removeAndGetLeadingComments(ast, ast.module);
794+
}
795+
if (ast.instance) {
796+
ast.instance.comments = removeAndGetLeadingComments(ast, ast.instance);
797+
}
798+
if (ast.css) {
799+
ast.css.comments = removeAndGetLeadingComments(ast, ast.css);
800+
}
801+
}
802+
803+
/**
804+
* Returns the comments that are above the current node and deletes them from the html ast.
805+
*/
806+
function removeAndGetLeadingComments(ast: ASTNode, current: Node): CommentInfo[] {
807+
const siblings = getChildren(ast.html);
808+
const comments: CommentNode[] = [];
809+
const newlines: TextNode[] = [];
810+
811+
if (!siblings.length) {
812+
return [];
813+
}
814+
815+
let node: Node = current;
816+
let prev: Node | undefined = siblings.find((child) => child.end === node.start);
817+
while (prev) {
818+
if (
819+
prev.type === 'Comment' &&
820+
!isIgnoreStartDirective(prev) &&
821+
!isIgnoreEndDirective(prev)
822+
) {
823+
comments.push(prev);
824+
if (comments.length !== newlines.length) {
825+
newlines.push({ type: 'Text', data: '', raw: '', start: -1, end: -1 });
826+
}
827+
} else if (isEmptyTextNode(prev)) {
828+
newlines.push(prev);
829+
} else {
830+
break;
831+
}
832+
833+
node = prev;
834+
prev = siblings.find((child) => child.end === node.start);
835+
}
836+
837+
newlines.length = comments.length; // could be one more if first comment is preceeded by empty text node
838+
839+
for (const comment of comments) {
840+
siblings.splice(siblings.indexOf(comment), 1);
841+
}
842+
843+
for (const text of newlines) {
844+
siblings.splice(siblings.indexOf(text), 1);
845+
}
846+
847+
return comments
848+
.map((comment, i) => ({
849+
comment,
850+
emptyLineAfter: getUnencodedText(newlines[i]).split('\n').length > 2,
851+
}))
852+
.reverse();
853+
}
854+
789855
function printTopLevelParts(
790856
n: ASTNode,
791857
options: ParserOptions,

src/print/nodes.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -207,17 +207,20 @@ export interface StyleNode extends BaseNode {
207207
attributes: Node[];
208208
children: Node[];
209209
content: StyleProgramNode;
210+
comments: CommentInfo[]; // doesn't exist on original node but we use it to store comments
210211
}
211212

212213
export interface ScriptNode extends BaseNode {
213214
type: 'Script';
214215
attributes: Node[];
215216
content: Node;
217+
comments: CommentInfo[]; // doesn't exist on original node but we use it to store comments
216218
}
217219

218220
export interface StyleProgramNode extends BaseNode {
219221
type: 'StyleProgram';
220222
styles: string;
223+
comments: CommentInfo[]; // doesn't exist on original node but we use it to store comments
221224
}
222225

223226
export interface ProgramNode extends BaseNode {
@@ -283,6 +286,11 @@ export interface ConstTagNode extends BaseNode {
283286
expression: Node;
284287
}
285288

289+
export interface CommentInfo {
290+
comment: CommentNode;
291+
emptyLineAfter: boolean;
292+
}
293+
286294
export type Node =
287295
| FragmentNode
288296
| ElementNode
@@ -334,13 +342,7 @@ export type Node =
334342
*/
335343
export interface ASTNode {
336344
html: Node;
337-
css?: Node & {
338-
attributes: Node[];
339-
children: Node[];
340-
content: Node & {
341-
styles: string;
342-
};
343-
};
345+
css?: StyleNode;
344346
js?: ScriptNode;
345347
instance?: ScriptNode;
346348
module?: ScriptNode;

test/formatting/samples/script-style-tags-multiple-nested/output.html

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
</div>
2323

2424
<!-- i stay above style -->
25+
2526
<style>
2627
.a {
2728
color: blue;

test/printer/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ for (const file of files) {
1717
const ending = file.split('.').pop();
1818
const input = readFileSync(`test/printer/samples/${file}`, 'utf-8').replace(/\r?\n/g, '\n');
1919
const options = readOptions(
20-
`test/printer/samples/${file.replace(`.${ending}`, '.options.json')}`,
20+
`test/printer/samples/${file.replace('.only', '').replace(`.${ending}`, '.options.json')}`,
2121
);
2222

2323
test(`printer: ${file.slice(0, file.length - `.${ending}`.length)}`, (t) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!-- asd1 -->
2+
<!-- asd2 -->
3+
<script></script>
4+
5+
<!-- asd3 -->
6+
<!-- asd4 -->
7+
<div />
8+
9+
<!-- asd5 -->
10+
<!-- asd6 -->
11+
<style></style>

test/printer/samples/sort-order-none2.html

+2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ <h1>Hello {name}!</h1>
66

77
<svelte:options />
88

9+
<!-- hi -->
910
<p>yeah why not</p>
11+
1012
<!-- hi -->
1113
<script context="module">
1214
const name1 = "world";

0 commit comments

Comments
 (0)