Repository: eslint/eslint
File: lib/rule-tester/rule-tester.js (JavaScript)

Code before:
    return function(...args) {

        const called = forbiddenMethodCalls.get(methodName);

        /* eslint-disable no-invalid-this -- needed to operate as a method. */
        if (!called.has(this)) {
            called.add(this);

            return original.apply(this, args);
        }
        /* eslint-enable no-invalid-this -- not needed past this point */

        throw new Error(
            `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
        );
    };
}

const messagePlaceholderRegex = /\{\{([^{}]+?)\}\}/gu;

/**
 * Extracts names of missing {{ placeholders }} from the reported message.
 * @param   {string} message Reported message
 * @returns {string[]} Array of missing placeholder names
 */
function getMissingMessagePlaceholders(message) {
    return Array.from(message.matchAll(messagePlaceholderRegex), ([, name]) => name.trim());
}

const metaSchemaDescription = `
\t- If the rule has options, set \`meta.schema\` to an array or non-empty object to enable options validation.
\t- If the rule doesn't have options, omit \`meta.schema\` to enforce that no options can be passed to the rule.
\t- You can also set \`meta.schema\` to \`false\` to opt-out of options validation (not recommended).

\thttps://eslint.org/docs/latest/extend/custom-rules#options-schemas
`;

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

// default separators for testing
const DESCRIBE = Symbol("describe");
const IT = Symbol("it");
const IT_ONLY = Symbol("itOnly");

/**
 * This is `it` default handler if `it` don't exist.
 * @this {Mocha}
 * @param {string} text The description of the test case.
 * @param {Function} method The logic of the test case.

Review comment (suggestion):
Does the concept of "missing" actually come into play in this function? Or is just looking for placeholders in the message? If it's just looking for placeholders, which happen to be the missing ones in the context where it's called, then the function could be more generic by removing "missing" from its name.

Code after:

    return function(...args) {

        const called = forbiddenMethodCalls.get(methodName);

        /* eslint-disable no-invalid-this -- needed to operate as a method. */
        if (!called.has(this)) {
            called.add(this);

            return original.apply(this, args);
        }
        /* eslint-enable no-invalid-this -- not needed past this point */

        throw new Error(
            `\`SourceCode#${methodName}()\` cannot be called inside a rule.`
        );
    };
}

/**
 * Extracts names of {{ placeholders }} from the reported message.
 * @param   {string} message Reported message
 * @returns {string[]} Array of placeholder names
 */
function getMessagePlaceholders(message) {
    const matcher = getPlaceholderMatcher();

    return Array.from(message.matchAll(matcher), ([, name]) => name.trim());
}

/**
 * Returns the placeholders in the reported messages but
 * only includes the placeholders available in the raw message and not in the provided data.
 * @param {string} message The reported message
 * @param {string} raw The raw message specified in the rule meta.messages
 * @param {undefined|Record<unknown, unknown>} data The passed
 * @returns {string[]} Missing placeholder names
 */
function getUnsubstitutedMessagePlaceholders(message, raw, data = {}) {
    const unsubstituted = getMessagePlaceholders(message);

    if (unsubstituted.length === 0) {
        return [];
    }

    // 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
    const known = getMessagePlaceholders(raw);
    const provided = Object.keys(data);

    return unsubstituted.filter(name => known.includes(name) && !provided.includes(name));
}