eslint-plugin-absolute 0.2.0 → 0.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.
@@ -8,45 +8,69 @@ type MinVarLengthOption = {
8
8
  type Options = [MinVarLengthOption?];
9
9
  type MessageIds = "variableNameTooShort";
10
10
 
11
- export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
12
- meta: {
13
- type: "problem",
14
- docs: {
15
- description:
16
- "Disallow variable names shorter than the configured minimum length unless an outer variable with a longer name starting with the same characters exists. You can exempt specific variable names using the allowedVars option."
17
- },
18
- schema: [
19
- {
20
- type: "object",
21
- properties: {
22
- minLength: {
23
- type: "number",
24
- default: 1
25
- },
26
- allowedVars: {
27
- type: "array",
28
- items: {
29
- type: "string",
30
- minLength: 1
31
- // Note: The maxLength for each string should be at most the configured minLength.
32
- },
33
- default: []
34
- }
35
- },
36
- additionalProperties: false
37
- }
38
- ],
39
- messages: {
40
- variableNameTooShort:
41
- "Variable '{{name}}' is too short. Minimum allowed length is {{minLength}} characters unless an outer variable with a longer name starting with '{{name}}' exists."
42
- }
43
- },
11
+ /**
12
+ * Recursively extract identifier names from a pattern (for destructuring)
13
+ */
14
+ const extractIdentifiersFromPattern = (
15
+ pattern: TSESTree.Node | null,
16
+ identifiers: string[] = []
17
+ ) => {
18
+ if (!pattern) return identifiers;
19
+ switch (pattern.type) {
20
+ case "Identifier":
21
+ identifiers.push(pattern.name);
22
+ break;
23
+ case "ObjectPattern":
24
+ pattern.properties.forEach((prop) => {
25
+ if (prop.type === "Property") {
26
+ extractIdentifiersFromPattern(prop.value, identifiers);
27
+ } else if (prop.type === "RestElement") {
28
+ extractIdentifiersFromPattern(prop.argument, identifiers);
29
+ }
30
+ });
31
+ break;
32
+ case "ArrayPattern":
33
+ pattern.elements
34
+ .filter(
35
+ (element): element is TSESTree.DestructuringPattern =>
36
+ element !== null
37
+ )
38
+ .forEach((element) => {
39
+ extractIdentifiersFromPattern(element, identifiers);
40
+ });
41
+ break;
42
+ case "AssignmentPattern":
43
+ extractIdentifiersFromPattern(pattern.left, identifiers);
44
+ break;
45
+ default:
46
+ break;
47
+ }
48
+ return identifiers;
49
+ };
44
50
 
45
- defaultOptions: [{}],
51
+ const getDeclaratorNames = (declarations: TSESTree.VariableDeclarator[]) =>
52
+ declarations
53
+ .filter(
54
+ (
55
+ decl
56
+ ): decl is TSESTree.VariableDeclarator & {
57
+ id: TSESTree.Identifier;
58
+ } => decl.id.type === "Identifier"
59
+ )
60
+ .map((decl) => decl.id.name);
46
61
 
62
+ const collectParamNames = (params: TSESTree.Parameter[]) => {
63
+ const names: string[] = [];
64
+ params.forEach((param) => {
65
+ extractIdentifiersFromPattern(param, names);
66
+ });
67
+ return names;
68
+ };
69
+
70
+ export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
47
71
  create(context) {
48
- const sourceCode = context.sourceCode;
49
- const options = context.options[0];
72
+ const { sourceCode } = context;
73
+ const [options] = context.options;
50
74
  const configuredMinLength =
51
75
  options && typeof options.minLength === "number"
52
76
  ? options.minLength
@@ -60,7 +84,7 @@ export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
60
84
  const allowedVars = configuredAllowedVars;
61
85
 
62
86
  // Helper: walk up the node.parent chain to get ancestors.
63
- function getAncestors(node: TSESTree.Node) {
87
+ const getAncestors = (node: TSESTree.Node) => {
64
88
  const ancestors: TSESTree.Node[] = [];
65
89
  let current: TSESTree.Node | null | undefined = node.parent;
66
90
  while (current) {
@@ -68,11 +92,11 @@ export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
68
92
  current = current.parent;
69
93
  }
70
94
  return ancestors;
71
- }
95
+ };
72
96
 
73
97
  // Helper: retrieve the scope for a given node using the scopeManager.
74
- function getScope(node: TSESTree.Node): TSESLint.Scope.Scope | null {
75
- const scopeManager = sourceCode.scopeManager;
98
+ const getScope = (node: TSESTree.Node) => {
99
+ const { scopeManager } = sourceCode;
76
100
  if (!scopeManager) {
77
101
  return null;
78
102
  }
@@ -81,220 +105,193 @@ export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
81
105
  return acquired;
82
106
  }
83
107
  return scopeManager.globalScope ?? null;
84
- }
108
+ };
85
109
 
86
110
  // Fallback: get declared variable names in the nearest BlockStatement.
87
- function getVariablesInNearestBlock(node: TSESTree.Node) {
111
+ const getVariablesInNearestBlock = (node: TSESTree.Node) => {
88
112
  let current: TSESTree.Node | null | undefined = node.parent;
89
113
  while (current && current.type !== "BlockStatement") {
90
114
  current = current.parent;
91
115
  }
92
- const names: string[] = [];
93
116
  if (
94
- current &&
95
- current.type === "BlockStatement" &&
96
- Array.isArray(current.body)
117
+ !current ||
118
+ current.type !== "BlockStatement" ||
119
+ !Array.isArray(current.body)
97
120
  ) {
98
- for (const stmt of current.body) {
99
- if (stmt.type === "VariableDeclaration") {
100
- for (const decl of stmt.declarations) {
101
- if (decl.id && decl.id.type === "Identifier") {
102
- names.push(decl.id.name);
103
- }
104
- }
105
- }
106
- }
121
+ return [];
107
122
  }
108
- return names;
109
- }
110
123
 
111
- /**
112
- * Recursively extract identifier names from a pattern (for destructuring)
113
- * @param {ASTNode} pattern The pattern node.
114
- * @param {string[]} identifiers Array to accumulate names.
115
- * @returns {string[]} Array of identifier names.
116
- */
117
- function extractIdentifiersFromPattern(
118
- pattern: TSESTree.Node | null,
119
- identifiers: string[] = []
120
- ): string[] {
121
- if (!pattern) return identifiers;
122
- switch (pattern.type) {
123
- case "Identifier":
124
- identifiers.push(pattern.name);
125
- break;
126
- case "ObjectPattern":
127
- for (const prop of pattern.properties) {
128
- if (prop.type === "Property") {
129
- extractIdentifiersFromPattern(
130
- prop.value,
131
- identifiers
132
- );
133
- } else if (prop.type === "RestElement") {
134
- extractIdentifiersFromPattern(
135
- prop.argument,
136
- identifiers
137
- );
138
- }
139
- }
140
- break;
141
- case "ArrayPattern":
142
- for (const element of pattern.elements) {
143
- if (element) {
144
- extractIdentifiersFromPattern(element, identifiers);
145
- }
146
- }
147
- break;
148
- case "AssignmentPattern":
149
- extractIdentifiersFromPattern(pattern.left, identifiers);
150
- break;
151
- default:
152
- break;
153
- }
154
- return identifiers;
155
- }
124
+ const varDeclarations = current.body.filter(
125
+ (stmt): stmt is TSESTree.VariableDeclaration =>
126
+ stmt.type === "VariableDeclaration"
127
+ );
128
+ return varDeclarations.flatMap((stmt) =>
129
+ getDeclaratorNames(stmt.declarations)
130
+ );
131
+ };
156
132
 
157
- /**
158
- * Checks if there is an outer variable whose name is longer than the current short name
159
- * and starts with the same characters.
160
- * It first uses the scope manager; if that doesn’t yield a match,
161
- * it falls back on scanning the nearest block and ancestors.
162
- * @param {string} shortName The variable name that is shorter than minLength.
163
- * @param {ASTNode} node The current identifier node.
164
- * @returns {boolean} True if an outer variable is found.
165
- */
166
- function hasOuterCorrespondingIdentifier(
133
+ const isLongerMatchInScope = (shortName: string, varName: string) =>
134
+ varName.length >= minLength &&
135
+ varName.length > shortName.length &&
136
+ varName.startsWith(shortName);
137
+
138
+ const checkScopeVariables = (
167
139
  shortName: string,
168
140
  node: TSESTree.Identifier
169
- ) {
170
- // First, try using the scope manager.
141
+ ) => {
171
142
  const startingScope = getScope(node);
172
143
  let outer =
173
144
  startingScope && startingScope.upper
174
145
  ? startingScope.upper
175
146
  : null;
176
147
  while (outer) {
177
- for (const variable of outer.variables) {
178
- if (
179
- variable.name.length >= minLength &&
180
- variable.name.length > shortName.length &&
181
- variable.name.startsWith(shortName)
182
- ) {
183
- return true;
184
- }
185
- }
186
- outer = outer.upper;
187
- }
188
- // Fallback: scan the nearest BlockStatement.
189
- const blockVars = getVariablesInNearestBlock(node);
190
- for (const name of blockVars) {
191
148
  if (
192
- name.length >= minLength &&
193
- name.length > shortName.length &&
194
- name.startsWith(shortName)
149
+ outer.variables.some((variable) =>
150
+ isLongerMatchInScope(shortName, variable.name)
151
+ )
195
152
  ) {
196
153
  return true;
197
154
  }
155
+ outer = outer.upper;
198
156
  }
199
- // Fallback: scan ancestors.
157
+ return false;
158
+ };
159
+
160
+ const checkBlockVariables = (
161
+ shortName: string,
162
+ node: TSESTree.Identifier
163
+ ) => {
164
+ const blockVars = getVariablesInNearestBlock(node);
165
+ return blockVars.some((varName) =>
166
+ isLongerMatchInScope(shortName, varName)
167
+ );
168
+ };
169
+
170
+ const checkAncestorDeclarators = (
171
+ shortName: string,
172
+ node: TSESTree.Identifier
173
+ ) => {
200
174
  const ancestors = getAncestors(node);
201
- for (const anc of ancestors) {
202
- if (
175
+ return ancestors.some(
176
+ (anc) =>
203
177
  anc.type === "VariableDeclarator" &&
204
178
  anc.id &&
205
- anc.id.type === "Identifier"
206
- ) {
207
- const outerName = anc.id.name;
208
- if (
209
- outerName.length >= minLength &&
210
- outerName.length > shortName.length &&
211
- outerName.startsWith(shortName)
212
- ) {
213
- return true;
214
- }
215
- }
179
+ anc.id.type === "Identifier" &&
180
+ isLongerMatchInScope(shortName, anc.id.name)
181
+ );
182
+ };
183
+
184
+ const checkFunctionAncestor = (
185
+ shortName: string,
186
+ anc:
187
+ | TSESTree.FunctionDeclaration
188
+ | TSESTree.FunctionExpression
189
+ | TSESTree.ArrowFunctionExpression
190
+ ) => {
191
+ const names = collectParamNames(anc.params);
192
+ return names.some((paramName) =>
193
+ isLongerMatchInScope(shortName, paramName)
194
+ );
195
+ };
196
+
197
+ const checkCatchAncestor = (
198
+ shortName: string,
199
+ anc: TSESTree.CatchClause
200
+ ) => {
201
+ if (!anc.param) {
202
+ return false;
203
+ }
204
+ const names = extractIdentifiersFromPattern(anc.param, []);
205
+ return names.some((paramName) =>
206
+ isLongerMatchInScope(shortName, paramName)
207
+ );
208
+ };
209
+
210
+ const checkAncestorParams = (
211
+ shortName: string,
212
+ node: TSESTree.Identifier
213
+ ) => {
214
+ const ancestors = getAncestors(node);
215
+ return ancestors.some((anc) => {
216
216
  if (
217
- (anc.type === "FunctionDeclaration" ||
218
- anc.type === "FunctionExpression" ||
219
- anc.type === "ArrowFunctionExpression") &&
220
- Array.isArray(anc.params)
217
+ anc.type === "FunctionDeclaration" ||
218
+ anc.type === "FunctionExpression" ||
219
+ anc.type === "ArrowFunctionExpression"
221
220
  ) {
222
- for (const param of anc.params) {
223
- const names = extractIdentifiersFromPattern(param, []);
224
- for (const n of names) {
225
- if (
226
- n.length >= minLength &&
227
- n.length > shortName.length &&
228
- n.startsWith(shortName)
229
- ) {
230
- return true;
231
- }
232
- }
233
- }
221
+ return checkFunctionAncestor(shortName, anc);
234
222
  }
235
- if (anc.type === "CatchClause" && anc.param) {
236
- const names = extractIdentifiersFromPattern(anc.param, []);
237
- for (const n of names) {
238
- if (
239
- n.length >= minLength &&
240
- n.length > shortName.length &&
241
- n.startsWith(shortName)
242
- ) {
243
- return true;
244
- }
245
- }
223
+ if (anc.type === "CatchClause") {
224
+ return checkCatchAncestor(shortName, anc);
246
225
  }
247
- }
248
- return false;
249
- }
226
+ return false;
227
+ });
228
+ };
229
+
230
+ /**
231
+ * Checks if there is an outer variable whose name is longer than the current short name
232
+ * and starts with the same characters.
233
+ */
234
+ const hasOuterCorrespondingIdentifier = (
235
+ shortName: string,
236
+ node: TSESTree.Identifier
237
+ ) =>
238
+ checkScopeVariables(shortName, node) ||
239
+ checkBlockVariables(shortName, node) ||
240
+ checkAncestorDeclarators(shortName, node) ||
241
+ checkAncestorParams(shortName, node);
250
242
 
251
243
  /**
252
244
  * Checks an Identifier node. If its name is shorter than minLength (and not in the allowed list)
253
245
  * and no outer variable with a longer name starting with the short name is found, it reports an error.
254
- * @param {ASTNode} node The Identifier node.
255
246
  */
256
- function checkIdentifier(node: TSESTree.Identifier) {
257
- const name = node.name;
258
- if (name.length < minLength) {
259
- // If the name is in the allowed list, skip.
260
- if (allowedVars.includes(name)) {
261
- return;
262
- }
263
- if (!hasOuterCorrespondingIdentifier(name, node)) {
264
- context.report({
265
- node,
266
- messageId: "variableNameTooShort",
267
- data: { name, minLength }
268
- });
269
- }
247
+ const checkIdentifier = (node: TSESTree.Identifier) => {
248
+ const { name } = node;
249
+ if (name.length >= minLength) {
250
+ return;
270
251
  }
271
- }
252
+
253
+ // If the name is in the allowed list, skip.
254
+ if (allowedVars.includes(name)) {
255
+ return;
256
+ }
257
+ if (!hasOuterCorrespondingIdentifier(name, node)) {
258
+ context.report({
259
+ data: { minLength, name },
260
+ messageId: "variableNameTooShort",
261
+ node
262
+ });
263
+ }
264
+ };
272
265
 
273
266
  /**
274
267
  * Recursively checks a pattern node for identifiers.
275
- * @param {ASTNode} pattern The pattern node.
276
268
  */
277
- function checkPattern(pattern: TSESTree.Node | null) {
269
+ const checkPattern = (pattern: TSESTree.Node | null) => {
278
270
  if (!pattern) return;
279
271
  switch (pattern.type) {
280
272
  case "Identifier":
281
273
  checkIdentifier(pattern);
282
274
  break;
283
275
  case "ObjectPattern":
284
- for (const prop of pattern.properties) {
276
+ pattern.properties.forEach((prop) => {
285
277
  if (prop.type === "Property") {
286
278
  checkPattern(prop.value);
287
279
  } else if (prop.type === "RestElement") {
288
280
  checkPattern(prop.argument);
289
281
  }
290
- }
282
+ });
291
283
  break;
292
284
  case "ArrayPattern":
293
- for (const element of pattern.elements) {
294
- if (element) {
285
+ pattern.elements
286
+ .filter(
287
+ (
288
+ element
289
+ ): element is TSESTree.DestructuringPattern =>
290
+ element !== null
291
+ )
292
+ .forEach((element) => {
295
293
  checkPattern(element);
296
- }
297
- }
294
+ });
298
295
  break;
299
296
  case "AssignmentPattern":
300
297
  checkPattern(pattern.left);
@@ -302,12 +299,12 @@ export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
302
299
  default:
303
300
  break;
304
301
  }
305
- }
302
+ };
306
303
 
307
304
  return {
308
- VariableDeclarator(node: TSESTree.VariableDeclarator) {
309
- if (node.id) {
310
- checkPattern(node.id);
305
+ CatchClause(node: TSESTree.CatchClause) {
306
+ if (node.param) {
307
+ checkPattern(node.param);
311
308
  }
312
309
  },
313
310
  "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
@@ -316,15 +313,48 @@ export const minVarLength: TSESLint.RuleModule<MessageIds, Options> = {
316
313
  | TSESTree.FunctionExpression
317
314
  | TSESTree.ArrowFunctionExpression
318
315
  ) {
319
- for (const param of node.params) {
316
+ node.params.forEach((param) => {
320
317
  checkPattern(param);
321
- }
318
+ });
322
319
  },
323
- CatchClause(node: TSESTree.CatchClause) {
324
- if (node.param) {
325
- checkPattern(node.param);
320
+ VariableDeclarator(node: TSESTree.VariableDeclarator) {
321
+ if (node.id) {
322
+ checkPattern(node.id);
326
323
  }
327
324
  }
328
325
  };
326
+ },
327
+ defaultOptions: [{}],
328
+ meta: {
329
+ docs: {
330
+ description:
331
+ "Disallow variable names shorter than the configured minimum length unless an outer variable with a longer name starting with the same characters exists. You can exempt specific variable names using the allowedVars option."
332
+ },
333
+ messages: {
334
+ variableNameTooShort:
335
+ "Variable '{{name}}' is too short. Minimum allowed length is {{minLength}} characters unless an outer variable with a longer name starting with '{{name}}' exists."
336
+ },
337
+ schema: [
338
+ {
339
+ additionalProperties: false,
340
+ properties: {
341
+ allowedVars: {
342
+ default: [],
343
+ items: {
344
+ minLength: 1,
345
+ type: "string"
346
+ // Note: The maxLength for each string should be at most the configured minLength.
347
+ },
348
+ type: "array"
349
+ },
350
+ minLength: {
351
+ default: 1,
352
+ type: "number"
353
+ }
354
+ },
355
+ type: "object"
356
+ }
357
+ ],
358
+ type: "problem"
329
359
  }
330
360
  };