Skip to content

Commit

Permalink
New: add getPhysicalFilename() method to rule context (fixes #11989) (
Browse files Browse the repository at this point in the history
#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 <milos.djermanovic@gmail.com>

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
snitin315 and mdjermanovic committed Jun 4, 2021
1 parent 2e43dac commit bb66a3d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/developer-guide/working-with-rules.md
Expand Up @@ -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 `<text>` 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`.
Expand Down
12 changes: 8 additions & 4 deletions lib/linter/linter.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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}`;
Expand Down Expand Up @@ -1284,6 +1287,7 @@ class Linter {
_verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
const filename = options.filename || "<input>";
const filenameToExpose = normalizeFilename(filename);
const physicalFilename = options.physicalFilename || filenameToExpose;
const text = ensureText(textOrSourceCode);
const preprocess = options.preprocess || (rawText => [rawText]);

Expand Down Expand Up @@ -1316,15 +1320,15 @@ class Linter {
return this._verifyWithConfigArray(
blockText,
configForRecursive,
{ ...options, filename: blockName }
{ ...options, filename: blockName, physicalFilename }
);
}

// Does lint.
return this._verifyWithoutProcessors(
blockText,
config,
{ ...options, filename: blockName }
{ ...options, filename: blockName, physicalFilename }
);
});

Expand Down
60 changes: 60 additions & 0 deletions tests/lib/linter/linter.js
Expand Up @@ -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 '<input>'", () => {
linter.defineRule(code, context => ({
Literal(node) {
Expand Down Expand Up @@ -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 <input> when options object doesn't have filename", () => {
const physicalFilenameChecker = sinon.spy(context => {
assert.strictEqual(context.getPhysicalFilename(), "<input>");
return {};
});

linter.defineRule("checker", physicalFilenameChecker);
linter.verify("foo;", { rules: { checker: "error" } }, {});
assert(physicalFilenameChecker.calledOnce);
});

it("should default physicalFilename to <input> when only two arguments are passed", () => {
const physicalFilenameChecker = sinon.spy(context => {
assert.strictEqual(context.getPhysicalFilename(), "<input>");
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')";
Expand Down Expand Up @@ -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 });
}
}));
Expand Down Expand Up @@ -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.", () => {
Expand Down

0 comments on commit bb66a3d

Please sign in to comment.