@blumintinc/eslint-plugin-blumint 1.20.51 → 1.20.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/lib/index.js CHANGED
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.51',
226
+ version: '1.20.52',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -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
- const ignorePatternRegexes = (options.ignorePatterns ?? []).map((p) => new RegExp(p));
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 invalidAllowPatterns = [];
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
- const allowNamePatterns = (options?.allowNames ?? []).map((pattern) => new RegExp(pattern));
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
- const effectivePatterns = options?.patterns?.map((p) => new RegExp(p, 'i')) ?? LOADING_PATTERNS;
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));
@@ -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.51",
3
+ "version": "1.20.52",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,34 @@
1
1
  [
2
+ {
3
+ "version": "1.20.52",
4
+ "date": "2026-07-31T19:23:46.059Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-m3-sentence-case",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1534
11
+ ],
12
+ "summary": "validate ignorePatterns regexes with an actionable error (closes #1534)"
13
+ },
14
+ {
15
+ "name": "no-render-function-components",
16
+ "changeType": "fix",
17
+ "issues": [
18
+ 1536
19
+ ],
20
+ "summary": "validate allowNames regexes with an actionable error (closes #1536)"
21
+ },
22
+ {
23
+ "name": "no-separate-loading-state",
24
+ "changeType": "fix",
25
+ "issues": [
26
+ 1535
27
+ ],
28
+ "summary": "validate patterns regexes with an actionable error (closes #1535)"
29
+ }
30
+ ]
31
+ },
2
32
  {
3
33
  "version": "1.20.51",
4
34
  "date": "2026-07-31T15:20:05.562Z",