@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 +1 -1
- package/lib/rules/avoid-utils-directory.js +11 -1
- package/lib/rules/enforce-assert-safe-object-key.js +20 -14
- package/lib/rules/enforce-dynamic-imports.d.ts +13 -0
- package/lib/rules/enforce-dynamic-imports.js +20 -0
- package/lib/rules/require-props-composition.js +15 -3
- package/lib/rules/test-file-location-enforcement.js +8 -1
- package/package.json +1 -1
- package/release-manifest.json +42 -0
package/lib/index.js
CHANGED
|
@@ -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(
|
|
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
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
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
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* functions
|
|
97
|
-
*
|
|
98
|
-
*
|
|
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(
|
|
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(
|
|
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
package/release-manifest.json
CHANGED
|
@@ -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",
|