From e501ab9b9f1955822c45dced841849dc214b6604 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 20 Dec 2023 18:19:51 -0500 Subject: [PATCH] feat!: Remove CodePath#currentSegments (#17756) fixes #17457 --- docs/src/extend/code-path-analysis.md | 3 +- lib/linter/code-path-analysis/code-path.js | 14 --- lib/rule-tester/flat-rule-tester.js | 31 ++---- lib/rule-tester/rule-tester.js | 42 ++++--- .../code-path-analysis/code-path-analyzer.js | 31 ------ tests/lib/rule-tester/flat-rule-tester.js | 39 ------- tests/lib/rule-tester/rule-tester.js | 104 ++++++++++++++++-- 7 files changed, 132 insertions(+), 132 deletions(-) diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index 879119574907..36d827bee774 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -37,7 +37,6 @@ This has references of both the initial segment and the final segments of a code * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. * `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. -* `currentSegments` (`CodePathSegment[]`) - **Deprecated.** Segments of the current traversal position. * `upper` (`CodePath|null`) - The code path of the upper function/global scope. * `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. @@ -635,7 +634,7 @@ last(); ``` If there is not `catch` block, `finally` block has two current segments. -At this time, `CodePath.currentSegments.length` is `2`. +At this time when running the previous example to find unreachable nodes, `currentSegments.length` is `2`. One is the normal path, and another is the leaving path (`throw` or `return`). :::img-container diff --git a/lib/linter/code-path-analysis/code-path.js b/lib/linter/code-path-analysis/code-path.js index 3bf570d754bf..90cafef68f0b 100644 --- a/lib/linter/code-path-analysis/code-path.js +++ b/lib/linter/code-path-analysis/code-path.js @@ -125,20 +125,6 @@ class CodePath { return this.internal.thrownForkContext; } - /** - * Tracks the traversal of the code path through each segment. This array - * starts empty and segments are added or removed as the code path is - * traversed. This array always ends up empty at the end of a code path - * traversal. The `CodePathState` uses this to track its progress through - * the code path. - * This is a passthrough to the underlying `CodePathState`. - * @type {CodePathSegment[]} - * @deprecated - */ - get currentSegments() { - return this.internal.currentSegments; - } - /** * Traverses all segments in this code path. * diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index 0a127c749a00..5e08eab93fdc 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -16,8 +16,7 @@ const equal = require("fast-deep-equal"), Traverser = require("../shared/traverser"), { getRuleOptionsSchema } = require("../config/flat-config-helpers"), - { Linter, SourceCodeFixer, interpolate } = require("../linter"), - CodePath = require("../linter/code-path-analysis/code-path"); + { Linter, SourceCodeFixer, interpolate } = require("../linter"); const { FlatConfigArray } = require("../config/flat-config-array"); const { defaultConfig } = require("../config/default-config"); @@ -275,18 +274,14 @@ function wrapParser(parser) { } /** - * Emit a deprecation warning if rule uses CodePath#currentSegments. - * @param {string} ruleName Name of the rule. + * Function to replace `SourceCode.prototype.getComments`. * @returns {void} + * @throws {Error} Deprecation message. */ -function emitCodePathCurrentSegmentsWarning(ruleName) { - if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { - emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; - process.emitWarning( - `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, - "DeprecationWarning" - ); - } +function getCommentsDeprecation() { + throw new Error( + "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead." + ); } /** @@ -716,17 +711,11 @@ class FlatRuleTester { } // Verify the code. - const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; - const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); + const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; let messages; try { - Object.defineProperty(CodePath.prototype, "currentSegments", { - get() { - emitCodePathCurrentSegmentsWarning(ruleName); - return originalCurrentSegments.get.call(this); - } - }); + SourceCode.prototype.getComments = getCommentsDeprecation; forbiddenMethods.forEach(methodName => { SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype); @@ -734,7 +723,7 @@ class FlatRuleTester { messages = linter.verify(code, configs, filename); } finally { - Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); + SourceCode.prototype.getComments = getComments; SourceCode.prototype.applyInlineConfig = applyInlineConfig; SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; SourceCode.prototype.finalize = finalize; diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index bd46fcc807a5..74ca7c11493d 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -48,8 +48,7 @@ const equal = require("fast-deep-equal"), Traverser = require("../../lib/shared/traverser"), { getRuleOptionsSchema, validate } = require("../shared/config-validator"), - { Linter, SourceCodeFixer, interpolate } = require("../linter"), - CodePath = require("../linter/code-path-analysis/code-path"); + { Linter, SourceCodeFixer, interpolate } = require("../linter"); const ajv = require("../shared/ajv")({ strictDefaults: true }); @@ -345,20 +344,37 @@ function emitMissingSchemaWarning(ruleName) { } /** - * Emit a deprecation warning if rule uses CodePath#currentSegments. + * 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 emitCodePathCurrentSegmentsWarning(ruleName) { - if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) { - emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = true; +function emitDeprecatedContextMethodWarning(ruleName, methodName) { + if (!emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`]) { + emitDeprecatedContextMethodWarning[`warned-${ruleName}-${methodName}`] = true; process.emitWarning( - `"${ruleName}" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples`, + `"${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" ); } } +/** + * Emit a deprecation warning if `context.parserServices` is used. + * @param {string} ruleName Name of the rule. + * @returns {void} + */ +function emitParserServicesWarning(ruleName) { + if (!emitParserServicesWarning[`warned-${ruleName}`]) { + emitParserServicesWarning[`warned-${ruleName}`] = true; + process.emitWarning( + `"${ruleName}" rule is using \`context.parserServices\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.parserServices\` instead.`, + "DeprecationWarning" + ); + } +} + + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -709,17 +725,11 @@ class RuleTester { validate(config, "rule-tester", id => (id === ruleName ? rule : null)); // Verify the code. - const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; - const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments"); + const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; let messages; try { - Object.defineProperty(CodePath.prototype, "currentSegments", { - get() { - emitCodePathCurrentSegmentsWarning(ruleName); - return originalCurrentSegments.get.call(this); - } - }); + SourceCode.prototype.getComments = getCommentsDeprecation; forbiddenMethods.forEach(methodName => { SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName); @@ -727,7 +737,7 @@ class RuleTester { messages = linter.verify(code, config, filename); } finally { - Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments); + SourceCode.prototype.getComments = getComments; SourceCode.prototype.applyInlineConfig = applyInlineConfig; SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; SourceCode.prototype.finalize = finalize; diff --git a/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/tests/lib/linter/code-path-analysis/code-path-analyzer.js index dbed9b46194f..e580c14d1344 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -137,37 +137,6 @@ describe("CodePathAnalyzer", () => { assert(actual[1].thrownSegments[0] instanceof CodePathSegment); }); - it("should have `currentSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].currentSegments)); - assert(Array.isArray(actual[1].currentSegments)); - assert(actual[0].currentSegments.length === 0); - assert(actual[1].currentSegments.length === 0); - - // there is the current segment in progress. - linter.defineRule("test", { - create() { - let codePath = null; - - return { - onCodePathStart(cp) { - codePath = cp; - }, - ReturnStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - }, - ThrowStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - } - }; - } - }); - linter.verify( - "function foo(a) { if (a) return 0; else throw new Error(); }", - { rules: { test: 2 } } - ); - }); }); describe("interface of code path segments", () => { diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index 5f47ce1fe4b4..d5d11e843339 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -2399,45 +2399,6 @@ describe("FlatRuleTester", () => { }); }); - describe("deprecations", () => { - let processStub; - - beforeEach(() => { - processStub = sinon.stub(process, "emitWarning"); - }); - - afterEach(() => { - processStub.restore(); - }); - - it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => { - - const useCurrentSegmentsRule = { - create: () => ({ - onCodePathStart(codePath) { - codePath.currentSegments.forEach(() => { }); - } - }) - }; - - ruleTester.run("use-current-segments", useCurrentSegmentsRule, { - valid: ["foo"], - invalid: [] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"use-current-segments\" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples", - "DeprecationWarning" - ] - ); - - }); - - }); - /** * Asserts that a particular value will be emitted from an EventEmitter. * @param {EventEmitter} emitter The emitter that should emit a value diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index 5af50dd7dea5..ba253da8f006 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -2490,31 +2490,117 @@ describe("RuleTester", () => { assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); }); - it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => { + it("should pass-through services from parseForESLint to the rule and log deprecation notice", () => { + const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser"); + const disallowHiRule = { + create: context => ({ + Literal(node) { + assert.strictEqual(context.parserServices, context.sourceCode.parserServices); + + const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" - const useCurrentSegmentsRule = { - create: () => ({ - onCodePathStart(codePath) { - codePath.currentSegments.forEach(() => {}); + if (node.value === disallowed) { + context.report({ node, message: `Don't use '${disallowed}'` }); + } } }) }; - ruleTester.run("use-current-segments", useCurrentSegmentsRule, { - valid: ["foo"], - invalid: [] + ruleTester.run("no-hi", disallowHiRule, { + valid: [ + { + code: "'Hello!'", + parser: enhancedParserPath + } + ], + invalid: [ + { + code: "'Hi!'", + parser: enhancedParserPath, + errors: [{ message: "Don't use 'Hi!'" }] + } + ] }); assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); assert.deepStrictEqual( processStub.getCall(0).args, [ - "\"use-current-segments\" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples", + "\"no-hi\" rule is using `context.parserServices`, which is deprecated and will be removed in ESLint v9. Please use `sourceCode.parserServices` instead.", "DeprecationWarning" ] ); + + }); + 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", + getScope: "getScope", + getAncestors: "getAncestors", + getDeclaredVariables: "getDeclaredVariables", + markVariableAsUsed: "markVariableAsUsed" + }).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" + ] + ); + }); + }); + }); /**