@blumintinc/eslint-plugin-blumint 1.20.49 → 1.20.51
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/lib/index.js +1 -1
- package/lib/rules/consistent-callback-naming.js +13 -5
- package/lib/rules/enforce-dynamic-imports.js +3 -1
- package/lib/rules/enforce-early-destructuring.js +13 -3
- package/lib/rules/enforce-microdiff.js +169 -72
- package/lib/rules/enforce-object-literal-as-const.js +185 -4
- package/lib/rules/enforce-timestamp-now.d.ts +2 -1
- package/lib/rules/enforce-timestamp-now.js +226 -71
- package/lib/rules/fast-deep-equal-over-microdiff.js +83 -38
- package/lib/rules/no-class-instance-destructuring.d.ts +2 -1
- package/lib/rules/no-class-instance-destructuring.js +240 -32
- package/lib/utils/fastDeepEqualModules.d.ts +30 -0
- package/lib/utils/fastDeepEqualModules.js +40 -0
- package/lib/utils/microdiffModules.d.ts +15 -0
- package/lib/utils/microdiffModules.js +22 -0
- package/package.json +1 -1
- package/release-manifest.json +84 -0
package/lib/index.js
CHANGED
|
@@ -52,7 +52,7 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
52
52
|
messages: {
|
|
53
53
|
callbackPropPrefix: 'Callback prop "{{propName}}" is a function but lacks the "on" prefix. ' +
|
|
54
54
|
'Consistent "on" prefixes signal event handlers to consumers and distinguish callbacks from data props. ' +
|
|
55
|
-
'Rename to "on{{eventName}}".',
|
|
55
|
+
'Rename to "on{{eventName}}" here, in the props type that declares it, and in every reader of that prop.',
|
|
56
56
|
callbackFunctionPrefix: 'Function "{{functionName}}" uses the "handle" prefix. ' +
|
|
57
57
|
'The "handle" prefix is redundant and less descriptive than action-oriented verb phrases. ' +
|
|
58
58
|
'Rename using a descriptive verb (e.g., click instead of handleClick).',
|
|
@@ -252,6 +252,18 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
252
252
|
!isRenderFunction(node.value.expression) &&
|
|
253
253
|
!isReactComponentType(node.value.expression)) {
|
|
254
254
|
const eventName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
|
255
|
+
// Reported without an autofix (Bug #1522). A JSX attribute name is
|
|
256
|
+
// one end of a props contract: the other end is the declaration
|
|
257
|
+
// that binds the name — a props `type`/`interface`, a
|
|
258
|
+
// `JSX.IntrinsicElements` augmentation for host elements — plus
|
|
259
|
+
// every reader of that member (`props.validate`, destructuring) and
|
|
260
|
+
// every other call site of the component. Rewriting only the
|
|
261
|
+
// attribute yields TS2322, and renaming the local declaration
|
|
262
|
+
// as well merely relocates the break: readers in the same file fail
|
|
263
|
+
// with TS2551 and call sites in other files (which a single-file
|
|
264
|
+
// fixer cannot see, let alone edit atomically) fail with TS2322.
|
|
265
|
+
// No subset of the rename is safe to apply in isolation, so the
|
|
266
|
+
// report carries the full instruction instead of a broken fix.
|
|
255
267
|
context.report({
|
|
256
268
|
node,
|
|
257
269
|
messageId: 'callbackPropPrefix',
|
|
@@ -259,10 +271,6 @@ module.exports = (0, createRule_1.createRule)({
|
|
|
259
271
|
propName,
|
|
260
272
|
eventName,
|
|
261
273
|
},
|
|
262
|
-
fix(fixer) {
|
|
263
|
-
// Convert camelCase to PascalCase for the event name
|
|
264
|
-
return fixer.replaceText(node.name, `on${eventName}`);
|
|
265
|
-
},
|
|
266
274
|
});
|
|
267
275
|
}
|
|
268
276
|
}
|
|
@@ -36,9 +36,11 @@ exports.DEFAULT_IGNORED_LIBRARIES = [
|
|
|
36
36
|
'use-latest-callback',
|
|
37
37
|
'@blumintinc/typescript-memoize',
|
|
38
38
|
'@blumintinc/use-deep-compare',
|
|
39
|
+
'@blumintinc/microdiff',
|
|
39
40
|
'microdiff',
|
|
40
41
|
'safe-stable-stringify',
|
|
41
|
-
'fast-deep-equal',
|
|
42
|
+
'@blumintinc/fast-deep-equal',
|
|
43
|
+
'fast-deep-equal', // fast-deep-equal-over-microdiff, for files already on upstream
|
|
42
44
|
];
|
|
43
45
|
exports.DEFAULT_INTERNAL_PREFIXES = ['src/', 'functions/'];
|
|
44
46
|
// Pre-built set of Node.js core module names for O(1) lookup.
|
|
@@ -407,8 +407,9 @@ function renderArrayPatternWithDefaults(pattern, sourceCode) {
|
|
|
407
407
|
return `${leftText} = ${sourceCode.getText(element.right)}`;
|
|
408
408
|
}
|
|
409
409
|
if (element.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
// No synthesized `= {}` here either, for the reason spelled out in
|
|
411
|
+
// formatPropertyText: the default is checked against the bindings under it.
|
|
412
|
+
return renderObjectPatternWithDefaults(element, sourceCode);
|
|
412
413
|
}
|
|
413
414
|
if (element.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
414
415
|
const nested = renderArrayPatternWithDefaults(element, sourceCode);
|
|
@@ -441,10 +442,19 @@ function formatPropertyText(property, sourceCode) {
|
|
|
441
442
|
return renderPropertyWithAssignment(property, value, keyText, sourceCode);
|
|
442
443
|
}
|
|
443
444
|
if (value.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
445
|
+
// A nested object pattern is re-emitted as authored, without a synthesized
|
|
446
|
+
// `= {}`. TypeScript checks such a default against every binding element
|
|
447
|
+
// beneath it, so `{ profile: { name, age } = {} }` reports TS2525 once per
|
|
448
|
+
// name: `{}` supplies no value and the names carry no defaults of their own.
|
|
449
|
+
// The default also only ever guarded a nullish parent (an explicitly `null`
|
|
450
|
+
// one still throws), so dropping it costs a partial runtime guard and buys
|
|
451
|
+
// back the invariant that compiling input yields compiling output.
|
|
444
452
|
const nested = renderObjectPatternWithDefaults(value, sourceCode);
|
|
445
|
-
return `${keyText}: ${nested}
|
|
453
|
+
return `${keyText}: ${nested}`;
|
|
446
454
|
}
|
|
447
455
|
if (value.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
|
|
456
|
+
// An array pattern's `= []` default is safe to synthesize: TypeScript does
|
|
457
|
+
// not push it down onto the element bindings the way it does for objects.
|
|
448
458
|
const nested = renderArrayPatternWithDefaults(value, sourceCode);
|
|
449
459
|
return `${keyText}: ${nested} = []`;
|
|
450
460
|
}
|
|
@@ -5,7 +5,25 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
7
|
const DIFF_NAME = 'diff';
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The package the fix imports from. BluMint's fork is the dependency this
|
|
10
|
+
* codebase declares, and every call site resolves against it.
|
|
11
|
+
*/
|
|
12
|
+
const MICRODIFF_MODULE = '@blumintinc/microdiff';
|
|
13
|
+
/**
|
|
14
|
+
* The specifiers that already satisfy this rule. The fix emits the fork, but a
|
|
15
|
+
* file importing upstream `microdiff` is diffing structurally all the same, so
|
|
16
|
+
* both are recognised on the way in: an unrecognised one would earn a second
|
|
17
|
+
* import of the same binding (TS2300).
|
|
18
|
+
*/
|
|
19
|
+
const MICRODIFF_MODULES = new Set([MICRODIFF_MODULE, 'microdiff']);
|
|
20
|
+
/**
|
|
21
|
+
* The import the fix emits. Both packages export their diff function as the
|
|
22
|
+
* module *default* — `Difference`, `MicrodiffOptions` and the `default*`
|
|
23
|
+
* predicates are the only named exports — so a `{ diff }` specifier binds
|
|
24
|
+
* nothing (TS2305) however the module resolves.
|
|
25
|
+
*/
|
|
26
|
+
const MICRODIFF_IMPORT = `import ${DIFF_NAME} from '${MICRODIFF_MODULE}';`;
|
|
9
27
|
/**
|
|
10
28
|
* The names a competing library's diff export is conventionally bound to. They
|
|
11
29
|
* are candidates for a report, never proof of one: what a call resolves to
|
|
@@ -68,7 +86,7 @@ function bindsMicrodiffDiff(specifier) {
|
|
|
68
86
|
*/
|
|
69
87
|
function findMicrodiffImport(program) {
|
|
70
88
|
return program.body.find((statement) => statement.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
71
|
-
statement.source.value
|
|
89
|
+
MICRODIFF_MODULES.has(String(statement.source.value)) &&
|
|
72
90
|
statement.importKind !== 'type' &&
|
|
73
91
|
statement.specifiers.some(bindsMicrodiffDiff));
|
|
74
92
|
}
|
|
@@ -85,7 +103,7 @@ function collectClaimableSpecifiers(program) {
|
|
|
85
103
|
return;
|
|
86
104
|
}
|
|
87
105
|
const source = statement.source.value;
|
|
88
|
-
if (source
|
|
106
|
+
if (MICRODIFF_MODULES.has(source)) {
|
|
89
107
|
statement.specifiers
|
|
90
108
|
.filter(bindsMicrodiffDiff)
|
|
91
109
|
.forEach((specifier) => claimable.add(specifier));
|
|
@@ -166,6 +184,28 @@ function toImportedDiffSource(variable, importedSpecifiers) {
|
|
|
166
184
|
}
|
|
167
185
|
return null;
|
|
168
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Whether a call carries operands `diff(obj, newObj, options?)` accepts.
|
|
189
|
+
* microdiff needs both sides of the comparison, so a call supplying fewer is
|
|
190
|
+
* reported but left alone: rewriting it would trade an unresolved name for a
|
|
191
|
+
* call that does not type-check (TS2554).
|
|
192
|
+
*/
|
|
193
|
+
function hasRewritableArity(call) {
|
|
194
|
+
return call.arguments.length >= 2;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Whether a reference sits where the call fix rewrites it — as the callee of a
|
|
198
|
+
* convertible call. A reference in any other position (passed as a value,
|
|
199
|
+
* assigned to a variable, re-exported) has no rewrite of its own, so retiring
|
|
200
|
+
* the import that binds it would leave it unresolved (TS2304).
|
|
201
|
+
*/
|
|
202
|
+
function isRewrittenCallee(identifier) {
|
|
203
|
+
const parent = identifier.parent;
|
|
204
|
+
return (!!parent &&
|
|
205
|
+
parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
206
|
+
parent.callee === identifier &&
|
|
207
|
+
hasRewritableArity(parent));
|
|
208
|
+
}
|
|
169
209
|
/**
|
|
170
210
|
* Whether a bare `diff` written at `scope` reaches microdiff's function.
|
|
171
211
|
* Resolving through the scope chain catches both failure modes: a module-scope
|
|
@@ -221,21 +261,86 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
221
261
|
return canEmitDiff(ASTHelpers_1.ASTHelpers.getScope(context, node), collectClaimableSpecifiers(sourceCode.ast));
|
|
222
262
|
}
|
|
223
263
|
/**
|
|
224
|
-
* Whether
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
264
|
+
* Whether the call handler rewrites the calls a specifier's references
|
|
265
|
+
* make. It fires on a specifier the import handler tracked, or on a local
|
|
266
|
+
* name a competing library conventionally binds; a specifier matching
|
|
267
|
+
* neither — `applyChange` from `deep-diff`, say — keeps its call sites, so
|
|
268
|
+
* its references survive only if the import that binds them does.
|
|
229
269
|
*/
|
|
230
|
-
function
|
|
270
|
+
function isRewrittenSpecifier(specifier) {
|
|
271
|
+
return (importedDiffSpecifiers.has(specifier) ||
|
|
272
|
+
DIFF_FUNCTION_NAMES.has(specifier.local.name));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Whether retiring `declaration` leaves every name it binds accounted for.
|
|
276
|
+
* The fix removes the whole declaration, so each reference it serves has to
|
|
277
|
+
* be one the call fixes rewrite to `diff` in the same pass — an import swap
|
|
278
|
+
* that strands a reference behind is the defect this guards.
|
|
279
|
+
*
|
|
280
|
+
* Two things have to hold at every reference. `diff` must reach microdiff
|
|
281
|
+
* there: the import rewrite lands at module scope while the references it
|
|
282
|
+
* serves sit in nested scopes, and one standing where `diff` is shadowed
|
|
283
|
+
* would resolve to the shadow. And the reference has to sit where a rewrite
|
|
284
|
+
* exists at all, which is the callee position of a convertible call.
|
|
285
|
+
*
|
|
286
|
+
* A specifier nothing references is vacuously safe: retiring it removes an
|
|
287
|
+
* import no code reads.
|
|
288
|
+
*/
|
|
289
|
+
function canRetireImport(declaration) {
|
|
231
290
|
const claimable = collectClaimableSpecifiers(sourceCode.ast);
|
|
232
291
|
const declarationScope = ASTHelpers_1.ASTHelpers.getScope(context, declaration);
|
|
233
292
|
return declaration.specifiers.every((specifier) => {
|
|
234
293
|
const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(declarationScope, specifier.local.name);
|
|
235
|
-
|
|
236
|
-
|
|
294
|
+
if (!variable) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
const rewritten = isRewrittenSpecifier(specifier);
|
|
298
|
+
return variable.references.every((reference) => canEmitDiff(reference.from, claimable) &&
|
|
299
|
+
rewritten &&
|
|
300
|
+
isRewrittenCallee(reference.identifier));
|
|
237
301
|
});
|
|
238
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Whether the import fix retires `declaration` in this same pass.
|
|
305
|
+
*/
|
|
306
|
+
function willRetireImport(declaration) {
|
|
307
|
+
return canEmitDiffAt(declaration) && canRetireImport(declaration);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Whether the call at `node`, whose callee `declaration` binds, may be
|
|
311
|
+
* rewritten to `diff(...)`. Separate from the decision to report: a call
|
|
312
|
+
* this rule cannot convert is still a use of a competing library, so it is
|
|
313
|
+
* reported and left for the author.
|
|
314
|
+
*
|
|
315
|
+
* The rename needs something to bind the `diff` it writes — an import the
|
|
316
|
+
* file already has, or the one that replaces `declaration` in this same
|
|
317
|
+
* pass. Renaming a callee whose import survives strands the rewritten call
|
|
318
|
+
* exactly as retiring an import whose callee survives strands that one.
|
|
319
|
+
*/
|
|
320
|
+
function canRewriteCall(node, declaration) {
|
|
321
|
+
if (!canEmitDiffAt(node) || !hasRewritableArity(node)) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
if (findMicrodiffImport(sourceCode.ast)) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
return !!declaration && willRetireImport(declaration);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* The fix element that puts microdiff's import at the top of the file, or
|
|
331
|
+
* null when the file already has one.
|
|
332
|
+
*
|
|
333
|
+
* It is its own element rather than text spliced into the replacement for
|
|
334
|
+
* the reported node, because the reported node is rarely at module scope: a
|
|
335
|
+
* comparison inside a function body, or a function behind an `export`,
|
|
336
|
+
* would otherwise take the import somewhere the grammar forbids it.
|
|
337
|
+
*/
|
|
338
|
+
function buildMicrodiffImportFix(fixer) {
|
|
339
|
+
if (findMicrodiffImport(sourceCode.ast)) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
return fixer.insertTextBeforeRange([0, 0], `${MICRODIFF_IMPORT}\n\n`);
|
|
343
|
+
}
|
|
239
344
|
// Add a specific set to track which import names are used
|
|
240
345
|
const usedImportNames = new Set();
|
|
241
346
|
// Check if a node is an object or array type
|
|
@@ -272,7 +377,7 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
272
377
|
ImportDeclaration(node) {
|
|
273
378
|
const importSource = node.source.value;
|
|
274
379
|
// Check for microdiff import
|
|
275
|
-
if (importSource
|
|
380
|
+
if (MICRODIFF_MODULES.has(importSource)) {
|
|
276
381
|
return;
|
|
277
382
|
}
|
|
278
383
|
// Track other diffing libraries
|
|
@@ -296,9 +401,10 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
296
401
|
},
|
|
297
402
|
fix(fixer) {
|
|
298
403
|
// Decline rather than duplicate or shadow a `diff` this file
|
|
299
|
-
// already binds to something else
|
|
300
|
-
//
|
|
301
|
-
|
|
404
|
+
// already binds to something else, and rather than retire an
|
|
405
|
+
// import whose references no call fix rewrites. The report stands
|
|
406
|
+
// either way, so the author resolves it deliberately.
|
|
407
|
+
if (!canEmitDiffAt(node) || !canRetireImport(node)) {
|
|
302
408
|
return null;
|
|
303
409
|
}
|
|
304
410
|
// If we already have a microdiff import, just remove this import
|
|
@@ -306,7 +412,7 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
306
412
|
return fixer.remove(node);
|
|
307
413
|
}
|
|
308
414
|
// Otherwise, replace with microdiff import
|
|
309
|
-
return fixer.replaceText(node,
|
|
415
|
+
return fixer.replaceText(node, MICRODIFF_IMPORT);
|
|
310
416
|
},
|
|
311
417
|
});
|
|
312
418
|
// Check if importing a diff function or a known equality library
|
|
@@ -346,6 +452,9 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
346
452
|
// Check if this call resolves to a function we specifically
|
|
347
453
|
// imported from a diff library
|
|
348
454
|
const importSource = toImportedDiffSource(calleeVariable, importedDiffSpecifiers);
|
|
455
|
+
// The declaration the rename depends on: retiring it is what frees
|
|
456
|
+
// the name `diff` and imports something under it.
|
|
457
|
+
const competingImport = toCompetingDiffImport(calleeVariable);
|
|
349
458
|
if (importSource) {
|
|
350
459
|
usedImportNames.add(name);
|
|
351
460
|
// Skip reporting if it's from fast-deep-equal
|
|
@@ -358,7 +467,7 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
358
467
|
node,
|
|
359
468
|
messageId: 'enforceMicrodiff',
|
|
360
469
|
fix(fixer) {
|
|
361
|
-
if (!
|
|
470
|
+
if (!canRewriteCall(node, competingImport)) {
|
|
362
471
|
return null;
|
|
363
472
|
}
|
|
364
473
|
return fixer.replaceText(callee, DIFF_NAME);
|
|
@@ -370,29 +479,28 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
370
479
|
// The name is only a candidate until the scope chain says what it
|
|
371
480
|
// binds: a local function, variable, parameter, or an import from
|
|
372
481
|
// anywhere but a competing diff library keeps its call untouched.
|
|
373
|
-
const competingImport = toCompetingDiffImport(calleeVariable);
|
|
374
482
|
if (!competingImport) {
|
|
375
483
|
return;
|
|
376
484
|
}
|
|
377
485
|
// Track this import name as used
|
|
378
486
|
usedImportNames.add(name);
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
}
|
|
487
|
+
// What the callee resolves to already settles this: the argument
|
|
488
|
+
// shapes say nothing a competing library's own import has not.
|
|
489
|
+
// Gating the report on them left the call behind while the import
|
|
490
|
+
// handler retired the declaration binding it, so `deepDiff(a, b)`
|
|
491
|
+
// came out of a fix unresolved.
|
|
492
|
+
reportedNodes.add(node);
|
|
493
|
+
context.report({
|
|
494
|
+
node,
|
|
495
|
+
messageId: 'enforceMicrodiff',
|
|
496
|
+
fix(fixer) {
|
|
497
|
+
if (!canRewriteCall(node, competingImport)) {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
// When handling fast-diff and similar libraries, need to ensure the function name is replaced
|
|
501
|
+
return fixer.replaceText(callee, DIFF_NAME);
|
|
502
|
+
},
|
|
503
|
+
});
|
|
396
504
|
}
|
|
397
505
|
}
|
|
398
506
|
}
|
|
@@ -461,28 +569,13 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
461
569
|
if (!canEmitDiffAt(node)) {
|
|
462
570
|
return null;
|
|
463
571
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
functionNode = functionNode.parent;
|
|
472
|
-
}
|
|
473
|
-
// If we found a program node and microdiff isn't imported,
|
|
474
|
-
// we'll need to add the import manually
|
|
475
|
-
if (functionNode &&
|
|
476
|
-
functionNode.type === utils_1.AST_NODE_TYPES.Program &&
|
|
477
|
-
!findMicrodiffImport(sourceCode.ast)) {
|
|
478
|
-
// Need to add an import
|
|
479
|
-
const importFix = fixer.insertTextBeforeRange([0, 0], `import { ${DIFF_NAME} } from '${MICRODIFF_MODULE}';\n\n`);
|
|
480
|
-
// Replace JSON.stringify comparison
|
|
481
|
-
const compareFix = fixer.replaceText(node, `${DIFF_NAME}(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
482
|
-
return [importFix, compareFix];
|
|
483
|
-
}
|
|
484
|
-
// Otherwise just replace the comparison
|
|
485
|
-
return fixer.replaceText(node, `${DIFF_NAME}(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
572
|
+
const compareFix = fixer.replaceText(node, `${DIFF_NAME}(${sourceCode.getText(leftArg)}, ${sourceCode.getText(rightArg)})${isEqual ? '.length === 0' : '.length > 0'}`);
|
|
573
|
+
// The comparison this rewrites almost always sits inside a
|
|
574
|
+
// function, and the `diff` it emits needs an import whatever
|
|
575
|
+
// encloses it. Deciding on the enclosing node left every
|
|
576
|
+
// nested comparison calling a `diff` nothing bound.
|
|
577
|
+
const importFix = buildMicrodiffImportFix(fixer);
|
|
578
|
+
return importFix ? [importFix, compareFix] : compareFix;
|
|
486
579
|
},
|
|
487
580
|
});
|
|
488
581
|
}
|
|
@@ -518,27 +611,31 @@ exports.enforceMicrodiff = (0, createRule_1.createRule)({
|
|
|
518
611
|
bodyText.includes('JSON.stringify') &&
|
|
519
612
|
bodyText.includes('!==')) {
|
|
520
613
|
reportedNodes.add(node);
|
|
521
|
-
|
|
522
|
-
|
|
614
|
+
// The operands are the parameter *names*, not the text of the
|
|
615
|
+
// parameters: a typed parameter's text carries its annotation,
|
|
616
|
+
// and `diff(oldConfig: Config, ...)` does not parse. A parameter
|
|
617
|
+
// that binds no single name — a destructuring or rest pattern —
|
|
618
|
+
// has no operand to pass, so the report stands without a fix.
|
|
619
|
+
const [firstParam, secondParam] = node.params;
|
|
620
|
+
const operands = firstParam.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
621
|
+
secondParam.type === utils_1.AST_NODE_TYPES.Identifier
|
|
622
|
+
? [firstParam.name, secondParam.name]
|
|
623
|
+
: null;
|
|
523
624
|
context.report({
|
|
524
625
|
node,
|
|
525
626
|
messageId: 'enforceMicrodiff',
|
|
526
627
|
fix(fixer) {
|
|
527
|
-
if (!canEmitDiffAt(node)) {
|
|
628
|
+
if (!operands || !canEmitDiffAt(node)) {
|
|
528
629
|
return null;
|
|
529
630
|
}
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
else {
|
|
539
|
-
// Just replace the function body
|
|
540
|
-
return fixer.replaceText(body, newFunctionBody);
|
|
541
|
-
}
|
|
631
|
+
// Only the body is rewritten, so the signature keeps its type
|
|
632
|
+
// annotations, its modifiers, and any `export` in front of
|
|
633
|
+
// it. Replacing the declaration wholesale used to drop those
|
|
634
|
+
// and, when it prefixed the import, put an `import` inside
|
|
635
|
+
// whatever enclosed the function.
|
|
636
|
+
const bodyFix = fixer.replaceText(body, `{\n return ${DIFF_NAME}(${operands[0]}, ${operands[1]}).length > 0;\n}`);
|
|
637
|
+
const importFix = buildMicrodiffImportFix(fixer);
|
|
638
|
+
return importFix ? [importFix, bodyFix] : bodyFix;
|
|
542
639
|
},
|
|
543
640
|
});
|
|
544
641
|
return;
|