Skip to content

Commit

Permalink
use regex library and reformat regex
Browse files Browse the repository at this point in the history
I also noticed that there is potential for catastrophic backtracking
and hopefully fixed it.
  • Loading branch information
David Odenwald authored and davidodenwald committed Sep 29, 2024
1 parent 0a0437c commit ccce1f5
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 10 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@
},
"peerDependencies": {
"prettier": "^3.0.0"
},
"dependencies": {
"regex": "^4.3.2"
}
}
55 changes: 45 additions & 10 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,52 @@
import { Parser } from "prettier";
import {
Delimiter,
Node,
Placeholder,
Expression,
Statement,
Block,
} from "./jinja";
import { Node, Placeholder, Expression, Statement, Block } from "./jinja";
const re = require("regex");

const NOT_FOUND = -1;

const regex =
/(?<node>{{(?<startDelimiterEx>[-+]?)\s*(?<expression>'([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*(?<endDelimiterEx>[-+]?)}}|{%(?<startDelimiter>[-+]?)\s*(?<statement>(?<keyword>\w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(?<endDelimiter>[-+]?)%}|(?<ignoreBlock>(?:<!-- prettier-ignore-start -->|{# prettier-ignore-start #})[\s\S]*(?:<!-- prettier-ignore-end -->|{# prettier-ignore-end #}))|(?<comment>{#[\S\s]*?#})|(?<scriptBlock><(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(?<styleBlock><(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>))/;
const regex = re.regex`
(?<node>
# Expression
\{\{
(?<startDelimiterEx>\g<delimiter>?)\s*
(?<expression>(?>\g<escapeQuotes> | \g<allSymbols>)*?)
\s*(?<endDelimiterEx>\g<delimiter>?)
\}\}
|
# Statement
\{%
(?<startDelimiter>\g<delimiter>?)\s*
(?<statement>
(?<keyword>\w+)
(?>\g<escapeQuotes> | \g<allSymbols>)*?
)
\s*(?<endDelimiter>\g<delimiter>?)
%\}
|
# Ignore block
(?<ignoreBlock>
(<!--\s*prettier-ignore-start\s*--> | \{\#\s*prettier-ignore-start\s*\#\})
\g<everything>
(<!--\s*prettier-ignore-end\s*--> | \{\#\s*prettier-ignore-end\s*\#\})
)
|
# Comment
(?<comment>\{\#\g<everything>\#\})
|
# Script Block
(?<scriptBlock><script\g<everything>>\g<everything><\/script>)
|
# Style Block
(?<styleBlock><style\g<everything>>\g<everything><\/style>)
)
(?(DEFINE)
(?<everything> \g<allSymbols>*?)
(?<allSymbols> [\s\S])
(?<delimiter> [\-+])
(?<escapeQuotes> '[^']*'|"[^"]*")
)
`;

export const parse: Parser<Node>["parse"] = (text) => {
const statementStack: Statement[] = [];
Expand Down
24 changes: 24 additions & 0 deletions test/parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { parse } from "../src/parser";
import { ParserOptions } from "prettier";

test("benchmark catastrophic backtracking in expression", async () => {
expect(
(
await parse(
"<div>{{ '''''''''''''''''''''''''''''''''' </div>",
{} as ParserOptions,
)
).content,
).toEqual("<div>{{ '''''''''''''''''''''''''''''''''' </div>");
});

test("benchmark catastrophic backtracking in statement", async () => {
expect(
(
await parse(
"<div>{% for '''''''''''''''''''''''''''''''''''' </div>",
{} as ParserOptions,
)
).content,
).toEqual("<div>{% for '''''''''''''''''''''''''''''''''''' </div>");
});

0 comments on commit ccce1f5

Please sign in to comment.