diff --git a/packages/eslint-plugin/lib/rules/indent/indent.js b/packages/eslint-plugin/lib/rules/indent/indent.js index 4a611b9e..8175c1d8 100644 --- a/packages/eslint-plugin/lib/rules/indent/indent.js +++ b/packages/eslint-plugin/lib/rules/indent/indent.js @@ -20,7 +20,8 @@ * @property {Record} [Option2.tagChildrenIndent] */ -const { parse } = require("@html-eslint/template-parser"); +const { getCachedParseResult } = require("../utils/template-cache"); +const { traverse } = require("@html-eslint/template-parser/lib/traverser"); const { NODE_TYPES } = require("@html-eslint/parser"); const { RULE_CATEGORY } = require("../../constants"); const { @@ -386,13 +387,18 @@ module.exports = { TaggedTemplateExpression(node) { if (shouldCheckTaggedTemplateExpression(node, context)) { const base = getTemplateLiteralBaseIndentLevel(node.quasi); - parse(node.quasi, getSourceCode(context), createIndentVisitor(base)); + const { ast } = getCachedParseResult( + node.quasi, + getSourceCode(context) + ); + traverse(ast, createIndentVisitor(base), null); } }, TemplateLiteral(node) { if (shouldCheckTemplateLiteral(node, context)) { const base = getTemplateLiteralBaseIndentLevel(node); - parse(node, getSourceCode(context), createIndentVisitor(base)); + const { ast } = getCachedParseResult(node, getSourceCode(context)); + traverse(ast, createIndentVisitor(base), null); } }, }; diff --git a/packages/eslint-plugin/lib/rules/no-duplicate-id.js b/packages/eslint-plugin/lib/rules/no-duplicate-id.js index c702adc7..f7cebc03 100644 --- a/packages/eslint-plugin/lib/rules/no-duplicate-id.js +++ b/packages/eslint-plugin/lib/rules/no-duplicate-id.js @@ -3,7 +3,8 @@ * @import {RuleModule} from "../types"; */ -const { parse } = require("@html-eslint/template-parser"); +const { getCachedParseResult } = require("./utils/template-cache"); +const { traverse } = require("@html-eslint/template-parser/lib/traverser"); const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); const { @@ -90,18 +91,31 @@ module.exports = { TaggedTemplateExpression(node) { const idAttrsMap = new Map(); if (shouldCheckTaggedTemplateExpression(node, context)) { - parse(node.quasi, getSourceCode(context), { - Tag: createTagVisitor(idAttrsMap), - }); + const { ast } = getCachedParseResult( + node.quasi, + getSourceCode(context) + ); + traverse( + ast, + { + Tag: createTagVisitor(idAttrsMap), + }, + null + ); } report(idAttrsMap); }, TemplateLiteral(node) { const idAttrsMap = new Map(); if (shouldCheckTemplateLiteral(node, context)) { - parse(node, getSourceCode(context), { - Tag: createTagVisitor(idAttrsMap), - }); + const { ast } = getCachedParseResult(node, getSourceCode(context)); + traverse( + ast, + { + Tag: createTagVisitor(idAttrsMap), + }, + null + ); } report(idAttrsMap); }, diff --git a/packages/eslint-plugin/lib/rules/no-duplicate-in-head.js b/packages/eslint-plugin/lib/rules/no-duplicate-in-head.js index 576f6ab9..3bb6cce2 100644 --- a/packages/eslint-plugin/lib/rules/no-duplicate-in-head.js +++ b/packages/eslint-plugin/lib/rules/no-duplicate-in-head.js @@ -3,7 +3,8 @@ * @import {RuleModule} from "../types"; */ -const { parse } = require("@html-eslint/template-parser"); +const { getCachedParseResult } = require("./utils/template-cache"); +const { traverse } = require("@html-eslint/template-parser/lib/traverser"); const { RULE_CATEGORY } = require("../constants"); const { findAttr } = require("./utils/node"); const { @@ -168,7 +169,11 @@ module.exports = { if (shouldCheckTaggedTemplateExpression(node, context)) { const visitor = createTagVisitor(tagsMap, headCountRef); - parse(node.quasi, getSourceCode(context), visitor); + const { ast } = getCachedParseResult( + node.quasi, + getSourceCode(context) + ); + traverse(ast, visitor, null); report(tagsMap); } }, @@ -179,7 +184,8 @@ module.exports = { if (shouldCheckTemplateLiteral(node, context)) { const visitor = createTagVisitor(tagsMap, headCountRef); - parse(node, getSourceCode(context), visitor); + const { ast } = getCachedParseResult(node, getSourceCode(context)); + traverse(ast, visitor, null); report(tagsMap); } }, diff --git a/packages/eslint-plugin/lib/rules/no-multiple-empty-lines.js b/packages/eslint-plugin/lib/rules/no-multiple-empty-lines.js index 4decfb37..75db3d8d 100644 --- a/packages/eslint-plugin/lib/rules/no-multiple-empty-lines.js +++ b/packages/eslint-plugin/lib/rules/no-multiple-empty-lines.js @@ -6,7 +6,7 @@ * @property {number} Option.max */ -const { parse } = require("@html-eslint/template-parser"); +const { getCachedParseResult } = require("./utils/template-cache"); const { RULE_CATEGORY } = require("../constants"); const { shouldCheckTaggedTemplateExpression, @@ -117,10 +117,9 @@ module.exports = { }, TaggedTemplateExpression(node) { if (shouldCheckTaggedTemplateExpression(node, context)) { - const { html, tokens } = parse( + const { html, tokens } = getCachedParseResult( node.quasi, - getSourceCode(context), - {} + getSourceCode(context) ); const lines = codeToLines(html); check( @@ -133,7 +132,10 @@ module.exports = { }, TemplateLiteral(node) { if (shouldCheckTemplateLiteral(node, context)) { - const { html, tokens } = parse(node, getSourceCode(context), {}); + const { html, tokens } = getCachedParseResult( + node, + getSourceCode(context) + ); const lines = codeToLines(html); check( lines, diff --git a/packages/eslint-plugin/lib/rules/no-trailing-spaces.js b/packages/eslint-plugin/lib/rules/no-trailing-spaces.js index c6fcb34c..20f5d298 100644 --- a/packages/eslint-plugin/lib/rules/no-trailing-spaces.js +++ b/packages/eslint-plugin/lib/rules/no-trailing-spaces.js @@ -3,7 +3,7 @@ * @import {CommentContent, Text} from "@html-eslint/types"; */ -const { parse } = require("@html-eslint/template-parser"); +const { getCachedParseResult } = require("./utils/template-cache"); const { RULE_CATEGORY } = require("../constants"); const { getTemplateTokens, @@ -107,10 +107,9 @@ module.exports = { }, TaggedTemplateExpression(node) { if (shouldCheckTaggedTemplateExpression(node, context)) { - const { html, tokens } = parse( + const { html, tokens } = getCachedParseResult( node.quasi, - getSourceCode(context), - {} + getSourceCode(context) ); const lines = codeToLines(html); check( @@ -127,7 +126,10 @@ module.exports = { }, TemplateLiteral(node) { if (shouldCheckTemplateLiteral(node, context)) { - const { html, tokens } = parse(node, getSourceCode(context), {}); + const { html, tokens } = getCachedParseResult( + node, + getSourceCode(context) + ); const lines = codeToLines(html); check( html, diff --git a/packages/eslint-plugin/lib/rules/utils/template-cache.js b/packages/eslint-plugin/lib/rules/utils/template-cache.js new file mode 100644 index 00000000..d1e96979 --- /dev/null +++ b/packages/eslint-plugin/lib/rules/utils/template-cache.js @@ -0,0 +1,36 @@ +/** + * @import {TemplateLiteral} from "@html-eslint/types"; + * @import {DocumentNode, AnyToken} from "es-html-parser"; + * @import {SourceCode} from "eslint"; + */ + +const { parse } = require("@html-eslint/template-parser"); + +/** + * Cache for parsed template literals to avoid re-parsing the same template multiple times. + * Uses WeakMap for automatic garbage collection when nodes are no longer referenced. + * @type {WeakMap} + */ +const templateCache = new WeakMap(); + +/** + * Get or create cached parse result for a template literal. + * @param {TemplateLiteral} node + * @param {SourceCode} sourceCode + * @returns {{ast: DocumentNode, html: string, tokens: AnyToken[]}} + */ +function getCachedParseResult(node, sourceCode) { + // Check if we already have a cached result for this node + const cachedResult = templateCache.get(node); + if (cachedResult) { + return cachedResult; + } + + // Parse and cache the result + const result = parse(node, sourceCode, {}); + templateCache.set(node, result); + + return result; +} + +module.exports = { getCachedParseResult }; diff --git a/packages/eslint-plugin/lib/rules/utils/visitors.js b/packages/eslint-plugin/lib/rules/utils/visitors.js index 013c778b..f3f6197b 100644 --- a/packages/eslint-plugin/lib/rules/utils/visitors.js +++ b/packages/eslint-plugin/lib/rules/utils/visitors.js @@ -6,7 +6,8 @@ const { shouldCheckTaggedTemplateExpression, shouldCheckTemplateLiteral, } = require("./settings"); -const { parse } = require("@html-eslint/template-parser"); +const { getCachedParseResult } = require("./template-cache"); +const { traverse } = require("@html-eslint/template-parser/lib/traverser"); const { getSourceCode } = require("./source-code"); /** @@ -18,12 +19,17 @@ function createTemplateVisitors(context, visitors) { return { TaggedTemplateExpression(node) { if (shouldCheckTaggedTemplateExpression(node, context)) { - parse(node.quasi, getSourceCode(context), visitors); + const { ast } = getCachedParseResult( + node.quasi, + getSourceCode(context) + ); + traverse(ast, visitors, null); } }, TemplateLiteral(node) { if (shouldCheckTemplateLiteral(node, context)) { - parse(node, getSourceCode(context), visitors); + const { ast } = getCachedParseResult(node, getSourceCode(context)); + traverse(ast, visitors, null); } }, };