Skip to content

Commit d534f44

Browse files
authored
refactor: cached parse template literal (#391)
* refactor: cached parse template literal * add tests * Update no-trailing-spaces.test.js
1 parent 8823fb3 commit d534f44

File tree

13 files changed

+156
-61
lines changed

13 files changed

+156
-61
lines changed

packages/eslint-plugin/lib/rules/indent/indent.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
* @property {Record<string, number>} [Option2.tagChildrenIndent]
2121
*/
2222

23-
const { getCachedParseResult } = require("../utils/template-cache");
24-
const { traverse } = require("@html-eslint/template-parser/lib/traverser");
23+
const { parseTemplateLiteral } = require("../utils/template-literal");
2524
const { NODE_TYPES } = require("@html-eslint/parser");
2625
const { RULE_CATEGORY } = require("../../constants");
2726
const {
@@ -387,18 +386,21 @@ module.exports = {
387386
TaggedTemplateExpression(node) {
388387
if (shouldCheckTaggedTemplateExpression(node, context)) {
389388
const base = getTemplateLiteralBaseIndentLevel(node.quasi);
390-
const { ast } = getCachedParseResult(
389+
parseTemplateLiteral(
391390
node.quasi,
392-
getSourceCode(context)
391+
getSourceCode(context),
392+
createIndentVisitor(base)
393393
);
394-
traverse(ast, createIndentVisitor(base), null);
395394
}
396395
},
397396
TemplateLiteral(node) {
398397
if (shouldCheckTemplateLiteral(node, context)) {
399398
const base = getTemplateLiteralBaseIndentLevel(node);
400-
const { ast } = getCachedParseResult(node, getSourceCode(context));
401-
traverse(ast, createIndentVisitor(base), null);
399+
parseTemplateLiteral(
400+
node,
401+
getSourceCode(context),
402+
createIndentVisitor(base)
403+
);
402404
}
403405
},
404406
};

packages/eslint-plugin/lib/rules/no-duplicate-id.js

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
* @import {RuleModule} from "../types";
44
*/
55

6-
const { getCachedParseResult } = require("./utils/template-cache");
7-
const { traverse } = require("@html-eslint/template-parser/lib/traverser");
6+
const { parseTemplateLiteral } = require("./utils/template-literal");
87
const { RULE_CATEGORY } = require("../constants");
98
const { findAttr } = require("./utils/node");
109
const {
@@ -91,31 +90,18 @@ module.exports = {
9190
TaggedTemplateExpression(node) {
9291
const idAttrsMap = new Map();
9392
if (shouldCheckTaggedTemplateExpression(node, context)) {
94-
const { ast } = getCachedParseResult(
95-
node.quasi,
96-
getSourceCode(context)
97-
);
98-
traverse(
99-
ast,
100-
{
101-
Tag: createTagVisitor(idAttrsMap),
102-
},
103-
null
104-
);
93+
parseTemplateLiteral(node.quasi, getSourceCode(context), {
94+
Tag: createTagVisitor(idAttrsMap),
95+
});
10596
}
10697
report(idAttrsMap);
10798
},
10899
TemplateLiteral(node) {
109100
const idAttrsMap = new Map();
110101
if (shouldCheckTemplateLiteral(node, context)) {
111-
const { ast } = getCachedParseResult(node, getSourceCode(context));
112-
traverse(
113-
ast,
114-
{
115-
Tag: createTagVisitor(idAttrsMap),
116-
},
117-
null
118-
);
102+
parseTemplateLiteral(node, getSourceCode(context), {
103+
Tag: createTagVisitor(idAttrsMap),
104+
});
119105
}
120106
report(idAttrsMap);
121107
},

packages/eslint-plugin/lib/rules/no-duplicate-in-head.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
* @import {RuleModule} from "../types";
44
*/
55

6-
const { getCachedParseResult } = require("./utils/template-cache");
7-
const { traverse } = require("@html-eslint/template-parser/lib/traverser");
6+
const { parseTemplateLiteral } = require("./utils/template-literal");
87
const { RULE_CATEGORY } = require("../constants");
98
const { findAttr } = require("./utils/node");
109
const {
@@ -169,11 +168,7 @@ module.exports = {
169168

170169
if (shouldCheckTaggedTemplateExpression(node, context)) {
171170
const visitor = createTagVisitor(tagsMap, headCountRef);
172-
const { ast } = getCachedParseResult(
173-
node.quasi,
174-
getSourceCode(context)
175-
);
176-
traverse(ast, visitor, null);
171+
parseTemplateLiteral(node.quasi, getSourceCode(context), visitor);
177172
report(tagsMap);
178173
}
179174
},
@@ -184,8 +179,7 @@ module.exports = {
184179

185180
if (shouldCheckTemplateLiteral(node, context)) {
186181
const visitor = createTagVisitor(tagsMap, headCountRef);
187-
const { ast } = getCachedParseResult(node, getSourceCode(context));
188-
traverse(ast, visitor, null);
182+
parseTemplateLiteral(node, getSourceCode(context), visitor);
189183
report(tagsMap);
190184
}
191185
},

packages/eslint-plugin/lib/rules/no-multiple-empty-lines.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @property {number} Option.max
77
*/
88

9-
const { getCachedParseResult } = require("./utils/template-cache");
9+
const { parseTemplateLiteral } = require("./utils/template-literal");
1010
const { RULE_CATEGORY } = require("../constants");
1111
const {
1212
shouldCheckTaggedTemplateExpression,
@@ -117,7 +117,7 @@ module.exports = {
117117
},
118118
TaggedTemplateExpression(node) {
119119
if (shouldCheckTaggedTemplateExpression(node, context)) {
120-
const { html, tokens } = getCachedParseResult(
120+
const { html, tokens } = parseTemplateLiteral(
121121
node.quasi,
122122
getSourceCode(context)
123123
);
@@ -132,7 +132,7 @@ module.exports = {
132132
},
133133
TemplateLiteral(node) {
134134
if (shouldCheckTemplateLiteral(node, context)) {
135-
const { html, tokens } = getCachedParseResult(
135+
const { html, tokens } = parseTemplateLiteral(
136136
node,
137137
getSourceCode(context)
138138
);

packages/eslint-plugin/lib/rules/no-trailing-spaces.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @import {CommentContent, Text} from "@html-eslint/types";
44
*/
55

6-
const { getCachedParseResult } = require("./utils/template-cache");
6+
const { parseTemplateLiteral } = require("./utils/template-literal");
77
const { RULE_CATEGORY } = require("../constants");
88
const {
99
getTemplateTokens,
@@ -107,7 +107,7 @@ module.exports = {
107107
},
108108
TaggedTemplateExpression(node) {
109109
if (shouldCheckTaggedTemplateExpression(node, context)) {
110-
const { html, tokens } = getCachedParseResult(
110+
const { html, tokens } = parseTemplateLiteral(
111111
node.quasi,
112112
getSourceCode(context)
113113
);
@@ -126,7 +126,7 @@ module.exports = {
126126
},
127127
TemplateLiteral(node) {
128128
if (shouldCheckTemplateLiteral(node, context)) {
129-
const { html, tokens } = getCachedParseResult(
129+
const { html, tokens } = parseTemplateLiteral(
130130
node,
131131
getSourceCode(context)
132132
);

packages/eslint-plugin/lib/rules/utils/template-cache.js renamed to packages/eslint-plugin/lib/rules/utils/template-literal.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
* @import {TemplateLiteral} from "@html-eslint/types";
33
* @import {DocumentNode, AnyToken} from "es-html-parser";
44
* @import {SourceCode} from "eslint";
5+
* @import {TemplateHTMLVisitor} from "@html-eslint/template-parser"
56
*/
67

7-
const { parse } = require("@html-eslint/template-parser");
8+
const { parse, traverse } = require("@html-eslint/template-parser");
89

910
/**
1011
* Cache for parsed template literals to avoid re-parsing the same template multiple times.
@@ -17,20 +18,26 @@ const templateCache = new WeakMap();
1718
* Get or create cached parse result for a template literal.
1819
* @param {TemplateLiteral} node
1920
* @param {SourceCode} sourceCode
21+
* @param {TemplateHTMLVisitor} [visitor]
2022
* @returns {{ast: DocumentNode, html: string, tokens: AnyToken[]}}
2123
*/
22-
function getCachedParseResult(node, sourceCode) {
24+
function parseTemplateLiteral(node, sourceCode, visitor) {
2325
// Check if we already have a cached result for this node
2426
const cachedResult = templateCache.get(node);
2527
if (cachedResult) {
28+
if (visitor) {
29+
traverse(cachedResult.ast, visitor);
30+
}
2631
return cachedResult;
2732
}
2833

2934
// Parse and cache the result
30-
const result = parse(node, sourceCode, {});
35+
const result = parse(node, sourceCode);
3136
templateCache.set(node, result);
32-
37+
if (visitor) {
38+
traverse(result.ast, visitor);
39+
}
3340
return result;
3441
}
3542

36-
module.exports = { getCachedParseResult };
43+
module.exports = { parseTemplateLiteral };

packages/eslint-plugin/lib/rules/utils/visitors.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ const {
66
shouldCheckTaggedTemplateExpression,
77
shouldCheckTemplateLiteral,
88
} = require("./settings");
9-
const { getCachedParseResult } = require("./template-cache");
10-
const { traverse } = require("@html-eslint/template-parser/lib/traverser");
9+
const { parseTemplateLiteral } = require("./template-literal");
1110
const { getSourceCode } = require("./source-code");
1211

1312
/**
@@ -19,17 +18,12 @@ function createTemplateVisitors(context, visitors) {
1918
return {
2019
TaggedTemplateExpression(node) {
2120
if (shouldCheckTaggedTemplateExpression(node, context)) {
22-
const { ast } = getCachedParseResult(
23-
node.quasi,
24-
getSourceCode(context)
25-
);
26-
traverse(ast, visitors, null);
21+
parseTemplateLiteral(node.quasi, getSourceCode(context), visitors);
2722
}
2823
},
2924
TemplateLiteral(node) {
3025
if (shouldCheckTemplateLiteral(node, context)) {
31-
const { ast } = getCachedParseResult(node, getSourceCode(context));
32-
traverse(ast, visitors, null);
26+
parseTemplateLiteral(node, getSourceCode(context), visitors);
3327
}
3428
},
3529
};

packages/eslint-plugin/tests/rules/no-duplicate-id.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ html\`<html>
7272
</html>\`
7373
`,
7474
},
75+
{
76+
code: `const code = /* html */\`<html>
77+
<body>
78+
<div id = "foo"></div>
79+
<div id = "bar"></div>
80+
</body>
81+
</html>\``,
82+
},
7583
],
7684
invalid: [
7785
{
@@ -103,6 +111,27 @@ html\`<html>
103111
<a id = "\${foo}"></div>
104112
</body>
105113
</html>\`
114+
`,
115+
116+
errors: [
117+
{
118+
messageId: "duplicateId",
119+
line: 4,
120+
},
121+
{
122+
messageId: "duplicateId",
123+
line: 5,
124+
},
125+
],
126+
},
127+
{
128+
code: `
129+
/* html */\`<html>
130+
<body>
131+
<div id = "\${foo}"></div>
132+
<a id = "\${foo}"></div>
133+
</body>
134+
</html>\`
106135
`,
107136

108137
errors: [

packages/eslint-plugin/tests/rules/no-trailing-spaces.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ templateRuleTester.run("[template] no-tailing-spaces", rule, {
9696
{
9797
code: `
9898
html\`<div>
99+
</div>\``,
100+
},
101+
{
102+
code: `
103+
/* html */\`<div>
99104
</div>\``,
100105
},
101106
],
@@ -175,5 +180,25 @@ text
175180
},
176181
],
177182
},
183+
{
184+
code: `
185+
/* html */\`<div id=\${foo}
186+
>
187+
text
188+
</div>\``,
189+
output: `
190+
/* html */\`<div id=\${foo}
191+
>
192+
text
193+
</div>\``,
194+
errors: [
195+
{
196+
messageId: "trailingSpace",
197+
line: 2,
198+
column: 26,
199+
endColumn: 29,
200+
},
201+
],
202+
},
178203
],
179204
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const { SourceCode } = require("eslint");
2+
const { parse } = require("espree");
3+
const {
4+
parseTemplateLiteral,
5+
} = require("../../../lib/rules/utils/template-literal");
6+
7+
/**
8+
* @param {string} code
9+
*/
10+
const parseCode = (code) => {
11+
const parsed = parse(code, {
12+
range: true,
13+
loc: true,
14+
ecmaVersion: "latest",
15+
});
16+
const sourceCode = new SourceCode({
17+
text: code,
18+
ast: {
19+
...parsed,
20+
tokens: [],
21+
comments: [],
22+
// @ts-ignore
23+
loc: parsed.loc,
24+
// @ts-ignore
25+
range: parsed.range,
26+
},
27+
});
28+
return { parsed, sourceCode };
29+
};
30+
31+
describe("parseTemplateLiteral", () => {
32+
it("should cache result", () => {
33+
const { parsed, sourceCode } = parseCode(`html\`<div></div>\`;`);
34+
// @ts-ignore
35+
const node = parsed.body[0].expression.quasi;
36+
const ast = parseTemplateLiteral(node, sourceCode);
37+
const cachedAst = parseTemplateLiteral(node, sourceCode);
38+
expect(ast).toBe(cachedAst);
39+
});
40+
41+
it("should run visitor", () => {
42+
const { parsed, sourceCode } = parseCode(`html\`<div></div>\`;`);
43+
// @ts-ignore
44+
const node = parsed.body[0].expression.quasi;
45+
const visitor = {
46+
OpenTagStart: jest.fn(),
47+
};
48+
parseTemplateLiteral(node, sourceCode, visitor);
49+
expect(visitor.OpenTagStart).toHaveBeenCalledTimes(1);
50+
parseTemplateLiteral(node, sourceCode, visitor);
51+
expect(visitor.OpenTagStart).toHaveBeenCalledTimes(2);
52+
});
53+
});

0 commit comments

Comments
 (0)