From 170e935a01676be718989cf39192cbcda7c66500 Mon Sep 17 00:00:00 2001 From: Rizwan Saleem Date: Fri, 31 Jul 2026 17:21:11 +0100 Subject: [PATCH] fix(no-raw-text): handle template literals without interpolations `report` read `node.expressions[0].name` unconditionally. A template literal with no interpolations has an empty `expressions` array, so this threw `TypeError: Cannot read properties of undefined (reading 'name')` and aborted the whole lint run instead of reporting a lint error. {`hello`} A second case: when the first expression is not an `Identifier` it has no `.name`, so `{`${this.props.text}`}` reported `Raw text (TemplateLiteral: undefined)`. Both now go through `templateLiteralValue`: - no interpolations -> the literal text, matching how `{'some text'}` is already reported - `Identifier` -> unchanged, so existing messages and tests still hold - anything else -> the expression's source text Uses `context.getSourceCode()` rather than `context.sourceCode`, since the peer range starts at eslint 3 and the modern accessor only exists from 8.40. Closes #341 --- lib/rules/no-raw-text.js | 21 ++++++++++++++++++++- tests/lib/rules/no-raw-text.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-raw-text.js b/lib/rules/no-raw-text.js index d2e07ef..8bece52 100644 --- a/lib/rules/no-raw-text.js +++ b/lib/rules/no-raw-text.js @@ -46,9 +46,28 @@ const hasAllowedParent = (parent, allowedElements) => { function create(context) { const options = context.options[0] || {}; + const templateLiteralValue = (node) => { + const firstExpression = node.expressions[0]; + + // A template literal with no interpolations has an empty `expressions`, so + // there is no expression to name. Describe the literal text instead, which + // matches how a plain string expression is reported. + if (!firstExpression) { + return node.quasis.map((quasi) => quasi.value.cooked).join('').trim(); + } + + if (firstExpression.type === 'Identifier') { + return `TemplateLiteral: ${firstExpression.name}`; + } + + // Member expressions, calls and the like have no `.name`, which previously + // rendered as `TemplateLiteral: undefined`. + return `TemplateLiteral: ${context.getSourceCode().getText(firstExpression)}`; + }; + const report = (node) => { const errorValue = node.type === 'TemplateLiteral' - ? `TemplateLiteral: ${node.expressions[0].name}` + ? templateLiteralValue(node) : node.value.trim(); const formattedErrorValue = errorValue.length > 0 diff --git a/tests/lib/rules/no-raw-text.js b/tests/lib/rules/no-raw-text.js index 311cfca..6f310a7 100644 --- a/tests/lib/rules/no-raw-text.js +++ b/tests/lib/rules/no-raw-text.js @@ -147,6 +147,34 @@ const tests = { message: 'Raw text (TemplateLiteral: text) cannot be used outside of a tag', }], }, + { + // A template literal with no interpolations used to throw: + // `Cannot read properties of undefined (reading 'name')` + code: ` + export default class MyComponent extends Component { + render() { + return ({\`some text\`}); + } + } + `, + errors: [{ + message: 'Raw text (some text) cannot be used outside of a tag', + }], + }, + { + // A non-Identifier interpolation has no `.name`, so this used to report + // `Raw text (TemplateLiteral: undefined)`. + code: ` + export default class MyComponent extends Component { + render() { + return ({\`\${this.props.text}\`}); + } + } + `, + errors: [{ + message: 'Raw text (TemplateLiteral: this.props.text) cannot be used outside of a tag', + }], + }, { code: ` export default class MyComponent extends Component {