xo 0.41.0-beta.1 → 0.44.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/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import process from 'node:process';
2
3
  import getStdin from 'get-stdin';
3
4
  import meow from 'meow';
4
5
  import formatterPretty from 'eslint-formatter-pretty';
@@ -172,8 +173,8 @@ if (options.nodeVersion) {
172
173
  }
173
174
 
174
175
  (async () => {
175
- if (options.printConfig) {
176
- if (input.length > 0) {
176
+ if (typeof options.printConfig === 'string') {
177
+ if (input.length > 0 || options.printConfig === '') {
177
178
  console.error('The `--print-config` flag must be used with exactly one filename');
178
179
  process.exit(1);
179
180
  }
@@ -222,10 +222,7 @@ module.exports = {
222
222
  },
223
223
  ],
224
224
  'import/no-useless-path-segments': 'error',
225
-
226
- // Disabled as it doesn't work with TypeScript
227
- // 'import/newline-after-import': 'error',
228
-
225
+ 'import/newline-after-import': 'error',
229
226
  'import/no-amd': 'error',
230
227
  'import/no-duplicates': 'error',
231
228
 
@@ -325,7 +322,7 @@ module.exports = {
325
322
  'node/no-deprecated-api': 'error',
326
323
  'node/prefer-global/buffer': [
327
324
  'error',
328
- 'always',
325
+ 'never',
329
326
  ],
330
327
  'node/prefer-global/console': [
331
328
  'error',
@@ -333,7 +330,7 @@ module.exports = {
333
330
  ],
334
331
  'node/prefer-global/process': [
335
332
  'error',
336
- 'always',
333
+ 'never',
337
334
  ],
338
335
  'node/prefer-global/text-decoder': [
339
336
  'error',
package/index.js CHANGED
@@ -1,167 +1,92 @@
1
1
  import path from 'node:path';
2
2
  import {ESLint} from 'eslint';
3
- import globby from 'globby';
3
+ import {globby, isGitIgnoredSync} from 'globby';
4
4
  import {isEqual} from 'lodash-es';
5
5
  import micromatch from 'micromatch';
6
6
  import arrify from 'arrify';
7
- import pReduce from 'p-reduce';
8
- import pMap from 'p-map';
9
- import {cosmiconfig, defaultLoaders} from 'cosmiconfig';
10
- import defineLazyProperty from 'define-lazy-prop';
11
- import pFilter from 'p-filter';
12
- import {CONFIG_FILES, MODULE_NAME, DEFAULT_IGNORES} from './lib/constants.js';
7
+ import slash from 'slash';
13
8
  import {
14
- normalizeOptions,
9
+ parseOptions,
15
10
  getIgnores,
16
11
  mergeWithFileConfig,
17
- mergeWithFileConfigs,
18
- buildConfig,
19
- mergeOptions,
20
12
  } from './lib/options-manager.js';
21
-
22
- /** Merge multiple reports into a single report */
23
- const mergeReports = reports => {
24
- const report = {
25
- results: [],
26
- errorCount: 0,
27
- warningCount: 0,
28
- };
29
-
30
- for (const currentReport of reports) {
31
- report.results.push(...currentReport.results);
32
- report.errorCount += currentReport.errorCount;
33
- report.warningCount += currentReport.warningCount;
13
+ import {mergeReports, processReport, getIgnoredReport} from './lib/report.js';
14
+
15
+ const runEslint = async (lint, options) => {
16
+ const {filePath, eslintOptions, isQuiet} = options;
17
+ const {cwd, baseConfig: {ignorePatterns}} = eslintOptions;
18
+
19
+ if (
20
+ filePath
21
+ && (
22
+ micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
23
+ || isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
24
+ )
25
+ ) {
26
+ return getIgnoredReport(filePath);
34
27
  }
35
28
 
36
- return report;
37
- };
29
+ const eslint = new ESLint(eslintOptions);
38
30
 
39
- const getReportStatistics = results => {
40
- const statistics = {
41
- errorCount: 0,
42
- warningCount: 0,
43
- fixableErrorCount: 0,
44
- fixableWarningCount: 0,
45
- };
46
-
47
- for (const result of results) {
48
- statistics.errorCount += result.errorCount;
49
- statistics.warningCount += result.warningCount;
50
- statistics.fixableErrorCount += result.fixableErrorCount;
51
- statistics.fixableWarningCount += result.fixableWarningCount;
31
+ if (filePath && await eslint.isPathIgnored(filePath)) {
32
+ return getIgnoredReport(filePath);
52
33
  }
53
34
 
54
- return statistics;
35
+ const report = await lint(eslint);
36
+ return processReport(report, {isQuiet});
55
37
  };
56
38
 
57
- const processReport = (report, {isQuiet = false} = {}) => {
58
- if (isQuiet) {
59
- report = ESLint.getErrorResults(report);
60
- }
61
-
62
- const result = {
63
- results: report,
64
- ...getReportStatistics(report),
65
- };
39
+ const globFiles = async (patterns, options) => {
40
+ const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options;
66
41
 
67
- defineLazyProperty(result, 'usedDeprecatedRules', () => {
68
- const seenRules = new Set();
69
- const rules = [];
42
+ patterns = patterns.length === 0
43
+ ? [`**/*.{${extensions.join(',')}}`]
44
+ : arrify(patterns).map(pattern => slash(pattern));
70
45
 
71
- for (const {usedDeprecatedRules} of report) {
72
- for (const rule of usedDeprecatedRules) {
73
- if (seenRules.has(rule.ruleId)) {
74
- continue;
75
- }
46
+ const files = await globby(
47
+ patterns,
48
+ {ignore: ignores, gitignore: true, absolute: true, cwd},
49
+ );
76
50
 
77
- seenRules.add(rule.ruleId);
78
- rules.push(rule);
79
- }
80
- }
81
-
82
- return rules;
83
- });
84
-
85
- return result;
51
+ return files.filter(file => extensions.includes(path.extname(file).slice(1)));
86
52
  };
87
53
 
88
- const runEslint = async (paths, options, processorOptions) => {
89
- const engine = new ESLint(options);
90
-
91
- const report = await engine.lintFiles(await pFilter(paths, async path => !(await engine.isPathIgnored(path))));
92
- return processReport(report, processorOptions);
93
- };
94
-
95
- const globFiles = async (patterns, {ignores, extensions, cwd}) => (
96
- await globby(
97
- patterns.length === 0 ? [`**/*.{${extensions.join(',')}}`] : arrify(patterns),
98
- {ignore: ignores, gitignore: true, cwd},
99
- )).filter(file => extensions.includes(path.extname(file).slice(1))).map(file => path.resolve(cwd, file));
100
-
101
54
  const getConfig = async options => {
102
- const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(options));
103
- const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions);
55
+ const {filePath, eslintOptions} = await parseOptions(options);
104
56
  const engine = new ESLint(eslintOptions);
105
57
  return engine.calculateConfigForFile(filePath);
106
58
  };
107
59
 
108
- const lintText = async (string, inputOptions = {}) => {
109
- const {options: foundOptions, prettierOptions} = mergeWithFileConfig(normalizeOptions(inputOptions));
110
- const options = buildConfig(foundOptions, prettierOptions);
60
+ const lintText = async (string, options) => {
61
+ options = await parseOptions(options);
62
+ const {filePath, warnIgnored, eslintOptions} = options;
63
+ const {ignorePatterns} = eslintOptions.baseConfig;
111
64
 
112
- if (options.baseConfig.ignorePatterns && !isEqual(getIgnores({}), options.baseConfig.ignorePatterns) && typeof options.filePath !== 'string') {
65
+ if (typeof filePath !== 'string' && !isEqual(getIgnores({}), ignorePatterns)) {
113
66
  throw new Error('The `ignores` option requires the `filePath` option to be defined.');
114
67
  }
115
68
 
116
- const {filePath, warnIgnored, ...eslintOptions} = options;
117
- const engine = new ESLint(eslintOptions);
69
+ return runEslint(
70
+ eslint => eslint.lintText(string, {filePath, warnIgnored}),
71
+ options,
72
+ );
73
+ };
118
74
 
119
- if (filePath) {
120
- const filename = path.relative(options.cwd, filePath);
121
-
122
- if (
123
- micromatch.isMatch(filename, options.baseConfig.ignorePatterns)
124
- || globby.gitignore.sync({cwd: options.cwd, ignore: options.baseConfig.ignorePatterns})(filePath)
125
- || await engine.isPathIgnored(filePath)
126
- ) {
127
- return {
128
- errorCount: 0,
129
- warningCount: 0,
130
- results: [{
131
- errorCount: 0,
132
- filePath: filename,
133
- messages: [],
134
- warningCount: 0,
135
- }],
136
- };
137
- }
138
- }
75
+ const lintFile = async (filePath, options) => runEslint(
76
+ eslint => eslint.lintFiles([filePath]),
77
+ await parseOptions({...options, filePath}),
78
+ );
139
79
 
140
- const report = await engine.lintText(string, {filePath, warnIgnored});
80
+ const lintFiles = async (patterns, options) => {
81
+ const files = await globFiles(patterns, options);
141
82
 
142
- return processReport(report, {isQuiet: inputOptions.quiet});
143
- };
83
+ const reports = await Promise.all(
84
+ files.map(filePath => lintFile(filePath, options)),
85
+ );
144
86
 
145
- const lintFiles = async (patterns, inputOptions = {}) => {
146
- inputOptions.cwd = path.resolve(inputOptions.cwd || process.cwd());
147
- const configExplorer = cosmiconfig(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: inputOptions.cwd});
148
-
149
- const configFiles = (await Promise.all(
150
- (await globby(
151
- CONFIG_FILES.map(configFile => `**/${configFile}`),
152
- {ignore: DEFAULT_IGNORES, gitignore: true, cwd: inputOptions.cwd},
153
- )).map(async configFile => configExplorer.load(path.resolve(inputOptions.cwd, configFile))),
154
- )).filter(Boolean);
155
-
156
- const paths = configFiles.length > 0
157
- ? await pReduce(
158
- configFiles,
159
- async (paths, {filepath, config}) =>
160
- [...paths, ...(await globFiles(patterns, {...mergeOptions(inputOptions, config), cwd: path.dirname(filepath)}))],
161
- [])
162
- : await globFiles(patterns, mergeOptions(inputOptions));
163
-
164
- return mergeReports(await pMap(await mergeWithFileConfigs([...new Set(paths)], inputOptions, configFiles), async ({files, options, prettierOptions}) => runEslint(files, buildConfig(options, prettierOptions), {isQuiet: options.quiet})));
87
+ const report = mergeReports(reports.filter(({isIgnored}) => !isIgnored));
88
+
89
+ return report;
165
90
  };
166
91
 
167
92
  const getFormatter = async name => {
@@ -1,22 +1,21 @@
1
+ import process from 'node:process';
1
2
  import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import fsExtra from 'fs-extra';
4
5
  import arrify from 'arrify';
5
- import {mergeWith, groupBy, flow, pick} from 'lodash-es';
6
+ import {mergeWith, flow, pick} from 'lodash-es';
6
7
  import pathExists from 'path-exists';
7
8
  import findUp from 'find-up';
8
9
  import findCacheDir from 'find-cache-dir';
9
10
  import prettier from 'prettier';
10
11
  import semver from 'semver';
11
- import {cosmiconfig, cosmiconfigSync, defaultLoaders} from 'cosmiconfig';
12
- import pReduce from 'p-reduce';
12
+ import {cosmiconfig, defaultLoaders} from 'cosmiconfig';
13
13
  import micromatch from 'micromatch';
14
14
  import JSON5 from 'json5';
15
15
  import toAbsoluteGlob from 'to-absolute-glob';
16
16
  import stringify from 'json-stable-stringify-without-jsonify';
17
17
  import murmur from 'imurmurhash';
18
- import isPathInside from 'is-path-inside';
19
- import eslintrc from '@eslint/eslintrc';
18
+ import {Legacy} from '@eslint/eslintrc';
20
19
  import createEsmUtils from 'esm-utils';
21
20
  import {
22
21
  DEFAULT_IGNORES,
@@ -32,10 +31,8 @@ import {
32
31
  } from './constants.js';
33
32
 
34
33
  const {__dirname, json, require} = createEsmUtils(import.meta);
35
- const pkg = json.loadSync('../package.json');
36
- const {outputJson, outputJsonSync} = fsExtra;
37
- const {normalizePackageName} = eslintrc.Legacy.naming;
38
- const resolveModule = eslintrc.Legacy.ModuleResolver.resolve;
34
+ const {normalizePackageName} = Legacy.naming;
35
+ const resolveModule = Legacy.ModuleResolver.resolve;
39
36
 
40
37
  const resolveFrom = (moduleId, fromDirectory = process.cwd()) => resolveModule(moduleId, path.join(fromDirectory, '__placeholder__.js'));
41
38
 
@@ -48,12 +45,12 @@ resolveFrom.silent = (moduleId, fromDirectory) => {
48
45
  const resolveLocalConfig = name => resolveModule(normalizePackageName(name, 'eslint-config'), import.meta.url);
49
46
 
50
47
  const nodeVersion = process && process.version;
51
- const cacheLocation = findCacheDir({name: CACHE_DIR_NAME}) || path.join(os.homedir() || os.tmpdir(), '.xo-cache/');
48
+ const cacheLocation = cwd => findCacheDir({name: CACHE_DIR_NAME, cwd}) || path.join(os.homedir() || os.tmpdir(), '.xo-cache/');
52
49
 
53
50
  const DEFAULT_CONFIG = {
54
51
  useEslintrc: false,
55
52
  cache: true,
56
- cacheLocation: path.join(cacheLocation, 'xo-cache.json'),
53
+ cacheLocation: path.join(cacheLocation(), 'xo-cache.json'),
57
54
  globInputPaths: false,
58
55
  baseConfig: {
59
56
  extends: [
@@ -104,18 +101,18 @@ const isTypescript = file => TYPESCRIPT_EXTENSION.includes(path.extname(file).sl
104
101
  Find config for `lintText`.
105
102
  The config files are searched starting from `options.filePath` if defined or `options.cwd` otherwise.
106
103
  */
107
- const mergeWithFileConfig = options => {
104
+ const mergeWithFileConfig = async options => {
108
105
  options.cwd = path.resolve(options.cwd || process.cwd());
109
- const configExplorer = cosmiconfigSync(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: options.cwd});
110
- const pkgConfigExplorer = cosmiconfigSync('engines', {searchPlaces: ['package.json'], stopDir: options.cwd});
106
+ const configExplorer = cosmiconfig(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: options.cwd});
107
+ const pkgConfigExplorer = cosmiconfig('engines', {searchPlaces: ['package.json'], stopDir: options.cwd});
111
108
  if (options.filePath) {
112
109
  options.filePath = path.resolve(options.cwd, options.filePath);
113
110
  }
114
111
 
115
112
  const searchPath = options.filePath || options.cwd;
116
113
 
117
- const {config: xoOptions, filepath: xoConfigPath} = configExplorer.search(searchPath) || {};
118
- const {config: enginesOptions} = pkgConfigExplorer.search(searchPath) || {};
114
+ const {config: xoOptions, filepath: xoConfigPath} = (await configExplorer.search(searchPath)) || {};
115
+ const {config: enginesOptions} = (await pkgConfigExplorer.search(searchPath)) || {};
119
116
 
120
117
  options = mergeOptions(options, xoOptions, enginesOptions);
121
118
  options.cwd = xoConfigPath && path.dirname(xoConfigPath) !== options.cwd ? path.resolve(options.cwd, path.dirname(xoConfigPath)) : options.cwd;
@@ -124,91 +121,31 @@ const mergeWithFileConfig = options => {
124
121
  ({options} = applyOverrides(options.filePath, options));
125
122
  }
126
123
 
127
- const prettierOptions = options.prettier ? prettier.resolveConfig.sync(searchPath, {editorconfig: true}) || {} : {};
124
+ const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {};
128
125
 
129
126
  if (options.filePath && isTypescript(options.filePath)) {
130
- const tsConfigExplorer = cosmiconfigSync([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
131
- const {config: tsConfig, filepath: tsConfigPath} = tsConfigExplorer.search(options.filePath) || {};
127
+ const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
128
+ const {config: tsConfig, filepath: tsConfigPath} = (await tsConfigExplorer.search(options.filePath)) || {};
132
129
 
133
- options.tsConfigPath = getTsConfigCachePath([options.filePath], options.tsConfigPath);
130
+ options.tsConfigPath = await getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd);
134
131
  options.ts = true;
135
- outputJsonSync(options.tsConfigPath, makeTSConfig(tsConfig, tsConfigPath, [options.filePath]));
132
+ await fsExtra.outputJson(options.tsConfigPath, makeTSConfig(tsConfig, tsConfigPath, [options.filePath]));
136
133
  }
137
134
 
138
135
  return {options, prettierOptions};
139
136
  };
140
137
 
141
- /**
142
- Find config for each files found by `lintFiles`.
143
- The config files are searched starting from each files.
144
- */
145
- const mergeWithFileConfigs = async (files, options, configFiles) => {
146
- configFiles = configFiles.sort((a, b) => b.filepath.split(path.sep).length - a.filepath.split(path.sep).length);
147
- const tsConfigs = {};
148
-
149
- const groups = [...(await pReduce(files, async (configs, file) => {
150
- const pkgConfigExplorer = cosmiconfig('engines', {searchPlaces: ['package.json'], stopDir: options.cwd});
151
-
152
- const {config: xoOptions, filepath: xoConfigPath} = findApplicableConfig(file, configFiles) || {};
153
- const {config: enginesOptions, filepath: enginesConfigPath} = await pkgConfigExplorer.search(file) || {};
154
-
155
- let fileOptions = mergeOptions(options, xoOptions, enginesOptions);
156
- fileOptions.cwd = xoConfigPath && path.dirname(xoConfigPath) !== fileOptions.cwd ? path.resolve(fileOptions.cwd, path.dirname(xoConfigPath)) : fileOptions.cwd;
157
-
158
- const {hash, options: optionsWithOverrides} = applyOverrides(file, fileOptions);
159
- fileOptions = optionsWithOverrides;
160
-
161
- const prettierOptions = fileOptions.prettier ? await prettier.resolveConfig(file, {editorconfig: true}) || {} : {};
162
-
163
- let tsConfigPath;
164
- if (isTypescript(file)) {
165
- let tsConfig;
166
- const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
167
- ({config: tsConfig, filepath: tsConfigPath} = await tsConfigExplorer.search(file) || {});
168
-
169
- fileOptions.tsConfigPath = tsConfigPath;
170
- tsConfigs[tsConfigPath || ''] = tsConfig;
171
- fileOptions.ts = true;
172
- }
173
-
174
- const cacheKey = stringify({xoConfigPath, enginesConfigPath, prettierOptions, hash, tsConfigPath: fileOptions.tsConfigPath, ts: fileOptions.ts});
175
- const cachedGroup = configs.get(cacheKey);
176
-
177
- configs.set(cacheKey, {
178
- files: [file, ...(cachedGroup ? cachedGroup.files : [])],
179
- options: cachedGroup ? cachedGroup.options : fileOptions,
180
- prettierOptions,
181
- });
182
-
183
- return configs;
184
- }, new Map())).values()];
185
-
186
- await Promise.all(Object.entries(groupBy(groups.filter(({options}) => Boolean(options.ts)), group => group.options.tsConfigPath || '')).map(
187
- ([tsConfigPath, groups]) => {
188
- const files = groups.flatMap(group => group.files);
189
- const cachePath = getTsConfigCachePath(files, tsConfigPath);
190
-
191
- for (const group of groups) {
192
- group.options.tsConfigPath = cachePath;
193
- }
194
-
195
- return outputJson(cachePath, makeTSConfig(tsConfigs[tsConfigPath], tsConfigPath, files));
196
- },
197
- ));
198
-
199
- return groups;
200
- };
201
-
202
- const findApplicableConfig = (file, configFiles) => configFiles.find(({filepath}) => isPathInside(file, path.dirname(filepath)));
203
-
204
138
  /**
205
139
  Generate a unique and consistent path for the temporary `tsconfig.json`.
206
140
  Hashing based on https://github.com/eslint/eslint/blob/cf38d0d939b62f3670cdd59f0143fd896fccd771/lib/cli-engine/lint-result-cache.js#L30
207
141
  */
208
- const getTsConfigCachePath = (files, tsConfigPath) => path.join(
209
- cacheLocation,
210
- `tsconfig.${murmur(`${pkg.version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36)}.json`,
211
- );
142
+ const getTsConfigCachePath = async (files, tsConfigPath, cwd) => {
143
+ const {version} = await json.load('../package.json');
144
+ return path.join(
145
+ cacheLocation(cwd),
146
+ `tsconfig.${murmur(`${version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36)}.json`,
147
+ );
148
+ };
212
149
 
213
150
  const makeTSConfig = (tsConfig, tsConfigPath, files) => {
214
151
  const config = {files: files.filter(file => isTypescript(file))};
@@ -492,7 +429,7 @@ const mergeWithPrettierConfig = (options, prettierOptions) => {
492
429
  singleQuote: true,
493
430
  bracketSpacing: false,
494
431
  jsxBracketSameLine: false,
495
- trailingComma: 'none',
432
+ trailingComma: 'all',
496
433
  tabWidth: normalizeSpaces(options),
497
434
  useTabs: !options.space,
498
435
  semi: options.semicolon !== false,
@@ -603,14 +540,27 @@ const gatherImportResolvers = options => {
603
540
  return resolvers;
604
541
  };
605
542
 
543
+ const parseOptions = async options => {
544
+ options = normalizeOptions(options);
545
+ const {options: foundOptions, prettierOptions} = await mergeWithFileConfig(options);
546
+ const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions);
547
+ return {
548
+ filePath,
549
+ warnIgnored,
550
+ isQuiet: options.quiet,
551
+ eslintOptions,
552
+ };
553
+ };
554
+
606
555
  export {
556
+ parseOptions,
557
+ getIgnores,
558
+ mergeWithFileConfig,
559
+
560
+ // For tests
561
+ applyOverrides,
607
562
  findApplicableOverrides,
608
563
  mergeWithPrettierConfig,
609
564
  normalizeOptions,
610
- getIgnores,
611
- mergeWithFileConfigs,
612
- mergeWithFileConfig,
613
565
  buildConfig,
614
- applyOverrides,
615
- mergeOptions,
616
566
  };
package/lib/report.js ADDED
@@ -0,0 +1,84 @@
1
+ import defineLazyProperty from 'define-lazy-prop';
2
+ import {ESLint} from 'eslint';
3
+
4
+ /** Merge multiple reports into a single report */
5
+ const mergeReports = reports => {
6
+ const report = {
7
+ results: [],
8
+ errorCount: 0,
9
+ warningCount: 0,
10
+ };
11
+
12
+ for (const currentReport of reports) {
13
+ report.results.push(...currentReport.results);
14
+ report.errorCount += currentReport.errorCount;
15
+ report.warningCount += currentReport.warningCount;
16
+ }
17
+
18
+ return report;
19
+ };
20
+
21
+ const processReport = (report, {isQuiet = false} = {}) => {
22
+ if (isQuiet) {
23
+ report = ESLint.getErrorResults(report);
24
+ }
25
+
26
+ const result = {
27
+ results: report,
28
+ ...getReportStatistics(report),
29
+ };
30
+
31
+ defineLazyProperty(result, 'usedDeprecatedRules', () => {
32
+ const seenRules = new Set();
33
+ const rules = [];
34
+
35
+ for (const {usedDeprecatedRules} of report) {
36
+ for (const rule of usedDeprecatedRules) {
37
+ if (seenRules.has(rule.ruleId)) {
38
+ continue;
39
+ }
40
+
41
+ seenRules.add(rule.ruleId);
42
+ rules.push(rule);
43
+ }
44
+ }
45
+
46
+ return rules;
47
+ });
48
+
49
+ return result;
50
+ };
51
+
52
+ const getReportStatistics = results => {
53
+ const statistics = {
54
+ errorCount: 0,
55
+ warningCount: 0,
56
+ fixableErrorCount: 0,
57
+ fixableWarningCount: 0,
58
+ };
59
+
60
+ for (const result of results) {
61
+ statistics.errorCount += result.errorCount;
62
+ statistics.warningCount += result.warningCount;
63
+ statistics.fixableErrorCount += result.fixableErrorCount;
64
+ statistics.fixableWarningCount += result.fixableWarningCount;
65
+ }
66
+
67
+ return statistics;
68
+ };
69
+
70
+ const getIgnoredReport = filePath => ({
71
+ errorCount: 0,
72
+ warningCount: 0,
73
+ results: [
74
+ {
75
+ errorCount: 0,
76
+ warningCount: 0,
77
+ filePath,
78
+ messages: [],
79
+ },
80
+ ],
81
+ isIgnored: true,
82
+ });
83
+
84
+ export {mergeReports, processReport, getIgnoredReport};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xo",
3
- "version": "0.41.0-beta.1",
3
+ "version": "0.44.0",
4
4
  "description": "JavaScript/TypeScript linter (ESLint wrapper) with great defaults",
5
5
  "license": "MIT",
6
6
  "repository": "xojs/xo",
@@ -16,7 +16,7 @@
16
16
  "node": ">=12.20"
17
17
  },
18
18
  "scripts": {
19
- "test": "eslint --quiet . --ext .js,.cjs && nyc ava"
19
+ "test": "node cli.js && nyc ava"
20
20
  },
21
21
  "files": [
22
22
  "config",
@@ -52,17 +52,17 @@
52
52
  "typescript"
53
53
  ],
54
54
  "dependencies": {
55
- "@eslint/eslintrc": "^0.4.2",
56
- "@typescript-eslint/eslint-plugin": "^4.28.1",
57
- "@typescript-eslint/parser": "^4.28.1",
55
+ "@eslint/eslintrc": "^1.0.0",
56
+ "@typescript-eslint/eslint-plugin": "^4.29.0",
57
+ "@typescript-eslint/parser": "^4.29.0",
58
58
  "arrify": "^3.0.0",
59
59
  "cosmiconfig": "^7.0.0",
60
60
  "debug": "^4.3.2",
61
61
  "define-lazy-prop": "^3.0.0",
62
- "eslint": "^7.30.0",
62
+ "eslint": "^7.32.0",
63
63
  "eslint-config-prettier": "^8.3.0",
64
- "eslint-config-xo": "^0.37.0",
65
- "eslint-config-xo-typescript": "^0.43.0",
64
+ "eslint-config-xo": "^0.38.0",
65
+ "eslint-config-xo-typescript": "^0.44.0",
66
66
  "eslint-formatter-pretty": "^4.1.0",
67
67
  "eslint-import-resolver-webpack": "^0.13.1",
68
68
  "eslint-plugin-ava": "^12.0.0",
@@ -72,24 +72,21 @@
72
72
  "eslint-plugin-node": "^11.1.0",
73
73
  "eslint-plugin-prettier": "^3.4.0",
74
74
  "eslint-plugin-promise": "^5.1.0",
75
- "eslint-plugin-unicorn": "^34.0.1",
75
+ "eslint-plugin-unicorn": "^35.0.0",
76
76
  "esm-utils": "^1.1.0",
77
77
  "find-cache-dir": "^3.3.1",
78
78
  "find-up": "^5.0.0",
79
79
  "fs-extra": "^10.0.0",
80
80
  "get-stdin": "^9.0.0",
81
- "globby": "^9.2.0",
81
+ "globby": "^12.0.0",
82
82
  "imurmurhash": "^0.1.4",
83
83
  "is-path-inside": "^4.0.0",
84
84
  "json-stable-stringify-without-jsonify": "^1.0.1",
85
85
  "json5": "^2.2.0",
86
86
  "lodash-es": "^4.17.21",
87
- "meow": "^10.0.1",
87
+ "meow": "^10.1.1",
88
88
  "micromatch": "^4.0.4",
89
89
  "open-editor": "^3.0.0",
90
- "p-filter": "^2.1.0",
91
- "p-map": "^5.0.0",
92
- "p-reduce": "^3.0.0",
93
90
  "path-exists": "^4.0.0",
94
91
  "prettier": "^2.3.2",
95
92
  "semver": "^7.3.5",
@@ -106,20 +103,19 @@
106
103
  "nyc": "^15.1.0",
107
104
  "proxyquire": "^2.1.3",
108
105
  "temp-write": "^5.0.0",
109
- "webpack": "^5.42.0"
106
+ "webpack": "^5.49.0"
110
107
  },
111
- "eslintConfig": {
112
- "extends": [
113
- "eslint-config-xo",
114
- "./config/plugins.cjs",
115
- "./config/overrides.cjs"
108
+ "xo": {
109
+ "ignores": [
110
+ "test/fixtures",
111
+ "test/temp",
112
+ "coverage"
116
113
  ]
117
114
  },
118
- "eslintIgnore": [
119
- "test/fixtures",
120
- "coverage"
121
- ],
122
115
  "ava": {
116
+ "files": [
117
+ "!test/temp"
118
+ ],
123
119
  "timeout": "1m"
124
120
  },
125
121
  "nyc": {
package/readme.md CHANGED
@@ -40,9 +40,11 @@ It uses [ESLint](https://eslint.org) underneath, so issues regarding built-in ru
40
40
  ## Install
41
41
 
42
42
  ```
43
- $ npm install --global xo
43
+ $ npm install xo --save-dev
44
44
  ```
45
45
 
46
+ *You must install XO locally. You can run it directly with `$ npx xo`.*
47
+
46
48
  *JSX is supported by default, but you'll need [eslint-config-xo-react](https://github.com/xojs/eslint-config-xo-react#use-with-xo) for React specific linting. Vue components are not supported by default. You'll need [eslint-config-xo-vue](https://github.com/ChocPanda/eslint-config-xo-vue#use-with-xo) for specific linting in a Vue app.*
47
49
 
48
50
  ## Usage
@@ -89,8 +91,6 @@ $ xo --help
89
91
  - Put options in package.json instead of using flags so other tools can read it.
90
92
  ```
91
93
 
92
- *Note that the CLI will use your local install of XO when available, even when run globally.*
93
-
94
94
  ## Default code style
95
95
 
96
96
  *Any of these can be [overridden](#rules) if necessary.*
@@ -98,6 +98,7 @@ $ xo --help
98
98
  - Tab indentation *[(or space)](#space)*
99
99
  - Semicolons *[(or not)](#semicolon)*
100
100
  - Single-quotes
101
+ - [Trailing comma](https://medium.com/@nikgraf/why-you-should-enforce-dangling-commas-for-multiline-statements-d034c98e36f8) for multiline statements
101
102
  - No unused variables
102
103
  - Space after keyword `if (condition) {}`
103
104
  - Always `===` instead of `==`
@@ -120,9 +121,9 @@ Simply run `$ npm init xo` (with any options) to add XO to your package.json or
120
121
  + "test": "xo && ava"
121
122
  },
122
123
  "devDependencies": {
123
- - "ava": "^2.0.0"
124
- + "ava": "^2.0.0",
125
- + "xo": "^0.25.0"
124
+ - "ava": "^3.0.0"
125
+ + "ava": "^3.0.0",
126
+ + "xo": "^0.41.0"
126
127
  }
127
128
  }
128
129
  ```
@@ -224,7 +225,7 @@ The [Prettier options](https://prettier.io/docs/en/options.html) will be read fr
224
225
  - [semi](https://prettier.io/docs/en/options.html#semicolons): based on [semicolon](#semicolon) option
225
226
  - [useTabs](https://prettier.io/docs/en/options.html#tabs): based on [space](#space) option
226
227
  - [tabWidth](https://prettier.io/docs/en/options.html#tab-width): based on [space](#space) option
227
- - [trailingComma](https://prettier.io/docs/en/options.html#trailing-commas): `none`
228
+ - [trailingComma](https://prettier.io/docs/en/options.html#trailing-commas): `all`
228
229
  - [singleQuote](https://prettier.io/docs/en/options.html#quotes): `true`
229
230
  - [bracketSpacing](https://prettier.io/docs/en/options.html#bracket-spacing): `false`
230
231
  - [jsxBracketSameLine](https://prettier.io/docs/en/options.html#jsx-brackets): `false`