@blumintinc/eslint-plugin-blumint 1.20.51 → 1.20.53
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/enforce-m3-sentence-case.js +6 -1
- package/lib/rules/no-handler-suffix.js +8 -28
- package/lib/rules/no-render-function-components.js +5 -1
- package/lib/rules/no-separate-loading-state.js +13 -1
- package/lib/rules/parallelize-async-operations.js +185 -0
- package/lib/utils/compilePatternOption.d.ts +40 -0
- package/lib/utils/compilePatternOption.js +63 -0
- package/package.json +4 -2
- package/release-manifest.json +44 -0
package/lib/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.enforceM3SentenceCase = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const compilePatternOption_1 = require("../utils/compilePatternOption");
|
|
5
6
|
const createRule_1 = require("../utils/createRule");
|
|
6
7
|
/**
|
|
7
8
|
* Default props that carry user-facing label text, per the issue spec.
|
|
@@ -452,7 +453,11 @@ exports.enforceM3SentenceCase = (0, createRule_1.createRule)({
|
|
|
452
453
|
...DEFAULT_IGNORED_WORDS,
|
|
453
454
|
...(options.ignoredWords ?? []),
|
|
454
455
|
]);
|
|
455
|
-
|
|
456
|
+
// Rejecting a malformed `ignorePatterns` entry rather than dropping it keeps
|
|
457
|
+
// the consumer's exception list honest: a silently discarded pattern would
|
|
458
|
+
// make text they deliberately excluded start getting reported with no
|
|
459
|
+
// indication why.
|
|
460
|
+
const ignorePatternRegexes = (0, compilePatternOption_1.compilePatternOption)('enforce-m3-sentence-case', 'ignorePatterns', options.ignorePatterns ?? []);
|
|
456
461
|
const allowListSet = new Set(options.allowList ?? []);
|
|
457
462
|
const checkJsxText = options.checkJsxText !== false;
|
|
458
463
|
/**
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noHandlerSuffix = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const minimatch_1 = require("minimatch");
|
|
6
|
+
const compilePatternOption_1 = require("../utils/compilePatternOption");
|
|
6
7
|
const createRule_1 = require("../utils/createRule");
|
|
7
8
|
const DEFAULT_OPTIONS = {
|
|
8
9
|
ignoreClassMethods: false,
|
|
@@ -26,6 +27,12 @@ function isUnsafeAllowPattern(pattern) {
|
|
|
26
27
|
const nestedQuantifierPattern = /\((?:[^()\\]|\\.)*[+*{][^)]*\)\s*[+*{]/;
|
|
27
28
|
return nestedQuantifierPattern.test(pattern);
|
|
28
29
|
}
|
|
30
|
+
// A pattern that compiles can still hang the linter, so allowlist sources are
|
|
31
|
+
// refused for catastrophic-backtracking risk as well as for syntax.
|
|
32
|
+
const UNSAFE_ALLOW_PATTERN_REJECTION = {
|
|
33
|
+
isRejected: isUnsafeAllowPattern,
|
|
34
|
+
describe: (optionName) => `unsafe ${optionName} (avoid nested quantifiers that risk catastrophic backtracking)`,
|
|
35
|
+
};
|
|
29
36
|
function getStaticKeyName(key) {
|
|
30
37
|
if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
31
38
|
return key.name;
|
|
@@ -131,34 +138,7 @@ exports.noHandlerSuffix = (0, createRule_1.createRule)({
|
|
|
131
138
|
const resolvedOptions = { ...DEFAULT_OPTIONS, ...(options ?? {}) };
|
|
132
139
|
const allowNames = new Set(resolvedOptions.allowNames);
|
|
133
140
|
const interfaceAllowlist = new Set(resolvedOptions.interfaceAllowlist);
|
|
134
|
-
const
|
|
135
|
-
const unsafeAllowPatterns = [];
|
|
136
|
-
const allowPatterns = (resolvedOptions.allowPatterns ?? []).flatMap((pattern) => {
|
|
137
|
-
try {
|
|
138
|
-
if (isUnsafeAllowPattern(pattern)) {
|
|
139
|
-
unsafeAllowPatterns.push(pattern);
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
return [new RegExp(pattern)];
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
const reason = error && typeof error === 'object' && 'message' in error
|
|
146
|
-
? ` (${String(error.message)})`
|
|
147
|
-
: '';
|
|
148
|
-
invalidAllowPatterns.push(`${pattern}${reason}`);
|
|
149
|
-
return [];
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
if (invalidAllowPatterns.length > 0 || unsafeAllowPatterns.length > 0) {
|
|
153
|
-
const errorParts = [];
|
|
154
|
-
if (invalidAllowPatterns.length > 0) {
|
|
155
|
-
errorParts.push(`invalid allowPatterns: ${invalidAllowPatterns.join(', ')}`);
|
|
156
|
-
}
|
|
157
|
-
if (unsafeAllowPatterns.length > 0) {
|
|
158
|
-
errorParts.push(`unsafe allowPatterns (avoid nested quantifiers that risk catastrophic backtracking): ${unsafeAllowPatterns.join(', ')}`);
|
|
159
|
-
}
|
|
160
|
-
throw new Error(`no-handler-suffix: ${errorParts.join('; ')}`);
|
|
161
|
-
}
|
|
141
|
+
const allowPatterns = (0, compilePatternOption_1.compilePatternOption)('no-handler-suffix', 'allowPatterns', resolvedOptions.allowPatterns ?? [], undefined, UNSAFE_ALLOW_PATTERN_REJECTION);
|
|
162
142
|
if (isInAllowedFile(filename, resolvedOptions.allowFilePatterns ?? [])) {
|
|
163
143
|
return {};
|
|
164
144
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noRenderFunctionComponents = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const compilePatternOption_1 = require("../utils/compilePatternOption");
|
|
5
6
|
const createRule_1 = require("../utils/createRule");
|
|
6
7
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
8
|
/**
|
|
@@ -95,7 +96,10 @@ exports.noRenderFunctionComponents = (0, createRule_1.createRule)({
|
|
|
95
96
|
...DEFAULT_RENDER_PROP_NAMES,
|
|
96
97
|
...userRenderPropNames,
|
|
97
98
|
]);
|
|
98
|
-
|
|
99
|
+
// Rejecting a malformed `allowNames` entry rather than dropping it keeps the
|
|
100
|
+
// consumer's allowlist honest: a silently discarded pattern would report the
|
|
101
|
+
// functions they deliberately exempted with no indication why.
|
|
102
|
+
const allowNamePatterns = (0, compilePatternOption_1.compilePatternOption)('no-render-function-components', 'allowNames', options?.allowNames ?? []);
|
|
99
103
|
const candidates = [];
|
|
100
104
|
function isAllowed(name) {
|
|
101
105
|
return allowNamePatterns.some((pattern) => pattern.test(name));
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noSeparateLoadingState = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const compilePatternOption_1 = require("../utils/compilePatternOption");
|
|
5
6
|
const createRule_1 = require("../utils/createRule");
|
|
6
7
|
const LOADING_PATTERNS = [
|
|
7
8
|
/^is.*Loading$/i,
|
|
@@ -34,7 +35,18 @@ exports.noSeparateLoadingState = (0, createRule_1.createRule)({
|
|
|
34
35
|
},
|
|
35
36
|
defaultOptions: [{}],
|
|
36
37
|
create(context, [options]) {
|
|
37
|
-
|
|
38
|
+
// Rejecting a malformed `patterns` entry rather than falling back to
|
|
39
|
+
// `LOADING_PATTERNS` keeps the consumer's detection list honest: a silent
|
|
40
|
+
// fallback would look configured while leaving the names they meant to flag
|
|
41
|
+
// unreported.
|
|
42
|
+
//
|
|
43
|
+
// The `undefined` check is load-bearing: an absent `patterns` falls back to
|
|
44
|
+
// the built-ins, while an explicit empty list stays empty, so the option can
|
|
45
|
+
// disable name matching entirely.
|
|
46
|
+
const configuredPatterns = options?.patterns === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: (0, compilePatternOption_1.compilePatternOption)('no-separate-loading-state', 'patterns', options.patterns, 'i');
|
|
49
|
+
const effectivePatterns = configuredPatterns ?? LOADING_PATTERNS;
|
|
38
50
|
const setterTrackers = [];
|
|
39
51
|
function isLoadingPattern(name) {
|
|
40
52
|
return effectivePatterns.some((pattern) => pattern.test(name));
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parallelizeAsyncOperations = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
7
|
// Anchored at the end of the path so multi-part suffixes such as
|
|
7
8
|
// `EventRegistry.integration.test.ts` are recognized while production modules
|
|
8
9
|
// that merely contain the word (`testHelpers.ts`, `latest.ts`, `contest/Thing.ts`)
|
|
@@ -334,6 +335,145 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
334
335
|
}
|
|
335
336
|
return callee.object.name;
|
|
336
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Matches the `Promise` combinators that take an array of promises and
|
|
340
|
+
* return a single promise standing in for the whole group. Awaiting one of
|
|
341
|
+
* them does not consume the member promises: they stay reachable through
|
|
342
|
+
* whatever names fed the combinator, so a later await mentioning one of
|
|
343
|
+
* those names is reading a promise the aggregate already owns. Anchored so
|
|
344
|
+
* only the combinator itself matches, never a user helper whose name merely
|
|
345
|
+
* contains the word.
|
|
346
|
+
*/
|
|
347
|
+
const PROMISE_AGGREGATOR_PATTERN = /^(all|allSettled|any|race)$/;
|
|
348
|
+
/**
|
|
349
|
+
* Node types that open a new function body. Traversals that ask "does
|
|
350
|
+
* evaluating this expression suspend the enclosing async function?" must
|
|
351
|
+
* stop here, because an `await` beyond this boundary belongs to the inner
|
|
352
|
+
* function and runs only when that function is called.
|
|
353
|
+
*/
|
|
354
|
+
const FUNCTION_BOUNDARY_TYPES = new Set([
|
|
355
|
+
utils_1.AST_NODE_TYPES.FunctionDeclaration,
|
|
356
|
+
utils_1.AST_NODE_TYPES.FunctionExpression,
|
|
357
|
+
utils_1.AST_NODE_TYPES.ArrowFunctionExpression,
|
|
358
|
+
]);
|
|
359
|
+
/**
|
|
360
|
+
* Reports whether evaluating this expression suspends the enclosing async
|
|
361
|
+
* function -- i.e. whether it contains an `await` of its own.
|
|
362
|
+
*
|
|
363
|
+
* The traversal deliberately stops at every function boundary: the awaits in
|
|
364
|
+
* `Promise.all(items.map(async (item) => await store(item)))` belong to the
|
|
365
|
+
* callback, so the outer expression evaluates straight through to a promise
|
|
366
|
+
* without ever suspending, and hoisting it is safe.
|
|
367
|
+
*/
|
|
368
|
+
function containsSuspendingAwait(node) {
|
|
369
|
+
if (isAwaitExpression(node)) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (FUNCTION_BOUNDARY_TYPES.has(node.type)) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
for (const key in node) {
|
|
376
|
+
if (key === 'parent' || key === 'range' || key === 'loc')
|
|
377
|
+
continue;
|
|
378
|
+
const child = node[key];
|
|
379
|
+
if (!child || typeof child !== 'object')
|
|
380
|
+
continue;
|
|
381
|
+
if (Array.isArray(child)) {
|
|
382
|
+
for (const item of child) {
|
|
383
|
+
if (item &&
|
|
384
|
+
typeof item === 'object' &&
|
|
385
|
+
'type' in item &&
|
|
386
|
+
containsSuspendingAwait(item)) {
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else if ('type' in child && containsSuspendingAwait(child)) {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Follows an identifier back to the array literal a `const` binds to it.
|
|
399
|
+
*
|
|
400
|
+
* Only `const` qualifies. A `let`/`var` array can be reassigned between the
|
|
401
|
+
* declaration and the await, so its literal elements are not a sound
|
|
402
|
+
* description of what the aggregate actually received, and treating them as
|
|
403
|
+
* such would suppress reports on genuinely independent operations.
|
|
404
|
+
*/
|
|
405
|
+
function resolveConstArrayLiteral(identifier) {
|
|
406
|
+
const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(ASTHelpers_1.ASTHelpers.getScope(context, identifier), identifier.name);
|
|
407
|
+
if (!variable) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
for (const definition of variable.defs) {
|
|
411
|
+
const declarator = definition.node;
|
|
412
|
+
if (declarator.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
413
|
+
declarator.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclaration &&
|
|
414
|
+
declarator.parent.kind === 'const' &&
|
|
415
|
+
declarator.init?.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
416
|
+
return declarator.init;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Expands an awaited promise aggregate (`await Promise.all(ops)`) to the
|
|
423
|
+
* names through which its member promises remain reachable afterwards: the
|
|
424
|
+
* array's own binding plus every element that is a bare identifier, or a
|
|
425
|
+
* spread of one.
|
|
426
|
+
*
|
|
427
|
+
* This is the aliasing channel the plain identifier-set comparison cannot
|
|
428
|
+
* see. In `await Promise.all(ops); await release({ results: [await
|
|
429
|
+
* dropped] })` the two awaits share no identifier at all -- one holds
|
|
430
|
+
* `Promise`/`all`/`ops`, the other `release`/`dropped` -- yet `dropped` is
|
|
431
|
+
* `ops[0]`, so the release genuinely cannot start before the drop settles.
|
|
432
|
+
* Elements that are freshly-constructed promises (`assign()`) are omitted
|
|
433
|
+
* because nothing binds them to a name, so no later await can reference
|
|
434
|
+
* them. (#1541)
|
|
435
|
+
*/
|
|
436
|
+
function getAggregatedPromiseNames(awaitExpr) {
|
|
437
|
+
const names = new Set();
|
|
438
|
+
const argument = awaitExpr.argument.type === utils_1.AST_NODE_TYPES.ChainExpression
|
|
439
|
+
? awaitExpr.argument.expression
|
|
440
|
+
: awaitExpr.argument;
|
|
441
|
+
if (argument.type !== utils_1.AST_NODE_TYPES.CallExpression) {
|
|
442
|
+
return names;
|
|
443
|
+
}
|
|
444
|
+
const callee = argument.callee;
|
|
445
|
+
if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
|
|
446
|
+
callee.computed ||
|
|
447
|
+
callee.object.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
448
|
+
callee.object.name !== 'Promise' ||
|
|
449
|
+
callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
|
|
450
|
+
!PROMISE_AGGREGATOR_PATTERN.test(callee.property.name)) {
|
|
451
|
+
return names;
|
|
452
|
+
}
|
|
453
|
+
const [aggregated] = argument.arguments;
|
|
454
|
+
if (!aggregated) {
|
|
455
|
+
return names;
|
|
456
|
+
}
|
|
457
|
+
let elements = [];
|
|
458
|
+
if (aggregated.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
459
|
+
elements = aggregated.elements;
|
|
460
|
+
}
|
|
461
|
+
else if (aggregated.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
462
|
+
names.add(aggregated.name);
|
|
463
|
+
elements = resolveConstArrayLiteral(aggregated)?.elements ?? [];
|
|
464
|
+
}
|
|
465
|
+
for (const element of elements) {
|
|
466
|
+
if (!element)
|
|
467
|
+
continue;
|
|
468
|
+
const value = element.type === utils_1.AST_NODE_TYPES.SpreadElement
|
|
469
|
+
? element.argument
|
|
470
|
+
: element;
|
|
471
|
+
if (value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
472
|
+
names.add(value.name);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return names;
|
|
476
|
+
}
|
|
337
477
|
/**
|
|
338
478
|
* Checks if there are dependencies between await expressions
|
|
339
479
|
*/
|
|
@@ -486,6 +626,51 @@ exports.parallelizeAsyncOperations = (0, createRule_1.createRule)({
|
|
|
486
626
|
}
|
|
487
627
|
}
|
|
488
628
|
}
|
|
629
|
+
// 8. Aggregate-element aliasing barrier. `await Promise.all(ops)` does not
|
|
630
|
+
// consume the promises in `ops`; they stay reachable by name, so a later
|
|
631
|
+
// await that mentions one of them -- `await release({ results: [await
|
|
632
|
+
// dropped] })`, where `dropped` is `ops[0]` -- reads a value the aggregate
|
|
633
|
+
// is still producing. The two awaits share no identifier at all
|
|
634
|
+
// (`Promise`/`all`/`ops` versus `release`/`dropped`), so the direct set
|
|
635
|
+
// comparison above classifies them independent; expanding the aggregate to
|
|
636
|
+
// its element names restores the link. Promise.all-ing that pair would
|
|
637
|
+
// start the consumer concurrently with the very operation whose result it
|
|
638
|
+
// reads. (#1541)
|
|
639
|
+
for (let i = 1; i < awaitNodes.length; i++) {
|
|
640
|
+
const currentIds = allIdentifiers[i];
|
|
641
|
+
if (currentIds.size === 0)
|
|
642
|
+
continue;
|
|
643
|
+
for (let j = 0; j < i; j++) {
|
|
644
|
+
const priorExpr = getAwaitExpression(awaitNodes[j]);
|
|
645
|
+
if (!priorExpr)
|
|
646
|
+
continue;
|
|
647
|
+
for (const aggregatedName of getAggregatedPromiseNames(priorExpr)) {
|
|
648
|
+
if (currentIds.has(aggregatedName)) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// 9. Nested-await hoist barrier. The rewrite splices each awaited
|
|
655
|
+
// expression into a `Promise.all([...])` ARRAY LITERAL, and array elements
|
|
656
|
+
// evaluate left to right. An element that contains an `await` of its own
|
|
657
|
+
// suspends the enclosing function mid-literal -- after the earlier
|
|
658
|
+
// elements' promises have been constructed, but before `Promise.all` has
|
|
659
|
+
// been called to attach handlers to them. If one of those already-running
|
|
660
|
+
// promises rejects during the suspension, the function throws at the inner
|
|
661
|
+
// await and the rejected promise is orphaned into an `unhandledRejection`,
|
|
662
|
+
// which the Cloud Functions runtime answers by killing the instance, so
|
|
663
|
+
// the caller receives an opaque crash instead of the real error. A leading
|
|
664
|
+
// nested await is unsound in a quieter way: it suspends before the later
|
|
665
|
+
// elements are evaluated, so their operations never start early and the
|
|
666
|
+
// rewrite buys no parallelism while still reshaping the code. Keep any run
|
|
667
|
+
// containing such an expression sequential. (#1541)
|
|
668
|
+
for (const node of awaitNodes) {
|
|
669
|
+
const awaitExpr = getAwaitExpression(node);
|
|
670
|
+
if (awaitExpr && containsSuspendingAwait(awaitExpr.argument)) {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
489
674
|
return false;
|
|
490
675
|
}
|
|
491
676
|
/**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern options are declared as bare `string[]` because JSON Schema cannot
|
|
3
|
+
* express "is a compilable regex". Schema validation therefore hands any string
|
|
4
|
+
* straight to `new RegExp`, and an exception raised while building a rule aborts
|
|
5
|
+
* the entire lint run — every file, every other rule — with an opaque
|
|
6
|
+
* `Error while loading rule …` naming neither the option nor the offending
|
|
7
|
+
* value.
|
|
8
|
+
*
|
|
9
|
+
* Rejecting the configuration is the right response: silently dropping a
|
|
10
|
+
* pattern would leave the consumer's allowlist inert, so the code they
|
|
11
|
+
* deliberately excluded would be reported anyway with no indication why. This
|
|
12
|
+
* helper makes the rejection actionable and uniform — every failure is
|
|
13
|
+
* collected and rethrown as a single error naming the rule, the option and each
|
|
14
|
+
* bad pattern alongside the underlying regex error.
|
|
15
|
+
*/
|
|
16
|
+
export type PatternRejection = {
|
|
17
|
+
/**
|
|
18
|
+
* Refuses a pattern that compiles but is still unacceptable — a source with
|
|
19
|
+
* nested quantifiers, say, which risks catastrophic backtracking. Checked
|
|
20
|
+
* before compilation, so a refused pattern is never also reported as invalid.
|
|
21
|
+
*/
|
|
22
|
+
isRejected: (pattern: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Builds the clause head for refused patterns, e.g.
|
|
25
|
+
* `unsafe allowPatterns (avoid nested quantifiers…)`. Receives the option name
|
|
26
|
+
* so callers need not repeat it.
|
|
27
|
+
*/
|
|
28
|
+
describe: (optionName: string) => string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Compiles a user-supplied list of regex sources, throwing one actionable
|
|
32
|
+
* configuration error listing every pattern that failed.
|
|
33
|
+
*
|
|
34
|
+
* @param ruleName Rule id used to prefix the thrown message.
|
|
35
|
+
* @param optionName Option the patterns came from, named in the thrown message.
|
|
36
|
+
* @param patterns Regex source strings supplied by the consumer.
|
|
37
|
+
* @param flags Flags applied to every compiled pattern (`'i'`, say).
|
|
38
|
+
* @param rejection Optional extra admissibility check applied before compiling.
|
|
39
|
+
*/
|
|
40
|
+
export declare function compilePatternOption(ruleName: string, optionName: string, patterns: readonly string[], flags?: string, rejection?: PatternRejection): RegExp[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pattern options are declared as bare `string[]` because JSON Schema cannot
|
|
4
|
+
* express "is a compilable regex". Schema validation therefore hands any string
|
|
5
|
+
* straight to `new RegExp`, and an exception raised while building a rule aborts
|
|
6
|
+
* the entire lint run — every file, every other rule — with an opaque
|
|
7
|
+
* `Error while loading rule …` naming neither the option nor the offending
|
|
8
|
+
* value.
|
|
9
|
+
*
|
|
10
|
+
* Rejecting the configuration is the right response: silently dropping a
|
|
11
|
+
* pattern would leave the consumer's allowlist inert, so the code they
|
|
12
|
+
* deliberately excluded would be reported anyway with no indication why. This
|
|
13
|
+
* helper makes the rejection actionable and uniform — every failure is
|
|
14
|
+
* collected and rethrown as a single error naming the rule, the option and each
|
|
15
|
+
* bad pattern alongside the underlying regex error.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.compilePatternOption = void 0;
|
|
19
|
+
function describeError(error) {
|
|
20
|
+
return error && typeof error === 'object' && 'message' in error
|
|
21
|
+
? ` (${String(error.message)})`
|
|
22
|
+
: '';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compiles a user-supplied list of regex sources, throwing one actionable
|
|
26
|
+
* configuration error listing every pattern that failed.
|
|
27
|
+
*
|
|
28
|
+
* @param ruleName Rule id used to prefix the thrown message.
|
|
29
|
+
* @param optionName Option the patterns came from, named in the thrown message.
|
|
30
|
+
* @param patterns Regex source strings supplied by the consumer.
|
|
31
|
+
* @param flags Flags applied to every compiled pattern (`'i'`, say).
|
|
32
|
+
* @param rejection Optional extra admissibility check applied before compiling.
|
|
33
|
+
*/
|
|
34
|
+
function compilePatternOption(ruleName, optionName, patterns, flags, rejection) {
|
|
35
|
+
const invalid = [];
|
|
36
|
+
const rejected = [];
|
|
37
|
+
const compiled = patterns.flatMap((pattern) => {
|
|
38
|
+
try {
|
|
39
|
+
if (rejection?.isRejected(pattern)) {
|
|
40
|
+
rejected.push(pattern);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
return [new RegExp(pattern, flags)];
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
invalid.push(`${pattern}${describeError(error)}`);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
if (invalid.length === 0 && rejected.length === 0) {
|
|
51
|
+
return compiled;
|
|
52
|
+
}
|
|
53
|
+
const clauses = [];
|
|
54
|
+
if (invalid.length > 0) {
|
|
55
|
+
clauses.push(`invalid ${optionName}: ${invalid.join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
if (rejected.length > 0 && rejection) {
|
|
58
|
+
clauses.push(`${rejection.describe(optionName)}: ${rejected.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`${ruleName}: ${clauses.join('; ')}`);
|
|
61
|
+
}
|
|
62
|
+
exports.compilePatternOption = compilePatternOption;
|
|
63
|
+
//# sourceMappingURL=compilePatternOption.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blumintinc/eslint-plugin-blumint",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.53",
|
|
4
4
|
"description": "Custom eslint rules for use within BluMint",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Brodie McGuire",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"@types/eslint": "8.37.0",
|
|
74
74
|
"@types/jest": "29.5.14",
|
|
75
75
|
"@types/node": "22.20.0",
|
|
76
|
+
"@types/semver": "7.5.8",
|
|
76
77
|
"@typescript-eslint/eslint-plugin": "5.34.0",
|
|
77
78
|
"@typescript-eslint/parser": "5.48.0",
|
|
78
79
|
"chai": "4.5.0",
|
|
@@ -103,13 +104,14 @@
|
|
|
103
104
|
"remark-lint": "9.1.1",
|
|
104
105
|
"remark-preset-lint-recommended": "6.1.2",
|
|
105
106
|
"semantic-release": "25.0.2",
|
|
107
|
+
"semver": "7.7.1",
|
|
106
108
|
"ts-jest": "29.2.6",
|
|
107
109
|
"ts-node": "10.9.1",
|
|
108
110
|
"tsx": "4.19.4",
|
|
109
111
|
"typescript": "5.0.3"
|
|
110
112
|
},
|
|
111
113
|
"peerDependencies": {
|
|
112
|
-
"eslint": ">=7"
|
|
114
|
+
"eslint": ">=7 <9"
|
|
113
115
|
},
|
|
114
116
|
"engines": {
|
|
115
117
|
"node": ">=22.0.0"
|
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,48 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.53",
|
|
4
|
+
"date": "2026-08-01T00:55:57.763Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "parallelize-async-operations",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1541
|
|
11
|
+
],
|
|
12
|
+
"summary": "stop hoisting dependent and await-containing operations (closes #1541)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.20.52",
|
|
18
|
+
"date": "2026-07-31T19:23:46.059Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "enforce-m3-sentence-case",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
1534
|
|
25
|
+
],
|
|
26
|
+
"summary": "validate ignorePatterns regexes with an actionable error (closes #1534)"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "no-render-function-components",
|
|
30
|
+
"changeType": "fix",
|
|
31
|
+
"issues": [
|
|
32
|
+
1536
|
|
33
|
+
],
|
|
34
|
+
"summary": "validate allowNames regexes with an actionable error (closes #1536)"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "no-separate-loading-state",
|
|
38
|
+
"changeType": "fix",
|
|
39
|
+
"issues": [
|
|
40
|
+
1535
|
|
41
|
+
],
|
|
42
|
+
"summary": "validate patterns regexes with an actionable error (closes #1535)"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
2
46
|
{
|
|
3
47
|
"version": "1.20.51",
|
|
4
48
|
"date": "2026-07-31T15:20:05.562Z",
|