Skip to content

Commit

Permalink
Add cwd option (#5721)
Browse files Browse the repository at this point in the history
* feat: Add cwd option

Resolves #5720

- Adds `cwd` to `LinterOptions` to allow specifying the working
directory from which Stylelint should search for files
- Adds `cwd` to `LinterResult` to allow formatters to format results
using the same working directory used in the lint run

* refactor: set internal API cwd in createStylelint

* refactor: require cwd in augmentConfigExtended
  • Loading branch information
adalinesimonian committed Nov 21, 2021
1 parent 3ce14b9 commit 2015d0d
Show file tree
Hide file tree
Showing 33 changed files with 538 additions and 63 deletions.
10 changes: 9 additions & 1 deletion docs/user-guide/usage/node-api.md
Expand Up @@ -22,6 +22,10 @@ Stylelint does not bother looking for a `.stylelintrc` file if you use this opti

A string to lint.

### `cwd`

The directory from which Stylelint will look for files. Defaults to the current working directory returned by `process.cwd()`.

### `files`

A file glob, or array of [file globs](https://github.com/sindresorhus/globby).
Expand All @@ -34,14 +38,18 @@ Though both `files` and `code` are "optional", you _must_ have one and _cannot_

The options that are passed with `files`.

For example, you can set a specific `cwd` manually. Relative globs in `files` are considered relative to this path. And by default, `cwd` will be set by `process.cwd()`.
For example, you can set a specific `cwd` to use when globbing paths. Relative globs in `files` are considered relative to this path. By default, `globbyOptions.cwd` will be set by `cwd`.

For more detail usage, see [Globby Guide](https://github.com/sindresorhus/globby#options).

## The returned promise

`stylelint.lint()` returns a Promise that resolves with an object containing the following properties:

### `cwd`

The directory used as the working directory for the linting operation.

### `errored`

Boolean. If `true`, at least one rule with an "error"-level severity registered a problem.
Expand Down
14 changes: 14 additions & 0 deletions lib/__tests__/extends.test.js
Expand Up @@ -88,3 +88,17 @@ describe('extending a config from process.cwd', () => {
expect(linted.results[0].warnings).toHaveLength(1);
});
});

describe('extending a config from options.cwd', () => {
it('works', async () => {
const linted = await standalone({
code: 'a { b: "c" }',
config: {
extends: ['./fixtures/config-string-quotes-single'],
},
cwd: __dirname,
});

expect(linted.results[0].warnings).toHaveLength(1);
});
});
85 changes: 85 additions & 0 deletions lib/__tests__/ignore.test.js
Expand Up @@ -71,6 +71,35 @@ test('same as above with no configBasedir, ignore-files path relative to process
process.chdir(actualCwd);
});

test('same as above with no configBasedir, ignore-files path relative to options.cwd', async () => {
const { results } = await standalone({
files: [fixtures('empty-block.css'), fixtures('invalid-hex.css')],
config: {
ignoreFiles: 'fixtures/invalid-hex.css',
extends: [fixtures('config-block-no-empty.json'), fixtures('config-color-no-invalid-hex')],
},
cwd: __dirname,
});

// two files found
expect(results).toHaveLength(2);

// empty-block.css found
expect(results[0].source).toContain('empty-block.css');

// empty-block.css linted
expect(results[0].warnings).toHaveLength(1);

// invalid-hex.css found
expect(results[1].source).toContain('invalid-hex.css');

// invalid-hex.css not linted
expect(results[1].warnings).toHaveLength(0);

// invalid-hex.css marked as ignored
expect(results[1].ignored).toBeTruthy();
});

