xo 1.2.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +7 -2
- package/dist/lib/config.js +57 -41
- package/dist/lib/handle-ts-files.d.ts +11 -8
- package/dist/lib/handle-ts-files.js +88 -14
- package/dist/lib/open-report.js +2 -2
- package/dist/lib/resolve-config.js +2 -3
- package/dist/lib/rules/no-use-extend-native.d.ts +3 -0
- package/dist/lib/rules/no-use-extend-native.js +386 -0
- package/dist/lib/types.d.ts +3 -1
- package/dist/lib/utils.d.ts +3 -3
- package/dist/lib/utils.js +30 -15
- package/dist/lib/xo-to-eslint.js +59 -4
- package/dist/lib/xo.d.ts +25 -1
- package/dist/lib/xo.js +201 -26
- package/package.json +42 -37
- package/readme.md +72 -5
package/dist/lib/xo.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ESLint, type Linter } from 'eslint';
|
|
|
2
2
|
import prettier from 'prettier';
|
|
3
3
|
import { type XoLintResult, type LinterOptions, type LintTextOptions, type XoConfigOptions, type XoConfigItem } from './types.js';
|
|
4
4
|
import { xoToEslintConfig } from './xo-to-eslint.js';
|
|
5
|
+
export declare const ignoredFileWarningMessage = "File ignored because of a matching ignore pattern.";
|
|
5
6
|
export declare class Xo {
|
|
6
7
|
/**
|
|
7
8
|
Static helper to convert an XO config to an ESLint config to be used in `eslint.config.js`.
|
|
@@ -65,6 +66,21 @@ export declare class Xo {
|
|
|
65
66
|
We use this to also add negative glob patterns in case a user overrides the parserOptions in their XO config.
|
|
66
67
|
*/
|
|
67
68
|
tsFilesIgnoresGlob: string[];
|
|
69
|
+
/**
|
|
70
|
+
Track whether ignores have been added to prevent duplicate ignore configs.
|
|
71
|
+
*/
|
|
72
|
+
private ignoresHandled;
|
|
73
|
+
/**
|
|
74
|
+
Store per-file configs separately from base config to prevent unbounded array growth.
|
|
75
|
+
Key: file path, Value: config for that file.
|
|
76
|
+
This prevents memory bloat in long-running processes (e.g., language servers).
|
|
77
|
+
*/
|
|
78
|
+
private readonly fileConfigs;
|
|
79
|
+
/**
|
|
80
|
+
Track virtual/stdin files that share a single tsconfig.stdin.json.
|
|
81
|
+
These are handled differently from regular files.
|
|
82
|
+
*/
|
|
83
|
+
private readonly virtualFiles;
|
|
68
84
|
constructor(_linterOptions: LinterOptions, _baseXoConfig?: XoConfigOptions);
|
|
69
85
|
/**
|
|
70
86
|
Sets the XO config on the XO instance.
|
|
@@ -91,7 +107,7 @@ export declare class Xo {
|
|
|
91
107
|
*/
|
|
92
108
|
ensureCacheDirectory(): Promise<void>;
|
|
93
109
|
/**
|
|
94
|
-
Checks every TS file to ensure its included in the tsconfig and any that are not included are added to
|
|
110
|
+
Checks every TS file to ensure its included in the tsconfig and any that are not included are added to an in-memory TypeScript Program for type aware linting.
|
|
95
111
|
|
|
96
112
|
@param files - The TypeScript files being linted.
|
|
97
113
|
*/
|
|
@@ -113,6 +129,14 @@ export declare class Xo {
|
|
|
113
129
|
lintText(code: string, lintTextOptions: LintTextOptions): Promise<XoLintResult>;
|
|
114
130
|
calculateConfigForFile(filePath: string): Promise<Linter.Config>;
|
|
115
131
|
getFormatter(name: string): Promise<ESLint.LoadedFormatter>;
|
|
132
|
+
/**
|
|
133
|
+
Add virtual files to the config with a tsconfig approach.
|
|
134
|
+
*/
|
|
135
|
+
private addVirtualFilesToConfig;
|
|
136
|
+
/**
|
|
137
|
+
Add existing files to the config with an in-memory TypeScript Program.
|
|
138
|
+
*/
|
|
139
|
+
private addExistingFilesToConfig;
|
|
116
140
|
private processReport;
|
|
117
141
|
private getReportStatistics;
|
|
118
142
|
}
|
package/dist/lib/xo.js
CHANGED
|
@@ -1,18 +1,62 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
+
import syncFs from 'node:fs';
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import process from 'node:process';
|
|
5
6
|
import { ESLint } from 'eslint';
|
|
6
7
|
import findCacheDirectory from 'find-cache-directory';
|
|
7
|
-
import { globby } from 'globby';
|
|
8
|
+
import { globby, isDynamicPattern } from 'globby';
|
|
8
9
|
import arrify from 'arrify';
|
|
9
10
|
import defineLazyProperty from 'define-lazy-prop';
|
|
10
11
|
import prettier from 'prettier';
|
|
11
|
-
import { defaultIgnores, cacheDirName, allExtensions, tsFilesGlob, } from './constants.js';
|
|
12
|
+
import { defaultIgnores, cacheDirName, allExtensions, tsFilesGlob, tsconfigDefaults, } from './constants.js';
|
|
12
13
|
import { xoToEslintConfig } from './xo-to-eslint.js';
|
|
13
14
|
import resolveXoConfig from './resolve-config.js';
|
|
14
15
|
import { handleTsconfig } from './handle-ts-files.js';
|
|
15
|
-
import { matchFilesForTsConfig, preProcessXoConfig } from './utils.js';
|
|
16
|
+
import { matchFilesForTsConfig, preProcessXoConfig, typescriptParser, } from './utils.js';
|
|
17
|
+
export const ignoredFileWarningMessage = 'File ignored because of a matching ignore pattern.';
|
|
18
|
+
const createIgnoredLintResult = (filePath) => ({
|
|
19
|
+
filePath,
|
|
20
|
+
messages: [
|
|
21
|
+
{
|
|
22
|
+
ruleId: null,
|
|
23
|
+
severity: 1,
|
|
24
|
+
message: ignoredFileWarningMessage,
|
|
25
|
+
line: 0,
|
|
26
|
+
column: 0,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
suppressedMessages: [],
|
|
30
|
+
errorCount: 0,
|
|
31
|
+
fatalErrorCount: 0,
|
|
32
|
+
warningCount: 1,
|
|
33
|
+
fixableErrorCount: 0,
|
|
34
|
+
fixableWarningCount: 0,
|
|
35
|
+
usedDeprecatedRules: [],
|
|
36
|
+
});
|
|
37
|
+
const resolveExplicitFilePath = (cwd, glob) => {
|
|
38
|
+
if (isDynamicPattern(glob)) {
|
|
39
|
+
// Negated and wildcard globs are treated as regular glob filtering, not as explicit file paths that should trigger an ignored-file warning.
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const absolutePath = path.resolve(cwd, glob);
|
|
43
|
+
try {
|
|
44
|
+
if (syncFs.statSync(absolutePath).isFile()) {
|
|
45
|
+
return absolutePath;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// File does not exist or is inaccessible.
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
const getIgnoredExplicitFileResults = async (cwd, globs, eslint) => {
|
|
54
|
+
const explicitFilePaths = [...new Set(globs
|
|
55
|
+
.map(glob => resolveExplicitFilePath(cwd, glob))
|
|
56
|
+
.filter(filePath => filePath !== undefined))];
|
|
57
|
+
const results = await Promise.all(explicitFilePaths.map(async (filePath) => await eslint.isPathIgnored(filePath) ? createIgnoredLintResult(filePath) : undefined));
|
|
58
|
+
return results.filter(result => result !== undefined);
|
|
59
|
+
};
|
|
16
60
|
export class Xo {
|
|
17
61
|
/**
|
|
18
62
|
Static helper to convert an XO config to an ESLint config to be used in `eslint.config.js`.
|
|
@@ -113,6 +157,21 @@ export class Xo {
|
|
|
113
157
|
We use this to also add negative glob patterns in case a user overrides the parserOptions in their XO config.
|
|
114
158
|
*/
|
|
115
159
|
tsFilesIgnoresGlob = [];
|
|
160
|
+
/**
|
|
161
|
+
Track whether ignores have been added to prevent duplicate ignore configs.
|
|
162
|
+
*/
|
|
163
|
+
ignoresHandled = false;
|
|
164
|
+
/**
|
|
165
|
+
Store per-file configs separately from base config to prevent unbounded array growth.
|
|
166
|
+
Key: file path, Value: config for that file.
|
|
167
|
+
This prevents memory bloat in long-running processes (e.g., language servers).
|
|
168
|
+
*/
|
|
169
|
+
fileConfigs = new Map();
|
|
170
|
+
/**
|
|
171
|
+
Track virtual/stdin files that share a single tsconfig.stdin.json.
|
|
172
|
+
These are handled differently from regular files.
|
|
173
|
+
*/
|
|
174
|
+
virtualFiles = new Set();
|
|
116
175
|
constructor(_linterOptions, _baseXoConfig = {}) {
|
|
117
176
|
this.linterOptions = _linterOptions;
|
|
118
177
|
this.baseXoConfig = _baseXoConfig;
|
|
@@ -120,6 +179,12 @@ export class Xo {
|
|
|
120
179
|
if (!path.isAbsolute(this.linterOptions.cwd)) {
|
|
121
180
|
this.linterOptions.cwd = path.resolve(process.cwd(), this.linterOptions.cwd);
|
|
122
181
|
}
|
|
182
|
+
try {
|
|
183
|
+
this.linterOptions.cwd = syncFs.realpathSync.native(this.linterOptions.cwd);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Ignore invalid paths here; the caller will handle errors later.
|
|
187
|
+
}
|
|
123
188
|
const backupCacheLocation = path.join(os.tmpdir(), cacheDirName);
|
|
124
189
|
this.cacheLocation = findCacheDirectory({ name: cacheDirName, cwd: this.linterOptions.cwd }) ?? backupCacheLocation;
|
|
125
190
|
}
|
|
@@ -155,7 +220,12 @@ export class Xo {
|
|
|
155
220
|
if (!this.xoConfig) {
|
|
156
221
|
throw new Error('"Xo.setEslintConfig" failed');
|
|
157
222
|
}
|
|
158
|
-
|
|
223
|
+
// Combine base config with per-file configs from Map
|
|
224
|
+
// Deduplicate configs since multiple files can share the same config object
|
|
225
|
+
const uniqueFileConfigs = [...new Set(this.fileConfigs.values())];
|
|
226
|
+
const allConfigs = [...this.xoConfig, ...uniqueFileConfigs];
|
|
227
|
+
// Always regenerate to support instance reuse with new files
|
|
228
|
+
this.eslintConfig = xoToEslintConfig(allConfigs, { prettierOptions: this.prettierConfig });
|
|
159
229
|
}
|
|
160
230
|
/**
|
|
161
231
|
Sets the ignores on the XO instance.
|
|
@@ -163,7 +233,7 @@ export class Xo {
|
|
|
163
233
|
@private
|
|
164
234
|
*/
|
|
165
235
|
setIgnores() {
|
|
166
|
-
if (!this.baseXoConfig.ignores) {
|
|
236
|
+
if (this.ignoresHandled || !this.baseXoConfig.ignores) {
|
|
167
237
|
return;
|
|
168
238
|
}
|
|
169
239
|
let ignores = [];
|
|
@@ -180,6 +250,7 @@ export class Xo {
|
|
|
180
250
|
return;
|
|
181
251
|
}
|
|
182
252
|
this.xoConfig.push({ ignores });
|
|
253
|
+
this.ignoresHandled = true;
|
|
183
254
|
}
|
|
184
255
|
/**
|
|
185
256
|
Ensures the cache directory exists. This needs to run once before both tsconfig handling and running ESLint occur.
|
|
@@ -201,7 +272,7 @@ export class Xo {
|
|
|
201
272
|
}
|
|
202
273
|
}
|
|
203
274
|
/**
|
|
204
|
-
Checks every TS file to ensure its included in the tsconfig and any that are not included are added to
|
|
275
|
+
Checks every TS file to ensure its included in the tsconfig and any that are not included are added to an in-memory TypeScript Program for type aware linting.
|
|
205
276
|
|
|
206
277
|
@param files - The TypeScript files being linted.
|
|
207
278
|
*/
|
|
@@ -209,25 +280,25 @@ export class Xo {
|
|
|
209
280
|
if (!this.linterOptions.ts || !files || files.length === 0) {
|
|
210
281
|
return;
|
|
211
282
|
}
|
|
212
|
-
|
|
213
|
-
|
|
283
|
+
// Get ALL TypeScript files being linted (both new and previously handled)
|
|
284
|
+
const allTsFiles = matchFilesForTsConfig(this.linterOptions.cwd, files, this.tsFilesGlob, this.tsFilesIgnoresGlob);
|
|
285
|
+
if (allTsFiles.length === 0) {
|
|
286
|
+
this.fileConfigs.clear();
|
|
287
|
+
if (this.virtualFiles.size > 0) {
|
|
288
|
+
await this.addVirtualFilesToConfig([]);
|
|
289
|
+
}
|
|
214
290
|
return;
|
|
215
291
|
}
|
|
216
|
-
const {
|
|
292
|
+
const { program, existingFiles, virtualFiles } = handleTsconfig({
|
|
293
|
+
files: allTsFiles,
|
|
217
294
|
cwd: this.linterOptions.cwd,
|
|
218
|
-
|
|
295
|
+
cacheLocation: this.cacheLocation,
|
|
219
296
|
});
|
|
220
|
-
|
|
221
|
-
|
|
297
|
+
this.fileConfigs.clear();
|
|
298
|
+
if (existingFiles.length > 0) {
|
|
299
|
+
this.addExistingFilesToConfig(existingFiles, program);
|
|
222
300
|
}
|
|
223
|
-
|
|
224
|
-
config.files = unincludedFiles.map(file => path.relative(this.linterOptions.cwd, file));
|
|
225
|
-
config.languageOptions ??= {};
|
|
226
|
-
config.languageOptions.parserOptions ??= {};
|
|
227
|
-
config.languageOptions.parserOptions['projectService'] = false;
|
|
228
|
-
config.languageOptions.parserOptions['project'] = fallbackTsConfigPath;
|
|
229
|
-
config.languageOptions.parserOptions['tsconfigRootDir'] = this.linterOptions.cwd;
|
|
230
|
-
this.xoConfig.push(config);
|
|
301
|
+
await this.addVirtualFilesToConfig(virtualFiles);
|
|
231
302
|
}
|
|
232
303
|
/**
|
|
233
304
|
Initializes the ESLint instance on the XO instance.
|
|
@@ -249,9 +320,12 @@ export class Xo {
|
|
|
249
320
|
warnIgnored: false,
|
|
250
321
|
cache: true,
|
|
251
322
|
cacheLocation: this.cacheLocation,
|
|
323
|
+
cacheStrategy: 'content',
|
|
252
324
|
fix: this.linterOptions.fix,
|
|
253
325
|
};
|
|
254
|
-
|
|
326
|
+
// Always create new instance to support reuse with updated config
|
|
327
|
+
// ESLint's file-based cache (cacheLocation) persists across instances
|
|
328
|
+
this.eslint = new ESLint(eslintOptions);
|
|
255
329
|
}
|
|
256
330
|
/**
|
|
257
331
|
Lints the files on the XO instance.
|
|
@@ -264,24 +338,28 @@ export class Xo {
|
|
|
264
338
|
globs = `**/*.{${allExtensions.join(',')}}`;
|
|
265
339
|
}
|
|
266
340
|
globs = arrify(globs);
|
|
267
|
-
|
|
341
|
+
const files = await globby(globs, {
|
|
268
342
|
// Merge in command line ignores
|
|
269
343
|
ignore: [...defaultIgnores, ...arrify(this.baseXoConfig.ignores)],
|
|
270
344
|
onlyFiles: true,
|
|
271
345
|
gitignore: true,
|
|
272
346
|
absolute: true,
|
|
347
|
+
dot: true,
|
|
273
348
|
cwd: this.linterOptions.cwd,
|
|
274
349
|
});
|
|
275
350
|
await this.initEslint(files);
|
|
276
351
|
if (!this.eslint) {
|
|
277
352
|
throw new Error('Failed to initialize ESLint');
|
|
278
353
|
}
|
|
354
|
+
const { eslint } = this;
|
|
355
|
+
const ignoredResults = await getIgnoredExplicitFileResults(this.linterOptions.cwd, globs, eslint);
|
|
279
356
|
if (files.length === 0) {
|
|
280
|
-
|
|
357
|
+
return this.processReport(ignoredResults);
|
|
281
358
|
}
|
|
282
|
-
const results = await
|
|
283
|
-
const rulesMeta =
|
|
284
|
-
|
|
359
|
+
const results = await eslint.lintFiles(files);
|
|
360
|
+
const rulesMeta = eslint.getRulesMetaForResults(results);
|
|
361
|
+
// No overlap: `warnIgnored: false` makes ESLint silently drop ignored files from `results`.
|
|
362
|
+
return this.processReport([...results, ...ignoredResults], { rulesMeta });
|
|
285
363
|
}
|
|
286
364
|
/**
|
|
287
365
|
Lints the text on the XO instance.
|
|
@@ -304,6 +382,7 @@ export class Xo {
|
|
|
304
382
|
if (!this.eslint) {
|
|
305
383
|
throw new Error('Failed to initialize ESLint');
|
|
306
384
|
}
|
|
385
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
307
386
|
return this.eslint.calculateConfigForFile(filePath);
|
|
308
387
|
}
|
|
309
388
|
async getFormatter(name) {
|
|
@@ -313,6 +392,102 @@ export class Xo {
|
|
|
313
392
|
}
|
|
314
393
|
return this.eslint.loadFormatter(name);
|
|
315
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
Add virtual files to the config with a tsconfig approach.
|
|
397
|
+
*/
|
|
398
|
+
async addVirtualFilesToConfig(files) {
|
|
399
|
+
if (!this.xoConfig) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
const nextVirtualFiles = new Set(files);
|
|
404
|
+
const tsconfigPath = path.join(this.cacheLocation, 'tsconfig.stdin.json');
|
|
405
|
+
const configIndex = this.xoConfig.findIndex(configItem => {
|
|
406
|
+
const { languageOptions } = configItem;
|
|
407
|
+
const parserOptionsCandidate = languageOptions?.parserOptions;
|
|
408
|
+
const parserOptions = parserOptionsCandidate;
|
|
409
|
+
return parserOptions?.project === tsconfigPath;
|
|
410
|
+
});
|
|
411
|
+
if (nextVirtualFiles.size > 0) {
|
|
412
|
+
const filesArray = [...nextVirtualFiles];
|
|
413
|
+
const relativeFiles = filesArray.map(file => path.relative(this.linterOptions.cwd, file));
|
|
414
|
+
const tsconfigContent = {
|
|
415
|
+
compilerOptions: {
|
|
416
|
+
...tsconfigDefaults.compilerOptions,
|
|
417
|
+
module: 'ESNext',
|
|
418
|
+
moduleResolution: 'NodeNext',
|
|
419
|
+
esModuleInterop: true,
|
|
420
|
+
skipLibCheck: true,
|
|
421
|
+
},
|
|
422
|
+
files: filesArray,
|
|
423
|
+
};
|
|
424
|
+
await fs.writeFile(tsconfigPath, JSON.stringify(tsconfigContent, null, 2));
|
|
425
|
+
if (configIndex === -1) {
|
|
426
|
+
const parserOptions = {
|
|
427
|
+
projectService: false,
|
|
428
|
+
project: tsconfigPath,
|
|
429
|
+
tsconfigRootDir: this.linterOptions.cwd,
|
|
430
|
+
};
|
|
431
|
+
this.xoConfig.push({
|
|
432
|
+
files: relativeFiles,
|
|
433
|
+
languageOptions: {
|
|
434
|
+
parser: typescriptParser,
|
|
435
|
+
parserOptions,
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const existingConfig = this.xoConfig[configIndex];
|
|
441
|
+
this.xoConfig[configIndex] = {
|
|
442
|
+
...existingConfig,
|
|
443
|
+
files: relativeFiles,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
this.virtualFiles.clear();
|
|
447
|
+
for (const file of nextVirtualFiles) {
|
|
448
|
+
this.virtualFiles.add(file);
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (configIndex >= 0) {
|
|
453
|
+
this.xoConfig.splice(configIndex, 1);
|
|
454
|
+
}
|
|
455
|
+
this.virtualFiles.clear();
|
|
456
|
+
await fs.rm(tsconfigPath, { force: true });
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
console.warn('XO: Failed to create tsconfig for virtual files. Type-aware linting will be disabled for these files.', error instanceof Error ? error.message : String(error));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
Add existing files to the config with an in-memory TypeScript Program.
|
|
464
|
+
*/
|
|
465
|
+
addExistingFilesToConfig(files, program) {
|
|
466
|
+
if (!this.xoConfig || files.length === 0) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const parserOptions = {
|
|
470
|
+
project: false,
|
|
471
|
+
projectService: false,
|
|
472
|
+
};
|
|
473
|
+
if (program) {
|
|
474
|
+
parserOptions.programs = [program];
|
|
475
|
+
}
|
|
476
|
+
const config = {
|
|
477
|
+
files: files.map(file => path.relative(this.linterOptions.cwd, file)),
|
|
478
|
+
languageOptions: {
|
|
479
|
+
parser: typescriptParser,
|
|
480
|
+
parserOptions,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
// IMPORTANT: All files intentionally share the same config object reference for memory efficiency.
|
|
484
|
+
// This prevents unbounded memory growth in long-running processes (e.g., language servers).
|
|
485
|
+
// The config is immutable after creation, so sharing is safe.
|
|
486
|
+
// Deduplication happens in setEslintConfig() via Set to avoid duplicate configs in the final array.
|
|
487
|
+
for (const file of files) {
|
|
488
|
+
this.fileConfigs.set(file, config);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
316
491
|
processReport(report, { rulesMeta = {} } = {}) {
|
|
317
492
|
if (this.linterOptions.quiet) {
|
|
318
493
|
report = ESLint.getErrorResults(report);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xo",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "JavaScript/TypeScript linter (ESLint wrapper) with great defaults",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "xojs/xo",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"sideEffects": false,
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=20.
|
|
21
|
+
"node": ">=20.19"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "npm run clean && tsc",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist/lib/*.js",
|
|
35
35
|
"dist/lib/*.d.ts",
|
|
36
|
+
"dist/lib/rules/*.js",
|
|
37
|
+
"dist/lib/rules/*.d.ts",
|
|
36
38
|
"dist/*.js",
|
|
37
39
|
"dist/*.d.ts"
|
|
38
40
|
],
|
|
@@ -65,53 +67,51 @@
|
|
|
65
67
|
"typescript"
|
|
66
68
|
],
|
|
67
69
|
"dependencies": {
|
|
68
|
-
"@eslint-community/eslint-plugin-eslint-comments": "^4.
|
|
69
|
-
"@
|
|
70
|
-
"@
|
|
71
|
-
"@typescript-eslint/parser": "^8.37.0",
|
|
70
|
+
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.1",
|
|
71
|
+
"@eslint/compat": "^2.0.2",
|
|
72
|
+
"@sindresorhus/tsconfig": "^8.1.0",
|
|
72
73
|
"arrify": "^3.0.0",
|
|
73
74
|
"cosmiconfig": "^9.0.0",
|
|
74
75
|
"define-lazy-prop": "^3.0.0",
|
|
75
|
-
"eslint": "^
|
|
76
|
-
"eslint-config-prettier": "^10.1.
|
|
77
|
-
"eslint-config-xo-react": "^0.
|
|
78
|
-
"eslint-config-xo-typescript": "^
|
|
79
|
-
"eslint-formatter-pretty": "^
|
|
80
|
-
"eslint-plugin-ava": "^
|
|
76
|
+
"eslint": "^10.0.2",
|
|
77
|
+
"eslint-config-prettier": "^10.1.8",
|
|
78
|
+
"eslint-config-xo-react": "^0.29.0",
|
|
79
|
+
"eslint-config-xo-typescript": "^10.0.0",
|
|
80
|
+
"eslint-formatter-pretty": "^7.0.0",
|
|
81
|
+
"eslint-plugin-ava": "^16.0.0",
|
|
81
82
|
"eslint-plugin-import-x": "^4.16.1",
|
|
82
|
-
"eslint-plugin-n": "^17.
|
|
83
|
-
"eslint-plugin-
|
|
84
|
-
"eslint-plugin-
|
|
85
|
-
"eslint-plugin-promise": "^7.2.1",
|
|
86
|
-
"eslint-plugin-unicorn": "^59.0.1",
|
|
83
|
+
"eslint-plugin-n": "^17.24.0",
|
|
84
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
85
|
+
"eslint-plugin-unicorn": "^63.0.0",
|
|
87
86
|
"find-cache-directory": "^6.0.0",
|
|
88
|
-
"get-stdin": "^
|
|
89
|
-
"get-tsconfig": "^4.
|
|
90
|
-
"globals": "^
|
|
91
|
-
"globby": "^
|
|
92
|
-
"meow": "^
|
|
87
|
+
"get-stdin": "^10.0.0",
|
|
88
|
+
"get-tsconfig": "^4.13.6",
|
|
89
|
+
"globals": "^17.3.0",
|
|
90
|
+
"globby": "^16.1.1",
|
|
91
|
+
"meow": "^14.1.0",
|
|
93
92
|
"micromatch": "^4.0.8",
|
|
94
|
-
"open-editor": "^
|
|
93
|
+
"open-editor": "^6.0.0",
|
|
95
94
|
"path-exists": "^5.0.0",
|
|
96
|
-
"prettier": "^3.
|
|
97
|
-
"type-fest": "^4.
|
|
98
|
-
"typescript
|
|
95
|
+
"prettier": "^3.8.1",
|
|
96
|
+
"type-fest": "^5.4.3",
|
|
97
|
+
"typescript": "^5.9.3",
|
|
98
|
+
"typescript-eslint": "^8.56.1"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
|
-
"@commitlint/cli": "^
|
|
102
|
-
"@commitlint/config-conventional": "^
|
|
103
|
-
"@types/
|
|
104
|
-
"@types/
|
|
101
|
+
"@commitlint/cli": "^20.4.1",
|
|
102
|
+
"@commitlint/config-conventional": "^20.4.1",
|
|
103
|
+
"@types/micromatch": "^4.0.10",
|
|
104
|
+
"@types/node": "^25.2.1",
|
|
105
105
|
"@types/prettier": "^3.0.0",
|
|
106
|
-
"ava": "^
|
|
107
|
-
"dedent": "^1.
|
|
108
|
-
"execa": "^9.
|
|
106
|
+
"ava": "^7.0.0",
|
|
107
|
+
"dedent": "^1.7.1",
|
|
108
|
+
"execa": "^9.6.1",
|
|
109
109
|
"husky": "^9.1.7",
|
|
110
|
-
"lint-staged": "^16.
|
|
111
|
-
"np": "^
|
|
112
|
-
"npm-package-json-lint": "^9.
|
|
110
|
+
"lint-staged": "^16.3.4",
|
|
111
|
+
"np": "^11.0.2",
|
|
112
|
+
"npm-package-json-lint": "^9.1.0",
|
|
113
113
|
"npm-package-json-lint-config-default": "^8.0.1",
|
|
114
|
-
"prettier-plugin-packagejson": "^
|
|
114
|
+
"prettier-plugin-packagejson": "^3.0.0",
|
|
115
115
|
"temp-dir": "^3.0.0",
|
|
116
116
|
"xo": "file:."
|
|
117
117
|
},
|
|
@@ -143,5 +143,10 @@
|
|
|
143
143
|
"text",
|
|
144
144
|
"lcov"
|
|
145
145
|
]
|
|
146
|
+
},
|
|
147
|
+
"xo": {
|
|
148
|
+
"rules": {
|
|
149
|
+
"ava/no-ignored-test-files": "off"
|
|
150
|
+
}
|
|
146
151
|
}
|
|
147
152
|
}
|
package/readme.md
CHANGED
|
@@ -27,7 +27,7 @@ It uses [ESLint](https://eslint.org) underneath, so issues regarding built-in ru
|
|
|
27
27
|
- No need to specify file paths to lint as it lints all JS/TS files except for [commonly ignored paths](#ignores).
|
|
28
28
|
- [Flat config customization.](#config)
|
|
29
29
|
- [TypeScript supported by default.](#typescript)
|
|
30
|
-
- Includes many useful ESLint plugins, like [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn), [`import`](https://github.com/
|
|
30
|
+
- Includes many useful ESLint plugins, like [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn), [`import-x`](https://github.com/un-ts/eslint-plugin-import-x), [`ava`](https://github.com/avajs/eslint-plugin-ava), [`n`](https://github.com/eslint-community/eslint-plugin-n) and more.
|
|
31
31
|
- Caches results between runs for much better performance.
|
|
32
32
|
- Super simple to add XO to a project with [`$ npm init xo`](https://github.com/xojs/create-xo).
|
|
33
33
|
- Fix many issues automagically with `$ xo --fix`.
|
|
@@ -46,7 +46,7 @@ npm install xo --save-dev
|
|
|
46
46
|
|
|
47
47
|
*You must install XO locally. You can run it directly with `$ npx xo`.*
|
|
48
48
|
|
|
49
|
-
*
|
|
49
|
+
*For framework-specific linting, see [Astro](#astro), [Svelte](#svelte), and [Vue](#vue).*
|
|
50
50
|
|
|
51
51
|
## Usage
|
|
52
52
|
|
|
@@ -58,6 +58,7 @@ $ xo --help
|
|
|
58
58
|
|
|
59
59
|
Options
|
|
60
60
|
--fix Automagically fix issues
|
|
61
|
+
--fix-dry-run Automagically fix issues without saving the changes to the file system
|
|
61
62
|
--reporter Reporter to use
|
|
62
63
|
--space Use space indent instead of tabs [Default: 2]
|
|
63
64
|
--config Path to a XO configuration file
|
|
@@ -136,16 +137,16 @@ export default [...] satisfies import('xo').FlatXoConfig
|
|
|
136
137
|
|
|
137
138
|
### files
|
|
138
139
|
|
|
139
|
-
Type: `string | string
|
|
140
|
+
Type: `string | (string | string[])[]`\
|
|
140
141
|
Default: `**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,vue,svelte,astro}`
|
|
141
142
|
|
|
142
|
-
A glob
|
|
143
|
+
A glob string, array of globs, or ESLint's native format (where nested arrays create AND patterns) indicating which files the config object applies to. By default `XO` will apply the configuration to [all files](lib/constants.ts). This is compatible with ESLint plugin configs, so you can spread them directly into your XO config.
|
|
143
144
|
|
|
144
145
|
> Tip: If you are adding additional `@typescript-eslint` rules to your config, these rules will apply to JS files as well unless you separate them appropriately with the `files` option. `@typescript-eslint` rules set to `'off'` or `0`, however, will have no effect on JS linting.
|
|
145
146
|
|
|
146
147
|
### ignores
|
|
147
148
|
|
|
148
|
-
Type: `string[]`
|
|
149
|
+
Type: `string | string[]`
|
|
149
150
|
|
|
150
151
|
Some [paths](lib/constants.ts) are ignored by default, including paths in `.gitignore`. Additional ignores can be added here.
|
|
151
152
|
|
|
@@ -204,6 +205,66 @@ Default: `false`
|
|
|
204
205
|
|
|
205
206
|
Adds `eslint-plugin-react`, `eslint-plugin-react-hooks`, and `eslint-config-xo-react` to get all the React best practices applied automatically.
|
|
206
207
|
|
|
208
|
+
### Astro
|
|
209
|
+
|
|
210
|
+
To lint [Astro](https://astro.build) files, install [`eslint-plugin-astro`](https://github.com/ota-meshi/eslint-plugin-astro):
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
npm install --save-dev eslint-plugin-astro
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Then spread its recommended config in your `xo.config.js`:
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
import astroPlugin from 'eslint-plugin-astro';
|
|
220
|
+
|
|
221
|
+
const xoConfig = [
|
|
222
|
+
...astroPlugin.configs.recommended,
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
export default xoConfig;
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Svelte
|
|
229
|
+
|
|
230
|
+
To lint [Svelte](https://svelte.dev) files, install [`eslint-plugin-svelte`](https://github.com/sveltejs/eslint-plugin-svelte):
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
npm install --save-dev eslint-plugin-svelte
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Then spread its recommended config in your `xo.config.js`:
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
import sveltePlugin from 'eslint-plugin-svelte';
|
|
240
|
+
|
|
241
|
+
const xoConfig = [
|
|
242
|
+
...sveltePlugin.configs.recommended,
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
export default xoConfig;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Vue
|
|
249
|
+
|
|
250
|
+
To lint [Vue](https://vuejs.org) files, install [`eslint-plugin-vue`](https://github.com/vuejs/eslint-plugin-vue):
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
npm install --save-dev eslint-plugin-vue
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Then spread its recommended config in your `xo.config.js`:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
import vuePlugin from 'eslint-plugin-vue';
|
|
260
|
+
|
|
261
|
+
const xoConfig = [
|
|
262
|
+
...vuePlugin.configs['flat/recommended'],
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
export default xoConfig;
|
|
266
|
+
```
|
|
267
|
+
|
|
207
268
|
## TypeScript
|
|
208
269
|
|
|
209
270
|
XO will automatically lint TypeScript files (`.ts`, `.mts`, `.cts`, and `.tsx`) with the rules defined in [eslint-config-xo-typescript#use-with-xo](https://github.com/xojs/eslint-config-xo-typescript#use-with-xo).
|
|
@@ -306,6 +367,12 @@ Show the world you're using XO → [](https://github.com/xojs/xo)
|
|
307
368
|
```
|
|
308
369
|
|
|
370
|
+
Large badge: [](https://github.com/xojs/xo)
|
|
371
|
+
|
|
372
|
+
```md
|
|
373
|
+
[](https://github.com/xojs/xo)
|
|
374
|
+
```
|
|
375
|
+
|
|
309
376
|
Or [customize the badge](https://github.com/xojs/xo/issues/689#issuecomment-1253127616).
|
|
310
377
|
|
|
311
378
|
You can also find some nice dynamic XO badges on [badgen.net](https://badgen.net/#xo).
|