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

Add cwd option #5721

Merged
merged 3 commits into from Nov 21, 2021
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
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