Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Emit deprecation warnings in RuleTester #17527

Merged
merged 3 commits into from Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
62 changes: 61 additions & 1 deletion lib/rule-tester/rule-tester.js
Expand Up @@ -164,6 +164,30 @@ const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters

const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);

const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
getSource: "getText",
getSourceLines: "getLines",
getAllComments: "getAllComments",
getNodeByRangeIndex: "getNodeByRangeIndex",

// getComments: "getComments", -- already handled by a separate error
getCommentsBefore: "getCommentsBefore",
getCommentsAfter: "getCommentsAfter",
getCommentsInside: "getCommentsInside",
getJSDocComment: "getJSDocComment",
getFirstToken: "getFirstToken",
getFirstTokens: "getFirstTokens",
getLastToken: "getLastToken",
getLastTokens: "getLastTokens",
getTokenAfter: "getTokenAfter",
getTokenBefore: "getTokenBefore",
getTokenByRangeStart: "getTokenByRangeStart",
getTokens: "getTokens",
getTokensAfter: "getTokensAfter",
getTokensBefore: "getTokensBefore",
getTokensBetween: "getTokensBetween"
};

/**
* Clones a given value deeply.
* Note: This ignores `parent` property.
Expand Down Expand Up @@ -335,6 +359,22 @@ function emitMissingSchemaWarning(ruleName) {
}
}

/**
* Emit a deprecation warning if a rule uses a deprecated `context` method.
* @param {string} ruleName Name of the rule.
* @param {string} methodName The name of the method on `context` that was used.
* @returns {void}
*/
function emitDeprecatedContextMethodWarning(ruleName, methodName) {
if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) {
emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true;
process.emitWarning(
`"${ruleName}" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${DEPRECATED_SOURCECODE_PASSTHROUGHS[methodName]}()\` instead.`,
"DeprecationWarning"
);
}
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -566,7 +606,27 @@ class RuleTester {
freezeDeeply(context.settings);
freezeDeeply(context.parserOptions);

return (typeof rule === "function" ? rule : rule.create)(context);
const newContext = Object.freeze(
Object.create(
context,
Object.fromEntries(Object.keys(DEPRECATED_SOURCECODE_PASSTHROUGHS).map(methodName => [
methodName,
{
value(...args) {

// emit deprecation warning
emitDeprecatedContextMethodWarning(ruleName, methodName);

// call the original method
return context[methodName].call(this, ...args);
},
enumerable: true
}
]))
)
);

return (typeof rule === "function" ? rule : rule.create)(newContext);
}
}));

Expand Down
66 changes: 66 additions & 0 deletions tests/lib/rule-tester/rule-tester.js
Expand Up @@ -2489,6 +2489,72 @@ describe("RuleTester", () => {

assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`");
});

Object.entries({
getSource: "getText",
getSourceLines: "getLines",
getAllComments: "getAllComments",
getNodeByRangeIndex: "getNodeByRangeIndex",
getCommentsBefore: "getCommentsBefore",
getCommentsAfter: "getCommentsAfter",
getCommentsInside: "getCommentsInside",
getJSDocComment: "getJSDocComment",
getFirstToken: "getFirstToken",
getFirstTokens: "getFirstTokens",
getLastToken: "getLastToken",
getLastTokens: "getLastTokens",
getTokenAfter: "getTokenAfter",
getTokenBefore: "getTokenBefore",
getTokenByRangeStart: "getTokenByRangeStart",
getTokens: "getTokens",
getTokensAfter: "getTokensAfter",
getTokensBefore: "getTokensBefore",
getTokensBetween: "getTokensBetween"
}).forEach(([methodName, replacementName]) => {


it(`should log a deprecation warning when calling \`context.${methodName}\``, () => {
const ruleToCheckDeprecation = {
meta: {
type: "problem",
schema: []
},
create(context) {
return {
Program(node) {

// special case
if (methodName === "getTokensBetween") {
context[methodName](node, node);
} else {
context[methodName](node);
}

context.report({ node, message: "bad" });
}
};
}
};

ruleTester.run("deprecated-method", ruleToCheckDeprecation, {
valid: [],
invalid: [
{ code: "var foo = bar;", options: [], errors: 1 }
]
});

assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once");
assert.deepStrictEqual(
processStub.getCall(0).args,
[
`"deprecated-method" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${replacementName}()\` instead.`,
"DeprecationWarning"
]
);
});

});

});

/**
Expand Down