test('absolute ignoreFiles glob path', async () => {
const { results } = await standalone({
files: [fixtures('empty-block.css'), fixtures('invalid-hex.css')],
Expand Down Expand Up @@ -153,6 +182,26 @@ test('specified `ignorePath` file ignoring one file', async () => {
process.chdir(actualCwd);
});

test('specified `ignorePath` file ignoring one file using options.cwd', async () => {
const files = [fixtures('empty-block.css')];
const noFilesErrorMessage = new NoFilesFoundError(files);

process.chdir(__dirname);

await expect(
standalone({
files,
config: {
rules: {
'block-no-empty': true,
},
},
ignorePath: fixtures('ignore.txt'),
cwd: __dirname,
}),
).rejects.toThrow(noFilesErrorMessage); // no files read
});

test('specified `ignorePattern` file ignoring one file', async () => {
const files = [fixtures('empty-block.css')];
const noFilesErrorMessage = new NoFilesFoundError(files);
Expand All @@ -175,6 +224,24 @@ test('specified `ignorePattern` file ignoring one file', async () => {
process.chdir(actualCwd);
});

test('specified `ignorePattern` file ignoring one file using options.cwd', async () => {
const files = [fixtures('empty-block.css')];
const noFilesErrorMessage = new NoFilesFoundError(files);

await expect(
standalone({
files,
config: {
rules: {
'block-no-empty': true,
},
},
ignorePattern: 'fixtures/empty-block.css',
cwd: __dirname,
}),
).rejects.toThrow(noFilesErrorMessage); // no files read
});

test('specified `ignorePattern` file ignoring two files', async () => {
const files = [fixtures('empty-block.css'), fixtures('no-syntax-error.css')];
const noFilesErrorMessage = new NoFilesFoundError(files);
Expand All @@ -197,6 +264,24 @@ test('specified `ignorePattern` file ignoring two files', async () => {
process.chdir(actualCwd);
});

test('specified `ignorePattern` file ignoring two files using options.cwd', async () => {
const files = [fixtures('empty-block.css'), fixtures('no-syntax-error.css')];
const noFilesErrorMessage = new NoFilesFoundError(files);

await expect(
standalone({
files,
config: {
rules: {
'block-no-empty': true,
},
},
ignorePattern: ['fixtures/empty-block.css', 'fixtures/no-syntax-error.css'],
cwd: __dirname,
}),
).rejects.toThrow(noFilesErrorMessage); // no files read
});

test('using ignoreFiles with input files that would cause a postcss syntax error', async () => {
const { results } = await standalone({
files: [fixtures('standaloneNoParsing', '*')],
Expand Down
25 changes: 25 additions & 0 deletions lib/__tests__/plugins.test.js
Expand Up @@ -269,3 +269,28 @@ describe('loading a plugin from process.cwd', () => {
expect(result.warnings()[0].rule).toBe('plugin/warn-about-foo');
});
});

describe('loading a plugin from options.cwd', () => {
let result;

const config = {
plugins: ['./fixtures/plugin-warn-about-foo'],
rules: {
'plugin/warn-about-foo': 'always',
},
};

beforeEach(async () => {
result = await postcss()
.use(stylelint({ cwd: __dirname, config }))
.process('.foo {}', { from: undefined });
});

it('error is caught', () => {
expect(result.warnings()).toHaveLength(1);
});

it('error is correct', () => {
expect(result.warnings()[0].rule).toBe('plugin/warn-about-foo');
});
});
35 changes: 35 additions & 0 deletions lib/__tests__/postcssPlugin.test.js
Expand Up @@ -135,3 +135,38 @@ describe('stylelintignore', () => {
).resolves.toHaveProperty('stylelint.ignored', true);
});
});

describe('stylelintignore with options.cwd', () => {
it('postcssPlugin with .stylelintignore and file is ignored', () => {
const options = {
config: {
rules: {
'block-no-empty': true,
},
},
cwd: __dirname,
};

return expect(
postcss([postcssPlugin(options)]).process('a {}', {
from: path.join(__dirname, 'postcssstylelintignore.css'),
}),
).resolves.toHaveProperty('stylelint.ignored', true);
});

it('postcssPlugin with ignorePath and file is ignored', () => {
const options = {
config: {
rules: {
'block-no-empty': true,
},
},
cwd: __dirname,
ignorePath: path.join(__dirname, './stylelintignore-test/.postcssPluginignore'),
};

return expect(
postcss([postcssPlugin(options)]).process('a {}', { from: path.join(__dirname, 'foo.css') }),
).resolves.toHaveProperty('stylelint.ignored', true);
});
});
38 changes: 38 additions & 0 deletions lib/__tests__/processors.test.js
Expand Up @@ -164,6 +164,44 @@ describe('loading processors (and extend) from process.cwd', () => {
});
});

describe('loading processors (and extend) from options.cwd', () => {
let results;

beforeEach(() => {
const code =
'one\ntwo\n```start\na {}\nb { color: pink }\n```end\nthree???startc {}???end' +
'\n\n???start```start\na {}\nb { color: pink }\n```end???end';

return standalone({
code,
config: {
extends: './__tests__/fixtures/config-block-no-empty',
processors: [
'./__tests__/fixtures/processor-triple-question-marks',
['./__tests__/fixtures/processor-fenced-blocks', { specialMessage: 'options worked' }],
],
},
cwd: path.join(__dirname, '..'),
}).then((data) => (results = data.results));
});

it('number of results', () => {
expect(results).toHaveLength(1);
});

it('number of warnings', () => {
expect(results[0].warnings).toHaveLength(1);
});

it('special message', () => {
expect(results[0].specialMessage).toBe('options worked');
});

it('tripleQuestionMarkBlocksFound', () => {
expect(results[0].tripleQuestionMarkBlocksFound).toBe(true);
});
});

describe('processor gets to modify result on CssSyntaxError', () => {
let results;

Expand Down
23 changes: 23 additions & 0 deletions lib/__tests__/standalone-globs.test.js
Expand Up @@ -189,6 +189,29 @@ describe('standalone globbing', () => {
);
});

it('setting "cwd" in options', async () => {
const cssGlob = `*.+(s|c)ss`;

const { results } = await standalone({
files: cssGlob,
config: {
rules: {
'block-no-empty': true,
},
},
cwd: `${fixturesPath}/got[braces] and (spaces)/`,
});

expect(results).toHaveLength(1);
expect(results[0].errored).toBe(true);
expect(results[0].warnings[0]).toEqual(
expect.objectContaining({
rule: 'block-no-empty',
severity: 'error',
}),
);
});

/* eslint-disable jest/no-commented-out-tests -- Failing case for reference. Documents behavior that doesn't work. */

// Note: This fails because there's no way to tell which parts of the glob are literal characters, and which are special globbing characters.
Expand Down
69 changes: 69 additions & 0 deletions lib/__tests__/standalone.test.js
Expand Up @@ -97,6 +97,30 @@ describe('standalone with files and globbyOptions', () => {
});
});

describe('standalone with files and cwd', () => {
let output;
let results;

beforeEach(() => {
return standalone({
files: 'empty-block.css',
cwd: fixturesPath,
// Path to config file
configFile: path.join(__dirname, 'fixtures/config-block-no-empty.json'),
}).then((data) => {
output = data.output;
results = data.results;
});
});

it('triggers warning', () => {
expect(output).toContain('block-no-empty');
expect(results).toHaveLength(1);
expect(results[0].warnings).toHaveLength(1);
expect(results[0].warnings[0].rule).toBe('block-no-empty');
});
});

it('standalone with input css', () => {
return standalone({
code: 'a {}',
Expand Down Expand Up @@ -305,6 +329,29 @@ describe('standalone with config locatable from process.cwd not file', () => {
});
});

describe('standalone with config locatable from options.cwd not file', () => {
let results;

beforeEach(() => {
return standalone({
cwd: path.join(__dirname, './fixtures/getConfigForFile/a/b'),
files: [replaceBackslashes(path.join(__dirname, './fixtures/empty-block.css'))],
}).then((data) => (results = data.results));
});

it('two warning', () => {
expect(results[0].warnings).toHaveLength(2);
});

it("'block-no-empty' correct warning", () => {
expect(results[0].warnings.find((warn) => warn.rule === 'block-no-empty')).toBeTruthy();
});

it("'plugin/warn-about-foo' correct warning", () => {
expect(results[0].warnings.find((warn) => warn.rule === 'plugin/warn-about-foo')).toBeTruthy();
});
});

describe('nonexistent codeFilename with loaded config', () => {
let actualCwd;

Expand Down Expand Up @@ -336,6 +383,28 @@ describe('nonexistent codeFilename with loaded config', () => {
});
});

describe('nonexistent codeFilename with loaded config and options.cwd', () => {
it('does not cause error', () => {
return expect(() =>
standalone({
code: 'a {}',
codeFilename: 'does-not-exist.css',
cwd: path.join(__dirname, './fixtures/getConfigForFile/a/b'),
}),
).not.toThrow();
});

it('does load config from options.cwd', () => {
return standalone({
code: 'a {}',
codeFilename: 'does-not-exist.css',
cwd: path.join(__dirname, './fixtures/getConfigForFile/a/b'),
}).then((linted) => {
expect(linted.results[0].warnings).toHaveLength(1);
});
});
});

describe('existing codeFilename for nested config detection', () => {
let actualCwd;

Expand Down

0 comments on commit 2015d0d

Please sign in to comment.