@blumintinc/eslint-plugin-blumint 1.20.61 → 1.20.62
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
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.enforceAssertSafeObjectKey = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
8
|
const path_1 = __importDefault(require("path"));
|
|
8
9
|
const utils_1 = require("@typescript-eslint/utils");
|
|
9
10
|
const createRule_1 = require("../utils/createRule");
|
|
@@ -30,6 +31,137 @@ function isAssertSafeSpecifier(specifier) {
|
|
|
30
31
|
function normalizeModulePath(value) {
|
|
31
32
|
return value.replace(/\\/g, '/').replace(/\.(tsx?|jsx?|mts|cts)$/i, '');
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Extensions whose module system the file name settles on its own, ahead of any
|
|
36
|
+
* package manifest: `.mjs` is ESM and `.cjs` is CommonJS by definition, while a
|
|
37
|
+
* TypeScript source — `.mts` included — is compiled before it runs and its
|
|
38
|
+
* specifiers are resolved by the compiler, which accepts the extensionless
|
|
39
|
+
* form. Only `.js` is ambiguous and has to ask the nearest manifest.
|
|
40
|
+
*/
|
|
41
|
+
const NATIVE_ESM_EXTENSION = /\.mjs$/i;
|
|
42
|
+
const NON_NATIVE_ESM_EXTENSION = /\.(cjs|tsx?|mts|cts)$/i;
|
|
43
|
+
const AMBIGUOUS_JS_EXTENSION = /\.js$/i;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the nearest `package.json` at or above `startDir` declares
|
|
46
|
+
* `"type": "module"`, which is what makes a `.js` file native ESM. Node consults
|
|
47
|
+
* only the first manifest found, and treats a missing `type` field as
|
|
48
|
+
* CommonJS — so the walk stops at the first manifest it can read, not at the
|
|
49
|
+
* first one that declares a type.
|
|
50
|
+
*
|
|
51
|
+
* Every filesystem touch is optional: a manifest that cannot be read is
|
|
52
|
+
* indistinguishable from an absent one and the walk continues, while a manifest
|
|
53
|
+
* that cannot be parsed declines the extension rather than guessing. Declining
|
|
54
|
+
* yields the bare specifier, which every non-ESM consumer resolves — the safer
|
|
55
|
+
* answer when the tree cannot say which kind of consumer this is.
|
|
56
|
+
*/
|
|
57
|
+
function nearestManifestDeclaresModule(startDir) {
|
|
58
|
+
let dir = startDir;
|
|
59
|
+
for (;;) {
|
|
60
|
+
let manifest;
|
|
61
|
+
try {
|
|
62
|
+
manifest = fs_1.default.readFileSync(path_1.default.join(dir, 'package.json'), 'utf8');
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
const parent = path_1.default.dirname(dir);
|
|
66
|
+
if (parent === dir) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
dir = parent;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return (JSON.parse(manifest)?.type === 'module');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Names that read as a positional sequence rather than a keyed record. */
|
|
81
|
+
const ARRAY_LIKE_NAME = /^(array|arr|items|elements|list|collection|data)s?$/i;
|
|
82
|
+
/**
|
|
83
|
+
* The name an indexed object is judged by. A collection reached as a field —
|
|
84
|
+
* `raster.data[i]`, `this.items[i]`, `a.b.list[i]` — carries its signal on the
|
|
85
|
+
* property rather than on the root object, so the property name is what the
|
|
86
|
+
* array-ish test sees there.
|
|
87
|
+
*/
|
|
88
|
+
function indexedObjectName(node) {
|
|
89
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
90
|
+
return node.name;
|
|
91
|
+
}
|
|
92
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
93
|
+
!node.computed &&
|
|
94
|
+
node.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
95
|
+
return node.property.name;
|
|
96
|
+
}
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Operators that coerce both operands with ToNumeric before operating, so the
|
|
101
|
+
* result is a number (or bigint) whatever the operands hold. `+` is absent: it
|
|
102
|
+
* keeps string concatenation, so it is judged from its operands instead.
|
|
103
|
+
*/
|
|
104
|
+
const NUMERIC_BINARY_OPERATORS = new Set([
|
|
105
|
+
'-',
|
|
106
|
+
'*',
|
|
107
|
+
'/',
|
|
108
|
+
'%',
|
|
109
|
+
'**',
|
|
110
|
+
'<<',
|
|
111
|
+
'>>',
|
|
112
|
+
'>>>',
|
|
113
|
+
'&',
|
|
114
|
+
'|',
|
|
115
|
+
'^',
|
|
116
|
+
]);
|
|
117
|
+
/** The compound assignments of NUMERIC_BINARY_OPERATORS, `+=` excluded. */
|
|
118
|
+
const NUMERIC_ASSIGNMENT_OPERATORS = new Set([
|
|
119
|
+
'-=',
|
|
120
|
+
'*=',
|
|
121
|
+
'/=',
|
|
122
|
+
'%=',
|
|
123
|
+
'**=',
|
|
124
|
+
'<<=',
|
|
125
|
+
'>>=',
|
|
126
|
+
'>>>=',
|
|
127
|
+
'&=',
|
|
128
|
+
'|=',
|
|
129
|
+
'^=',
|
|
130
|
+
]);
|
|
131
|
+
/** Global conversions whose result is a number regardless of the argument. */
|
|
132
|
+
const NUMERIC_CALLEE_NAMES = new Set(['Number', 'parseInt', 'parseFloat']);
|
|
133
|
+
/**
|
|
134
|
+
* Whether the call is guaranteed to produce a number. Every `Math` member
|
|
135
|
+
* returns one, so the namespace is accepted wholesale rather than enumerated.
|
|
136
|
+
*/
|
|
137
|
+
function isNumericCall(node) {
|
|
138
|
+
const { callee } = node;
|
|
139
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
140
|
+
return NUMERIC_CALLEE_NAMES.has(callee.name);
|
|
141
|
+
}
|
|
142
|
+
return (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
143
|
+
!callee.computed &&
|
|
144
|
+
callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
145
|
+
callee.object.name === 'Math');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* A `: number` annotation on a binding name. Only parameters and variable
|
|
149
|
+
* declarators carry one, and a declarator is judged from its writes, so this
|
|
150
|
+
* effectively identifies a numeric parameter.
|
|
151
|
+
*/
|
|
152
|
+
function isNumberAnnotated(node) {
|
|
153
|
+
return (node.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
154
|
+
node.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSNumberKeyword);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Whether the definition can hold a number: a declarator (whose value is proven
|
|
158
|
+
* by its writes) or a parameter annotated `: number`. Anything else — an
|
|
159
|
+
* import, a function or class name, a catch binding — is not.
|
|
160
|
+
*/
|
|
161
|
+
function definesNumericBinding(def) {
|
|
162
|
+
return (def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
163
|
+
isNumberAnnotated(def.name));
|
|
164
|
+
}
|
|
33
165
|
exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
34
166
|
name: 'enforce-assert-safe-object-key',
|
|
35
167
|
meta: {
|
|
@@ -91,6 +223,28 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
91
223
|
const fileRelToCwd = path_1.default.relative(cwd, rawFilename).replace(/\\/g, '/');
|
|
92
224
|
return path_1.default.posix.dirname(fileRelToCwd);
|
|
93
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Whether the file being fixed runs as native ESM, where node's resolver
|
|
228
|
+
* takes a specifier literally and an extensionless one throws
|
|
229
|
+
* ERR_MODULE_NOT_FOUND at startup. TypeScript and bundler consumers resolve
|
|
230
|
+
* extensionless specifiers themselves, so they keep the bare form.
|
|
231
|
+
*
|
|
232
|
+
* The walk for an ambiguous `.js` file runs only while a fix is being built,
|
|
233
|
+
* costs a handful of stat-sized reads, and needs an absolute name to have a
|
|
234
|
+
* directory to start from.
|
|
235
|
+
*/
|
|
236
|
+
const isNativeEsmFile = () => {
|
|
237
|
+
const filename = context.getFilename().replace(/\\/g, '/');
|
|
238
|
+
if (NATIVE_ESM_EXTENSION.test(filename)) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (NON_NATIVE_ESM_EXTENSION.test(filename) ||
|
|
242
|
+
!AMBIGUOUS_JS_EXTENSION.test(filename) ||
|
|
243
|
+
!path_1.default.isAbsolute(filename)) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return nearestManifestDeclaresModule(path_1.default.dirname(filename));
|
|
247
|
+
};
|
|
94
248
|
/**
|
|
95
249
|
* Computes the module specifier for the injected assertSafe import.
|
|
96
250
|
*
|
|
@@ -104,17 +258,22 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
104
258
|
* emitted import resolves from that file's location.
|
|
105
259
|
*/
|
|
106
260
|
const computeImportSpecifier = () => {
|
|
261
|
+
// The helper is authored as TypeScript and runs as its compiled output, so
|
|
262
|
+
// a native-ESM importer names the emitted `.js` file. TS resolves a `.js`
|
|
263
|
+
// specifier back to the `.ts` source under nodenext, which keeps the one
|
|
264
|
+
// spelling correct for both.
|
|
265
|
+
const extension = isNativeEsmFile() ? '.js' : '';
|
|
107
266
|
const fileDir = fileDirFromRoot();
|
|
108
267
|
// Emitting the configured path verbatim preserves the option's literal
|
|
109
268
|
// value for non-file lints.
|
|
110
269
|
if (fileDir === null) {
|
|
111
|
-
return importPath
|
|
270
|
+
return `${importPath}${extension}`;
|
|
112
271
|
}
|
|
113
272
|
let specifier = path_1.default.posix.relative(fileDir, assertSafeTarget);
|
|
114
273
|
if (!specifier.startsWith('.')) {
|
|
115
274
|
specifier = `./${specifier}`;
|
|
116
275
|
}
|
|
117
|
-
return specifier
|
|
276
|
+
return `${specifier}${extension}`;
|
|
118
277
|
};
|
|
119
278
|
/**
|
|
120
279
|
* Whether a module specifier written in the file denotes the same helper the
|
|
@@ -245,6 +404,86 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
245
404
|
init.callee.name === 'assertSafe');
|
|
246
405
|
});
|
|
247
406
|
};
|
|
407
|
+
/**
|
|
408
|
+
* Whether the syntax alone proves the key is a number. `__proto__`,
|
|
409
|
+
* `constructor` and `prototype` are never the string form of a number, so a
|
|
410
|
+
* numeric key cannot reach the prototype surface assertSafe exists to
|
|
411
|
+
* guard: the call would be dead weight, and in an index-heavy loop a
|
|
412
|
+
* per-iteration coercion. The judgement stays syntactic — a key the syntax
|
|
413
|
+
* does not prove numeric keeps being reported.
|
|
414
|
+
*/
|
|
415
|
+
const isStaticallyNumeric = (node, seen = new Set()) => {
|
|
416
|
+
switch (node.type) {
|
|
417
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
418
|
+
return typeof node.value === 'number';
|
|
419
|
+
case utils_1.AST_NODE_TYPES.UpdateExpression:
|
|
420
|
+
return true;
|
|
421
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
422
|
+
return (node.operator === '-' ||
|
|
423
|
+
node.operator === '+' ||
|
|
424
|
+
node.operator === '~');
|
|
425
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
426
|
+
if (NUMERIC_BINARY_OPERATORS.has(node.operator)) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
return (node.operator === '+' &&
|
|
430
|
+
isStaticallyNumeric(node.left, seen) &&
|
|
431
|
+
isStaticallyNumeric(node.right, seen));
|
|
432
|
+
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
433
|
+
return isNumericCall(node);
|
|
434
|
+
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
435
|
+
// `.length` is a number on arrays, typed arrays and strings alike.
|
|
436
|
+
return (!node.computed &&
|
|
437
|
+
node.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
438
|
+
node.property.name === 'length');
|
|
439
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
440
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
441
|
+
return isStaticallyNumeric(node.expression, seen);
|
|
442
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
443
|
+
return isNumericIdentifier(node, seen);
|
|
444
|
+
default:
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Whether every declaration and every write of the resolved binding keeps it
|
|
450
|
+
* numeric. `seen` is copied per resolution step so a cycle
|
|
451
|
+
* (`let a = b; let b = a;`) terminates without a sibling occurrence of one
|
|
452
|
+
* variable (`arr[i + i]`) being mistaken for that cycle.
|
|
453
|
+
*/
|
|
454
|
+
const isNumericIdentifier = (node, seen) => {
|
|
455
|
+
const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(ASTHelpers_1.ASTHelpers.getScope(context, node), node.name);
|
|
456
|
+
if (!variable || seen.has(variable)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
const nextSeen = new Set(seen).add(variable);
|
|
460
|
+
if (variable.defs.length === 0 ||
|
|
461
|
+
!variable.defs.every(definesNumericBinding)) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
const writes = variable.references.filter((reference) => reference.isWrite());
|
|
465
|
+
const staysNumeric = writes.every((reference) => {
|
|
466
|
+
const { writeExpr } = reference;
|
|
467
|
+
// `i++`/`--i` write a number back with no expression to inspect.
|
|
468
|
+
if (!writeExpr) {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
const assignment = writeExpr.parent;
|
|
472
|
+
if (assignment?.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
473
|
+
NUMERIC_ASSIGNMENT_OPERATORS.has(assignment.operator)) {
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
return isStaticallyNumeric(writeExpr, nextSeen);
|
|
477
|
+
});
|
|
478
|
+
if (!staysNumeric) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
// A `: number` parameter is numeric from its declaration; a declarator is
|
|
482
|
+
// only proven by a write, and its initializer is one — so `let k;` with no
|
|
483
|
+
// write anywhere holds undefined and stays unproven.
|
|
484
|
+
return (writes.length > 0 ||
|
|
485
|
+
variable.defs.every((def) => isNumberAnnotated(def.name)));
|
|
486
|
+
};
|
|
248
487
|
return {
|
|
249
488
|
// Handle computed property in object destructuring
|
|
250
489
|
Property(node) {
|
|
@@ -305,14 +544,7 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
305
544
|
return;
|
|
306
545
|
}
|
|
307
546
|
// Try to determine if this is likely an array or dictionary
|
|
308
|
-
const
|
|
309
|
-
let objectName = '';
|
|
310
|
-
let isLikelyArray = false;
|
|
311
|
-
if (objectNode.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
312
|
-
objectName = objectNode.name.toLowerCase();
|
|
313
|
-
isLikelyArray =
|
|
314
|
-
/^(array|arr|items|elements|list|collection|data)s?$/i.test(objectName);
|
|
315
|
-
}
|
|
547
|
+
const isLikelyArray = ARRAY_LIKE_NAME.test(indexedObjectName(node.object));
|
|
316
548
|
// Check for string literals - allow them for dictionaries but not for regular objects
|
|
317
549
|
if (property.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
318
550
|
typeof property.value === 'string') {
|
|
@@ -325,6 +557,12 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
|
|
|
325
557
|
// Numeric literals are fine, no need for assertSafe
|
|
326
558
|
return;
|
|
327
559
|
}
|
|
560
|
+
// A key the syntax proves numeric — a loop counter, an offset
|
|
561
|
+
// computation, Math.floor(...) — cannot name a prototype field, so
|
|
562
|
+
// validating it guards nothing.
|
|
563
|
+
if (isStaticallyNumeric(property)) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
328
566
|
// Check if we're using String(id) pattern
|
|
329
567
|
if (property.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
330
568
|
property.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
type Options = [
|
|
2
|
+
{
|
|
3
|
+
readonly externallyNamedExports?: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
];
|
|
6
|
+
export declare const enforceVerbNounNaming: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"functionVerbPhrase", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
7
|
+
export {};
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.enforceVerbNounNaming = void 0;
|
|
7
7
|
const utils_1 = require("@typescript-eslint/utils");
|
|
8
|
+
const minimatch_1 = require("minimatch");
|
|
8
9
|
const createRule_1 = require("../utils/createRule");
|
|
9
10
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
10
11
|
const compromise_1 = __importDefault(require("compromise"));
|
|
@@ -3776,13 +3777,87 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
|
3776
3777
|
requiresTypeChecking: false,
|
|
3777
3778
|
extendsBaseRule: false,
|
|
3778
3779
|
},
|
|
3779
|
-
schema: [
|
|
3780
|
+
schema: [
|
|
3781
|
+
{
|
|
3782
|
+
type: 'object',
|
|
3783
|
+
properties: {
|
|
3784
|
+
externallyNamedExports: {
|
|
3785
|
+
type: 'array',
|
|
3786
|
+
items: { type: 'string' },
|
|
3787
|
+
},
|
|
3788
|
+
},
|
|
3789
|
+
additionalProperties: false,
|
|
3790
|
+
},
|
|
3791
|
+
],
|
|
3780
3792
|
messages: {
|
|
3781
3793
|
functionVerbPhrase: 'Function "{{name}}" should start with an action verb followed by the thing it acts on. Verb-first names tell readers this symbol performs work instead of representing data, which keeps APIs predictable and prevents accidental misuse. Rename "{{name}}" to a verb-noun phrase such as "fetchUsers" or "processRequest".',
|
|
3782
3794
|
},
|
|
3783
3795
|
},
|
|
3784
|
-
defaultOptions: [],
|
|
3785
|
-
create(context) {
|
|
3796
|
+
defaultOptions: [{}],
|
|
3797
|
+
create(context, [options]) {
|
|
3798
|
+
const externallyNamedExports = options?.externallyNamedExports ?? [];
|
|
3799
|
+
const rawFilename = context.filename ?? context.getFilename();
|
|
3800
|
+
const normalizedFilename = rawFilename.replace(/\\/g, '/');
|
|
3801
|
+
// A leading `**` consumes the extra leading segments of an absolute path,
|
|
3802
|
+
// so one match handles both repo-relative and absolute filenames. Malformed
|
|
3803
|
+
// globs are treated as non-matching by minimatch rather than throwing, which
|
|
3804
|
+
// keeps a bad pattern from aborting the whole lint run.
|
|
3805
|
+
const hasExternallyNamedExports = externallyNamedExports.some((glob) => (0, minimatch_1.minimatch)(normalizedFilename, glob, { dot: true, matchBase: true }));
|
|
3806
|
+
// `export { local as exported }` and `export default local` name the module
|
|
3807
|
+
// binding from a distance, so the local name still belongs to the external
|
|
3808
|
+
// specification. Collected once per file, and only when a glob matched.
|
|
3809
|
+
let deferredExportedNames;
|
|
3810
|
+
function getDeferredExportedNames() {
|
|
3811
|
+
if (deferredExportedNames) {
|
|
3812
|
+
return deferredExportedNames;
|
|
3813
|
+
}
|
|
3814
|
+
const names = new Set();
|
|
3815
|
+
for (const statement of context.sourceCode.ast.body) {
|
|
3816
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration &&
|
|
3817
|
+
!statement.source) {
|
|
3818
|
+
for (const specifier of statement.specifiers) {
|
|
3819
|
+
const local = specifier.local;
|
|
3820
|
+
if (local.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
3821
|
+
names.add(local.name);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
else if (statement.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration &&
|
|
3826
|
+
statement.declaration.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
3827
|
+
names.add(statement.declaration.name);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
deferredExportedNames = names;
|
|
3831
|
+
return names;
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3834
|
+
* Exported symbols in a file whose names are specified elsewhere (a runtime
|
|
3835
|
+
* registry key, a CLI verb, an artifact path) cannot be renamed without
|
|
3836
|
+
* desynchronizing them from that specification. Non-exported symbols in the
|
|
3837
|
+
* same file are ordinary local code and stay checked.
|
|
3838
|
+
*/
|
|
3839
|
+
function isExternallyNamedExport(node, name) {
|
|
3840
|
+
if (!hasExternallyNamedExports) {
|
|
3841
|
+
return false;
|
|
3842
|
+
}
|
|
3843
|
+
if (node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
3844
|
+
const parent = node.parent;
|
|
3845
|
+
if (parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
|
|
3846
|
+
parent?.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
|
|
3847
|
+
return true;
|
|
3848
|
+
}
|
|
3849
|
+
// Only a module-level binding can be the target of a deferred export.
|
|
3850
|
+
return (parent?.type === utils_1.AST_NODE_TYPES.Program &&
|
|
3851
|
+
getDeferredExportedNames().has(name));
|
|
3852
|
+
}
|
|
3853
|
+
const declaration = node.parent;
|
|
3854
|
+
const declarationParent = declaration?.parent;
|
|
3855
|
+
if (declarationParent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
3856
|
+
return true;
|
|
3857
|
+
}
|
|
3858
|
+
return (declarationParent?.type === utils_1.AST_NODE_TYPES.Program &&
|
|
3859
|
+
getDeferredExportedNames().has(name));
|
|
3860
|
+
}
|
|
3786
3861
|
function extractFirstWord(name) {
|
|
3787
3862
|
const firstChar = name.charAt(0);
|
|
3788
3863
|
const rest = name.slice(1);
|
|
@@ -3925,6 +4000,9 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
|
3925
4000
|
if (isReactComponent(node)) {
|
|
3926
4001
|
return;
|
|
3927
4002
|
}
|
|
4003
|
+
if (isExternallyNamedExport(node, node.id.name)) {
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
3928
4006
|
if (!isVerbPhrase(node.id.name)) {
|
|
3929
4007
|
context.report({
|
|
3930
4008
|
node: node.id,
|
|
@@ -3942,6 +4020,9 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
|
|
|
3942
4020
|
if (isReactComponent(node.init)) {
|
|
3943
4021
|
return;
|
|
3944
4022
|
}
|
|
4023
|
+
if (isExternallyNamedExport(node, node.id.name)) {
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
3945
4026
|
if (!isVerbPhrase(node.id.name)) {
|
|
3946
4027
|
context.report({
|
|
3947
4028
|
node: node.id,
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.62",
|
|
4
|
+
"date": "2026-08-01T09:01:13.989Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-assert-safe-object-key",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1554,
|
|
11
|
+
1556
|
|
12
|
+
],
|
|
13
|
+
"summary": "append .js to the injected specifier for native-ESM consumers (closes #1556); exempt statically numeric keys and array-ish member objects (closes #1554)"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "enforce-verb-noun-naming",
|
|
17
|
+
"changeType": "fix",
|
|
18
|
+
"issues": [
|
|
19
|
+
1555
|
|
20
|
+
],
|
|
21
|
+
"summary": "add externallyNamedExports glob option for externally-specified names (closes #1555)"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
2
25
|
{
|
|
3
26
|
"version": "1.20.61",
|
|
4
27
|
"date": "2026-08-01T06:30:50.860Z",
|