From 63af8d7cd845a5c21f9e3b962c0ff4e036be7159 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Tue, 16 Nov 2021 14:05:37 -0800 Subject: [PATCH 1/3] 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 --- docs/user-guide/usage/node-api.md | 10 +- lib/__tests__/extends.test.js | 14 +++ lib/__tests__/ignore.test.js | 85 +++++++++++++ lib/__tests__/plugins.test.js | 25 ++++ lib/__tests__/postcssPlugin.test.js | 35 ++++++ lib/__tests__/processors.test.js | 38 ++++++ lib/__tests__/standalone-globs.test.js | 23 ++++ lib/__tests__/standalone.test.js | 69 ++++++++++ .../stylelintignore.test.js | 119 ++++++++++++++++++ lib/augmentConfig.js | 37 +++--- lib/createStylelint.js | 2 +- lib/formatters/stringFormatter.js | 18 ++- lib/formatters/verboseFormatter.js | 4 +- lib/getConfigForFile.js | 11 +- lib/isPathIgnored.js | 6 +- lib/lintSource.js | 4 +- lib/postcssPlugin.js | 6 +- lib/prepareReturnValue.js | 4 +- lib/printConfig.js | 6 +- lib/standalone.js | 39 +++--- lib/utils/FileCache.js | 8 +- lib/utils/getFileIgnorer.js | 2 +- lib/utils/getModulePath.js | 7 +- .../001/__snapshots__/fs.test.js.snap | 1 + .../001/__snapshots__/no-fs.test.js.snap | 1 + .../002/__snapshots__/fs.test.js.snap | 1 + .../002/__snapshots__/no-fs.test.js.snap | 1 + .../003/__snapshots__/fs.test.js.snap | 1 + .../003/__snapshots__/no-fs.test.js.snap | 1 + .../004/__snapshots__/fs.test.js.snap | 2 + .../004/__snapshots__/no-fs.test.js.snap | 2 + system-tests/systemTestUtils.js | 3 +- types/stylelint/index.d.ts | 10 ++ 33 files changed, 534 insertions(+), 61 deletions(-) diff --git a/docs/user-guide/usage/node-api.md b/docs/user-guide/usage/node-api.md index c5a34f96c3..d992885f13 100644 --- a/docs/user-guide/usage/node-api.md +++ b/docs/user-guide/usage/node-api.md @@ -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). @@ -34,7 +38,7 @@ 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). @@ -42,6 +46,10 @@ For more detail usage, see [Globby Guide](https://github.com/sindresorhus/globby `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. diff --git a/lib/__tests__/extends.test.js b/lib/__tests__/extends.test.js index fccf5dc31f..e56181c0d1 100644 --- a/lib/__tests__/extends.test.js +++ b/lib/__tests__/extends.test.js @@ -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); + }); +}); diff --git a/lib/__tests__/ignore.test.js b/lib/__tests__/ignore.test.js index 39a654df4f..876cc900b4 100644 --- a/lib/__tests__/ignore.test.js +++ b/lib/__tests__/ignore.test.js @@ -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')], @@ -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); @@ -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); @@ -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', '*')], diff --git a/lib/__tests__/plugins.test.js b/lib/__tests__/plugins.test.js index 81ecec5c28..e748dd023f 100644 --- a/lib/__tests__/plugins.test.js +++ b/lib/__tests__/plugins.test.js @@ -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'); + }); +}); diff --git a/lib/__tests__/postcssPlugin.test.js b/lib/__tests__/postcssPlugin.test.js index 964c3d2a78..bf83f62bf1 100644 --- a/lib/__tests__/postcssPlugin.test.js +++ b/lib/__tests__/postcssPlugin.test.js @@ -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); + }); +}); diff --git a/lib/__tests__/processors.test.js b/lib/__tests__/processors.test.js index bec4492d51..2a1456b8fb 100644 --- a/lib/__tests__/processors.test.js +++ b/lib/__tests__/processors.test.js @@ -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; diff --git a/lib/__tests__/standalone-globs.test.js b/lib/__tests__/standalone-globs.test.js index e602b75743..01ff6549f2 100644 --- a/lib/__tests__/standalone-globs.test.js +++ b/lib/__tests__/standalone-globs.test.js @@ -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. diff --git a/lib/__tests__/standalone.test.js b/lib/__tests__/standalone.test.js index 4d1932a655..44b08a0c08 100644 --- a/lib/__tests__/standalone.test.js +++ b/lib/__tests__/standalone.test.js @@ -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 {}', @@ -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; @@ -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; diff --git a/lib/__tests__/stylelintignore-test/stylelintignore.test.js b/lib/__tests__/stylelintignore-test/stylelintignore.test.js index 04919e5e47..b7babf9e42 100644 --- a/lib/__tests__/stylelintignore-test/stylelintignore.test.js +++ b/lib/__tests__/stylelintignore-test/stylelintignore.test.js @@ -62,6 +62,7 @@ describe('stylelintignore', () => { }, }).then((data) => { expect(data).toEqual({ + cwd: expect.any(String), errored: false, output: '[]', reportedDisables: [], @@ -97,6 +98,7 @@ describe('stylelintignore', () => { }, }).then((data) => { expect(data).toEqual({ + cwd: expect.any(String), errored: false, output: '[]', reportedDisables: [], @@ -112,6 +114,123 @@ describe('stylelintignore', () => { ignorePath: path.join(__dirname, '.stylelintignore2'), }).then((data) => { expect(data).toEqual({ + cwd: expect.any(String), + errored: false, + output: '[]', + reportedDisables: [], + results: [], + }); + }); + }); + }); +}); + +describe('stylelintignore with options.cwd', () => { + let results; + const fixturesPath = path.join(__dirname, './fixtures'); + + describe('standalone with .stylelintignore file ignoring one file', () => { + beforeEach(() => { + return standalone({ + files: [ + replaceBackslashes(`${fixturesPath}/empty-block.css`), + replaceBackslashes(`${fixturesPath}/invalid-hex.css`), + ], + config: { + extends: [ + `${fixturesPath}/config-block-no-empty`, + `${fixturesPath}/config-color-no-invalid-hex`, + ], + }, + cwd: __dirname, + }).then((data) => (results = data.results)); + }); + + it('one file read', () => { + expect(results).toHaveLength(1); + }); + + it('empty-block.css not read', () => { + expect(/empty-block\.css/.test(results[0].source)).toBe(false); + }); + + it('color-no-invalid-hex.css read', () => { + expect(/invalid-hex\.css/.test(results[0].source)).toBe(true); + }); + + it('color-no-invalid-hex.css linted', () => { + expect(results[0].warnings).toHaveLength(1); + }); + }); + + describe('standalone with .stylelintignore file ignoring codeFilename', () => { + it('ignored file is ignored', () => { + return standalone({ + code: '.bar {}', + codeFilename: `${fixturesPath}/empty-block.css`, + ignorePath: path.join(__dirname, '.stylelintignore'), + config: { + extends: `${fixturesPath}/config-block-no-empty`, + }, + cwd: __dirname, + }).then((data) => { + expect(data).toEqual({ + cwd: expect.any(String), + errored: false, + output: '[]', + reportedDisables: [], + results: [], + }); + }); + }); + + it('not ignored file is linted', () => { + return standalone({ + code: '.bar {}', + codeFilename: `${fixturesPath}/empty-block.css`, + ignorePath: path.join(__dirname, '.stylelintignore-empty'), + config: { + extends: `${fixturesPath}/config-block-no-empty`, + }, + cwd: __dirname, + }).then((data) => { + expect(data).toMatchObject({ + errored: true, + output: expect.stringContaining('Unexpected empty block (block-no-empty)'), + results: expect.any(Array), + }); + }); + }); + + it('ignored file with syntax error is ignored', () => { + return standalone({ + code: 'var a = {', + codeFilename: `test.js`, + ignorePath: path.join(__dirname, '.stylelintignore2'), + config: { + extends: `${fixturesPath}/config-block-no-empty`, + }, + cwd: __dirname, + }).then((data) => { + expect(data).toEqual({ + cwd: expect.any(String), + errored: false, + output: '[]', + reportedDisables: [], + results: [], + }); + }); + }); + + it('ignored file with syntax error is ignored and there is no config', () => { + return standalone({ + code: 'var a = {', + codeFilename: `test.js`, + ignorePath: path.join(__dirname, '.stylelintignore2'), + cwd: __dirname, + }).then((data) => { + expect(data).toEqual({ + cwd: expect.any(String), errored: false, output: '[]', reportedDisables: [], diff --git a/lib/augmentConfig.js b/lib/augmentConfig.js index 294dc1944b..2cfd8b3be0 100644 --- a/lib/augmentConfig.js +++ b/lib/augmentConfig.js @@ -57,29 +57,33 @@ async function augmentConfigBasic( filePath, ); - return absolutizePaths(augmentedConfig, configDir); + const cwd = stylelint._options.cwd || process.cwd(); + + return absolutizePaths(augmentedConfig, configDir, cwd); } /** * Extended configs need to be run through augmentConfigBasic * but do not need the full treatment. Things like pluginFunctions * will be resolved and added by the parent config. - * @param {StylelintCosmiconfigResult} [cosmiconfigResult] - * @returns {Promise} + * @param {string} [cwd] + * @returns {(cosmiconfigResult?: StylelintCosmiconfigResult) => Promise} */ -async function augmentConfigExtended(cosmiconfigResult) { - if (!cosmiconfigResult) { - return null; - } +function augmentConfigExtended(cwd = process.cwd()) { + return async (cosmiconfigResult) => { + if (!cosmiconfigResult) { + return null; + } - const configDir = path.dirname(cosmiconfigResult.filepath || ''); - const { config } = cosmiconfigResult; + const configDir = path.dirname(cosmiconfigResult.filepath || ''); + const { config } = cosmiconfigResult; - const augmentedConfig = absolutizePaths(config, configDir); + const augmentedConfig = absolutizePaths(config, configDir, cwd); - return { - config: augmentedConfig, - filepath: cosmiconfigResult.filepath, + return { + config: augmentedConfig, + filepath: cosmiconfigResult.filepath, + }; }; } @@ -133,9 +137,10 @@ async function augmentConfigFull(stylelint, filePath, cosmiconfigResult) { * (extends handled elsewhere) * @param {StylelintConfig} config * @param {string} configDir + * @param {string} cwd * @returns {StylelintConfig} */ -function absolutizePaths(config, configDir) { +function absolutizePaths(config, configDir, cwd) { if (config.ignoreFiles) { config.ignoreFiles = [config.ignoreFiles].flat().map((glob) => { if (path.isAbsolute(glob.replace(/^!/, ''))) return glob; @@ -145,7 +150,7 @@ function absolutizePaths(config, configDir) { } if (config.plugins) { - config.plugins = [config.plugins].flat().map((lookup) => getModulePath(configDir, lookup)); + config.plugins = [config.plugins].flat().map((lookup) => getModulePath(configDir, lookup, cwd)); } if (config.processors) { @@ -222,7 +227,7 @@ async function extendConfig(stylelint, config, configDir, rootConfigDir, filePat * @return {Promise} */ function loadExtendedConfig(stylelint, configDir, extendLookup) { - const extendPath = getModulePath(configDir, extendLookup); + const extendPath = getModulePath(configDir, extendLookup, stylelint._options.cwd); return stylelint._extendExplorer.load(extendPath); } diff --git a/lib/createStylelint.js b/lib/createStylelint.js index 8cef189234..bec7abc71c 100644 --- a/lib/createStylelint.js +++ b/lib/createStylelint.js @@ -27,7 +27,7 @@ function createStylelint(options = {}) { const stylelint = { _options: options }; stylelint._extendExplorer = cosmiconfig('', { - transform: augmentConfig.augmentConfigExtended, + transform: augmentConfig.augmentConfigExtended(options.cwd), stopDir: STOP_DIR, }); diff --git a/lib/formatters/stringFormatter.js b/lib/formatters/stringFormatter.js index ddf91d9d33..4d3d804ee3 100644 --- a/lib/formatters/stringFormatter.js +++ b/lib/formatters/stringFormatter.js @@ -79,12 +79,13 @@ function invalidOptionsFormatter(results) { /** * @param {string} fromValue + * @param {string} cwd * @return {string} */ -function logFrom(fromValue) { +function logFrom(fromValue, cwd) { if (fromValue.startsWith('<')) return fromValue; - return path.relative(process.cwd(), fromValue).split(path.sep).join('/'); + return path.relative(cwd, fromValue).split(path.sep).join('/'); } /** @@ -110,9 +111,10 @@ function getMessageWidth(columnWidths) { /** * @param {import('stylelint').Warning[]} messages * @param {string} source + * @param {string} cwd * @return {string} */ -function formatter(messages, source) { +function formatter(messages, source, cwd) { if (!messages.length) return ''; const orderedMessages = [...messages].sort((a, b) => { @@ -157,7 +159,7 @@ function formatter(messages, source) { let output = '\n'; if (source) { - output += `${underline(logFrom(source))}\n`; + output += `${underline(logFrom(source, cwd))}\n`; } /** @@ -231,7 +233,7 @@ function formatter(messages, source) { /** * @type {import('stylelint').Formatter} */ -module.exports = function (results) { +module.exports = function (results, returnValue) { let output = invalidOptionsFormatter(results); output += deprecationsFormatter(results); @@ -249,7 +251,11 @@ module.exports = function (results) { }); } - accum += formatter(result.warnings, result.source || ''); + accum += formatter( + result.warnings, + result.source || '', + (returnValue && returnValue.cwd) || process.cwd(), + ); return accum; }, output); diff --git a/lib/formatters/verboseFormatter.js b/lib/formatters/verboseFormatter.js index 3c9e89f504..d56af20cc1 100644 --- a/lib/formatters/verboseFormatter.js +++ b/lib/formatters/verboseFormatter.js @@ -9,8 +9,8 @@ const { underline, red, yellow, dim, green } = require('picocolors'); /** * @type {Formatter} */ -module.exports = function (results) { - let output = stringFormatter(results); +module.exports = function (results, returnValue) { + let output = stringFormatter(results, returnValue); if (output === '') { output = '\n'; diff --git a/lib/getConfigForFile.js b/lib/getConfigForFile.js index d602fbdd48..d8bd3938b6 100644 --- a/lib/getConfigForFile.js +++ b/lib/getConfigForFile.js @@ -18,8 +18,13 @@ const STOP_DIR = IS_TEST ? process.cwd() : undefined; * @param {string} [filePath] * @returns {Promise} */ -module.exports = async function getConfigForFile(stylelint, searchPath = process.cwd(), filePath) { +module.exports = async function getConfigForFile( + stylelint, + searchPath = stylelint._options.cwd || process.cwd(), + filePath, +) { const optionsConfig = stylelint._options.config; + const cwd = stylelint._options.cwd || process.cwd(); if (optionsConfig !== undefined) { const cached = stylelint._specifiedConfigCache.get(optionsConfig); @@ -34,7 +39,7 @@ module.exports = async function getConfigForFile(stylelint, searchPath = process config: optionsConfig, // Add the extra path part so that we can get the directory without being // confused - filepath: path.join(process.cwd(), 'argument-config'), + filepath: path.join(cwd, 'argument-config'), }); stylelint._specifiedConfigCache.set(optionsConfig, augmentedResult); @@ -52,7 +57,7 @@ module.exports = async function getConfigForFile(stylelint, searchPath = process : await configExplorer.search(searchPath); if (!config) { - config = await configExplorer.search(process.cwd()); + config = await configExplorer.search(cwd); } if (!config) { diff --git a/lib/isPathIgnored.js b/lib/isPathIgnored.js index b3c05d3e27..457d4d95af 100644 --- a/lib/isPathIgnored.js +++ b/lib/isPathIgnored.js @@ -19,7 +19,7 @@ module.exports = async function isPathIgnored(stylelint, filePath) { return false; } - const cwd = process.cwd(); + const cwd = stylelint._options.cwd || process.cwd(); const ignorer = getFileIgnorer(stylelint._options); const result = await stylelint.getConfigForFile(filePath, filePath); @@ -33,9 +33,7 @@ module.exports = async function isPathIgnored(stylelint, filePath) { normalizePath(s), ); - const absoluteFilePath = path.isAbsolute(filePath) - ? filePath - : path.resolve(process.cwd(), filePath); + const absoluteFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath); if (micromatch([absoluteFilePath], ignoreFiles).length) { return true; diff --git a/lib/lintSource.js b/lib/lintSource.js index 7682d52908..4ffa906163 100644 --- a/lib/lintSource.js +++ b/lib/lintSource.js @@ -49,12 +49,12 @@ module.exports = async function lintSource(stylelint, options = {}) { } const configSearchPath = stylelint._options.configFile || inputFilePath; + const cwd = stylelint._options.cwd || process.cwd(); const configForFile = await stylelint .getConfigForFile(configSearchPath, inputFilePath) .catch((err) => { - if (isCodeNotFile && isPathNotFoundError(err)) - return stylelint.getConfigForFile(process.cwd()); + if (isCodeNotFile && isPathNotFoundError(err)) return stylelint.getConfigForFile(cwd); throw err; }); diff --git a/lib/postcssPlugin.js b/lib/postcssPlugin.js index fed318110f..01ab5335dc 100644 --- a/lib/postcssPlugin.js +++ b/lib/postcssPlugin.js @@ -10,7 +10,9 @@ const path = require('path'); * @type {import('postcss').PluginCreator} * */ module.exports = (options = {}) => { - const tailoredOptions = isConfig(options) ? { config: options } : options; + const [cwd, tailoredOptions] = isConfig(options) + ? [process.cwd(), { config: options }] + : [options.cwd || process.cwd(), options]; const stylelint = createStylelint(tailoredOptions); return { @@ -19,7 +21,7 @@ module.exports = (options = {}) => { let filePath = root.source && root.source.input.file; if (filePath && !path.isAbsolute(filePath)) { - filePath = path.join(process.cwd(), filePath); + filePath = path.join(cwd, filePath); } return stylelint._lintSource({ diff --git a/lib/prepareReturnValue.js b/lib/prepareReturnValue.js index f97e311b48..c358dd3ae0 100644 --- a/lib/prepareReturnValue.js +++ b/lib/prepareReturnValue.js @@ -14,10 +14,11 @@ const reportDisables = require('./reportDisables'); * @param {StylelintResult[]} stylelintResults * @param {maxWarnings} maxWarnings * @param {Formatter} formatter + * @param {string} cwd * * @returns {LinterResult} */ -function prepareReturnValue(stylelintResults, maxWarnings, formatter) { +function prepareReturnValue(stylelintResults, maxWarnings, formatter, cwd) { reportDisables(stylelintResults); needlessDisables(stylelintResults); invalidScopeDisables(stylelintResults); @@ -32,6 +33,7 @@ function prepareReturnValue(stylelintResults, maxWarnings, formatter) { /** @type {LinterResult} */ const returnValue = { + cwd, errored, results: [], output: '', diff --git a/lib/printConfig.js b/lib/printConfig.js index ad25bdf9b6..286cf7f686 100644 --- a/lib/printConfig.js +++ b/lib/printConfig.js @@ -11,6 +11,7 @@ const path = require('path'); * @returns {Promise} */ module.exports = function printConfig({ + cwd = process.cwd(), code, config, configBasedir, @@ -36,11 +37,12 @@ module.exports = function printConfig({ config, configFile, configBasedir, + cwd, }); - const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); + const globCWD = (globbyOptions && globbyOptions.cwd) || cwd; const absoluteFilePath = !path.isAbsolute(filePath) - ? path.join(cwd, filePath) + ? path.join(globCWD, filePath) : path.normalize(filePath); const configSearchPath = stylelint._options.configFile || absoluteFilePath; diff --git a/lib/standalone.js b/lib/standalone.js index 10371313c4..d8552b93ca 100644 --- a/lib/standalone.js +++ b/lib/standalone.js @@ -44,6 +44,7 @@ async function standalone({ configBasedir, configFile, customSyntax, + cwd = process.cwd(), disableDefaultIgnores, files, fix, @@ -75,7 +76,7 @@ async function standalone({ // before any files are actually read const absoluteIgnoreFilePath = path.isAbsolute(ignorePath) ? ignorePath - : path.resolve(process.cwd(), ignorePath); + : path.resolve(cwd, ignorePath); let ignoreText = ''; try { @@ -101,6 +102,7 @@ async function standalone({ config, configFile, configBasedir, + cwd, ignoreDisables, ignorePath, reportNeedlessDisables, @@ -115,15 +117,15 @@ async function standalone({ if (!files) { const absoluteCodeFilename = codeFilename !== undefined && !path.isAbsolute(codeFilename) - ? path.join(process.cwd(), codeFilename) + ? path.join(cwd, codeFilename) : codeFilename; // if file is ignored, return nothing if ( absoluteCodeFilename && - !filterFilePaths(ignorer, [path.relative(process.cwd(), absoluteCodeFilename)]).length + !filterFilePaths(ignorer, [path.relative(cwd, absoluteCodeFilename)]).length ) { - return prepareReturnValue([], maxWarnings, formatterFunction); + return prepareReturnValue([], maxWarnings, formatterFunction, cwd); } let stylelintResult; @@ -140,7 +142,7 @@ async function standalone({ } const postcssResult = stylelintResult._postcssResult; - const returnValue = prepareReturnValue([stylelintResult], maxWarnings, formatterFunction); + const returnValue = prepareReturnValue([stylelintResult], maxWarnings, formatterFunction, cwd); if ( fix && @@ -160,8 +162,10 @@ async function standalone({ } let fileList = [files].flat().map((entry) => { - const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); - const absolutePath = !path.isAbsolute(entry) ? path.join(cwd, entry) : path.normalize(entry); + const globCWD = (globbyOptions && globbyOptions.cwd) || cwd; + const absolutePath = !path.isAbsolute(entry) + ? path.join(globCWD, entry) + : path.normalize(entry); if (fs.existsSync(absolutePath)) { // This path points to a file. Return an escaped path to avoid globbing @@ -179,29 +183,36 @@ async function standalone({ const stylelintVersion = pkg.version; const hashOfConfig = hash(`${stylelintVersion}_${JSON.stringify(config || {})}`); - fileCache = new FileCache(cacheLocation, hashOfConfig); + fileCache = new FileCache(cacheLocation, cwd, hashOfConfig); } else { // No need to calculate hash here, we just want to delete cache file. - fileCache = new FileCache(cacheLocation); + fileCache = new FileCache(cacheLocation, cwd); // Remove cache file if cache option is disabled fileCache.destroy(); } - let filePaths = await globby(fileList, globbyOptions); + const effectiveGlobbyOptions = { + cwd, + ...(globbyOptions || {}), + absolute: true, + }; + + const globCWD = effectiveGlobbyOptions.cwd; + + let filePaths = await globby(fileList, effectiveGlobbyOptions); // The ignorer filter needs to check paths relative to cwd filePaths = filterFilePaths( ignorer, - filePaths.map((p) => path.relative(process.cwd(), p)), + filePaths.map((p) => path.relative(globCWD, p)), ); let stylelintResults; if (filePaths.length) { - const cwd = (globbyOptions && globbyOptions.cwd) || process.cwd(); let absoluteFilePaths = filePaths.map((filePath) => { const absoluteFilepath = !path.isAbsolute(filePath) - ? path.join(cwd, filePath) + ? path.join(globCWD, filePath) : path.normalize(filePath); return absoluteFilepath; @@ -265,7 +276,7 @@ async function standalone({ fileCache.reconcile(); } - const result = prepareReturnValue(stylelintResults, maxWarnings, formatterFunction); + const result = prepareReturnValue(stylelintResults, maxWarnings, formatterFunction, cwd); debug(`Linting complete in ${Date.now() - startTime}ms`); diff --git a/lib/utils/FileCache.js b/lib/utils/FileCache.js index 0474a6ce81..698b3105f1 100644 --- a/lib/utils/FileCache.js +++ b/lib/utils/FileCache.js @@ -16,8 +16,12 @@ const DEFAULT_HASH = ''; * @constructor */ class FileCache { - constructor(cacheLocation = DEFAULT_CACHE_LOCATION, hashOfConfig = DEFAULT_HASH) { - const cacheFile = path.resolve(getCacheFile(cacheLocation, process.cwd())); + constructor( + cacheLocation = DEFAULT_CACHE_LOCATION, + cwd = process.cwd(), + hashOfConfig = DEFAULT_HASH, + ) { + const cacheFile = path.resolve(getCacheFile(cacheLocation, cwd)); debug(`Cache file is created at ${cacheFile}`); this._fileCache = fileEntryCache.create(cacheFile); diff --git a/lib/utils/getFileIgnorer.js b/lib/utils/getFileIgnorer.js index 3f9ecb557c..d7e248a08f 100644 --- a/lib/utils/getFileIgnorer.js +++ b/lib/utils/getFileIgnorer.js @@ -19,7 +19,7 @@ module.exports = function (options) { const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME; const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) ? ignoreFilePath - : path.resolve(process.cwd(), ignoreFilePath); + : path.resolve(options.cwd || process.cwd(), ignoreFilePath); let ignoreText = ''; try { diff --git a/lib/utils/getModulePath.js b/lib/utils/getModulePath.js index 68fa9b1ae7..8960279891 100644 --- a/lib/utils/getModulePath.js +++ b/lib/utils/getModulePath.js @@ -7,16 +7,17 @@ const resolveFrom = require('resolve-from'); /** * @param {string} basedir * @param {string} lookup + * @param {string} [cwd] * @return {string} */ -module.exports = function getModulePath(basedir, lookup) { +module.exports = function getModulePath(basedir, lookup, cwd = process.cwd()) { // 1. Try to resolve from the provided directory - // 2. Try to resolve from `process.cwd` + // 2. Try to resolve from `cwd` or `process.cwd()` // 3. Try to resolve from global `node_modules` directory let path = resolveFrom.silent(basedir, lookup); if (!path) { - path = resolveFrom.silent(process.cwd(), lookup); + path = resolveFrom.silent(cwd, lookup); } if (!path) { diff --git a/system-tests/001/__snapshots__/fs.test.js.snap b/system-tests/001/__snapshots__/fs.test.js.snap index dc44057660..361bc18a48 100644 --- a/system-tests/001/__snapshots__/fs.test.js.snap +++ b/system-tests/001/__snapshots__/fs.test.js.snap @@ -2,6 +2,7 @@ exports[`fs - valid sanitize.css and their config 1`] = ` Object { + "cwd": "", "errored": false, "output": Array [ Object { diff --git a/system-tests/001/__snapshots__/no-fs.test.js.snap b/system-tests/001/__snapshots__/no-fs.test.js.snap index a22d9111a8..319de1c94d 100644 --- a/system-tests/001/__snapshots__/no-fs.test.js.snap +++ b/system-tests/001/__snapshots__/no-fs.test.js.snap @@ -2,6 +2,7 @@ exports[`no-fs - valid sanitize.css and their config 1`] = ` Object { + "cwd": "", "errored": false, "output": Array [ Object { diff --git a/system-tests/002/__snapshots__/fs.test.js.snap b/system-tests/002/__snapshots__/fs.test.js.snap index f7b6f0e9e7..1c01c2cfb0 100644 --- a/system-tests/002/__snapshots__/fs.test.js.snap +++ b/system-tests/002/__snapshots__/fs.test.js.snap @@ -2,6 +2,7 @@ exports[`fs - invalid twbs buttons and their config 1`] = ` Object { + "cwd": "", "errored": true, "output": Array [ Object { diff --git a/system-tests/002/__snapshots__/no-fs.test.js.snap b/system-tests/002/__snapshots__/no-fs.test.js.snap index 06cdbf4b68..f978cfdcd2 100644 --- a/system-tests/002/__snapshots__/no-fs.test.js.snap +++ b/system-tests/002/__snapshots__/no-fs.test.js.snap @@ -2,6 +2,7 @@ exports[`no-fs - invalid twbs buttons and their config 1`] = ` Object { + "cwd": "", "errored": true, "output": Array [ Object { diff --git a/system-tests/003/__snapshots__/fs.test.js.snap b/system-tests/003/__snapshots__/fs.test.js.snap index ebf70f2485..3f1d5ef5d8 100644 --- a/system-tests/003/__snapshots__/fs.test.js.snap +++ b/system-tests/003/__snapshots__/fs.test.js.snap @@ -2,6 +2,7 @@ exports[`fs - zen garden CSS with standard config 1`] = ` Object { + "cwd": "", "errored": true, "output": Array [ Object { diff --git a/system-tests/003/__snapshots__/no-fs.test.js.snap b/system-tests/003/__snapshots__/no-fs.test.js.snap index 9aa6062945..9ba0a8862a 100644 --- a/system-tests/003/__snapshots__/no-fs.test.js.snap +++ b/system-tests/003/__snapshots__/no-fs.test.js.snap @@ -2,6 +2,7 @@ exports[`no-fs - zen garden CSS with standard config 1`] = ` Object { + "cwd": "", "errored": true, "output": "/* css Zen Garden default style v1.02 */ diff --git a/system-tests/004/__snapshots__/fs.test.js.snap b/system-tests/004/__snapshots__/fs.test.js.snap index aca4f03c3d..e449cca0f3 100644 --- a/system-tests/004/__snapshots__/fs.test.js.snap +++ b/system-tests/004/__snapshots__/fs.test.js.snap @@ -2,6 +2,7 @@ exports[`fs - errored state for reportNeedlessDisables 1`] = ` Object { + "cwd": "", "errored": true, "output": Array [ Object { @@ -44,6 +45,7 @@ Object { exports[`fs - no errored state 1`] = ` Object { + "cwd": "", "errored": false, "output": Array [ Object { diff --git a/system-tests/004/__snapshots__/no-fs.test.js.snap b/system-tests/004/__snapshots__/no-fs.test.js.snap index 5cd171095b..f92ecf965f 100644 --- a/system-tests/004/__snapshots__/no-fs.test.js.snap +++ b/system-tests/004/__snapshots__/no-fs.test.js.snap @@ -2,6 +2,7 @@ exports[`no-fs - errored state for reportNeedlessDisables 1`] = ` Object { + "cwd": "", "errored": true, "output": Array [ Object { @@ -44,6 +45,7 @@ Object { exports[`no-fs - no errored state 1`] = ` Object { + "cwd": "", "errored": false, "output": Array [ Object { diff --git a/system-tests/systemTestUtils.js b/system-tests/systemTestUtils.js index 551ee1e895..54c0617a9c 100644 --- a/system-tests/systemTestUtils.js +++ b/system-tests/systemTestUtils.js @@ -35,7 +35,7 @@ async function caseFilesForFix(caseNumber, ext = 'css') { return tempPath; } -function prepForSnapshot({ results, output, ...rest }) { +function prepForSnapshot({ results, cwd, output, ...rest }) { // If output isn't fixed code if (output.startsWith('[')) { // The `source` of each file varies between platforms or if a tmp file is used @@ -47,6 +47,7 @@ function prepForSnapshot({ results, output, ...rest }) { } return { + cwd: path.relative(process.cwd(), cwd), // The _postcssResult object is not part of our API and is huge results: results.map((result) => { delete result.source; diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index e63f8fa956..9a4b4cea50 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -190,6 +190,11 @@ declare module 'stylelint' { config?: Config; configFile?: string; configBasedir?: string; + /** + * The working directory to resolve files from. Defaults to the + * current working directory. + */ + cwd?: string; ignoreDisables?: boolean; ignorePath?: string; ignorePattern?: string[]; @@ -267,6 +272,11 @@ declare module 'stylelint' { }; export type LinterResult = { + /** + * The working directory from which the linter was run when the + * results were generated. + */ + cwd: string; results: LintResult[]; errored: boolean; output: any; From 328351e037a2b55c562c2305f80f56043bb8212c Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Thu, 18 Nov 2021 11:03:41 -0800 Subject: [PATCH 2/3] refactor: set internal API cwd in createStylelint --- lib/augmentConfig.js | 2 +- lib/createStylelint.js | 6 ++++-- lib/getConfigForFile.js | 4 ++-- lib/isPathIgnored.js | 2 +- lib/lintSource.js | 2 +- types/stylelint/index.d.ts | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/augmentConfig.js b/lib/augmentConfig.js index 2cfd8b3be0..d224feb456 100644 --- a/lib/augmentConfig.js +++ b/lib/augmentConfig.js @@ -57,7 +57,7 @@ async function augmentConfigBasic( filePath, ); - const cwd = stylelint._options.cwd || process.cwd(); + const cwd = stylelint._options.cwd; return absolutizePaths(augmentedConfig, configDir, cwd); } diff --git a/lib/createStylelint.js b/lib/createStylelint.js index bec7abc71c..ffe0a04582 100644 --- a/lib/createStylelint.js +++ b/lib/createStylelint.js @@ -22,12 +22,14 @@ const STOP_DIR = IS_TEST ? process.cwd() : undefined; * @returns {StylelintInternalApi} */ function createStylelint(options = {}) { + const cwd = options.cwd || process.cwd(); + /** @type {StylelintInternalApi} */ // @ts-expect-error -- TS2740: Type '{ _options: LinterOptions; }' is missing the following properties from type 'InternalApi' - const stylelint = { _options: options }; + const stylelint = { _options: { ...options, cwd } }; stylelint._extendExplorer = cosmiconfig('', { - transform: augmentConfig.augmentConfigExtended(options.cwd), + transform: augmentConfig.augmentConfigExtended(cwd), stopDir: STOP_DIR, }); diff --git a/lib/getConfigForFile.js b/lib/getConfigForFile.js index d8bd3938b6..1d843df103 100644 --- a/lib/getConfigForFile.js +++ b/lib/getConfigForFile.js @@ -20,11 +20,11 @@ const STOP_DIR = IS_TEST ? process.cwd() : undefined; */ module.exports = async function getConfigForFile( stylelint, - searchPath = stylelint._options.cwd || process.cwd(), + searchPath = stylelint._options.cwd, filePath, ) { const optionsConfig = stylelint._options.config; - const cwd = stylelint._options.cwd || process.cwd(); + const cwd = stylelint._options.cwd; if (optionsConfig !== undefined) { const cached = stylelint._specifiedConfigCache.get(optionsConfig); diff --git a/lib/isPathIgnored.js b/lib/isPathIgnored.js index 457d4d95af..13c006146a 100644 --- a/lib/isPathIgnored.js +++ b/lib/isPathIgnored.js @@ -19,7 +19,7 @@ module.exports = async function isPathIgnored(stylelint, filePath) { return false; } - const cwd = stylelint._options.cwd || process.cwd(); + const cwd = stylelint._options.cwd; const ignorer = getFileIgnorer(stylelint._options); const result = await stylelint.getConfigForFile(filePath, filePath); diff --git a/lib/lintSource.js b/lib/lintSource.js index 4ffa906163..caf877b44b 100644 --- a/lib/lintSource.js +++ b/lib/lintSource.js @@ -49,7 +49,7 @@ module.exports = async function lintSource(stylelint, options = {}) { } const configSearchPath = stylelint._options.configFile || inputFilePath; - const cwd = stylelint._options.cwd || process.cwd(); + const cwd = stylelint._options.cwd; const configForFile = await stylelint .getConfigForFile(configSearchPath, inputFilePath) diff --git a/types/stylelint/index.d.ts b/types/stylelint/index.d.ts index 9a4b4cea50..ffbc8eac36 100644 --- a/types/stylelint/index.d.ts +++ b/types/stylelint/index.d.ts @@ -374,7 +374,7 @@ declare module 'stylelint' { * @internal */ export type InternalApi = { - _options: LinterOptions; + _options: LinterOptions & { cwd: string }; _extendExplorer: ReturnType; _specifiedConfigCache: Map>; _postcssResultCache: Map; From 14a796787cf42c91500321d828000db308ed59b3 Mon Sep 17 00:00:00 2001 From: Adaline Valentina Simonian Date: Thu, 18 Nov 2021 18:16:18 -0800 Subject: [PATCH 3/3] refactor: require cwd in augmentConfigExtended --- lib/augmentConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/augmentConfig.js b/lib/augmentConfig.js index d224feb456..a06ad2d782 100644 --- a/lib/augmentConfig.js +++ b/lib/augmentConfig.js @@ -66,10 +66,10 @@ async function augmentConfigBasic( * Extended configs need to be run through augmentConfigBasic * but do not need the full treatment. Things like pluginFunctions * will be resolved and added by the parent config. - * @param {string} [cwd] + * @param {string} cwd * @returns {(cosmiconfigResult?: StylelintCosmiconfigResult) => Promise} */ -function augmentConfigExtended(cwd = process.cwd()) { +function augmentConfigExtended(cwd) { return async (cosmiconfigResult) => { if (!cosmiconfigResult) { return null;