Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: pass cwd from cli engine #12389

Merged
merged 2 commits into from Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/developer-guide/nodejs-api.md
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions docs/developer-guide/working-with-rules.md
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion lib/cli-engine/cli-engine.js
Expand Up @@ -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()];
Expand Down
33 changes: 30 additions & 3 deletions lib/linter/linter.js
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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<Linter, LinterInternalSlots>}
Expand All @@ -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]]),
Expand Down Expand Up @@ -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}`;
Expand Down
52 changes: 52 additions & 0 deletions tests/lib/linter/linter.js
Expand Up @@ -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(
Expand Down