eslint-plugin-flawless 0.1.0 → 0.1.2
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/README.md +10 -6
- package/dist/index.d.mts +42 -7
- package/dist/index.mjs +427 -149
- package/package.json +31 -28
package/README.md
CHANGED
|
@@ -171,12 +171,16 @@ pnpm eslint-docs
|
|
|
171
171
|
|
|
172
172
|
<!-- begin auto-generated rules list -->
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
[
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
|
174
|
+
🔧 Automatically fixable by the
|
|
175
|
+
[`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
176
|
+
💭
|
|
177
|
+
Requires [type information](https://typescript-eslint.io/linting/typed-linting).
|
|
178
|
+
|
|
179
|
+
| Name | Description | 🔧 | 💭 |
|
|
180
|
+
| :---------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :-- | :-- |
|
|
181
|
+
| [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase | | 💭 |
|
|
182
|
+
| [toml-sort-keys](src/rules/toml-sort-keys/documentation.md) | Enforce a configured sort order for TOML keys and tables | 🔧 | |
|
|
183
|
+
| [yaml-block-key-blank-lines](src/rules/yaml-block-key-blank-lines/documentation.md) | Enforce blank lines around top-level YAML block collection keys | 🔧 | |
|
|
180
184
|
|
|
181
185
|
<!-- end auto-generated rules list -->
|
|
182
186
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { TSESLint } from "@typescript-eslint/utils";
|
|
2
|
-
import "@typescript-eslint/utils/eslint-utils";
|
|
3
|
-
import * as _typescript_eslint_utils_ts_eslint0 from "@typescript-eslint/utils/ts-eslint";
|
|
4
2
|
import { Linter } from "eslint";
|
|
5
3
|
|
|
6
4
|
//#region src/util.d.ts
|
|
@@ -117,9 +115,22 @@ interface NamingSelector {
|
|
|
117
115
|
}
|
|
118
116
|
//#endregion
|
|
119
117
|
//#region src/rules/naming-convention/rule.d.ts
|
|
120
|
-
type MessageIds
|
|
118
|
+
type MessageIds = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
|
|
121
119
|
type Options$1 = Array<NamingSelector>;
|
|
122
120
|
//#endregion
|
|
121
|
+
//#region src/rules/toml-sort-keys/rule.d.ts
|
|
122
|
+
type Options = Array<SortSpec>;
|
|
123
|
+
interface SortOrderObject {
|
|
124
|
+
caseSensitive?: boolean;
|
|
125
|
+
natural?: boolean;
|
|
126
|
+
type?: "asc" | "desc";
|
|
127
|
+
}
|
|
128
|
+
type SortOrder = Array<string> | SortOrderObject;
|
|
129
|
+
interface SortSpec {
|
|
130
|
+
order: SortOrder;
|
|
131
|
+
pathPattern: string;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
123
134
|
//#region src/plugin.d.ts
|
|
124
135
|
declare const plugin: {
|
|
125
136
|
meta: {
|
|
@@ -127,7 +138,15 @@ declare const plugin: {
|
|
|
127
138
|
version: string;
|
|
128
139
|
};
|
|
129
140
|
rules: {
|
|
130
|
-
"naming-convention": TSESLint.RuleModule<MessageIds
|
|
141
|
+
"naming-convention": TSESLint.RuleModule<MessageIds, Options$1, PluginDocumentation, TSESLint.RuleListener> & {
|
|
142
|
+
name: string;
|
|
143
|
+
};
|
|
144
|
+
"toml-sort-keys": TSESLint.RuleModule<"unsorted", Options, PluginDocumentation, TSESLint.RuleListener> & {
|
|
145
|
+
name: string;
|
|
146
|
+
};
|
|
147
|
+
"yaml-block-key-blank-lines": TSESLint.RuleModule<"blankLine", [], PluginDocumentation, TSESLint.RuleListener> & {
|
|
148
|
+
name: string;
|
|
149
|
+
};
|
|
131
150
|
};
|
|
132
151
|
};
|
|
133
152
|
//#endregion
|
|
@@ -142,7 +161,15 @@ declare const _default: {
|
|
|
142
161
|
version: string;
|
|
143
162
|
};
|
|
144
163
|
rules: {
|
|
145
|
-
"naming-convention":
|
|
164
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds, Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
165
|
+
name: string;
|
|
166
|
+
};
|
|
167
|
+
"toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
168
|
+
name: string;
|
|
169
|
+
};
|
|
170
|
+
"yaml-block-key-blank-lines": import("${configDir}").RuleModule<"blankLine", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
171
|
+
name: string;
|
|
172
|
+
};
|
|
146
173
|
};
|
|
147
174
|
};
|
|
148
175
|
};
|
|
@@ -154,10 +181,18 @@ declare const _default: {
|
|
|
154
181
|
version: string;
|
|
155
182
|
};
|
|
156
183
|
rules: {
|
|
157
|
-
"naming-convention":
|
|
184
|
+
"naming-convention": import("${configDir}").RuleModule<MessageIds, Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
185
|
+
name: string;
|
|
186
|
+
};
|
|
187
|
+
"toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
188
|
+
name: string;
|
|
189
|
+
};
|
|
190
|
+
"yaml-block-key-blank-lines": import("${configDir}").RuleModule<"blankLine", [], PluginDocumentation, import("${configDir}").RuleListener> & {
|
|
191
|
+
name: string;
|
|
192
|
+
};
|
|
158
193
|
};
|
|
159
194
|
};
|
|
160
|
-
type RuleOptions = { [K in keyof RuleDefinitions]: RuleDefinitions[K]["defaultOptions"] };
|
|
195
|
+
type RuleOptions = { [K in keyof RuleDefinitions]: NonNullable<RuleDefinitions[K]["defaultOptions"]> };
|
|
161
196
|
type Rules = { [K in keyof RuleOptions]: Linter.RuleEntry<RuleOptions[K]> };
|
|
162
197
|
type RuleDefinitions = typeof plugin.rules;
|
|
163
198
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -3,21 +3,34 @@ import { requiresQuoting } from "@typescript-eslint/type-utils";
|
|
|
3
3
|
import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
|
|
4
4
|
import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
|
|
5
5
|
import assert from "node:assert";
|
|
6
|
-
|
|
6
|
+
import { getStaticTOMLValue } from "toml-eslint-parser";
|
|
7
7
|
//#region package.json
|
|
8
8
|
var name = "eslint-plugin-flawless";
|
|
9
|
-
var version = "0.1.
|
|
9
|
+
var version = "0.1.2";
|
|
10
10
|
var repository = {
|
|
11
11
|
"url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
|
|
12
12
|
"type": "git"
|
|
13
13
|
};
|
|
14
|
-
|
|
15
14
|
//#endregion
|
|
16
15
|
//#region src/util.ts
|
|
17
|
-
const
|
|
18
|
-
return `${repository.url.replace(/^git\+/, "").replace(/\.git$/, "")}/blob/v${version}/src/rules/${name
|
|
16
|
+
const createRule = RuleCreator((name) => {
|
|
17
|
+
return `${repository.url.replace(/^git\+/, "").replace(/\.git$/, "")}/blob/v${version}/src/rules/${name}/documentation.md`;
|
|
19
18
|
});
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Creates a rule with a docs URL, allowing a rule's `create` to receive a
|
|
21
|
+
* context with a custom `sourceCode` type (such as a YAML source code). The
|
|
22
|
+
* `SourceCode` type parameter defaults to ESLint's `SourceCode`, so standard
|
|
23
|
+
* rules are unaffected.
|
|
24
|
+
*
|
|
25
|
+
* @template Options - The rule's options tuple.
|
|
26
|
+
* @template MessageIds - The rule's message identifiers.
|
|
27
|
+
* @template SourceCode - The source code type exposed on `context.sourceCode`.
|
|
28
|
+
* @param rule - The rule definition (meta, name, defaultOptions, create).
|
|
29
|
+
* @returns The created rule module.
|
|
30
|
+
*/
|
|
31
|
+
function createEslintRule(rule) {
|
|
32
|
+
return createRule(rule);
|
|
33
|
+
}
|
|
21
34
|
//#endregion
|
|
22
35
|
//#region src/utils/is-type-import.ts
|
|
23
36
|
/**
|
|
@@ -34,7 +47,6 @@ const createEslintRule = RuleCreator((name$1) => {
|
|
|
34
47
|
function isTypeImport(definition) {
|
|
35
48
|
return definition?.type === DefinitionType.ImportBinding && (definition.parent.importKind === "type" || definition.node.type === AST_NODE_TYPES.ImportSpecifier && definition.node.importKind === "type");
|
|
36
49
|
}
|
|
37
|
-
|
|
38
50
|
//#endregion
|
|
39
51
|
//#region src/utils/reference-contains-type-query.ts
|
|
40
52
|
/**
|
|
@@ -50,7 +62,6 @@ function referenceContainsTypeQuery(node) {
|
|
|
50
62
|
default: return false;
|
|
51
63
|
}
|
|
52
64
|
}
|
|
53
|
-
|
|
54
65
|
//#endregion
|
|
55
66
|
//#region src/utils/collect-variables.ts
|
|
56
67
|
/**
|
|
@@ -113,10 +124,15 @@ var UnusedVariablesVisitor = class extends Visitor {
|
|
|
113
124
|
}
|
|
114
125
|
TSParameterProperty(node) {
|
|
115
126
|
let identifier;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
switch (node.parameter.type) {
|
|
128
|
+
case AST_NODE_TYPES.AssignmentPattern:
|
|
129
|
+
identifier = node.parameter.left;
|
|
130
|
+
break;
|
|
131
|
+
case AST_NODE_TYPES.Identifier:
|
|
132
|
+
identifier = node.parameter;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
this.markVariableAsUsed(identifier);
|
|
120
136
|
}
|
|
121
137
|
collectUnusedVariables({ scope, variables = {
|
|
122
138
|
unusedVariables: /* @__PURE__ */ new Set(),
|
|
@@ -140,9 +156,9 @@ var UnusedVariablesVisitor = class extends Visitor {
|
|
|
140
156
|
const scope = this.#scopeManager.acquire(node, inner);
|
|
141
157
|
if (scope) {
|
|
142
158
|
if (scope.type === ScopeType.functionExpressionName) {
|
|
143
|
-
const returnValue
|
|
144
|
-
assert(returnValue
|
|
145
|
-
return returnValue
|
|
159
|
+
const returnValue = scope.childScopes[0];
|
|
160
|
+
assert(returnValue, "Function expression name scope should have a child scope");
|
|
161
|
+
return returnValue;
|
|
146
162
|
}
|
|
147
163
|
return scope;
|
|
148
164
|
}
|
|
@@ -157,19 +173,19 @@ var UnusedVariablesVisitor = class extends Visitor {
|
|
|
157
173
|
variableOrIdentifierOrName.eslintUsed = true;
|
|
158
174
|
return;
|
|
159
175
|
}
|
|
160
|
-
let name
|
|
176
|
+
let name;
|
|
161
177
|
let node;
|
|
162
178
|
if (typeof variableOrIdentifierOrName === "string") {
|
|
163
|
-
name
|
|
179
|
+
name = variableOrIdentifierOrName;
|
|
164
180
|
assert(parent, "Parent node is required when marking by name");
|
|
165
181
|
node = parent;
|
|
166
182
|
} else {
|
|
167
|
-
({name
|
|
183
|
+
({name} = variableOrIdentifierOrName);
|
|
168
184
|
node = variableOrIdentifierOrName;
|
|
169
185
|
}
|
|
170
186
|
let currentScope = this.getScope(node);
|
|
171
187
|
while (currentScope) {
|
|
172
|
-
const variable = currentScope.variables.find((scopeVariable) => scopeVariable.name === name
|
|
188
|
+
const variable = currentScope.variables.find((scopeVariable) => scopeVariable.name === name);
|
|
173
189
|
if (variable) {
|
|
174
190
|
variable.eslintUsed = true;
|
|
175
191
|
return;
|
|
@@ -223,8 +239,8 @@ var UnusedVariablesVisitor = class extends Visitor {
|
|
|
223
239
|
if (variable?.defs.length === 0) this.markVariableAsUsed(variable);
|
|
224
240
|
}
|
|
225
241
|
visitFunctionTypeSignature(node) {
|
|
226
|
-
for (const parameter of node.params) this.visitPattern(parameter, (name
|
|
227
|
-
this.markVariableAsUsed(name
|
|
242
|
+
for (const parameter of node.params) this.visitPattern(parameter, (name) => {
|
|
243
|
+
this.markVariableAsUsed(name);
|
|
228
244
|
});
|
|
229
245
|
}
|
|
230
246
|
visitSetter(node) {
|
|
@@ -257,7 +273,7 @@ function isSelfReference(ref, nodes) {
|
|
|
257
273
|
}
|
|
258
274
|
return false;
|
|
259
275
|
}
|
|
260
|
-
const MERGEABLE_TYPES = new Set([
|
|
276
|
+
const MERGEABLE_TYPES = /* @__PURE__ */ new Set([
|
|
261
277
|
AST_NODE_TYPES.ClassDeclaration,
|
|
262
278
|
AST_NODE_TYPES.FunctionDeclaration,
|
|
263
279
|
AST_NODE_TYPES.TSInterfaceDeclaration,
|
|
@@ -289,7 +305,7 @@ function isMergeableExported(variable) {
|
|
|
289
305
|
}
|
|
290
306
|
return false;
|
|
291
307
|
}
|
|
292
|
-
const LOGICAL_ASSIGNMENT_OPERATORS = new Set([
|
|
308
|
+
const LOGICAL_ASSIGNMENT_OPERATORS = /* @__PURE__ */ new Set([
|
|
293
309
|
"&&=",
|
|
294
310
|
"??=",
|
|
295
311
|
"||="
|
|
@@ -409,7 +425,7 @@ function isUsedVariable(variable) {
|
|
|
409
425
|
* @param rhsNode - The RHS node of the previous assignment.
|
|
410
426
|
* @returns The reference is a read to update itself.
|
|
411
427
|
*/
|
|
412
|
-
function isReadForItself(ref, rhsNode
|
|
428
|
+
function isReadForItself(ref, rhsNode) {
|
|
413
429
|
/**
|
|
414
430
|
* Checks whether a given Identifier node exists inside of a function node which can be used later.
|
|
415
431
|
*
|
|
@@ -423,7 +439,7 @@ function isUsedVariable(variable) {
|
|
|
423
439
|
* @param rightHandSideNode - The RHS node of the previous assignment.
|
|
424
440
|
* @returns `true` if the `id` node exists inside of a function node which can be used later.
|
|
425
441
|
*/
|
|
426
|
-
function isInsideOfStorableFunction(id
|
|
442
|
+
function isInsideOfStorableFunction(id, rightHandSideNode) {
|
|
427
443
|
/**
|
|
428
444
|
* Finds a function node from ancestors of a node.
|
|
429
445
|
* @param node - A start node to find.
|
|
@@ -446,32 +462,32 @@ function isUsedVariable(variable) {
|
|
|
446
462
|
* - the funcNode is bound as an argument of a function call.
|
|
447
463
|
* - the function is bound to a property and the object satisfies above conditions.
|
|
448
464
|
*/
|
|
449
|
-
function isStorableFunction(funcNode
|
|
450
|
-
let node = funcNode
|
|
451
|
-
let { parent
|
|
452
|
-
while (parent
|
|
453
|
-
switch (parent
|
|
465
|
+
function isStorableFunction(funcNode, storableRhsNode) {
|
|
466
|
+
let node = funcNode;
|
|
467
|
+
let { parent } = funcNode;
|
|
468
|
+
while (parent && isInside(parent, storableRhsNode)) {
|
|
469
|
+
switch (parent.type) {
|
|
454
470
|
case AST_NODE_TYPES.AssignmentExpression:
|
|
455
471
|
case AST_NODE_TYPES.TaggedTemplateExpression:
|
|
456
472
|
case AST_NODE_TYPES.YieldExpression: return true;
|
|
457
473
|
case AST_NODE_TYPES.CallExpression:
|
|
458
|
-
case AST_NODE_TYPES.NewExpression: return parent
|
|
474
|
+
case AST_NODE_TYPES.NewExpression: return parent.callee !== node;
|
|
459
475
|
case AST_NODE_TYPES.SequenceExpression:
|
|
460
|
-
if (parent
|
|
476
|
+
if (parent.expressions[parent.expressions.length - 1] !== node) return false;
|
|
461
477
|
break;
|
|
462
|
-
default: if (parent
|
|
478
|
+
default: if (parent.type.endsWith("Statement") || parent.type.endsWith("Declaration")) return true;
|
|
463
479
|
}
|
|
464
|
-
node = parent
|
|
465
|
-
({parent
|
|
480
|
+
node = parent;
|
|
481
|
+
({parent} = parent);
|
|
466
482
|
}
|
|
467
483
|
return false;
|
|
468
484
|
}
|
|
469
|
-
const funcNode = getUpperFunction(id
|
|
485
|
+
const funcNode = getUpperFunction(id);
|
|
470
486
|
return !!funcNode && isInside(funcNode, rightHandSideNode) && isStorableFunction(funcNode, rightHandSideNode);
|
|
471
487
|
}
|
|
472
488
|
const id = ref.identifier;
|
|
473
489
|
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
|
|
490
|
+
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 && isInside(id, rhsNode) && !isInsideOfStorableFunction(id, rhsNode));
|
|
475
491
|
}
|
|
476
492
|
const functionNodes = getFunctionDefinitions(variable);
|
|
477
493
|
const isFunctionDefinition = functionNodes.size > 0;
|
|
@@ -489,7 +505,6 @@ function isUsedVariable(variable) {
|
|
|
489
505
|
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
506
|
});
|
|
491
507
|
}
|
|
492
|
-
|
|
493
508
|
//#endregion
|
|
494
509
|
//#region src/rules/naming-convention/utils/enums.ts
|
|
495
510
|
const Selector = {
|
|
@@ -567,7 +582,6 @@ const UnderscoreOption = {
|
|
|
567
582
|
allowDouble: 5,
|
|
568
583
|
allowSingleOrDouble: 6
|
|
569
584
|
};
|
|
570
|
-
|
|
571
585
|
//#endregion
|
|
572
586
|
//#region src/rules/naming-convention/utils/shared.ts
|
|
573
587
|
function isMetaSelector(selector) {
|
|
@@ -580,13 +594,15 @@ function selectorTypeToMessageString(selectorType) {
|
|
|
580
594
|
const notCamelCase = selectorType.replaceAll(/([A-Z])/g, " $1");
|
|
581
595
|
return notCamelCase.charAt(0).toUpperCase() + notCamelCase.slice(1);
|
|
582
596
|
}
|
|
583
|
-
|
|
584
597
|
//#endregion
|
|
585
598
|
//#region src/rules/naming-convention/utils/format.ts
|
|
586
|
-
function
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
599
|
+
function isUppercaseChar(char) {
|
|
600
|
+
return char === char.toUpperCase() && char !== char.toLowerCase();
|
|
601
|
+
}
|
|
602
|
+
function hasStrictCamelHumps(name, isUpper) {
|
|
603
|
+
if (name.startsWith("_")) return false;
|
|
604
|
+
for (let index = 1; index < name.length; ++index) {
|
|
605
|
+
const char = name[index];
|
|
590
606
|
if (char === "_") return false;
|
|
591
607
|
if (isUpper === isUppercaseChar(char)) {
|
|
592
608
|
if (isUpper) return false;
|
|
@@ -594,41 +610,38 @@ function hasStrictCamelHumps(name$1, isUpper) {
|
|
|
594
610
|
}
|
|
595
611
|
return true;
|
|
596
612
|
}
|
|
597
|
-
function isCamelCase(name
|
|
598
|
-
return name
|
|
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);
|
|
613
|
+
function isCamelCase(name) {
|
|
614
|
+
return name.length === 0 || name[0] === name[0]?.toLowerCase() && !name.includes("_");
|
|
614
615
|
}
|
|
615
|
-
function
|
|
616
|
-
return
|
|
616
|
+
function isPascalCase(name) {
|
|
617
|
+
return name.length === 0 || name[0] === name[0]?.toUpperCase() && !name.includes("_");
|
|
617
618
|
}
|
|
618
619
|
/**
|
|
619
620
|
* Check for leading trailing and adjacent underscores.
|
|
620
621
|
* @param name - The name to check.
|
|
621
622
|
* @returns True if the underscores are valid.
|
|
622
623
|
*/
|
|
623
|
-
function validateUnderscores(name
|
|
624
|
-
if (name
|
|
624
|
+
function validateUnderscores(name) {
|
|
625
|
+
if (name.startsWith("_")) return false;
|
|
625
626
|
let wasUnderscore = false;
|
|
626
|
-
for (let index = 1; index < name
|
|
627
|
+
for (let index = 1; index < name.length; ++index) if (name[index] === "_") {
|
|
627
628
|
if (wasUnderscore) return false;
|
|
628
629
|
wasUnderscore = true;
|
|
629
630
|
} else wasUnderscore = false;
|
|
630
631
|
return !wasUnderscore;
|
|
631
632
|
}
|
|
633
|
+
function isSnakeCase(name) {
|
|
634
|
+
return name.length === 0 || name === name.toLowerCase() && validateUnderscores(name);
|
|
635
|
+
}
|
|
636
|
+
function isStrictCamelCase(name) {
|
|
637
|
+
return name.length === 0 || name[0] === name[0]?.toLowerCase() && hasStrictCamelHumps(name, false);
|
|
638
|
+
}
|
|
639
|
+
function isStrictPascalCase(name) {
|
|
640
|
+
return name.length === 0 || name[0] === name[0]?.toUpperCase() && hasStrictCamelHumps(name, true);
|
|
641
|
+
}
|
|
642
|
+
function isUpperCase(name) {
|
|
643
|
+
return name.length === 0 || name === name.toUpperCase() && validateUnderscores(name);
|
|
644
|
+
}
|
|
632
645
|
const FormatCheckersMap = {
|
|
633
646
|
[PredefinedFormat.camelCase]: isCamelCase,
|
|
634
647
|
[PredefinedFormat.PascalCase]: isPascalCase,
|
|
@@ -637,12 +650,11 @@ const FormatCheckersMap = {
|
|
|
637
650
|
[PredefinedFormat.StrictPascalCase]: isStrictPascalCase,
|
|
638
651
|
[PredefinedFormat.UPPER_CASE]: isUpperCase
|
|
639
652
|
};
|
|
640
|
-
|
|
641
653
|
//#endregion
|
|
642
654
|
//#region src/rules/naming-convention/utils/validator.ts
|
|
643
655
|
function createValidator(type, context, allConfigs) {
|
|
644
656
|
const selectorType = Selector[type];
|
|
645
|
-
const configs
|
|
657
|
+
const configs = allConfigs.filter((configItem) => {
|
|
646
658
|
return (configItem.selector & selectorType) !== 0 || configItem.selector === MetaSelector.default;
|
|
647
659
|
}).sort((a, b) => {
|
|
648
660
|
if (a.selector === b.selector) return b.modifierWeight - a.modifierWeight;
|
|
@@ -658,53 +670,53 @@ function createValidator(type, context, allConfigs) {
|
|
|
658
670
|
});
|
|
659
671
|
return (node, modifiers = /* @__PURE__ */ new Set()) => {
|
|
660
672
|
const originalName = node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`;
|
|
661
|
-
for (const config of configs
|
|
673
|
+
for (const config of configs) {
|
|
662
674
|
if (config.filter?.regex.test(originalName) !== config.filter?.match) continue;
|
|
663
675
|
if (config.modifiers?.some((modifier) => !modifiers.has(modifier)) === true) continue;
|
|
664
676
|
if (!isCorrectType(node, config, context, selectorType)) continue;
|
|
665
|
-
let name
|
|
666
|
-
name
|
|
677
|
+
let name = originalName;
|
|
678
|
+
name = validateUnderscore({
|
|
679
|
+
name,
|
|
667
680
|
config,
|
|
668
|
-
name: name$1,
|
|
669
681
|
node,
|
|
670
682
|
originalName,
|
|
671
683
|
position: "leading"
|
|
672
684
|
});
|
|
673
|
-
if (name
|
|
674
|
-
name
|
|
685
|
+
if (name === void 0) return;
|
|
686
|
+
name = validateUnderscore({
|
|
687
|
+
name,
|
|
675
688
|
config,
|
|
676
|
-
name: name$1,
|
|
677
689
|
node,
|
|
678
690
|
originalName,
|
|
679
691
|
position: "trailing"
|
|
680
692
|
});
|
|
681
|
-
if (name
|
|
682
|
-
name
|
|
693
|
+
if (name === void 0) return;
|
|
694
|
+
name = validateAffix({
|
|
695
|
+
name,
|
|
683
696
|
config,
|
|
684
|
-
name: name$1,
|
|
685
697
|
node,
|
|
686
698
|
originalName,
|
|
687
699
|
position: "prefix"
|
|
688
700
|
});
|
|
689
|
-
if (name
|
|
690
|
-
name
|
|
701
|
+
if (name === void 0) return;
|
|
702
|
+
name = validateAffix({
|
|
703
|
+
name,
|
|
691
704
|
config,
|
|
692
|
-
name: name$1,
|
|
693
705
|
node,
|
|
694
706
|
originalName,
|
|
695
707
|
position: "suffix"
|
|
696
708
|
});
|
|
697
|
-
if (name
|
|
709
|
+
if (name === void 0) return;
|
|
698
710
|
if (!validateCustom({
|
|
711
|
+
name,
|
|
699
712
|
config,
|
|
700
|
-
name: name$1,
|
|
701
713
|
node,
|
|
702
714
|
originalName
|
|
703
715
|
})) return;
|
|
704
716
|
if (!validatePredefinedFormat({
|
|
717
|
+
name,
|
|
705
718
|
config,
|
|
706
719
|
modifiers,
|
|
707
|
-
name: name$1,
|
|
708
720
|
node,
|
|
709
721
|
originalName
|
|
710
722
|
})) return;
|
|
@@ -716,10 +728,10 @@ function createValidator(type, context, allConfigs) {
|
|
|
716
728
|
if (custom?.match === true) regexMatch = "match";
|
|
717
729
|
else if (custom?.match === false) regexMatch = "not match";
|
|
718
730
|
return {
|
|
731
|
+
name: originalName,
|
|
719
732
|
affixes: affixes?.join(", "),
|
|
720
733
|
count,
|
|
721
734
|
formats: formats?.map((formatItem) => PredefinedFormatValueToKey[formatItem]).join(", "),
|
|
722
|
-
name: originalName,
|
|
723
735
|
position,
|
|
724
736
|
processedName,
|
|
725
737
|
regex: custom?.regex.toString(),
|
|
@@ -727,24 +739,24 @@ function createValidator(type, context, allConfigs) {
|
|
|
727
739
|
type: selectorTypeToMessageString(type)
|
|
728
740
|
};
|
|
729
741
|
}
|
|
730
|
-
function validateUnderscore({
|
|
742
|
+
function validateUnderscore({ name, config, node, originalName, position }) {
|
|
731
743
|
const option = position === "leading" ? config.leadingUnderscore : config.trailingUnderscore;
|
|
732
|
-
if (!option) return name
|
|
733
|
-
const hasSingleUnderscore = position === "leading" ? () => name
|
|
734
|
-
const trimSingleUnderscore = position === "leading" ? () => name
|
|
735
|
-
const hasDoubleUnderscore = position === "leading" ? () => name
|
|
736
|
-
const trimDoubleUnderscore = position === "leading" ? () => name
|
|
744
|
+
if (!option) return name;
|
|
745
|
+
const hasSingleUnderscore = position === "leading" ? () => name.startsWith("_") : () => name.endsWith("_");
|
|
746
|
+
const trimSingleUnderscore = position === "leading" ? () => name.slice(1) : () => name.slice(0, -1);
|
|
747
|
+
const hasDoubleUnderscore = position === "leading" ? () => name.startsWith("__") : () => name.endsWith("__");
|
|
748
|
+
const trimDoubleUnderscore = position === "leading" ? () => name.slice(2) : () => name.slice(0, -2);
|
|
737
749
|
switch (option) {
|
|
738
750
|
case UnderscoreOption.allow:
|
|
739
751
|
if (hasSingleUnderscore()) return trimSingleUnderscore();
|
|
740
|
-
return name
|
|
752
|
+
return name;
|
|
741
753
|
case UnderscoreOption.allowDouble:
|
|
742
754
|
if (hasDoubleUnderscore()) return trimDoubleUnderscore();
|
|
743
|
-
return name
|
|
755
|
+
return name;
|
|
744
756
|
case UnderscoreOption.allowSingleOrDouble:
|
|
745
757
|
if (hasDoubleUnderscore()) return trimDoubleUnderscore();
|
|
746
758
|
if (hasSingleUnderscore()) return trimSingleUnderscore();
|
|
747
|
-
return name
|
|
759
|
+
return name;
|
|
748
760
|
case UnderscoreOption.forbid:
|
|
749
761
|
if (hasSingleUnderscore()) {
|
|
750
762
|
context.report({
|
|
@@ -758,7 +770,7 @@ function createValidator(type, context, allConfigs) {
|
|
|
758
770
|
});
|
|
759
771
|
return;
|
|
760
772
|
}
|
|
761
|
-
return name
|
|
773
|
+
return name;
|
|
762
774
|
case UnderscoreOption.require:
|
|
763
775
|
if (!hasSingleUnderscore()) {
|
|
764
776
|
context.report({
|
|
@@ -789,12 +801,12 @@ function createValidator(type, context, allConfigs) {
|
|
|
789
801
|
return trimDoubleUnderscore();
|
|
790
802
|
}
|
|
791
803
|
}
|
|
792
|
-
function validateAffix({
|
|
804
|
+
function validateAffix({ name, config, node, originalName, position }) {
|
|
793
805
|
const affixes = config[position];
|
|
794
|
-
if (!affixes || affixes.length === 0) return name
|
|
806
|
+
if (!affixes || affixes.length === 0) return name;
|
|
795
807
|
for (const affix of affixes) {
|
|
796
|
-
const hasAffix = position === "prefix" ? name
|
|
797
|
-
const trimAffix = position === "prefix" ? () => name
|
|
808
|
+
const hasAffix = position === "prefix" ? name.startsWith(affix) : name.endsWith(affix);
|
|
809
|
+
const trimAffix = position === "prefix" ? () => name.slice(affix.length) : () => name.slice(0, -affix.length);
|
|
798
810
|
if (hasAffix) return trimAffix();
|
|
799
811
|
}
|
|
800
812
|
context.report({
|
|
@@ -807,10 +819,10 @@ function createValidator(type, context, allConfigs) {
|
|
|
807
819
|
node
|
|
808
820
|
});
|
|
809
821
|
}
|
|
810
|
-
function validateCustom({
|
|
822
|
+
function validateCustom({ name, config, node, originalName }) {
|
|
811
823
|
const { custom } = config;
|
|
812
824
|
if (!custom) return true;
|
|
813
|
-
const result = custom.regex.test(name
|
|
825
|
+
const result = custom.regex.test(name);
|
|
814
826
|
if (custom.match && result) return true;
|
|
815
827
|
if (!custom.match && !result) return true;
|
|
816
828
|
context.report({
|
|
@@ -823,20 +835,20 @@ function createValidator(type, context, allConfigs) {
|
|
|
823
835
|
});
|
|
824
836
|
return false;
|
|
825
837
|
}
|
|
826
|
-
function validatePredefinedFormat({ config, modifiers,
|
|
838
|
+
function validatePredefinedFormat({ name, config, modifiers, node, originalName }) {
|
|
827
839
|
const formats = config.format;
|
|
828
840
|
if (!formats || formats.length === 0) return true;
|
|
829
841
|
if (!modifiers.has(Modifier.requiresQuotes)) for (const format of formats) {
|
|
830
842
|
const checker = FormatCheckersMap[format];
|
|
831
|
-
if (checker(name
|
|
843
|
+
if (checker(name)) return true;
|
|
832
844
|
}
|
|
833
845
|
context.report({
|
|
834
846
|
data: formatReportData({
|
|
835
847
|
formats,
|
|
836
848
|
originalName,
|
|
837
|
-
processedName: name
|
|
849
|
+
processedName: name
|
|
838
850
|
}),
|
|
839
|
-
messageId: originalName === name
|
|
851
|
+
messageId: originalName === name ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed",
|
|
840
852
|
node
|
|
841
853
|
});
|
|
842
854
|
return false;
|
|
@@ -859,19 +871,15 @@ function isCorrectType(node, config, context, selector) {
|
|
|
859
871
|
break;
|
|
860
872
|
case TypeModifier.boolean:
|
|
861
873
|
case TypeModifier.number:
|
|
862
|
-
case TypeModifier.string:
|
|
863
|
-
|
|
864
|
-
const allowedTypeString = TypeModifierValueToKey[allowedType];
|
|
865
|
-
if (typeString === allowedTypeString) return true;
|
|
874
|
+
case TypeModifier.string:
|
|
875
|
+
if (checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type))) === TypeModifierValueToKey[allowedType]) return true;
|
|
866
876
|
break;
|
|
867
|
-
}
|
|
868
877
|
case TypeModifier.function:
|
|
869
878
|
if (isAllTypesMatch(type, (inner) => inner.getCallSignatures().length > 0)) return true;
|
|
870
879
|
break;
|
|
871
880
|
}
|
|
872
881
|
return false;
|
|
873
882
|
}
|
|
874
|
-
|
|
875
883
|
//#endregion
|
|
876
884
|
//#region src/rules/naming-convention/utils/parse-options.ts
|
|
877
885
|
function parseOptions(context) {
|
|
@@ -911,7 +919,6 @@ function normalizeOption(option) {
|
|
|
911
919
|
};
|
|
912
920
|
});
|
|
913
921
|
}
|
|
914
|
-
|
|
915
922
|
//#endregion
|
|
916
923
|
//#region src/rules/naming-convention/utils/schema.ts
|
|
917
924
|
const $DEFS = {
|
|
@@ -1182,11 +1189,10 @@ const SCHEMA = {
|
|
|
1182
1189
|
] },
|
|
1183
1190
|
type: "array"
|
|
1184
1191
|
};
|
|
1185
|
-
|
|
1186
1192
|
//#endregion
|
|
1187
1193
|
//#region src/rules/naming-convention/rule.ts
|
|
1188
|
-
const RULE_NAME = "naming-convention";
|
|
1189
|
-
const messages = {
|
|
1194
|
+
const RULE_NAME$2 = "naming-convention";
|
|
1195
|
+
const messages$2 = {
|
|
1190
1196
|
doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
|
|
1191
1197
|
doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
|
|
1192
1198
|
missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
|
|
@@ -1216,7 +1222,7 @@ const camelCaseNamingConfig = [
|
|
|
1216
1222
|
selector: "typeLike"
|
|
1217
1223
|
}
|
|
1218
1224
|
];
|
|
1219
|
-
function create(contextWithoutDefaults) {
|
|
1225
|
+
function create$2(contextWithoutDefaults) {
|
|
1220
1226
|
const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
|
|
1221
1227
|
const validators = parseOptions(context);
|
|
1222
1228
|
const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
|
|
@@ -1237,11 +1243,11 @@ function create(contextWithoutDefaults) {
|
|
|
1237
1243
|
return modifiers;
|
|
1238
1244
|
}
|
|
1239
1245
|
const { unusedVariables } = collectVariables(context);
|
|
1240
|
-
function isUnused(name
|
|
1246
|
+
function isUnused(name, initialScope) {
|
|
1241
1247
|
let variable = null;
|
|
1242
1248
|
let scope = initialScope;
|
|
1243
1249
|
while (scope) {
|
|
1244
|
-
variable = scope.set.get(name
|
|
1250
|
+
variable = scope.set.get(name) ?? null;
|
|
1245
1251
|
if (variable) break;
|
|
1246
1252
|
scope = scope.upper;
|
|
1247
1253
|
}
|
|
@@ -1323,22 +1329,19 @@ function create(contextWithoutDefaults) {
|
|
|
1323
1329
|
const selectors = {
|
|
1324
1330
|
":matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
|
|
1325
1331
|
handler: (node, validator) => {
|
|
1326
|
-
|
|
1327
|
-
handleMember(validator, node, modifiers);
|
|
1332
|
+
handleMember(validator, node, getMemberModifiers(node));
|
|
1328
1333
|
},
|
|
1329
1334
|
validator: validators.classProperty
|
|
1330
1335
|
},
|
|
1331
1336
|
":not(ObjectPattern) > Property[computed = false][kind = \"init\"][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
|
|
1332
1337
|
handler: (node, validator) => {
|
|
1333
|
-
|
|
1334
|
-
handleMember(validator, node, modifiers);
|
|
1338
|
+
handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
|
|
1335
1339
|
},
|
|
1336
1340
|
validator: validators.objectLiteralProperty
|
|
1337
1341
|
},
|
|
1338
1342
|
[["TSMethodSignature[computed = false]", "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type = \"TSFunctionType\"]"].join(", ")]: {
|
|
1339
1343
|
handler: (node, validator) => {
|
|
1340
|
-
|
|
1341
|
-
handleMember(validator, node, modifiers);
|
|
1344
|
+
handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
|
|
1342
1345
|
},
|
|
1343
1346
|
validator: validators.typeMethod
|
|
1344
1347
|
},
|
|
@@ -1358,8 +1361,7 @@ function create(contextWithoutDefaults) {
|
|
|
1358
1361
|
},
|
|
1359
1362
|
[["MethodDefinition[computed = false]:matches([kind = \"get\"], [kind = \"set\"])", "TSAbstractMethodDefinition[computed = false]:matches([kind=\"get\"], [kind=\"set\"])"].join(", ")]: {
|
|
1360
1363
|
handler: (node, validator) => {
|
|
1361
|
-
|
|
1362
|
-
handleMember(validator, node, modifiers);
|
|
1364
|
+
handleMember(validator, node, getMemberModifiers(node));
|
|
1363
1365
|
},
|
|
1364
1366
|
validator: validators.classicAccessor
|
|
1365
1367
|
},
|
|
@@ -1369,7 +1371,7 @@ function create(contextWithoutDefaults) {
|
|
|
1369
1371
|
"Property[computed = false][kind = \"init\"][value.type = \"TSEmptyBodyFunctionExpression\"]"
|
|
1370
1372
|
].join(", ")]: {
|
|
1371
1373
|
handler: (node, validator) => {
|
|
1372
|
-
const modifiers = new Set([Modifier.public]);
|
|
1374
|
+
const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
|
|
1373
1375
|
if (isAsyncMemberOrProperty(node)) modifiers.add(Modifier.async);
|
|
1374
1376
|
handleMember(validator, node, modifiers);
|
|
1375
1377
|
},
|
|
@@ -1377,14 +1379,13 @@ function create(contextWithoutDefaults) {
|
|
|
1377
1379
|
},
|
|
1378
1380
|
[[AST_NODE_TYPES.AccessorProperty, AST_NODE_TYPES.TSAbstractAccessorProperty].join(", ")]: {
|
|
1379
1381
|
handler: (node, validator) => {
|
|
1380
|
-
|
|
1381
|
-
handleMember(validator, node, modifiers);
|
|
1382
|
+
handleMember(validator, node, getMemberModifiers(node));
|
|
1382
1383
|
},
|
|
1383
1384
|
validator: validators.autoAccessor
|
|
1384
1385
|
},
|
|
1385
1386
|
"ClassDeclaration, ClassExpression": {
|
|
1386
1387
|
handler: (node, validator) => {
|
|
1387
|
-
const {
|
|
1388
|
+
const { id, abstract } = node;
|
|
1388
1389
|
if (id === null) return;
|
|
1389
1390
|
const modifiers = /* @__PURE__ */ new Set();
|
|
1390
1391
|
const scope = context.sourceCode.getScope(node).upper;
|
|
@@ -1444,8 +1445,7 @@ function create(contextWithoutDefaults) {
|
|
|
1444
1445
|
},
|
|
1445
1446
|
"Property[computed = false]:matches([kind = \"get\"], [kind = \"set\"])": {
|
|
1446
1447
|
handler: (node, validator) => {
|
|
1447
|
-
|
|
1448
|
-
handleMember(validator, node, modifiers);
|
|
1448
|
+
handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
|
|
1449
1449
|
},
|
|
1450
1450
|
validator: validators.classicAccessor
|
|
1451
1451
|
},
|
|
@@ -1488,7 +1488,7 @@ function create(contextWithoutDefaults) {
|
|
|
1488
1488
|
},
|
|
1489
1489
|
"TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type != \"TSFunctionType\"]": {
|
|
1490
1490
|
handler: (node, validator) => {
|
|
1491
|
-
const modifiers = new Set([Modifier.public]);
|
|
1491
|
+
const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
|
|
1492
1492
|
if (node.readonly) modifiers.add(Modifier.readonly);
|
|
1493
1493
|
handleMember(validator, node, modifiers);
|
|
1494
1494
|
},
|
|
@@ -1542,7 +1542,8 @@ function create(contextWithoutDefaults) {
|
|
|
1542
1542
|
}));
|
|
1543
1543
|
}
|
|
1544
1544
|
const namingConvention = createEslintRule({
|
|
1545
|
-
|
|
1545
|
+
name: RULE_NAME$2,
|
|
1546
|
+
create: create$2,
|
|
1546
1547
|
defaultOptions: camelCaseNamingConfig,
|
|
1547
1548
|
meta: {
|
|
1548
1549
|
docs: {
|
|
@@ -1552,21 +1553,22 @@ const namingConvention = createEslintRule({
|
|
|
1552
1553
|
},
|
|
1553
1554
|
fixable: void 0,
|
|
1554
1555
|
hasSuggestions: false,
|
|
1555
|
-
messages,
|
|
1556
|
+
messages: messages$2,
|
|
1556
1557
|
schema: SCHEMA,
|
|
1557
1558
|
type: "suggestion"
|
|
1558
|
-
}
|
|
1559
|
-
name: RULE_NAME
|
|
1559
|
+
}
|
|
1560
1560
|
});
|
|
1561
1561
|
function getIdentifiersFromPattern(pattern) {
|
|
1562
1562
|
const identifiers = [];
|
|
1563
|
-
new PatternVisitor({}, pattern, (id) =>
|
|
1563
|
+
new PatternVisitor({}, pattern, (id) => {
|
|
1564
|
+
identifiers.push(id);
|
|
1565
|
+
}).visit(pattern);
|
|
1564
1566
|
return identifiers;
|
|
1565
1567
|
}
|
|
1566
|
-
function isExported(node, name
|
|
1568
|
+
function isExported(node, name, scope) {
|
|
1567
1569
|
if (node?.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || node?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) return true;
|
|
1568
1570
|
if (scope === null) return false;
|
|
1569
|
-
const variable = scope.set.get(name
|
|
1571
|
+
const variable = scope.set.get(name);
|
|
1570
1572
|
if (variable) for (const ref of variable.references) {
|
|
1571
1573
|
const refParent = ref.identifier.parent;
|
|
1572
1574
|
if (refParent.type === AST_NODE_TYPES.ExportDefaultDeclaration || refParent.type === AST_NODE_TYPES.ExportSpecifier) return true;
|
|
@@ -1578,10 +1580,285 @@ function isGlobal(scope) {
|
|
|
1578
1580
|
return scope.type === TSESLint.Scope.ScopeType.global || scope.type === TSESLint.Scope.ScopeType.module;
|
|
1579
1581
|
}
|
|
1580
1582
|
function requiresQuoting$1(node, target) {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
+
return requiresQuoting(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target);
|
|
1584
|
+
}
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region src/rules/toml-sort-keys/rule.ts
|
|
1587
|
+
const RULE_NAME$1 = "toml-sort-keys";
|
|
1588
|
+
const MESSAGE_ID = "unsorted";
|
|
1589
|
+
const messages$1 = { [MESSAGE_ID]: "Expected {{target}} to follow the configured order." };
|
|
1590
|
+
const schema = {
|
|
1591
|
+
items: {
|
|
1592
|
+
additionalProperties: false,
|
|
1593
|
+
properties: {
|
|
1594
|
+
order: { oneOf: [{
|
|
1595
|
+
items: { type: "string" },
|
|
1596
|
+
type: "array"
|
|
1597
|
+
}, {
|
|
1598
|
+
additionalProperties: false,
|
|
1599
|
+
properties: {
|
|
1600
|
+
caseSensitive: { type: "boolean" },
|
|
1601
|
+
natural: { type: "boolean" },
|
|
1602
|
+
type: {
|
|
1603
|
+
enum: ["asc", "desc"],
|
|
1604
|
+
type: "string"
|
|
1605
|
+
}
|
|
1606
|
+
},
|
|
1607
|
+
type: "object"
|
|
1608
|
+
}] },
|
|
1609
|
+
pathPattern: { type: "string" }
|
|
1610
|
+
},
|
|
1611
|
+
required: ["order", "pathPattern"],
|
|
1612
|
+
type: "object"
|
|
1613
|
+
},
|
|
1614
|
+
type: "array"
|
|
1615
|
+
};
|
|
1616
|
+
function isTable(entry) {
|
|
1617
|
+
return entry.type === "TOMLTable";
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* The dotted name of an entry, e.g. `settings`, `settings.node`, `_.path`.
|
|
1621
|
+
*
|
|
1622
|
+
* @param entry - The table header or key-value entry.
|
|
1623
|
+
* @returns The dotted key path as a string.
|
|
1624
|
+
*/
|
|
1625
|
+
function nameOf(entry) {
|
|
1626
|
+
return getStaticTOMLValue(entry.key).join(".");
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Builds a rank function from an explicit order list. An entry matches an order
|
|
1630
|
+
* item when it equals it or is a dotted child of it (`settings.node` matches
|
|
1631
|
+
* `settings`), which keeps sub-tables grouped under their parent's slot.
|
|
1632
|
+
*
|
|
1633
|
+
* @param order - The configured names in their desired order.
|
|
1634
|
+
* @returns A function giving a name's rank, or `Infinity` when unlisted.
|
|
1635
|
+
*/
|
|
1636
|
+
function makeRank(order) {
|
|
1637
|
+
return (name) => {
|
|
1638
|
+
for (const [index, item] of order.entries()) if (name === item || name.startsWith(`${item}.`)) return index;
|
|
1639
|
+
return Number.POSITIVE_INFINITY;
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function makeFallback(config) {
|
|
1643
|
+
const { caseSensitive = true, natural = false, type = "asc" } = config;
|
|
1644
|
+
const sensitivity = caseSensitive ? "variant" : "accent";
|
|
1645
|
+
return (a, b) => {
|
|
1646
|
+
const result = a.localeCompare(b, "en", {
|
|
1647
|
+
numeric: natural,
|
|
1648
|
+
sensitivity
|
|
1649
|
+
});
|
|
1650
|
+
return type === "desc" ? -result : result;
|
|
1651
|
+
};
|
|
1583
1652
|
}
|
|
1584
|
-
|
|
1653
|
+
/**
|
|
1654
|
+
* Builds a comparator for one table body. Explicit-order entries sort first by
|
|
1655
|
+
* their listed position, then unlisted entries fall back to a natural/asc sort
|
|
1656
|
+
* (mirroring `yaml/sort-keys`). At the top level, bare key-values are always
|
|
1657
|
+
* kept before any `[table]` header, since TOML would otherwise re-scope them.
|
|
1658
|
+
*
|
|
1659
|
+
* @param order - The order configuration for this table's path.
|
|
1660
|
+
* @param isTopLevel - Whether the body is the top-level table.
|
|
1661
|
+
* @returns A comparator over two entries.
|
|
1662
|
+
*/
|
|
1663
|
+
function makeComparator(order, isTopLevel) {
|
|
1664
|
+
const rank = Array.isArray(order) ? makeRank(order) : void 0;
|
|
1665
|
+
const fallback = makeFallback(Array.isArray(order) ? {
|
|
1666
|
+
natural: true,
|
|
1667
|
+
type: "asc"
|
|
1668
|
+
} : order);
|
|
1669
|
+
return (a, b) => {
|
|
1670
|
+
if (isTopLevel) {
|
|
1671
|
+
const aRank = isTable(a) ? 1 : 0;
|
|
1672
|
+
const bRank = isTable(b) ? 1 : 0;
|
|
1673
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
1674
|
+
}
|
|
1675
|
+
if (rank !== void 0) {
|
|
1676
|
+
const aRank = rank(nameOf(a));
|
|
1677
|
+
const bRank = rank(nameOf(b));
|
|
1678
|
+
if (aRank !== bRank) {
|
|
1679
|
+
if (aRank === Number.POSITIVE_INFINITY) return 1;
|
|
1680
|
+
if (bRank === Number.POSITIVE_INFINITY) return -1;
|
|
1681
|
+
return aRank - bRank;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return fallback(nameOf(a), nameOf(b));
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function create$1(context) {
|
|
1688
|
+
const { options, sourceCode } = context;
|
|
1689
|
+
if (sourceCode.parserServices.isTOML !== true) return {};
|
|
1690
|
+
const specs = options.map(({ order, pathPattern }) => {
|
|
1691
|
+
return {
|
|
1692
|
+
order,
|
|
1693
|
+
pattern: new RegExp(pathPattern, "u")
|
|
1694
|
+
};
|
|
1695
|
+
});
|
|
1696
|
+
if (specs.length === 0) return {};
|
|
1697
|
+
function resolveOrder(path) {
|
|
1698
|
+
for (const spec of specs) if (spec.pattern.test(path)) return spec.order;
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* A comment counts as attached to an entry when it sits on its own line
|
|
1702
|
+
* directly above it (no blank line, no trailing code). Such comments travel
|
|
1703
|
+
* with the entry when it moves.
|
|
1704
|
+
*
|
|
1705
|
+
* @param comment - The comment to classify.
|
|
1706
|
+
* @returns Whether the comment stands alone on its line.
|
|
1707
|
+
*/
|
|
1708
|
+
function isOwnLineComment(comment) {
|
|
1709
|
+
const before = sourceCode.getTokenBefore(comment, { includeComments: true });
|
|
1710
|
+
return before === null || before.loc.end.line < comment.loc.start.line;
|
|
1711
|
+
}
|
|
1712
|
+
function leadingStart(entry) {
|
|
1713
|
+
const comments = sourceCode.getCommentsBefore(entry);
|
|
1714
|
+
let start = entry.range[0];
|
|
1715
|
+
let boundaryLine = entry.loc.start.line;
|
|
1716
|
+
for (let index = comments.length - 1; index >= 0; index -= 1) {
|
|
1717
|
+
const comment = comments[index];
|
|
1718
|
+
if (comment === void 0) break;
|
|
1719
|
+
if (comment.loc.end.line !== boundaryLine - 1 || !isOwnLineComment(comment)) break;
|
|
1720
|
+
start = comment.range[0];
|
|
1721
|
+
boundaryLine = comment.loc.start.line;
|
|
1722
|
+
}
|
|
1723
|
+
return start;
|
|
1724
|
+
}
|
|
1725
|
+
function trailingEnd(entry) {
|
|
1726
|
+
const [comment] = sourceCode.getCommentsAfter(entry);
|
|
1727
|
+
if (comment?.loc.start.line === entry.loc.end.line) return comment.range[1];
|
|
1728
|
+
return entry.range[1];
|
|
1729
|
+
}
|
|
1730
|
+
function buildFix(entries, target) {
|
|
1731
|
+
const text = sourceCode.getText();
|
|
1732
|
+
const blocks = entries.map((entry) => {
|
|
1733
|
+
return {
|
|
1734
|
+
end: trailingEnd(entry),
|
|
1735
|
+
entry,
|
|
1736
|
+
start: leadingStart(entry)
|
|
1737
|
+
};
|
|
1738
|
+
});
|
|
1739
|
+
for (let index = 1; index < blocks.length; index += 1) {
|
|
1740
|
+
const previous = blocks[index - 1];
|
|
1741
|
+
const current = blocks[index];
|
|
1742
|
+
if (previous === void 0 || current === void 0) return;
|
|
1743
|
+
if (/\S/u.test(text.slice(previous.end, current.start))) return;
|
|
1744
|
+
}
|
|
1745
|
+
const first = blocks[0];
|
|
1746
|
+
const last = blocks[blocks.length - 1];
|
|
1747
|
+
if (first === void 0 || last === void 0) return;
|
|
1748
|
+
const textByEntry = new Map(blocks.map((block) => [block.entry, text.slice(block.start, block.end)]));
|
|
1749
|
+
const parts = [];
|
|
1750
|
+
for (const [index, entry] of target.entries()) {
|
|
1751
|
+
const previous = target[index - 1];
|
|
1752
|
+
if (previous !== void 0) parts.push(isTable(entry) || isTable(previous) ? "\n\n" : "\n");
|
|
1753
|
+
parts.push(textByEntry.get(entry) ?? "");
|
|
1754
|
+
}
|
|
1755
|
+
const sortedText = parts.join("");
|
|
1756
|
+
return (fixer) => fixer.replaceTextRange([first.start, last.end], sortedText);
|
|
1757
|
+
}
|
|
1758
|
+
function verify(path, body, isTopLevel) {
|
|
1759
|
+
if (body.length < 2) return;
|
|
1760
|
+
const order = resolveOrder(path);
|
|
1761
|
+
if (order === void 0) return;
|
|
1762
|
+
const entries = [...body];
|
|
1763
|
+
const target = [...entries].sort(makeComparator(order, isTopLevel));
|
|
1764
|
+
let outOfPlace;
|
|
1765
|
+
for (const [index, entry] of entries.entries()) if (entry !== target[index]) {
|
|
1766
|
+
outOfPlace = entry;
|
|
1767
|
+
break;
|
|
1768
|
+
}
|
|
1769
|
+
if (outOfPlace === void 0) return;
|
|
1770
|
+
context.report({
|
|
1771
|
+
data: { target: path === "" ? "top-level tables" : `keys in "${path}"` },
|
|
1772
|
+
fix: buildFix(entries, target),
|
|
1773
|
+
loc: outOfPlace.key.loc,
|
|
1774
|
+
messageId: MESSAGE_ID
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
return {
|
|
1778
|
+
TOMLTable(node) {
|
|
1779
|
+
verify(getStaticTOMLValue(node.key).join("."), node.body, false);
|
|
1780
|
+
},
|
|
1781
|
+
TOMLTopLevelTable(node) {
|
|
1782
|
+
verify("", node.body, true);
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
const tomlSortKeys = createEslintRule({
|
|
1787
|
+
name: RULE_NAME$1,
|
|
1788
|
+
create: create$1,
|
|
1789
|
+
defaultOptions: [],
|
|
1790
|
+
meta: {
|
|
1791
|
+
defaultOptions: [],
|
|
1792
|
+
docs: {
|
|
1793
|
+
description: "Enforce a configured sort order for TOML keys and tables",
|
|
1794
|
+
recommended: false,
|
|
1795
|
+
requiresTypeChecking: false
|
|
1796
|
+
},
|
|
1797
|
+
fixable: "code",
|
|
1798
|
+
hasSuggestions: false,
|
|
1799
|
+
messages: messages$1,
|
|
1800
|
+
schema,
|
|
1801
|
+
type: "layout"
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
//#endregion
|
|
1805
|
+
//#region src/rules/yaml-block-key-blank-lines/rule.ts
|
|
1806
|
+
const RULE_NAME = "yaml-block-key-blank-lines";
|
|
1807
|
+
const messages = { blankLine: "Expected {{count}} blank line(s) around this top-level key." };
|
|
1808
|
+
/**
|
|
1809
|
+
* Determines whether a pair value is a block collection (block mapping or block
|
|
1810
|
+
* sequence). Flow collections (`{ ... }` / `[ ... ]`) count as scalars.
|
|
1811
|
+
*
|
|
1812
|
+
* @param value - The value node of a YAML pair.
|
|
1813
|
+
* @returns True if the value is a block mapping or block sequence.
|
|
1814
|
+
*/
|
|
1815
|
+
function isBlock(value) {
|
|
1816
|
+
return value !== null && (value.type === "YAMLMapping" || value.type === "YAMLSequence") && value.style === "block";
|
|
1817
|
+
}
|
|
1818
|
+
function create(context) {
|
|
1819
|
+
const { sourceCode } = context;
|
|
1820
|
+
if (sourceCode.parserServices.isYAML !== true) return {};
|
|
1821
|
+
return { YAMLMapping(yamlNode) {
|
|
1822
|
+
if (yamlNode.parent.type !== "YAMLDocument") return;
|
|
1823
|
+
const { pairs } = yamlNode;
|
|
1824
|
+
for (let index = 1; index < pairs.length; index += 1) {
|
|
1825
|
+
const previous = pairs[index - 1];
|
|
1826
|
+
const current = pairs[index];
|
|
1827
|
+
if (previous === void 0 || current === void 0) continue;
|
|
1828
|
+
const left = sourceCode.getLastToken(previous);
|
|
1829
|
+
const right = sourceCode.getFirstToken(current);
|
|
1830
|
+
if (sourceCode.commentsExistBetween(left, right)) continue;
|
|
1831
|
+
const blanks = right.loc.start.line - left.loc.end.line - 1;
|
|
1832
|
+
const want = isBlock(previous.value) || isBlock(current.value) ? 1 : 0;
|
|
1833
|
+
if (blanks === want) continue;
|
|
1834
|
+
context.report({
|
|
1835
|
+
data: { count: want },
|
|
1836
|
+
fix(fixer) {
|
|
1837
|
+
return fixer.replaceTextRange([left.range[1], right.range[0]], "\n".repeat(want + 1));
|
|
1838
|
+
},
|
|
1839
|
+
loc: (current.key ?? current).loc,
|
|
1840
|
+
messageId: "blankLine"
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
} };
|
|
1844
|
+
}
|
|
1845
|
+
const yamlBlockKeyBlankLines = createEslintRule({
|
|
1846
|
+
name: RULE_NAME,
|
|
1847
|
+
create,
|
|
1848
|
+
defaultOptions: [],
|
|
1849
|
+
meta: {
|
|
1850
|
+
docs: {
|
|
1851
|
+
description: "Enforce blank lines around top-level YAML block collection keys",
|
|
1852
|
+
recommended: false,
|
|
1853
|
+
requiresTypeChecking: false
|
|
1854
|
+
},
|
|
1855
|
+
fixable: "whitespace",
|
|
1856
|
+
hasSuggestions: false,
|
|
1857
|
+
messages,
|
|
1858
|
+
schema: [],
|
|
1859
|
+
type: "layout"
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1585
1862
|
//#endregion
|
|
1586
1863
|
//#region src/plugin.ts
|
|
1587
1864
|
const PLUGIN_NAME = name.replace(/^eslint-plugin-/, "");
|
|
@@ -1600,23 +1877,24 @@ const plugin = {
|
|
|
1600
1877
|
name: PLUGIN_NAME,
|
|
1601
1878
|
version
|
|
1602
1879
|
},
|
|
1603
|
-
rules: {
|
|
1880
|
+
rules: {
|
|
1881
|
+
"naming-convention": namingConvention,
|
|
1882
|
+
"toml-sort-keys": tomlSortKeys,
|
|
1883
|
+
"yaml-block-key-blank-lines": yamlBlockKeyBlankLines
|
|
1884
|
+
}
|
|
1604
1885
|
};
|
|
1605
|
-
|
|
1606
|
-
|
|
1886
|
+
getRules(PLUGIN_NAME, plugin.rules);
|
|
1607
1887
|
//#endregion
|
|
1608
1888
|
//#region src/configs/index.ts
|
|
1609
1889
|
const configs = { recommended: {
|
|
1610
1890
|
plugins: { [PLUGIN_NAME]: plugin },
|
|
1611
1891
|
rules: {}
|
|
1612
1892
|
} };
|
|
1613
|
-
|
|
1614
1893
|
//#endregion
|
|
1615
1894
|
//#region src/index.ts
|
|
1616
1895
|
var src_default = {
|
|
1617
1896
|
...plugin,
|
|
1618
1897
|
configs
|
|
1619
1898
|
};
|
|
1620
|
-
|
|
1621
1899
|
//#endregion
|
|
1622
|
-
export { src_default as default };
|
|
1900
|
+
export { src_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-flawless",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Your ESLint plugin description",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -34,44 +34,47 @@
|
|
|
34
34
|
"*": "eslint --fix --cache"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@typescript-eslint/scope-manager": "8.
|
|
38
|
-
"@typescript-eslint/type-utils": "
|
|
39
|
-
"@typescript-eslint/utils": "8.
|
|
37
|
+
"@typescript-eslint/scope-manager": "8.62.1",
|
|
38
|
+
"@typescript-eslint/type-utils": "8.62.1",
|
|
39
|
+
"@typescript-eslint/utils": "8.62.1",
|
|
40
|
+
"toml-eslint-parser": "1.0.3"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@antfu/ni": "
|
|
43
|
-
"@isentinel/eslint-config": "
|
|
44
|
-
"@isentinel/tsconfig": "1.
|
|
43
|
+
"@antfu/ni": "30.2.0",
|
|
44
|
+
"@isentinel/eslint-config": "5.2.0",
|
|
45
|
+
"@isentinel/tsconfig": "1.2.0",
|
|
45
46
|
"@types/node": "24.0.13",
|
|
46
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
47
|
-
"@typescript-eslint/parser": "8.
|
|
48
|
-
"@vitest/eslint-plugin": "1.
|
|
49
|
-
"bumpp": "
|
|
50
|
-
"eslint": "9.
|
|
51
|
-
"eslint-doc-generator": "
|
|
52
|
-
"eslint-plugin-eslint-plugin": "7.
|
|
53
|
-
"eslint-plugin-n": "
|
|
54
|
-
"eslint-plugin-pnpm": "1.1
|
|
55
|
-
"eslint-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "8.62.1",
|
|
48
|
+
"@typescript-eslint/parser": "8.62.1",
|
|
49
|
+
"@vitest/eslint-plugin": "1.6.20",
|
|
50
|
+
"bumpp": "11.1.0",
|
|
51
|
+
"eslint": "9.39.4",
|
|
52
|
+
"eslint-doc-generator": "3.6.0",
|
|
53
|
+
"eslint-plugin-eslint-plugin": "7.4.1",
|
|
54
|
+
"eslint-plugin-n": "18.2.1",
|
|
55
|
+
"eslint-plugin-pnpm": "1.6.1",
|
|
56
|
+
"eslint-plugin-toml": "1.4.0",
|
|
57
|
+
"eslint-plugin-yml": "3.5.0",
|
|
58
|
+
"eslint-vitest-rule-tester": "3.1.0",
|
|
59
|
+
"jiti": "2.7.0",
|
|
60
|
+
"lint-staged": "17.0.8",
|
|
61
|
+
"publint": "0.3.21",
|
|
59
62
|
"simple-git-hooks": "2.13.1",
|
|
60
|
-
"ts-api-utils": "2.
|
|
61
|
-
"tsdown": "0.
|
|
62
|
-
"tsx": "4.
|
|
63
|
-
"typescript": "
|
|
64
|
-
"unplugin-unused": "0.5.
|
|
65
|
-
"vitest": "
|
|
63
|
+
"ts-api-utils": "2.5.0",
|
|
64
|
+
"tsdown": "0.22.3",
|
|
65
|
+
"tsx": "4.22.4",
|
|
66
|
+
"typescript": "6.0.3",
|
|
67
|
+
"unplugin-unused": "0.5.7",
|
|
68
|
+
"vitest": "4.1.9",
|
|
69
|
+
"yaml-eslint-parser": "2.0.0"
|
|
66
70
|
},
|
|
67
71
|
"peerDependencies": {
|
|
68
72
|
"eslint": ">=9.15.0"
|
|
69
73
|
},
|
|
70
74
|
"engines": {
|
|
71
|
-
"node": ">=
|
|
75
|
+
"node": ">=24.12.0"
|
|
72
76
|
},
|
|
73
77
|
"publishConfig": {
|
|
74
|
-
"access": "public",
|
|
75
78
|
"provenance": true
|
|
76
79
|
},
|
|
77
80
|
"scripts": {
|