@blumintinc/eslint-plugin-blumint 1.20.130 → 1.20.131
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
|
@@ -22,5 +22,23 @@ type Options = [
|
|
|
22
22
|
*/
|
|
23
23
|
export declare const DEFAULT_IGNORED_LIBRARIES: string[];
|
|
24
24
|
export declare const DEFAULT_INTERNAL_PREFIXES: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Builds an O(1) + glob matcher from a list of library patterns.
|
|
27
|
+
*
|
|
28
|
+
* With `coverSubpaths`, a non-glob entry stands for the package *and*
|
|
29
|
+
* everything published under it. A package's subpath entry point is the same
|
|
30
|
+
* dependency as its root — `fast-deep-equal/es6` is upstream's documented ESM
|
|
31
|
+
* build, and the spelling `fast-deep-equal-over-microdiff` steers code toward —
|
|
32
|
+
* so exempting the root while enforcing the subpath left the pair of rules
|
|
33
|
+
* jointly unsatisfiable (#1845).
|
|
34
|
+
*
|
|
35
|
+
* The boundary is `entry + '/'`, never a bare substring: `fast-deep-equal-extra`
|
|
36
|
+
* is a different package on the registry and stays enforced. Glob entries keep
|
|
37
|
+
* their minimatch semantics untouched, since a pattern already says how far it
|
|
38
|
+
* reaches.
|
|
39
|
+
*/
|
|
40
|
+
export declare const buildLibraryMatcher: (list: string[], { coverSubpaths }: {
|
|
41
|
+
coverSubpaths: boolean;
|
|
42
|
+
}) => (source: string) => boolean;
|
|
25
43
|
declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"dynamicImportRequired", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
26
44
|
export default _default;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_INTERNAL_PREFIXES = exports.DEFAULT_IGNORED_LIBRARIES = exports.RULE_NAME = void 0;
|
|
3
|
+
exports.buildLibraryMatcher = exports.DEFAULT_INTERNAL_PREFIXES = exports.DEFAULT_IGNORED_LIBRARIES = exports.RULE_NAME = void 0;
|
|
4
4
|
const createRule_1 = require("../utils/createRule");
|
|
5
5
|
const minimatch_1 = require("minimatch");
|
|
6
6
|
const module_1 = require("module");
|
|
@@ -43,6 +43,42 @@ exports.DEFAULT_IGNORED_LIBRARIES = [
|
|
|
43
43
|
'fast-deep-equal', // fast-deep-equal-over-microdiff, for files already on upstream
|
|
44
44
|
];
|
|
45
45
|
exports.DEFAULT_INTERNAL_PREFIXES = ['src/', 'functions/'];
|
|
46
|
+
/**
|
|
47
|
+
* Builds an O(1) + glob matcher from a list of library patterns.
|
|
48
|
+
*
|
|
49
|
+
* With `coverSubpaths`, a non-glob entry stands for the package *and*
|
|
50
|
+
* everything published under it. A package's subpath entry point is the same
|
|
51
|
+
* dependency as its root — `fast-deep-equal/es6` is upstream's documented ESM
|
|
52
|
+
* build, and the spelling `fast-deep-equal-over-microdiff` steers code toward —
|
|
53
|
+
* so exempting the root while enforcing the subpath left the pair of rules
|
|
54
|
+
* jointly unsatisfiable (#1845).
|
|
55
|
+
*
|
|
56
|
+
* The boundary is `entry + '/'`, never a bare substring: `fast-deep-equal-extra`
|
|
57
|
+
* is a different package on the registry and stays enforced. Glob entries keep
|
|
58
|
+
* their minimatch semantics untouched, since a pattern already says how far it
|
|
59
|
+
* reaches.
|
|
60
|
+
*/
|
|
61
|
+
const buildLibraryMatcher = (list, { coverSubpaths }) => {
|
|
62
|
+
const exactSet = new Set();
|
|
63
|
+
const subpathPrefixes = [];
|
|
64
|
+
const globs = [];
|
|
65
|
+
for (const lib of list) {
|
|
66
|
+
const mm = new minimatch_1.Minimatch(lib);
|
|
67
|
+
if (mm.hasMagic()) {
|
|
68
|
+
globs.push(mm);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
exactSet.add(lib);
|
|
72
|
+
if (coverSubpaths) {
|
|
73
|
+
subpathPrefixes.push(lib.endsWith('/') ? lib : `${lib}/`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return (source) => exactSet.has(source) ||
|
|
78
|
+
subpathPrefixes.some((prefix) => source.startsWith(prefix)) ||
|
|
79
|
+
globs.some((mm) => mm.match(source));
|
|
80
|
+
};
|
|
81
|
+
exports.buildLibraryMatcher = buildLibraryMatcher;
|
|
46
82
|
// Pre-built set of Node.js core module names for O(1) lookup.
|
|
47
83
|
const NODE_BUILTINS = new Set(module_1.builtinModules);
|
|
48
84
|
// Returns true for any source that resolves to a Node builtin: bare name
|
|
@@ -106,25 +142,22 @@ exports.default = (0, createRule_1.createRule)({
|
|
|
106
142
|
// When `libraries` is absent, enforce-by-default mode applies:
|
|
107
143
|
// all external imports are flagged unless in `ignoredLibraries`.
|
|
108
144
|
const isWhitelistMode = libraries !== undefined;
|
|
109
|
-
// Build an O(1) + glob matcher from a list of library patterns.
|
|
110
|
-
const buildMatcher = (list) => {
|
|
111
|
-
const exactSet = new Set();
|
|
112
|
-
const globs = [];
|
|
113
|
-
for (const lib of list) {
|
|
114
|
-
const mm = new minimatch_1.Minimatch(lib);
|
|
115
|
-
if (mm.hasMagic()) {
|
|
116
|
-
globs.push(mm);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
exactSet.add(lib);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return (source) => exactSet.has(source) || globs.some((mm) => mm.match(source));
|
|
123
|
-
};
|
|
124
145
|
// In whitelist mode, `libraries` is defined (checked above). In
|
|
125
146
|
// enforce-by-default mode, `ignoredLibraries` is used instead.
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
//
|
|
148
|
+
// Subpath covering is asymmetric between the two lists because the lists
|
|
149
|
+
// point in opposite directions. Widening `ignoredLibraries` only ever
|
|
150
|
+
// REMOVES reports, so it can safely absorb a package's subpath entry
|
|
151
|
+
// points. Widening `libraries` would ADD reports — a consumer who listed
|
|
152
|
+
// `pkg` to restore pre-1.16.0 behaviour would start failing on `pkg/sub` —
|
|
153
|
+
// so the whitelist keeps exact + glob semantics, and consumers who do want
|
|
154
|
+
// the subpaths enforced spell that as a glob (`pkg/**`), which still works.
|
|
155
|
+
const isListedInWhitelist = (0, exports.buildLibraryMatcher)(libraries ?? [], {
|
|
156
|
+
coverSubpaths: false,
|
|
157
|
+
});
|
|
158
|
+
const isIgnoredLibrary = (0, exports.buildLibraryMatcher)(ignoredLibraries, {
|
|
159
|
+
coverSubpaths: true,
|
|
160
|
+
});
|
|
128
161
|
// A source is external only if it looks like an npm package specifier AND
|
|
129
162
|
// is not a known-internal path. Node builtins and configured internal
|
|
130
163
|
// prefixes (e.g. src/, functions/) are excluded to avoid false positives
|
|
@@ -8,6 +8,189 @@ const renameFixes_1 = require("../utils/renameFixes");
|
|
|
8
8
|
const LOWERCASE_TYPES = ['ReactNode', 'JSX.Element'];
|
|
9
9
|
// Types that should have uppercase variable names
|
|
10
10
|
const UPPERCASE_TYPES = ['ComponentType', 'FC', 'FunctionComponent'];
|
|
11
|
+
/**
|
|
12
|
+
* `global-const-style` owns the NAME of a module-scope `const`, and the two
|
|
13
|
+
* rules cannot both be satisfied there: it demands UPPER_SNAKE_CASE, this rule
|
|
14
|
+
* demands a lowercase initial for `ReactNode`/`JSX.Element`. Every spelling
|
|
15
|
+
* reports under one or the other, so a consumer running both — they are both
|
|
16
|
+
* `'error'` in `recommended` — cannot write the line at all.
|
|
17
|
+
*
|
|
18
|
+
* Unexported, both renamers also autofix, so `--fix` oscillates (`element` ->
|
|
19
|
+
* `ELEMENT` -> `eLEMENT` -> `E_LEMENT` -> `e_LEMENT` -> …) until ESLint's
|
|
20
|
+
* ten-pass cap and writes the mangled identifier to disk (Issue #1846).
|
|
21
|
+
* EXPORTED, both withhold the rename — an exported name is a cross-file
|
|
22
|
+
* contract a single-file fixer cannot complete — so `--fix` is a no-op and the
|
|
23
|
+
* damage is only the unsatisfiable report pair (Issue #1847).
|
|
24
|
+
*
|
|
25
|
+
* The pair is resolved by this rule yielding: module-scope constant naming is
|
|
26
|
+
* `global-const-style`'s universal contract, while this rule's purpose —
|
|
27
|
+
* telling an element VALUE apart from a COMPONENT — is about local and
|
|
28
|
+
* parameter naming, where nothing competes with it. Do not re-open the
|
|
29
|
+
* carve-out without changing `global-const-style` in the same breath.
|
|
30
|
+
*
|
|
31
|
+
* GOVERNANCE FOLLOWS WHICH RULE REPORTS ON THE NAME, NOT WHICH ONE FIXES IT.
|
|
32
|
+
* #1846 drew the boundary at the fixer war and so excluded exports; that left
|
|
33
|
+
* the exported form unsatisfiable, because `global-const-style` withholds only
|
|
34
|
+
* its FIX there (#1700) and still emits `upperSnakeCase`. The predicate below
|
|
35
|
+
* therefore mirrors that rule's ACTUAL reporting gates, which is also why it
|
|
36
|
+
* cannot be simplified to "module-scope const" — it declines on several shapes,
|
|
37
|
+
* and yielding on one of those would leave the declaration governed by nothing:
|
|
38
|
+
*
|
|
39
|
+
* - `let`/`var`, and any non-module scope, are outside it entirely;
|
|
40
|
+
* - a declaration whose parent is neither `Program` nor an
|
|
41
|
+
* `ExportNamedDeclaration` (a block, a `for` head, a namespace body) never
|
|
42
|
+
* reaches its check;
|
|
43
|
+
* - an exported Next.js reserved name (`config`, `getStaticProps`, …) has its
|
|
44
|
+
* rename declined outright (#1257), so this rule keeps its report there —
|
|
45
|
+
* report-only, since its own fixer stands down for exports too;
|
|
46
|
+
* - a function value or a `memo`/`forwardRef` call makes it skip the whole
|
|
47
|
+
* declaration list — `const button: FC = () => …` is this rule's alone;
|
|
48
|
+
* - an absent initializer, a dynamic value, a binding alias and a
|
|
49
|
+
* `jest.Mock*` cast each silence its rename check.
|
|
50
|
+
*
|
|
51
|
+
* When any of that cannot be established the answer is `false`: keeping a
|
|
52
|
+
* report is recoverable, silently governing nothing is not.
|
|
53
|
+
*/
|
|
54
|
+
const VALUE_WRAPPER_TYPES = new Set([
|
|
55
|
+
utils_1.AST_NODE_TYPES.TSAsExpression,
|
|
56
|
+
utils_1.AST_NODE_TYPES.TSTypeAssertion,
|
|
57
|
+
utils_1.AST_NODE_TYPES.TSSatisfiesExpression,
|
|
58
|
+
utils_1.AST_NODE_TYPES.TSNonNullExpression,
|
|
59
|
+
]);
|
|
60
|
+
const isValueWrapper = (node) => VALUE_WRAPPER_TYPES.has(node.type);
|
|
61
|
+
const unwrapValueWrappers = (node) => {
|
|
62
|
+
let target = node;
|
|
63
|
+
while (isValueWrapper(target)) {
|
|
64
|
+
target = target.expression;
|
|
65
|
+
}
|
|
66
|
+
return target;
|
|
67
|
+
};
|
|
68
|
+
// `global-const-style` unwraps only `as`/`<T>` casts before classifying an
|
|
69
|
+
// initializer as dynamic or as a binding alias, so the mirror does the same.
|
|
70
|
+
const unwrapCasts = (node) => {
|
|
71
|
+
let target = node;
|
|
72
|
+
while (target.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
|
|
73
|
+
target.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
|
|
74
|
+
target = target.expression;
|
|
75
|
+
}
|
|
76
|
+
return target;
|
|
77
|
+
};
|
|
78
|
+
const COMPONENT_FACTORY_NAMES = new Set(['forwardRef', 'memo']);
|
|
79
|
+
const isComponentFactoryCall = (node) => {
|
|
80
|
+
if (node.type !== utils_1.AST_NODE_TYPES.CallExpression) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const { callee } = node;
|
|
84
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
85
|
+
return COMPONENT_FACTORY_NAMES.has(callee.name);
|
|
86
|
+
}
|
|
87
|
+
return (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
88
|
+
!callee.computed &&
|
|
89
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
90
|
+
COMPONENT_FACTORY_NAMES.has(callee.property.name));
|
|
91
|
+
};
|
|
92
|
+
const isFunctionValue = (node) => node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
93
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression;
|
|
94
|
+
const isDynamicValue = (node) => {
|
|
95
|
+
const target = unwrapCasts(node);
|
|
96
|
+
if (target.type === utils_1.AST_NODE_TYPES.CallExpression ||
|
|
97
|
+
target.type === utils_1.AST_NODE_TYPES.NewExpression ||
|
|
98
|
+
target.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (target.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
102
|
+
return isDynamicValue(target.expression);
|
|
103
|
+
}
|
|
104
|
+
if (target.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
105
|
+
return isDynamicValue(target.object);
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
};
|
|
109
|
+
const PRIMITIVE_VALUE_GLOBALS = new Set(['undefined', 'NaN', 'Infinity']);
|
|
110
|
+
const isBindingAlias = (node) => {
|
|
111
|
+
const target = unwrapCasts(node);
|
|
112
|
+
return (target.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
113
|
+
!PRIMITIVE_VALUE_GLOBALS.has(target.name));
|
|
114
|
+
};
|
|
115
|
+
const JEST_MOCK_TYPE_NAMES = new Set([
|
|
116
|
+
'Mock',
|
|
117
|
+
'MockedFunction',
|
|
118
|
+
'Mocked',
|
|
119
|
+
'MockedClass',
|
|
120
|
+
]);
|
|
121
|
+
const isJestMockTypeReference = (typeAnnotation) => {
|
|
122
|
+
if (typeAnnotation.type !== utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const { typeName } = typeAnnotation;
|
|
126
|
+
return (typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName &&
|
|
127
|
+
typeName.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
128
|
+
typeName.left.name === 'jest' &&
|
|
129
|
+
typeName.right.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
130
|
+
JEST_MOCK_TYPE_NAMES.has(typeName.right.name));
|
|
131
|
+
};
|
|
132
|
+
const isJestMockCast = (node) => {
|
|
133
|
+
let current = node;
|
|
134
|
+
while (isValueWrapper(current)) {
|
|
135
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression &&
|
|
136
|
+
isJestMockTypeReference(current.typeAnnotation)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
current = current.expression;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
};
|
|
143
|
+
// Mirrors `global-const-style`'s own list. Next.js recognizes these export
|
|
144
|
+
// names by their literal identifier, so that rule declines the rename outright
|
|
145
|
+
// rather than breaking the framework contract (#1257) — nothing there governs
|
|
146
|
+
// the name, so this rule keeps its report. Only the EXPORT name matters to
|
|
147
|
+
// Next.js, exactly as the sibling gates it.
|
|
148
|
+
const NEXTJS_RESERVED_EXPORTS = new Set([
|
|
149
|
+
'config',
|
|
150
|
+
'getServerSideProps',
|
|
151
|
+
'getStaticProps',
|
|
152
|
+
'getStaticPaths',
|
|
153
|
+
'getInitialProps',
|
|
154
|
+
'middleware',
|
|
155
|
+
]);
|
|
156
|
+
const isGlobalConstStyleGoverned = (declarator) => {
|
|
157
|
+
const declaration = declarator.parent;
|
|
158
|
+
if (!declaration ||
|
|
159
|
+
declaration.type !== utils_1.AST_NODE_TYPES.VariableDeclaration ||
|
|
160
|
+
declaration.kind !== 'const') {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
// The sibling's own scope gate: module scope, whether written bare or behind
|
|
164
|
+
// an inline `export`. Exports are INCLUDED because it reports `upperSnakeCase`
|
|
165
|
+
// on them — it withholds only the FIX (#1700) — so leaving them out kept the
|
|
166
|
+
// pair unsatisfiable for `export const element: JSX.Element = …` (#1847).
|
|
167
|
+
const isExported = declaration.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration;
|
|
168
|
+
if (declaration.parent?.type !== utils_1.AST_NODE_TYPES.Program && !isExported) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (isExported &&
|
|
172
|
+
declarator.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
173
|
+
NEXTJS_RESERVED_EXPORTS.has(declarator.id.name)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
// The function-value / component-factory skip is evaluated over the whole
|
|
177
|
+
// declaration LIST there, so `const a = () => {}, b = <div />;` exempts both.
|
|
178
|
+
const listSkipped = declaration.declarations.some((one) => {
|
|
179
|
+
if (one.id.type !== utils_1.AST_NODE_TYPES.Identifier || !one.init) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const target = unwrapValueWrappers(one.init);
|
|
183
|
+
return isFunctionValue(target) || isComponentFactoryCall(target);
|
|
184
|
+
});
|
|
185
|
+
if (listSkipped) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const { init } = declarator;
|
|
189
|
+
if (!init) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return (!isDynamicValue(init) && !isBindingAlias(init) && !isJestMockCast(init));
|
|
193
|
+
};
|
|
11
194
|
exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
12
195
|
name: 'enforce-react-type-naming',
|
|
13
196
|
meta: {
|
|
@@ -123,6 +306,12 @@ exports.enforceReactTypeNaming = (0, createRule_1.createRule)({
|
|
|
123
306
|
// Skip destructured variables
|
|
124
307
|
if (isDestructured(id))
|
|
125
308
|
return;
|
|
309
|
+
// Yield the name to `global-const-style` where it governs (Issue #1846).
|
|
310
|
+
// Both branches yield: its UPPER_SNAKE_CASE target already satisfies the
|
|
311
|
+
// component branch's "starts uppercase", so nothing is lost there, and
|
|
312
|
+
// the element branch is the one that cannot coexist with it at all.
|
|
313
|
+
if (isGlobalConstStyleGoverned(node))
|
|
314
|
+
return;
|
|
126
315
|
const variableName = id.name;
|
|
127
316
|
// Get the type annotation
|
|
128
317
|
const typeAnnotation = id.typeAnnotation?.typeAnnotation;
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.131",
|
|
4
|
+
"date": "2026-08-07T10:28:22.079Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-dynamic-imports",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1845
|
|
11
|
+
],
|
|
12
|
+
"summary": "cover a package's subpaths from one ignoredLibraries entry (closes #1845)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "enforce-react-type-naming",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1846,
|
|
19
|
+
1847
|
|
20
|
+
],
|
|
21
|
+
"summary": "yield exported module-scope constants to global-const-style (closes #1847); yield module-scope constants to global-const-style (closes #1846)"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
2
25
|
{
|
|
3
26
|
"version": "1.20.130",
|
|
4
27
|
"date": "2026-08-07T08:38:15.008Z",
|