@trackunit/eslint-plugin-trackunit 0.6.50 → 0.6.52
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/lib/config/fragments/typescript-rules.js +1 -1
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.d.ts +1 -15
- package/src/lib/rules/prefer-event-specific-callback-naming/utils.js +0 -81
- package/src/lib/utils/ast-utils.js +0 -39
- package/src/lib/utils/classname-utils.js +0 -80
- package/src/lib/utils/import-utils.js +0 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 0.6.52 (2026-07-01)
|
|
2
|
+
|
|
3
|
+
### 🧱 Updated Dependencies
|
|
4
|
+
|
|
5
|
+
- Updated shared-utils to 1.15.52
|
|
6
|
+
|
|
7
|
+
## 0.6.51 (2026-07-01)
|
|
8
|
+
|
|
9
|
+
### 🧱 Updated Dependencies
|
|
10
|
+
|
|
11
|
+
- Updated shared-utils to 1.15.51
|
|
12
|
+
|
|
1
13
|
## 0.6.50 (2026-06-30)
|
|
2
14
|
|
|
3
15
|
### 🧱 Updated Dependencies
|
package/package.json
CHANGED
|
@@ -4,19 +4,6 @@
|
|
|
4
4
|
* used by both string-based and type-based detection strategies.
|
|
5
5
|
*/
|
|
6
6
|
import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
7
|
-
/**
|
|
8
|
-
* Result of extracting callback information from an expression.
|
|
9
|
-
* Does not include messageId - each strategy adds its own.
|
|
10
|
-
*/
|
|
11
|
-
export type CallbackExtractionResult = {
|
|
12
|
-
callbackName: string;
|
|
13
|
-
suggestedName: string;
|
|
14
|
-
node: TSESTree.Node;
|
|
15
|
-
/** The identifier node for the callback - used for fix ranges */
|
|
16
|
-
identifierNode: TSESTree.Identifier;
|
|
17
|
-
/** Whether the callback is wrapped in an arrow function */
|
|
18
|
-
isWrapped: boolean;
|
|
19
|
-
} | null;
|
|
20
7
|
/**
|
|
21
8
|
* Information about a locally defined variable, including its definition
|
|
22
9
|
* and all references within the same file.
|
|
@@ -40,8 +27,7 @@ export type LocalDefinitionInfo = {
|
|
|
40
27
|
export declare const findLocalDefinitionInfo: (identifier: TSESTree.Identifier, context: TSESLint.RuleContext<string, ReadonlyArray<unknown>>) => LocalDefinitionInfo | null;
|
|
41
28
|
/**
|
|
42
29
|
* Result of extracting a callback identifier from an expression.
|
|
43
|
-
*
|
|
44
|
-
* since those depend on the specific event handler being used.
|
|
30
|
+
* Does not include suggested names since those depend on the specific event handler being used.
|
|
45
31
|
*/
|
|
46
32
|
export type CallbackIdentifierResult = {
|
|
47
33
|
callbackName: string;
|
|
@@ -105,87 +105,6 @@ const findLocalDefinitionInfo = (identifier, context) => {
|
|
|
105
105
|
};
|
|
106
106
|
};
|
|
107
107
|
exports.findLocalDefinitionInfo = findLocalDefinitionInfo;
|
|
108
|
-
/**
|
|
109
|
-
* Checks if a callback name is problematic when used with click handlers.
|
|
110
|
-
* A name is problematic if it starts with "on" but not "onClick".
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* isProblematicName("onClose") // true - should be onClickClose
|
|
114
|
-
* isProblematicName("onClickClose") // false - already correct
|
|
115
|
-
* isProblematicName("handleClose") // false - different convention
|
|
116
|
-
* isProblematicName("close") // false - doesn't start with "on"
|
|
117
|
-
*/
|
|
118
|
-
const isProblematicName = (name) => {
|
|
119
|
-
return name.startsWith("on") && !name.startsWith("onClick");
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* Converts a problematic callback name to the suggested onClick* format.
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* getSuggestedName("onClose") // "onClickClose"
|
|
126
|
-
* getSuggestedName("onCancel") // "onClickCancel"
|
|
127
|
-
*/
|
|
128
|
-
const getSuggestedName = (name) => {
|
|
129
|
-
// Remove "on" prefix and add "onClick" prefix
|
|
130
|
-
return `onClick${name.slice(2)}`;
|
|
131
|
-
};
|
|
132
|
-
/**
|
|
133
|
-
* Extracts callback info from a direct identifier pattern: onClick={onClose}
|
|
134
|
-
*/
|
|
135
|
-
const extractFromDirectIdentifier = (expression) => {
|
|
136
|
-
if (expression.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
const callbackName = expression.name;
|
|
140
|
-
if (!isProblematicName(callbackName)) {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
callbackName,
|
|
145
|
-
suggestedName: getSuggestedName(callbackName),
|
|
146
|
-
node: expression,
|
|
147
|
-
identifierNode: expression,
|
|
148
|
-
isWrapped: false,
|
|
149
|
-
};
|
|
150
|
-
};
|
|
151
|
-
/**
|
|
152
|
-
* Extracts callback info from a single-call arrow function: onClick={() => onClose()}
|
|
153
|
-
*/
|
|
154
|
-
const extractFromSingleCallArrow = (expression) => {
|
|
155
|
-
if (expression.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
const { body } = expression;
|
|
159
|
-
// Only check if body is a single call expression (not a block statement)
|
|
160
|
-
if (body.type !== utils_1.AST_NODE_TYPES.CallExpression) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
// Only check if the callee is a simple identifier
|
|
164
|
-
if (body.callee.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
const callbackName = body.callee.name;
|
|
168
|
-
if (!isProblematicName(callbackName)) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
callbackName,
|
|
173
|
-
suggestedName: getSuggestedName(callbackName),
|
|
174
|
-
node: body.callee,
|
|
175
|
-
identifierNode: body.callee,
|
|
176
|
-
isWrapped: true,
|
|
177
|
-
};
|
|
178
|
-
};
|
|
179
|
-
/**
|
|
180
|
-
* Extracts callback information from an expression.
|
|
181
|
-
* Handles both direct identifiers (onClick={onClose}) and
|
|
182
|
-
* single-call arrow functions (onClick={() => onClose()}).
|
|
183
|
-
*
|
|
184
|
-
* @returns Callback extraction result without messageId, or null if no problematic callback found
|
|
185
|
-
*/
|
|
186
|
-
const extractCallbackInfo = (expression) => {
|
|
187
|
-
return extractFromDirectIdentifier(expression) ?? extractFromSingleCallArrow(expression);
|
|
188
|
-
};
|
|
189
108
|
/**
|
|
190
109
|
* Extracts the callback identifier from an expression without checking naming patterns.
|
|
191
110
|
* Used by strict prop events check which has its own naming criteria.
|
|
@@ -12,7 +12,6 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
12
12
|
* - `isParameterExplicitlyDestructured()` - Check if parameter is explicitly destructured
|
|
13
13
|
* - `isParameterInitialized()` - Check if parameter has a default value
|
|
14
14
|
* - `isParameterUsedInFunction()` - Check if parameter is used in function body
|
|
15
|
-
* - `getParameterRestUsage()` - Get info about parameter accessed via rest params
|
|
16
15
|
* - `hasExistingBodyInitializations()` - Check for initialization patterns in function body
|
|
17
16
|
*
|
|
18
17
|
* ## Identifier Origin Analysis (Usage → Origin)
|
|
@@ -194,44 +193,6 @@ const isParameterUsedInFunction = (context, functionNode, paramName) => {
|
|
|
194
193
|
return false;
|
|
195
194
|
};
|
|
196
195
|
exports.isParameterUsedInFunction = isParameterUsedInFunction;
|
|
197
|
-
/**
|
|
198
|
-
* Check if a parameter is accessed via rest parameters (e.g., rest.paramName)
|
|
199
|
-
* and return information about the rest parameter usage
|
|
200
|
-
*/
|
|
201
|
-
const getParameterRestUsage = (context, functionNode, paramName) => {
|
|
202
|
-
// Look for rest elements in both direct params and inside object patterns
|
|
203
|
-
const restParameters = [];
|
|
204
|
-
functionNode.params.forEach(param => {
|
|
205
|
-
if (param.type === utils_1.AST_NODE_TYPES.RestElement && param.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
206
|
-
// Direct rest parameter: (...rest: Params)
|
|
207
|
-
restParameters.push({ name: param.argument.name, param });
|
|
208
|
-
}
|
|
209
|
-
else if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
210
|
-
// Look for rest elements inside object patterns: ({ param1, ...rest }: Params)
|
|
211
|
-
param.properties.forEach(property => {
|
|
212
|
-
if (property.type === utils_1.AST_NODE_TYPES.RestElement && property.argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
213
|
-
restParameters.push({ name: property.argument.name, param });
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
if (restParameters.length === 0) {
|
|
219
|
-
return { isUsedViaRest: false };
|
|
220
|
-
}
|
|
221
|
-
// Check if the function body contains rest.paramName patterns
|
|
222
|
-
const functionText = context.sourceCode.getText(functionNode);
|
|
223
|
-
for (const restParam of restParameters) {
|
|
224
|
-
const pattern = new RegExp(`\\b${restParam.name}\\.${paramName}\\b`);
|
|
225
|
-
if (pattern.test(functionText)) {
|
|
226
|
-
return {
|
|
227
|
-
isUsedViaRest: true,
|
|
228
|
-
restParamName: restParam.name,
|
|
229
|
-
restParam: restParam.param,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return { isUsedViaRest: false };
|
|
234
|
-
};
|
|
235
196
|
const jsxTagNameIsIntrinsic = (name) => name.type === utils_1.AST_NODE_TYPES.JSXIdentifier && /^[a-z]/.test(name.name);
|
|
236
197
|
const nodeContainsJSX = (node) => {
|
|
237
198
|
if (node.type === utils_1.AST_NODE_TYPES.JSXElement) {
|
|
@@ -337,49 +337,6 @@ const extractFromJsxAttribute = (node) => {
|
|
|
337
337
|
return [];
|
|
338
338
|
};
|
|
339
339
|
// =============================================================================
|
|
340
|
-
// Variable Declaration Extraction
|
|
341
|
-
// =============================================================================
|
|
342
|
-
/**
|
|
343
|
-
* Extract classname locations from a variable declaration with a classname-like name.
|
|
344
|
-
*
|
|
345
|
-
* Example: const buttonClass = "flex items-center";
|
|
346
|
-
*/
|
|
347
|
-
const extractFromVariableDeclarator = (node) => {
|
|
348
|
-
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
349
|
-
return [];
|
|
350
|
-
}
|
|
351
|
-
const varName = node.id.name;
|
|
352
|
-
if (!(0, exports.isClassnameVariable)(varName)) {
|
|
353
|
-
return [];
|
|
354
|
-
}
|
|
355
|
-
if (!node.init) {
|
|
356
|
-
return [];
|
|
357
|
-
}
|
|
358
|
-
const context = {
|
|
359
|
-
type: "variable",
|
|
360
|
-
variableName: varName,
|
|
361
|
-
};
|
|
362
|
-
// Handle string literal
|
|
363
|
-
const extracted = extractStringValue(node.init);
|
|
364
|
-
if (extracted) {
|
|
365
|
-
return [
|
|
366
|
-
{
|
|
367
|
-
value: extracted.value,
|
|
368
|
-
reportNode: node.init,
|
|
369
|
-
fixNode: extracted.node,
|
|
370
|
-
context,
|
|
371
|
-
},
|
|
372
|
-
];
|
|
373
|
-
}
|
|
374
|
-
// Handle array of strings
|
|
375
|
-
if (node.init.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
376
|
-
return extractFromArray(node.init, context);
|
|
377
|
-
}
|
|
378
|
-
// Note: Function calls in variable initializers are handled separately
|
|
379
|
-
// by the CallExpression handler
|
|
380
|
-
return [];
|
|
381
|
-
};
|
|
382
|
-
// =============================================================================
|
|
383
340
|
// Unified Entry Points
|
|
384
341
|
// =============================================================================
|
|
385
342
|
/**
|
|
@@ -426,27 +383,6 @@ const findClassnameStringsInAttribute = (node) => {
|
|
|
426
383
|
return extractFromJsxAttribute(node);
|
|
427
384
|
};
|
|
428
385
|
exports.findClassnameStringsInAttribute = findClassnameStringsInAttribute;
|
|
429
|
-
/**
|
|
430
|
-
* Find all classname string locations in a VariableDeclarator node.
|
|
431
|
-
*
|
|
432
|
-
* Use this in your ESLint rule's VariableDeclarator handler to process
|
|
433
|
-
* variables with classname-like names.
|
|
434
|
-
*
|
|
435
|
-
* @example
|
|
436
|
-
* ```typescript
|
|
437
|
-
* VariableDeclarator(node) {
|
|
438
|
-
* const locations = findClassnameStringsInVariable(node);
|
|
439
|
-
* for (const location of locations) {
|
|
440
|
-
* if (hasBannedPattern(location.value)) {
|
|
441
|
-
* context.report({ node: location.reportNode, ... });
|
|
442
|
-
* }
|
|
443
|
-
* }
|
|
444
|
-
* }
|
|
445
|
-
* ```
|
|
446
|
-
*/
|
|
447
|
-
const findClassnameStringsInVariable = (node) => {
|
|
448
|
-
return extractFromVariableDeclarator(node);
|
|
449
|
-
};
|
|
450
386
|
// =============================================================================
|
|
451
387
|
// Utility Helpers for Fixing
|
|
452
388
|
// =============================================================================
|
|
@@ -473,20 +409,4 @@ const formatAsArray = (classes) => {
|
|
|
473
409
|
return JSON.stringify(classes);
|
|
474
410
|
};
|
|
475
411
|
exports.formatAsArray = formatAsArray;
|
|
476
|
-
/**
|
|
477
|
-
* Get the quote character used in a string literal.
|
|
478
|
-
*/
|
|
479
|
-
const getQuoteChar = (node) => {
|
|
480
|
-
if (typeof node.value === "string" && node.raw) {
|
|
481
|
-
return node.raw.charAt(0);
|
|
482
|
-
}
|
|
483
|
-
return '"';
|
|
484
|
-
};
|
|
485
|
-
/**
|
|
486
|
-
* Create a quoted string with the same quote style as the original.
|
|
487
|
-
*/
|
|
488
|
-
const quoteString = (value, originalNode) => {
|
|
489
|
-
const quote = getQuoteChar(originalNode);
|
|
490
|
-
return `${quote}${value}${quote}`;
|
|
491
|
-
};
|
|
492
412
|
//# sourceMappingURL=classname-utils.js.map
|
|
@@ -12,11 +12,8 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
12
12
|
* ## Main Functions
|
|
13
13
|
* - `addImportSpecifiers()` - Add new imports or merge with existing ones
|
|
14
14
|
* - `replaceNamespaceWithDestructured()` - Replace namespace imports (e.g., `import React from "react"`)
|
|
15
|
-
* - `addSingleImportSpecifier()` - Convenience for adding a single import
|
|
16
|
-
*
|
|
17
15
|
* ## Helper Functions
|
|
18
16
|
* - `findImportDeclaration()` - Find existing import for a package
|
|
19
|
-
* - `hasImportSpecifier()` - Check if specific import already exists
|
|
20
17
|
* - `getImportSpecifiers()` - Extract specifiers from an import
|
|
21
18
|
*/
|
|
22
19
|
/**
|
|
@@ -39,21 +36,6 @@ const findImportDeclaration = (sourceCode, packageName) => {
|
|
|
39
36
|
return found;
|
|
40
37
|
};
|
|
41
38
|
exports.findImportDeclaration = findImportDeclaration;
|
|
42
|
-
/**
|
|
43
|
-
* Check if a specific specifier is already imported from a package.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* // File has: import { useState, useEffect } from "react";
|
|
47
|
-
* hasImportSpecifier(sourceCode, "react", "useState") // → true
|
|
48
|
-
* hasImportSpecifier(sourceCode, "react", "useRef") // → false
|
|
49
|
-
*/
|
|
50
|
-
const hasImportSpecifier = (sourceCode, packageName, specifierName) => {
|
|
51
|
-
return sourceCode.ast.body.some(node => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration &&
|
|
52
|
-
node.source.value === packageName &&
|
|
53
|
-
node.specifiers.some(specifier => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
54
|
-
specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
55
|
-
specifier.imported.name === specifierName));
|
|
56
|
-
};
|
|
57
39
|
/**
|
|
58
40
|
* Get existing import specifiers from an import declaration.
|
|
59
41
|
*
|
|
@@ -119,24 +101,6 @@ const addImportSpecifiers = (options) => {
|
|
|
119
101
|
return fixes;
|
|
120
102
|
};
|
|
121
103
|
exports.addImportSpecifiers = addImportSpecifiers;
|
|
122
|
-
/**
|
|
123
|
-
* Add a single import specifier - convenience wrapper around addImportSpecifiers.
|
|
124
|
-
* Automatically finds and merges with existing imports from the same package.
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* // Before: import { useState } from "react";
|
|
128
|
-
* // After: import { useEffect, useState } from "react";
|
|
129
|
-
* addSingleImportSpecifier({ sourceCode, fixer, packageName: "react", specifier: "useEffect" })
|
|
130
|
-
*/
|
|
131
|
-
const addSingleImportSpecifier = (options) => {
|
|
132
|
-
const { sourceCode, packageName, specifier } = options;
|
|
133
|
-
const existingImport = (0, exports.findImportDeclaration)(sourceCode, packageName);
|
|
134
|
-
return (0, exports.addImportSpecifiers)({
|
|
135
|
-
...options,
|
|
136
|
-
specifiers: [specifier],
|
|
137
|
-
existingImport,
|
|
138
|
-
});
|
|
139
|
-
};
|
|
140
104
|
/**
|
|
141
105
|
* Replace a namespace/default import with destructured imports.
|
|
142
106
|
* Automatically handles merging, semicolons, sorting, and formatting.
|