From f1fd0df87be2d121f18762f09704762eb4ebd456 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 9 May 2024 14:55:45 +0800 Subject: [PATCH 1/4] Add utility `isTaggedTemplateLiteral`, support member expression paths in `template-indent` tags --- docs/rules/template-indent.md | 2 +- rules/ast/index.js | 1 + rules/ast/is-tagged-template-literal.js | 28 +++++++++++++++++++++++++ rules/escape-case.js | 16 +++++++------- rules/no-hex-escape.js | 16 +++++++------- rules/template-indent.js | 11 +++++----- test/template-indent.mjs | 20 ++++++++++++++++++ 7 files changed, 70 insertions(+), 24 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..93b764b3a2 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 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..0c04f91f2a --- /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 node is 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..883c901506 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,13 +47,8 @@ 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']) - ) { - return; + if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { + return } return getProblem({ diff --git a/rules/no-hex-escape.js b/rules/no-hex-escape.js index bb3db1fa9a..42fc5f68b9 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,13 +34,8 @@ const create = context => ({ } }, TemplateElement(node) { - const templateLiteral = node.parent; - if ( - templateLiteral.parent.type === 'TaggedTemplateExpression' - && templateLiteral.parent.quasi === templateLiteral - && isNodeMatches(templateLiteral.parent.tag, ['String.raw']) - ) { - return; + if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { + return } return checkEscape(context, node, node.value.raw); diff --git a/rules/template-indent.js b/rules/template-indent.js index f46ee5e92e..813387afc9 100644 --- a/rules/template-indent.js +++ b/rules/template-indent.js @@ -3,7 +3,11 @@ 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 MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent'; const messages = { @@ -114,10 +118,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; } diff --git a/test/template-indent.mjs b/test/template-indent.mjs index 55a377ab6a..86675afa8b 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'], From 991b6a7fbdea569e72c804b97872787ffe0d92d5 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 9 May 2024 15:02:38 +0800 Subject: [PATCH 2/4] Support paths in `functions` --- docs/rules/template-indent.md | 2 +- rules/template-indent.js | 4 ++-- test/template-indent.mjs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/rules/template-indent.md b/docs/rules/template-indent.md index 93b764b3a2..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 or member expression paths, 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/template-indent.js b/rules/template-indent.js index 813387afc9..16a772997c 100644 --- a/rules/template-indent.js +++ b/rules/template-indent.js @@ -8,6 +8,7 @@ const { isCallExpression, isTaggedTemplateLiteral, } = require('./ast/index.js'); +const {isNodeMatches} = require('./utils/index.js') const MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent'; const messages = { @@ -127,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 86675afa8b..eacc8b196e 100644 --- a/test/template-indent.mjs +++ b/test/template-indent.mjs @@ -432,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 @@ -448,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 From 6206c1025cd2d8a75be32c0009751ac3b6fb0ff5 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 9 May 2024 15:07:16 +0800 Subject: [PATCH 3/4] Linting --- rules/ast/is-tagged-template-literal.js | 4 ++-- rules/escape-case.js | 2 +- rules/no-hex-escape.js | 2 +- rules/template-indent.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rules/ast/is-tagged-template-literal.js b/rules/ast/is-tagged-template-literal.js index 0c04f91f2a..35449e4fb8 100644 --- a/rules/ast/is-tagged-template-literal.js +++ b/rules/ast/is-tagged-template-literal.js @@ -1,6 +1,6 @@ 'use strict'; -const {isNodeMatches} = require('../utils/is-node-matches.js') +const {isNodeMatches} = require('../utils/is-node-matches.js'); /** Check if node is tagged template literal. @@ -15,7 +15,7 @@ function isTaggedTemplateLiteral(node, tags) { || node.parent.type !== 'TaggedTemplateExpression' || node.parent.quasi !== node ) { - return false + return false; } if (tags) { diff --git a/rules/escape-case.js b/rules/escape-case.js index 883c901506..6c34aadae4 100644 --- a/rules/escape-case.js +++ b/rules/escape-case.js @@ -48,7 +48,7 @@ const create = context => { context.on('TemplateElement', node => { if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { - return + return; } return getProblem({ diff --git a/rules/no-hex-escape.js b/rules/no-hex-escape.js index 42fc5f68b9..f57b093b01 100644 --- a/rules/no-hex-escape.js +++ b/rules/no-hex-escape.js @@ -35,7 +35,7 @@ const create = context => ({ }, TemplateElement(node) { if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { - return + return; } return checkEscape(context, node, node.value.raw); diff --git a/rules/template-indent.js b/rules/template-indent.js index 16a772997c..e702007820 100644 --- a/rules/template-indent.js +++ b/rules/template-indent.js @@ -8,7 +8,7 @@ const { isCallExpression, isTaggedTemplateLiteral, } = require('./ast/index.js'); -const {isNodeMatches} = require('./utils/index.js') +const {isNodeMatches} = require('./utils/index.js'); const MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent'; const messages = { From 0af162b6e244b23cbbb5aeee8d83b481010e7b1b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 9 May 2024 15:57:06 +0700 Subject: [PATCH 4/4] Update is-tagged-template-literal.js --- rules/ast/is-tagged-template-literal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/ast/is-tagged-template-literal.js b/rules/ast/is-tagged-template-literal.js index 35449e4fb8..a21fd819bb 100644 --- a/rules/ast/is-tagged-template-literal.js +++ b/rules/ast/is-tagged-template-literal.js @@ -3,7 +3,7 @@ const {isNodeMatches} = require('../utils/is-node-matches.js'); /** -Check if node is tagged template literal. +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.