From 874fe1642a10a0fb937ccccdd4d22343b84f80dc Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 16 Oct 2019 11:07:03 +1100 Subject: [PATCH] New: pass cwd from cli engine (#12389) * New: pass cwd from cli engine * fix linting error --- docs/developer-guide/nodejs-api.md | 13 +++++- docs/developer-guide/working-with-rules.md | 1 + lib/cli-engine/cli-engine.js | 2 +- lib/linter/linter.js | 33 ++++++++++++-- tests/lib/linter/linter.js | 52 ++++++++++++++++++++++ 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md index 464d6dbd19b..f5ec84616bf 100644 --- a/docs/developer-guide/nodejs-api.md +++ b/docs/developer-guide/nodejs-api.md @@ -80,13 +80,22 @@ const codeLines = SourceCode.splitLines(code); ## Linter -The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. You can retrieve instances of `Linter` like this: +The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. +The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: + +* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](./working-with-rules.md#The-Context-Object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. + +For example: ```js const Linter = require("eslint").Linter; -const linter = new Linter(); +const linter1 = new Linter({ cwd: 'path/to/project' }); +const linter2 = new Linter(); ``` +In this example, rules run on `linter1` will get `path/to/project` when calling `context.getCwd()`. +Those run on `linter2` will get `process.cwd()` if the global `process` object is defined or `undefined` otherwise (e.g. on the browser https://eslint.org/demo). + ### Linter#verify The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments: diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md index 35f224b3229..ef6cbd63468 100644 --- a/docs/developer-guide/working-with-rules.md +++ b/docs/developer-guide/working-with-rules.md @@ -126,6 +126,7 @@ The `context` object contains additional functionality that is helpful for rules Additionally, the `context` object has the following methods: * `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api.md#Linter). It is a path to a directory that should be considered as the current working directory. * `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface.md#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js index e768da837d0..17ed8d9f01b 100644 --- a/lib/cli-engine/cli-engine.js +++ b/lib/cli-engine/cli-engine.js @@ -567,7 +567,7 @@ class CLIEngine { }); const lintResultCache = options.cache ? new LintResultCache(cacheFilePath) : null; - const linter = new Linter(); + const linter = new Linter({ cwd: options.cwd }); /** @type {ConfigArray[]} */ const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()]; diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 5b9ce07a50d..8bd29b94795 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -810,9 +810,10 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze( * @param {Object} settings The settings that were enabled in the config * @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 * @returns {Problem[]} An array of reported problems */ -function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes) { +function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd) { const emitter = createEmitter(); const nodeQueue = []; let currentNode = sourceCode.ast; @@ -839,6 +840,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser { getAncestors: () => getAncestors(currentNode), getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager), + getCwd: () => cwd, getFilename: () => filename, getScope: () => getScope(sourceCode.scopeManager, currentNode), getSourceCode: () => sourceCode, @@ -985,6 +987,24 @@ function getRule(slots, ruleId) { ); } +/** + * Normalize the value of the cwd + * @param {string | undefined} cwd raw value of the cwd, path to a directory that should be considered as the current working directory, can be undefined. + * @returns {string | undefined} normalized cwd + */ +function normalizeCwd(cwd) { + if (cwd) { + return cwd; + } + if (typeof process === "object") { + return process.cwd(); + } + + // It's more explicit to assign the undefined + // eslint-disable-next-line no-undefined + return undefined; +} + /** * The map to store private data. * @type {WeakMap} @@ -1001,8 +1021,14 @@ const internalSlotsMap = new WeakMap(); */ class Linter { - constructor() { + /** + * Initialize the Linter. + * @param {Object} [config] the config object + * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. + */ + constructor({ cwd } = {}) { internalSlotsMap.set(this, { + cwd: normalizeCwd(cwd), lastConfigArray: null, lastSourceCode: null, parserMap: new Map([["espree", espree]]), @@ -1134,7 +1160,8 @@ class Linter { parserName, settings, options.filename, - options.disableFixes + options.disableFixes, + slots.cwd ); } catch (err) { err.message += `\nOccurred while linting ${options.filename}`; diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index e4e4e163e67..4d04eb34ddc 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -3195,6 +3195,58 @@ describe("Linter", () => { }); }); + describe("when receiving cwd in options during instantiation", () => { + const code = "a;\nb;"; + const config = { rules: { checker: "error" } }; + + it("should get cwd correctly in the context", () => { + const cwd = "cwd"; + const linterWithOption = new Linter({ cwd }); + let spy; + + linterWithOption.defineRule("checker", context => { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), cwd); + }); + return { Program: spy }; + }); + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if cwd is undefined", () => { + let spy; + const linterWithOption = new Linter({ }); + + linterWithOption.defineRule("checker", context => { + + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + }); + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if the option is undefined", () => { + let spy; + + linter.defineRule("checker", context => { + + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), process.cwd()); + }); + return { Program: spy }; + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + describe("reportUnusedDisable option", () => { it("reports problems for unused eslint-disable comments", () => { assert.deepStrictEqual(