Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(fix) keep whitespace around script/style tags #199

Merged
merged 1 commit into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# prettier-plugin-svelte changelog

## 2.1.2 (Unreleased)

* Keep whitespace around script/style tags ([#197](https://github.com/sveltejs/prettier-plugin-svelte/issues/197))

## 2.1.1

* Fix `svelteBracketNewLine: true` sometimes not having `>` on a separate line ([#194](https://github.com/sveltejs/prettier-plugin-svelte/issues/194))
Expand Down
6 changes: 2 additions & 4 deletions src/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { snippedTagContentAttribute } from './lib/snipTagContent';
import { PrintFn } from './print';
import {
getAttributeTextValue,
getPreviousNode,
getLeadingComment,
isIgnoreDirective,
isNodeSupportedLanguage,
} from './print/node-helpers';
Expand Down Expand Up @@ -176,9 +176,7 @@ function embedTag(
) {
const node: Node = path.getNode();
const content = getSnippedContent(node);

const previousNode = getPreviousNode(path);
const previousComment = previousNode && previousNode.type === 'Comment' ? previousNode : null;
const previousComment = getLeadingComment(path);

const body: Doc =
isNodeSupportedLanguage(node) && !isIgnoreDirective(previousComment)
Expand Down
4 changes: 2 additions & 2 deletions src/lib/snipTagContent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const snippedTagContentAttribute = '✂prettier:content✂'
export const snippedTagContentAttribute = '✂prettier:content✂';

export function snipTagContent(tagName: string, source: string, placeholder = ''): string {
const regex = new RegExp(`[\\s\n]*<${tagName}([^]*?)>([^]*?)<\/${tagName}>[\\s\n]*`, 'gi');
const regex = new RegExp(`<${tagName}([^]*?)>([^]*?)<\/${tagName}>`, 'gi');
return source.replace(regex, (_, attributes, content) => {
const encodedContent = Buffer.from(content).toString('base64');
return `<${tagName}${attributes} ${snippedTagContentAttribute}="${encodedContent}">${placeholder}</${tagName}>`;
Expand Down
15 changes: 13 additions & 2 deletions src/print/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { flatten, isASTNode, isPreTagContent } from './helpers';
import {
checkWhitespaceAtEndOfSvelteBlock,
checkWhitespaceAtStartOfSvelteBlock,
doesEmbedStartAt,
doesEmbedStartAfterNode,
endsWithLinebreak,
getUnencodedText,
isBlockElement,
Expand All @@ -32,6 +32,7 @@ import {
trimTextNodeLeft,
trimTextNodeRight,
canOmitSoftlineBeforeClosingTag,
getNextNode,
} from './node-helpers';
import {
ASTNode,
Expand Down Expand Up @@ -568,14 +569,20 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
case 'Ref':
return concat([line, 'ref:', node.name]);
case 'Comment': {
const nodeAfterComment = getNextNode(path);

/**
* If there is no sibling node that starts right after us but the parent indicates
* that there used to be, that means that node was actually an embedded `<style>`
* or `<script>` node that was cut out.
* If so, the comment does not refer to the next line we will see.
* The `embed` function handles printing the comment in the right place.
*/
if (doesEmbedStartAt(node.end, path)) {
if (
doesEmbedStartAfterNode(node, path) ||
(isEmptyTextNode(nodeAfterComment) &&
doesEmbedStartAfterNode(nodeAfterComment, path))
) {
return '';
} else if (isIgnoreDirective(node)) {
ignoreNext = true;
Expand Down Expand Up @@ -884,6 +891,10 @@ function prepareChildren(children: Node[], path: FastPath, print: PrintFn): Node
continue;
}

if (isEmptyTextNode(currentChild) && doesEmbedStartAfterNode(currentChild, path)) {
continue;
}

if (isCommentFollowedByOptions(currentChild, idx)) {
svelteOptionsComment = printComment(currentChild);
const nextChild = children[idx + 1];
Expand Down
55 changes: 44 additions & 11 deletions src/print/node-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
KeyBlockNode,
PendingBlockNode,
ThenBlockNode,
CommentNode,
} from './nodes';
import { blockElements, TagName } from '../lib/elements';
import { FastPath, ParserOptions } from 'prettier';
Expand Down Expand Up @@ -73,32 +74,64 @@ export function getChildren(node: Node): Node[] {
}

/**
* Returns the previous sibling node.
* Returns siblings, that is, the children of the parent.
*/
export function getPreviousNode(path: FastPath): Node | undefined {
const node: Node = path.getNode();
export function getSiblings(path: FastPath): Node[] {
let parent: Node = path.getParentNode();

if (isASTNode(parent)) {
parent = parent.html;
}

return getChildren(parent).find((child) => child.end === node.start);
return getChildren(parent);
}

/**
* Returns the previous sibling node.
*/
export function getPreviousNode(path: FastPath): Node | undefined {
const node: Node = path.getNode();
return getSiblings(path).find((child) => child.end === node.start);
}

/**
* Returns the next sibling node.
*/
export function getNextNode(path: FastPath, node: Node = path.getNode()): Node | undefined {
return getSiblings(path).find((child) => child.start === node.end);
}

/**
* Returns the comment that is above the current node.
*/
export function getLeadingComment(path: FastPath): CommentNode | undefined {
const siblings = getSiblings(path);

let node: Node = path.getNode();
let prev: Node | undefined = siblings.find((child) => child.end === node.start);
while (prev && (prev.type !== 'Comment' || isEmptyTextNode(prev))) {
node = prev;
prev = siblings.find((child) => child.end === node.start);
}

return prev && prev.type === 'Comment' ? prev : undefined;
}

/**
* Did there use to be any embedded object (that has been snipped out of the AST to be moved)
* at the specified position?
*/
export function doesEmbedStartAt(position: number, path: FastPath) {
export function doesEmbedStartAfterNode(node: Node, path: FastPath, siblings = getSiblings(path)) {
const position = node.end;
const root = path.stack[0];
const embeds = [root.css, root.html, root.instance, root.js, root.module] as Node[];

return embeds.find((n) => n && n.start === position) != null;
const nextNode = siblings[siblings.indexOf(node) + 1];
return embeds.find((n) => n && n.start >= position && (!nextNode || n.end <= nextNode.start));
}

export function isEmptyTextNode(node: Node): node is TextNode {
return node.type === 'Text' && getUnencodedText(node).trim() === '';
export function isEmptyTextNode(node: Node | undefined): node is TextNode {
return !!node && node.type === 'Text' && getUnencodedText(node).trim() === '';
}

export function isIgnoreDirective(node: Node | undefined | null): boolean {
Expand Down Expand Up @@ -254,17 +287,17 @@ export function trimTextNodeLeft(node: TextNode): void {
*/
export function trimChildren(children: Node[], path: FastPath): void {
let firstNonEmptyNode = children.findIndex(
(n) => !isEmptyTextNode(n) && !doesEmbedStartAt(n.end, path),
(n) => !isEmptyTextNode(n) && !doesEmbedStartAfterNode(n, path),
);
firstNonEmptyNode = firstNonEmptyNode === -1 ? children.length - 1 : firstNonEmptyNode;

let lastNonEmptyNode = findLastIndex((n, idx) => {
// Last node is ok to end and the start of an embeded region,
// Last node is ok to end at the start of an embedded region,
// if it's not a comment (which should stick to the region)
return (
!isEmptyTextNode(n) &&
((idx === children.length - 1 && n.type !== 'Comment') ||
!doesEmbedStartAt(n.end, path))
!doesEmbedStartAfterNode(n, path))
);
}, children);
lastNonEmptyNode = lastNonEmptyNode === -1 ? 0 : lastNonEmptyNode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<div>above</div>
<script>
const hi = '';
</script>
<div>below</div>

<div>
<div>above</div>
<script>
const hi = '';
</script>
<div>below</div>
</div>

<div>
<div>above</div>
<script>
const hi = '';
</script>
<style></style>
</div>

<!-- i stay above style -->


<style>
.a {
color: blue;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script>
const hi = "";
</script>

<div>above</div>
<div>below</div>

<div>
<div>above</div>
<script>
const hi = "";
</script>
<div>below</div>
</div>

<div>
<div>above</div>
<script>
const hi = "";
</script>
<style></style>
</div>

<!-- i stay above style -->
<style>
.a {
color: blue;
}
</style>
7 changes: 7 additions & 0 deletions test/printer/samples/svelte-head-script-content.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<svelte:head>
<script async src="helloooooooooooooo{id}"></script>
<script>
let foo = "bar";
window.foo = foo;
</script>
</svelte:head>