diff --git a/CHANGELOG.md b/CHANGELOG.md index acca81ee..cba15df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - (feat) support `requirePragma` and `insertPragma` options ([#350](https://github.com/sveltejs/prettier-plugin-svelte/issues/350)) - (feat) support `` - (feat) trim whitespace in `class` attributes ([#339](https://github.com/sveltejs/prettier-plugin-svelte/issues/339)) +- (feat) allow multiple comments atop of script/style tags ([#291](https://github.com/sveltejs/prettier-plugin-svelte/issues/291)) - (fix) handle script/style attributes without quotes ([#344](https://github.com/sveltejs/prettier-plugin-svelte/issues/344)) ## 2.9.0 diff --git a/src/embed.ts b/src/embed.ts index a03f60d3..bf96f7c4 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -14,7 +14,7 @@ import { isTypeScript, printRaw, } from './print/node-helpers'; -import { ElementNode, Node, ScriptNode, StyleNode } from './print/nodes'; +import { CommentNode, ElementNode, Node, ScriptNode, StyleNode } from './print/nodes'; const { builders: { concat, hardline, softline, indent, dedent, literalline }, @@ -193,11 +193,16 @@ function embedTag( const node: ScriptNode | StyleNode | ElementNode = path.getNode(); const content = tag === 'template' ? printRaw(node as ElementNode, text) : getSnippedContent(node); - const previousComment = getLeadingComment(path); + const previousComments = + node.type === 'Script' || node.type === 'Style' + ? node.comments + : [getLeadingComment(path)] + .filter(Boolean) + .map((comment) => ({ comment: comment as CommentNode, emptyLineAfter: false })); const canFormat = isNodeSupportedLanguage(node) && - !isIgnoreDirective(previousComment) && + !isIgnoreDirective(previousComments[previousComments.length - 1]?.comment) && (tag !== 'template' || options.plugins.some( (plugin) => typeof plugin !== 'string' && plugin.parsers && plugin.parsers.pug, @@ -223,16 +228,21 @@ function embedTag( ]); let result = groupConcat([openingTag, body, '']); + const comments = []; + for (const comment of previousComments) { + comments.push(''); + comments.push(hardline); + if (comment.emptyLineAfter) { + comments.push(hardline); + } + } + if (isTopLevel && options.svelteSortOrder !== 'none') { // top level embedded nodes have been moved from their normal position in the // node tree. if there is a comment referring to it, it must be recreated at // the new position. - if (previousComment) { - result = concat(['', hardline, result, hardline]); - } else { - result = concat([result, hardline]); - } + return concat([...comments, result, hardline]); + } else { + return comments.length ? concat([...comments, result]) : result; } - - return result; } diff --git a/src/print/index.ts b/src/print/index.ts index 98f8c3a9..18ec471e 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -49,6 +49,7 @@ import { import { ASTNode, AttributeNode, + CommentInfo, CommentNode, IfBlockNode, Node, @@ -100,6 +101,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D } if (isASTNode(n)) { + assignCommentsToNodes(n); return printTopLevelParts(n, options, path, print); } @@ -786,6 +788,70 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D throw new Error('unknown node type: ' + node.type); } +function assignCommentsToNodes(ast: ASTNode) { + if (ast.module) { + ast.module.comments = removeAndGetLeadingComments(ast, ast.module); + } + if (ast.instance) { + ast.instance.comments = removeAndGetLeadingComments(ast, ast.instance); + } + if (ast.css) { + ast.css.comments = removeAndGetLeadingComments(ast, ast.css); + } +} + +/** + * Returns the comments that are above the current node and deletes them from the html ast. + */ +function removeAndGetLeadingComments(ast: ASTNode, current: Node): CommentInfo[] { + const siblings = getChildren(ast.html); + const comments: CommentNode[] = []; + const newlines: TextNode[] = []; + + if (!siblings.length) { + return []; + } + + let node: Node = current; + let prev: Node | undefined = siblings.find((child) => child.end === node.start); + while (prev) { + if ( + prev.type === 'Comment' && + !isIgnoreStartDirective(prev) && + !isIgnoreEndDirective(prev) + ) { + comments.push(prev); + if (comments.length !== newlines.length) { + newlines.push({ type: 'Text', data: '', raw: '', start: -1, end: -1 }); + } + } else if (isEmptyTextNode(prev)) { + newlines.push(prev); + } else { + break; + } + + node = prev; + prev = siblings.find((child) => child.end === node.start); + } + + newlines.length = comments.length; // could be one more if first comment is preceeded by empty text node + + for (const comment of comments) { + siblings.splice(siblings.indexOf(comment), 1); + } + + for (const text of newlines) { + siblings.splice(siblings.indexOf(text), 1); + } + + return comments + .map((comment, i) => ({ + comment, + emptyLineAfter: getUnencodedText(newlines[i]).split('\n').length > 2, + })) + .reverse(); +} + function printTopLevelParts( n: ASTNode, options: ParserOptions, diff --git a/src/print/nodes.ts b/src/print/nodes.ts index c9e00d50..48c247ac 100644 --- a/src/print/nodes.ts +++ b/src/print/nodes.ts @@ -207,17 +207,20 @@ export interface StyleNode extends BaseNode { attributes: Node[]; children: Node[]; content: StyleProgramNode; + comments: CommentInfo[]; // doesn't exist on original node but we use it to store comments } export interface ScriptNode extends BaseNode { type: 'Script'; attributes: Node[]; content: Node; + comments: CommentInfo[]; // doesn't exist on original node but we use it to store comments } export interface StyleProgramNode extends BaseNode { type: 'StyleProgram'; styles: string; + comments: CommentInfo[]; // doesn't exist on original node but we use it to store comments } export interface ProgramNode extends BaseNode { @@ -283,6 +286,11 @@ export interface ConstTagNode extends BaseNode { expression: Node; } +export interface CommentInfo { + comment: CommentNode; + emptyLineAfter: boolean; +} + export type Node = | FragmentNode | ElementNode @@ -334,13 +342,7 @@ export type Node = */ export interface ASTNode { html: Node; - css?: Node & { - attributes: Node[]; - children: Node[]; - content: Node & { - styles: string; - }; - }; + css?: StyleNode; js?: ScriptNode; instance?: ScriptNode; module?: ScriptNode; diff --git a/test/formatting/samples/script-style-tags-multiple-nested/output.html b/test/formatting/samples/script-style-tags-multiple-nested/output.html index cc4e12a4..df34455d 100644 --- a/test/formatting/samples/script-style-tags-multiple-nested/output.html +++ b/test/formatting/samples/script-style-tags-multiple-nested/output.html @@ -22,6 +22,7 @@ + diff --git a/test/printer/samples/sort-order-none2.html b/test/printer/samples/sort-order-none2.html index df863350..667ab2a9 100644 --- a/test/printer/samples/sort-order-none2.html +++ b/test/printer/samples/sort-order-none2.html @@ -6,7 +6,9 @@

Hello {name}!

+

yeah why not

+