xo 2.0.0 → 2.0.2

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
@@ -4,6 +4,10 @@ declare const cli: import("meow").Result<{
4
4
  type: "boolean";
5
5
  default: false;
6
6
  };
7
+ fixDryRun: {
8
+ type: "boolean";
9
+ default: false;
10
+ };
7
11
  reporter: {
8
12
  type: "string";
9
13
  };
package/dist/cli.js CHANGED
@@ -16,6 +16,7 @@ const cli = meow(`
16
16
 
17
17
  Options
18
18
  --fix Automagically fix issues
19
+ --fix-dry-run Automagically fix issues without saving the changes to the file system
19
20
  --reporter Reporter to use
20
21
  --space Use space indent instead of tabs [Default: 2]
21
22
  --config Path to a XO configuration file
@@ -46,6 +47,10 @@ const cli = meow(`
46
47
  type: 'boolean',
47
48
  default: false,
48
49
  },
50
+ fixDryRun: {
51
+ type: 'boolean',
52
+ default: false,
53
+ },
49
54
  reporter: {
50
55
  type: 'string',
51
56
  },
@@ -105,7 +110,7 @@ const baseXoConfigOptions = {
105
110
  react: cliOptions.react,
106
111
  };
107
112
  const linterOptions = {
108
- fix: cliOptions.fix,
113
+ fix: cliOptions.fix || cliOptions.fixDryRun,
109
114
  cwd: (cliOptions.cwd && path.resolve(cliOptions.cwd)) ?? process.cwd(),
110
115
  quiet: cliOptions.quiet,
111
116
  ts: true,
@@ -114,7 +119,7 @@ const linterOptions = {
114
119
  // Make data types for `options.space` match those of the API
115
120
  if (typeof cliOptions.space === 'string') {
116
121
  cliOptions.space = cliOptions.space.trim();
117
- if (/^\d+$/u.test(cliOptions.space)) {
122
+ if (/^\d+$/v.test(cliOptions.space)) {
118
123
  baseXoConfigOptions.space = Number.parseInt(cliOptions.space, 10);
119
124
  }
120
125
  else if (cliOptions.space === 'true') {
@@ -171,7 +176,7 @@ if (cliOptions.stdin) {
171
176
  await fs.writeFile(cliOptions.stdinFilename, stdin);
172
177
  }
173
178
  }
174
- if (cliOptions.fix) {
179
+ if (linterOptions.fix) {
175
180
  const xo = new Xo(linterOptions, baseXoConfigOptions);
176
181
  const { results: [result] } = await xo.lintText(stdin, {
177
182
  filePath: cliOptions.stdinFilename,
@@ -29,7 +29,8 @@ export async function resolveXoConfig(options) {
29
29
  });
30
30
  options.filePath &&= path.resolve(options.cwd, options.filePath);
31
31
  const searchPath = options.filePath ?? options.cwd;
32
- let { config: flatOptions = [], filepath: flatConfigPath = '', } = await (options.configPath
32
+ let { config: flatOptions = [], filepath: flatConfigPath = '', // eslint-disable-line @typescript-eslint/no-useless-default-assignment
33
+ } = await (options.configPath
33
34
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
34
35
  ? flatConfigExplorer.load(path.resolve(options.cwd, options.configPath))
35
36
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -41,6 +42,7 @@ export async function resolveXoConfig(options) {
41
42
  };
42
43
  }
43
44
  catch (error) {
45
+ // eslint-disable-next-line preserve-caught-error
44
46
  throw new AggregateError([error], 'Error resolving XO config, there is likely an issue with your config file. Please check the file for mistakes.');
45
47
  }
46
48
  }
@@ -0,0 +1,3 @@
1
+ import { type Rule } from 'eslint';
2
+ declare const noUseExtendNativeRule: Rule.RuleModule;
3
+ export default noUseExtendNativeRule;
@@ -0,0 +1,386 @@
1
+ const isPropertyContainer = (value) => typeof value === 'function' || (typeof value === 'object' && value !== null);
2
+ const isIdentifierNode = (node) => node.type === 'Identifier' && 'name' in node && typeof node.name === 'string';
3
+ const isLiteralNode = (node) => node.type === 'Literal';
4
+ const isMemberExpressionNode = (node) => node.type === 'MemberExpression'
5
+ && 'computed' in node
6
+ && typeof node.computed === 'boolean'
7
+ && 'object' in node
8
+ && 'property' in node;
9
+ const isBinaryExpressionNode = (node) => node.type === 'BinaryExpression'
10
+ && 'operator' in node
11
+ && typeof node.operator === 'string'
12
+ && 'left' in node
13
+ && 'right' in node;
14
+ const isNewExpressionNode = (node) => node.type === 'NewExpression' && 'callee' in node;
15
+ const createPropertyInfo = (value, extraProperties = []) => {
16
+ const all = new Set();
17
+ const callable = new Set();
18
+ for (const propertyName of extraProperties) {
19
+ all.add(propertyName);
20
+ }
21
+ for (let currentValue = value; isPropertyContainer(currentValue); currentValue = Object.getPrototypeOf(currentValue)) {
22
+ for (const propertyName of Object.getOwnPropertyNames(currentValue)) {
23
+ all.add(propertyName);
24
+ const descriptor = Object.getOwnPropertyDescriptor(currentValue, propertyName);
25
+ if (typeof descriptor?.value === 'function') {
26
+ callable.add(propertyName);
27
+ }
28
+ }
29
+ }
30
+ return {
31
+ all,
32
+ callable,
33
+ };
34
+ };
35
+ const emptyFunction = () => undefined;
36
+ // Keep the checked native object list explicit so rule behavior stays predictable.
37
+ const nativeObjectDefinitions = [
38
+ {
39
+ typeName: 'Array',
40
+ instance: [],
41
+ static: Array,
42
+ prototype: Array.prototype,
43
+ },
44
+ {
45
+ typeName: 'ArrayBuffer',
46
+ instance: new ArrayBuffer(0),
47
+ static: ArrayBuffer,
48
+ prototype: ArrayBuffer.prototype,
49
+ },
50
+ {
51
+ typeName: 'Boolean',
52
+ instance: Boolean.prototype,
53
+ static: Boolean,
54
+ prototype: Boolean.prototype,
55
+ },
56
+ {
57
+ typeName: 'DataView',
58
+ instance: new DataView(new ArrayBuffer(1)),
59
+ static: DataView,
60
+ prototype: DataView.prototype,
61
+ },
62
+ {
63
+ typeName: 'Date',
64
+ instance: new Date(),
65
+ static: Date,
66
+ prototype: Date.prototype,
67
+ },
68
+ {
69
+ typeName: 'Float32Array',
70
+ instance: new Float32Array(),
71
+ static: Float32Array,
72
+ prototype: Float32Array.prototype,
73
+ },
74
+ {
75
+ typeName: 'Float64Array',
76
+ instance: new Float64Array(),
77
+ static: Float64Array,
78
+ prototype: Float64Array.prototype,
79
+ },
80
+ {
81
+ typeName: 'Function',
82
+ instance: emptyFunction,
83
+ static: Function,
84
+ prototype: Function.prototype,
85
+ },
86
+ {
87
+ typeName: 'Int8Array',
88
+ instance: new Int8Array(),
89
+ static: Int8Array,
90
+ prototype: Int8Array.prototype,
91
+ },
92
+ {
93
+ typeName: 'Int16Array',
94
+ instance: new Int16Array(),
95
+ static: Int16Array,
96
+ prototype: Int16Array.prototype,
97
+ },
98
+ {
99
+ typeName: 'Int32Array',
100
+ instance: new Int32Array(),
101
+ static: Int32Array,
102
+ prototype: Int32Array.prototype,
103
+ },
104
+ {
105
+ typeName: 'Map',
106
+ instance: new Map(),
107
+ static: Map,
108
+ prototype: Map.prototype,
109
+ },
110
+ {
111
+ typeName: 'Number',
112
+ instance: Number.prototype,
113
+ static: Number,
114
+ prototype: Number.prototype,
115
+ },
116
+ {
117
+ typeName: 'Object',
118
+ instance: {},
119
+ static: Object,
120
+ prototype: Object.prototype,
121
+ },
122
+ {
123
+ typeName: 'Promise',
124
+ instance: Promise.resolve(),
125
+ static: Promise,
126
+ prototype: Promise.prototype,
127
+ },
128
+ {
129
+ typeName: 'RegExp',
130
+ instance: /./v,
131
+ static: RegExp,
132
+ prototype: RegExp.prototype,
133
+ },
134
+ {
135
+ typeName: 'Set',
136
+ instance: new Set(),
137
+ static: Set,
138
+ prototype: Set.prototype,
139
+ },
140
+ {
141
+ typeName: 'String',
142
+ instance: String.prototype,
143
+ instanceProperties: ['length'],
144
+ static: String,
145
+ prototype: String.prototype,
146
+ },
147
+ {
148
+ typeName: 'Uint8Array',
149
+ instance: new Uint8Array(),
150
+ static: Uint8Array,
151
+ prototype: Uint8Array.prototype,
152
+ },
153
+ {
154
+ typeName: 'Uint8ClampedArray',
155
+ instance: new Uint8ClampedArray(),
156
+ static: Uint8ClampedArray,
157
+ prototype: Uint8ClampedArray.prototype,
158
+ },
159
+ {
160
+ typeName: 'Uint16Array',
161
+ instance: new Uint16Array(),
162
+ static: Uint16Array,
163
+ prototype: Uint16Array.prototype,
164
+ },
165
+ {
166
+ typeName: 'Uint32Array',
167
+ instance: new Uint32Array(),
168
+ static: Uint32Array,
169
+ prototype: Uint32Array.prototype,
170
+ },
171
+ {
172
+ typeName: 'URL',
173
+ instance: new URL('https://example.com'),
174
+ static: URL,
175
+ prototype: URL.prototype,
176
+ },
177
+ {
178
+ typeName: 'URLSearchParams',
179
+ instance: new URLSearchParams(),
180
+ static: URLSearchParams,
181
+ prototype: URLSearchParams.prototype,
182
+ },
183
+ {
184
+ typeName: 'JSON',
185
+ static: JSON,
186
+ },
187
+ {
188
+ typeName: 'Math',
189
+ static: Math,
190
+ },
191
+ {
192
+ typeName: 'Reflect',
193
+ static: Reflect,
194
+ },
195
+ ];
196
+ const nativeObjects = new Map();
197
+ for (const nativeObjectDefinition of nativeObjectDefinitions) {
198
+ const nativeObjectInfo = {};
199
+ if (nativeObjectDefinition.instance) {
200
+ nativeObjectInfo.instance = createPropertyInfo(nativeObjectDefinition.instance, nativeObjectDefinition.instanceProperties);
201
+ }
202
+ if (nativeObjectDefinition.prototype) {
203
+ nativeObjectInfo.prototype = createPropertyInfo(nativeObjectDefinition.prototype);
204
+ }
205
+ if (nativeObjectDefinition.static) {
206
+ nativeObjectInfo.static = createPropertyInfo(nativeObjectDefinition.static);
207
+ }
208
+ nativeObjects.set(nativeObjectDefinition.typeName, nativeObjectInfo);
209
+ }
210
+ const getPropertyName = (memberExpression) => {
211
+ const { property } = memberExpression;
212
+ if (memberExpression.computed) {
213
+ return isLiteralNode(property) && typeof property.value === 'string' ? property.value : undefined;
214
+ }
215
+ return isIdentifierNode(property) ? property.name : undefined;
216
+ };
217
+ const resolveBinaryExpressionType = (binaryExpression) => {
218
+ if (binaryExpression.operator !== '+') {
219
+ return undefined;
220
+ }
221
+ const leftReference = resolveNativeObjectReference(binaryExpression.left);
222
+ const rightReference = resolveNativeObjectReference(binaryExpression.right);
223
+ if (leftReference?.usage !== 'instance' || rightReference?.usage !== 'instance') {
224
+ return undefined;
225
+ }
226
+ if (leftReference.typeName === 'String' || rightReference.typeName === 'String') {
227
+ return {
228
+ typeName: 'String',
229
+ usage: 'instance',
230
+ };
231
+ }
232
+ return undefined;
233
+ };
234
+ const resolveIdentifierReference = (node) => {
235
+ if (!nativeObjects.has(node.name)) {
236
+ return undefined;
237
+ }
238
+ return {
239
+ typeName: node.name,
240
+ usage: 'static',
241
+ };
242
+ };
243
+ const resolveLiteralReference = (node) => {
244
+ if (node.regex) {
245
+ return {
246
+ typeName: 'RegExp',
247
+ usage: 'instance',
248
+ };
249
+ }
250
+ if (typeof node.value === 'boolean') {
251
+ return {
252
+ typeName: 'Boolean',
253
+ usage: 'instance',
254
+ };
255
+ }
256
+ if (typeof node.value === 'number') {
257
+ return {
258
+ typeName: 'Number',
259
+ usage: 'instance',
260
+ };
261
+ }
262
+ if (typeof node.value === 'string') {
263
+ return {
264
+ typeName: 'String',
265
+ usage: 'instance',
266
+ };
267
+ }
268
+ return undefined;
269
+ };
270
+ const resolvePrototypeReference = (node) => {
271
+ if (getPropertyName(node) !== 'prototype' || !isIdentifierNode(node.object) || !nativeObjects.has(node.object.name)) {
272
+ return undefined;
273
+ }
274
+ return {
275
+ typeName: node.object.name,
276
+ usage: 'prototype',
277
+ };
278
+ };
279
+ const resolveNewExpressionReference = (node) => {
280
+ if (!isIdentifierNode(node.callee) || !nativeObjects.has(node.callee.name)) {
281
+ return undefined;
282
+ }
283
+ return {
284
+ typeName: node.callee.name,
285
+ usage: 'instance',
286
+ };
287
+ };
288
+ function resolveNativeObjectReference(node) {
289
+ if (!node) {
290
+ return undefined;
291
+ }
292
+ if (isMemberExpressionNode(node)) {
293
+ return resolvePrototypeReference(node);
294
+ }
295
+ if (isBinaryExpressionNode(node)) {
296
+ return resolveBinaryExpressionType(node);
297
+ }
298
+ if (isLiteralNode(node)) {
299
+ return resolveLiteralReference(node);
300
+ }
301
+ if (isIdentifierNode(node)) {
302
+ return resolveIdentifierReference(node);
303
+ }
304
+ if (isNewExpressionNode(node)) {
305
+ return resolveNewExpressionReference(node);
306
+ }
307
+ switch (node.type) {
308
+ case 'ArrayExpression': {
309
+ return {
310
+ typeName: 'Array',
311
+ usage: 'instance',
312
+ };
313
+ }
314
+ case 'ArrowFunctionExpression':
315
+ case 'FunctionExpression': {
316
+ return {
317
+ typeName: 'Function',
318
+ usage: 'instance',
319
+ };
320
+ }
321
+ case 'ObjectExpression': {
322
+ return {
323
+ typeName: 'Object',
324
+ usage: 'instance',
325
+ };
326
+ }
327
+ case 'TemplateLiteral': {
328
+ return {
329
+ typeName: 'String',
330
+ usage: 'instance',
331
+ };
332
+ }
333
+ default: {
334
+ return undefined;
335
+ }
336
+ }
337
+ }
338
+ const noUseExtendNativeRule = {
339
+ meta: {
340
+ type: 'problem',
341
+ docs: {
342
+ description: 'Disallow relying on non-standard properties on native objects',
343
+ },
344
+ messages: {
345
+ unexpected: 'Avoid relying on extended native objects.',
346
+ },
347
+ schema: [],
348
+ },
349
+ create(context) {
350
+ return {
351
+ // eslint-disable-next-line @typescript-eslint/naming-convention
352
+ MemberExpression(node) {
353
+ const propertyName = getPropertyName(node);
354
+ if (!propertyName) {
355
+ return;
356
+ }
357
+ const nativeObjectReference = resolveNativeObjectReference(node.object);
358
+ if (!nativeObjectReference) {
359
+ return;
360
+ }
361
+ const propertyInfo = nativeObjects.get(nativeObjectReference.typeName)?.[nativeObjectReference.usage];
362
+ if (!propertyInfo) {
363
+ return;
364
+ }
365
+ const isCall = node.parent.type === 'CallExpression' && node.parent.callee === node;
366
+ if (isCall) {
367
+ if (!propertyInfo.callable.has(propertyName)) {
368
+ context.report({
369
+ node,
370
+ messageId: 'unexpected',
371
+ });
372
+ }
373
+ return;
374
+ }
375
+ if (!propertyInfo.all.has(propertyName)) {
376
+ context.report({
377
+ node,
378
+ messageId: 'unexpected',
379
+ });
380
+ }
381
+ },
382
+ };
383
+ },
384
+ };
385
+ export default noUseExtendNativeRule;
386
+ //# sourceMappingURL=no-use-extend-native.js.map
@@ -107,12 +107,12 @@ export function xoToEslintConfig(flatXoConfig, { prettierOptions = {} } = {}) {
107
107
  if (xoConfigItem.react) {
108
108
  // Ensure the files applied to the React config are the same as the config they are derived from
109
109
  // TODO: Remove `fixupConfigRules` wrapping when eslint-config-xo-react supports ESLint 10 natively.
110
- baseConfig.push({ ...fixupConfigRules(configReact)[0], files: eslintConfigItem.files, name: 'xo/react' });
110
+ baseConfig.push({ ...fixupConfigRules(configReact)[0], ...(eslintConfigItem.files ? { files: eslintConfigItem.files } : {}), name: 'xo/react' });
111
111
  }
112
112
  // Prettier should generally be the last config in the array
113
113
  if (xoConfigItem.prettier) {
114
114
  if (xoConfigItem.prettier === 'compat') {
115
- baseConfig.push({ ...eslintConfigPrettier, files: eslintConfigItem.files });
115
+ baseConfig.push({ ...eslintConfigPrettier, ...(eslintConfigItem.files ? { files: eslintConfigItem.files } : {}) });
116
116
  }
117
117
  else {
118
118
  // Validate that Prettier options match other `xoConfig` options.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xo",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "JavaScript/TypeScript linter (ESLint wrapper) with great defaults",
5
5
  "license": "MIT",
6
6
  "repository": "xojs/xo",
@@ -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
  ],
@@ -74,7 +76,7 @@
74
76
  "eslint": "^10.0.2",
75
77
  "eslint-config-prettier": "^10.1.8",
76
78
  "eslint-config-xo-react": "^0.29.0",
77
- "eslint-config-xo-typescript": "^9.0.0",
79
+ "eslint-config-xo-typescript": "^10.0.0",
78
80
  "eslint-formatter-pretty": "^7.0.0",
79
81
  "eslint-plugin-ava": "^16.0.0",
80
82
  "eslint-plugin-import-x": "^4.16.1",
@@ -101,11 +103,11 @@
101
103
  "@types/micromatch": "^4.0.10",
102
104
  "@types/node": "^25.2.1",
103
105
  "@types/prettier": "^3.0.0",
104
- "ava": "^6.4.1",
106
+ "ava": "^7.0.0",
105
107
  "dedent": "^1.7.1",
106
108
  "execa": "^9.6.1",
107
109
  "husky": "^9.1.7",
108
- "lint-staged": "^16.2.7",
110
+ "lint-staged": "^16.3.4",
109
111
  "np": "^11.0.2",
110
112
  "npm-package-json-lint": "^9.1.0",
111
113
  "npm-package-json-lint-config-default": "^8.0.1",
package/readme.md CHANGED
@@ -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