From bb66a3d91af426dac9a7ffdbe47bdbbc0ffd4dd7 Mon Sep 17 00:00:00 2001 From: Nitin Kumar Date: Fri, 4 Jun 2021 21:18:02 +0530 Subject: [PATCH] New: add `getPhysicalFilename()` method to rule context (fixes #11989) (#14616) * New: add `getPhysicalFilename()` method to the rule context object * Docs: update * Chore: add test * Chore: update more instances * Chore: apply suggestions * Chore: apply suggestions * Chore: fix typo Co-authored-by: Milos Djermanovic Co-authored-by: Milos Djermanovic --- docs/developer-guide/working-with-rules.md | 1 + lib/linter/linter.js | 12 +++-- tests/lib/linter/linter.js | 60 ++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md index 59e275de527..9dadae00e50 100644 --- a/docs/developer-guide/working-with-rules.md +++ b/docs/developer-guide/working-with-rules.md @@ -139,6 +139,7 @@ Additionally, the `context` object has the following methods: * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. * Otherwise, if the node does not declare any variables, an empty array is returned. * `getFilename()` - returns the filename associated with the source. +* `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. * `getScope()` - returns the [scope](./scope-manager-interface.md#scope-interface) of the currently-traversed node. This information can be used to track references to variables. * `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. * `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. diff --git a/lib/linter/linter.js b/lib/linter/linter.js index bdc6c1b1d01..44e88f845fa 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -828,9 +828,10 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze( * @param {string} filename The reported filename of the code * @param {boolean} disableFixes If true, it doesn't make `fix` properties. * @param {string | undefined} cwd cwd of the cli + * @param {string} physicalFilename The full path of the file on disk without any code block information * @returns {Problem[]} An array of reported problems */ -function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd) { +function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd, physicalFilename) { const emitter = createEmitter(); const nodeQueue = []; let currentNode = sourceCode.ast; @@ -859,6 +860,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager), getCwd: () => cwd, getFilename: () => filename, + getPhysicalFilename: () => physicalFilename || filename, getScope: () => getScope(sourceCode.scopeManager, currentNode), getSourceCode: () => sourceCode, markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name), @@ -1181,7 +1183,8 @@ class Linter { settings, options.filename, options.disableFixes, - slots.cwd + slots.cwd, + providedOptions.physicalFilename ); } catch (err) { err.message += `\nOccurred while linting ${options.filename}`; @@ -1284,6 +1287,7 @@ class Linter { _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) { const filename = options.filename || ""; const filenameToExpose = normalizeFilename(filename); + const physicalFilename = options.physicalFilename || filenameToExpose; const text = ensureText(textOrSourceCode); const preprocess = options.preprocess || (rawText => [rawText]); @@ -1316,7 +1320,7 @@ class Linter { return this._verifyWithConfigArray( blockText, configForRecursive, - { ...options, filename: blockName } + { ...options, filename: blockName, physicalFilename } ); } @@ -1324,7 +1328,7 @@ class Linter { return this._verifyWithoutProcessors( blockText, config, - { ...options, filename: blockName } + { ...options, filename: blockName, physicalFilename } ); }); diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 976bd765755..bcf3edfb90c 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -1559,6 +1559,22 @@ describe("Linter", () => { assert.strictEqual(messages[0].message, filename); }); + it("has access to the physicalFilename", () => { + linter.defineRule(code, context => ({ + Literal(node) { + context.report(node, context.getPhysicalFilename()); + } + })); + + const config = { rules: {} }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config, filename); + + assert.strictEqual(messages[0].message, filename); + }); + it("defaults filename to ''", () => { linter.defineRule(code, context => ({ Literal(node) { @@ -3408,6 +3424,41 @@ var a = "test2"; }); }); + describe("physicalFilenames", () => { + it("should be same as `filename` passed on options object, if no processors are used", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual(context.getPhysicalFilename(), "foo.js"); + return {}; + }); + + linter.defineRule("checker", physicalFilenameChecker); + linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); + assert(physicalFilenameChecker.calledOnce); + }); + + it("should default physicalFilename to when options object doesn't have filename", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual(context.getPhysicalFilename(), ""); + return {}; + }); + + linter.defineRule("checker", physicalFilenameChecker); + linter.verify("foo;", { rules: { checker: "error" } }, {}); + assert(physicalFilenameChecker.calledOnce); + }); + + it("should default physicalFilename to when only two arguments are passed", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual(context.getPhysicalFilename(), ""); + return {}; + }); + + linter.defineRule("checker", physicalFilenameChecker); + linter.verify("foo;", { rules: { checker: "error" } }); + assert(physicalFilenameChecker.calledOnce); + }); + }); + it("should report warnings in order by line and column when called", () => { const code = "foo()\n alert('test')"; @@ -4783,14 +4834,17 @@ var a = "test2"; describe("processors", () => { let receivedFilenames = []; + let receivedPhysicalFilenames = []; beforeEach(() => { receivedFilenames = []; + receivedPhysicalFilenames = []; // A rule that always reports the AST with a message equal to the source text linter.defineRule("report-original-text", context => ({ Program(ast) { receivedFilenames.push(context.getFilename()); + receivedPhysicalFilenames.push(context.getPhysicalFilename()); context.report({ node: ast, message: context.getSourceCode().text }); } })); @@ -4845,10 +4899,16 @@ var a = "test2"; assert.strictEqual(problems.length, 3); assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); + + // filename assert.strictEqual(receivedFilenames.length, 3); assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0])); assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1])); assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2])); + + // physical filename + assert.strictEqual(receivedPhysicalFilenames.length, 3); + assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true); }); it("should receive text even if a SourceCode object was given.", () => {