|
17 | 17 | equal=require("fast-deep-equal"), |
18 | 18 | Traverser=require("../shared/traverser"), |
19 | 19 | { getRuleOptionsSchema}=require("../config/flat-config-helpers"), |
20 | | -{ Linter, SourceCodeFixer, interpolate}=require("../linter"), |
| 20 | +{ Linter, SourceCodeFixer}=require("../linter"), |
| 21 | +{ interpolate, getPlaceholderMatcher}=require("../linter/interpolate"), |
21 | 22 | stringify=require("json-stable-stringify-without-jsonify"); |
22 | 23 |
|
23 | 24 | const{ FlatConfigArray}=require("../config/flat-config-array"); |
@@ -304,6 +305,39 @@ function throwForbiddenMethodError(methodName, prototype) { |
304 | 305 | }; |
305 | 306 | } |
306 | 307 |
|
| 308 | +/** |
| 309 | + * Extracts names of {{ placeholders }} from the reported message. |
| 310 | + *@param {string} message Reported message |
| 311 | + *@returns {string[]} Array of placeholder names |
| 312 | + */ |
| 313 | +functiongetMessagePlaceholders(message){ |
| 314 | +constmatcher=getPlaceholderMatcher(); |
| 315 | + |
| 316 | +returnArray.from(message.matchAll(matcher),([,name])=>name.trim()); |
| 317 | +} |
| 318 | + |
| 319 | +/** |
| 320 | + * Returns the placeholders in the reported messages but |
| 321 | + * only includes the placeholders available in the raw message and not in the provided data. |
| 322 | + *@param {string} message The reported message |
| 323 | + *@param {string} raw The raw message specified in the rule meta.messages |
| 324 | + *@param {undefined|Record<unknown, unknown>} data The passed |
| 325 | + *@returns {string[]} Missing placeholder names |
| 326 | + */ |
| 327 | +functiongetUnsubstitutedMessagePlaceholders(message,raw,data={}){ |
| 328 | +constunsubstituted=getMessagePlaceholders(message); |
| 329 | + |
| 330 | +if(unsubstituted.length===0){ |
| 331 | +return[]; |
| 332 | +} |
| 333 | + |
| 334 | +// Remove false positives by only counting placeholders in the raw message, which were not provided in the data matcher or added with a data property |
| 335 | +constknown=getMessagePlaceholders(raw); |
| 336 | +constprovided=Object.keys(data); |
| 337 | + |
| 338 | +returnunsubstituted.filter(name=>known.includes(name)&&!provided.includes(name)); |
| 339 | +} |
| 340 | + |
307 | 341 | constmetaSchemaDescription=` |
308 | 342 | \t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation. |
309 | 343 | \t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule. |
@@ -997,6 +1031,18 @@ class RuleTester { |
997 | 1031 | error.messageId, |
998 | 1032 | `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.` |
999 | 1033 | ); |
| 1034 | + |
| 1035 | +constunsubstitutedPlaceholders=getUnsubstitutedMessagePlaceholders( |
| 1036 | +message.message, |
| 1037 | +rule.meta.messages[message.messageId], |
| 1038 | +error.data |
| 1039 | +); |
| 1040 | + |
| 1041 | +assert.ok( |
| 1042 | +unsubstitutedPlaceholders.length===0, |
| 1043 | +`The reported message has${unsubstitutedPlaceholders.length>1 ?`unsubstituted placeholders:${unsubstitutedPlaceholders.map(name=>`'${name}'`).join(", ")}` :`an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing${unsubstitutedPlaceholders.length>1 ?"values" :"value"} via the 'data' property in the context.report() call.` |
| 1044 | +); |
| 1045 | + |
1000 | 1046 | if(hasOwnProperty(error,"data")){ |
1001 | 1047 |
|
1002 | 1048 | /* |
@@ -1096,6 +1142,18 @@ class RuleTester { |
1096 | 1142 | expectedSuggestion.messageId, |
1097 | 1143 | `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` |
1098 | 1144 | ); |
| 1145 | + |
| 1146 | +constunsubstitutedPlaceholders=getUnsubstitutedMessagePlaceholders( |
| 1147 | +actualSuggestion.desc, |
| 1148 | +rule.meta.messages[expectedSuggestion.messageId], |
| 1149 | +expectedSuggestion.data |
| 1150 | +); |
| 1151 | + |
| 1152 | +assert.ok( |
| 1153 | +unsubstitutedPlaceholders.length===0, |
| 1154 | +`The message of the suggestion has${unsubstitutedPlaceholders.length>1 ?`unsubstituted placeholders:${unsubstitutedPlaceholders.map(name=>`'${name}'`).join(", ")}` :`an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing${unsubstitutedPlaceholders.length>1 ?"values" :"value"} via the 'data' property for the suggestion in the context.report() call.` |
| 1155 | +); |
| 1156 | + |
1099 | 1157 | if(hasOwnProperty(expectedSuggestion,"data")){ |
1100 | 1158 | constunformattedMetaMessage=rule.meta.messages[expectedSuggestion.messageId]; |
1101 | 1159 | constrehydratedDesc=interpolate(unformattedMetaMessage,expectedSuggestion.data); |
|