eslint-plugin-flawless 0.1.0
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/LICENSE +23 -0
- package/README.md +200 -0
- package/dist/index.d.mts +164 -0
- package/dist/index.mjs +1622 -0
- package/package.json +90 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1622 @@
|
|
|
1
|
+
import { DefinitionType, ImplicitLibVariable, PatternVisitor, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
|
|
2
|
+
import { requiresQuoting } from "@typescript-eslint/type-utils";
|
|
3
|
+
import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
|
|
4
|
+
import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
|
|
5
|
+
import assert from "node:assert";
|
|
6
|
+
|
|
7
|
+
//#region package.json
|
|
8
|
+
var name = "eslint-plugin-flawless";
|
|
9
|
+
var version = "0.1.0";
|
|
10
|
+
var repository = {
|
|
11
|
+
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
12
|
+
"type": "git"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/util.ts
|
|
17
|
+
const createEslintRule = RuleCreator((name$1) => {
|
|
18
|
+
return `${repository.url.replace(/^git\+/, "").replace(/\.git$/, "")}/blob/v${version}/src/rules/${name$1}/documentation.md`;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/utils/is-type-import.ts
|
|
23
|
+
/**
|
|
24
|
+
* Determine whether a variable definition is a type import. E.g.:.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import type { Foo } from 'foo';
|
|
28
|
+
* import { type Bar } from 'bar';
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @param definition - The variable definition to check.
|
|
32
|
+
* @returns True if the definition is a type import, false otherwise.
|
|
33
|
+
*/
|
|
34
|
+
function isTypeImport(definition) {
|
|
35
|
+
return definition?.type === DefinitionType.ImportBinding && (definition.parent.importKind === "type" || definition.node.type === AST_NODE_TYPES.ImportSpecifier && definition.node.importKind === "type");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/utils/reference-contains-type-query.ts
|
|
40
|
+
/**
|
|
41
|
+
* Recursively checks whether a given reference has a type query declaration among its parents.
|
|
42
|
+
* @param node - The AST node to check.
|
|
43
|
+
* @returns True if a TSTypeQuery is found in the parent chain, false otherwise.
|
|
44
|
+
*/
|
|
45
|
+
function referenceContainsTypeQuery(node) {
|
|
46
|
+
switch (node.type) {
|
|
47
|
+
case AST_NODE_TYPES.Identifier:
|
|
48
|
+
case AST_NODE_TYPES.TSQualifiedName: return referenceContainsTypeQuery(node.parent);
|
|
49
|
+
case AST_NODE_TYPES.TSTypeQuery: return true;
|
|
50
|
+
default: return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/utils/collect-variables.ts
|
|
56
|
+
/**
|
|
57
|
+
* This class leverages an AST visitor to mark variables as used via the
|
|
58
|
+
* `eslintUsed` property.
|
|
59
|
+
*/
|
|
60
|
+
var UnusedVariablesVisitor = class extends Visitor {
|
|
61
|
+
/**
|
|
62
|
+
* We keep a weak cache so that multiple rules can share the calculation.
|
|
63
|
+
*/
|
|
64
|
+
static RESULTS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
65
|
+
#scopeManager;
|
|
66
|
+
ClassDeclaration = this.visitClass;
|
|
67
|
+
ClassExpression = this.visitClass;
|
|
68
|
+
ForInStatement = this.visitForInForOf;
|
|
69
|
+
/**
|
|
70
|
+
* #region HELPERS
|
|
71
|
+
*/
|
|
72
|
+
ForOfStatement = this.visitForInForOf;
|
|
73
|
+
FunctionDeclaration = this.visitFunction;
|
|
74
|
+
FunctionExpression = this.visitFunction;
|
|
75
|
+
MethodDefinition = this.visitSetter;
|
|
76
|
+
Property = this.visitSetter;
|
|
77
|
+
TSCallSignatureDeclaration = this.visitFunctionTypeSignature;
|
|
78
|
+
TSConstructorType = this.visitFunctionTypeSignature;
|
|
79
|
+
TSConstructSignatureDeclaration = this.visitFunctionTypeSignature;
|
|
80
|
+
TSDeclareFunction = this.visitFunctionTypeSignature;
|
|
81
|
+
/**
|
|
82
|
+
* NOTE - This is a simple visitor - meaning it does not support selectors.
|
|
83
|
+
*/
|
|
84
|
+
TSEmptyBodyFunctionExpression = this.visitFunctionTypeSignature;
|
|
85
|
+
TSFunctionType = this.visitFunctionTypeSignature;
|
|
86
|
+
TSMethodSignature = this.visitFunctionTypeSignature;
|
|
87
|
+
constructor(scopeManager) {
|
|
88
|
+
super({ visitChildrenEvenIfSelectorExists: true });
|
|
89
|
+
this.#scopeManager = scopeManager;
|
|
90
|
+
}
|
|
91
|
+
static collectUnusedVariables(program, scopeManager) {
|
|
92
|
+
const cached = this.RESULTS_CACHE.get(program);
|
|
93
|
+
if (cached) return cached;
|
|
94
|
+
const visitor = new this(scopeManager);
|
|
95
|
+
visitor.visit(program);
|
|
96
|
+
const unusedVariables = visitor.collectUnusedVariables({ scope: visitor.getScope(program) });
|
|
97
|
+
this.RESULTS_CACHE.set(program, unusedVariables);
|
|
98
|
+
return unusedVariables;
|
|
99
|
+
}
|
|
100
|
+
Identifier(node) {
|
|
101
|
+
const scope = this.getScope(node);
|
|
102
|
+
if (scope.type === TSESLint.Scope.ScopeType.function && node.name === "this" && "params" in scope.block && scope.block.params.includes(node)) this.markVariableAsUsed(node);
|
|
103
|
+
}
|
|
104
|
+
TSEnumDeclaration(node) {
|
|
105
|
+
const scope = this.getScope(node);
|
|
106
|
+
for (const variable of scope.variables) this.markVariableAsUsed(variable);
|
|
107
|
+
}
|
|
108
|
+
TSMappedType(node) {
|
|
109
|
+
this.markVariableAsUsed(node.key);
|
|
110
|
+
}
|
|
111
|
+
TSModuleDeclaration(node) {
|
|
112
|
+
if (node.kind === "global") this.markVariableAsUsed("global", node.parent);
|
|
113
|
+
}
|
|
114
|
+
TSParameterProperty(node) {
|
|
115
|
+
let identifier;
|
|
116
|
+
if (node.parameter.type === AST_NODE_TYPES.AssignmentPattern) {
|
|
117
|
+
if (node.parameter.left.type === AST_NODE_TYPES.Identifier) identifier = node.parameter.left;
|
|
118
|
+
} else if (node.parameter.type === AST_NODE_TYPES.Identifier) identifier = node.parameter;
|
|
119
|
+
if (identifier) this.markVariableAsUsed(identifier);
|
|
120
|
+
}
|
|
121
|
+
collectUnusedVariables({ scope, variables = {
|
|
122
|
+
unusedVariables: /* @__PURE__ */ new Set(),
|
|
123
|
+
usedVariables: /* @__PURE__ */ new Set()
|
|
124
|
+
} }) {
|
|
125
|
+
if (!scope.functionExpressionScope) for (const variable of scope.variables) {
|
|
126
|
+
if (variable instanceof ImplicitLibVariable) continue;
|
|
127
|
+
if (variable.eslintUsed || isExported$1(variable) || isMergeableExported(variable) || isUsedVariable(variable)) variables.usedVariables.add(variable);
|
|
128
|
+
else variables.unusedVariables.add(variable);
|
|
129
|
+
}
|
|
130
|
+
for (const childScope of scope.childScopes) this.collectUnusedVariables({
|
|
131
|
+
scope: childScope,
|
|
132
|
+
variables
|
|
133
|
+
});
|
|
134
|
+
return variables;
|
|
135
|
+
}
|
|
136
|
+
getScope(currentNode) {
|
|
137
|
+
const inner = currentNode.type !== AST_NODE_TYPES.Program;
|
|
138
|
+
let node = currentNode;
|
|
139
|
+
while (node) {
|
|
140
|
+
const scope = this.#scopeManager.acquire(node, inner);
|
|
141
|
+
if (scope) {
|
|
142
|
+
if (scope.type === ScopeType.functionExpressionName) {
|
|
143
|
+
const returnValue$1 = scope.childScopes[0];
|
|
144
|
+
assert(returnValue$1, "Function expression name scope should have a child scope");
|
|
145
|
+
return returnValue$1;
|
|
146
|
+
}
|
|
147
|
+
return scope;
|
|
148
|
+
}
|
|
149
|
+
node = node.parent;
|
|
150
|
+
}
|
|
151
|
+
const returnValue = this.#scopeManager.scopes[0];
|
|
152
|
+
assert(returnValue, "There should be at least one scope");
|
|
153
|
+
return returnValue;
|
|
154
|
+
}
|
|
155
|
+
markVariableAsUsed(variableOrIdentifierOrName, parent) {
|
|
156
|
+
if (typeof variableOrIdentifierOrName !== "string" && !("type" in variableOrIdentifierOrName)) {
|
|
157
|
+
variableOrIdentifierOrName.eslintUsed = true;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
let name$1;
|
|
161
|
+
let node;
|
|
162
|
+
if (typeof variableOrIdentifierOrName === "string") {
|
|
163
|
+
name$1 = variableOrIdentifierOrName;
|
|
164
|
+
assert(parent, "Parent node is required when marking by name");
|
|
165
|
+
node = parent;
|
|
166
|
+
} else {
|
|
167
|
+
({name: name$1} = variableOrIdentifierOrName);
|
|
168
|
+
node = variableOrIdentifierOrName;
|
|
169
|
+
}
|
|
170
|
+
let currentScope = this.getScope(node);
|
|
171
|
+
while (currentScope) {
|
|
172
|
+
const variable = currentScope.variables.find((scopeVariable) => scopeVariable.name === name$1);
|
|
173
|
+
if (variable) {
|
|
174
|
+
variable.eslintUsed = true;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
currentScope = currentScope.upper ?? void 0;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
visitClass(node) {
|
|
181
|
+
const scope = this.getScope(node);
|
|
182
|
+
for (const variable of scope.variables) if (variable.identifiers[0] === scope.block.id) {
|
|
183
|
+
this.markVariableAsUsed(variable);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
visitForInForOf(node) {
|
|
188
|
+
/**
|
|
189
|
+
* // cspell:ignore Zacher
|
|
190
|
+
* (Brad Zacher): I hate that this has to exist.
|
|
191
|
+
* But it is required for compat with the base ESLint rule.
|
|
192
|
+
*
|
|
193
|
+
* In 2015, ESLint decided to add an exception for these two specific cases.
|
|
194
|
+
* ```
|
|
195
|
+
* for (var key in object) return;
|
|
196
|
+
*
|
|
197
|
+
* var key;
|
|
198
|
+
* for (key in object) return;
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* I disagree with it, but what are you going to do...
|
|
202
|
+
*
|
|
203
|
+
* Https://github.com/eslint/eslint/issues/2342.
|
|
204
|
+
*/
|
|
205
|
+
let idOrVariable;
|
|
206
|
+
if (node.left.type === AST_NODE_TYPES.VariableDeclaration) {
|
|
207
|
+
const variable = this.#scopeManager.getDeclaredVariables(node.left).at(0);
|
|
208
|
+
if (!variable) return;
|
|
209
|
+
idOrVariable = variable;
|
|
210
|
+
}
|
|
211
|
+
if (node.left.type === AST_NODE_TYPES.Identifier) idOrVariable = node.left;
|
|
212
|
+
if (idOrVariable === void 0) return;
|
|
213
|
+
let { body } = node;
|
|
214
|
+
if (body.type === AST_NODE_TYPES.BlockStatement) {
|
|
215
|
+
if (body.body.length !== 1) return;
|
|
216
|
+
body = body.body[0];
|
|
217
|
+
}
|
|
218
|
+
if (body.type !== AST_NODE_TYPES.ReturnStatement) return;
|
|
219
|
+
this.markVariableAsUsed(idOrVariable);
|
|
220
|
+
}
|
|
221
|
+
visitFunction(node) {
|
|
222
|
+
const variable = this.getScope(node).set.get("arguments");
|
|
223
|
+
if (variable?.defs.length === 0) this.markVariableAsUsed(variable);
|
|
224
|
+
}
|
|
225
|
+
visitFunctionTypeSignature(node) {
|
|
226
|
+
for (const parameter of node.params) this.visitPattern(parameter, (name$1) => {
|
|
227
|
+
this.markVariableAsUsed(name$1);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
visitSetter(node) {
|
|
231
|
+
if (node.kind === "set") for (const parameter of node.value.params) this.visitPattern(parameter, (id) => {
|
|
232
|
+
this.markVariableAsUsed(id);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Checks the position of given nodes.
|
|
238
|
+
* @param inner - A node which is expected as inside.
|
|
239
|
+
* @param outer - A node which is expected as outside.
|
|
240
|
+
* @returns `true` if the `inner` node exists in the `outer` node.
|
|
241
|
+
*/
|
|
242
|
+
function isInside(inner, outer) {
|
|
243
|
+
return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Determine if an identifier is referencing an enclosing name.
|
|
247
|
+
* This only applies to declarations that create their own scope (modules, functions, classes).
|
|
248
|
+
* @param ref - The reference to check.
|
|
249
|
+
* @param nodes - The candidate function nodes.
|
|
250
|
+
* @returns True if it's a self-reference, false if not.
|
|
251
|
+
*/
|
|
252
|
+
function isSelfReference(ref, nodes) {
|
|
253
|
+
let scope = ref.from;
|
|
254
|
+
while (scope) {
|
|
255
|
+
if (nodes.has(scope.block)) return true;
|
|
256
|
+
scope = scope.upper ?? void 0;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const MERGEABLE_TYPES = new Set([
|
|
261
|
+
AST_NODE_TYPES.ClassDeclaration,
|
|
262
|
+
AST_NODE_TYPES.FunctionDeclaration,
|
|
263
|
+
AST_NODE_TYPES.TSInterfaceDeclaration,
|
|
264
|
+
AST_NODE_TYPES.TSModuleDeclaration,
|
|
265
|
+
AST_NODE_TYPES.TSTypeAliasDeclaration
|
|
266
|
+
]);
|
|
267
|
+
/**
|
|
268
|
+
* Determines if a given variable is being exported from a module.
|
|
269
|
+
* @param variable - Eslint-scope variable object.
|
|
270
|
+
* @returns True if the variable is exported, false if not.
|
|
271
|
+
*/
|
|
272
|
+
function isExported$1(variable) {
|
|
273
|
+
return variable.defs.some((definition) => {
|
|
274
|
+
let { node } = definition;
|
|
275
|
+
if (node.type === AST_NODE_TYPES.VariableDeclarator) node = node.parent;
|
|
276
|
+
else if (definition.type === TSESLint.Scope.DefinitionType.Parameter) return false;
|
|
277
|
+
return node.parent.type.startsWith("Export");
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Determine if the variable is directly exported.
|
|
282
|
+
* @param variable - The variable to check.
|
|
283
|
+
* @returns True if the variable is exported via a merged declaration.
|
|
284
|
+
*/
|
|
285
|
+
function isMergeableExported(variable) {
|
|
286
|
+
for (const definition of variable.defs) {
|
|
287
|
+
if (definition.type === TSESLint.Scope.DefinitionType.Parameter) continue;
|
|
288
|
+
if (MERGEABLE_TYPES.has(definition.node.type) && definition.node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration || definition.node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration) return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const LOGICAL_ASSIGNMENT_OPERATORS = new Set([
|
|
293
|
+
"&&=",
|
|
294
|
+
"??=",
|
|
295
|
+
"||="
|
|
296
|
+
]);
|
|
297
|
+
/**
|
|
298
|
+
* Collects the set of unused variables for a given context.
|
|
299
|
+
*
|
|
300
|
+
* Due to complexity, this does not take into consideration:
|
|
301
|
+
* - variables within declaration files
|
|
302
|
+
* - variables within ambient module declarations.
|
|
303
|
+
* @param context - The rule context.
|
|
304
|
+
* @returns The collected variables.
|
|
305
|
+
* @template MessageIds
|
|
306
|
+
* @template Options
|
|
307
|
+
*/
|
|
308
|
+
function collectVariables(context) {
|
|
309
|
+
return UnusedVariablesVisitor.collectUnusedVariables(context.sourceCode.ast, ESLintUtils.nullThrows(context.sourceCode.scopeManager, "Missing required scope manager"));
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Determines if the variable is used.
|
|
313
|
+
* @param variable - The variable to check.
|
|
314
|
+
* @returns True if the variable is used.
|
|
315
|
+
*/
|
|
316
|
+
function isUsedVariable(variable) {
|
|
317
|
+
/**
|
|
318
|
+
* Gets a list of function definitions for a specified variable.
|
|
319
|
+
* @param scopeVariable - Eslint-scope variable object.
|
|
320
|
+
* @returns Function nodes.
|
|
321
|
+
*/
|
|
322
|
+
function getFunctionDefinitions(scopeVariable) {
|
|
323
|
+
const functionDefinitions = /* @__PURE__ */ new Set();
|
|
324
|
+
for (const definition of scopeVariable.defs) {
|
|
325
|
+
if (definition.type === TSESLint.Scope.DefinitionType.FunctionName) functionDefinitions.add(definition.node);
|
|
326
|
+
if (definition.type === TSESLint.Scope.DefinitionType.Variable && (definition.node.init?.type === AST_NODE_TYPES.FunctionExpression || definition.node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression)) functionDefinitions.add(definition.node.init);
|
|
327
|
+
}
|
|
328
|
+
return functionDefinitions;
|
|
329
|
+
}
|
|
330
|
+
function getTypeDeclarations(scopeVariable) {
|
|
331
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
332
|
+
for (const definition of scopeVariable.defs) if (definition.node.type === AST_NODE_TYPES.TSInterfaceDeclaration || definition.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration) nodes.add(definition.node);
|
|
333
|
+
return nodes;
|
|
334
|
+
}
|
|
335
|
+
function getModuleDeclarations(scopeVariable) {
|
|
336
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
337
|
+
for (const definition of scopeVariable.defs) if (definition.node.type === AST_NODE_TYPES.TSModuleDeclaration) nodes.add(definition.node);
|
|
338
|
+
return nodes;
|
|
339
|
+
}
|
|
340
|
+
function getEnumDeclarations(scopeVariable) {
|
|
341
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
342
|
+
for (const definition of scopeVariable.defs) if (definition.node.type === AST_NODE_TYPES.TSEnumDeclaration) nodes.add(definition.node);
|
|
343
|
+
return nodes;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Checks if the ref is contained within one of the given nodes.
|
|
347
|
+
* @param ref - A reference to check.
|
|
348
|
+
* @param nodes - A set of nodes.
|
|
349
|
+
* @returns `true` if the ref is inside one of the nodes.
|
|
350
|
+
*/
|
|
351
|
+
function isInsideOneOf(ref, nodes) {
|
|
352
|
+
for (const node of nodes) if (isInside(ref.identifier, node)) return true;
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Checks whether a given node is unused expression or not.
|
|
357
|
+
* @param node - The node itself.
|
|
358
|
+
* @returns The node is an unused expression.
|
|
359
|
+
*/
|
|
360
|
+
function isUnusedExpression(node) {
|
|
361
|
+
const { parent } = node;
|
|
362
|
+
if (parent.type === AST_NODE_TYPES.ExpressionStatement) return true;
|
|
363
|
+
if (parent.type === AST_NODE_TYPES.SequenceExpression) {
|
|
364
|
+
if (!(parent.expressions[parent.expressions.length - 1] === node)) return true;
|
|
365
|
+
return isUnusedExpression(parent);
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* If a given reference is left-hand side of an assignment, this gets
|
|
371
|
+
* the right-hand side node of the assignment.
|
|
372
|
+
*
|
|
373
|
+
* In the following cases, this returns undefined.
|
|
374
|
+
*
|
|
375
|
+
* - The reference is not the LHS of an assignment expression.
|
|
376
|
+
* - The reference is inside of a loop.
|
|
377
|
+
* - The reference is inside of a function scope which is different from
|
|
378
|
+
* the declaration.
|
|
379
|
+
* @param ref - A reference to check.
|
|
380
|
+
* @param previousRhsNode - The previous RHS node. This is for `a = a + a`-like code.
|
|
381
|
+
* @returns The RHS node or undefined.
|
|
382
|
+
*/
|
|
383
|
+
function getRhsNode(ref, previousRhsNode) {
|
|
384
|
+
/**
|
|
385
|
+
* Checks whether the given node is in a loop or not.
|
|
386
|
+
* @param node - The node to check.
|
|
387
|
+
* @returns `true` if the node is in a loop.
|
|
388
|
+
*/
|
|
389
|
+
function isInLoop(node) {
|
|
390
|
+
let currentNode = node;
|
|
391
|
+
while (currentNode) {
|
|
392
|
+
if (ASTUtils.isFunction(currentNode)) break;
|
|
393
|
+
if (ASTUtils.isLoop(currentNode)) return true;
|
|
394
|
+
currentNode = currentNode.parent;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
const id = ref.identifier;
|
|
399
|
+
const { parent } = id;
|
|
400
|
+
const refScope = ref.from.variableScope;
|
|
401
|
+
const { variableScope } = ref.resolved.scope;
|
|
402
|
+
const canBeUsedLater = refScope !== variableScope || isInLoop(id);
|
|
403
|
+
if (previousRhsNode && isInside(id, previousRhsNode)) return previousRhsNode;
|
|
404
|
+
if (parent.type === AST_NODE_TYPES.AssignmentExpression && isUnusedExpression(parent) && id === parent.left && !canBeUsedLater) return parent.right;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Checks whether a given reference is a read to update itself or not.
|
|
408
|
+
* @param ref - A reference to check.
|
|
409
|
+
* @param rhsNode - The RHS node of the previous assignment.
|
|
410
|
+
* @returns The reference is a read to update itself.
|
|
411
|
+
*/
|
|
412
|
+
function isReadForItself(ref, rhsNode$1) {
|
|
413
|
+
/**
|
|
414
|
+
* Checks whether a given Identifier node exists inside of a function node which can be used later.
|
|
415
|
+
*
|
|
416
|
+
* "can be used later" means:
|
|
417
|
+
* - the function is assigned to a variable.
|
|
418
|
+
* - the function is bound to a property and the object can be used later.
|
|
419
|
+
* - the function is bound as an argument of a function call.
|
|
420
|
+
*
|
|
421
|
+
* If a reference exists in a function which can be used later, the reference is read when the function is called.
|
|
422
|
+
* @param id - An Identifier node to check.
|
|
423
|
+
* @param rightHandSideNode - The RHS node of the previous assignment.
|
|
424
|
+
* @returns `true` if the `id` node exists inside of a function node which can be used later.
|
|
425
|
+
*/
|
|
426
|
+
function isInsideOfStorableFunction(id$1, rightHandSideNode) {
|
|
427
|
+
/**
|
|
428
|
+
* Finds a function node from ancestors of a node.
|
|
429
|
+
* @param node - A start node to find.
|
|
430
|
+
* @returns A found function node.
|
|
431
|
+
*/
|
|
432
|
+
function getUpperFunction(node) {
|
|
433
|
+
let currentNode = node;
|
|
434
|
+
while (currentNode) {
|
|
435
|
+
if (ASTUtils.isFunction(currentNode)) return currentNode;
|
|
436
|
+
currentNode = currentNode.parent;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Checks whether a given function node is stored to somewhere or not.
|
|
441
|
+
* If the function node is stored, the function can be used later.
|
|
442
|
+
* @param funcNode - A function node to check.
|
|
443
|
+
* @param storableRhsNode - The RHS node of the previous assignment.
|
|
444
|
+
* @returns `true` if under the following conditions:
|
|
445
|
+
* - the funcNode is assigned to a variable.
|
|
446
|
+
* - the funcNode is bound as an argument of a function call.
|
|
447
|
+
* - the function is bound to a property and the object satisfies above conditions.
|
|
448
|
+
*/
|
|
449
|
+
function isStorableFunction(funcNode$1, storableRhsNode) {
|
|
450
|
+
let node = funcNode$1;
|
|
451
|
+
let { parent: parent$1 } = funcNode$1;
|
|
452
|
+
while (parent$1 && isInside(parent$1, storableRhsNode)) {
|
|
453
|
+
switch (parent$1.type) {
|
|
454
|
+
case AST_NODE_TYPES.AssignmentExpression:
|
|
455
|
+
case AST_NODE_TYPES.TaggedTemplateExpression:
|
|
456
|
+
case AST_NODE_TYPES.YieldExpression: return true;
|
|
457
|
+
case AST_NODE_TYPES.CallExpression:
|
|
458
|
+
case AST_NODE_TYPES.NewExpression: return parent$1.callee !== node;
|
|
459
|
+
case AST_NODE_TYPES.SequenceExpression:
|
|
460
|
+
if (parent$1.expressions[parent$1.expressions.length - 1] !== node) return false;
|
|
461
|
+
break;
|
|
462
|
+
default: if (parent$1.type.endsWith("Statement") || parent$1.type.endsWith("Declaration")) return true;
|
|
463
|
+
}
|
|
464
|
+
node = parent$1;
|
|
465
|
+
({parent: parent$1} = parent$1);
|
|
466
|
+
}
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
const funcNode = getUpperFunction(id$1);
|
|
470
|
+
return !!funcNode && isInside(funcNode, rightHandSideNode) && isStorableFunction(funcNode, rightHandSideNode);
|
|
471
|
+
}
|
|
472
|
+
const id = ref.identifier;
|
|
473
|
+
const { parent } = id;
|
|
474
|
+
return ref.isRead() && (parent.type === AST_NODE_TYPES.AssignmentExpression && !LOGICAL_ASSIGNMENT_OPERATORS.has(parent.operator) && isUnusedExpression(parent) && parent.left === id || parent.type === AST_NODE_TYPES.UpdateExpression && isUnusedExpression(parent) || !!rhsNode$1 && isInside(id, rhsNode$1) && !isInsideOfStorableFunction(id, rhsNode$1));
|
|
475
|
+
}
|
|
476
|
+
const functionNodes = getFunctionDefinitions(variable);
|
|
477
|
+
const isFunctionDefinition = functionNodes.size > 0;
|
|
478
|
+
const typeDeclNodes = getTypeDeclarations(variable);
|
|
479
|
+
const isTypeDecl = typeDeclNodes.size > 0;
|
|
480
|
+
const moduleDeclNodes = getModuleDeclarations(variable);
|
|
481
|
+
const isModuleDecl = moduleDeclNodes.size > 0;
|
|
482
|
+
const enumDeclNodes = getEnumDeclarations(variable);
|
|
483
|
+
const isEnumDecl = enumDeclNodes.size > 0;
|
|
484
|
+
const isImportedAsType = variable.defs.every(isTypeImport);
|
|
485
|
+
let rhsNode;
|
|
486
|
+
return variable.references.some((ref) => {
|
|
487
|
+
const forItself = isReadForItself(ref, rhsNode);
|
|
488
|
+
rhsNode = getRhsNode(ref, rhsNode);
|
|
489
|
+
return ref.isRead() && !forItself && (isImportedAsType || !referenceContainsTypeQuery(ref.identifier)) && (!isFunctionDefinition || !isSelfReference(ref, functionNodes)) && (!isTypeDecl || !isInsideOneOf(ref, typeDeclNodes)) && (!isModuleDecl || !isSelfReference(ref, moduleDeclNodes)) && (!isEnumDecl || !isSelfReference(ref, enumDeclNodes));
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/rules/naming-convention/utils/enums.ts
|
|
495
|
+
const Selector = {
|
|
496
|
+
variable: 1,
|
|
497
|
+
function: 2,
|
|
498
|
+
parameter: 4,
|
|
499
|
+
objectStyleEnum: 8,
|
|
500
|
+
parameterProperty: 16,
|
|
501
|
+
classicAccessor: 32,
|
|
502
|
+
enumMember: 64,
|
|
503
|
+
classMethod: 128,
|
|
504
|
+
objectLiteralMethod: 256,
|
|
505
|
+
typeMethod: 512,
|
|
506
|
+
classProperty: 1024,
|
|
507
|
+
objectLiteralProperty: 2048,
|
|
508
|
+
typeProperty: 4096,
|
|
509
|
+
autoAccessor: 8192,
|
|
510
|
+
class: 16384,
|
|
511
|
+
interface: 32768,
|
|
512
|
+
typeAlias: 65536,
|
|
513
|
+
enum: 131072,
|
|
514
|
+
typeParameter: 262144,
|
|
515
|
+
import: 524288
|
|
516
|
+
};
|
|
517
|
+
const MetaSelector = {
|
|
518
|
+
default: -1,
|
|
519
|
+
variableLike: 15,
|
|
520
|
+
memberLike: 16368,
|
|
521
|
+
typeLike: 507904,
|
|
522
|
+
method: 896,
|
|
523
|
+
property: 7168,
|
|
524
|
+
accessor: 8224
|
|
525
|
+
};
|
|
526
|
+
const Modifier = {
|
|
527
|
+
"const": 1,
|
|
528
|
+
"readonly": 2,
|
|
529
|
+
"static": 4,
|
|
530
|
+
"public": 8,
|
|
531
|
+
"protected": 16,
|
|
532
|
+
"private": 32,
|
|
533
|
+
"#private": 64,
|
|
534
|
+
"abstract": 128,
|
|
535
|
+
"destructured": 256,
|
|
536
|
+
"global": 512,
|
|
537
|
+
"exported": 1024,
|
|
538
|
+
"unused": 2048,
|
|
539
|
+
"requiresQuotes": 4096,
|
|
540
|
+
"override": 8192,
|
|
541
|
+
"async": 16384,
|
|
542
|
+
"default": 32768,
|
|
543
|
+
"namespace": 65536
|
|
544
|
+
};
|
|
545
|
+
const PredefinedFormat = {
|
|
546
|
+
camelCase: 1,
|
|
547
|
+
strictCamelCase: 2,
|
|
548
|
+
PascalCase: 3,
|
|
549
|
+
StrictPascalCase: 4,
|
|
550
|
+
snake_case: 5,
|
|
551
|
+
UPPER_CASE: 6
|
|
552
|
+
};
|
|
553
|
+
const PredefinedFormatValueToKey = Object.fromEntries(Object.entries(PredefinedFormat).map(([key, value]) => [value, key]));
|
|
554
|
+
const TypeModifier = {
|
|
555
|
+
boolean: 131072,
|
|
556
|
+
string: 262144,
|
|
557
|
+
number: 524288,
|
|
558
|
+
function: 1048576,
|
|
559
|
+
array: 2097152
|
|
560
|
+
};
|
|
561
|
+
const TypeModifierValueToKey = Object.fromEntries(Object.entries(TypeModifier).map(([key, value]) => [value, key]));
|
|
562
|
+
const UnderscoreOption = {
|
|
563
|
+
forbid: 1,
|
|
564
|
+
allow: 2,
|
|
565
|
+
require: 3,
|
|
566
|
+
requireDouble: 4,
|
|
567
|
+
allowDouble: 5,
|
|
568
|
+
allowSingleOrDouble: 6
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/rules/naming-convention/utils/shared.ts
|
|
573
|
+
function isMetaSelector(selector) {
|
|
574
|
+
return selector in MetaSelector;
|
|
575
|
+
}
|
|
576
|
+
function isMethodOrPropertySelector(selector) {
|
|
577
|
+
return selector === MetaSelector.method || selector === MetaSelector.property;
|
|
578
|
+
}
|
|
579
|
+
function selectorTypeToMessageString(selectorType) {
|
|
580
|
+
const notCamelCase = selectorType.replaceAll(/([A-Z])/g, " $1");
|
|
581
|
+
return notCamelCase.charAt(0).toUpperCase() + notCamelCase.slice(1);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/rules/naming-convention/utils/format.ts
|
|
586
|
+
function hasStrictCamelHumps(name$1, isUpper) {
|
|
587
|
+
if (name$1.startsWith("_")) return false;
|
|
588
|
+
for (let index = 1; index < name$1.length; ++index) {
|
|
589
|
+
const char = name$1[index];
|
|
590
|
+
if (char === "_") return false;
|
|
591
|
+
if (isUpper === isUppercaseChar(char)) {
|
|
592
|
+
if (isUpper) return false;
|
|
593
|
+
} else isUpper = !isUpper;
|
|
594
|
+
}
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
function isCamelCase(name$1) {
|
|
598
|
+
return name$1.length === 0 || name$1[0] === name$1[0]?.toLowerCase() && !name$1.includes("_");
|
|
599
|
+
}
|
|
600
|
+
function isPascalCase(name$1) {
|
|
601
|
+
return name$1.length === 0 || name$1[0] === name$1[0]?.toUpperCase() && !name$1.includes("_");
|
|
602
|
+
}
|
|
603
|
+
function isSnakeCase(name$1) {
|
|
604
|
+
return name$1.length === 0 || name$1 === name$1.toLowerCase() && validateUnderscores(name$1);
|
|
605
|
+
}
|
|
606
|
+
function isStrictCamelCase(name$1) {
|
|
607
|
+
return name$1.length === 0 || name$1[0] === name$1[0]?.toLowerCase() && hasStrictCamelHumps(name$1, false);
|
|
608
|
+
}
|
|
609
|
+
function isStrictPascalCase(name$1) {
|
|
610
|
+
return name$1.length === 0 || name$1[0] === name$1[0]?.toUpperCase() && hasStrictCamelHumps(name$1, true);
|
|
611
|
+
}
|
|
612
|
+
function isUpperCase(name$1) {
|
|
613
|
+
return name$1.length === 0 || name$1 === name$1.toUpperCase() && validateUnderscores(name$1);
|
|
614
|
+
}
|
|
615
|
+
function isUppercaseChar(char) {
|
|
616
|
+
return char === char.toUpperCase() && char !== char.toLowerCase();
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Check for leading trailing and adjacent underscores.
|
|
620
|
+
* @param name - The name to check.
|
|
621
|
+
* @returns True if the underscores are valid.
|
|
622
|
+
*/
|
|
623
|
+
function validateUnderscores(name$1) {
|
|
624
|
+
if (name$1.startsWith("_")) return false;
|
|
625
|
+
let wasUnderscore = false;
|
|
626
|
+
for (let index = 1; index < name$1.length; ++index) if (name$1[index] === "_") {
|
|
627
|
+
if (wasUnderscore) return false;
|
|
628
|
+
wasUnderscore = true;
|
|
629
|
+
} else wasUnderscore = false;
|
|
630
|
+
return !wasUnderscore;
|
|
631
|
+
}
|
|
632
|
+
const FormatCheckersMap = {
|
|
633
|
+
[PredefinedFormat.camelCase]: isCamelCase,
|
|
634
|
+
[PredefinedFormat.PascalCase]: isPascalCase,
|
|
635
|
+
[PredefinedFormat.snake_case]: isSnakeCase,
|
|
636
|
+
[PredefinedFormat.strictCamelCase]: isStrictCamelCase,
|
|
637
|
+
[PredefinedFormat.StrictPascalCase]: isStrictPascalCase,
|
|
638
|
+
[PredefinedFormat.UPPER_CASE]: isUpperCase
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region src/rules/naming-convention/utils/validator.ts
|
|
643
|
+
function createValidator(type, context, allConfigs) {
|
|
644
|
+
const selectorType = Selector[type];
|
|
645
|
+
const configs$1 = allConfigs.filter((configItem) => {
|
|
646
|
+
return (configItem.selector & selectorType) !== 0 || configItem.selector === MetaSelector.default;
|
|
647
|
+
}).sort((a, b) => {
|
|
648
|
+
if (a.selector === b.selector) return b.modifierWeight - a.modifierWeight;
|
|
649
|
+
const aIsMeta = isMetaSelector(a.selector);
|
|
650
|
+
const bIsMeta = isMetaSelector(b.selector);
|
|
651
|
+
if (aIsMeta && !bIsMeta) return 1;
|
|
652
|
+
if (!aIsMeta && bIsMeta) return -1;
|
|
653
|
+
const aIsMethodOrProperty = isMethodOrPropertySelector(a.selector);
|
|
654
|
+
const bIsMethodOrProperty = isMethodOrPropertySelector(b.selector);
|
|
655
|
+
if (aIsMethodOrProperty && !bIsMethodOrProperty) return -1;
|
|
656
|
+
if (!aIsMethodOrProperty && bIsMethodOrProperty) return 1;
|
|
657
|
+
return b.selector - a.selector;
|
|
658
|
+
});
|
|
659
|
+
return (node, modifiers = /* @__PURE__ */ new Set()) => {
|
|
660
|
+
const originalName = node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`;
|
|
661
|
+
for (const config of configs$1) {
|
|
662
|
+
if (config.filter?.regex.test(originalName) !== config.filter?.match) continue;
|
|
663
|
+
if (config.modifiers?.some((modifier) => !modifiers.has(modifier)) === true) continue;
|
|
664
|
+
if (!isCorrectType(node, config, context, selectorType)) continue;
|
|
665
|
+
let name$1 = originalName;
|
|
666
|
+
name$1 = validateUnderscore({
|
|
667
|
+
config,
|
|
668
|
+
name: name$1,
|
|
669
|
+
node,
|
|
670
|
+
originalName,
|
|
671
|
+
position: "leading"
|
|
672
|
+
});
|
|
673
|
+
if (name$1 === void 0) return;
|
|
674
|
+
name$1 = validateUnderscore({
|
|
675
|
+
config,
|
|
676
|
+
name: name$1,
|
|
677
|
+
node,
|
|
678
|
+
originalName,
|
|
679
|
+
position: "trailing"
|
|
680
|
+
});
|
|
681
|
+
if (name$1 === void 0) return;
|
|
682
|
+
name$1 = validateAffix({
|
|
683
|
+
config,
|
|
684
|
+
name: name$1,
|
|
685
|
+
node,
|
|
686
|
+
originalName,
|
|
687
|
+
position: "prefix"
|
|
688
|
+
});
|
|
689
|
+
if (name$1 === void 0) return;
|
|
690
|
+
name$1 = validateAffix({
|
|
691
|
+
config,
|
|
692
|
+
name: name$1,
|
|
693
|
+
node,
|
|
694
|
+
originalName,
|
|
695
|
+
position: "suffix"
|
|
696
|
+
});
|
|
697
|
+
if (name$1 === void 0) return;
|
|
698
|
+
if (!validateCustom({
|
|
699
|
+
config,
|
|
700
|
+
name: name$1,
|
|
701
|
+
node,
|
|
702
|
+
originalName
|
|
703
|
+
})) return;
|
|
704
|
+
if (!validatePredefinedFormat({
|
|
705
|
+
config,
|
|
706
|
+
modifiers,
|
|
707
|
+
name: name$1,
|
|
708
|
+
node,
|
|
709
|
+
originalName
|
|
710
|
+
})) return;
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
function formatReportData({ affixes, count, custom, formats, originalName, position, processedName }) {
|
|
715
|
+
let regexMatch = null;
|
|
716
|
+
if (custom?.match === true) regexMatch = "match";
|
|
717
|
+
else if (custom?.match === false) regexMatch = "not match";
|
|
718
|
+
return {
|
|
719
|
+
affixes: affixes?.join(", "),
|
|
720
|
+
count,
|
|
721
|
+
formats: formats?.map((formatItem) => PredefinedFormatValueToKey[formatItem]).join(", "),
|
|
722
|
+
name: originalName,
|
|
723
|
+
position,
|
|
724
|
+
processedName,
|
|
725
|
+
regex: custom?.regex.toString(),
|
|
726
|
+
regexMatch,
|
|
727
|
+
type: selectorTypeToMessageString(type)
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function validateUnderscore({ config, name: name$1, node, originalName, position }) {
|
|
731
|
+
const option = position === "leading" ? config.leadingUnderscore : config.trailingUnderscore;
|
|
732
|
+
if (!option) return name$1;
|
|
733
|
+
const hasSingleUnderscore = position === "leading" ? () => name$1.startsWith("_") : () => name$1.endsWith("_");
|
|
734
|
+
const trimSingleUnderscore = position === "leading" ? () => name$1.slice(1) : () => name$1.slice(0, -1);
|
|
735
|
+
const hasDoubleUnderscore = position === "leading" ? () => name$1.startsWith("__") : () => name$1.endsWith("__");
|
|
736
|
+
const trimDoubleUnderscore = position === "leading" ? () => name$1.slice(2) : () => name$1.slice(0, -2);
|
|
737
|
+
switch (option) {
|
|
738
|
+
case UnderscoreOption.allow:
|
|
739
|
+
if (hasSingleUnderscore()) return trimSingleUnderscore();
|
|
740
|
+
return name$1;
|
|
741
|
+
case UnderscoreOption.allowDouble:
|
|
742
|
+
if (hasDoubleUnderscore()) return trimDoubleUnderscore();
|
|
743
|
+
return name$1;
|
|
744
|
+
case UnderscoreOption.allowSingleOrDouble:
|
|
745
|
+
if (hasDoubleUnderscore()) return trimDoubleUnderscore();
|
|
746
|
+
if (hasSingleUnderscore()) return trimSingleUnderscore();
|
|
747
|
+
return name$1;
|
|
748
|
+
case UnderscoreOption.forbid:
|
|
749
|
+
if (hasSingleUnderscore()) {
|
|
750
|
+
context.report({
|
|
751
|
+
data: formatReportData({
|
|
752
|
+
count: "one",
|
|
753
|
+
originalName,
|
|
754
|
+
position
|
|
755
|
+
}),
|
|
756
|
+
messageId: "unexpectedUnderscore",
|
|
757
|
+
node
|
|
758
|
+
});
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
return name$1;
|
|
762
|
+
case UnderscoreOption.require:
|
|
763
|
+
if (!hasSingleUnderscore()) {
|
|
764
|
+
context.report({
|
|
765
|
+
data: formatReportData({
|
|
766
|
+
count: "one",
|
|
767
|
+
originalName,
|
|
768
|
+
position
|
|
769
|
+
}),
|
|
770
|
+
messageId: "missingUnderscore",
|
|
771
|
+
node
|
|
772
|
+
});
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
return trimSingleUnderscore();
|
|
776
|
+
case UnderscoreOption.requireDouble:
|
|
777
|
+
if (!hasDoubleUnderscore()) {
|
|
778
|
+
context.report({
|
|
779
|
+
data: formatReportData({
|
|
780
|
+
count: "two",
|
|
781
|
+
originalName,
|
|
782
|
+
position
|
|
783
|
+
}),
|
|
784
|
+
messageId: "missingUnderscore",
|
|
785
|
+
node
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
return trimDoubleUnderscore();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function validateAffix({ config, name: name$1, node, originalName, position }) {
|
|
793
|
+
const affixes = config[position];
|
|
794
|
+
if (!affixes || affixes.length === 0) return name$1;
|
|
795
|
+
for (const affix of affixes) {
|
|
796
|
+
const hasAffix = position === "prefix" ? name$1.startsWith(affix) : name$1.endsWith(affix);
|
|
797
|
+
const trimAffix = position === "prefix" ? () => name$1.slice(affix.length) : () => name$1.slice(0, -affix.length);
|
|
798
|
+
if (hasAffix) return trimAffix();
|
|
799
|
+
}
|
|
800
|
+
context.report({
|
|
801
|
+
data: formatReportData({
|
|
802
|
+
affixes,
|
|
803
|
+
originalName,
|
|
804
|
+
position
|
|
805
|
+
}),
|
|
806
|
+
messageId: "missingAffix",
|
|
807
|
+
node
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
function validateCustom({ config, name: name$1, node, originalName }) {
|
|
811
|
+
const { custom } = config;
|
|
812
|
+
if (!custom) return true;
|
|
813
|
+
const result = custom.regex.test(name$1);
|
|
814
|
+
if (custom.match && result) return true;
|
|
815
|
+
if (!custom.match && !result) return true;
|
|
816
|
+
context.report({
|
|
817
|
+
data: formatReportData({
|
|
818
|
+
custom,
|
|
819
|
+
originalName
|
|
820
|
+
}),
|
|
821
|
+
messageId: "satisfyCustom",
|
|
822
|
+
node
|
|
823
|
+
});
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
function validatePredefinedFormat({ config, modifiers, name: name$1, node, originalName }) {
|
|
827
|
+
const formats = config.format;
|
|
828
|
+
if (!formats || formats.length === 0) return true;
|
|
829
|
+
if (!modifiers.has(Modifier.requiresQuotes)) for (const format of formats) {
|
|
830
|
+
const checker = FormatCheckersMap[format];
|
|
831
|
+
if (checker(name$1)) return true;
|
|
832
|
+
}
|
|
833
|
+
context.report({
|
|
834
|
+
data: formatReportData({
|
|
835
|
+
formats,
|
|
836
|
+
originalName,
|
|
837
|
+
processedName: name$1
|
|
838
|
+
}),
|
|
839
|
+
messageId: originalName === name$1 ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed",
|
|
840
|
+
node
|
|
841
|
+
});
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const SelectorsAllowedToHaveTypes = Selector.variable | Selector.parameter | Selector.classProperty | Selector.objectLiteralProperty | Selector.typeProperty | Selector.parameterProperty | Selector.classicAccessor;
|
|
846
|
+
function isAllTypesMatch(type, callback) {
|
|
847
|
+
if (type.isUnion()) return type.types.every((inner) => callback(inner));
|
|
848
|
+
return callback(type);
|
|
849
|
+
}
|
|
850
|
+
function isCorrectType(node, config, context, selector) {
|
|
851
|
+
if (config.types === void 0) return true;
|
|
852
|
+
if ((SelectorsAllowedToHaveTypes & selector) === 0) return true;
|
|
853
|
+
const services = getParserServices(context);
|
|
854
|
+
const checker = services.program.getTypeChecker();
|
|
855
|
+
const type = services.getTypeAtLocation(node).getNonNullableType();
|
|
856
|
+
for (const allowedType of config.types) switch (allowedType) {
|
|
857
|
+
case TypeModifier.array:
|
|
858
|
+
if (isAllTypesMatch(type, (inner) => checker.isArrayType(inner) || checker.isTupleType(inner))) return true;
|
|
859
|
+
break;
|
|
860
|
+
case TypeModifier.boolean:
|
|
861
|
+
case TypeModifier.number:
|
|
862
|
+
case TypeModifier.string: {
|
|
863
|
+
const typeString = checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type)));
|
|
864
|
+
const allowedTypeString = TypeModifierValueToKey[allowedType];
|
|
865
|
+
if (typeString === allowedTypeString) return true;
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
case TypeModifier.function:
|
|
869
|
+
if (isAllTypesMatch(type, (inner) => inner.getCallSignatures().length > 0)) return true;
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
//#endregion
|
|
876
|
+
//#region src/rules/naming-convention/utils/parse-options.ts
|
|
877
|
+
function parseOptions(context) {
|
|
878
|
+
const normalizedOptions = context.options.flatMap(normalizeOption);
|
|
879
|
+
return Object.fromEntries(Object.keys(Selector).map((key) => [key, createValidator(key, context, normalizedOptions)]));
|
|
880
|
+
}
|
|
881
|
+
function normalizeOption(option) {
|
|
882
|
+
let weight = 0;
|
|
883
|
+
if (option.modifiers) for (const modifier of option.modifiers) weight |= Modifier[modifier];
|
|
884
|
+
if (option.types) for (const type of option.types) weight |= TypeModifier[type];
|
|
885
|
+
if (option.filter !== void 0) weight |= 1 << 30;
|
|
886
|
+
const normalizedOption = {
|
|
887
|
+
custom: option.custom ? {
|
|
888
|
+
match: option.custom.match,
|
|
889
|
+
regex: new RegExp(option.custom.regex, "u")
|
|
890
|
+
} : void 0,
|
|
891
|
+
filter: option.filter !== void 0 ? typeof option.filter === "string" ? {
|
|
892
|
+
match: true,
|
|
893
|
+
regex: new RegExp(option.filter, "u")
|
|
894
|
+
} : {
|
|
895
|
+
match: option.filter.match,
|
|
896
|
+
regex: new RegExp(option.filter.regex, "u")
|
|
897
|
+
} : void 0,
|
|
898
|
+
format: option.format ? option.format.map((format) => PredefinedFormat[format]) : void 0,
|
|
899
|
+
leadingUnderscore: option.leadingUnderscore !== void 0 ? UnderscoreOption[option.leadingUnderscore] : void 0,
|
|
900
|
+
modifiers: option.modifiers?.map((modifier) => Modifier[modifier]) ?? void 0,
|
|
901
|
+
modifierWeight: weight,
|
|
902
|
+
prefix: option.prefix && option.prefix.length > 0 ? option.prefix : void 0,
|
|
903
|
+
suffix: option.suffix && option.suffix.length > 0 ? option.suffix : void 0,
|
|
904
|
+
trailingUnderscore: option.trailingUnderscore !== void 0 ? UnderscoreOption[option.trailingUnderscore] : void 0,
|
|
905
|
+
types: option.types?.map((type) => TypeModifier[type]) ?? void 0
|
|
906
|
+
};
|
|
907
|
+
return (Array.isArray(option.selector) ? option.selector : [option.selector]).map((selector) => {
|
|
908
|
+
return {
|
|
909
|
+
selector: isMetaSelector(selector) ? MetaSelector[selector] : Selector[selector],
|
|
910
|
+
...normalizedOption
|
|
911
|
+
};
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/rules/naming-convention/utils/schema.ts
|
|
917
|
+
const $DEFS = {
|
|
918
|
+
formatOptionsConfig: { oneOf: [{
|
|
919
|
+
additionalItems: false,
|
|
920
|
+
items: { $ref: "#/$defs/predefinedFormats" },
|
|
921
|
+
type: "array"
|
|
922
|
+
}, { type: "null" }] },
|
|
923
|
+
matchRegexConfig: {
|
|
924
|
+
additionalProperties: false,
|
|
925
|
+
properties: {
|
|
926
|
+
match: { type: "boolean" },
|
|
927
|
+
regex: { type: "string" }
|
|
928
|
+
},
|
|
929
|
+
required: ["match", "regex"],
|
|
930
|
+
type: "object"
|
|
931
|
+
},
|
|
932
|
+
predefinedFormats: {
|
|
933
|
+
enum: Object.keys(PredefinedFormat),
|
|
934
|
+
type: "string"
|
|
935
|
+
},
|
|
936
|
+
prefixSuffixConfig: {
|
|
937
|
+
additionalItems: false,
|
|
938
|
+
items: {
|
|
939
|
+
minLength: 1,
|
|
940
|
+
type: "string"
|
|
941
|
+
},
|
|
942
|
+
type: "array"
|
|
943
|
+
},
|
|
944
|
+
typeModifiers: {
|
|
945
|
+
enum: Object.keys(TypeModifier),
|
|
946
|
+
type: "string"
|
|
947
|
+
},
|
|
948
|
+
underscoreOptions: {
|
|
949
|
+
enum: Object.keys(UnderscoreOption),
|
|
950
|
+
type: "string"
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
const UNDERSCORE_SCHEMA = { $ref: "#/$defs/underscoreOptions" };
|
|
954
|
+
const PREFIX_SUFFIX_SCHEMA = { $ref: "#/$defs/prefixSuffixConfig" };
|
|
955
|
+
const MATCH_REGEX_SCHEMA = { $ref: "#/$defs/matchRegexConfig" };
|
|
956
|
+
const FORMAT_OPTIONS_PROPERTIES = {
|
|
957
|
+
custom: MATCH_REGEX_SCHEMA,
|
|
958
|
+
failureMessage: { type: "string" },
|
|
959
|
+
format: { $ref: "#/$defs/formatOptionsConfig" },
|
|
960
|
+
leadingUnderscore: UNDERSCORE_SCHEMA,
|
|
961
|
+
prefix: PREFIX_SUFFIX_SCHEMA,
|
|
962
|
+
suffix: PREFIX_SUFFIX_SCHEMA,
|
|
963
|
+
trailingUnderscore: UNDERSCORE_SCHEMA
|
|
964
|
+
};
|
|
965
|
+
function selectorSchema(selectorString, allowType, modifiers) {
|
|
966
|
+
const selector = {
|
|
967
|
+
filter: { oneOf: [{
|
|
968
|
+
minLength: 1,
|
|
969
|
+
type: "string"
|
|
970
|
+
}, MATCH_REGEX_SCHEMA] },
|
|
971
|
+
selector: {
|
|
972
|
+
enum: [selectorString],
|
|
973
|
+
type: "string"
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
if (modifiers && modifiers.length > 0) selector["modifiers"] = {
|
|
977
|
+
additionalItems: false,
|
|
978
|
+
items: {
|
|
979
|
+
enum: modifiers,
|
|
980
|
+
type: "string"
|
|
981
|
+
},
|
|
982
|
+
type: "array"
|
|
983
|
+
};
|
|
984
|
+
if (allowType) selector["types"] = {
|
|
985
|
+
additionalItems: false,
|
|
986
|
+
items: { $ref: "#/$defs/typeModifiers" },
|
|
987
|
+
type: "array"
|
|
988
|
+
};
|
|
989
|
+
return [{
|
|
990
|
+
additionalProperties: false,
|
|
991
|
+
description: `Selector '${selectorString}'`,
|
|
992
|
+
properties: {
|
|
993
|
+
...FORMAT_OPTIONS_PROPERTIES,
|
|
994
|
+
...selector
|
|
995
|
+
},
|
|
996
|
+
required: ["selector", "format"],
|
|
997
|
+
type: "object"
|
|
998
|
+
}];
|
|
999
|
+
}
|
|
1000
|
+
function selectorsSchema() {
|
|
1001
|
+
return {
|
|
1002
|
+
additionalProperties: false,
|
|
1003
|
+
description: "Multiple selectors in one config",
|
|
1004
|
+
properties: {
|
|
1005
|
+
...FORMAT_OPTIONS_PROPERTIES,
|
|
1006
|
+
filter: { oneOf: [{
|
|
1007
|
+
minLength: 1,
|
|
1008
|
+
type: "string"
|
|
1009
|
+
}, MATCH_REGEX_SCHEMA] },
|
|
1010
|
+
modifiers: {
|
|
1011
|
+
additionalItems: false,
|
|
1012
|
+
items: {
|
|
1013
|
+
enum: Object.keys(Modifier),
|
|
1014
|
+
type: "string"
|
|
1015
|
+
},
|
|
1016
|
+
type: "array"
|
|
1017
|
+
},
|
|
1018
|
+
selector: {
|
|
1019
|
+
additionalItems: false,
|
|
1020
|
+
items: {
|
|
1021
|
+
enum: [...Object.keys(MetaSelector), ...Object.keys(Selector)],
|
|
1022
|
+
type: "string"
|
|
1023
|
+
},
|
|
1024
|
+
type: "array"
|
|
1025
|
+
},
|
|
1026
|
+
types: {
|
|
1027
|
+
additionalItems: false,
|
|
1028
|
+
items: { $ref: "#/$defs/typeModifiers" },
|
|
1029
|
+
type: "array"
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
required: ["selector", "format"],
|
|
1033
|
+
type: "object"
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
const SCHEMA = {
|
|
1037
|
+
$defs: $DEFS,
|
|
1038
|
+
additionalItems: false,
|
|
1039
|
+
items: { oneOf: [
|
|
1040
|
+
selectorsSchema(),
|
|
1041
|
+
...selectorSchema("default", false, Object.keys(Modifier)),
|
|
1042
|
+
...selectorSchema("variableLike", false, ["unused", "async"]),
|
|
1043
|
+
...selectorSchema("variable", true, [
|
|
1044
|
+
"const",
|
|
1045
|
+
"destructured",
|
|
1046
|
+
"exported",
|
|
1047
|
+
"global",
|
|
1048
|
+
"unused",
|
|
1049
|
+
"async"
|
|
1050
|
+
]),
|
|
1051
|
+
...selectorSchema("function", false, [
|
|
1052
|
+
"exported",
|
|
1053
|
+
"global",
|
|
1054
|
+
"unused",
|
|
1055
|
+
"async"
|
|
1056
|
+
]),
|
|
1057
|
+
...selectorSchema("parameter", true, ["destructured", "unused"]),
|
|
1058
|
+
...selectorSchema("objectStyleEnum", false, [
|
|
1059
|
+
"const",
|
|
1060
|
+
"exported",
|
|
1061
|
+
"global",
|
|
1062
|
+
"unused"
|
|
1063
|
+
]),
|
|
1064
|
+
...selectorSchema("memberLike", false, [
|
|
1065
|
+
"abstract",
|
|
1066
|
+
"private",
|
|
1067
|
+
"#private",
|
|
1068
|
+
"protected",
|
|
1069
|
+
"public",
|
|
1070
|
+
"readonly",
|
|
1071
|
+
"requiresQuotes",
|
|
1072
|
+
"static",
|
|
1073
|
+
"override",
|
|
1074
|
+
"async"
|
|
1075
|
+
]),
|
|
1076
|
+
...selectorSchema("classProperty", true, [
|
|
1077
|
+
"abstract",
|
|
1078
|
+
"private",
|
|
1079
|
+
"#private",
|
|
1080
|
+
"protected",
|
|
1081
|
+
"public",
|
|
1082
|
+
"readonly",
|
|
1083
|
+
"requiresQuotes",
|
|
1084
|
+
"static",
|
|
1085
|
+
"override"
|
|
1086
|
+
]),
|
|
1087
|
+
...selectorSchema("objectLiteralProperty", true, ["public", "requiresQuotes"]),
|
|
1088
|
+
...selectorSchema("typeProperty", true, [
|
|
1089
|
+
"public",
|
|
1090
|
+
"readonly",
|
|
1091
|
+
"requiresQuotes"
|
|
1092
|
+
]),
|
|
1093
|
+
...selectorSchema("parameterProperty", true, [
|
|
1094
|
+
"private",
|
|
1095
|
+
"protected",
|
|
1096
|
+
"public",
|
|
1097
|
+
"readonly"
|
|
1098
|
+
]),
|
|
1099
|
+
...selectorSchema("property", true, [
|
|
1100
|
+
"abstract",
|
|
1101
|
+
"private",
|
|
1102
|
+
"#private",
|
|
1103
|
+
"protected",
|
|
1104
|
+
"public",
|
|
1105
|
+
"readonly",
|
|
1106
|
+
"requiresQuotes",
|
|
1107
|
+
"static",
|
|
1108
|
+
"override",
|
|
1109
|
+
"async"
|
|
1110
|
+
]),
|
|
1111
|
+
...selectorSchema("classMethod", false, [
|
|
1112
|
+
"abstract",
|
|
1113
|
+
"private",
|
|
1114
|
+
"#private",
|
|
1115
|
+
"protected",
|
|
1116
|
+
"public",
|
|
1117
|
+
"requiresQuotes",
|
|
1118
|
+
"static",
|
|
1119
|
+
"override",
|
|
1120
|
+
"async"
|
|
1121
|
+
]),
|
|
1122
|
+
...selectorSchema("objectLiteralMethod", false, [
|
|
1123
|
+
"public",
|
|
1124
|
+
"requiresQuotes",
|
|
1125
|
+
"async"
|
|
1126
|
+
]),
|
|
1127
|
+
...selectorSchema("typeMethod", false, ["public", "requiresQuotes"]),
|
|
1128
|
+
...selectorSchema("method", false, [
|
|
1129
|
+
"abstract",
|
|
1130
|
+
"private",
|
|
1131
|
+
"#private",
|
|
1132
|
+
"protected",
|
|
1133
|
+
"public",
|
|
1134
|
+
"requiresQuotes",
|
|
1135
|
+
"static",
|
|
1136
|
+
"override",
|
|
1137
|
+
"async"
|
|
1138
|
+
]),
|
|
1139
|
+
...selectorSchema("classicAccessor", true, [
|
|
1140
|
+
"abstract",
|
|
1141
|
+
"private",
|
|
1142
|
+
"protected",
|
|
1143
|
+
"public",
|
|
1144
|
+
"requiresQuotes",
|
|
1145
|
+
"static",
|
|
1146
|
+
"override"
|
|
1147
|
+
]),
|
|
1148
|
+
...selectorSchema("autoAccessor", true, [
|
|
1149
|
+
"abstract",
|
|
1150
|
+
"private",
|
|
1151
|
+
"protected",
|
|
1152
|
+
"public",
|
|
1153
|
+
"requiresQuotes",
|
|
1154
|
+
"static",
|
|
1155
|
+
"override"
|
|
1156
|
+
]),
|
|
1157
|
+
...selectorSchema("accessor", true, [
|
|
1158
|
+
"abstract",
|
|
1159
|
+
"private",
|
|
1160
|
+
"protected",
|
|
1161
|
+
"public",
|
|
1162
|
+
"requiresQuotes",
|
|
1163
|
+
"static",
|
|
1164
|
+
"override"
|
|
1165
|
+
]),
|
|
1166
|
+
...selectorSchema("enumMember", false, ["requiresQuotes"]),
|
|
1167
|
+
...selectorSchema("typeLike", false, [
|
|
1168
|
+
"abstract",
|
|
1169
|
+
"exported",
|
|
1170
|
+
"unused"
|
|
1171
|
+
]),
|
|
1172
|
+
...selectorSchema("class", false, [
|
|
1173
|
+
"abstract",
|
|
1174
|
+
"exported",
|
|
1175
|
+
"unused"
|
|
1176
|
+
]),
|
|
1177
|
+
...selectorSchema("interface", false, ["exported", "unused"]),
|
|
1178
|
+
...selectorSchema("typeAlias", false, ["exported", "unused"]),
|
|
1179
|
+
...selectorSchema("enum", false, ["exported", "unused"]),
|
|
1180
|
+
...selectorSchema("typeParameter", false, ["unused"]),
|
|
1181
|
+
...selectorSchema("import", false, ["default", "namespace"])
|
|
1182
|
+
] },
|
|
1183
|
+
type: "array"
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
//#endregion
|
|
1187
|
+
//#region src/rules/naming-convention/rule.ts
|
|
1188
|
+
const RULE_NAME = "naming-convention";
|
|
1189
|
+
const messages = {
|
|
1190
|
+
doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
|
|
1191
|
+
doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
|
|
1192
|
+
missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
|
|
1193
|
+
missingUnderscore: "{{type}} name `{{name}}` must have {{count}} {{position}} underscore(s).",
|
|
1194
|
+
satisfyCustom: "{{type}} name `{{name}}` must {{regexMatch}} the RegExp: {{regex}}",
|
|
1195
|
+
unexpectedUnderscore: "{{type}} name `{{name}}` must not have a {{position}} underscore."
|
|
1196
|
+
};
|
|
1197
|
+
const camelCaseNamingConfig = [
|
|
1198
|
+
{
|
|
1199
|
+
format: ["camelCase"],
|
|
1200
|
+
leadingUnderscore: "allow",
|
|
1201
|
+
selector: "default",
|
|
1202
|
+
trailingUnderscore: "allow"
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
format: ["camelCase", "PascalCase"],
|
|
1206
|
+
selector: "import"
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
format: ["camelCase", "UPPER_CASE"],
|
|
1210
|
+
leadingUnderscore: "allow",
|
|
1211
|
+
selector: "variable",
|
|
1212
|
+
trailingUnderscore: "allow"
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
format: ["PascalCase"],
|
|
1216
|
+
selector: "typeLike"
|
|
1217
|
+
}
|
|
1218
|
+
];
|
|
1219
|
+
function create(contextWithoutDefaults) {
|
|
1220
|
+
const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
|
|
1221
|
+
const validators = parseOptions(context);
|
|
1222
|
+
const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
|
|
1223
|
+
function handleMember(validator, node, modifiers) {
|
|
1224
|
+
const { key } = node;
|
|
1225
|
+
if (requiresQuoting$1(key, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
|
|
1226
|
+
validator(key, modifiers);
|
|
1227
|
+
}
|
|
1228
|
+
function getMemberModifiers(node) {
|
|
1229
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1230
|
+
if ("key" in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) modifiers.add(Modifier["#private"]);
|
|
1231
|
+
else if (node.accessibility) modifiers.add(Modifier[node.accessibility]);
|
|
1232
|
+
else modifiers.add(Modifier.public);
|
|
1233
|
+
if (node.static) modifiers.add(Modifier.static);
|
|
1234
|
+
if ("readonly" in node && node.readonly) modifiers.add(Modifier.readonly);
|
|
1235
|
+
if ("override" in node && node.override) modifiers.add(Modifier.override);
|
|
1236
|
+
if (node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition || node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || node.type === AST_NODE_TYPES.TSAbstractAccessorProperty) modifiers.add(Modifier.abstract);
|
|
1237
|
+
return modifiers;
|
|
1238
|
+
}
|
|
1239
|
+
const { unusedVariables } = collectVariables(context);
|
|
1240
|
+
function isUnused(name$1, initialScope) {
|
|
1241
|
+
let variable = null;
|
|
1242
|
+
let scope = initialScope;
|
|
1243
|
+
while (scope) {
|
|
1244
|
+
variable = scope.set.get(name$1) ?? null;
|
|
1245
|
+
if (variable) break;
|
|
1246
|
+
scope = scope.upper;
|
|
1247
|
+
}
|
|
1248
|
+
if (!variable) return false;
|
|
1249
|
+
return unusedVariables.has(variable);
|
|
1250
|
+
}
|
|
1251
|
+
function isDestructured(id) {
|
|
1252
|
+
return id.parent.type === AST_NODE_TYPES.Property && id.parent.shorthand || id.parent.type === AST_NODE_TYPES.AssignmentPattern && id.parent.parent.type === AST_NODE_TYPES.Property && id.parent.parent.shorthand;
|
|
1253
|
+
}
|
|
1254
|
+
function isAsyncMemberOrProperty(propertyOrMemberNode) {
|
|
1255
|
+
return Boolean("value" in propertyOrMemberNode && propertyOrMemberNode.value && "async" in propertyOrMemberNode.value && propertyOrMemberNode.value.async);
|
|
1256
|
+
}
|
|
1257
|
+
function isAsyncVariableIdentifier(id) {
|
|
1258
|
+
return Boolean("async" in id.parent && id.parent.async || "init" in id.parent && id.parent.init && "async" in id.parent.init && id.parent.init.async);
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Determines if a VariableDeclarator represents an object-style enum declaration.
|
|
1262
|
+
*
|
|
1263
|
+
* Object-style enums are const assertions applied to object expressions, commonly
|
|
1264
|
+
* used as an alternative to TypeScript enums. Examples:
|
|
1265
|
+
* - `const Colors = { RED: 'red', BLUE: 'blue' } as const`
|
|
1266
|
+
* - `const Status = <const>{ OK: 200, ERROR: 500 }`.
|
|
1267
|
+
*
|
|
1268
|
+
* @param node - The VariableDeclarator AST node to check.
|
|
1269
|
+
* @param parent - The parent VariableDeclaration node.
|
|
1270
|
+
* @returns True if this represents an object-style enum declaration.
|
|
1271
|
+
*/
|
|
1272
|
+
function isObjectStyleEnumDeclaration(node, parent) {
|
|
1273
|
+
if (parent.kind !== "const") return false;
|
|
1274
|
+
if (!node.init) return false;
|
|
1275
|
+
if (node.init.type === AST_NODE_TYPES.TSAsExpression && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
|
|
1276
|
+
if (node.init.type === AST_NODE_TYPES.TSTypeAssertion && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
|
|
1277
|
+
return false;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Checks if a method name exists in any of the implemented interfaces.
|
|
1281
|
+
*
|
|
1282
|
+
* @param implementsList - List of implemented interfaces.
|
|
1283
|
+
* @param methodName - Name of the method to check.
|
|
1284
|
+
* @param checker - TypeScript type checker.
|
|
1285
|
+
* @param services - Parser services.
|
|
1286
|
+
* @returns True if the method name is found in any interface.
|
|
1287
|
+
*/
|
|
1288
|
+
function checkInterfacesForMethod(implementsList, methodName, checker, services) {
|
|
1289
|
+
for (const implementsClause of implementsList) {
|
|
1290
|
+
const interfaceType = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(implementsClause));
|
|
1291
|
+
if (!interfaceType.getSymbol()) continue;
|
|
1292
|
+
const interfaceMembers = checker.getPropertiesOfType(interfaceType);
|
|
1293
|
+
for (const member of interfaceMembers) if (member.name === methodName) return true;
|
|
1294
|
+
}
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Determines if a class method is implementing an interface method.
|
|
1299
|
+
*
|
|
1300
|
+
* @param node - The class method node to check.
|
|
1301
|
+
* @returns True if the method is implementing an interface method.
|
|
1302
|
+
*/
|
|
1303
|
+
function isImplementingInterfaceMethod(node) {
|
|
1304
|
+
const services = getParserServices(context, true);
|
|
1305
|
+
if (!services.program) return false;
|
|
1306
|
+
const checker = services.program.getTypeChecker();
|
|
1307
|
+
let parentClass = null;
|
|
1308
|
+
let current = node.parent;
|
|
1309
|
+
while (current) {
|
|
1310
|
+
if (current.type === AST_NODE_TYPES.ClassDeclaration || current.type === AST_NODE_TYPES.ClassExpression) {
|
|
1311
|
+
parentClass = current;
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
current = current.parent;
|
|
1315
|
+
}
|
|
1316
|
+
if (!parentClass?.implements || parentClass.implements.length === 0) return false;
|
|
1317
|
+
let methodName = null;
|
|
1318
|
+
if (node.key.type === AST_NODE_TYPES.Identifier) methodName = node.key.name;
|
|
1319
|
+
else if (node.key.type === AST_NODE_TYPES.Literal) methodName = String(node.key.value);
|
|
1320
|
+
if (methodName === null) return false;
|
|
1321
|
+
return checkInterfacesForMethod(parentClass.implements, methodName, checker, services);
|
|
1322
|
+
}
|
|
1323
|
+
const selectors = {
|
|
1324
|
+
":matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
|
|
1325
|
+
handler: (node, validator) => {
|
|
1326
|
+
const modifiers = getMemberModifiers(node);
|
|
1327
|
+
handleMember(validator, node, modifiers);
|
|
1328
|
+
},
|
|
1329
|
+
validator: validators.classProperty
|
|
1330
|
+
},
|
|
1331
|
+
":not(ObjectPattern) > Property[computed = false][kind = \"init\"][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
|
|
1332
|
+
handler: (node, validator) => {
|
|
1333
|
+
const modifiers = new Set([Modifier.public]);
|
|
1334
|
+
handleMember(validator, node, modifiers);
|
|
1335
|
+
},
|
|
1336
|
+
validator: validators.objectLiteralProperty
|
|
1337
|
+
},
|
|
1338
|
+
[["TSMethodSignature[computed = false]", "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type = \"TSFunctionType\"]"].join(", ")]: {
|
|
1339
|
+
handler: (node, validator) => {
|
|
1340
|
+
const modifiers = new Set([Modifier.public]);
|
|
1341
|
+
handleMember(validator, node, modifiers);
|
|
1342
|
+
},
|
|
1343
|
+
validator: validators.typeMethod
|
|
1344
|
+
},
|
|
1345
|
+
[[
|
|
1346
|
+
":matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type = \"ArrowFunctionExpression\"]",
|
|
1347
|
+
":matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type = \"FunctionExpression\"]",
|
|
1348
|
+
":matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type = \"TSEmptyBodyFunctionExpression\"]",
|
|
1349
|
+
":matches(MethodDefinition, TSAbstractMethodDefinition)[computed = false][kind = \"method\"]"
|
|
1350
|
+
].join(", ")]: {
|
|
1351
|
+
handler: (node, validator) => {
|
|
1352
|
+
if (isImplementingInterfaceMethod(node)) return;
|
|
1353
|
+
const modifiers = getMemberModifiers(node);
|
|
1354
|
+
if (isAsyncMemberOrProperty(node)) modifiers.add(Modifier.async);
|
|
1355
|
+
handleMember(validator, node, modifiers);
|
|
1356
|
+
},
|
|
1357
|
+
validator: validators.classMethod
|
|
1358
|
+
},
|
|
1359
|
+
[["MethodDefinition[computed = false]:matches([kind = \"get\"], [kind = \"set\"])", "TSAbstractMethodDefinition[computed = false]:matches([kind=\"get\"], [kind=\"set\"])"].join(", ")]: {
|
|
1360
|
+
handler: (node, validator) => {
|
|
1361
|
+
const modifiers = getMemberModifiers(node);
|
|
1362
|
+
handleMember(validator, node, modifiers);
|
|
1363
|
+
},
|
|
1364
|
+
validator: validators.classicAccessor
|
|
1365
|
+
},
|
|
1366
|
+
[[
|
|
1367
|
+
"Property[computed = false][kind = \"init\"][value.type = \"ArrowFunctionExpression\"]",
|
|
1368
|
+
"Property[computed = false][kind = \"init\"][value.type = \"FunctionExpression\"]",
|
|
1369
|
+
"Property[computed = false][kind = \"init\"][value.type = \"TSEmptyBodyFunctionExpression\"]"
|
|
1370
|
+
].join(", ")]: {
|
|
1371
|
+
handler: (node, validator) => {
|
|
1372
|
+
const modifiers = new Set([Modifier.public]);
|
|
1373
|
+
if (isAsyncMemberOrProperty(node)) modifiers.add(Modifier.async);
|
|
1374
|
+
handleMember(validator, node, modifiers);
|
|
1375
|
+
},
|
|
1376
|
+
validator: validators.objectLiteralMethod
|
|
1377
|
+
},
|
|
1378
|
+
[[AST_NODE_TYPES.AccessorProperty, AST_NODE_TYPES.TSAbstractAccessorProperty].join(", ")]: {
|
|
1379
|
+
handler: (node, validator) => {
|
|
1380
|
+
const modifiers = getMemberModifiers(node);
|
|
1381
|
+
handleMember(validator, node, modifiers);
|
|
1382
|
+
},
|
|
1383
|
+
validator: validators.autoAccessor
|
|
1384
|
+
},
|
|
1385
|
+
"ClassDeclaration, ClassExpression": {
|
|
1386
|
+
handler: (node, validator) => {
|
|
1387
|
+
const { abstract, id } = node;
|
|
1388
|
+
if (id === null) return;
|
|
1389
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1390
|
+
const scope = context.sourceCode.getScope(node).upper;
|
|
1391
|
+
if (abstract) modifiers.add(Modifier.abstract);
|
|
1392
|
+
if (isExported(node, id.name, scope)) modifiers.add(Modifier.exported);
|
|
1393
|
+
if (isUnused(id.name, scope)) modifiers.add(Modifier.unused);
|
|
1394
|
+
validator(id, modifiers);
|
|
1395
|
+
},
|
|
1396
|
+
validator: validators.class
|
|
1397
|
+
},
|
|
1398
|
+
"FunctionDeclaration, TSDeclareFunction, FunctionExpression": {
|
|
1399
|
+
handler: (node, validator) => {
|
|
1400
|
+
if (node.id === null) return;
|
|
1401
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1402
|
+
const scope = context.sourceCode.getScope(node).upper;
|
|
1403
|
+
if (isGlobal(scope)) modifiers.add(Modifier.global);
|
|
1404
|
+
if (isExported(node, node.id.name, scope)) modifiers.add(Modifier.exported);
|
|
1405
|
+
if (isUnused(node.id.name, scope)) modifiers.add(Modifier.unused);
|
|
1406
|
+
if (node.async) modifiers.add(Modifier.async);
|
|
1407
|
+
validator(node.id, modifiers);
|
|
1408
|
+
},
|
|
1409
|
+
validator: validators.function
|
|
1410
|
+
},
|
|
1411
|
+
"FunctionDeclaration, TSDeclareFunction, TSEmptyBodyFunctionExpression, FunctionExpression, ArrowFunctionExpression": {
|
|
1412
|
+
handler: (node, validator) => {
|
|
1413
|
+
for (const parameter of node.params) {
|
|
1414
|
+
if (parameter.type === AST_NODE_TYPES.TSParameterProperty) continue;
|
|
1415
|
+
const identifiers = getIdentifiersFromPattern(parameter);
|
|
1416
|
+
for (const index of identifiers) {
|
|
1417
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1418
|
+
if (isDestructured(index)) modifiers.add(Modifier.destructured);
|
|
1419
|
+
if (isUnused(index.name, context.sourceCode.getScope(index))) modifiers.add(Modifier.unused);
|
|
1420
|
+
validator(index, modifiers);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
validator: validators.parameter
|
|
1425
|
+
},
|
|
1426
|
+
"ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier": {
|
|
1427
|
+
handler: (node, validator) => {
|
|
1428
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1429
|
+
switch (node.type) {
|
|
1430
|
+
case AST_NODE_TYPES.ImportDefaultSpecifier:
|
|
1431
|
+
modifiers.add(Modifier.default);
|
|
1432
|
+
break;
|
|
1433
|
+
case AST_NODE_TYPES.ImportNamespaceSpecifier:
|
|
1434
|
+
modifiers.add(Modifier.namespace);
|
|
1435
|
+
break;
|
|
1436
|
+
case AST_NODE_TYPES.ImportSpecifier:
|
|
1437
|
+
if (node.imported.type === AST_NODE_TYPES.Identifier && node.imported.name !== "default") return;
|
|
1438
|
+
modifiers.add(Modifier.default);
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
validator(node.local, modifiers);
|
|
1442
|
+
},
|
|
1443
|
+
validator: validators.import
|
|
1444
|
+
},
|
|
1445
|
+
"Property[computed = false]:matches([kind = \"get\"], [kind = \"set\"])": {
|
|
1446
|
+
handler: (node, validator) => {
|
|
1447
|
+
const modifiers = new Set([Modifier.public]);
|
|
1448
|
+
handleMember(validator, node, modifiers);
|
|
1449
|
+
},
|
|
1450
|
+
validator: validators.classicAccessor
|
|
1451
|
+
},
|
|
1452
|
+
"TSEnumDeclaration": {
|
|
1453
|
+
handler: (node, validator) => {
|
|
1454
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1455
|
+
const scope = context.sourceCode.getScope(node).upper;
|
|
1456
|
+
if (isExported(node, node.id.name, scope)) modifiers.add(Modifier.exported);
|
|
1457
|
+
if (isUnused(node.id.name, scope)) modifiers.add(Modifier.unused);
|
|
1458
|
+
validator(node.id, modifiers);
|
|
1459
|
+
},
|
|
1460
|
+
validator: validators.enum
|
|
1461
|
+
},
|
|
1462
|
+
"TSEnumMember": {
|
|
1463
|
+
handler: (node, validator) => {
|
|
1464
|
+
const { id } = node;
|
|
1465
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1466
|
+
if (requiresQuoting$1(id, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
|
|
1467
|
+
validator(id, modifiers);
|
|
1468
|
+
},
|
|
1469
|
+
validator: validators.enumMember
|
|
1470
|
+
},
|
|
1471
|
+
"TSInterfaceDeclaration": {
|
|
1472
|
+
handler: (node, validator) => {
|
|
1473
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1474
|
+
const scope = context.sourceCode.getScope(node);
|
|
1475
|
+
if (isExported(node, node.id.name, scope)) modifiers.add(Modifier.exported);
|
|
1476
|
+
if (isUnused(node.id.name, scope)) modifiers.add(Modifier.unused);
|
|
1477
|
+
validator(node.id, modifiers);
|
|
1478
|
+
},
|
|
1479
|
+
validator: validators.interface
|
|
1480
|
+
},
|
|
1481
|
+
"TSParameterProperty": {
|
|
1482
|
+
handler: (node, validator) => {
|
|
1483
|
+
const modifiers = getMemberModifiers(node);
|
|
1484
|
+
const identifiers = getIdentifiersFromPattern(node.parameter);
|
|
1485
|
+
for (const index of identifiers) validator(index, modifiers);
|
|
1486
|
+
},
|
|
1487
|
+
validator: validators.parameterProperty
|
|
1488
|
+
},
|
|
1489
|
+
"TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type != \"TSFunctionType\"]": {
|
|
1490
|
+
handler: (node, validator) => {
|
|
1491
|
+
const modifiers = new Set([Modifier.public]);
|
|
1492
|
+
if (node.readonly) modifiers.add(Modifier.readonly);
|
|
1493
|
+
handleMember(validator, node, modifiers);
|
|
1494
|
+
},
|
|
1495
|
+
validator: validators.typeProperty
|
|
1496
|
+
},
|
|
1497
|
+
"TSTypeAliasDeclaration": {
|
|
1498
|
+
handler: (node, validator) => {
|
|
1499
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1500
|
+
const scope = context.sourceCode.getScope(node);
|
|
1501
|
+
if (isExported(node, node.id.name, scope)) modifiers.add(Modifier.exported);
|
|
1502
|
+
if (isUnused(node.id.name, scope)) modifiers.add(Modifier.unused);
|
|
1503
|
+
validator(node.id, modifiers);
|
|
1504
|
+
},
|
|
1505
|
+
validator: validators.typeAlias
|
|
1506
|
+
},
|
|
1507
|
+
"TSTypeParameterDeclaration > TSTypeParameter": {
|
|
1508
|
+
handler: (node, validator) => {
|
|
1509
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1510
|
+
const scope = context.sourceCode.getScope(node);
|
|
1511
|
+
if (isUnused(node.name.name, scope)) modifiers.add(Modifier.unused);
|
|
1512
|
+
validator(node.name, modifiers);
|
|
1513
|
+
},
|
|
1514
|
+
validator: validators.typeParameter
|
|
1515
|
+
},
|
|
1516
|
+
"VariableDeclarator": {
|
|
1517
|
+
handler: (node, validator) => {
|
|
1518
|
+
const identifiers = getIdentifiersFromPattern(node.id);
|
|
1519
|
+
const baseModifiers = /* @__PURE__ */ new Set();
|
|
1520
|
+
const { parent } = node;
|
|
1521
|
+
if (parent.kind === "const") baseModifiers.add(Modifier.const);
|
|
1522
|
+
if (isGlobal(context.sourceCode.getScope(node))) baseModifiers.add(Modifier.global);
|
|
1523
|
+
const isObjectStyleEnum = isObjectStyleEnumDeclaration(node, parent);
|
|
1524
|
+
for (const id of identifiers) {
|
|
1525
|
+
const modifiers = new Set(baseModifiers);
|
|
1526
|
+
if (isDestructured(id)) modifiers.add(Modifier.destructured);
|
|
1527
|
+
const scope = context.sourceCode.getScope(id);
|
|
1528
|
+
if (isExported(parent, id.name, scope)) modifiers.add(Modifier.exported);
|
|
1529
|
+
if (isUnused(id.name, scope)) modifiers.add(Modifier.unused);
|
|
1530
|
+
if (isAsyncVariableIdentifier(id)) modifiers.add(Modifier.async);
|
|
1531
|
+
if (isObjectStyleEnum) validators.objectStyleEnum(id, modifiers);
|
|
1532
|
+
else validator(id, modifiers);
|
|
1533
|
+
}
|
|
1534
|
+
},
|
|
1535
|
+
validator: validators.variable
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
return Object.fromEntries(Object.entries(selectors).map(([selector, { handler, validator }]) => {
|
|
1539
|
+
return [selector, (node) => {
|
|
1540
|
+
handler(node, validator);
|
|
1541
|
+
}];
|
|
1542
|
+
}));
|
|
1543
|
+
}
|
|
1544
|
+
const namingConvention = createEslintRule({
|
|
1545
|
+
create,
|
|
1546
|
+
defaultOptions: camelCaseNamingConfig,
|
|
1547
|
+
meta: {
|
|
1548
|
+
docs: {
|
|
1549
|
+
description: "Enforce naming conventions for everything across a codebase",
|
|
1550
|
+
recommended: true,
|
|
1551
|
+
requiresTypeChecking: true
|
|
1552
|
+
},
|
|
1553
|
+
fixable: void 0,
|
|
1554
|
+
hasSuggestions: false,
|
|
1555
|
+
messages,
|
|
1556
|
+
schema: SCHEMA,
|
|
1557
|
+
type: "suggestion"
|
|
1558
|
+
},
|
|
1559
|
+
name: RULE_NAME
|
|
1560
|
+
});
|
|
1561
|
+
function getIdentifiersFromPattern(pattern) {
|
|
1562
|
+
const identifiers = [];
|
|
1563
|
+
new PatternVisitor({}, pattern, (id) => identifiers.push(id)).visit(pattern);
|
|
1564
|
+
return identifiers;
|
|
1565
|
+
}
|
|
1566
|
+
function isExported(node, name$1, scope) {
|
|
1567
|
+
if (node?.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || node?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) return true;
|
|
1568
|
+
if (scope === null) return false;
|
|
1569
|
+
const variable = scope.set.get(name$1);
|
|
1570
|
+
if (variable) for (const ref of variable.references) {
|
|
1571
|
+
const refParent = ref.identifier.parent;
|
|
1572
|
+
if (refParent.type === AST_NODE_TYPES.ExportDefaultDeclaration || refParent.type === AST_NODE_TYPES.ExportSpecifier) return true;
|
|
1573
|
+
}
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
function isGlobal(scope) {
|
|
1577
|
+
if (scope === null) return false;
|
|
1578
|
+
return scope.type === TSESLint.Scope.ScopeType.global || scope.type === TSESLint.Scope.ScopeType.module;
|
|
1579
|
+
}
|
|
1580
|
+
function requiresQuoting$1(node, target) {
|
|
1581
|
+
const name$1 = node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`;
|
|
1582
|
+
return requiresQuoting(name$1, target);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region src/plugin.ts
|
|
1587
|
+
const PLUGIN_NAME = name.replace(/^eslint-plugin-/, "");
|
|
1588
|
+
/**
|
|
1589
|
+
* Generates a rules record where all plugin rules are set to "error".
|
|
1590
|
+
*
|
|
1591
|
+
* @param pluginName - The plugin identifier used to prefix rule names.
|
|
1592
|
+
* @param rules - The rules record to transform.
|
|
1593
|
+
* @returns A Linter.RulesRecord with all rules enabled.
|
|
1594
|
+
*/
|
|
1595
|
+
function getRules(pluginName, rules) {
|
|
1596
|
+
return Object.fromEntries(Object.keys(rules).map((ruleName) => [`${pluginName}/${ruleName}`, "error"]));
|
|
1597
|
+
}
|
|
1598
|
+
const plugin = {
|
|
1599
|
+
meta: {
|
|
1600
|
+
name: PLUGIN_NAME,
|
|
1601
|
+
version
|
|
1602
|
+
},
|
|
1603
|
+
rules: { "naming-convention": namingConvention }
|
|
1604
|
+
};
|
|
1605
|
+
const allRules = getRules(PLUGIN_NAME, plugin.rules);
|
|
1606
|
+
|
|
1607
|
+
//#endregion
|
|
1608
|
+
//#region src/configs/index.ts
|
|
1609
|
+
const configs = { recommended: {
|
|
1610
|
+
plugins: { [PLUGIN_NAME]: plugin },
|
|
1611
|
+
rules: {}
|
|
1612
|
+
} };
|
|
1613
|
+
|
|
1614
|
+
//#endregion
|
|
1615
|
+
//#region src/index.ts
|
|
1616
|
+
var src_default = {
|
|
1617
|
+
...plugin,
|
|
1618
|
+
configs
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1621
|
+
//#endregion
|
|
1622
|
+
export { src_default as default };
|