From aabcf1dd6b9cd8c8e58e9e26e227a7c2668c47ee Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 9 May 2024 16:57:22 +0800 Subject: [PATCH] `template-indent`: Support member expression paths in `tags` and `functions` (#2346) --- docs/rules/template-indent.md | 2 +- rules/ast/index.js | 1 + rules/ast/is-tagged-template-literal.js | 28 +++++++++++++++++++++++ rules/escape-case.js | 14 +++++------- rules/no-hex-escape.js | 14 +++++------- rules/template-indent.js | 15 +++++++------ test/template-indent.mjs | 30 ++++++++++++++++++++----- 7 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 rules/ast/is-tagged-template-literal.js diff --git a/docs/rules/template-indent.md b/docs/rules/template-indent.md index b6fbb40be7..f082bceafa 100644 --- a/docs/rules/template-indent.md +++ b/docs/rules/template-indent.md @@ -69,7 +69,7 @@ Under the hood, [strip-indent](https://npmjs.com/package/strip-indent) is used t ## Options -The rule accepts lists of `tags`, `functions`, `selectors` and `comments` to match template literals. `tags` are tagged template literal identifiers, functions are names of utility functions like `stripIndent`, selectors can be any [ESLint selector](https://eslint.org/docs/developer-guide/selectors), and comments are `/* block-commented */` strings. +The rule accepts lists of `tags`, `functions`, `selectors` and `comments` to match template literals. `tags` are tagged template literal identifiers or member expression paths, functions are names or member expression paths of utility functions like `stripIndent`, selectors can be any [ESLint selector](https://eslint.org/docs/developer-guide/selectors), and comments are `/* block-commented */` strings. Default configuration: diff --git a/rules/ast/index.js b/rules/ast/index.js index b6fb7a7006..1799d515f3 100644 --- a/rules/ast/index.js +++ b/rules/ast/index.js @@ -33,6 +33,7 @@ module.exports = { isNewExpression, isReferenceIdentifier: require('./is-reference-identifier.js'), isStaticRequire: require('./is-static-require.js'), + isTaggedTemplateLiteral: require('./is-tagged-template-literal.js'), isUndefined: require('./is-undefined.js'), functionTypes: require('./function-types.js'), diff --git a/rules/ast/is-tagged-template-literal.js b/rules/ast/is-tagged-template-literal.js new file mode 100644 index 0000000000..a21fd819bb --- /dev/null +++ b/rules/ast/is-tagged-template-literal.js @@ -0,0 +1,28 @@ +'use strict'; + +const {isNodeMatches} = require('../utils/is-node-matches.js'); + +/** +Check if the given node is a tagged template literal. + +@param {Node} node - The AST node to check. +@param {string[]} tags - The object name or key paths. +@returns {boolean} +*/ +function isTaggedTemplateLiteral(node, tags) { + if ( + node.type !== 'TemplateLiteral' + || node.parent.type !== 'TaggedTemplateExpression' + || node.parent.quasi !== node + ) { + return false; + } + + if (tags) { + return isNodeMatches(node.parent.tag, tags); + } + + return true; +} + +module.exports = isTaggedTemplateLiteral; diff --git a/rules/escape-case.js b/rules/escape-case.js index b20c65d614..6c34aadae4 100644 --- a/rules/escape-case.js +++ b/rules/escape-case.js @@ -1,7 +1,10 @@ 'use strict'; const {replaceTemplateElement} = require('./fix/index.js'); -const {isRegexLiteral, isStringLiteral} = require('./ast/index.js'); -const {isNodeMatches} = require('./utils/index.js'); +const { + isRegexLiteral, + isStringLiteral, + isTaggedTemplateLiteral, +} = require('./ast/index.js'); const MESSAGE_ID = 'escape-case'; const messages = { @@ -44,12 +47,7 @@ const create = context => { }); context.on('TemplateElement', node => { - const templateLiteral = node.parent; - if ( - templateLiteral.parent.type === 'TaggedTemplateExpression' - && templateLiteral.parent.quasi === templateLiteral - && isNodeMatches(templateLiteral.parent.tag, ['String.raw']) - ) { + if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { return; } diff --git a/rules/no-hex-escape.js b/rules/no-hex-escape.js index bb3db1fa9a..f57b093b01 100644 --- a/rules/no-hex-escape.js +++ b/rules/no-hex-escape.js @@ -1,7 +1,10 @@ 'use strict'; const {replaceTemplateElement} = require('./fix/index.js'); -const {isStringLiteral, isRegexLiteral} = require('./ast/index.js'); -const {isNodeMatches} = require('./utils/index.js'); +const { + isStringLiteral, + isRegexLiteral, + isTaggedTemplateLiteral, +} = require('./ast/index.js'); const MESSAGE_ID = 'no-hex-escape'; const messages = { @@ -31,12 +34,7 @@ const create = context => ({ } }, TemplateElement(node) { - const templateLiteral = node.parent; - if ( - templateLiteral.parent.type === 'TaggedTemplateExpression' - && templateLiteral.parent.quasi === templateLiteral - && isNodeMatches(templateLiteral.parent.tag, ['String.raw']) - ) { + if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { return; } diff --git a/rules/template-indent.js b/rules/template-indent.js index f46ee5e92e..e702007820 100644 --- a/rules/template-indent.js +++ b/rules/template-indent.js @@ -3,7 +3,12 @@ const stripIndent = require('strip-indent'); const indentString = require('indent-string'); const esquery = require('esquery'); const {replaceTemplateElement} = require('./fix/index.js'); -const {isMethodCall, isCallExpression} = require('./ast/index.js'); +const { + isMethodCall, + isCallExpression, + isTaggedTemplateLiteral, +} = require('./ast/index.js'); +const {isNodeMatches} = require('./utils/index.js'); const MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent'; const messages = { @@ -114,10 +119,7 @@ const create = context => { if ( options.tags.length > 0 - && node.parent.type === 'TaggedTemplateExpression' - && node.parent.quasi === node - && node.parent.tag.type === 'Identifier' - && options.tags.includes(node.parent.tag.name) + && isTaggedTemplateLiteral(node, options.tags) ) { return true; } @@ -126,8 +128,7 @@ const create = context => { options.functions.length > 0 && node.parent.type === 'CallExpression' && node.parent.arguments.includes(node) - && node.parent.callee.type === 'Identifier' - && options.functions.includes(node.parent.callee.name) + && isNodeMatches(node.parent.callee, options.functions) ) { return true; } diff --git a/test/template-indent.mjs b/test/template-indent.mjs index 55a377ab6a..eacc8b196e 100644 --- a/test/template-indent.mjs +++ b/test/template-indent.mjs @@ -85,6 +85,26 @@ test({ ••••••••\` `), }, + { + options: [{ + tags: ['utils.dedent'], + }], + code: fixInput(` + foo = utils.dedent\` + ••••••••one + ••••••••two + ••••••••••three + ••••••••\` + `), + errors, + output: fixInput(` + foo = utils.dedent\` + ••one + ••two + ••••three + \` + `), + }, { options: [{ tags: ['customIndentableTag'], @@ -412,15 +432,15 @@ test({ }, { options: [{ - functions: ['customDedentFunction'], + functions: ['customDedentFunction1', 'utils.customDedentFunction2'], }], code: fixInput(` - foo = customDedentFunction(\` + foo = customDedentFunction1(\` ••••••••one ••••••••two ••••••••••three ••••••••\`) - foo = customDedentFunction('some-other-arg', \` + foo = utils.customDedentFunction2('some-other-arg', \` ••••••••one ••••••••two ••••••••••three @@ -428,12 +448,12 @@ test({ `), errors: [...errors, ...errors], output: fixInput(` - foo = customDedentFunction(\` + foo = customDedentFunction1(\` ••one ••two ••••three \`) - foo = customDedentFunction('some-other-arg', \` + foo = utils.customDedentFunction2('some-other-arg', \` ••one ••two ••••three