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 +3 -2
- package/config/plugins.cjs +3 -6
- package/index.js +55 -130
- package/lib/options-manager.js +44 -94
- package/lib/report.js +84 -0
- package/package.json +20 -24
- package/readme.md +8 -7
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
|
}
|
package/config/plugins.cjs
CHANGED
|
@@ -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
|
-
'
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
9
|
+
parseOptions,
|
|
15
10
|
getIgnores,
|
|
16
11
|
mergeWithFileConfig,
|
|
17
|
-
mergeWithFileConfigs,
|
|
18
|
-
buildConfig,
|
|
19
|
-
mergeOptions,
|
|
20
12
|
} from './lib/options-manager.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
};
|
|
29
|
+
const eslint = new ESLint(eslintOptions);
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
35
|
+
const report = await lint(eslint);
|
|
36
|
+
return processReport(report, {isQuiet});
|
|
55
37
|
};
|
|
56
38
|
|
|
57
|
-
const
|
|
58
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
42
|
+
patterns = patterns.length === 0
|
|
43
|
+
? [`**/*.{${extensions.join(',')}}`]
|
|
44
|
+
: arrify(patterns).map(pattern => slash(pattern));
|
|
70
45
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
46
|
+
const files = await globby(
|
|
47
|
+
patterns,
|
|
48
|
+
{ignore: ignores, gitignore: true, absolute: true, cwd},
|
|
49
|
+
);
|
|
76
50
|
|
|
77
|
-
|
|
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 {
|
|
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,
|
|
109
|
-
|
|
110
|
-
const
|
|
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 (
|
|
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
|
-
|
|
117
|
-
|
|
69
|
+
return runEslint(
|
|
70
|
+
eslint => eslint.lintText(string, {filePath, warnIgnored}),
|
|
71
|
+
options,
|
|
72
|
+
);
|
|
73
|
+
};
|
|
118
74
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
80
|
+
const lintFiles = async (patterns, options) => {
|
|
81
|
+
const files = await globFiles(patterns, options);
|
|
141
82
|
|
|
142
|
-
|
|
143
|
-
|
|
83
|
+
const reports = await Promise.all(
|
|
84
|
+
files.map(filePath => lintFile(filePath, options)),
|
|
85
|
+
);
|
|
144
86
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
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 => {
|
package/lib/options-manager.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
|
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
|
|
36
|
-
const
|
|
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 =
|
|
110
|
-
const pkgConfigExplorer =
|
|
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
|
|
124
|
+
const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {};
|
|
128
125
|
|
|
129
126
|
if (options.filePath && isTypescript(options.filePath)) {
|
|
130
|
-
const tsConfigExplorer =
|
|
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
|
-
|
|
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) =>
|
|
209
|
-
|
|
210
|
-
|
|
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: '
|
|
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.
|
|
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": "
|
|
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.
|
|
56
|
-
"@typescript-eslint/eslint-plugin": "^4.
|
|
57
|
-
"@typescript-eslint/parser": "^4.
|
|
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.
|
|
62
|
+
"eslint": "^7.32.0",
|
|
63
63
|
"eslint-config-prettier": "^8.3.0",
|
|
64
|
-
"eslint-config-xo": "^0.
|
|
65
|
-
"eslint-config-xo-typescript": "^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": "^
|
|
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": "^
|
|
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.
|
|
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.
|
|
106
|
+
"webpack": "^5.49.0"
|
|
110
107
|
},
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
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 --
|
|
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": "^
|
|
124
|
-
+ "ava": "^
|
|
125
|
-
+ "xo": "^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): `
|
|
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`
|