eslint-config-agent 1.0.21 → 1.0.23
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/CHANGELOG.md +16 -0
- package/index.js +49 -17
- package/package.json +1 -2
- package/rules/max-file-lines/index.js +43 -0
- package/rules/max-file-lines/max-file-lines.spec.js +75 -0
- package/rules/max-function-lines/index.js +43 -0
- package/rules/max-function-lines/max-function-lines.spec.js +75 -0
- package/rules/no-env-access/index.js +66 -0
- package/rules/no-env-access/no-env-access.spec.js +196 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. See [Conven
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [1.0.23](https://github.com/tupe12334/eslint-config/compare/v1.0.22...v1.0.23) (2025-09-05)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add custom no-env-access rule with tests and update test runner ([b4f4fc5](https://github.com/tupe12334/eslint-config/commit/b4f4fc5fdecfdc8fa6f1524403d2f6df8afb3a40))
|
|
12
|
+
|
|
13
|
+
## [1.0.22](https://github.com/tupe12334/eslint-config/compare/v1.0.21...v1.0.22) (2025-09-04)
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* add max-lines and max-lines-per-function rules with configurations and tests ([218f29c](https://github.com/tupe12334/eslint-config/commit/218f29ca497ca1b49e027f3e33f87cf836e7ff02))
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* resolve linting issues for release ([3c812cd](https://github.com/tupe12334/eslint-config/commit/3c812cd223f169360cb0d1520e401a17a6a9b960))
|
|
22
|
+
|
|
7
23
|
## [1.0.21](https://github.com/tupe12334/eslint-config/compare/v1.0.19...v1.0.21) (2025-09-04)
|
|
8
24
|
|
|
9
25
|
### Bug Fixes
|
package/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
2
1
|
import js from "@eslint/js";
|
|
3
2
|
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
4
3
|
import tsParser from "@typescript-eslint/parser";
|
|
@@ -7,6 +6,9 @@ import reactPlugin from "eslint-plugin-react";
|
|
|
7
6
|
import importPlugin from "eslint-plugin-import";
|
|
8
7
|
import globals from "globals";
|
|
9
8
|
import noTrailingSpacesConfig from "./rules/no-trailing-spaces/index.js";
|
|
9
|
+
import { maxFunctionLinesWarning, maxFunctionLinesError } from "./rules/max-function-lines/index.js";
|
|
10
|
+
import { maxFileLinesWarning, maxFileLinesError } from "./rules/max-file-lines/index.js";
|
|
11
|
+
import noEnvAccessRule from "./rules/no-env-access/index.js";
|
|
10
12
|
|
|
11
13
|
// Conditionally import preact plugin if available
|
|
12
14
|
let preactPlugin = null;
|
|
@@ -16,9 +18,6 @@ try {
|
|
|
16
18
|
// eslint-plugin-preact is not available
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const compat = new FlatCompat({
|
|
20
|
-
recommendedConfig: js.configs.recommended,
|
|
21
|
-
});
|
|
22
21
|
|
|
23
22
|
// Shared rules for both JS and TS files
|
|
24
23
|
const sharedRules = {
|
|
@@ -41,10 +40,8 @@ const sharedRules = {
|
|
|
41
40
|
"import/first": "off",
|
|
42
41
|
"import/prefer-default-export": "off",
|
|
43
42
|
"react/react-in-jsx-scope": "off",
|
|
44
|
-
"max-lines-per-function":
|
|
45
|
-
|
|
46
|
-
{ max: 100, skipBlankLines: true, skipComments: true },
|
|
47
|
-
],
|
|
43
|
+
"max-lines-per-function": maxFunctionLinesWarning,
|
|
44
|
+
"max-lines": maxFileLinesWarning,
|
|
48
45
|
"react/jsx-filename-extension": ["error", { extensions: [".tsx", ".jsx"] }],
|
|
49
46
|
semi: "off",
|
|
50
47
|
"react/function-component-definition": "off",
|
|
@@ -64,6 +61,7 @@ const sharedRules = {
|
|
|
64
61
|
"react/jsx-no-useless-fragment": "off",
|
|
65
62
|
"import/group-exports": "off",
|
|
66
63
|
"import/no-default-export": "off",
|
|
64
|
+
"custom-rules/no-env-access": "error",
|
|
67
65
|
};
|
|
68
66
|
|
|
69
67
|
// Shared no-restricted-syntax rules for both JS and TS
|
|
@@ -73,8 +71,10 @@ const sharedRestrictedSyntax = [
|
|
|
73
71
|
message: "Optional chaining is not allowed.",
|
|
74
72
|
},
|
|
75
73
|
{
|
|
76
|
-
selector:
|
|
77
|
-
|
|
74
|
+
selector:
|
|
75
|
+
"MemberExpression[object.type='MemberExpression'][object.object.name='process'][object.property.name='env']",
|
|
76
|
+
message:
|
|
77
|
+
"Direct access to process.env properties is not allowed. Use a configuration object or environment validation instead.",
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
selector: "CallExpression[optional=true]",
|
|
@@ -255,13 +255,22 @@ const tsOnlyRestrictedSyntax = [
|
|
|
255
255
|
];
|
|
256
256
|
|
|
257
257
|
const config = [
|
|
258
|
+
// Global plugin definitions
|
|
259
|
+
{
|
|
260
|
+
plugins: {
|
|
261
|
+
"custom-rules": {
|
|
262
|
+
rules: {
|
|
263
|
+
"no-env-access": noEnvAccessRule,
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
258
268
|
reactHooks.configs["recommended-latest"],
|
|
259
269
|
{
|
|
260
270
|
ignores: ["packages/auth-service-sdk/**"],
|
|
261
271
|
},
|
|
262
272
|
js.configs.recommended,
|
|
263
273
|
|
|
264
|
-
|
|
265
274
|
// TypeScript and TSX files
|
|
266
275
|
{
|
|
267
276
|
files: ["**/*.ts", "**/*.tsx"],
|
|
@@ -500,10 +509,7 @@ const config = [
|
|
|
500
509
|
},
|
|
501
510
|
rules: {
|
|
502
511
|
...sharedRules,
|
|
503
|
-
"no-restricted-syntax": [
|
|
504
|
-
"error",
|
|
505
|
-
...sharedRestrictedSyntax,
|
|
506
|
-
],
|
|
512
|
+
"no-restricted-syntax": ["error", ...sharedRestrictedSyntax],
|
|
507
513
|
},
|
|
508
514
|
},
|
|
509
515
|
|
|
@@ -769,8 +775,10 @@ const config = [
|
|
|
769
775
|
"error",
|
|
770
776
|
// Process.env rule (applies to all file types)
|
|
771
777
|
{
|
|
772
|
-
selector:
|
|
773
|
-
|
|
778
|
+
selector:
|
|
779
|
+
"MemberExpression[object.type='MemberExpression'][object.object.name='process'][object.property.name='env']",
|
|
780
|
+
message:
|
|
781
|
+
"Direct access to process.env properties is not allowed. Use a configuration object or environment validation instead.",
|
|
774
782
|
},
|
|
775
783
|
// Switch case rules as errors
|
|
776
784
|
{
|
|
@@ -853,6 +861,30 @@ const config = [
|
|
|
853
861
|
},
|
|
854
862
|
},
|
|
855
863
|
|
|
864
|
+
// Function and file length rules - strict error thresholds
|
|
865
|
+
{
|
|
866
|
+
files: ["**/*.{ts,tsx,js,jsx}"],
|
|
867
|
+
rules: {
|
|
868
|
+
// Function length: error at 70+ lines
|
|
869
|
+
"max-lines-per-function": maxFunctionLinesError,
|
|
870
|
+
// File length: error at 100+ lines
|
|
871
|
+
"max-lines": maxFileLinesError,
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
|
|
875
|
+
// Disable file length rules for configuration and spec files
|
|
876
|
+
{
|
|
877
|
+
files: [
|
|
878
|
+
"index.js", // Main configuration file
|
|
879
|
+
"**/rules/**/*.spec.js", // Spec files in rules directory
|
|
880
|
+
"**/scripts/**/*.js", // Script files
|
|
881
|
+
],
|
|
882
|
+
rules: {
|
|
883
|
+
"max-lines": "off",
|
|
884
|
+
"max-lines-per-function": "off",
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
|
|
856
888
|
// Allow default exports in configuration files (must be last to override)
|
|
857
889
|
{
|
|
858
890
|
files: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-config-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "ESLint configuration package with TypeScript support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"test:performance": "eslint test/performance-test.tsx",
|
|
30
30
|
"test:comprehensive": "node scripts/test-runner.js",
|
|
31
31
|
"test:ci": "eslint . --ignore-pattern 'test/**' --ignore-pattern 'scripts/**' --max-warnings 0",
|
|
32
|
-
"test:rules": "node rules/no-trailing-spaces/no-trailing-spaces.spec.js",
|
|
33
32
|
"validate": "node scripts/validate-config.js",
|
|
34
33
|
"release": "dotenv -e .env -- release-it",
|
|
35
34
|
"release:patch": "dotenv -e .env -- release-it patch",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule configuration for max-lines
|
|
3
|
+
*
|
|
4
|
+
* This rule enforces a maximum number of lines per file to encourage
|
|
5
|
+
* smaller, more maintainable files and better separation of concerns.
|
|
6
|
+
*
|
|
7
|
+
* Warnings at >70 lines, errors at >100 lines
|
|
8
|
+
*
|
|
9
|
+
* @see https://eslint.org/docs/latest/rules/max-lines
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Warning configuration (70 lines)
|
|
13
|
+
export const warningRule = "warn";
|
|
14
|
+
export const warningOptions = {
|
|
15
|
+
max: 70,
|
|
16
|
+
skipBlankLines: true,
|
|
17
|
+
skipComments: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Error configuration (100 lines)
|
|
21
|
+
export const errorRule = "error";
|
|
22
|
+
export const errorOptions = {
|
|
23
|
+
max: 100,
|
|
24
|
+
skipBlankLines: true,
|
|
25
|
+
skipComments: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Export the warning rule configuration (applied first)
|
|
30
|
+
* Can be used in ESLint config as:
|
|
31
|
+
* "max-lines": maxFileLinesWarning
|
|
32
|
+
*/
|
|
33
|
+
export const maxFileLinesWarning = [warningRule, warningOptions];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Export the error rule configuration (applied later to override)
|
|
37
|
+
* Can be used in ESLint config as:
|
|
38
|
+
* "max-lines": maxFileLinesError
|
|
39
|
+
*/
|
|
40
|
+
export const maxFileLinesError = [errorRule, errorOptions];
|
|
41
|
+
|
|
42
|
+
// Default export is the warning configuration
|
|
43
|
+
export default maxFileLinesWarning;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* global process */
|
|
2
|
+
import {
|
|
3
|
+
warningRule,
|
|
4
|
+
warningOptions,
|
|
5
|
+
errorRule,
|
|
6
|
+
errorOptions,
|
|
7
|
+
maxFileLinesWarning,
|
|
8
|
+
maxFileLinesError
|
|
9
|
+
} from "./index.js";
|
|
10
|
+
|
|
11
|
+
console.log("Testing max-file-lines rule configuration exports...");
|
|
12
|
+
|
|
13
|
+
// Test warning configuration
|
|
14
|
+
console.log("\n📋 Warning Configuration:");
|
|
15
|
+
console.log("Rule level:", warningRule);
|
|
16
|
+
console.log("Options:", warningOptions);
|
|
17
|
+
console.log("Complete config:", maxFileLinesWarning);
|
|
18
|
+
|
|
19
|
+
// Validate warning configuration
|
|
20
|
+
if (warningRule === "warn" &&
|
|
21
|
+
warningOptions.max === 70 &&
|
|
22
|
+
warningOptions.skipBlankLines === true &&
|
|
23
|
+
warningOptions.skipComments === true) {
|
|
24
|
+
console.log("✅ Warning configuration is correct");
|
|
25
|
+
} else {
|
|
26
|
+
console.log("❌ Warning configuration is incorrect");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Test error configuration
|
|
31
|
+
console.log("\n📋 Error Configuration:");
|
|
32
|
+
console.log("Rule level:", errorRule);
|
|
33
|
+
console.log("Options:", errorOptions);
|
|
34
|
+
console.log("Complete config:", maxFileLinesError);
|
|
35
|
+
|
|
36
|
+
// Validate error configuration
|
|
37
|
+
if (errorRule === "error" &&
|
|
38
|
+
errorOptions.max === 100 &&
|
|
39
|
+
errorOptions.skipBlankLines === true &&
|
|
40
|
+
errorOptions.skipComments === true) {
|
|
41
|
+
console.log("✅ Error configuration is correct");
|
|
42
|
+
} else {
|
|
43
|
+
console.log("❌ Error configuration is incorrect");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log("\n✅ All max-file-lines configuration tests passed!");
|
|
48
|
+
|
|
49
|
+
// Test that the configurations are properly formatted for ESLint
|
|
50
|
+
console.log("\n📋 ESLint Configuration Format:");
|
|
51
|
+
console.log("Warning format:", JSON.stringify(maxFileLinesWarning));
|
|
52
|
+
console.log("Error format:", JSON.stringify(maxFileLinesError));
|
|
53
|
+
|
|
54
|
+
// Validate ESLint format
|
|
55
|
+
if (Array.isArray(maxFileLinesWarning) &&
|
|
56
|
+
maxFileLinesWarning.length === 2 &&
|
|
57
|
+
maxFileLinesWarning[0] === "warn" &&
|
|
58
|
+
typeof maxFileLinesWarning[1] === "object") {
|
|
59
|
+
console.log("✅ Warning ESLint format is correct");
|
|
60
|
+
} else {
|
|
61
|
+
console.log("❌ Warning ESLint format is incorrect");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(maxFileLinesError) &&
|
|
66
|
+
maxFileLinesError.length === 2 &&
|
|
67
|
+
maxFileLinesError[0] === "error" &&
|
|
68
|
+
typeof maxFileLinesError[1] === "object") {
|
|
69
|
+
console.log("✅ Error ESLint format is correct");
|
|
70
|
+
} else {
|
|
71
|
+
console.log("❌ Error ESLint format is incorrect");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log("\n🎉 All tests completed successfully!");
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule configuration for max-lines-per-function
|
|
3
|
+
*
|
|
4
|
+
* This rule enforces a maximum number of lines per function to encourage
|
|
5
|
+
* smaller, more maintainable functions.
|
|
6
|
+
*
|
|
7
|
+
* Warnings at >50 lines, errors at >70 lines
|
|
8
|
+
*
|
|
9
|
+
* @see https://eslint.org/docs/latest/rules/max-lines-per-function
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Warning configuration (50 lines)
|
|
13
|
+
export const warningRule = "warn";
|
|
14
|
+
export const warningOptions = {
|
|
15
|
+
max: 50,
|
|
16
|
+
skipBlankLines: true,
|
|
17
|
+
skipComments: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Error configuration (70 lines)
|
|
21
|
+
export const errorRule = "error";
|
|
22
|
+
export const errorOptions = {
|
|
23
|
+
max: 70,
|
|
24
|
+
skipBlankLines: true,
|
|
25
|
+
skipComments: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Export the warning rule configuration (applied first)
|
|
30
|
+
* Can be used in ESLint config as:
|
|
31
|
+
* "max-lines-per-function": maxFunctionLinesWarning
|
|
32
|
+
*/
|
|
33
|
+
export const maxFunctionLinesWarning = [warningRule, warningOptions];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Export the error rule configuration (applied later to override)
|
|
37
|
+
* Can be used in ESLint config as:
|
|
38
|
+
* "max-lines-per-function": maxFunctionLinesError
|
|
39
|
+
*/
|
|
40
|
+
export const maxFunctionLinesError = [errorRule, errorOptions];
|
|
41
|
+
|
|
42
|
+
// Default export is the warning configuration
|
|
43
|
+
export default maxFunctionLinesWarning;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/* global process */
|
|
2
|
+
import {
|
|
3
|
+
warningRule,
|
|
4
|
+
warningOptions,
|
|
5
|
+
errorRule,
|
|
6
|
+
errorOptions,
|
|
7
|
+
maxFunctionLinesWarning,
|
|
8
|
+
maxFunctionLinesError
|
|
9
|
+
} from "./index.js";
|
|
10
|
+
|
|
11
|
+
console.log("Testing max-function-lines rule configuration exports...");
|
|
12
|
+
|
|
13
|
+
// Test warning configuration
|
|
14
|
+
console.log("\n📋 Warning Configuration:");
|
|
15
|
+
console.log("Rule level:", warningRule);
|
|
16
|
+
console.log("Options:", warningOptions);
|
|
17
|
+
console.log("Complete config:", maxFunctionLinesWarning);
|
|
18
|
+
|
|
19
|
+
// Validate warning configuration
|
|
20
|
+
if (warningRule === "warn" &&
|
|
21
|
+
warningOptions.max === 50 &&
|
|
22
|
+
warningOptions.skipBlankLines === true &&
|
|
23
|
+
warningOptions.skipComments === true) {
|
|
24
|
+
console.log("✅ Warning configuration is correct");
|
|
25
|
+
} else {
|
|
26
|
+
console.log("❌ Warning configuration is incorrect");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Test error configuration
|
|
31
|
+
console.log("\n📋 Error Configuration:");
|
|
32
|
+
console.log("Rule level:", errorRule);
|
|
33
|
+
console.log("Options:", errorOptions);
|
|
34
|
+
console.log("Complete config:", maxFunctionLinesError);
|
|
35
|
+
|
|
36
|
+
// Validate error configuration
|
|
37
|
+
if (errorRule === "error" &&
|
|
38
|
+
errorOptions.max === 70 &&
|
|
39
|
+
errorOptions.skipBlankLines === true &&
|
|
40
|
+
errorOptions.skipComments === true) {
|
|
41
|
+
console.log("✅ Error configuration is correct");
|
|
42
|
+
} else {
|
|
43
|
+
console.log("❌ Error configuration is incorrect");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log("\n✅ All max-function-lines configuration tests passed!");
|
|
48
|
+
|
|
49
|
+
// Test that the configurations are properly formatted for ESLint
|
|
50
|
+
console.log("\n📋 ESLint Configuration Format:");
|
|
51
|
+
console.log("Warning format:", JSON.stringify(maxFunctionLinesWarning));
|
|
52
|
+
console.log("Error format:", JSON.stringify(maxFunctionLinesError));
|
|
53
|
+
|
|
54
|
+
// Validate ESLint format
|
|
55
|
+
if (Array.isArray(maxFunctionLinesWarning) &&
|
|
56
|
+
maxFunctionLinesWarning.length === 2 &&
|
|
57
|
+
maxFunctionLinesWarning[0] === "warn" &&
|
|
58
|
+
typeof maxFunctionLinesWarning[1] === "object") {
|
|
59
|
+
console.log("✅ Warning ESLint format is correct");
|
|
60
|
+
} else {
|
|
61
|
+
console.log("❌ Warning ESLint format is incorrect");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(maxFunctionLinesError) &&
|
|
66
|
+
maxFunctionLinesError.length === 2 &&
|
|
67
|
+
maxFunctionLinesError[0] === "error" &&
|
|
68
|
+
typeof maxFunctionLinesError[1] === "object") {
|
|
69
|
+
console.log("✅ Error ESLint format is correct");
|
|
70
|
+
} else {
|
|
71
|
+
console.log("❌ Error ESLint format is incorrect");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log("\n🎉 All tests completed successfully!");
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule: no-env-access
|
|
3
|
+
*
|
|
4
|
+
* Prevents direct access to properties on variables named 'env'.
|
|
5
|
+
* This encourages the use of configuration objects or environment validation
|
|
6
|
+
* instead of directly accessing environment variables.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // ❌ Bad - direct env access
|
|
10
|
+
* const nodeEnv = env.NODE_ENV;
|
|
11
|
+
* const port = env.PORT;
|
|
12
|
+
*
|
|
13
|
+
* // ✅ Good - configuration object
|
|
14
|
+
* const config = getEnvConfig();
|
|
15
|
+
* const nodeEnv = config.nodeEnv;
|
|
16
|
+
*
|
|
17
|
+
* // ✅ Good - not property access
|
|
18
|
+
* const env = 'production';
|
|
19
|
+
* const envConfig = { env: 'development' };
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const rule = {
|
|
23
|
+
meta: {
|
|
24
|
+
type: "problem",
|
|
25
|
+
docs: {
|
|
26
|
+
description: "Disallow direct access to properties on 'env' variables",
|
|
27
|
+
category: "Best Practices",
|
|
28
|
+
recommended: true,
|
|
29
|
+
},
|
|
30
|
+
messages: {
|
|
31
|
+
noEnvAccess:
|
|
32
|
+
"Direct access to env properties is not allowed. Use a configuration object or environment validation instead.",
|
|
33
|
+
},
|
|
34
|
+
schema: [], // No options
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
create(context) {
|
|
38
|
+
return {
|
|
39
|
+
MemberExpression(node) {
|
|
40
|
+
// Check if we're accessing a property on a variable named 'env'
|
|
41
|
+
if (
|
|
42
|
+
node.object &&
|
|
43
|
+
node.object.type === "Identifier" &&
|
|
44
|
+
node.object.name === "env" &&
|
|
45
|
+
node.computed === false // Only flag dot notation, not bracket notation
|
|
46
|
+
) {
|
|
47
|
+
// Don't flag if this member expression itself is being called as a function
|
|
48
|
+
// e.g., env.call(), env.apply(), but do flag env.NODE_ENV.toLowerCase()
|
|
49
|
+
const isDirectMethodCall =
|
|
50
|
+
node.parent &&
|
|
51
|
+
node.parent.type === "CallExpression" &&
|
|
52
|
+
node.parent.callee === node;
|
|
53
|
+
|
|
54
|
+
if (!isDirectMethodCall) {
|
|
55
|
+
context.report({
|
|
56
|
+
node,
|
|
57
|
+
messageId: "noEnvAccess",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default rule;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { RuleTester } from "eslint";
|
|
2
|
+
import noEnvAccessRule from "./index.js";
|
|
3
|
+
|
|
4
|
+
// Create RuleTester instance
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
ecmaVersion: "latest",
|
|
8
|
+
sourceType: "module",
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Test the no-env-access custom rule
|
|
13
|
+
ruleTester.run("no-env-access", noEnvAccessRule, {
|
|
14
|
+
valid: [
|
|
15
|
+
// Valid: not accessing properties on 'env'
|
|
16
|
+
"const foo = 'bar';",
|
|
17
|
+
"const config = getEnvConfig();",
|
|
18
|
+
"const nodeEnv = config.NODE_ENV;",
|
|
19
|
+
|
|
20
|
+
// Valid: 'env' as a value, not property access
|
|
21
|
+
"const env = 'production';",
|
|
22
|
+
"const environment = env;",
|
|
23
|
+
"const settings = { env: 'development' };",
|
|
24
|
+
"const mode = settings.env;",
|
|
25
|
+
|
|
26
|
+
// Valid: different variable names
|
|
27
|
+
"const environment = { NODE_ENV: 'test' };",
|
|
28
|
+
"const nodeEnv = environment.NODE_ENV;",
|
|
29
|
+
"const config = { NODE_ENV: 'prod' };",
|
|
30
|
+
"const env_config = { PORT: 3000 };",
|
|
31
|
+
"const port = env_config.PORT;",
|
|
32
|
+
|
|
33
|
+
// Valid: function calls
|
|
34
|
+
"env();",
|
|
35
|
+
"env.call();",
|
|
36
|
+
"env.apply();",
|
|
37
|
+
"getEnv().NODE_ENV;",
|
|
38
|
+
|
|
39
|
+
// Valid: bracket notation (not enforced by this rule)
|
|
40
|
+
"const nodeEnv = env['NODE_ENV'];",
|
|
41
|
+
"const port = env['PORT'];",
|
|
42
|
+
|
|
43
|
+
// Valid: other operations
|
|
44
|
+
"typeof env;",
|
|
45
|
+
"env instanceof Object;",
|
|
46
|
+
"env || 'default';",
|
|
47
|
+
"env ? 'prod' : 'dev';",
|
|
48
|
+
|
|
49
|
+
// Valid: in function parameters, destructuring, etc.
|
|
50
|
+
"function test(env) { return env; }",
|
|
51
|
+
"const { env } = config;",
|
|
52
|
+
"const [env] = environments;",
|
|
53
|
+
|
|
54
|
+
// Valid: class members named env
|
|
55
|
+
"class Config { env = 'prod'; }",
|
|
56
|
+
"const config = new Config(); config.env;",
|
|
57
|
+
|
|
58
|
+
// Valid: accessing properties on process.env (handled by different rule)
|
|
59
|
+
"const nodeEnv = process.env.NODE_ENV;",
|
|
60
|
+
"const port = process.env.PORT;",
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
invalid: [
|
|
64
|
+
// Invalid: direct property access on 'env' variable
|
|
65
|
+
{
|
|
66
|
+
code: "const nodeEnv = env.NODE_ENV;",
|
|
67
|
+
errors: [
|
|
68
|
+
{
|
|
69
|
+
messageId: "noEnvAccess",
|
|
70
|
+
type: "MemberExpression",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
code: "const port = env.PORT;",
|
|
76
|
+
errors: [
|
|
77
|
+
{
|
|
78
|
+
messageId: "noEnvAccess",
|
|
79
|
+
type: "MemberExpression",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
code: "const apiUrl = env.API_URL;",
|
|
85
|
+
errors: [
|
|
86
|
+
{
|
|
87
|
+
messageId: "noEnvAccess",
|
|
88
|
+
type: "MemberExpression",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
code: "const dbUrl = env.DATABASE_URL;",
|
|
94
|
+
errors: [
|
|
95
|
+
{
|
|
96
|
+
messageId: "noEnvAccess",
|
|
97
|
+
type: "MemberExpression",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Invalid: multiple accesses in same code
|
|
103
|
+
{
|
|
104
|
+
code: `
|
|
105
|
+
const nodeEnv = env.NODE_ENV;
|
|
106
|
+
const port = env.PORT;
|
|
107
|
+
const dbUrl = env.DATABASE_URL;
|
|
108
|
+
`,
|
|
109
|
+
errors: [
|
|
110
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
111
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
112
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Invalid: in various contexts
|
|
117
|
+
{
|
|
118
|
+
code: "console.log(env.NODE_ENV);",
|
|
119
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
code: "if (env.NODE_ENV === 'production') { }",
|
|
123
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
code: "const config = { nodeEnv: env.NODE_ENV };",
|
|
127
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
code: "function test() { return env.NODE_ENV || 'development'; }",
|
|
131
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
code: "const port = parseInt(env.PORT, 10);",
|
|
135
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Invalid: function calls on accessed properties
|
|
139
|
+
{
|
|
140
|
+
code: "const lower = env.NODE_ENV.toLowerCase();",
|
|
141
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
code: "const trimmed = env.API_URL.trim();",
|
|
145
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Invalid: in function parameters and returns
|
|
149
|
+
{
|
|
150
|
+
code: "function getNodeEnv() { return env.NODE_ENV; }",
|
|
151
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
code: "const fn = () => env.NODE_ENV;",
|
|
155
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
code: "someFunction(env.NODE_ENV, env.PORT);",
|
|
159
|
+
errors: [
|
|
160
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
161
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// Invalid: in object/array literals
|
|
166
|
+
{
|
|
167
|
+
code: "const config = [env.NODE_ENV, env.PORT];",
|
|
168
|
+
errors: [
|
|
169
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
170
|
+
{ messageId: "noEnvAccess", type: "MemberExpression" },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
// Invalid: in template literals
|
|
175
|
+
{
|
|
176
|
+
code: "const message = `Environment: ${env.NODE_ENV}`;",
|
|
177
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// Invalid: in assignments and operations
|
|
181
|
+
{
|
|
182
|
+
code: "let nodeEnv; nodeEnv = env.NODE_ENV;",
|
|
183
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
code: "const isDev = env.NODE_ENV === 'development';",
|
|
187
|
+
errors: [{ messageId: "noEnvAccess", type: "MemberExpression" }],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
console.log("✅ All no-env-access rule tests passed!");
|
|
193
|
+
console.log(" Rule prevents direct access to properties on 'env' variables");
|
|
194
|
+
console.log(
|
|
195
|
+
" Encourages use of configuration objects for better environment handling"
|
|
196
|
+
);
|