diff --git a/src/jinja.ts b/src/jinja.ts index 2cbf7f5..e3ef42a 100644 --- a/src/jinja.ts +++ b/src/jinja.ts @@ -5,9 +5,9 @@ export const Placeholder = { export interface Node { id: string; - type: "root" | "expression" | "statement" | "block" | "ignore"; + type: "root" | "expression" | "statement" | "block" | "comment" | "ignore"; content: string; - ownLine: boolean; + preNewLines: number; originalText: string; index: number; length: number; @@ -31,10 +31,7 @@ export interface Block extends Node { type: "block"; start: Statement; end: Statement; -} - -export interface IgnoreBlock extends Node { - type: "ignore"; + containsNewLines: boolean; } export const nonClosingStatements = [ diff --git a/src/parser.ts b/src/parser.ts index 2d20b42..eb40075 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -12,7 +12,7 @@ import { const NOT_FOUND = -1; const regex = - /(?
(?\n)?(\s*?))(? {{(? [-+]?)\s*(? '([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*(? [-+]?)}}|{%(? [-+]?)\s*(? (? \w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(? [-+]?)%}|(? {#[\S\s]*?#})|(? <(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(? <(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>)|(? [\s\S]*))/; + /(? {{(? [-+]?)\s*(? '([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*(? [-+]?)}}|{%(? [-+]?)\s*(? (? \w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(? [-+]?)%}|(? {#[\S\s]*?#})|(? <(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(? <(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>)|(? [\s\S]*))/; export const parse: Parser ["parse"] = (text) => { const statementStack: Statement[] = []; @@ -21,7 +21,7 @@ export const parse: Parser ["parse"] = (text) => { id: "0", type: "root" as const, content: text, - ownLine: false, + preNewLines: 0, originalText: text, index: 0, length: 0, @@ -44,9 +44,6 @@ export const parse: Parser ["parse"] = (text) => { continue; } - const pre = match.groups.pre || ""; - const newline = !!match.groups.newline; - const matchText = match.groups.node; const expression = match.groups.expression; const statement = match.groups.statement; @@ -58,21 +55,28 @@ export const parse: Parser ["parse"] = (text) => { } const placeholder = generatePlaceholder(); + const emptyLinesBetween = text.slice(i, i + match.index).match(/^\s+$/) || [ + "", + ]; + const preNewLines = emptyLinesBetween.length + ? emptyLinesBetween[0].split("\n").length - 1 + : 0; + const node = { id: placeholder, - ownLine: newline, + preNewLines, originalText: matchText, - index: match.index + i + pre.length, + index: match.index + i, length: matchText.length, nodes: root.nodes, }; - if (ignoreBlock || comment) { + if (comment || ignoreBlock) { root.content = root.content.replace(matchText, placeholder); root.nodes[node.id] = { ...node, - type: "ignore", - content: ignoreBlock || comment, + type: comment ? "comment" : "ignore", + content: comment || ignoreBlock, }; } @@ -158,7 +162,8 @@ export const parse: Parser ["parse"] = (text) => { start: start, end: end, content: blockText.slice(start.length, blockText.length - end.length), - ownLine: originalText.search("\n") !== NOT_FOUND, + preNewLines: start.preNewLines, + containsNewLines: originalText.search("\n") !== NOT_FOUND, originalText, index: start.index, length: end.index + end.length - start.index, diff --git a/src/printer.ts b/src/printer.ts index 20f7fd4..d13b3fe 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -1,13 +1,6 @@ -import { Printer } from "prettier"; +import { AstPath, Printer } from "prettier"; import { builders, utils } from "prettier/doc"; -import { - Placeholder, - Node, - Expression, - Statement, - Block, - IgnoreBlock, -} from "./jinja"; +import { Placeholder, Node, Expression, Statement, Block } from "./jinja"; const NOT_FOUND = -1; @@ -24,8 +17,10 @@ export const print: Printer ["print"] = (path) => { return printExpression(node as Expression); case "statement": return printStatement(node as Statement); + case "comment": + return printCommentBlock(node); case "ignore": - return printIgnoreBlock(node as IgnoreBlock); + return printIgnoreBlock(node); } return []; }; @@ -33,20 +28,24 @@ export const print: Printer ["print"] = (path) => { const printExpression = (node: Expression): builders.Doc => { const multiline = node.content.includes("\n"); - return builders.group( + const expression = builders.group( builders.join(" ", [ ["{{", node.delimiter], multiline - ? builders.indent([getMultilineGroup(node.content)]) + ? builders.indent(getMultilineGroup(node.content)) : node.content, multiline ? [builders.hardline, node.delimiter, "}}"] : [node.delimiter, "}}"], ]), { - shouldBreak: node.ownLine, + shouldBreak: node.preNewLines > 0, } ); + + return node.preNewLines > 1 + ? builders.group([builders.hardline, builders.trim, expression]) + : expression; }; const printStatement = (node: Statement): builders.Doc => { @@ -62,20 +61,30 @@ const printStatement = (node: Statement): builders.Doc => { ? [builders.hardline, node.delimiter, "%}"] : [node.delimiter, "%}"], ]), - { shouldBreak: node.ownLine } + { shouldBreak: node.preNewLines > 0 } ); if ( ["else", "elif"].includes(node.keyword) && - surroundingBlock(node)?.ownLine + surroundingBlock(node)?.containsNewLines ) { return [builders.dedent(builders.hardline), statemnt, builders.hardline]; } return statemnt; }; -const printIgnoreBlock = (node: IgnoreBlock): builders.Doc => { - return builders.group(node.content, { shouldBreak: node.ownLine }); +const printCommentBlock = (node: Node): builders.Doc => { + const comment = builders.group(node.content, { + shouldBreak: node.preNewLines > 0, + }); + + return node.preNewLines > 1 + ? builders.group([builders.hardline, builders.trim, comment]) + : comment; +}; + +const printIgnoreBlock = (node: Node): builders.Doc => { + return node.content; }; export const embed: Printer ["embed"] = ( @@ -150,22 +159,11 @@ export const embed: Printer ["embed"] = ( }); if (node.type === "block") { - if (node.content.includes("\n")) { - return builders.group([ - path.call(print, "nodes", (node as Block).start.id), - builders.indent([ - builders.softline, - utils.stripTrailingHardline(mapped), - ]), - builders.hardline, - path.call(print, "nodes", (node as Block).end.id), - ]); - } - return builders.group([ - path.call(print, "nodes", (node as Block).start.id), - utils.stripTrailingHardline(mapped), - path.call(print, "nodes", (node as Block).end.id), - ]); + const block = buildBlock(path, print, node as Block, mapped); + + return node.preNewLines > 1 + ? builders.group([builders.hardline, builders.trim, block]) + : block; } return [...mapped, builders.hardline]; }; @@ -223,3 +221,24 @@ export const surroundingBlock = (node: Node): Block | undefined => { (n) => n.type === "block" && n.content.search(node.id) !== NOT_FOUND ) as Block; }; + +const buildBlock = ( + path: AstPath , + print: (path: AstPath ) => builders.Doc, + block: Block, + mapped: (string | builders.Doc[] | builders.DocCommand)[] +): builders.Doc => { + if (block.containsNewLines) { + return builders.group([ + path.call(print, "nodes", block.start.id), + builders.indent([builders.softline, utils.stripTrailingHardline(mapped)]), + builders.hardline, + path.call(print, "nodes", block.end.id), + ]); + } + return builders.group([ + path.call(print, "nodes", block.start.id), + utils.stripTrailingHardline(mapped), + path.call(print, "nodes", block.end.id), + ]); +}; diff --git a/test/cases/expression/expected.html b/test/cases/expression/expected.html index d34eb4c..b73aa96 100644 --- a/test/cases/expression/expected.html +++ b/test/cases/expression/expected.html @@ -1,5 +1,3 @@ - - {{ user.name }} -+{{ user.name }}{{ user.age }}diff --git a/test/cases/newline_between/expected.html b/test/cases/newline_between/expected.html new file mode 100644 index 0000000..ff17c82 --- /dev/null +++ b/test/cases/newline_between/expected.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block title %}Index{% endblock %} + +{% block head %} + {{ super() }} + +{% endblock %} + +{{ title }} + +{# content #} +{% block content %} +Index
+Welcome to my awesome homepage.
+{% endblock %} + + + +{{ name }} + +{% for user in users %} +{{ user.username|e }} +{% else %} +no users found +{% endfor %} diff --git a/test/cases/newline_between/input.html b/test/cases/newline_between/input.html new file mode 100644 index 0000000..2704213 --- /dev/null +++ b/test/cases/newline_between/input.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + + + {% block title %}Index{% endblock %} + + +{% block head %} + {{ super() }} + +{% endblock %} + + +{{title}} + + +{# content #} +{% block content %} +Index
++ Welcome to my awesome homepage. +
+ +{% endblock %} + + + +{{name}} + + +{% for user in users %} +{{ user.username|e }} + + + {% else %} +no users found + + {% endfor %}