Skip to content

Commit de01ff1

Browse files
authored
Update dependencies and improve files option handling (#848)
1 parent 4a6488c commit de01ff1

14 files changed

+109
-190
lines changed

lib/config.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import pluginComments from '@eslint-community/eslint-plugin-eslint-comments';
66
import pluginPromise from 'eslint-plugin-promise';
77
import pluginNoUseExtendNative from 'eslint-plugin-no-use-extend-native';
88
import configXoTypescript from 'eslint-config-xo-typescript';
9-
import stylisticPlugin from '@stylistic/eslint-plugin';
109
import globals from 'globals';
1110
import {type Linter} from 'eslint';
1211
import {
@@ -22,7 +21,7 @@ if (Array.isArray(pluginAva?.configs?.['recommended'])) {
2221
throw new TypeError('Invalid eslint-plugin-ava');
2322
}
2423

25-
if (!configXoTypescript[1]) {
24+
if (!configXoTypescript[4]) {
2625
throw new Error('Invalid eslint-config-xo-typescript');
2726
}
2827

@@ -31,23 +30,23 @@ The base config that XO builds on top of from user options.
3130
*/
3231
export const config: Linter.Config[] = [
3332
{
34-
name: 'XO Default Ignores',
33+
name: 'xo/ignores',
3534
ignores: defaultIgnores,
3635
},
3736
{
38-
name: 'XO',
39-
files: [
40-
allFilesGlob,
41-
],
37+
name: 'xo/base',
38+
files: [allFilesGlob],
4239
plugins: {
40+
...configXoTypescript[0]?.plugins,
41+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
42+
'@typescript-eslint': configXoTypescript[4]?.plugins?.['@typescript-eslint']!,
4343
'no-use-extend-native': pluginNoUseExtendNative,
4444
ava: pluginAva,
4545
unicorn: pluginUnicorn,
4646
'import-x': pluginImport,
4747
n: pluginN,
4848
'@eslint-community/eslint-comments': pluginComments,
4949
promise: pluginPromise,
50-
'@stylistic': stylisticPlugin, // eslint-disable-line @typescript-eslint/naming-convention
5150
},
5251
languageOptions: {
5352
globals: {
@@ -376,17 +375,22 @@ export const config: Linter.Config[] = [
376375
},
377376
},
378377
{
379-
name: 'Xo TypeScript',
380-
plugins: configXoTypescript[1]?.plugins,
378+
name: 'xo/typescript',
379+
plugins: configXoTypescript[4]?.plugins, // ['@typescript-eslint'],
381380
files: [tsFilesGlob],
382381
languageOptions: {
383-
...configXoTypescript[1]?.languageOptions,
382+
...configXoTypescript[4]?.languageOptions,
383+
parserOptions: {
384+
...configXoTypescript[4]?.languageOptions?.parserOptions,
385+
// This needs to be explicitly set to `true`
386+
projectService: true,
387+
},
384388
},
385389
/**
386390
This turns on rules in `typescript-eslint`` and turns off rules from ESLint that conflict.
387391
*/
388392
rules: {
389-
...configXoTypescript[1]?.rules,
393+
...configXoTypescript[4]?.rules,
390394
'unicorn/import-style': 'off',
391395
'n/file-extension-in-import': 'off',
392396
// Disabled because of https://github.com/benmosher/eslint-plugin-import-x/issues/1590
@@ -398,7 +402,7 @@ export const config: Linter.Config[] = [
398402
'import-x/named': 'off',
399403
},
400404
},
401-
...configXoTypescript.slice(2),
405+
...configXoTypescript.slice(5),
402406
{
403407
files: ['xo.config.{js,ts}'],
404408
rules: {

lib/open-report.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const sortResults = (a: ESLint.LintResult, b: ESLint.LintResult) => a.errorCount
66

77
const resultToFile = (result: ESLint.LintResult) => {
88
const [message] = result.messages
9-
.sort((a, b) => {
9+
.toSorted((a, b) => {
1010
if (a.severity < b.severity) {
1111
return 1;
1212
}
@@ -35,7 +35,7 @@ const resultToFile = (result: ESLint.LintResult) => {
3535

3636
const getFiles = (report: XoLintResult, predicate: (result: ESLint.LintResult) => boolean) => report.results
3737
.filter(result => predicate(result))
38-
.sort(sortResults)
38+
.toSorted(sortResults)
3939
.map(result => resultToFile(result));
4040

4141
const openReport = async (report: XoLintResult) => {

lib/resolve-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ export async function resolveXoConfig(options: LinterOptions): Promise<{
4848
filepath: flatConfigPath = '',
4949
} = await (
5050
options.configPath
51+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
5152
? flatConfigExplorer.load(path.resolve(options.cwd, options.configPath)) as Promise<{config: FlatXoConfig | undefined; filepath: string}>
53+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
5254
: flatConfigExplorer.search(searchPath) as Promise<{config: FlatXoConfig | undefined; filepath: string}>
5355
) ?? {};
5456

lib/utils.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import path from 'node:path';
22
import micromatch from 'micromatch';
33
import {type Linter} from 'eslint';
4-
import {type SetRequired} from 'type-fest';
54
import arrify from 'arrify';
65
import configXoTypescript from 'eslint-config-xo-typescript';
76
import {type XoConfigItem} from './types.js';
@@ -21,7 +20,7 @@ Files and rules will always be defined and all other ESLint config properties ar
2120
@param xoConfig
2221
@returns eslintConfig
2322
*/
24-
export const xoToEslintConfigItem = (xoConfig: XoConfigItem): SetRequired<Linter.Config, 'rules' | 'files'> => {
23+
export const xoToEslintConfigItem = (xoConfig: XoConfigItem): Linter.Config => {
2524
const {
2625
files,
2726
rules,
@@ -33,10 +32,10 @@ export const xoToEslintConfigItem = (xoConfig: XoConfigItem): SetRequired<Linter
3332
..._xoConfig
3433
} = xoConfig;
3534

36-
const eslintConfig: SetRequired<Linter.Config, 'rules' | 'files'> = {
35+
const eslintConfig: Linter.Config = {
3736
..._xoConfig,
38-
files: arrify(xoConfig.files ?? allFilesGlob),
39-
rules: xoConfig.rules ?? {},
37+
...(xoConfig.files ? {files: arrify(xoConfig.files)} : {}),
38+
...(xoConfig.rules ? {rules: xoConfig.rules} : {}),
4039
};
4140

4241
eslintConfig.ignores &&= arrify(xoConfig.ignores);
@@ -53,8 +52,8 @@ Function used to match files which should be included in the `tsconfig.json` fil
5352
@param ignores - The globs to ignore when matching the files.
5453
@returns An array of file paths that match the globs and do not match the ignores.
5554
*/
56-
export const matchFilesForTsConfig = (cwd: string, files: string[], globs: string[], ignores: string[]) => micromatch(
57-
files.map(file => path.normalize(path.relative(cwd, file))),
55+
export const matchFilesForTsConfig = (cwd: string, files: string[] | undefined, globs: string[], ignores: string[]) => micromatch(
56+
files?.map(file => path.normalize(path.relative(cwd, file))) ?? [],
5857
// https://github.com/micromatch/micromatch/issues/217
5958
globs.map(glob => path.normalize(glob)),
6059
{

lib/xo-to-eslint.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function xoToEslintConfig(flatXoConfig: XoConfigItem[] | undefined, {pret
4747
const eslintConfigItem = xoToEslintConfigItem(xoConfigItem);
4848

4949
if (xoConfigItem.semicolon === false) {
50+
eslintConfigItem.rules ??= {};
5051
eslintConfigItem.rules['@stylistic/semi'] = ['error', 'never'];
5152
eslintConfigItem.rules['@stylistic/semi-spacing'] = [
5253
'error',
@@ -56,6 +57,7 @@ export function xoToEslintConfig(flatXoConfig: XoConfigItem[] | undefined, {pret
5657

5758
if (xoConfigItem.space) {
5859
const spaces = typeof xoConfigItem.space === 'number' ? xoConfigItem.space : 2;
60+
eslintConfigItem.rules ??= {};
5961
eslintConfigItem.rules['@stylistic/indent'] = [
6062
'error',
6163
spaces,
@@ -66,13 +68,14 @@ export function xoToEslintConfig(flatXoConfig: XoConfigItem[] | undefined, {pret
6668
} else if (xoConfigItem.space === false) {
6769
// If a user sets this to false for a small subset of files for some reason,
6870
// then we need to set them back to their original values.
71+
eslintConfigItem.rules ??= {};
6972
eslintConfigItem.rules['@stylistic/indent'] = configXoTypescript[1]?.rules?.['@stylistic/indent'];
7073
eslintConfigItem.rules['@stylistic/indent-binary-ops'] = configXoTypescript[1]?.rules?.['@stylistic/indent-binary-ops'];
7174
}
7275

7376
if (xoConfigItem.react) {
7477
// Ensure the files applied to the React config are the same as the config they are derived from
75-
baseConfig.push({...configReact[0], files: eslintConfigItem.files});
78+
baseConfig.push({...configReact[0], files: eslintConfigItem.files, name: 'xo/react'});
7679
}
7780

7881
// Prettier should generally be the last config in the array
@@ -114,6 +117,7 @@ export function xoToEslintConfig(flatXoConfig: XoConfigItem[] | undefined, {pret
114117
// Configure Prettier rules
115118
const rulesWithPrettier: Linter.RulesRecord = {
116119
...eslintConfigItem.rules,
120+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
117121
...(pluginPrettier.configs?.['recommended'] as ESLint.ConfigData)?.rules,
118122
// eslint-disable-next-line @typescript-eslint/naming-convention
119123
'prettier/prettier': ['error', prettierConfig],
@@ -124,9 +128,14 @@ export function xoToEslintConfig(flatXoConfig: XoConfigItem[] | undefined, {pret
124128
}
125129
} else if (xoConfigItem.prettier === false) {
126130
// Turn Prettier off for a subset of files
131+
eslintConfigItem.rules ??= {};
127132
eslintConfigItem.rules['prettier/prettier'] = 'off';
128133
}
129134

135+
if (Object.keys(eslintConfigItem).length === 0) {
136+
continue;
137+
}
138+
130139
baseConfig.push(eslintConfigItem);
131140
}
132141

lib/xo.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {globby} from 'globby';
88
import arrify from 'arrify';
99
import defineLazyProperty from 'define-lazy-prop';
1010
import prettier from 'prettier';
11+
import configXoTypescript from 'eslint-config-xo-typescript';
1112
import {
1213
type XoLintResult,
1314
type LinterOptions,
@@ -26,6 +27,10 @@ import resolveXoConfig from './resolve-config.js';
2627
import {handleTsconfig} from './handle-ts-files.js';
2728
import {matchFilesForTsConfig, preProcessXoConfig} from './utils.js';
2829

30+
if (!configXoTypescript[4]) {
31+
throw new Error('Invalid eslint-config-xo-typescript');
32+
}
33+
2934
export class Xo {
3035
/**
3136
Static helper to convert an XO config to an ESLint config to be used in `eslint.config.js`.
@@ -257,7 +262,7 @@ export class Xo {
257262
@param files - The TypeScript files being linted.
258263
*/
259264
async handleUnincludedTsFiles(files?: string[]) {
260-
if (!this.linterOptions.ts || !files || files.length === 0) {
265+
if (!this.linterOptions.ts) {
261266
return;
262267
}
263268

@@ -278,7 +283,7 @@ export class Xo {
278283

279284
const config: XoConfigItem = {};
280285
config.files = unincludedFiles.map(file => path.relative(this.linterOptions.cwd, file));
281-
config.languageOptions ??= {};
286+
config.languageOptions ??= {...configXoTypescript[4]?.languageOptions};
282287
config.languageOptions.parserOptions ??= {};
283288
config.languageOptions.parserOptions['projectService'] = false;
284289
config.languageOptions.parserOptions['project'] = fallbackTsConfigPath;
@@ -331,7 +336,7 @@ export class Xo {
331336

332337
globs = arrify(globs);
333338

334-
let files: string | string[] = await globby(globs, {
339+
const files: string | string[] = await globby(globs, {
335340
// Merge in command line ignores
336341
ignore: [...defaultIgnores, ...arrify(this.baseXoConfig.ignores)],
337342
onlyFiles: true,
@@ -347,7 +352,7 @@ export class Xo {
347352
}
348353

349354
if (files.length === 0) {
350-
files = '!**/*';
355+
return this.processReport([]);
351356
}
352357

353358
const results = await this.eslint.lintFiles(files);
@@ -389,6 +394,7 @@ export class Xo {
389394
throw new Error('Failed to initialize ESLint');
390395
}
391396

397+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
392398
return this.eslint.calculateConfigForFile(filePath) as Promise<Linter.Config>;
393399
}
394400

package.json

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,52 +66,50 @@
6666
],
6767
"dependencies": {
6868
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
69-
"@sindresorhus/tsconfig": "^7.0.0",
70-
"@stylistic/eslint-plugin": "^4.2.0",
71-
"@typescript-eslint/parser": "^8.37.0",
69+
"@sindresorhus/tsconfig": "^8.0.1",
7270
"arrify": "^3.0.0",
7371
"cosmiconfig": "^9.0.0",
7472
"define-lazy-prop": "^3.0.0",
75-
"eslint": "^9.31.0",
76-
"eslint-config-prettier": "^10.1.5",
73+
"eslint": "^9.37.0",
74+
"eslint-config-prettier": "^10.1.8",
7775
"eslint-config-xo-react": "^0.28.0",
78-
"eslint-config-xo-typescript": "^7.0.0",
79-
"eslint-formatter-pretty": "^6.0.1",
80-
"eslint-plugin-ava": "^15.0.1",
76+
"eslint-config-xo-typescript": "^9.0.0",
77+
"eslint-formatter-pretty": "^7.0.0",
78+
"eslint-plugin-ava": "^15.1.0",
8179
"eslint-plugin-import-x": "^4.16.1",
82-
"eslint-plugin-n": "^17.21.0",
80+
"eslint-plugin-n": "^17.23.1",
8381
"eslint-plugin-no-use-extend-native": "^0.7.2",
84-
"eslint-plugin-prettier": "^5.5.1",
82+
"eslint-plugin-prettier": "^5.5.4",
8583
"eslint-plugin-promise": "^7.2.1",
86-
"eslint-plugin-unicorn": "^59.0.1",
84+
"eslint-plugin-unicorn": "^61.0.2",
8785
"find-cache-directory": "^6.0.0",
8886
"get-stdin": "^9.0.0",
89-
"get-tsconfig": "^4.10.1",
90-
"globals": "^16.3.0",
91-
"globby": "^14.1.0",
92-
"meow": "^13.2.0",
87+
"get-tsconfig": "^4.12.0",
88+
"globals": "^16.4.0",
89+
"globby": "^15.0.0",
90+
"meow": "^14.0.0",
9391
"micromatch": "^4.0.8",
9492
"open-editor": "^5.1.0",
9593
"path-exists": "^5.0.0",
9694
"prettier": "^3.6.2",
97-
"type-fest": "^4.41.0",
98-
"typescript-eslint": "^8.37.0"
95+
"type-fest": "^5.1.0",
96+
"typescript-eslint": "^8.46.1"
9997
},
10098
"devDependencies": {
101-
"@commitlint/cli": "^19.8.1",
102-
"@commitlint/config-conventional": "^19.8.1",
99+
"@commitlint/cli": "^20.1.0",
100+
"@commitlint/config-conventional": "^20.0.0",
103101
"@types/eslint": "9.6.1",
104102
"@types/micromatch": "^4.0.9",
105103
"@types/prettier": "^3.0.0",
106104
"ava": "^6.4.1",
107-
"dedent": "^1.6.0",
108-
"execa": "^9.5.3",
105+
"dedent": "^1.7.0",
106+
"execa": "^9.6.0",
109107
"husky": "^9.1.7",
110-
"lint-staged": "^16.0.0",
108+
"lint-staged": "^16.2.4",
111109
"np": "^10.2.0",
112110
"npm-package-json-lint": "^9.0.0",
113111
"npm-package-json-lint-config-default": "^8.0.1",
114-
"prettier-plugin-packagejson": "^2.5.18",
112+
"prettier-plugin-packagejson": "^2.5.19",
115113
"temp-dir": "^3.0.0",
116114
"xo": "file:."
117115
},

test/cli.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
12
import fs from 'node:fs/promises';
23
import path from 'node:path';
34
import _test, {type TestFn} from 'ava'; // eslint-disable-line ava/use-test
@@ -168,7 +169,7 @@ test('xo --reporter json', async t => {
168169
const error: ExecaError = await t.throwsAsync($`node ./dist/cli --cwd ${t.context.cwd} --reporter=json`);
169170

170171
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
171-
const results = JSON.parse(error?.stdout?.toString() ?? '');
172+
const results: unknown[] = JSON.parse(error?.stdout?.toString() ?? '');
172173

173174
t.true(Array.isArray(results));
174175
t.is(results.length, 1);

0 commit comments

Comments
 (0)