eslint-plugin-prefer-let 4.1.0 → 4.2.1

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # eslint-plugin-prefer-let
2
2
 
3
+ ## \[4.2.1]
4
+
5
+ - [`fadad0b`](https://github.com/thefrontside/javascript/commit/fadad0b5cf786b3186d74a35d9933b5e8bea0e34) Ignore `export const` cases
6
+
7
+ ## \[4.2.0]
8
+
9
+ - [`9066434`](https://github.com/thefrontside/javascript/commit/90664342144bce7d4a13812e82c155ba3d4ea7e0) Add `forceUpperCaseConst` option
10
+
3
11
  ## \[4.1.0]
4
12
 
5
13
  - [`6a5ba09`](https://github.com/thefrontside/javascript/commit/6a5ba096b531357d4c9ddd41fe158623cbebf601) Support ESLint v10
package/README.md CHANGED
@@ -82,6 +82,38 @@ Then configure the rules you want to use under the rules section.
82
82
  }
83
83
  ```
84
84
 
85
+ ### Options
86
+
87
+ #### `forceUpperCaseConst`
88
+
89
+ When set to `true`, this option enforces `const` for top-level `UPPER_CASE` names (e.g. `PI`, `API_BASE_URL`)
90
+
91
+ ```json
92
+ {
93
+ "rules": {
94
+ "prefer-let/prefer-let": [2, { "forceUpperCaseConst": true }]
95
+ }
96
+ }
97
+ ```
98
+
99
+ This makes the distinction between true constants and regular bindings explicit and machine-enforced.
100
+
101
+ Good:
102
+
103
+ ```javascript
104
+ const PI = 3.14;
105
+ const API_BASE_URL = 'https://example.com';
106
+
107
+ let config = loadConfig();
108
+ ```
109
+
110
+ Bad:
111
+
112
+ ```javascript
113
+ const config = loadConfig(); // not UPPER_CASE — use let
114
+ let PI = 3.14; // UPPER_CASE — use const
115
+ ```
116
+
85
117
  ### Possible Conflicts
86
118
 
87
119
  This plugin may conflict with other plugins or configs that set `eslint prefer-const`. You can configure the rules to avoid this:
@@ -17,12 +17,22 @@ module.exports = {
17
17
  },
18
18
  fixable: "code", // or "code" or "whitespace"
19
19
  schema: [
20
- // fill in your schema
20
+ {
21
+ type: "object",
22
+ properties: {
23
+ forceUpperCaseConst: {
24
+ type: "boolean"
25
+ }
26
+ },
27
+ additionalProperties: false
28
+ }
21
29
  ]
22
30
  },
23
31
 
24
32
  create: function(context) {
25
33
  let sourceCode = context.sourceCode ?? context.getSourceCode();
34
+ let options = context.options[0] || {};
35
+ let forceUpperCaseConst = options.forceUpperCaseConst || false;
26
36
 
27
37
  //----------------------------------------------------------------------
28
38
  // Helpers
@@ -51,6 +61,40 @@ module.exports = {
51
61
  return isGlobalScope(node) || isModuleScope(node) || isProgramScope(node);
52
62
  }
53
63
 
64
+ function isUpperCase(name) {
65
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(name);
66
+ }
67
+
68
+ function getBindingNames(node) {
69
+ if (node.type === 'Identifier') {
70
+ return [node.name];
71
+ }
72
+ if (node.type === 'ObjectPattern') {
73
+ return node.properties.flatMap(function(prop) {
74
+ return getBindingNames(prop.value || prop.argument);
75
+ });
76
+ }
77
+ if (node.type === 'ArrayPattern') {
78
+ return node.elements.filter(Boolean).flatMap(function(el) {
79
+ return getBindingNames(el);
80
+ });
81
+ }
82
+ if (node.type === 'RestElement') {
83
+ return getBindingNames(node.argument);
84
+ }
85
+ if (node.type === 'AssignmentPattern') {
86
+ return getBindingNames(node.left);
87
+ }
88
+ return [];
89
+ }
90
+
91
+ function allDeclaratorsUpperCase(node) {
92
+ return node.declarations.every(function(decl) {
93
+ let names = getBindingNames(decl.id);
94
+ return names.length > 0 && names.every(isUpperCase);
95
+ });
96
+ }
97
+
54
98
  function isInAmbientContext(node) {
55
99
  let current = node.parent;
56
100
  while (current) {
@@ -76,14 +120,37 @@ module.exports = {
76
120
  message: 'prefer `let` over `var` to declare value bindings',
77
121
  node
78
122
  });
79
- } else if (node.kind === 'const' && !isTopLevelScope(node)) {
80
- let constToken = sourceCode.getFirstToken(node);
81
-
123
+ } else if (node.kind === 'const') {
124
+ if (isTopLevelScope(node)) {
125
+ if (forceUpperCaseConst && !allDeclaratorsUpperCase(node)) {
126
+ let constToken = sourceCode.getFirstToken(node);
127
+ context.report({
128
+ message: '`const` declaration for non-constant names at top-level scope. Use `let` or rename to UPPER_CASE',
129
+ node,
130
+ fix: function(fixer) {
131
+ return fixer.replaceText(constToken, 'let');
132
+ }
133
+ });
134
+ }
135
+ } else if (node.parent && node.parent.type === 'ExportNamedDeclaration') {
136
+ // ignore `export const` cases
137
+ } else {
138
+ let constToken = sourceCode.getFirstToken(node);
139
+ context.report({
140
+ message: '`const` declaration outside top-level scope',
141
+ node,
142
+ fix: function(fixer) {
143
+ return fixer.replaceText(constToken, 'let');
144
+ }
145
+ });
146
+ }
147
+ } else if (node.kind === 'let' && forceUpperCaseConst && isTopLevelScope(node) && allDeclaratorsUpperCase(node)) {
148
+ let letToken = sourceCode.getFirstToken(node);
82
149
  context.report({
83
- message: '`const` declaration outside top-level scope',
150
+ message: 'use `const` for constant names (UPPER_CASE) at top-level scope',
84
151
  node,
85
152
  fix: function(fixer) {
86
- return fixer.replaceText(constToken, 'let');
153
+ return fixer.replaceText(letToken, 'const');
87
154
  }
88
155
  });
89
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-prefer-let",
3
- "version": "4.1.0",
3
+ "version": "4.2.1",
4
4
  "description": "Rule to prefer using `let` to bind names to values",
5
5
  "repository": {
6
6
  "type": "git",