diff --git a/lib/cli-engine/formatter-metadata.js b/lib/cli-engine/formatter-metadata.js new file mode 100644 index 00000000000..a16ccffe2f3 --- /dev/null +++ b/lib/cli-engine/formatter-metadata.js @@ -0,0 +1,103 @@ +/** + * @fileoverview `FormatterMetadata` class. + * + * `FormatterMetadata` class is the type for the second parameter of formatters. + * + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const builtInRules = require("../rules"); +const { emitDeprecationWarning } = require("../shared/deprecation-warnings"); +const { getCLIEngineInternalSlots } = require("./cli-engine"); + +/** @typedef {import("../shared/types").RuleMeta} RuleMeta */ +/** @typedef {InstanceType} CLIEngine */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * @typedef {Object} FormatterMetadataInternalSlots + * @property {CLIEngine} engine The CLIEngine instance for this formatting. + */ + +/** @type {WeakMap} */ +const internalSlots = new WeakMap(); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +/** + * The class for the second parameter of formatters. + */ +class FormatterMetadata { + + /** + * Initialize this instance. + * @param {CLIEngine} engine The CLIEngine instance. + */ + constructor(engine) { + internalSlots.set(this, { engine }); + + /* + * `rulesMeta` must be an instance property for backward compatibility. + * Otherwise, `JSON.stringify` ignores `rulesMeta`. + */ + Object.defineProperty( + this, + "rulesMeta", + Object.getOwnPropertyDescriptor(FormatterMetadata.prototype, "rulesMeta") + ); + } + + /** + * Get a rule meta. + * @param {string} ruleId The rule ID to get. + * @param {string} filePath The path to a target file to determine configuration. + * @returns {RuleMeta | undefined} The metadata of the rule. + */ + getRuleMeta(ruleId, filePath) { + const { engine } = internalSlots.get(this); + + // To avoid using `getRulesForFile(filePath)` because it merges all rules into a map. + const { configArrayFactory } = getCLIEngineInternalSlots(engine); + const configArray = + configArrayFactory.getConfigArrayForFile( + filePath, + { ignoreNotFoundError: true } + ); + const rule = + configArray.pluginRules.get(ruleId) || + builtInRules.get(ruleId); + + return rule && rule.meta; + } + + /** + * Get the metadata of all rules. + * @returns {Record} The metadata of rules. + * @deprecated + */ + get rulesMeta() { + emitDeprecationWarning("rulesMeta", "ESLINT_LEGACY_RULES_META"); + + const { engine } = internalSlots.get(this); + const rulesMeta = {}; + + for (const [ruleId, rule] of engine.getRules()) { + rulesMeta[ruleId] = rule.meta; + } + + return rulesMeta; + } +} +Object.defineProperty(FormatterMetadata.prototype, "rulesMeta", { enumerable: true }); + +module.exports = { FormatterMetadata }; diff --git a/lib/cli-engine/index.js b/lib/cli-engine/index.js index 52e45a6d791..c768e5c371b 100644 --- a/lib/cli-engine/index.js +++ b/lib/cli-engine/index.js @@ -1,7 +1,9 @@ "use strict"; const { CLIEngine } = require("./cli-engine"); +const { FormatterMetadata } = require("./formatter-metadata"); module.exports = { - CLIEngine + CLIEngine, + FormatterMetadata }; diff --git a/lib/cli.js b/lib/cli.js index 815ce68c22f..f34d98536fa 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -17,7 +17,7 @@ const fs = require("fs"), path = require("path"), - { CLIEngine } = require("./cli-engine"), + { CLIEngine, FormatterMetadata } = require("./cli-engine"), options = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"); @@ -83,7 +83,6 @@ function translateOptions(cliOptions) { */ function printResults(engine, results, format, outputFile) { let formatter; - let rulesMeta; try { formatter = engine.getFormatter(format); @@ -92,17 +91,7 @@ function printResults(engine, results, format, outputFile) { return false; } - const output = formatter(results, { - get rulesMeta() { - if (!rulesMeta) { - rulesMeta = {}; - for (const [ruleId, rule] of engine.getRules()) { - rulesMeta[ruleId] = rule.meta; - } - } - return rulesMeta; - } - }); + const output = formatter(results, new FormatterMetadata(engine)); if (output) { if (outputFile) { diff --git a/lib/shared/deprecation-warnings.js b/lib/shared/deprecation-warnings.js index 4b6b29a8b2e..77821fee46a 100644 --- a/lib/shared/deprecation-warnings.js +++ b/lib/shared/deprecation-warnings.js @@ -31,7 +31,13 @@ const deprecationWarningMessages = { "ESLint may use plugins that have the same name but different " + "implementations in each target file. This method will be confused in " + "such a case. " + - "Please use 'CLIEngine::getRulesForFile(filePath)' method instead." + "Please use 'CLIEngine::getRulesForFile(filePath)' method instead.", + ESLINT_LEGACY_RULES_META: + "'metadata.rulesMeta' property in formatters has been deprecated. " + + "ESLint may use plugins that have the same name but different " + + "implementations in each target file. This method will be confused in " + + "such a case. " + + "Please use 'metadata.getRuleMeta(ruleId, filePath)' method instead." }; /** @@ -53,10 +59,19 @@ const emitDeprecationWarning = lodash.memoize((source, errorCode) => { ); }, (...args) => JSON.stringify(args)); +/** + * Reset warning emission counts. + * @returns {void} + */ +function resetDeprecationWarning() { + emitDeprecationWarning.cache.clear(); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ module.exports = { - emitDeprecationWarning + emitDeprecationWarning, + resetDeprecationWarning }; diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index 6acfcbae089..eed332d4295 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -18,6 +18,7 @@ const assert = require("chai").assert, os = require("os"), hash = require("../../../lib/cli-engine/hash"), { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory"), + { resetDeprecationWarning } = require("../../../lib/shared/deprecation-warnings"), { unIndent } = require("../_utils"), { defineCLIEngineWithInMemoryFileSystem } = require("./_utils"); @@ -4553,6 +4554,7 @@ describe("CLIEngine", () => { describe("getRules()", () => { it("should emit deprecation warning on called.", async() => { + resetDeprecationWarning(); const warningListener = sinon.spy(); process.on("warning", warningListener); diff --git a/tests/lib/cli.js b/tests/lib/cli.js index 6af3b5c838e..9ac73826039 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -15,7 +15,8 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - CLIEngine = require("../../lib/cli-engine/index").CLIEngine, + { CLIEngine, FormatterMetadata } = require("../../lib/cli-engine"), + { resetDeprecationWarning } = require("../../lib/shared/deprecation-warnings"), path = require("path"), sinon = require("sinon"), fs = require("fs"), @@ -59,7 +60,7 @@ describe("cli", () => { sinon.stub(fakeCLIEngine.prototype, "getFormatter").returns(sinon.spy()); const localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -231,6 +232,28 @@ describe("cli", () => { }); describe("when given a valid built-in formatter name that uses rules meta.", () => { + it("should emit deprecation warning on 'rulesMeta' is used.", async() => { + resetDeprecationWarning(); + const warningListener = sinon.spy(); + + process.on("warning", warningListener); + try { + const filePath = getFixturePath("passing.js"); + + cli.execute(`-f json-with-metadata ${filePath} --no-eslintrc`); + + // Wait for event emission. + await new Promise(resolve => setTimeout(resolve, 0)); + + // deprecated `rulesMeta` calls deprecated `getRules()` as well. + assert.strictEqual(warningListener.callCount, 2); + assert.strictEqual(warningListener.args[0][0].code, "ESLINT_LEGACY_RULES_META"); + assert.strictEqual(warningListener.args[1][0].code, "ESLINT_LEGACY_GET_RULES"); + } finally { + process.removeListener("warning", warningListener); + } + }); + it("should execute without any errors", () => { const filePath = getFixturePath("passing.js"); const exit = cli.execute(`-f json-with-metadata ${filePath} --no-eslintrc`); @@ -811,7 +834,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.stub(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -834,7 +857,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.stub(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -869,7 +892,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().once(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -907,7 +930,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().withExactArgs(report); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -945,7 +968,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().withExactArgs(report); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -961,7 +984,7 @@ describe("cli", () => { const fakeCLIEngine = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -995,7 +1018,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -1026,7 +1049,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.stub(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -1062,7 +1085,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -1100,7 +1123,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -1137,7 +1160,7 @@ describe("cli", () => { fakeCLIEngine.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log }); @@ -1152,7 +1175,7 @@ describe("cli", () => { const fakeCLIEngine = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./cli-engine/index": { CLIEngine: fakeCLIEngine }, + "./cli-engine/index": { CLIEngine: fakeCLIEngine, FormatterMetadata }, "./shared/logging": log });