From ffc482368cc2df5cbcf37bbbc07c37c707308d13 Mon Sep 17 00:00:00 2001 From: Spencer Snyder Date: Tue, 19 Oct 2021 15:53:13 -0400 Subject: [PATCH 1/8] fix: group files by config (closes #599) --- index.js | 98 +++++++++++++++++++++++++----------------- lib/options-manager.js | 14 ++++-- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 2675f9d1..f86cc715 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ import path from 'node:path'; import {ESLint} from 'eslint'; import {globby, isGitIgnoredSync} from 'globby'; -import {isEqual} from 'lodash-es'; +import {isEqual, groupBy} from 'lodash-es'; import micromatch from 'micromatch'; import arrify from 'arrify'; import slash from 'slash'; @@ -12,30 +12,6 @@ import { } from './lib/options-manager.js'; import {mergeReports, processReport, getIgnoredReport} from './lib/report.js'; -const runEslint = async (lint, options) => { - const {filePath, eslintOptions, isQuiet} = options; - const {cwd, baseConfig: {ignorePatterns}} = eslintOptions; - - if ( - filePath - && ( - micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns) - || isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath) - ) - ) { - return getIgnoredReport(filePath); - } - - const eslint = new ESLint(eslintOptions); - - if (filePath && await eslint.isPathIgnored(filePath)) { - return getIgnoredReport(filePath); - } - - const report = await lint(eslint); - return processReport(report, {isQuiet}); -}; - const globFiles = async (patterns, options) => { const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options; @@ -59,32 +35,76 @@ const getConfig = async options => { const lintText = async (string, options) => { options = await parseOptions(options); - const {filePath, warnIgnored, eslintOptions} = options; - const {ignorePatterns} = eslintOptions.baseConfig; + const {filePath, warnIgnored, eslintOptions, isQuiet} = options; + const {cwd, baseConfig: {ignorePatterns}} = eslintOptions; if (typeof filePath !== 'string' && !isEqual(getIgnores({}), ignorePatterns)) { throw new Error('The `ignores` option requires the `filePath` option to be defined.'); } - return runEslint( - eslint => eslint.lintText(string, {filePath, warnIgnored}), - options, - ); -}; + if ( + filePath + && ( + micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns) + || isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath) + ) + ) { + return getIgnoredReport(filePath); + } -const lintFile = async (filePath, options) => runEslint( - eslint => eslint.lintFiles([filePath]), - await parseOptions({...options, filePath}), -); + const eslint = new ESLint(eslintOptions); + + if (filePath && await eslint.isPathIgnored(filePath)) { + return getIgnoredReport(filePath); + } + + const report = await eslint.lintText(string, {filePath, warnIgnored}); + return processReport(report, {isQuiet}); +}; const lintFiles = async (patterns, options) => { const files = await globFiles(patterns, options); - const reports = await Promise.all( - files.map(filePath => lintFile(filePath, options)), + const allOptions = await Promise.all( + files.map(filePath => parseOptions({...options, filePath})), ); - const report = mergeReports(reports.filter(({isIgnored}) => !isIgnored)); + // Files with same `xoConfigPath` can lint together + // https://github.com/xojs/xo/issues/599 + const groups = groupBy(allOptions, 'xoConfigPath'); + + const reports = await Promise.all( + Object.values(groups) + .map(async filesWithOptions => { + const options = filesWithOptions[0]; + const eslint = new ESLint(options.eslintOptions); + const files = []; + + for (const options of filesWithOptions) { + const {filePath, eslintOptions} = options; + const {cwd, baseConfig: {ignorePatterns}} = eslintOptions; + if (filePath + && ( + micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns) + || isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath) + )) { + continue; + } + + // eslint-disable-next-line no-await-in-loop + if ((await eslint.isPathIgnored(filePath))) { + continue; + } + + files.push(filePath); + } + + const report = await eslint.lintFiles(files); + + return processReport(report, {isQuiet: options.isQuiet}); + })); + + const report = mergeReports(reports); return report; }; diff --git a/lib/options-manager.js b/lib/options-manager.js index 664cf423..d2cde38c 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -109,14 +109,19 @@ const mergeWithFileConfig = async options => { const searchPath = options.filePath || options.cwd; - const {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {}; + let {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {}; const {config: enginesOptions} = (await pkgConfigExplorer.search(searchPath)) || {}; options = mergeOptions(options, xoOptions, enginesOptions); options.cwd = xoConfigPath && path.dirname(xoConfigPath) !== options.cwd ? path.resolve(options.cwd, path.dirname(xoConfigPath)) : options.cwd; if (options.filePath) { - ({options} = applyOverrides(options.filePath, options)); + const overrides = applyOverrides(options.filePath, options); + options = overrides.options; + + if (overrides.hash) { + xoConfigPath += overrides.hash; + } } const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {}; @@ -132,7 +137,7 @@ const mergeWithFileConfig = async options => { await fs.writeFile(options.tsConfigPath, JSON.stringify(config)); } - return {options, prettierOptions}; + return {options, prettierOptions, xoConfigPath}; }; /** @@ -538,13 +543,14 @@ const gatherImportResolvers = options => { const parseOptions = async options => { options = normalizeOptions(options); - const {options: foundOptions, prettierOptions} = await mergeWithFileConfig(options); + const {options: foundOptions, prettierOptions, xoConfigPath} = await mergeWithFileConfig(options); const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions); return { filePath, warnIgnored, isQuiet: options.quiet, eslintOptions, + xoConfigPath, }; }; From 221131c9156953aad63f6f2a57cb14258211dec3 Mon Sep 17 00:00:00 2001 From: Spencer Snyder Date: Wed, 20 Oct 2021 14:02:49 -0400 Subject: [PATCH 2/8] fix: separate linting for ts config hash and more clear var names --- index.js | 2 +- lib/options-manager.js | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index f86cc715..233a337e 100644 --- a/index.js +++ b/index.js @@ -71,7 +71,7 @@ const lintFiles = async (patterns, options) => { // Files with same `xoConfigPath` can lint together // https://github.com/xojs/xo/issues/599 - const groups = groupBy(allOptions, 'xoConfigPath'); + const groups = groupBy(allOptions, 'eslintConfigId'); const reports = await Promise.all( Object.values(groups) diff --git a/lib/options-manager.js b/lib/options-manager.js index d2cde38c..2c70648a 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -109,18 +109,19 @@ const mergeWithFileConfig = async options => { const searchPath = options.filePath || options.cwd; - let {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {}; + const {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {}; const {config: enginesOptions} = (await pkgConfigExplorer.search(searchPath)) || {}; options = mergeOptions(options, xoOptions, enginesOptions); options.cwd = xoConfigPath && path.dirname(xoConfigPath) !== options.cwd ? path.resolve(options.cwd, path.dirname(xoConfigPath)) : options.cwd; + let eslintConfigId = xoConfigPath; if (options.filePath) { const overrides = applyOverrides(options.filePath, options); options = overrides.options; if (overrides.hash) { - xoConfigPath += overrides.hash; + eslintConfigId += overrides.hash; } } @@ -130,14 +131,16 @@ const mergeWithFileConfig = async options => { const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}}); const {config: tsConfig, filepath: tsConfigPath} = (await tsConfigExplorer.search(options.filePath)) || {}; - options.tsConfigPath = await getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd); + const {path: tsConfigCachePath, hash: tsConfigHash} = await getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd); + eslintConfigId += tsConfigHash; + options.tsConfigPath = tsConfigCachePath; options.ts = true; const config = makeTSConfig(tsConfig, tsConfigPath, [options.filePath]); await fs.mkdir(path.dirname(options.tsConfigPath), {recursive: true}); await fs.writeFile(options.tsConfigPath, JSON.stringify(config)); } - return {options, prettierOptions, xoConfigPath}; + return {options, prettierOptions, eslintConfigId}; }; /** @@ -146,10 +149,14 @@ Hashing based on https://github.com/eslint/eslint/blob/cf38d0d939b62f3670cdd59f0 */ const getTsConfigCachePath = async (files, tsConfigPath, cwd) => { const {version} = await json.load('../package.json'); - return path.join( - cacheLocation(cwd), - `tsconfig.${murmur(`${version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36)}.json`, - ); + const tsConfigHash = murmur(`${version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36); + return { + path: path.join( + cacheLocation(cwd), + `tsconfig.${tsConfigHash}.json`, + ), + hash: tsConfigHash, + }; }; const makeTSConfig = (tsConfig, tsConfigPath, files) => { @@ -543,14 +550,14 @@ const gatherImportResolvers = options => { const parseOptions = async options => { options = normalizeOptions(options); - const {options: foundOptions, prettierOptions, xoConfigPath} = await mergeWithFileConfig(options); + const {options: foundOptions, prettierOptions, eslintConfigId} = await mergeWithFileConfig(options); const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions); return { filePath, warnIgnored, isQuiet: options.quiet, eslintOptions, - xoConfigPath, + eslintConfigId, }; }; From 9d4286767a4e580eb90b85dac8cd796c0c3e905b Mon Sep 17 00:00:00 2001 From: Spencer Snyder Date: Thu, 21 Oct 2021 11:48:08 -0400 Subject: [PATCH 3/8] fix: only cache tsconfig files when necessary --- lib/options-manager.js | 37 +++++++++++++---- .../parseroptions-project/package.json | 7 ++++ .../parseroptions-project/projectconfig.json | 3 ++ .../parseroptions-project/tsconfig.json | 3 ++ test/options-manager.js | 40 ++++++++++--------- 5 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 test/fixtures/typescript/parseroptions-project/package.json create mode 100644 test/fixtures/typescript/parseroptions-project/projectconfig.json create mode 100644 test/fixtures/typescript/parseroptions-project/tsconfig.json diff --git a/lib/options-manager.js b/lib/options-manager.js index 2c70648a..520bd2dc 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -115,6 +115,8 @@ const mergeWithFileConfig = async options => { options = mergeOptions(options, xoOptions, enginesOptions); options.cwd = xoConfigPath && path.dirname(xoConfigPath) !== options.cwd ? path.resolve(options.cwd, path.dirname(xoConfigPath)) : options.cwd; + // Very simple way to ensure eslint is ran minimal times across + // all linted files, once for each unique configuration - xo config path + override hash + tsconfig path let eslintConfigId = xoConfigPath; if (options.filePath) { const overrides = applyOverrides(options.filePath, options); @@ -128,16 +130,35 @@ const mergeWithFileConfig = async options => { const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {}; if (options.filePath && isTypescript(options.filePath)) { - const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}}); - const {config: tsConfig, filepath: tsConfigPath} = (await tsConfigExplorer.search(options.filePath)) || {}; + // We can skip looking up the tsconfig if we have it defined + // in our parser options already. Otherwise we can look it up and create it as normal + const {project: tsConfigProjectPath, tsconfigRootDir} = options.parserOptions || {}; + + let tsConfig; + let tsConfigPath; + if (tsConfigProjectPath) { + tsConfigPath = path.resolve(options.cwd, tsConfigProjectPath); + tsConfig = await json.load(tsConfigPath); + } else { + const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}, stopDir: tsconfigRootDir}); + const searchResults = (await tsConfigExplorer.search(options.filePath)) || {}; + tsConfigPath = searchResults.filepath; + tsConfig = searchResults.config; + } + + if (tsConfigPath) { + options.tsConfigPath = tsConfigPath; + eslintConfigId += tsConfigPath; + } else { + const {path: tsConfigCachePath, hash: tsConfigHash} = await getTsConfigCachePath([eslintConfigId], tsConfigPath, options.cwd); + eslintConfigId += tsConfigHash; + options.tsConfigPath = tsConfigCachePath; + const config = makeTSConfig(tsConfig, tsConfigPath, [options.filePath]); + await fs.mkdir(path.dirname(options.tsConfigPath), {recursive: true}); + await fs.writeFile(options.tsConfigPath, JSON.stringify(config)); + } - const {path: tsConfigCachePath, hash: tsConfigHash} = await getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd); - eslintConfigId += tsConfigHash; - options.tsConfigPath = tsConfigCachePath; options.ts = true; - const config = makeTSConfig(tsConfig, tsConfigPath, [options.filePath]); - await fs.mkdir(path.dirname(options.tsConfigPath), {recursive: true}); - await fs.writeFile(options.tsConfigPath, JSON.stringify(config)); } return {options, prettierOptions, eslintConfigId}; diff --git a/test/fixtures/typescript/parseroptions-project/package.json b/test/fixtures/typescript/parseroptions-project/package.json new file mode 100644 index 00000000..174a84ca --- /dev/null +++ b/test/fixtures/typescript/parseroptions-project/package.json @@ -0,0 +1,7 @@ +{ + "xo": { + "parserOptions": { + "project": "./projectconfig.json" + } + } +} diff --git a/test/fixtures/typescript/parseroptions-project/projectconfig.json b/test/fixtures/typescript/parseroptions-project/projectconfig.json new file mode 100644 index 00000000..ea6be8e9 --- /dev/null +++ b/test/fixtures/typescript/parseroptions-project/projectconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/test/fixtures/typescript/parseroptions-project/tsconfig.json b/test/fixtures/typescript/parseroptions-project/tsconfig.json new file mode 100644 index 00000000..10671361 --- /dev/null +++ b/test/fixtures/typescript/parseroptions-project/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/test/options-manager.js b/test/options-manager.js index 937245e6..64fcca3d 100644 --- a/test/options-manager.js +++ b/test/options-manager.js @@ -1,8 +1,6 @@ -import {promises as fs} from 'node:fs'; import process from 'node:process'; import path from 'node:path'; import test from 'ava'; -import {omit} from 'lodash-es'; import slash from 'slash'; import createEsmUtils from 'esm-utils'; import {DEFAULT_EXTENSION, DEFAULT_IGNORES} from '../lib/constants.js'; @@ -542,9 +540,10 @@ test('mergeWithFileConfig: XO engine options false supersede package.json\'s', a t.deepEqual(options, expected); }); -test('mergeWithFileConfig: typescript files', async t => { +test('mergeWithFileConfig: resolves expected typescript file options', async t => { const cwd = path.resolve('fixtures', 'typescript', 'child'); const filePath = path.resolve(cwd, 'file.ts'); + const tsConfigPath = path.resolve(cwd, 'tsconfig.json'); const {options} = await manager.mergeWithFileConfig({cwd, filePath}); const expected = { filePath, @@ -553,21 +552,16 @@ test('mergeWithFileConfig: typescript files', async t => { cwd, semicolon: false, ts: true, + tsConfigPath, }; - const expectedConfigPath = new RegExp(`${slash(cwd)}/node_modules/.cache/xo-linter/tsconfig\\..*\\.json[\\/]?$`, 'u'); - t.regex(slash(options.tsConfigPath), expectedConfigPath); - t.deepEqual(omit(options, 'tsConfigPath'), expected); - t.deepEqual(JSON.parse(await fs.readFile(options.tsConfigPath)), { - extends: path.resolve(cwd, 'tsconfig.json'), - files: [path.resolve(cwd, 'file.ts')], - include: [slash(path.resolve(cwd, '**/*.ts')), slash(path.resolve(cwd, '**/*.tsx'))], - }); + t.deepEqual(options, expected); }); -test('mergeWithFileConfig: tsx files', async t => { +test('mergeWithFileConfig: resolves expected tsx file options', async t => { const cwd = path.resolve('fixtures', 'typescript', 'child'); const filePath = path.resolve(cwd, 'file.tsx'); const {options} = await manager.mergeWithFileConfig({cwd, filePath}); + const tsConfigPath = path.resolve(cwd, 'tsconfig.json'); const expected = { filePath, extensions: DEFAULT_EXTENSION, @@ -575,15 +569,25 @@ test('mergeWithFileConfig: tsx files', async t => { cwd, semicolon: false, ts: true, + tsConfigPath, }; + t.deepEqual(options, expected); +}); + +test('mergeWithFileConfig: uses specified parserOptions.project as tsconfig', async t => { + const cwd = path.resolve('fixtures', 'typescript', 'parseroptions-project'); + const filePath = path.resolve(cwd, 'does-not-matter.ts'); + const expectedTsConfigPath = path.resolve(cwd, 'projectconfig.json'); + const {options} = await manager.mergeWithFileConfig({cwd, filePath}); + t.is(options.tsConfigPath, expectedTsConfigPath); +}); + +test('mergeWithFileConfig: creates temp tsconfig if none present', async t => { + const cwd = path.resolve('fixtures', 'typescript'); const expectedConfigPath = new RegExp(`${slash(cwd)}/node_modules/.cache/xo-linter/tsconfig\\..*\\.json[\\/]?$`, 'u'); + const filePath = path.resolve(cwd, 'does-not-matter.ts'); + const {options} = await manager.mergeWithFileConfig({cwd, filePath}); t.regex(slash(options.tsConfigPath), expectedConfigPath); - t.deepEqual(omit(options, 'tsConfigPath'), expected); - t.deepEqual(JSON.parse(await fs.readFile(options.tsConfigPath)), { - extends: path.resolve(cwd, 'tsconfig.json'), - files: [path.resolve(cwd, 'file.tsx')], - include: [slash(path.resolve(cwd, '**/*.ts')), slash(path.resolve(cwd, '**/*.tsx'))], - }); }); test('applyOverrides', t => { From 1a0f590b779a6361301fdcdbe27618d34950d1d4 Mon Sep 17 00:00:00 2001 From: Spencer Snyder Date: Thu, 21 Oct 2021 13:23:50 -0400 Subject: [PATCH 4/8] fix: refactor some failing tests for predictability --- lib/options-manager.js | 6 ++++- package.json | 1 + test/lint-text.js | 52 +++++++++++++++++++++++++++--------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/lib/options-manager.js b/lib/options-manager.js index 520bd2dc..1a07f03a 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -140,7 +140,11 @@ const mergeWithFileConfig = async options => { tsConfigPath = path.resolve(options.cwd, tsConfigProjectPath); tsConfig = await json.load(tsConfigPath); } else { - const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}, stopDir: tsconfigRootDir}); + const tsConfigExplorer = cosmiconfig([], { + searchPlaces: ['tsconfig.json'], + loaders: {'.json': (_, content) => JSON5.parse(content)}, + stopDir: tsconfigRootDir, + }); const searchResults = (await tsConfigExplorer.search(options.filePath)) || {}; tsConfigPath = searchResults.filepath; tsConfig = searchResults.config; diff --git a/package.json b/package.json index 0f55e1c1..db9eb55b 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "node": ">=12.20" }, "scripts": { + "test:clean": "find ./test -type d -name 'node_modules' -prune -not -path ./test/fixtures/project/node_modules -exec rm -rf '{}' +", "test": "node cli.js && nyc ava" }, "files": [ diff --git a/test/lint-text.js b/test/lint-text.js index 18a7d90b..f454530f 100644 --- a/test/lint-text.js +++ b/test/lint-text.js @@ -278,35 +278,49 @@ test('find configurations close to linted file', async t => { }); test('typescript files', async t => { - let {results} = await xo.lintText(`console.log([ - 2, -]); -`, {filePath: 'fixtures/typescript/two-spaces.tsx'}); - + const twoSpacesCwd = path.resolve('fixtures', 'typescript'); + const twoSpacesfilePath = path.resolve(twoSpacesCwd, 'two-spaces.tsx'); + const twoSpacesText = (await fs.readFile(twoSpacesfilePath)).toString(); + let {results} = await xo.lintText(twoSpacesText, { + filePath: twoSpacesfilePath, + }); t.true(hasRule(results, '@typescript-eslint/indent')); - ({results} = await xo.lintText(`console.log([ - 2, -]); -`, {filePath: 'fixtures/typescript/two-spaces.tsx', space: 2})); + ({results} = await xo.lintText(twoSpacesText, { + filePath: twoSpacesfilePath, + space: 2, + })); t.is(results[0].errorCount, 0); - ({results} = await xo.lintText('console.log(\'extra-semicolon\');;\n', {filePath: 'fixtures/typescript/child/extra-semicolon.ts'})); + const extraSemiCwd = path.resolve('fixtures', 'typescript', 'child'); + const extraSemiFilePath = path.resolve(extraSemiCwd, 'extra-semicolon.ts'); + const extraSemiText = (await fs.readFile(extraSemiFilePath)).toString(); + ({results} = await xo.lintText(extraSemiText, { + filePath: extraSemiFilePath, + })); t.true(hasRule(results, '@typescript-eslint/no-extra-semi')); - ({results} = await xo.lintText('console.log(\'no-semicolon\')\n', {filePath: 'fixtures/typescript/child/no-semicolon.ts', semicolon: false})); + const noSemiCwd = path.resolve('fixtures', 'typescript', 'child'); + const noSemiFilePath = path.resolve(noSemiCwd, 'no-semicolon.ts'); + const noSemiText = (await fs.readFile(noSemiFilePath)).toString(); + ({results} = await xo.lintText(noSemiText, { + filePath: noSemiFilePath, + semicolon: false, + })); t.is(results[0].errorCount, 0); - ({results} = await xo.lintText(`console.log([ - 4, -]); -`, {filePath: 'fixtures/typescript/child/sub-child/four-spaces.ts'})); + const fourSpacesCwd = path.resolve('fixtures', 'typescript', 'child', 'sub-child'); + const fourSpacesFilePath = path.resolve(fourSpacesCwd, 'four-spaces.ts'); + const fourSpacesText = (await fs.readFile(fourSpacesFilePath)).toString(); + ({results} = await xo.lintText(fourSpacesText, { + filePath: fourSpacesFilePath, + })); t.true(hasRule(results, '@typescript-eslint/indent')); - ({results} = await xo.lintText(`console.log([ - 4, -]); -`, {filePath: 'fixtures/typescript/child/sub-child/four-spaces.ts', space: 4})); + ({results} = await xo.lintText(fourSpacesText, { + filePath: fourSpacesFilePath, + space: 4, + })); t.is(results[0].errorCount, 0); }); From 0ebdc37499ab5558a695b124144708db281d2c25 Mon Sep 17 00:00:00 2001 From: Spencer Snyder Date: Thu, 21 Oct 2021 13:55:28 -0400 Subject: [PATCH 5/8] refactor: improve ci test output --- test/lint-text.js | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/test/lint-text.js b/test/lint-text.js index f454530f..12ddaf74 100644 --- a/test/lint-text.js +++ b/test/lint-text.js @@ -277,50 +277,66 @@ test('find configurations close to linted file', async t => { t.true(hasRule(results, 'indent')); }); -test('typescript files', async t => { +test('typescript files: two spaces fails', async t => { const twoSpacesCwd = path.resolve('fixtures', 'typescript'); const twoSpacesfilePath = path.resolve(twoSpacesCwd, 'two-spaces.tsx'); const twoSpacesText = (await fs.readFile(twoSpacesfilePath)).toString(); - let {results} = await xo.lintText(twoSpacesText, { + const {results} = await xo.lintText(twoSpacesText, { filePath: twoSpacesfilePath, }); t.true(hasRule(results, '@typescript-eslint/indent')); +}); - ({results} = await xo.lintText(twoSpacesText, { +test('typescript files: two spaces pass', async t => { + const twoSpacesCwd = path.resolve('fixtures', 'typescript'); + const twoSpacesfilePath = path.resolve(twoSpacesCwd, 'two-spaces.tsx'); + const twoSpacesText = (await fs.readFile(twoSpacesfilePath)).toString(); + const {results} = await xo.lintText(twoSpacesText, { filePath: twoSpacesfilePath, space: 2, - })); + }); t.is(results[0].errorCount, 0); +}); +test('typescript files: extra semi fail', async t => { const extraSemiCwd = path.resolve('fixtures', 'typescript', 'child'); const extraSemiFilePath = path.resolve(extraSemiCwd, 'extra-semicolon.ts'); const extraSemiText = (await fs.readFile(extraSemiFilePath)).toString(); - ({results} = await xo.lintText(extraSemiText, { + const {results} = await xo.lintText(extraSemiText, { filePath: extraSemiFilePath, - })); + }); t.true(hasRule(results, '@typescript-eslint/no-extra-semi')); +}); +test('typescript files: extra semi pass', async t => { const noSemiCwd = path.resolve('fixtures', 'typescript', 'child'); const noSemiFilePath = path.resolve(noSemiCwd, 'no-semicolon.ts'); const noSemiText = (await fs.readFile(noSemiFilePath)).toString(); - ({results} = await xo.lintText(noSemiText, { + const {results} = await xo.lintText(noSemiText, { filePath: noSemiFilePath, semicolon: false, - })); + }); t.is(results[0].errorCount, 0); +}); +test('typescript files: four space fail', async t => { const fourSpacesCwd = path.resolve('fixtures', 'typescript', 'child', 'sub-child'); const fourSpacesFilePath = path.resolve(fourSpacesCwd, 'four-spaces.ts'); const fourSpacesText = (await fs.readFile(fourSpacesFilePath)).toString(); - ({results} = await xo.lintText(fourSpacesText, { + const {results} = await xo.lintText(fourSpacesText, { filePath: fourSpacesFilePath, - })); + }); t.true(hasRule(results, '@typescript-eslint/indent')); +}); - ({results} = await xo.lintText(fourSpacesText, { +test('typescript files: four space pass', async t => { + const fourSpacesCwd = path.resolve('fixtures', 'typescript', 'child', 'sub-child'); + const fourSpacesFilePath = path.resolve(fourSpacesCwd, 'four-spaces.ts'); + const fourSpacesText = (await fs.readFile(fourSpacesFilePath)).toString(); + const {results} = await xo.lintText(fourSpacesText, { filePath: fourSpacesFilePath, space: 4, - })); + }); t.is(results[0].errorCount, 0); }); From c93e2955d5a3e14f78fd2dcf359e9916d5dccd8a Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 27 Oct 2021 19:14:12 +0700 Subject: [PATCH 6/8] Update index.js --- index.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/index.js b/index.js index 9ece6ea7..8330f4e4 100644 --- a/index.js +++ b/index.js @@ -13,33 +13,6 @@ import { } from './lib/options-manager.js'; import {mergeReports, processReport, getIgnoredReport} from './lib/report.js'; -const runEslint = async (lint, options) => { - const {filePath, eslintOptions, isQuiet} = options; - const {cwd, baseConfig: {ignorePatterns}} = eslintOptions; - - if ( - filePath - && ( - micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns) - || isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath) - ) - ) { - return getIgnoredReport(filePath); - } - - const eslint = new ESLint({ - ...eslintOptions, - resolvePluginsRelativeTo: path.dirname(fileURLToPath(import.meta.url)), - }); - - if (filePath && await eslint.isPathIgnored(filePath)) { - return getIgnoredReport(filePath); - } - - const report = await lint(eslint); - return processReport(report, {isQuiet}); -}; - const globFiles = async (patterns, options) => { const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options; From 1a2cbf5064993e9713245155fbe0e8ecf93f8db7 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 27 Oct 2021 19:18:26 +0700 Subject: [PATCH 7/8] Update options-manager.js --- lib/options-manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/options-manager.js b/lib/options-manager.js index 45fe5aef..7ebf3597 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -50,6 +50,7 @@ const DEFAULT_CONFIG = { cache: true, cacheLocation: path.join(cacheLocation(), 'xo-cache.json'), globInputPaths: false, + resolvePluginsRelativeTo: __dirname, baseConfig: { extends: [ resolveLocalConfig('xo'), From 1ffae51775cf5cc70d3191bc4f65c29ffa755e85 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 27 Oct 2021 19:19:39 +0700 Subject: [PATCH 8/8] Update index.js --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 8330f4e4..233a337e 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ -import {fileURLToPath} from 'node:url'; import path from 'node:path'; import {ESLint} from 'eslint'; import {globby, isGitIgnoredSync} from 'globby';