@blumintinc/eslint-plugin-blumint 1.20.39 → 1.20.40

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.39',
226
+ version: '1.20.40',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -21,6 +21,14 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
21
21
  },
22
22
  defaultOptions: [],
23
23
  create(context) {
24
+ // Anchor the reported path at the directory ESLint was configured with,
25
+ // not the node process cwd. The two differ under the VS Code ESLint
26
+ // extension, in monorepos, and for any programmatic `new ESLint({ cwd })`,
27
+ // where reading the process cwd names the file by a path the reader cannot
28
+ // locate (issue #1475). The `typeof` guard keeps the rule working under
29
+ // harnesses that predate `getCwd`, matching the sibling rules that resolve
30
+ // a cwd.
31
+ const cwd = typeof context.getCwd === 'function' ? context.getCwd() : process.cwd();
24
32
  return {
25
33
  Program(node) {
26
34
  // Normalize Windows backslash separators so the forward-slash `utils/`
@@ -28,8 +36,10 @@ exports.avoidUtilsDirectory = (0, createRule_1.createRule)({
28
36
  // returns `C:\repo\src\utils\foo.ts` on Windows, the regex never
29
37
  // matches, and the rule silently reports nothing (issue #1270).
30
38
  const filename = context.getFilename().replace(/\\/g, '/');
39
+ // `|| filename` covers the case where the file IS the cwd (an empty
40
+ // relative path), keeping a usable path in the message.
31
41
  const relativePath = path_1.default.isAbsolute(filename)
32
- ? path_1.default.relative(process.cwd(), filename) || filename
42
+ ? path_1.default.relative(cwd, filename) || filename
33
43
  : filename;
34
44
  // Skip files in node_modules
35
45
  if (filename.includes('node_modules')) {
@@ -71,31 +71,37 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
71
71
  */
72
72
  const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
73
73
  /**
74
- * Directory of the file being fixed, relative to the repo root, or null for
75
- * virtual/stdin files (RuleTester default 'file.ts', '<input>', '<text>')
76
- * whose non-absolute name cannot anchor a relative path.
74
+ * The directory ESLint itself was configured with, which is what
75
+ * `importPath` is anchored at. The node process cwd is only a fallback for
76
+ * harnesses that predate `getCwd`: the two differ under the VS Code ESLint
77
+ * extension, in monorepos, and for any programmatic `new ESLint({ cwd })`,
78
+ * and anchoring at the process cwd there emits an unresolvable specifier.
79
+ */
80
+ const cwd = typeof context.getCwd === 'function' ? context.getCwd() : process.cwd();
81
+ /**
82
+ * Directory of the file being fixed, relative to the configured cwd, or
83
+ * null for virtual/stdin files (RuleTester default 'file.ts', '<input>',
84
+ * '<text>') whose non-absolute name cannot anchor a relative path.
77
85
  */
78
86
  const fileDirFromRoot = () => {
79
87
  const rawFilename = context.getFilename().replace(/\\/g, '/');
80
88
  if (!path_1.default.isAbsolute(rawFilename)) {
81
89
  return null;
82
90
  }
83
- const fileRelToCwd = path_1.default
84
- .relative(process.cwd(), rawFilename)
85
- .replace(/\\/g, '/');
91
+ const fileRelToCwd = path_1.default.relative(cwd, rawFilename).replace(/\\/g, '/');
86
92
  return path_1.default.posix.dirname(fileRelToCwd);
87
93
  };
88
94
  /**
89
95
  * Computes the module specifier for the injected assertSafe import.
90
96
  *
91
- * `importPath` is anchored at the repo root (relative to process.cwd(),
92
- * which is the repo root in real eslint runs), matching how
93
- * avoid-utils-directory and test-file-location-enforcement treat paths.
94
- * A bare repo-root specifier such as 'functions/src/util/assertSafe' is
95
- * unresolvable inside the functions/ TS project, whose baseUrl is
96
- * functions/: it would resolve to functions/functions/src/util/assertSafe,
97
- * which does not exist. The specifier is therefore derived relative to the
98
- * file being fixed so the emitted import resolves from that file's location.
97
+ * `importPath` is anchored at the repo root the directory ESLint runs
98
+ * with, not the node process cwd matching how avoid-utils-directory and
99
+ * test-file-location-enforcement treat paths. A bare repo-root specifier
100
+ * such as 'functions/src/util/assertSafe' is unresolvable inside the
101
+ * functions/ TS project, whose baseUrl is functions/: it would resolve to
102
+ * functions/functions/src/util/assertSafe, which does not exist. The
103
+ * specifier is therefore derived relative to the file being fixed so the
104
+ * emitted import resolves from that file's location.
99
105
  */
100
106
  const computeImportSpecifier = () => {
101
107
  const fileDir = fileDirFromRoot();
@@ -7,6 +7,19 @@ type Options = [
7
7
  allowImportType?: boolean;
8
8
  }
9
9
  ];
10
+ /**
11
+ * A module this plugin injects on the user's behalf must be one the plugin's
12
+ * own default config accepts. Several rules in the recommended config write a
13
+ * *static* import as part of their autofix; without these entries `--fix`
14
+ * would trade an auto-fixable violation for a non-fixable one, and the
15
+ * suggested remedy is impossible for most of them anyway (hooks must be called
16
+ * unconditionally and decorators must resolve statically, so neither can be
17
+ * loaded dynamically).
18
+ *
19
+ * `src/tests/enforce-dynamic-imports.test.ts` derives the set of specifiers the
20
+ * fixers emit from the rule sources and asserts every external one is listed
21
+ * here, so a seventh injected module cannot slip in unlisted.
22
+ */
10
23
  export declare const DEFAULT_IGNORED_LIBRARIES: string[];
11
24
  export declare const DEFAULT_INTERNAL_PREFIXES: string[];
12
25
  declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"dynamicImportRequired", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -5,6 +5,19 @@ const createRule_1 = require("../utils/createRule");
5
5
  const minimatch_1 = require("minimatch");
6
6
  const module_1 = require("module");
7
7
  exports.RULE_NAME = 'enforce-dynamic-imports';
8
+ /**
9
+ * A module this plugin injects on the user's behalf must be one the plugin's
10
+ * own default config accepts. Several rules in the recommended config write a
11
+ * *static* import as part of their autofix; without these entries `--fix`
12
+ * would trade an auto-fixable violation for a non-fixable one, and the
13
+ * suggested remedy is impossible for most of them anyway (hooks must be called
14
+ * unconditionally and decorators must resolve statically, so neither can be
15
+ * loaded dynamically).
16
+ *
17
+ * `src/tests/enforce-dynamic-imports.test.ts` derives the set of specifiers the
18
+ * fixers emit from the rule sources and asserts every external one is listed
19
+ * here, so a seventh injected module cannot slip in unlisted.
20
+ */
8
21
  exports.DEFAULT_IGNORED_LIBRARIES = [
9
22
  'react',
10
23
  'react/**',
@@ -19,6 +32,13 @@ exports.DEFAULT_IGNORED_LIBRARIES = [
19
32
  '@emotion/**',
20
33
  'clsx',
21
34
  'tailwind-merge',
35
+ // Injected by this plugin's own fixers:
36
+ 'use-latest-callback',
37
+ '@blumintinc/typescript-memoize',
38
+ '@blumintinc/use-deep-compare',
39
+ 'microdiff',
40
+ 'safe-stable-stringify',
41
+ 'fast-deep-equal', // fast-deep-equal-over-microdiff
22
42
  ];
23
43
  exports.DEFAULT_INTERNAL_PREFIXES = ['src/', 'functions/'];
24
44
  // Pre-built set of Node.js core module names for O(1) lookup.
@@ -838,7 +838,7 @@ function isNameDeclaredWithin(root, name) {
838
838
  * anything the parse cannot decide with certainty) still reports, so the rule
839
839
  * cannot be silently disabled by an unresolvable name.
840
840
  */
841
- function isPropLessImportedComponent(program, localName, filename, componentRoot) {
841
+ function isPropLessImportedComponent(program, localName, filename, componentRoot, cwd) {
842
842
  const relativeImport = findRelativeImport(program, localName);
843
843
  if (!relativeImport) {
844
844
  return false;
@@ -849,9 +849,16 @@ function isPropLessImportedComponent(program, localName, filename, componentRoot
849
849
  return false;
850
850
  }
851
851
  try {
852
+ // A relative filename is anchored at the directory ESLint was configured
853
+ // with, never the node process cwd. The two differ under the VS Code ESLint
854
+ // extension, in monorepos, and for any programmatic `new Linter({ cwd })`,
855
+ // and anchoring at the process cwd there resolves the sibling import
856
+ // against the wrong directory: the child module is not found, the
857
+ // prop-less relaxation silently stops applying, and the parent reports a
858
+ // composition it cannot satisfy (issue #1476).
852
859
  const absolute = path_1.default.isAbsolute(filename)
853
860
  ? filename
854
- : path_1.default.resolve(process.cwd(), filename);
861
+ : path_1.default.resolve(cwd, filename);
855
862
  const resolved = resolveRelativeModule(path_1.default.dirname(absolute), relativeImport.source);
856
863
  if (!resolved) {
857
864
  return false;
@@ -948,6 +955,11 @@ exports.requirePropsComposition = (0, createRule_1.createRule)({
948
955
  // `/src/`) so absolute POSIX and Windows paths both resolve (issue #1268).
949
956
  // Glob matching needs forward slashes, while resolving a relative import off
950
957
  // disk needs the platform-native path — keep both.
958
+ // Sibling modules are resolved from disk relative to the file under lint,
959
+ // so a non-absolute filename needs the directory ESLint itself was
960
+ // configured with. The node process cwd is only a fallback for harnesses
961
+ // that predate `getCwd`.
962
+ const cwd = typeof context.getCwd === 'function' ? context.getCwd() : process.cwd();
951
963
  const rawFilename = context.getFilename();
952
964
  const filename = rawFilename.replace(/\\/g, '/');
953
965
  const matchesTargetPath = targetPaths.some((pattern) => {
@@ -1089,7 +1101,7 @@ exports.requirePropsComposition = (0, createRule_1.createRule)({
1089
1101
  // the file reads, and it drops the dep from the *reported* set rather than
1090
1102
  // suppressing the whole report — a sibling child that genuinely needs
1091
1103
  // composition still fires.
1092
- const reportableDeps = depComponents.filter((dep) => !isPropLessImportedComponent(prog, dep, rawFilename, funcNode));
1104
+ const reportableDeps = depComponents.filter((dep) => !isPropLessImportedComponent(prog, dep, rawFilename, funcNode, cwd));
1093
1105
  if (reportableDeps.length < minDependencyCount) {
1094
1106
  return;
1095
1107
  }
@@ -66,6 +66,13 @@ exports.testFileLocationEnforcement = (0, createRule_1.createRule)({
66
66
  ...SUPPORTED_EXTENSIONS,
67
67
  ...additionalExtensions.filter((extension) => !SUPPORTED_EXTENSIONS.includes(extension)),
68
68
  ];
69
+ // Anchor the reported path at the directory ESLint was configured with, not
70
+ // the node process cwd. The two differ under the VS Code ESLint extension,
71
+ // in monorepos, and for any programmatic `new ESLint({ cwd })`, where
72
+ // reading the process cwd names the misplaced test by a path the reader
73
+ // cannot locate (issue #1476). The `typeof` guard keeps the rule working
74
+ // under harnesses that predate `getCwd`.
75
+ const cwd = typeof context.getCwd === 'function' ? context.getCwd() : process.cwd();
69
76
  return {
70
77
  Program(node) {
71
78
  const filename = context.getFilename();
@@ -84,7 +91,7 @@ exports.testFileLocationEnforcement = (0, createRule_1.createRule)({
84
91
  return;
85
92
  }
86
93
  const relativePath = path_1.default.isAbsolute(filename)
87
- ? path_1.default.relative(process.cwd(), filename) || filename
94
+ ? path_1.default.relative(cwd, filename) || filename
88
95
  : filename;
89
96
  // Naming the shortest prefix alongside the full stem keeps the guidance
90
97
  // honest: either subject name satisfies the rule.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.39",
3
+ "version": "1.20.40",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,46 @@
1
1
  [
2
+ {
3
+ "version": "1.20.40",
4
+ "date": "2026-07-30T23:29:30.176Z",
5
+ "rules": [
6
+ {
7
+ "name": "avoid-utils-directory",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1475
11
+ ],
12
+ "summary": "report the path relative to ESLint's cwd (closes #1475)"
13
+ },
14
+ {
15
+ "name": "enforce-assert-safe-object-key",
16
+ "changeType": "fix",
17
+ "issues": [
18
+ 1473
19
+ ],
20
+ "summary": "anchor the injected import at ESLint's cwd (closes #1473)"
21
+ },
22
+ {
23
+ "name": "enforce-dynamic-imports",
24
+ "changeType": "fix",
25
+ "issues": [
26
+ 1474
27
+ ],
28
+ "summary": "accept the helper modules this plugin's own fixers inject (closes #1474)"
29
+ },
30
+ {
31
+ "name": "require-props-composition",
32
+ "changeType": "fix",
33
+ "issues": [],
34
+ "summary": "resolve sibling components from ESLint's cwd"
35
+ },
36
+ {
37
+ "name": "test-file-location-enforcement",
38
+ "changeType": "fix",
39
+ "issues": [],
40
+ "summary": "name the file relative to ESLint's cwd"
41
+ }
42
+ ]
43
+ },
2
44
  {
3
45
  "version": "1.20.39",
4
46
  "date": "2026-07-30T21:18:09.189Z",