xo 2.0.0 → 2.0.1

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,
@@ -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,
@@ -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: /./u,
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xo",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
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