xo 2.0.2 → 3.0.0

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 CHANGED
@@ -1,61 +1,2 @@
1
1
  #!/usr/bin/env node
2
- declare const cli: import("meow").Result<{
3
- fix: {
4
- type: "boolean";
5
- default: false;
6
- };
7
- fixDryRun: {
8
- type: "boolean";
9
- default: false;
10
- };
11
- reporter: {
12
- type: "string";
13
- };
14
- space: {
15
- type: "string";
16
- };
17
- configPath: {
18
- type: "string";
19
- aliases: string[];
20
- };
21
- quiet: {
22
- type: "boolean";
23
- };
24
- semicolon: {
25
- type: "boolean";
26
- };
27
- prettier: {
28
- type: "boolean";
29
- };
30
- react: {
31
- type: "boolean";
32
- default: false;
33
- };
34
- cwd: {
35
- type: "string";
36
- default: string;
37
- };
38
- printConfig: {
39
- type: "string";
40
- };
41
- version: {
42
- type: "boolean";
43
- };
44
- stdin: {
45
- type: "boolean";
46
- };
47
- stdinFilename: {
48
- type: "string";
49
- default: string;
50
- };
51
- open: {
52
- type: "boolean";
53
- };
54
- ignore: {
55
- type: "string";
56
- isMultiple: true;
57
- aliases: string[];
58
- };
59
- }>;
60
- export type CliOptions = typeof cli;
61
2
  export {};
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs/promises';
4
4
  import process from 'node:process';
5
+ import { ESLint } from 'eslint';
5
6
  import formatterPretty from 'eslint-formatter-pretty';
6
7
  import getStdin from 'get-stdin';
7
8
  import meow from 'meow';
@@ -10,27 +11,29 @@ import findCacheDirectory from 'find-cache-directory';
10
11
  import { cacheDirName, tsExtensions } from './lib/constants.js';
11
12
  import { Xo } from './lib/xo.js';
12
13
  import openReport from './lib/open-report.js';
