Skip to content

Commit

Permalink
Revert "feat!: Remove CodePath#currentSegments" (eslint#17890)
Browse files Browse the repository at this point in the history
Revert "feat!: Remove CodePath#currentSegments (eslint#17756)"

This reverts commit e501ab9.
  • Loading branch information
nzakas authored Dec 20, 2023
1 parent 8fe8c56 commit 29bc729
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 132 deletions.
3 changes: 2 additions & 1 deletion docs/src/extend/code-path-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ 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.

Expand Down Expand Up @@ -634,7 +635,7 @@ last();
```

If there is not `catch` block, `finally` block has two current segments.
At this time when running the previous example to find unreachable nodes, `currentSegments.length` is `2`.
At this time, `CodePath.currentSegments.length` is `2`.
One is the normal path, and another is the leaving path (`throw` or `return`).

:::img-container
Expand Down
14 changes: 14 additions & 0 deletions lib/linter/code-path-analysis/code-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ 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.
*
Expand Down
31 changes: 21 additions & 10 deletions lib/rule-tester/flat-rule-tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const
equal = require("fast-deep-equal"),
Traverser = require("../shared/traverser"),
{ getRuleOptionsSchema } = require("../config/flat-config-helpers"),
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
CodePath = require("../linter/code-path-analysis/code-path");

const { FlatConfigArray } = require("../config/flat-config-array");
const { defaultConfig } = require("../config/default-config");
Expand Down Expand Up @@ -274,14 +275,18 @@ function wrapParser(parser) {
}

/**
* Function to replace `SourceCode.prototype.getComments`.
* Emit a deprecation warning if rule uses CodePath#currentSegments.
* @param {string} ruleName Name of the rule.
* @returns {void}
* @throws {Error} Deprecation message.
*/
function getCommentsDeprecation() {
throw new Error(
"`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
);
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"
);
}
}

/**
Expand Down Expand Up @@ -711,19 +716,25 @@ class FlatRuleTester {
}

// Verify the code.
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
let messages;

try {
SourceCode.prototype.getComments = getCommentsDeprecation;
Object.defineProperty(CodePath.prototype, "currentSegments", {
get() {
emitCodePathCurrentSegmentsWarning(ruleName);
return originalCurrentSegments.get.call(this);
}
});

forbiddenMethods.forEach(methodName => {
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype);
});

messages = linter.verify(code, configs, filename);
} finally {
SourceCode.prototype.getComments = getComments;
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
SourceCode.prototype.finalize = finalize;
Expand Down
42 changes: 16 additions & 26 deletions lib/rule-tester/rule-tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ const
equal = require("fast-deep-equal"),
Traverser = require("../../lib/shared/traverser"),
{ getRuleOptionsSchema, validate } = require("../shared/config-validator"),
{ Linter, SourceCodeFixer, interpolate } = require("../linter");
{ Linter, SourceCodeFixer, interpolate } = require("../linter"),
CodePath = require("../linter/code-path-analysis/code-path");

const ajv = require("../shared/ajv")({ strictDefaults: true });

Expand Down Expand Up @@ -344,37 +345,20 @@ function emitMissingSchemaWarning(ruleName) {
}

/**
* Emit a deprecation warning if a rule uses a deprecated `context` method.
* Emit a deprecation warning if rule uses CodePath#currentSegments.
* @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;
function emitCodePathCurrentSegmentsWarning(ruleName) {
if (!emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`]) {
emitCodePathCurrentSegmentsWarning[`warned-${ruleName}`] = 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.`,
`"${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"
);
}
}

/**
* 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
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -725,19 +709,25 @@ class RuleTester {
validate(config, "rule-tester", id => (id === ruleName ? rule : null));

// Verify the code.
const { getComments, applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype;
const originalCurrentSegments = Object.getOwnPropertyDescriptor(CodePath.prototype, "currentSegments");
let messages;

try {
SourceCode.prototype.getComments = getCommentsDeprecation;
Object.defineProperty(CodePath.prototype, "currentSegments", {
get() {
emitCodePathCurrentSegmentsWarning(ruleName);
return originalCurrentSegments.get.call(this);
}
});

forbiddenMethods.forEach(methodName => {
SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName);
});

messages = linter.verify(code, config, filename);
} finally {
SourceCode.prototype.getComments = getComments;
Object.defineProperty(CodePath.prototype, "currentSegments", originalCurrentSegments);
SourceCode.prototype.applyInlineConfig = applyInlineConfig;
SourceCode.prototype.applyLanguageOptions = applyLanguageOptions;
SourceCode.prototype.finalize = finalize;
Expand Down
31 changes: 31 additions & 0 deletions tests/lib/linter/code-path-analysis/code-path-analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,37 @@ 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", () => {
Expand Down
39 changes: 39 additions & 0 deletions tests/lib/rule-tester/flat-rule-tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -2399,6 +2399,45 @@ 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
Expand Down
104 changes: 9 additions & 95 deletions tests/lib/rule-tester/rule-tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -2490,117 +2490,31 @@ describe("RuleTester", () => {
assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`");
});

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!"
it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => {

if (node.value === disallowed) {
context.report({ node, message: `Don't use '${disallowed}'` });
}
const useCurrentSegmentsRule = {
create: () => ({
onCodePathStart(codePath) {
codePath.currentSegments.forEach(() => {});
}
})
};

ruleTester.run("no-hi", disallowHiRule, {
valid: [
{
code: "'Hello!'",
parser: enhancedParserPath
}
],
invalid: [
{
code: "'Hi!'",
parser: enhancedParserPath,
errors: [{ message: "Don't use 'Hi!'" }]
}
]
ruleTester.run("use-current-segments", useCurrentSegmentsRule, {
valid: ["foo"],
invalid: []
});

assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once");
assert.deepStrictEqual(
processStub.getCall(0).args,
[
"\"no-hi\" rule is using `context.parserServices`, which is deprecated and will be removed in ESLint v9. Please use `sourceCode.parserServices` instead.",
"\"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"
]
);

});
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"
]
);
});

});


});

/**
Expand Down

0 comments on commit 29bc729

Please sign in to comment.