Compare commits
17 commits
better-tes
...
master
Author | SHA1 | Date | |
---|---|---|---|
6e119d7583 | |||
8409c44d0d | |||
79e54384f3 | |||
559b2b0506 | |||
|
bea4b534bc | ||
|
c0f1ca478d | ||
|
be28ec949f | ||
|
ac512dd0b3 | ||
|
f54a1ff40a | ||
|
fd332a60e1 | ||
|
c854cfd82d | ||
|
1aec2a655e | ||
|
85a635f03b | ||
|
d72173d3fe | ||
|
60a2cc487e | ||
|
65dd4f1aea | ||
|
7620e52d97 |
21 changed files with 1413 additions and 1181 deletions
14
.github/workflows/node.js.yml
vendored
14
.github/workflows/node.js.yml
vendored
|
@ -17,14 +17,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
- name: Install dependencies
|
- run: npm install -f
|
||||||
run: npm install -f
|
- run: npm run build
|
||||||
|
- run: npm run test
|
||||||
- name: Build project
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: npm run test
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: codecov/codecov-action@v1
|
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
.vscode
|
.vscode
|
||||||
node_modules
|
node_modules
|
||||||
lib
|
lib
|
||||||
coverage
|
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
registry=https://git.fetbuk.ru/api/packages/WerySkok/npm/
|
21
README.md
21
README.md
|
@ -1,11 +1,17 @@
|
||||||
# prettier-plugin-jinja-template
|
# prettier-plugin-askama-template
|
||||||
|
[prettier-plugin-jinja-template](https://github.com/davidodenwald/prettier-plugin-jinja-template) but with patches to work with Askama.
|
||||||
Formatter plugin for jinja2 template files.
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install --save-dev prettier prettier-plugin-jinja-template
|
npm install --save-dev prettier https://git.fetbuk.ru/WerySkok/-/packages/npm/prettier-plugin-askama-template/1.0.0/files/2
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the plugin to your `.prettierrc`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-askama-template"]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
@ -17,7 +23,7 @@ To format basic .html files, you'll have to override the used parser inside your
|
||||||
{
|
{
|
||||||
"files": ["*.html"],
|
"files": ["*.html"],
|
||||||
"options": {
|
"options": {
|
||||||
"parser": "jinja-template"
|
"parser": "askama-template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -27,4 +33,9 @@ To format basic .html files, you'll have to override the used parser inside your
|
||||||
Run it on all html files in your project:
|
Run it on all html files in your project:
|
||||||
```bash
|
```bash
|
||||||
npx prettier --write **/*.html
|
npx prettier --write **/*.html
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't have a prettier config you can run the plugin with this command:
|
||||||
|
```bash
|
||||||
|
npx prettier --write **/*.html --plugin=prettier-plugin-askama-template
|
||||||
```
|
```
|
|
@ -1,8 +1,4 @@
|
||||||
const { defaults } = require("jest-config");
|
|
||||||
|
|
||||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||||
module.exports = () => ({
|
module.exports = () => ({
|
||||||
preset: "ts-jest",
|
preset: "ts-jest",
|
||||||
moduleFileExtensions: [...defaults.moduleFileExtensions, "html"],
|
|
||||||
collectCoverage: true,
|
|
||||||
});
|
});
|
||||||
|
|
2036
package-lock.json
generated
2036
package-lock.json
generated
File diff suppressed because it is too large
Load diff
34
package.json
34
package.json
|
@ -1,43 +1,41 @@
|
||||||
{
|
{
|
||||||
"name": "prettier-plugin-jinja-template",
|
"name": "prettier-plugin-askama-template",
|
||||||
"version": "0.2.0",
|
"version": "1.0.0",
|
||||||
"description": "Prettier plugin for formatting jinja templates.",
|
"description": "prettier-plugin-jinja-template but with patches to work with Askama.",
|
||||||
"author": "David Odenwald",
|
"author": "Alexander Minkin",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/davidodenwald/prettier-plugin-jinja-template.git"
|
"url": "git+https://git.fetbuk.ru/WerySkok/prettier-plugin-askama-template.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/davidodenwald/prettier-plugin-jinja-template/issues"
|
"url": "https://git.fetbuk.ru/WerySkok/prettier-plugin-askama-template/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/davidodenwald/prettier-plugin-jinja-template#readme",
|
"homepage": "https://git.fetbuk.ru/WerySkok/prettier-plugin-askama-template#readme",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"prettier",
|
"prettier",
|
||||||
"plugin",
|
"plugin",
|
||||||
"template",
|
"template",
|
||||||
"html",
|
"html",
|
||||||
"jinja",
|
"askama"
|
||||||
"jinja2",
|
|
||||||
"flask"
|
|
||||||
],
|
],
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"watch": "npm run build -- --watch",
|
"watch": "npm run build -- --watch",
|
||||||
"test": "jest --verbose",
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --verbose",
|
||||||
"test:watch": "jest --watch --verbose",
|
"test:watch": "jest --watch --verbose",
|
||||||
"publish:release": "npm run test && npm run build && npm publish"
|
"publish:release": "npm run test && npm run build && npm publish"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-typescript": "^7.21.0",
|
"@babel/preset-typescript": "^7.22.5",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.6.2",
|
||||||
"prettier": "^2.8.4",
|
"prettier": "^3.0.1",
|
||||||
"ts-jest": "^29.0.5",
|
"ts-jest": "^29.1.1",
|
||||||
"typescript": "^5.0.2"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"prettier": "^2.0.0"
|
"prettier": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/index.ts
11
src/index.ts
|
@ -1,16 +1,16 @@
|
||||||
import { Node } from "./jinja";
|
import { Node } from "./jinja";
|
||||||
import { parse } from "./parser";
|
import { parse } from "./parser";
|
||||||
import { print, embed } from "./printer";
|
import { print, embed, getVisitorKeys } from "./printer";
|
||||||
import { Parser, Printer, SupportLanguage } from "prettier";
|
import { Parser, Printer, SupportLanguage } from "prettier";
|
||||||
|
|
||||||
const PLUGIN_KEY = "jinja-template";
|
const PLUGIN_KEY = "askama-template";
|
||||||
|
|
||||||
export const languages: SupportLanguage[] = [
|
export const languages: SupportLanguage[] = [
|
||||||
{
|
{
|
||||||
name: "JinjaTemplate",
|
name: "AskamaTemplate",
|
||||||
parsers: [PLUGIN_KEY],
|
parsers: [PLUGIN_KEY],
|
||||||
extensions: [".jinja", ".jinja2", ".j2", ".html"],
|
extensions: [".jinja", ".jinja2", ".j2", ".html", "askama"],
|
||||||
vscodeLanguageIds: ["jinja"],
|
vscodeLanguageIds: ["askama"],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -27,5 +27,6 @@ export const printers = {
|
||||||
[PLUGIN_KEY]: <Printer<Node>>{
|
[PLUGIN_KEY]: <Printer<Node>>{
|
||||||
print,
|
print,
|
||||||
embed,
|
embed,
|
||||||
|
getVisitorKeys,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
12
src/jinja.ts
12
src/jinja.ts
|
@ -5,9 +5,9 @@ export const Placeholder = {
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
id: string;
|
id: string;
|
||||||
type: "root" | "expression" | "statement" | "block" | "ignore";
|
type: "root" | "expression" | "statement" | "block" | "comment" | "ignore";
|
||||||
content: string;
|
content: string;
|
||||||
ownLine: boolean;
|
preNewLines: number;
|
||||||
originalText: string;
|
originalText: string;
|
||||||
index: number;
|
index: number;
|
||||||
length: number;
|
length: number;
|
||||||
|
@ -16,6 +16,7 @@ export interface Node {
|
||||||
|
|
||||||
export interface Expression extends Node {
|
export interface Expression extends Node {
|
||||||
type: "expression";
|
type: "expression";
|
||||||
|
delimiter: Delimiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Delimiter = "" | "-" | "+";
|
export type Delimiter = "" | "-" | "+";
|
||||||
|
@ -30,10 +31,7 @@ export interface Block extends Node {
|
||||||
type: "block";
|
type: "block";
|
||||||
start: Statement;
|
start: Statement;
|
||||||
end: Statement;
|
end: Statement;
|
||||||
}
|
containsNewLines: boolean;
|
||||||
|
|
||||||
export interface IgnoreBlock extends Node {
|
|
||||||
type: "ignore";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nonClosingStatements = [
|
export const nonClosingStatements = [
|
||||||
|
@ -43,4 +41,6 @@ export const nonClosingStatements = [
|
||||||
"import",
|
"import",
|
||||||
"from",
|
"from",
|
||||||
"extends",
|
"extends",
|
||||||
|
"pluralize",
|
||||||
|
"when"
|
||||||
];
|
];
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Delimiter,
|
Delimiter,
|
||||||
Node,
|
Node,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
|
Expression,
|
||||||
Statement,
|
Statement,
|
||||||
Block,
|
Block,
|
||||||
nonClosingStatements,
|
nonClosingStatements,
|
||||||
|
@ -11,7 +12,7 @@ import {
|
||||||
const NOT_FOUND = -1;
|
const NOT_FOUND = -1;
|
||||||
|
|
||||||
const regex =
|
const regex =
|
||||||
/(?<pre>(?<newline>\n)?(\s*?))(?<node>{{\s*(?<expression>'([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*}}|{%(?<startDelimiter>[-+]?)\s*(?<statement>(?<keyword>\w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(?<endDelimiter>[-+]?)%}|(?<comment>{#[\S\s]*?#})|(?<scriptBlock><(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(?<styleBlock><(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>)|(?<ignoreBlock><!-- prettier-ignore-start -->[\s\S]*<!-- prettier-ignore-end -->))/;
|
/(?<node>{{(?<startDelimiterEx>[-+]?)\s*(?<expression>'([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*(?<endDelimiterEx>[-+]?)}}|{%(?<startDelimiter>[-+]?)\s*(?<statement>(?<keyword>\w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(?<endDelimiter>[-+]?)%}|(?<comment>{#[\S\s]*?#})|(?<scriptBlock><(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(?<styleBlock><(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>)|(?<ignoreBlock><!-- prettier-ignore-start -->[\s\S]*<!-- prettier-ignore-end -->))/;
|
||||||
|
|
||||||
export const parse: Parser<Node>["parse"] = (text) => {
|
export const parse: Parser<Node>["parse"] = (text) => {
|
||||||
const statementStack: Statement[] = [];
|
const statementStack: Statement[] = [];
|
||||||
|
@ -20,7 +21,7 @@ export const parse: Parser<Node>["parse"] = (text) => {
|
||||||
id: "0",
|
id: "0",
|
||||||
type: "root" as const,
|
type: "root" as const,
|
||||||
content: text,
|
content: text,
|
||||||
ownLine: false,
|
preNewLines: 0,
|
||||||
originalText: text,
|
originalText: text,
|
||||||
index: 0,
|
index: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
|
@ -43,9 +44,6 @@ export const parse: Parser<Node>["parse"] = (text) => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pre = match.groups.pre || "";
|
|
||||||
const newline = !!match.groups.newline;
|
|
||||||
|
|
||||||
const matchText = match.groups.node;
|
const matchText = match.groups.node;
|
||||||
const expression = match.groups.expression;
|
const expression = match.groups.expression;
|
||||||
const statement = match.groups.statement;
|
const statement = match.groups.statement;
|
||||||
|
@ -57,31 +55,42 @@ export const parse: Parser<Node>["parse"] = (text) => {
|
||||||
}
|
}
|
||||||
const placeholder = generatePlaceholder();
|
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 = {
|
const node = {
|
||||||
id: placeholder,
|
id: placeholder,
|
||||||
ownLine: newline,
|
preNewLines,
|
||||||
originalText: matchText,
|
originalText: matchText,
|
||||||
index: match.index + i + pre.length,
|
index: match.index + i,
|
||||||
length: matchText.length,
|
length: matchText.length,
|
||||||
nodes: root.nodes,
|
nodes: root.nodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ignoreBlock || comment) {
|
if (comment || ignoreBlock) {
|
||||||
root.content = root.content.replace(matchText, placeholder);
|
root.content = root.content.replace(matchText, placeholder);
|
||||||
root.nodes[node.id] = {
|
root.nodes[node.id] = {
|
||||||
...node,
|
...node,
|
||||||
type: "ignore",
|
type: comment ? "comment" : "ignore",
|
||||||
content: ignoreBlock || comment,
|
content: comment || ignoreBlock,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expression) {
|
if (expression) {
|
||||||
|
const delimiter = (match.groups.startDelimiterEx ||
|
||||||
|
match.groups.endDelimiterEx) as Delimiter;
|
||||||
|
|
||||||
root.content = root.content.replace(matchText, placeholder);
|
root.content = root.content.replace(matchText, placeholder);
|
||||||
root.nodes[node.id] = {
|
root.nodes[node.id] = {
|
||||||
...node,
|
...node,
|
||||||
type: "expression",
|
type: "expression",
|
||||||
content: expression,
|
content: expression,
|
||||||
};
|
delimiter,
|
||||||
|
} as Expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (statement) {
|
if (statement) {
|
||||||
|
@ -127,7 +136,7 @@ export const parse: Parser<Node>["parse"] = (text) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Closung statement "${statement}" doesn't match Opening Statement "${start.content}".`
|
`Closing statement "${statement}" doesn't match Opening Statement "${start.content}".`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +162,8 @@ export const parse: Parser<Node>["parse"] = (text) => {
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
content: blockText.slice(start.length, blockText.length - end.length),
|
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,
|
originalText,
|
||||||
index: start.index,
|
index: start.index,
|
||||||
length: end.index + end.length - start.index,
|
length: end.index + end.length - start.index,
|
||||||
|
|
266
src/printer.ts
266
src/printer.ts
|
@ -1,18 +1,24 @@
|
||||||
import { Printer } from "prettier";
|
import { AstPath, Printer, Options, Doc } from "prettier";
|
||||||
import { builders, utils } from "prettier/doc";
|
import { builders, utils } from "prettier/doc";
|
||||||
import {
|
import { Placeholder, Node, Expression, Statement, Block } from "./jinja";
|
||||||
Placeholder,
|
|
||||||
Node,
|
|
||||||
Expression,
|
|
||||||
Statement,
|
|
||||||
Block,
|
|
||||||
IgnoreBlock,
|
|
||||||
} from "./jinja";
|
|
||||||
|
|
||||||
const NOT_FOUND = -1;
|
const NOT_FOUND = -1;
|
||||||
|
|
||||||
process.env.PRETTIER_DEBUG = "true";
|
process.env.PRETTIER_DEBUG = "true";
|
||||||
|
|
||||||
|
export const getVisitorKeys = (
|
||||||
|
ast: Node | { [id: string]: Node },
|
||||||
|
): string[] => {
|
||||||
|
if ("type" in ast) {
|
||||||
|
return ast.type === "root" ? ["nodes"] : [];
|
||||||
|
}
|
||||||
|
return Object.values(ast)
|
||||||
|
.filter((node) => {
|
||||||
|
return node.type === "block";
|
||||||
|
})
|
||||||
|
.map((e) => e.id);
|
||||||
|
};
|
||||||
|
|
||||||
export const print: Printer<Node>["print"] = (path) => {
|
export const print: Printer<Node>["print"] = (path) => {
|
||||||
const node = path.getNode();
|
const node = path.getNode();
|
||||||
if (!node) {
|
if (!node) {
|
||||||
|
@ -24,8 +30,10 @@ export const print: Printer<Node>["print"] = (path) => {
|
||||||
return printExpression(node as Expression);
|
return printExpression(node as Expression);
|
||||||
case "statement":
|
case "statement":
|
||||||
return printStatement(node as Statement);
|
return printStatement(node as Statement);
|
||||||
|
case "comment":
|
||||||
|
return printCommentBlock(node);
|
||||||
case "ignore":
|
case "ignore":
|
||||||
return printIgnoreBlock(node as IgnoreBlock);
|
return printIgnoreBlock(node);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
@ -33,18 +41,24 @@ export const print: Printer<Node>["print"] = (path) => {
|
||||||
const printExpression = (node: Expression): builders.Doc => {
|
const printExpression = (node: Expression): builders.Doc => {
|
||||||
const multiline = node.content.includes("\n");
|
const multiline = node.content.includes("\n");
|
||||||
|
|
||||||
return builders.group(
|
const expression = builders.group(
|
||||||
builders.join(" ", [
|
builders.join(" ", [
|
||||||
"{{",
|
["{{", node.delimiter],
|
||||||
multiline
|
multiline
|
||||||
? builders.indent([getMultilineGroup(node.content)])
|
? builders.indent(getMultilineGroup(node.content))
|
||||||
: node.content,
|
: node.content,
|
||||||
multiline ? [builders.hardline, "}}"] : "}}",
|
multiline
|
||||||
|
? [builders.hardline, node.delimiter, "}}"]
|
||||||
|
: [node.delimiter, "}}"],
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
shouldBreak: node.ownLine,
|
shouldBreak: node.preNewLines > 0,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return node.preNewLines > 1
|
||||||
|
? builders.group([builders.trim, builders.hardline, expression])
|
||||||
|
: expression;
|
||||||
};
|
};
|
||||||
|
|
||||||
const printStatement = (node: Statement): builders.Doc => {
|
const printStatement = (node: Statement): builders.Doc => {
|
||||||
|
@ -60,110 +74,113 @@ const printStatement = (node: Statement): builders.Doc => {
|
||||||
? [builders.hardline, node.delimiter, "%}"]
|
? [builders.hardline, node.delimiter, "%}"]
|
||||||
: [node.delimiter, "%}"],
|
: [node.delimiter, "%}"],
|
||||||
]),
|
]),
|
||||||
{ shouldBreak: node.ownLine }
|
{ shouldBreak: node.preNewLines > 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
["else", "elif"].includes(node.keyword) &&
|
["else", "elif"].includes(node.keyword) &&
|
||||||
surroundingBlock(node)?.ownLine
|
surroundingBlock(node)?.containsNewLines
|
||||||
) {
|
) {
|
||||||
return [builders.dedent(builders.hardline), statemnt, builders.hardline];
|
return [builders.dedent(builders.hardline), statemnt, builders.hardline];
|
||||||
}
|
}
|
||||||
return statemnt;
|
return statemnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
const printIgnoreBlock = (node: IgnoreBlock): builders.Doc => {
|
const printCommentBlock = (node: Node): builders.Doc => {
|
||||||
return builders.group(node.content, { shouldBreak: node.ownLine });
|
const comment = builders.group(node.content, {
|
||||||
};
|
shouldBreak: node.preNewLines > 0,
|
||||||
|
|
||||||
export const embed: Printer<Node>["embed"] = (
|
|
||||||
path,
|
|
||||||
print,
|
|
||||||
textToDoc,
|
|
||||||
options
|
|
||||||
) => {
|
|
||||||
const node = path.getNode();
|
|
||||||
if (!node || !["root", "block"].includes(node.type)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapped = splitAtElse(node).map((content) => {
|
|
||||||
let doc;
|
|
||||||
if (content in node.nodes) {
|
|
||||||
doc = content;
|
|
||||||
} else {
|
|
||||||
doc = utils.stripTrailingHardline(
|
|
||||||
textToDoc(content, {
|
|
||||||
...options,
|
|
||||||
parser: "html",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ignoreDoc = false;
|
|
||||||
|
|
||||||
return utils.mapDoc(doc, (currentDoc) => {
|
|
||||||
if (typeof currentDoc !== "string") {
|
|
||||||
return currentDoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentDoc === "<!-- prettier-ignore -->") {
|
|
||||||
ignoreDoc = true;
|
|
||||||
return currentDoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const idxs = findPlaceholders(currentDoc).filter(
|
|
||||||
([start, end]) => currentDoc.slice(start, end + 1) in node.nodes
|
|
||||||
);
|
|
||||||
if (!idxs.length) {
|
|
||||||
ignoreDoc = false;
|
|
||||||
return currentDoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res: builders.Doc = [];
|
|
||||||
let lastEnd = 0;
|
|
||||||
for (const [start, end] of idxs) {
|
|
||||||
if (lastEnd < start) {
|
|
||||||
res.push(currentDoc.slice(lastEnd, start));
|
|
||||||
}
|
|
||||||
|
|
||||||
const p = currentDoc.slice(start, end + 1) as string;
|
|
||||||
|
|
||||||
if (ignoreDoc) {
|
|
||||||
res.push(node.nodes[p].originalText);
|
|
||||||
} else {
|
|
||||||
res.push(path.call(print, "nodes", p));
|
|
||||||
}
|
|
||||||
|
|
||||||
lastEnd = end + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastEnd > 0 && currentDoc.length > lastEnd) {
|
|
||||||
res.push(currentDoc.slice(lastEnd));
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreDoc = false;
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return node.preNewLines > 1
|
||||||
|
? builders.group([builders.trim, builders.hardline, comment])
|
||||||
|
: comment;
|
||||||
|
};
|
||||||
|
|
||||||
|
const printIgnoreBlock = (node: Node): builders.Doc => {
|
||||||
|
return node.content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const embed: Printer<Node>["embed"] = () => {
|
||||||
|
return _embed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _embed = async (
|
||||||
|
textToDoc: (text: string, options: Options) => Promise<Doc>,
|
||||||
|
print: (selector?: string | number | Array<string | number> | AstPath) => Doc,
|
||||||
|
path: AstPath,
|
||||||
|
options: Options,
|
||||||
|
): Promise<Doc | undefined> => {
|
||||||
|
const node = path.getNode();
|
||||||
|
if (!node || !["root", "block"].includes(node.type)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapped = await Promise.all(
|
||||||
|
splitAtElse(node).map(async (content) => {
|
||||||
|
let doc;
|
||||||
|
if (content in node.nodes) {
|
||||||
|
doc = content;
|
||||||
|
} else {
|
||||||
|
doc = await textToDoc(content, {
|
||||||
|
...options,
|
||||||
|
parser: "html",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let ignoreDoc = false;
|
||||||
|
|
||||||
|
return utils.mapDoc(doc, (currentDoc) => {
|
||||||
|
if (typeof currentDoc !== "string") {
|
||||||
|
return currentDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDoc === "<!-- prettier-ignore -->") {
|
||||||
|
ignoreDoc = true;
|
||||||
|
return currentDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idxs = findPlaceholders(currentDoc).filter(
|
||||||
|
([start, end]) => currentDoc.slice(start, end + 1) in node.nodes,
|
||||||
|
);
|
||||||
|
if (!idxs.length) {
|
||||||
|
ignoreDoc = false;
|
||||||
|
return currentDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res: builders.Doc = [];
|
||||||
|
let lastEnd = 0;
|
||||||
|
for (const [start, end] of idxs) {
|
||||||
|
if (lastEnd < start) {
|
||||||
|
res.push(currentDoc.slice(lastEnd, start));
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = currentDoc.slice(start, end + 1) as string;
|
||||||
|
|
||||||
|
if (ignoreDoc) {
|
||||||
|
res.push(node.nodes[p].originalText);
|
||||||
|
} else {
|
||||||
|
res.push(path.call(print, "nodes", p));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEnd = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastEnd > 0 && currentDoc.length > lastEnd) {
|
||||||
|
res.push(currentDoc.slice(lastEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreDoc = false;
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
if (node.type === "block") {
|
if (node.type === "block") {
|
||||||
if (node.content.includes("\n")) {
|
const block = buildBlock(path, print, node as Block, mapped);
|
||||||
return builders.group([
|
|
||||||
path.call(print, "nodes", (node as Block).start.id),
|
return node.preNewLines > 1
|
||||||
builders.indent([
|
? builders.group([builders.trim, builders.hardline, block])
|
||||||
builders.softline,
|
: block;
|
||||||
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),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
return [...mapped, builders.hardline];
|
return [...mapped, builders.hardline];
|
||||||
};
|
};
|
||||||
|
@ -172,7 +189,7 @@ const getMultilineGroup = (content: String): builders.Group => {
|
||||||
return builders.group(
|
return builders.group(
|
||||||
content.split("\n").map((line, i) => {
|
content.split("\n").map((line, i) => {
|
||||||
return [builders.hardline, line.trim()];
|
return [builders.hardline, line.trim()];
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -181,7 +198,7 @@ const splitAtElse = (node: Node): string[] => {
|
||||||
(n) =>
|
(n) =>
|
||||||
n.type === "statement" &&
|
n.type === "statement" &&
|
||||||
["else", "elif"].includes((n as Statement).keyword) &&
|
["else", "elif"].includes((n as Statement).keyword) &&
|
||||||
node.content.search(n.id) !== NOT_FOUND
|
node.content.search(n.id) !== NOT_FOUND,
|
||||||
);
|
);
|
||||||
if (!elseNodes.length) {
|
if (!elseNodes.length) {
|
||||||
return [node.content];
|
return [node.content];
|
||||||
|
@ -218,6 +235,35 @@ export const findPlaceholders = (text: string): [number, number][] => {
|
||||||
|
|
||||||
export const surroundingBlock = (node: Node): Block | undefined => {
|
export const surroundingBlock = (node: Node): Block | undefined => {
|
||||||
return Object.values(node.nodes).find(
|
return Object.values(node.nodes).find(
|
||||||
(n) => n.type === "block" && n.content.search(node.id) !== NOT_FOUND
|
(n) => n.type === "block" && n.content.search(node.id) !== NOT_FOUND,
|
||||||
) as Block;
|
) as Block;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildBlock = (
|
||||||
|
path: AstPath<Node>,
|
||||||
|
print: (path: AstPath<Node>) => builders.Doc,
|
||||||
|
block: Block,
|
||||||
|
mapped: (string | builders.Doc[] | builders.DocCommand)[],
|
||||||
|
): builders.Doc => {
|
||||||
|
// if the content is empty or whitespace only.
|
||||||
|
if (block.content.match(/^\s*$/)) {
|
||||||
|
return builders.fill([
|
||||||
|
path.call(print, "nodes", block.start.id),
|
||||||
|
builders.softline,
|
||||||
|
path.call(print, "nodes", block.end.id),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (block.containsNewLines) {
|
||||||
|
return builders.group([
|
||||||
|
path.call(print, "nodes", block.start.id),
|
||||||
|
builders.indent([builders.softline, mapped]),
|
||||||
|
builders.hardline,
|
||||||
|
path.call(print, "nodes", block.end.id),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return builders.group([
|
||||||
|
path.call(print, "nodes", block.start.id),
|
||||||
|
mapped,
|
||||||
|
path.call(print, "nodes", block.end.id),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
<div>
|
<div>{{ user.name }}</div>
|
||||||
{{ user.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>{{ user.age }}</div>
|
<div>{{ user.age }}</div>
|
||||||
|
|
9
test/cases/expression_whitespace/expected.html
Normal file
9
test/cases/expression_whitespace/expected.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="extemely_long__class_name extemely_long__class_name--modifier">
|
||||||
|
{{- some_variable -}}
|
||||||
|
</div>
|
||||||
|
<div class="extemely_long__class_name extemely_long__class_name--modifier">
|
||||||
|
{{- some_variable -}}
|
||||||
|
</div>
|
||||||
|
<div class="extemely_long__class_name extemely_long__class_name--modifier">
|
||||||
|
{{- some_variable -}}
|
||||||
|
</div>
|
9
test/cases/expression_whitespace/input.html
Normal file
9
test/cases/expression_whitespace/input.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="extemely_long__class_name extemely_long__class_name--modifier">
|
||||||
|
{{- some_variable -}}
|
||||||
|
</div>
|
||||||
|
<div class="extemely_long__class_name extemely_long__class_name--modifier">
|
||||||
|
{{ some_variable -}}
|
||||||
|
</div>
|
||||||
|
<div class="extemely_long__class_name extemely_long__class_name--modifier">
|
||||||
|
{{- some_variable }}
|
||||||
|
</div>
|
46
test/cases/newline_between/expected.html
Normal file
46
test/cases/newline_between/expected.html
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Index{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<style type="text/css">
|
||||||
|
.important {
|
||||||
|
color: #336699;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{{ title }}
|
||||||
|
|
||||||
|
{# content #}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Index</h1>
|
||||||
|
<p class="important">Welcome to my awesome homepage.</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
{{ name }}
|
||||||
|
|
||||||
|
{% for user in users %}
|
||||||
|
<li>{{ user.username|e }}</li>
|
||||||
|
{% else %}
|
||||||
|
<li><em>no users found</em></li>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% macro input(name, value='', type='text', size=20) %}
|
||||||
|
|
||||||
|
{{ my_variable|default('my_variable is not defined') }}
|
||||||
|
|
||||||
|
{% for city, items in users|groupby("city") %}
|
||||||
|
<li>
|
||||||
|
{{ city }}
|
||||||
|
<ul>
|
||||||
|
{% for user in items %}
|
||||||
|
<li>{{ user.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endmacro %}
|
53
test/cases/newline_between/input.html
Normal file
53
test/cases/newline_between/input.html
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}Index{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<style type="text/css">
|
||||||
|
.important { color: #336699; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{{title}}
|
||||||
|
|
||||||
|
|
||||||
|
{# content #}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Index</h1>
|
||||||
|
<p class="important">
|
||||||
|
Welcome to my awesome homepage.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
{{name}}
|
||||||
|
|
||||||
|
|
||||||
|
{% for user in users %}
|
||||||
|
<li>{{ user.username|e }}</li>
|
||||||
|
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<li><em>no users found</em></li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% macro input(name, value='', type='text', size=20) %}
|
||||||
|
|
||||||
|
{{ my_variable|default('my_variable is not defined') }}
|
||||||
|
|
||||||
|
{% for city, items in users|groupby("city") %}
|
||||||
|
<li>{{ city }}
|
||||||
|
<ul>{% for user in items %}
|
||||||
|
<li>{{ user.name }}
|
||||||
|
{% endfor %}</ul>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endmacro %}
|
|
@ -1 +1 @@
|
||||||
Error('Closung statement "endif" doesn't match Opening Statement "for item in seq".')
|
Error('Closing statement "endif" doesn't match Opening Statement "for item in seq".')
|
||||||
|
|
2
test/cases/statement_empty_block/expected.html
Normal file
2
test/cases/statement_empty_block/expected.html
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{% block title %}
|
||||||
|
{% endblock %}
|
5
test/cases/statement_empty_block/input.html
Normal file
5
test/cases/statement_empty_block/input.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{% block title %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,11 +1,11 @@
|
||||||
import { existsSync, readdirSync, readFileSync } from "fs";
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { format, Options, ParserOptions } from "prettier";
|
import { format, Options } from "prettier";
|
||||||
import * as jinjaPlugin from "../src/index";
|
import * as jinjaPlugin from "../src/index";
|
||||||
|
|
||||||
const prettify = (code: string, options: Options) =>
|
const prettify = (code: string, options: Options) =>
|
||||||
format(code, {
|
format(code, {
|
||||||
parser: "jinja-template",
|
parser: "askama-template",
|
||||||
plugins: [jinjaPlugin],
|
plugins: [jinjaPlugin],
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@ tests.forEach((test) => {
|
||||||
if (test.startsWith("_")) {
|
if (test.startsWith("_")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return it(test, () => {
|
return it(test, async () => {
|
||||||
const path = join(testFolder, test);
|
const path = join(testFolder, test);
|
||||||
const input = readFileSync(join(path, "input.html")).toString();
|
const input = readFileSync(join(path, "input.html")).toString();
|
||||||
const expected = readFileSync(join(path, "expected.html")).toString();
|
const expected = readFileSync(join(path, "expected.html")).toString();
|
||||||
|
@ -34,21 +34,11 @@ tests.forEach((test) => {
|
||||||
|
|
||||||
if (expectedError) {
|
if (expectedError) {
|
||||||
jest.spyOn(console, "error").mockImplementation(() => {});
|
jest.spyOn(console, "error").mockImplementation(() => {});
|
||||||
expect(format).toThrow(expectedError);
|
await expect(format()).rejects.toThrow(expectedError);
|
||||||
} else {
|
} else {
|
||||||
const result = format();
|
const result = await format();
|
||||||
expect(result).toEqual(expected);
|
expect(result).toEqual(expected);
|
||||||
expect(prettify(result, configObject)).toEqual(expected);
|
expect(await prettify(result, configObject)).toEqual(expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("node has correct locStart and -End", () => {
|
|
||||||
const plugin = jinjaPlugin.parsers["jinja-template"];
|
|
||||||
|
|
||||||
const ast = plugin.parse(`<p>{{ test }}</p>`, {}, {} as ParserOptions);
|
|
||||||
const node = Object.values(ast.nodes)[0]!;
|
|
||||||
|
|
||||||
expect(plugin.locStart(node)).toEqual(3);
|
|
||||||
expect(plugin.locEnd(node)).toEqual(13);
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"target": "ES6",
|
"target": "esnext",
|
||||||
"module": "commonjs",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"lib": ["esnext"],
|
"lib": ["esnext"],
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
|
Loading…
Reference in a new issue