14
+ const isErrorWithExitCode = (error) => error instanceof Error && 'exitCode' in error && typeof error.exitCode === 'number';
13
15
  const cli = meow(`
14
16
  Usage
15
17
  $ xo [<file|glob> ...]
16
18
 
17
19
  Options
18
- --fix Automagically fix issues
19
- --fix-dry-run Automagically fix issues without saving the changes to the file system
20
- --reporter Reporter to use
21
- --space Use space indent instead of tabs [Default: 2]
22
- --config Path to a XO configuration file
23
- --semicolon Use semicolons [Default: true]
24
- --react Include React specific parsing and xo-react linting rules [Default: false]
25
- --prettier Format with prettier or turn off Prettier-conflicted rules when set to 'compat' [Default: false]
26
- --print-config Print the effective ESLint config for the given file
27
- --version Print XO version
28
- --open Open files with issues in your editor
29
- --quiet Show only errors and no warnings
30
- --stdin Validate/fix code from stdin
31
- --stdin-filename Specify a filename for the --stdin option
32
- --ignore Ignore pattern globs, can be set multiple times
33
- --cwd=<dir> Working directory for files [Default: process.cwd()]
20
+ --fix Automagically fix issues
21
+ --fix-dry-run Automagically fix issues without saving the changes to the file system
22
+ --reporter Reporter to use
23
+ --space Use space indent instead of tabs [Default: 2]
24
+ --config Path to a XO configuration file
25
+ --semicolon Use semicolons [Default: true]
26
+ --prettier Format with prettier or turn off Prettier-conflicted rules when set to 'compat' [Default: false]
27
+ --print-config Print the effective ESLint config for the given file
28
+ --version Print XO version
29
+ --open Open files with issues in your editor
30
+ --quiet Show only errors and no warnings
31
+ --max-warnings Number of warnings to trigger nonzero exit code [Default: -1]
32
+ --stdin Validate/fix code from stdin
33
+ --stdin-filename Specify a filename for the --stdin option
34
+ --ignore Ignore pattern globs, can be set multiple times
35
+ --suppressions-location Path to a custom ESLint suppressions file
36
+ --cwd=<dir> Working directory for files [Default: process.cwd()]
34
37
 
35
38
  Examples
36
39
  $ xo
@@ -70,10 +73,6 @@ const cli = meow(`
70
73
  prettier: {
71
74
  type: 'boolean',
72
75
  },
73
- react: {
74
- type: 'boolean',
75
- default: false,
76
- },
77
76
  cwd: {
78
77
  type: 'string',
79
78
  default: process.cwd(),
@@ -99,6 +98,13 @@ const cli = meow(`
99
98
  isMultiple: true,
100
99
  aliases: ['ignores'],
101
100
  },
101
+ suppressionsLocation: {
102
+ type: 'string',
103
+ },
104
+ maxWarnings: {
105
+ type: 'number',
106
+ default: -1,
107
+ },
102
108
  },
103
109
  });
104
110
  const { input, flags: cliOptions, showVersion } = cli;
@@ -107,20 +113,20 @@ const baseXoConfigOptions = {
107
113
  semicolon: cliOptions.semicolon,
108
114
  prettier: cliOptions.prettier,
109
115
  ignores: cliOptions.ignore,
110
- react: cliOptions.react,
111
116
  };
112
117
  const linterOptions = {
113
118
  fix: cliOptions.fix || cliOptions.fixDryRun,
114
- cwd: (cliOptions.cwd && path.resolve(cliOptions.cwd)) ?? process.cwd(),
119
+ cwd: cliOptions.cwd === '' ? process.cwd() : path.resolve(cliOptions.cwd),
115
120
  quiet: cliOptions.quiet,
116
121
  ts: true,
117
122
  configPath: cliOptions.configPath,
123
+ suppressionsLocation: cliOptions.suppressionsLocation,
118
124
  };
119
125
  // Make data types for `options.space` match those of the API
120
126
  if (typeof cliOptions.space === 'string') {
121
127
  cliOptions.space = cliOptions.space.trim();
122
128
  if (/^\d+$/v.test(cliOptions.space)) {
123
- baseXoConfigOptions.space = Number.parseInt(cliOptions.space, 10);
129
+ baseXoConfigOptions.space = Number(cliOptions.space);
124
130
  }
125
131
  else if (cliOptions.space === 'true') {
126
132
  baseXoConfigOptions.space = true;
@@ -136,86 +142,131 @@ if (typeof cliOptions.space === 'string') {
136
142
  baseXoConfigOptions.space = true;
137
143
  }
138
144
  }
139
- if (process.env['GITHUB_ACTIONS']
145
+ const isGitHubActions = Boolean(process.env['GITHUB_ACTIONS']);
146
+ if (isGitHubActions
140
147
  && !linterOptions.fix
141
- && !cliOptions.reporter) {
148
+ && cliOptions.reporter === undefined) {
142
149
  linterOptions.quiet = true;
143
150
  }
144
- const log = async (report) => {
145
- const reporter = cliOptions.reporter
146
- ? await new Xo(linterOptions, baseXoConfigOptions).getFormatter(cliOptions.reporter)
147
- : { format: formatterPretty };
148
- // @ts-expect-error the types don't quite match up here
149
- console.log(reporter.format(report.results, { cwd: linterOptions.cwd, ...report }));
151
+ // --max-warnings needs warning counts, which --quiet filters out
152
+ if (cliOptions.maxWarnings >= 0) {
153
+ linterOptions.quiet = false;
154
+ }
155
+ const log = async (report, xo) => {
156
+ const reporterName = cliOptions.reporter;
157
+ const shouldUsePrettyReporter = reporterName === undefined;
158
+ // Hide warnings when there are errors to reduce noise in the default human-readable output.
159
+ const results = shouldUsePrettyReporter && report.errorCount > 0 && cliOptions.maxWarnings < 0
160
+ ? ESLint.getErrorResults(report.results)
161
+ : report.results;
162
+ if (shouldUsePrettyReporter) {
163
+ const counts = {
164
+ errorCount: 0,
165
+ warningCount: 0,
166
+ fixableErrorCount: 0,
167
+ fixableWarningCount: 0,
168
+ };
169
+ for (const result of results) {
170
+ counts.errorCount += result.errorCount;
171
+ counts.warningCount += result.warningCount;
172
+ counts.fixableErrorCount += result.fixableErrorCount;
173
+ counts.fixableWarningCount += result.fixableWarningCount;
174
+ }
175
+ const formatterMetadata = {
176
+ cwd: linterOptions.cwd,
177
+ ...report,
178
+ ...counts,
179
+ results,
180
+ };
181
+ console.log(formatterPretty(results, formatterMetadata));
182
+ }
183
+ else {
184
+ const reporter = await xo.getFormatter(reporterName);
185
+ console.log(await reporter.format(results));
186
+ }
150
187
  process.exitCode = report.errorCount === 0 ? 0 : 1;
188
+ if (cliOptions.maxWarnings >= 0 && report.warningCount > cliOptions.maxWarnings) {
189
+ console.error(`XO found too many warnings (maximum: ${cliOptions.maxWarnings}).`);
190
+ process.exitCode = 1;
191
+ }
151
192
  };
152
- if (cliOptions.version) {
153
- showVersion();
154
- }
155
- if (cliOptions.stdin) {
156
- const stdin = await getStdin();
157
- let shouldRemoveStdInFile = false;
158
- // For TypeScript, we need a file on the filesystem to lint it or else @typescript-eslint will blow up.
159
- // We create a temporary file in the node_modules/.cache/xo-linter directory to avoid conflicts with the user's files and lint that file as if it were the stdin input as a work around.
160
- // We clean up the file after linting.
161
- if (cliOptions.stdinFilename && tsExtensions.includes(path.extname(cliOptions.stdinFilename).slice(1))) {
162
- const absoluteFilePath = path.resolve(cliOptions.cwd, cliOptions.stdinFilename);
163
- if (!await pathExists(absoluteFilePath)) {
164
- const cacheDir = findCacheDirectory({ name: cacheDirName, cwd: linterOptions.cwd }) ?? path.join(cliOptions.cwd, 'node_modules', '.cache', cacheDirName);
165
- cliOptions.stdinFilename = path.join(cacheDir, path.basename(absoluteFilePath));
166
- shouldRemoveStdInFile = true;
167
- baseXoConfigOptions.ignores = [
168
- '!**/node_modules/**',
169
- '!node_modules/**',
170
- '!node_modules/',
171
- `!${path.relative(cliOptions.cwd, cliOptions.stdinFilename)}`,
172
- ];
173
- if (!await pathExists(path.dirname(cliOptions.stdinFilename))) {
174
- await fs.mkdir(path.dirname(cliOptions.stdinFilename), { recursive: true });
193
+ try {
194
+ if (cliOptions.version) {
195
+ showVersion();
196
+ }
197
+ if (cliOptions.stdin) {
198
+ const stdin = await getStdin();
199
+ let shouldRemoveStdInFile = false;
200
+ // For TypeScript, we need a file on the filesystem to lint it or else @typescript-eslint will blow up.
201
+ // We create a temporary file in the node_modules/.cache/xo-linter directory to avoid conflicts with the user's files and lint that file as if it were the stdin input as a work around.
202
+ // We clean up the file after linting.
203
+ if (cliOptions.stdinFilename !== '' && tsExtensions.includes(path.extname(cliOptions.stdinFilename).slice(1))) {
204
+ const absoluteFilePath = path.resolve(cliOptions.cwd, cliOptions.stdinFilename);
205
+ if (!await pathExists(absoluteFilePath)) {
206
+ const cacheDir = findCacheDirectory({ name: cacheDirName, cwd: linterOptions.cwd }) ?? path.join(cliOptions.cwd, 'node_modules', '.cache', cacheDirName);
207
+ cliOptions.stdinFilename = path.join(cacheDir, path.basename(absoluteFilePath));
208
+ shouldRemoveStdInFile = true;
209
+ baseXoConfigOptions.ignores = [
210
+ '!**/node_modules/**',
211
+ '!node_modules/**',
212
+ '!node_modules/',
213
+ `!${path.relative(cliOptions.cwd, cliOptions.stdinFilename)}`,
214
+ ];
215
+ if (!await pathExists(path.dirname(cliOptions.stdinFilename))) {
216
+ await fs.mkdir(path.dirname(cliOptions.stdinFilename), { recursive: true });
217
+ }
218
+ await fs.writeFile(cliOptions.stdinFilename, stdin);
175
219
  }
176
- await fs.writeFile(cliOptions.stdinFilename, stdin);
177
220
  }
178
- }
179
- if (linterOptions.fix) {
221
+ if (linterOptions.fix) {
222
+ const xo = new Xo(linterOptions, baseXoConfigOptions);
223
+ const { results } = await xo.lintText(stdin, {
224
+ filePath: cliOptions.stdinFilename,
225
+ });
226
+ const [result] = results;
227
+ process.stdout.write((result?.output) ?? stdin);
228
+ process.exit(0);
229
+ }
230
+ if (cliOptions.open) {
231
+ console.error('The `--open` flag is not supported on stdin');
232
+ if (shouldRemoveStdInFile) {
233
+ await fs.rm(cliOptions.stdinFilename);
234
+ }
235
+ process.exit(1);
236
+ }
180
237
  const xo = new Xo(linterOptions, baseXoConfigOptions);
181
- const { results: [result] } = await xo.lintText(stdin, {
182
- filePath: cliOptions.stdinFilename,
183
- });
184
- process.stdout.write((result?.output) ?? stdin);
185
- process.exit(0);
186
- }
187
- if (cliOptions.open) {
188
- console.error('The `--open` flag is not supported on stdin');
238
+ await log(await xo.lintText(stdin, { filePath: cliOptions.stdinFilename, warnIgnored: false }), xo);
189
239
  if (shouldRemoveStdInFile) {
190
240
  await fs.rm(cliOptions.stdinFilename);
191
241
  }
192
- process.exit(1);
242
+ process.exit(0);
193
243
  }
194
- const xo = new Xo(linterOptions, baseXoConfigOptions);
195
- await log(await xo.lintText(stdin, { filePath: cliOptions.stdinFilename, warnIgnored: false }));
196
- if (shouldRemoveStdInFile) {
197
- await fs.rm(cliOptions.stdinFilename);
244
+ if (typeof cliOptions.printConfig === 'string') {
245
+ if (input.length > 0 || cliOptions.printConfig === '') {
246
+ console.error('The `--print-config` flag must be used with exactly one filename');
247
+ process.exit(1);
248
+ }
249
+ const absoluteFilePath = path.resolve(cliOptions.cwd, cliOptions.printConfig);
250
+ const config = await new Xo(linterOptions, baseXoConfigOptions).calculateConfigForFile(absoluteFilePath);
251
+ console.log(JSON.stringify(config, undefined, '\t'));
198
252
  }
199
- process.exit(0);
200
- }
201
- if (typeof cliOptions.printConfig === 'string') {
202
- if (input.length > 0 || cliOptions.printConfig === '') {
203
- console.error('The `--print-config` flag must be used with exactly one filename');
204
- process.exit(1);
253
+ else {
254
+ const xo = new Xo(linterOptions, baseXoConfigOptions);
255
+ const report = await xo.lintFiles(input);
256
+ if (cliOptions.fix) {
257
+ await Xo.outputFixes(report);
258
+ }
259
+ if (cliOptions.open) {
260
+ await openReport(report);
261
+ }
262
+ await log(report, xo);
205
263
  }
206
- const absoluteFilePath = path.resolve(cliOptions.cwd, cliOptions.printConfig);
207
- const config = await new Xo(linterOptions, baseXoConfigOptions).calculateConfigForFile(absoluteFilePath);
208
- console.log(JSON.stringify(config, undefined, '\t'));
209
264
  }
210
- else {
211
- const xo = new Xo(linterOptions, baseXoConfigOptions);
212
- const report = await xo.lintFiles(input);
213
- if (cliOptions.fix) {
214
- await Xo.outputFixes(report);
215
- }
216
- if (cliOptions.open) {
217
- await openReport(report);
265
+ catch (error) {
266
+ if (isErrorWithExitCode(error)) {
267
+ console.error(error.message);
268
+ process.exit(error.exitCode);
218
269
  }
219
- await log(report);
270
+ throw error;
220
271
  }
221
272
  //# sourceMappingURL=cli.js.map