xo 0.45.0 → 0.46.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/config/plugins.cjs +15 -12
- package/index.js +59 -39
- package/lib/options-manager.js +53 -14
- package/package.json +19 -14
- package/readme.md +11 -2
package/config/plugins.cjs
CHANGED
|
@@ -14,7 +14,8 @@ module.exports = {
|
|
|
14
14
|
'no-use-extend-native',
|
|
15
15
|
'ava',
|
|
16
16
|
'unicorn',
|
|
17
|
-
'
|
|
17
|
+
// Disabled as the plugin doesn't support ESLint 8 yet.
|
|
18
|
+
// 'promise',
|
|
18
19
|
'import',
|
|
19
20
|
'node',
|
|
20
21
|
'eslint-comments',
|
|
@@ -173,17 +174,19 @@ module.exports = {
|
|
|
173
174
|
// TODO: Temporarily disabled as the rule is buggy.
|
|
174
175
|
'function-call-argument-newline': 'off',
|
|
175
176
|
|
|
176
|
-
'
|
|
177
|
-
'promise/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
'promise/no-
|
|
185
|
-
'promise/
|
|
186
|
-
'promise/
|
|
177
|
+
// Disabled as the plugin doesn't support ESLint 8 yet.
|
|
178
|
+
// 'promise/param-names': 'error',
|
|
179
|
+
// 'promise/no-return-wrap': [
|
|
180
|
+
// 'error',
|
|
181
|
+
// {
|
|
182
|
+
// allowReject: true,
|
|
183
|
+
// },
|
|
184
|
+
// ],
|
|
185
|
+
// 'promise/no-new-statics': 'error',
|
|
186
|
+
// 'promise/no-return-in-finally': 'error',
|
|
187
|
+
// 'promise/valid-params': 'error',
|
|
188
|
+
// 'promise/prefer-await-to-then': 'error',
|
|
189
|
+
|
|
187
190
|
'import/default': 'error',
|
|
188
191
|
'import/export': 'error',
|
|
189
192
|
'import/extensions': [
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import {ESLint} from 'eslint';
|
|
3
3
|
import {globby, isGitIgnoredSync} from 'globby';
|
|
4
|
-
import {isEqual} from 'lodash-es';
|
|
4
|
+
import {isEqual, groupBy} from 'lodash-es';
|
|
5
5
|
import micromatch from 'micromatch';
|
|
6
6
|
import arrify from 'arrify';
|
|
7
7
|
import slash from 'slash';
|
|
@@ -12,30 +12,6 @@ import {
|
|
|
12
12
|
} from './lib/options-manager.js';
|
|
13
13
|
import {mergeReports, processReport, getIgnoredReport} from './lib/report.js';
|
|
14
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);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const eslint = new ESLint(eslintOptions);
|
|
30
|
-
|
|
31
|
-
if (filePath && await eslint.isPathIgnored(filePath)) {
|
|
32
|
-
return getIgnoredReport(filePath);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const report = await lint(eslint);
|
|
36
|
-
return processReport(report, {isQuiet});
|
|
37
|
-
};
|
|
38
|
-
|
|
39
15
|
const globFiles = async (patterns, options) => {
|
|
40
16
|
const {ignores, extensions, cwd} = (await mergeWithFileConfig(options)).options;
|
|
41
17
|
|
|
@@ -59,32 +35,76 @@ const getConfig = async options => {
|
|
|
59
35
|
|
|
60
36
|
const lintText = async (string, options) => {
|
|
61
37
|
options = await parseOptions(options);
|
|
62
|
-
const {filePath, warnIgnored, eslintOptions} = options;
|
|
63
|
-
const {ignorePatterns} = eslintOptions
|
|
38
|
+
const {filePath, warnIgnored, eslintOptions, isQuiet} = options;
|
|
39
|
+
const {cwd, baseConfig: {ignorePatterns}} = eslintOptions;
|
|
64
40
|
|
|
65
41
|
if (typeof filePath !== 'string' && !isEqual(getIgnores({}), ignorePatterns)) {
|
|
66
42
|
throw new Error('The `ignores` option requires the `filePath` option to be defined.');
|
|
67
43
|
}
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
45
|
+
if (
|
|
46
|
+
filePath
|
|
47
|
+
&& (
|
|
48
|
+
micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
|
|
49
|
+
|| isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
|
|
50
|
+
)
|
|
51
|
+
) {
|
|
52
|
+
return getIgnoredReport(filePath);
|
|
53
|
+
}
|
|
74
54
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
await
|
|
78
|
-
);
|
|
55
|
+
const eslint = new ESLint(eslintOptions);
|
|
56
|
+
|
|
57
|
+
if (filePath && await eslint.isPathIgnored(filePath)) {
|
|
58
|
+
return getIgnoredReport(filePath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const report = await eslint.lintText(string, {filePath, warnIgnored});
|
|
62
|
+
return processReport(report, {isQuiet});
|
|
63
|
+
};
|
|
79
64
|
|
|
80
65
|
const lintFiles = async (patterns, options) => {
|
|
81
66
|
const files = await globFiles(patterns, options);
|
|
82
67
|
|
|
83
|
-
const
|
|
84
|
-
files.map(filePath =>
|
|
68
|
+
const allOptions = await Promise.all(
|
|
69
|
+
files.map(filePath => parseOptions({...options, filePath})),
|
|
85
70
|
);
|
|
86
71
|
|
|
87
|
-
|
|
72
|
+
// Files with same `xoConfigPath` can lint together
|
|
73
|
+
// https://github.com/xojs/xo/issues/599
|
|
74
|
+
const groups = groupBy(allOptions, 'eslintConfigId');
|
|
75
|
+
|
|
76
|
+
const reports = await Promise.all(
|
|
77
|
+
Object.values(groups)
|
|
78
|
+
.map(async filesWithOptions => {
|
|
79
|
+
const options = filesWithOptions[0];
|
|
80
|
+
const eslint = new ESLint(options.eslintOptions);
|
|
81
|
+
const files = [];
|
|
82
|
+
|
|
83
|
+
for (const options of filesWithOptions) {
|
|
84
|
+
const {filePath, eslintOptions} = options;
|
|
85
|
+
const {cwd, baseConfig: {ignorePatterns}} = eslintOptions;
|
|
86
|
+
if (filePath
|
|
87
|
+
&& (
|
|
88
|
+
micromatch.isMatch(path.relative(cwd, filePath), ignorePatterns)
|
|
89
|
+
|| isGitIgnoredSync({cwd, ignore: ignorePatterns})(filePath)
|
|
90
|
+
)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// eslint-disable-next-line no-await-in-loop
|
|
95
|
+
if ((await eslint.isPathIgnored(filePath))) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
files.push(filePath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const report = await eslint.lintFiles(files);
|
|
103
|
+
|
|
104
|
+
return processReport(report, {isQuiet: options.isQuiet});
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const report = mergeReports(reports);
|
|
88
108
|
|
|
89
109
|
return report;
|
|
90
110
|
};
|
package/lib/options-manager.js
CHANGED
|
@@ -50,6 +50,7 @@ const DEFAULT_CONFIG = {
|
|
|
50
50
|
cache: true,
|
|
51
51
|
cacheLocation: path.join(cacheLocation(), 'xo-cache.json'),
|
|
52
52
|
globInputPaths: false,
|
|
53
|
+
resolvePluginsRelativeTo: __dirname,
|
|
53
54
|
baseConfig: {
|
|
54
55
|
extends: [
|
|
55
56
|
resolveLocalConfig('xo'),
|
|
@@ -115,24 +116,57 @@ const mergeWithFileConfig = async options => {
|
|
|
115
116
|
options = mergeOptions(options, xoOptions, enginesOptions);
|
|
116
117
|
options.cwd = xoConfigPath && path.dirname(xoConfigPath) !== options.cwd ? path.resolve(options.cwd, path.dirname(xoConfigPath)) : options.cwd;
|
|
117
118
|
|
|
119
|
+
// Very simple way to ensure eslint is ran minimal times across
|
|
120
|
+
// all linted files, once for each unique configuration - xo config path + override hash + tsconfig path
|
|
121
|
+
let eslintConfigId = xoConfigPath;
|
|
118
122
|
if (options.filePath) {
|
|
119
|
-
|
|
123
|
+
const overrides = applyOverrides(options.filePath, options);
|
|
124
|
+
options = overrides.options;
|
|
125
|
+
|
|
126
|
+
if (overrides.hash) {
|
|
127
|
+
eslintConfigId += overrides.hash;
|
|
128
|
+
}
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
const prettierOptions = options.prettier ? await prettier.resolveConfig(searchPath, {editorconfig: true}) || {} : {};
|
|
123
132
|
|
|
124
133
|
if (options.filePath && isTypescript(options.filePath)) {
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
// We can skip looking up the tsconfig if we have it defined
|
|
135
|
+
// in our parser options already. Otherwise we can look it up and create it as normal
|
|
136
|
+
const {project: tsConfigProjectPath, tsconfigRootDir} = options.parserOptions || {};
|
|
137
|
+
|
|
138
|
+
let tsConfig;
|
|
139
|
+
let tsConfigPath;
|
|
140
|
+
if (tsConfigProjectPath) {
|
|
141
|
+
tsConfigPath = path.resolve(options.cwd, tsConfigProjectPath);
|
|
142
|
+
tsConfig = await json.load(tsConfigPath);
|
|
143
|
+
} else {
|
|
144
|
+
const tsConfigExplorer = cosmiconfig([], {
|
|
145
|
+
searchPlaces: ['tsconfig.json'],
|
|
146
|
+
loaders: {'.json': (_, content) => JSON5.parse(content)},
|
|
147
|
+
stopDir: tsconfigRootDir,
|
|
148
|
+
});
|
|
149
|
+
const searchResults = (await tsConfigExplorer.search(options.filePath)) || {};
|
|
150
|
+
tsConfigPath = searchResults.filepath;
|
|
151
|
+
tsConfig = searchResults.config;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (tsConfigPath) {
|
|
155
|
+
options.tsConfigPath = tsConfigPath;
|
|
156
|
+
eslintConfigId += tsConfigPath;
|
|
157
|
+
} else {
|
|
158
|
+
const {path: tsConfigCachePath, hash: tsConfigHash} = await getTsConfigCachePath([eslintConfigId], tsConfigPath, options.cwd);
|
|
159
|
+
eslintConfigId += tsConfigHash;
|
|
160
|
+
options.tsConfigPath = tsConfigCachePath;
|
|
161
|
+
const config = makeTSConfig(tsConfig, tsConfigPath, [options.filePath]);
|
|
162
|
+
await fs.mkdir(path.dirname(options.tsConfigPath), {recursive: true});
|
|
163
|
+
await fs.writeFile(options.tsConfigPath, JSON.stringify(config));
|
|
164
|
+
}
|
|
127
165
|
|
|
128
|
-
options.tsConfigPath = await getTsConfigCachePath([options.filePath], options.tsConfigPath, options.cwd);
|
|
129
166
|
options.ts = true;
|
|
130
|
-
const config = makeTSConfig(tsConfig, tsConfigPath, [options.filePath]);
|
|
131
|
-
await fs.mkdir(path.dirname(options.tsConfigPath), {recursive: true});
|
|
132
|
-
await fs.writeFile(options.tsConfigPath, JSON.stringify(config));
|
|
133
167
|
}
|
|
134
168
|
|
|
135
|
-
return {options, prettierOptions};
|
|
169
|
+
return {options, prettierOptions, eslintConfigId};
|
|
136
170
|
};
|
|
137
171
|
|
|
138
172
|
/**
|
|
@@ -141,10 +175,14 @@ Hashing based on https://github.com/eslint/eslint/blob/cf38d0d939b62f3670cdd59f0
|
|
|
141
175
|
*/
|
|
142
176
|
const getTsConfigCachePath = async (files, tsConfigPath, cwd) => {
|
|
143
177
|
const {version} = await json.load('../package.json');
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
178
|
+
const tsConfigHash = murmur(`${version}_${nodeVersion}_${stringify({files: files.sort(), tsConfigPath})}`).result().toString(36);
|
|
179
|
+
return {
|
|
180
|
+
path: path.join(
|
|
181
|
+
cacheLocation(cwd),
|
|
182
|
+
`tsconfig.${tsConfigHash}.json`,
|
|
183
|
+
),
|
|
184
|
+
hash: tsConfigHash,
|
|
185
|
+
};
|
|
148
186
|
};
|
|
149
187
|
|
|
150
188
|
const makeTSConfig = (tsConfig, tsConfigPath, files) => {
|
|
@@ -437,7 +475,7 @@ const mergeWithPrettierConfig = (options, prettierOptions) => {
|
|
|
437
475
|
|
|
438
476
|
const buildTSConfig = options => config => {
|
|
439
477
|
if (options.ts) {
|
|
440
|
-
config.baseConfig.extends.push('xo-typescript');
|
|
478
|
+
config.baseConfig.extends.push(require.resolve('eslint-config-xo-typescript'));
|
|
441
479
|
config.baseConfig.parser = require.resolve('@typescript-eslint/parser');
|
|
442
480
|
config.baseConfig.parserOptions = {
|
|
443
481
|
...config.baseConfig.parserOptions,
|
|
@@ -538,13 +576,14 @@ const gatherImportResolvers = options => {
|
|
|
538
576
|
|
|
539
577
|
const parseOptions = async options => {
|
|
540
578
|
options = normalizeOptions(options);
|
|
541
|
-
const {options: foundOptions, prettierOptions} = await mergeWithFileConfig(options);
|
|
579
|
+
const {options: foundOptions, prettierOptions, eslintConfigId} = await mergeWithFileConfig(options);
|
|
542
580
|
const {filePath, warnIgnored, ...eslintOptions} = buildConfig(foundOptions, prettierOptions);
|
|
543
581
|
return {
|
|
544
582
|
filePath,
|
|
545
583
|
warnIgnored,
|
|
546
584
|
isQuiet: options.quiet,
|
|
547
585
|
eslintOptions,
|
|
586
|
+
eslintConfigId,
|
|
548
587
|
};
|
|
549
588
|
};
|
|
550
589
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "JavaScript/TypeScript linter (ESLint wrapper) with great defaults",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "xojs/xo",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"node": ">=12.20"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
+
"test:clean": "find ./test -type d -name 'node_modules' -prune -not -path ./test/fixtures/project/node_modules -exec rm -rf '{}' +",
|
|
19
20
|
"test": "node cli.js && nyc ava"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
@@ -51,30 +52,34 @@
|
|
|
51
52
|
"javascript",
|
|
52
53
|
"typescript"
|
|
53
54
|
],
|
|
55
|
+
"bundledDependencies": [
|
|
56
|
+
"@typescript-eslint/eslint-plugin",
|
|
57
|
+
"@typescript-eslint/parser",
|
|
58
|
+
"eslint-config-xo-typescript"
|
|
59
|
+
],
|
|
54
60
|
"dependencies": {
|
|
55
|
-
"@eslint/eslintrc": "^1.0.
|
|
56
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
57
|
-
"@typescript-eslint/parser": "^
|
|
61
|
+
"@eslint/eslintrc": "^1.0.3",
|
|
62
|
+
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
|
63
|
+
"@typescript-eslint/parser": "^5.2.0",
|
|
58
64
|
"arrify": "^3.0.0",
|
|
59
65
|
"cosmiconfig": "^7.0.1",
|
|
60
66
|
"define-lazy-prop": "^3.0.0",
|
|
61
|
-
"eslint": "^
|
|
67
|
+
"eslint": "^8.1.0",
|
|
62
68
|
"eslint-config-prettier": "^8.3.0",
|
|
63
69
|
"eslint-config-xo": "^0.39.0",
|
|
64
|
-
"eslint-config-xo-typescript": "^0.
|
|
70
|
+
"eslint-config-xo-typescript": "^0.47.0",
|
|
65
71
|
"eslint-formatter-pretty": "^4.1.0",
|
|
66
|
-
"eslint-import-resolver-webpack": "^0.13.
|
|
67
|
-
"eslint-plugin-ava": "^13.
|
|
72
|
+
"eslint-import-resolver-webpack": "^0.13.2",
|
|
73
|
+
"eslint-plugin-ava": "^13.1.0",
|
|
68
74
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
69
|
-
"eslint-plugin-import": "^2.
|
|
75
|
+
"eslint-plugin-import": "^2.25.2",
|
|
70
76
|
"eslint-plugin-no-use-extend-native": "^0.5.0",
|
|
71
77
|
"eslint-plugin-node": "^11.1.0",
|
|
72
78
|
"eslint-plugin-prettier": "^4.0.0",
|
|
73
|
-
"eslint-plugin-
|
|
74
|
-
"eslint-plugin-unicorn": "^36.0.0",
|
|
79
|
+
"eslint-plugin-unicorn": "^37.0.1",
|
|
75
80
|
"esm-utils": "^2.0.0",
|
|
76
81
|
"find-cache-dir": "^3.3.2",
|
|
77
|
-
"find-up": "^6.
|
|
82
|
+
"find-up": "^6.2.0",
|
|
78
83
|
"get-stdin": "^9.0.0",
|
|
79
84
|
"globby": "^12.0.2",
|
|
80
85
|
"imurmurhash": "^0.1.4",
|
|
@@ -88,7 +93,7 @@
|
|
|
88
93
|
"semver": "^7.3.5",
|
|
89
94
|
"slash": "^4.0.0",
|
|
90
95
|
"to-absolute-glob": "^2.0.2",
|
|
91
|
-
"typescript": "^4.4.
|
|
96
|
+
"typescript": "^4.4.4"
|
|
92
97
|
},
|
|
93
98
|
"devDependencies": {
|
|
94
99
|
"ava": "^3.15.0",
|
|
@@ -99,7 +104,7 @@
|
|
|
99
104
|
"nyc": "^15.1.0",
|
|
100
105
|
"proxyquire": "^2.1.3",
|
|
101
106
|
"temp-write": "^5.0.0",
|
|
102
|
-
"webpack": "^5.
|
|
107
|
+
"webpack": "^5.60.0"
|
|
103
108
|
},
|
|
104
109
|
"xo": {
|
|
105
110
|
"ignores": [
|
package/readme.md
CHANGED
|
@@ -221,14 +221,23 @@ Default: `false`
|
|
|
221
221
|
|
|
222
222
|
Format code with [Prettier](https://github.com/prettier/prettier).
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
[Prettier options](https://prettier.io/docs/en/options.html) will be based on your [Prettier config](https://prettier.io/docs/en/configuration.html). XO will then **merge** your options with its own defaults:
|
|
225
225
|
- [semi](https://prettier.io/docs/en/options.html#semicolons): based on [semicolon](#semicolon) option
|
|
226
226
|
- [useTabs](https://prettier.io/docs/en/options.html#tabs): based on [space](#space) option
|
|
227
227
|
- [tabWidth](https://prettier.io/docs/en/options.html#tab-width): based on [space](#space) option
|
|
228
228
|
- [trailingComma](https://prettier.io/docs/en/options.html#trailing-commas): `all`
|
|
229
229
|
- [singleQuote](https://prettier.io/docs/en/options.html#quotes): `true`
|
|
230
230
|
- [bracketSpacing](https://prettier.io/docs/en/options.html#bracket-spacing): `false`
|
|
231
|
-
|
|
231
|
+
|
|
232
|
+
To stick with Prettier's defaults, add this to your Prettier config:
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
module.exports = {
|
|
236
|
+
trailingComma: 'es5',
|
|
237
|
+
singleQuote: false,
|
|
238
|
+
bracketSpacing: true,
|
|
239
|
+
};
|
|
240
|
+
```
|
|
232
241
|
|
|
233
242
|
If contradicting options are set for both Prettier and XO an error will be thrown.
|
|
234
243
|
|