eslint-plugin-flawless 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -171,12 +171,15 @@ pnpm eslint-docs
171
171
 
172
172
  <!-- begin auto-generated rules list -->
173
173
 
174
- 💭 Requires
175
- [type information](https://typescript-eslint.io/linting/typed-linting).
176
-
177
- | Name | Description | 💭 |
178
- | :---------------------------------------------------------------- | :---------------------------------------------------------- | :-- |
179
- | [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase | 💭 |
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
+ | [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
183
 
181
184
  <!-- end auto-generated rules list -->
182
185
 
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,8 +115,8 @@ interface NamingSelector {
117
115
  }
118
116
  //#endregion
119
117
  //#region src/rules/naming-convention/rule.d.ts
120
- type MessageIds$1 = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
121
- type Options$1 = Array<NamingSelector>;
118
+ type MessageIds = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
119
+ type Options = Array<NamingSelector>;
122
120
  //#endregion
123
121
  //#region src/plugin.d.ts
124
122
  declare const plugin: {
@@ -127,7 +125,12 @@ declare const plugin: {
127
125
  version: string;
128
126
  };
129
127
  rules: {
130
- "naming-convention": TSESLint.RuleModule<MessageIds$1, Options$1, PluginDocumentation, TSESLint.RuleListener>;
128
+ "naming-convention": TSESLint.RuleModule<MessageIds, Options, PluginDocumentation, TSESLint.RuleListener> & {
129
+ name: string;
130
+ };
131
+ "yaml-block-key-blank-lines": TSESLint.RuleModule<"blankLine", [], PluginDocumentation, TSESLint.RuleListener> & {
132
+ name: string;
133
+ };
131
134
  };
132
135
  };
133
136
  //#endregion
@@ -142,7 +145,12 @@ declare const _default: {
142
145
  version: string;
143
146
  };
144
147
  rules: {
145
- "naming-convention": _typescript_eslint_utils_ts_eslint0.RuleModule<MessageIds$1, Options$1, PluginDocumentation, _typescript_eslint_utils_ts_eslint0.RuleListener>;
148
+ "naming-convention": import("${configDir}").RuleModule<MessageIds, Options, PluginDocumentation, import("${configDir}").RuleListener> & {
149
+ name: string;
150
+ };
151
+ "yaml-block-key-blank-lines": import("${configDir}").RuleModule<"blankLine", [], PluginDocumentation, import("${configDir}").RuleListener> & {
152
+ name: string;
153
+ };
146
154
  };
147
155
  };
148
156
  };
@@ -154,10 +162,15 @@ declare const _default: {
154
162
  version: string;
155
163
  };
156
164
  rules: {
157
- "naming-convention": _typescript_eslint_utils_ts_eslint0.RuleModule<MessageIds$1, Options$1, PluginDocumentation, _typescript_eslint_utils_ts_eslint0.RuleListener>;
165
+ "naming-convention": import("${configDir}").RuleModule<MessageIds, Options, PluginDocumentation, import("${configDir}").RuleListener> & {
166
+ name: string;
167
+ };
168
+ "yaml-block-key-blank-lines": import("${configDir}").RuleModule<"blankLine", [], PluginDocumentation, import("${configDir}").RuleListener> & {
169
+ name: string;
170
+ };
158
171
  };
159
172
  };
160
- type RuleOptions = { [K in keyof RuleDefinitions]: RuleDefinitions[K]["defaultOptions"] };
173
+ type RuleOptions = { [K in keyof RuleDefinitions]: NonNullable<RuleDefinitions[K]["defaultOptions"]> };
161
174
  type Rules = { [K in keyof RuleOptions]: Linter.RuleEntry<RuleOptions[K]> };
162
175
  type RuleDefinitions = typeof plugin.rules;
163
176
  //#endregion
package/dist/index.mjs CHANGED
@@ -3,21 +3,33 @@ 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
-
7
6
  //#region package.json
8
7
  var name = "eslint-plugin-flawless";
9
- var version = "0.1.0";
8
+ var version = "0.1.1";
10
9
  var repository = {
11
10
  "url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
12
11
  "type": "git"
13
12
  };
14
-
15
13
  //#endregion
16
14
  //#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`;
15
+ const createRule = RuleCreator((name) => {
16
+ return `${repository.url.replace(/^git\+/, "").replace(/\.git$/, "")}/blob/v${version}/src/rules/${name}/documentation.md`;
19
17
  });
20
-
18
+ /**
19
+ * Creates a rule with a docs URL, allowing a rule's `create` to receive a
20
+ * context with a custom `sourceCode` type (such as a YAML source code). The
21
+ * `SourceCode` type parameter defaults to ESLint's `SourceCode`, so standard
22
+ * rules are unaffected.
23
+ *
24
+ * @template Options - The rule's options tuple.
25
+ * @template MessageIds - The rule's message identifiers.
26
+ * @template SourceCode - The source code type exposed on `context.sourceCode`.
27
+ * @param rule - The rule definition (meta, name, defaultOptions, create).
28
+ * @returns The created rule module.
29
+ */
30
+ function createEslintRule(rule) {
31
+ return createRule(rule);
32
+ }
21
33
  //#endregion
22
34
  //#region src/utils/is-type-import.ts
23
35
  /**
@@ -34,7 +46,6 @@ const createEslintRule = RuleCreator((name$1) => {
34
46
  function isTypeImport(definition) {
35
47
  return definition?.type === DefinitionType.ImportBinding && (definition.parent.importKind === "type" || definition.node.type === AST_NODE_TYPES.ImportSpecifier && definition.node.importKind === "type");
36
48
  }
37
-
38
49
  //#endregion
39
50
  //#region src/utils/reference-contains-type-query.ts
40
51
  /**
@@ -50,7 +61,6 @@ function referenceContainsTypeQuery(node) {
50
61
  default: return false;
51
62
  }
52
63
  }
53
-
54
64
  //#endregion
55
65
  //#region src/utils/collect-variables.ts
56
66
  /**
@@ -113,10 +123,15 @@ var UnusedVariablesVisitor = class extends Visitor {
113
123
  }
114
124
  TSParameterProperty(node) {
115
125
  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);
126
+ switch (node.parameter.type) {
127
+ case AST_NODE_TYPES.AssignmentPattern:
128
+ identifier = node.parameter.left;
129
+ break;
130
+ case AST_NODE_TYPES.Identifier:
131
+ identifier = node.parameter;
132
+ break;
133
+ }
134
+ this.markVariableAsUsed(identifier);
120
135
  }
121
136
  collectUnusedVariables({ scope, variables = {
122
137
  unusedVariables: /* @__PURE__ */ new Set(),
@@ -140,9 +155,9 @@ var UnusedVariablesVisitor = class extends Visitor {
140
155
  const scope = this.#scopeManager.acquire(node, inner);
141
156
  if (scope) {
142
157
  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;
158
+ const returnValue = scope.childScopes[0];
159
+ assert(returnValue, "Function expression name scope should have a child scope");
160
+ return returnValue;
146
161
  }
147
162
  return scope;
148
163
  }
@@ -157,19 +172,19 @@ var UnusedVariablesVisitor = class extends Visitor {
157
172
  variableOrIdentifierOrName.eslintUsed = true;
158
173
  return;
159
174
  }
160
- let name$1;
175
+ let name;
161
176
  let node;
162
177
  if (typeof variableOrIdentifierOrName === "string") {
163
- name$1 = variableOrIdentifierOrName;
178
+ name = variableOrIdentifierOrName;
164
179
  assert(parent, "Parent node is required when marking by name");
165
180
  node = parent;
166
181
  } else {
167
- ({name: name$1} = variableOrIdentifierOrName);
182
+ ({name} = variableOrIdentifierOrName);
168
183
  node = variableOrIdentifierOrName;
169
184
  }
170
185
  let currentScope = this.getScope(node);
171
186
  while (currentScope) {
172
- const variable = currentScope.variables.find((scopeVariable) => scopeVariable.name === name$1);
187
+ const variable = currentScope.variables.find((scopeVariable) => scopeVariable.name === name);
173
188
  if (variable) {
174
189
  variable.eslintUsed = true;
175
190
  return;
@@ -223,8 +238,8 @@ var UnusedVariablesVisitor = class extends Visitor {
223
238
  if (variable?.defs.length === 0) this.markVariableAsUsed(variable);
224
239
  }
225
240
  visitFunctionTypeSignature(node) {
226
- for (const parameter of node.params) this.visitPattern(parameter, (name$1) => {
227
- this.markVariableAsUsed(name$1);
241
+ for (const parameter of node.params) this.visitPattern(parameter, (name) => {
242
+ this.markVariableAsUsed(name);
228
243
  });
229
244
  }
230
245
  visitSetter(node) {
@@ -257,7 +272,7 @@ function isSelfReference(ref, nodes) {
257
272
  }
258
273
  return false;
259
274
  }
260
- const MERGEABLE_TYPES = new Set([
275
+ const MERGEABLE_TYPES = /* @__PURE__ */ new Set([
261
276
  AST_NODE_TYPES.ClassDeclaration,
262
277
  AST_NODE_TYPES.FunctionDeclaration,
263
278
  AST_NODE_TYPES.TSInterfaceDeclaration,
@@ -289,7 +304,7 @@ function isMergeableExported(variable) {
289
304
  }
290
305
  return false;
291
306
  }
292
- const LOGICAL_ASSIGNMENT_OPERATORS = new Set([
307
+ const LOGICAL_ASSIGNMENT_OPERATORS = /* @__PURE__ */ new Set([
293
308
  "&&=",
294
309
  "??=",
295
310
  "||="
@@ -409,7 +424,7 @@ function isUsedVariable(variable) {
409
424
  * @param rhsNode - The RHS node of the previous assignment.
410
425
  * @returns The reference is a read to update itself.
411
426
  */
412
- function isReadForItself(ref, rhsNode$1) {
427
+ function isReadForItself(ref, rhsNode) {
413
428
  /**
414
429
  * Checks whether a given Identifier node exists inside of a function node which can be used later.
415
430
  *
@@ -423,7 +438,7 @@ function isUsedVariable(variable) {
423
438
  * @param rightHandSideNode - The RHS node of the previous assignment.
424
439
  * @returns `true` if the `id` node exists inside of a function node which can be used later.
425
440
  */
426
- function isInsideOfStorableFunction(id$1, rightHandSideNode) {
441
+ function isInsideOfStorableFunction(id, rightHandSideNode) {
427
442
  /**
428
443
  * Finds a function node from ancestors of a node.
429
444
  * @param node - A start node to find.
@@ -446,32 +461,32 @@ function isUsedVariable(variable) {
446
461
  * - the funcNode is bound as an argument of a function call.
447
462
  * - the function is bound to a property and the object satisfies above conditions.
448
463
  */
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) {
464
+ function isStorableFunction(funcNode, storableRhsNode) {
465
+ let node = funcNode;
466
+ let { parent } = funcNode;
467
+ while (parent && isInside(parent, storableRhsNode)) {
468
+ switch (parent.type) {
454
469
  case AST_NODE_TYPES.AssignmentExpression:
455
470
  case AST_NODE_TYPES.TaggedTemplateExpression:
456
471
  case AST_NODE_TYPES.YieldExpression: return true;
457
472
  case AST_NODE_TYPES.CallExpression:
458
- case AST_NODE_TYPES.NewExpression: return parent$1.callee !== node;
473
+ case AST_NODE_TYPES.NewExpression: return parent.callee !== node;
459
474
  case AST_NODE_TYPES.SequenceExpression:
460
- if (parent$1.expressions[parent$1.expressions.length - 1] !== node) return false;
475
+ if (parent.expressions[parent.expressions.length - 1] !== node) return false;
461
476
  break;
462
- default: if (parent$1.type.endsWith("Statement") || parent$1.type.endsWith("Declaration")) return true;
477
+ default: if (parent.type.endsWith("Statement") || parent.type.endsWith("Declaration")) return true;
463
478
  }
464
- node = parent$1;
465
- ({parent: parent$1} = parent$1);
479
+ node = parent;
480
+ ({parent} = parent);
466
481
  }
467
482
  return false;
468
483
  }
469
- const funcNode = getUpperFunction(id$1);
484
+ const funcNode = getUpperFunction(id);
470
485
  return !!funcNode && isInside(funcNode, rightHandSideNode) && isStorableFunction(funcNode, rightHandSideNode);
471
486
  }
472
487
  const id = ref.identifier;
473
488
  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));
489
+ 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
490
  }
476
491
  const functionNodes = getFunctionDefinitions(variable);
477
492
  const isFunctionDefinition = functionNodes.size > 0;
@@ -489,7 +504,6 @@ function isUsedVariable(variable) {
489
504
  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
505
  });
491
506
  }
492
-
493
507
  //#endregion
494
508
  //#region src/rules/naming-convention/utils/enums.ts
495
509
  const Selector = {
@@ -567,7 +581,6 @@ const UnderscoreOption = {
567
581
  allowDouble: 5,
568
582
  allowSingleOrDouble: 6
569
583
  };
570
-
571
584
  //#endregion
572
585
  //#region src/rules/naming-convention/utils/shared.ts
573
586
  function isMetaSelector(selector) {
@@ -580,13 +593,15 @@ function selectorTypeToMessageString(selectorType) {
580
593
  const notCamelCase = selectorType.replaceAll(/([A-Z])/g, " $1");
581
594
  return notCamelCase.charAt(0).toUpperCase() + notCamelCase.slice(1);
582
595
  }
583
-
584
596
  //#endregion
585
597
  //#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];
598
+ function isUppercaseChar(char) {
599
+ return char === char.toUpperCase() && char !== char.toLowerCase();
600
+ }
601
+ function hasStrictCamelHumps(name, isUpper) {
602
+ if (name.startsWith("_")) return false;
603
+ for (let index = 1; index < name.length; ++index) {
604
+ const char = name[index];
590
605
  if (char === "_") return false;
591
606
  if (isUpper === isUppercaseChar(char)) {
592
607
  if (isUpper) return false;
@@ -594,41 +609,38 @@ function hasStrictCamelHumps(name$1, isUpper) {
594
609
  }
595
610
  return true;
596
611
  }
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);
612
+ function isCamelCase(name) {
613
+ return name.length === 0 || name[0] === name[0]?.toLowerCase() && !name.includes("_");
611
614
  }
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();
615
+ function isPascalCase(name) {
616
+ return name.length === 0 || name[0] === name[0]?.toUpperCase() && !name.includes("_");
617
617
  }
618
618
  /**
619
619
  * Check for leading trailing and adjacent underscores.
620
620
  * @param name - The name to check.
621
621
  * @returns True if the underscores are valid.
622
622
  */
623
- function validateUnderscores(name$1) {
624
- if (name$1.startsWith("_")) return false;
623
+ function validateUnderscores(name) {
624
+ if (name.startsWith("_")) return false;
625
625
  let wasUnderscore = false;
626
- for (let index = 1; index < name$1.length; ++index) if (name$1[index] === "_") {
626
+ for (let index = 1; index < name.length; ++index) if (name[index] === "_") {
627
627
  if (wasUnderscore) return false;
628
628
  wasUnderscore = true;
629
629
  } else wasUnderscore = false;
630
630
  return !wasUnderscore;
631
631
  }
632
+ function isSnakeCase(name) {
633
+ return name.length === 0 || name === name.toLowerCase() && validateUnderscores(name);
634
+ }
635
+ function isStrictCamelCase(name) {
636
+ return name.length === 0 || name[0] === name[0]?.toLowerCase() && hasStrictCamelHumps(name, false);
637
+ }
638
+ function isStrictPascalCase(name) {
639
+ return name.length === 0 || name[0] === name[0]?.toUpperCase() && hasStrictCamelHumps(name, true);
640
+ }
641
+ function isUpperCase(name) {
642
+ return name.length === 0 || name === name.toUpperCase() && validateUnderscores(name);
643
+ }
632
644
  const FormatCheckersMap = {
633
645
  [PredefinedFormat.camelCase]: isCamelCase,
634
646
  [PredefinedFormat.PascalCase]: isPascalCase,
@@ -637,12 +649,11 @@ const FormatCheckersMap = {
637
649
  [PredefinedFormat.StrictPascalCase]: isStrictPascalCase,
638
650
  [PredefinedFormat.UPPER_CASE]: isUpperCase
639
651
  };
640
-
641
652
  //#endregion
642
653
  //#region src/rules/naming-convention/utils/validator.ts
643
654
  function createValidator(type, context, allConfigs) {
644
655
  const selectorType = Selector[type];
645
- const configs$1 = allConfigs.filter((configItem) => {
656
+ const configs = allConfigs.filter((configItem) => {
646
657
  return (configItem.selector & selectorType) !== 0 || configItem.selector === MetaSelector.default;
647
658
  }).sort((a, b) => {
648
659
  if (a.selector === b.selector) return b.modifierWeight - a.modifierWeight;
@@ -658,53 +669,53 @@ function createValidator(type, context, allConfigs) {
658
669
  });
659
670
  return (node, modifiers = /* @__PURE__ */ new Set()) => {
660
671
  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) {
672
+ for (const config of configs) {
662
673
  if (config.filter?.regex.test(originalName) !== config.filter?.match) continue;
663
674
  if (config.modifiers?.some((modifier) => !modifiers.has(modifier)) === true) continue;
664
675
  if (!isCorrectType(node, config, context, selectorType)) continue;
665
- let name$1 = originalName;
666
- name$1 = validateUnderscore({
676
+ let name = originalName;
677
+ name = validateUnderscore({
678
+ name,
667
679
  config,
668
- name: name$1,
669
680
  node,
670
681
  originalName,
671
682
  position: "leading"
672
683
  });
673
- if (name$1 === void 0) return;
674
- name$1 = validateUnderscore({
684
+ if (name === void 0) return;
685
+ name = validateUnderscore({
686
+ name,
675
687
  config,
676
- name: name$1,
677
688
  node,
678
689
  originalName,
679
690
  position: "trailing"
680
691
  });
681
- if (name$1 === void 0) return;
682
- name$1 = validateAffix({
692
+ if (name === void 0) return;
693
+ name = validateAffix({
694
+ name,
683
695
  config,
684
- name: name$1,
685
696
  node,
686
697
  originalName,
687
698
  position: "prefix"
688
699
  });
689
- if (name$1 === void 0) return;
690
- name$1 = validateAffix({
700
+ if (name === void 0) return;
701
+ name = validateAffix({
702
+ name,
691
703
  config,
692
- name: name$1,
693
704
  node,
694
705
  originalName,
695
706
  position: "suffix"
696
707
  });
697
- if (name$1 === void 0) return;
708
+ if (name === void 0) return;
698
709
  if (!validateCustom({
710
+ name,
699
711
  config,
700
- name: name$1,
701
712
  node,
702
713
  originalName
703
714
  })) return;
704
715
  if (!validatePredefinedFormat({
716
+ name,
705
717
  config,
706
718
  modifiers,
707
- name: name$1,
708
719
  node,
709
720
  originalName
710
721
  })) return;
@@ -716,10 +727,10 @@ function createValidator(type, context, allConfigs) {
716
727
  if (custom?.match === true) regexMatch = "match";
717
728
  else if (custom?.match === false) regexMatch = "not match";
718
729
  return {
730
+ name: originalName,
719
731
  affixes: affixes?.join(", "),
720
732
  count,
721
733
  formats: formats?.map((formatItem) => PredefinedFormatValueToKey[formatItem]).join(", "),
722
- name: originalName,
723
734
  position,
724
735
  processedName,
725
736
  regex: custom?.regex.toString(),
@@ -727,24 +738,24 @@ function createValidator(type, context, allConfigs) {
727
738
  type: selectorTypeToMessageString(type)
728
739
  };
729
740
  }
730
- function validateUnderscore({ config, name: name$1, node, originalName, position }) {
741
+ function validateUnderscore({ name, config, node, originalName, position }) {
731
742
  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);
743
+ if (!option) return name;
744
+ const hasSingleUnderscore = position === "leading" ? () => name.startsWith("_") : () => name.endsWith("_");
745
+ const trimSingleUnderscore = position === "leading" ? () => name.slice(1) : () => name.slice(0, -1);
746
+ const hasDoubleUnderscore = position === "leading" ? () => name.startsWith("__") : () => name.endsWith("__");
747
+ const trimDoubleUnderscore = position === "leading" ? () => name.slice(2) : () => name.slice(0, -2);
737
748
  switch (option) {
738
749
  case UnderscoreOption.allow:
739
750
  if (hasSingleUnderscore()) return trimSingleUnderscore();
740
- return name$1;
751
+ return name;
741
752
  case UnderscoreOption.allowDouble:
742
753
  if (hasDoubleUnderscore()) return trimDoubleUnderscore();
743
- return name$1;
754
+ return name;
744
755
  case UnderscoreOption.allowSingleOrDouble:
745
756
  if (hasDoubleUnderscore()) return trimDoubleUnderscore();
746
757
  if (hasSingleUnderscore()) return trimSingleUnderscore();
747
- return name$1;
758
+ return name;
748
759
  case UnderscoreOption.forbid:
749
760
  if (hasSingleUnderscore()) {
750
761
  context.report({
@@ -758,7 +769,7 @@ function createValidator(type, context, allConfigs) {
758
769
  });
759
770
  return;
760
771
  }
761
- return name$1;
772
+ return name;
762
773
  case UnderscoreOption.require:
763
774
  if (!hasSingleUnderscore()) {
764
775
  context.report({
@@ -789,12 +800,12 @@ function createValidator(type, context, allConfigs) {
789
800
  return trimDoubleUnderscore();
790
801
  }
791
802
  }
792
- function validateAffix({ config, name: name$1, node, originalName, position }) {
803
+ function validateAffix({ name, config, node, originalName, position }) {
793
804
  const affixes = config[position];
794
- if (!affixes || affixes.length === 0) return name$1;
805
+ if (!affixes || affixes.length === 0) return name;
795
806
  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);
807
+ const hasAffix = position === "prefix" ? name.startsWith(affix) : name.endsWith(affix);
808
+ const trimAffix = position === "prefix" ? () => name.slice(affix.length) : () => name.slice(0, -affix.length);
798
809
  if (hasAffix) return trimAffix();
799
810
  }
800
811
  context.report({
@@ -807,10 +818,10 @@ function createValidator(type, context, allConfigs) {
807
818
  node
808
819
  });
809
820
  }
810
- function validateCustom({ config, name: name$1, node, originalName }) {
821
+ function validateCustom({ name, config, node, originalName }) {
811
822
  const { custom } = config;
812
823
  if (!custom) return true;
813
- const result = custom.regex.test(name$1);
824
+ const result = custom.regex.test(name);
814
825
  if (custom.match && result) return true;
815
826
  if (!custom.match && !result) return true;
816
827
  context.report({
@@ -823,20 +834,20 @@ function createValidator(type, context, allConfigs) {
823
834
  });
824
835
  return false;
825
836
  }
826
- function validatePredefinedFormat({ config, modifiers, name: name$1, node, originalName }) {
837
+ function validatePredefinedFormat({ name, config, modifiers, node, originalName }) {
827
838
  const formats = config.format;
828
839
  if (!formats || formats.length === 0) return true;
829
840
  if (!modifiers.has(Modifier.requiresQuotes)) for (const format of formats) {
830
841
  const checker = FormatCheckersMap[format];
831
- if (checker(name$1)) return true;
842
+ if (checker(name)) return true;
832
843
  }
833
844
  context.report({
834
845
  data: formatReportData({
835
846
  formats,
836
847
  originalName,
837
- processedName: name$1
848
+ processedName: name
838
849
  }),
839
- messageId: originalName === name$1 ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed",
850
+ messageId: originalName === name ? "doesNotMatchFormat" : "doesNotMatchFormatTrimmed",
840
851
  node
841
852
  });
842
853
  return false;
@@ -859,19 +870,15 @@ function isCorrectType(node, config, context, selector) {
859
870
  break;
860
871
  case TypeModifier.boolean:
861
872
  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;
873
+ case TypeModifier.string:
874
+ if (checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type))) === TypeModifierValueToKey[allowedType]) return true;
866
875
  break;
867
- }
868
876
  case TypeModifier.function:
869
877
  if (isAllTypesMatch(type, (inner) => inner.getCallSignatures().length > 0)) return true;
870
878
  break;
871
879
  }
872
880
  return false;
873
881
  }
874
-
875
882
  //#endregion
876
883
  //#region src/rules/naming-convention/utils/parse-options.ts
877
884
  function parseOptions(context) {
@@ -911,7 +918,6 @@ function normalizeOption(option) {
911
918
  };
912
919
  });
913
920
  }
914
-
915
921
  //#endregion
916
922
  //#region src/rules/naming-convention/utils/schema.ts
917
923
  const $DEFS = {
@@ -1182,11 +1188,10 @@ const SCHEMA = {
1182
1188
  ] },
1183
1189
  type: "array"
1184
1190
  };
1185
-
1186
1191
  //#endregion
1187
1192
  //#region src/rules/naming-convention/rule.ts
1188
- const RULE_NAME = "naming-convention";
1189
- const messages = {
1193
+ const RULE_NAME$1 = "naming-convention";
1194
+ const messages$1 = {
1190
1195
  doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
1191
1196
  doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
1192
1197
  missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
@@ -1216,7 +1221,7 @@ const camelCaseNamingConfig = [
1216
1221
  selector: "typeLike"
1217
1222
  }
1218
1223
  ];
1219
- function create(contextWithoutDefaults) {
1224
+ function create$1(contextWithoutDefaults) {
1220
1225
  const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
1221
1226
  const validators = parseOptions(context);
1222
1227
  const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
@@ -1237,11 +1242,11 @@ function create(contextWithoutDefaults) {
1237
1242
  return modifiers;
1238
1243
  }
1239
1244
  const { unusedVariables } = collectVariables(context);
1240
- function isUnused(name$1, initialScope) {
1245
+ function isUnused(name, initialScope) {
1241
1246
  let variable = null;
1242
1247
  let scope = initialScope;
1243
1248
  while (scope) {
1244
- variable = scope.set.get(name$1) ?? null;
1249
+ variable = scope.set.get(name) ?? null;
1245
1250
  if (variable) break;
1246
1251
  scope = scope.upper;
1247
1252
  }
@@ -1323,22 +1328,19 @@ function create(contextWithoutDefaults) {
1323
1328
  const selectors = {
1324
1329
  ":matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
1325
1330
  handler: (node, validator) => {
1326
- const modifiers = getMemberModifiers(node);
1327
- handleMember(validator, node, modifiers);
1331
+ handleMember(validator, node, getMemberModifiers(node));
1328
1332
  },
1329
1333
  validator: validators.classProperty
1330
1334
  },
1331
1335
  ":not(ObjectPattern) > Property[computed = false][kind = \"init\"][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
1332
1336
  handler: (node, validator) => {
1333
- const modifiers = new Set([Modifier.public]);
1334
- handleMember(validator, node, modifiers);
1337
+ handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
1335
1338
  },
1336
1339
  validator: validators.objectLiteralProperty
1337
1340
  },
1338
1341
  [["TSMethodSignature[computed = false]", "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type = \"TSFunctionType\"]"].join(", ")]: {
1339
1342
  handler: (node, validator) => {
1340
- const modifiers = new Set([Modifier.public]);
1341
- handleMember(validator, node, modifiers);
1343
+ handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
1342
1344
  },
1343
1345
  validator: validators.typeMethod
1344
1346
  },
@@ -1358,8 +1360,7 @@ function create(contextWithoutDefaults) {
1358
1360
  },
1359
1361
  [["MethodDefinition[computed = false]:matches([kind = \"get\"], [kind = \"set\"])", "TSAbstractMethodDefinition[computed = false]:matches([kind=\"get\"], [kind=\"set\"])"].join(", ")]: {
1360
1362
  handler: (node, validator) => {
1361
- const modifiers = getMemberModifiers(node);
1362
- handleMember(validator, node, modifiers);
1363
+ handleMember(validator, node, getMemberModifiers(node));
1363
1364
  },
1364
1365
  validator: validators.classicAccessor
1365
1366
  },
@@ -1369,7 +1370,7 @@ function create(contextWithoutDefaults) {
1369
1370
  "Property[computed = false][kind = \"init\"][value.type = \"TSEmptyBodyFunctionExpression\"]"
1370
1371
  ].join(", ")]: {
1371
1372
  handler: (node, validator) => {
1372
- const modifiers = new Set([Modifier.public]);
1373
+ const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
1373
1374
  if (isAsyncMemberOrProperty(node)) modifiers.add(Modifier.async);
1374
1375
  handleMember(validator, node, modifiers);
1375
1376
  },
@@ -1377,14 +1378,13 @@ function create(contextWithoutDefaults) {
1377
1378
  },
1378
1379
  [[AST_NODE_TYPES.AccessorProperty, AST_NODE_TYPES.TSAbstractAccessorProperty].join(", ")]: {
1379
1380
  handler: (node, validator) => {
1380
- const modifiers = getMemberModifiers(node);
1381
- handleMember(validator, node, modifiers);
1381
+ handleMember(validator, node, getMemberModifiers(node));
1382
1382
  },
1383
1383
  validator: validators.autoAccessor
1384
1384
  },
1385
1385
  "ClassDeclaration, ClassExpression": {
1386
1386
  handler: (node, validator) => {
1387
- const { abstract, id } = node;
1387
+ const { id, abstract } = node;
1388
1388
  if (id === null) return;
1389
1389
  const modifiers = /* @__PURE__ */ new Set();
1390
1390
  const scope = context.sourceCode.getScope(node).upper;
@@ -1444,8 +1444,7 @@ function create(contextWithoutDefaults) {
1444
1444
  },
1445
1445
  "Property[computed = false]:matches([kind = \"get\"], [kind = \"set\"])": {
1446
1446
  handler: (node, validator) => {
1447
- const modifiers = new Set([Modifier.public]);
1448
- handleMember(validator, node, modifiers);
1447
+ handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
1449
1448
  },
1450
1449
  validator: validators.classicAccessor
1451
1450
  },
@@ -1488,7 +1487,7 @@ function create(contextWithoutDefaults) {
1488
1487
  },
1489
1488
  "TSPropertySignature[computed = false][typeAnnotation.typeAnnotation.type != \"TSFunctionType\"]": {
1490
1489
  handler: (node, validator) => {
1491
- const modifiers = new Set([Modifier.public]);
1490
+ const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
1492
1491
  if (node.readonly) modifiers.add(Modifier.readonly);
1493
1492
  handleMember(validator, node, modifiers);
1494
1493
  },
@@ -1542,7 +1541,8 @@ function create(contextWithoutDefaults) {
1542
1541
  }));
1543
1542
  }
1544
1543
  const namingConvention = createEslintRule({
1545
- create,
1544
+ name: RULE_NAME$1,
1545
+ create: create$1,
1546
1546
  defaultOptions: camelCaseNamingConfig,
1547
1547
  meta: {
1548
1548
  docs: {
@@ -1552,21 +1552,22 @@ const namingConvention = createEslintRule({
1552
1552
  },
1553
1553
  fixable: void 0,
1554
1554
  hasSuggestions: false,
1555
- messages,
1555
+ messages: messages$1,
1556
1556
  schema: SCHEMA,
1557
1557
  type: "suggestion"
1558
- },
1559
- name: RULE_NAME
1558
+ }
1560
1559
  });
1561
1560
  function getIdentifiersFromPattern(pattern) {
1562
1561
  const identifiers = [];
1563
- new PatternVisitor({}, pattern, (id) => identifiers.push(id)).visit(pattern);
1562
+ new PatternVisitor({}, pattern, (id) => {
1563
+ identifiers.push(id);
1564
+ }).visit(pattern);
1564
1565
  return identifiers;
1565
1566
  }
1566
- function isExported(node, name$1, scope) {
1567
+ function isExported(node, name, scope) {
1567
1568
  if (node?.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || node?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) return true;
1568
1569
  if (scope === null) return false;
1569
- const variable = scope.set.get(name$1);
1570
+ const variable = scope.set.get(name);
1570
1571
  if (variable) for (const ref of variable.references) {
1571
1572
  const refParent = ref.identifier.parent;
1572
1573
  if (refParent.type === AST_NODE_TYPES.ExportDefaultDeclaration || refParent.type === AST_NODE_TYPES.ExportSpecifier) return true;
@@ -1578,10 +1579,66 @@ function isGlobal(scope) {
1578
1579
  return scope.type === TSESLint.Scope.ScopeType.global || scope.type === TSESLint.Scope.ScopeType.module;
1579
1580
  }
1580
1581
  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);
1582
+ return requiresQuoting(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target);
1583
1583
  }
1584
-
1584
+ //#endregion
1585
+ //#region src/rules/yaml-block-key-blank-lines/rule.ts
1586
+ const RULE_NAME = "yaml-block-key-blank-lines";
1587
+ const messages = { blankLine: "Expected {{count}} blank line(s) around this top-level key." };
1588
+ /**
1589
+ * Determines whether a pair value is a block collection (block mapping or block
1590
+ * sequence). Flow collections (`{ ... }` / `[ ... ]`) count as scalars.
1591
+ *
1592
+ * @param value - The value node of a YAML pair.
1593
+ * @returns True if the value is a block mapping or block sequence.
1594
+ */
1595
+ function isBlock(value) {
1596
+ return value !== null && (value.type === "YAMLMapping" || value.type === "YAMLSequence") && value.style === "block";
1597
+ }
1598
+ function create(context) {
1599
+ const { sourceCode } = context;
1600
+ if (sourceCode.parserServices.isYAML !== true) return {};
1601
+ return { YAMLMapping(yamlNode) {
1602
+ if (yamlNode.parent.type !== "YAMLDocument") return;
1603
+ const { pairs } = yamlNode;
1604
+ for (let index = 1; index < pairs.length; index += 1) {
1605
+ const previous = pairs[index - 1];
1606
+ const current = pairs[index];
1607
+ if (previous === void 0 || current === void 0) continue;
1608
+ const left = sourceCode.getLastToken(previous);
1609
+ const right = sourceCode.getFirstToken(current);
1610
+ if (sourceCode.commentsExistBetween(left, right)) continue;
1611
+ const blanks = right.loc.start.line - left.loc.end.line - 1;
1612
+ const want = isBlock(previous.value) || isBlock(current.value) ? 1 : 0;
1613
+ if (blanks === want) continue;
1614
+ context.report({
1615
+ data: { count: want },
1616
+ fix(fixer) {
1617
+ return fixer.replaceTextRange([left.range[1], right.range[0]], "\n".repeat(want + 1));
1618
+ },
1619
+ loc: (current.key ?? current).loc,
1620
+ messageId: "blankLine"
1621
+ });
1622
+ }
1623
+ } };
1624
+ }
1625
+ const yamlBlockKeyBlankLines = createEslintRule({
1626
+ name: RULE_NAME,
1627
+ create,
1628
+ defaultOptions: [],
1629
+ meta: {
1630
+ docs: {
1631
+ description: "Enforce blank lines around top-level YAML block collection keys",
1632
+ recommended: false,
1633
+ requiresTypeChecking: false
1634
+ },
1635
+ fixable: "whitespace",
1636
+ hasSuggestions: false,
1637
+ messages,
1638
+ schema: [],
1639
+ type: "layout"
1640
+ }
1641
+ });
1585
1642
  //#endregion
1586
1643
  //#region src/plugin.ts
1587
1644
  const PLUGIN_NAME = name.replace(/^eslint-plugin-/, "");
@@ -1600,23 +1657,23 @@ const plugin = {
1600
1657
  name: PLUGIN_NAME,
1601
1658
  version
1602
1659
  },
1603
- rules: { "naming-convention": namingConvention }
1660
+ rules: {
1661
+ "naming-convention": namingConvention,
1662
+ "yaml-block-key-blank-lines": yamlBlockKeyBlankLines
1663
+ }
1604
1664
  };
1605
- const allRules = getRules(PLUGIN_NAME, plugin.rules);
1606
-
1665
+ getRules(PLUGIN_NAME, plugin.rules);
1607
1666
  //#endregion
1608
1667
  //#region src/configs/index.ts
1609
1668
  const configs = { recommended: {
1610
1669
  plugins: { [PLUGIN_NAME]: plugin },
1611
1670
  rules: {}
1612
1671
  } };
1613
-
1614
1672
  //#endregion
1615
1673
  //#region src/index.ts
1616
1674
  var src_default = {
1617
1675
  ...plugin,
1618
1676
  configs
1619
1677
  };
1620
-
1621
1678
  //#endregion
1622
- export { src_default as default };
1679
+ 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.0",
3
+ "version": "0.1.1",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",
@@ -34,44 +34,45 @@
34
34
  "*": "eslint --fix --cache"
35
35
  },
36
36
  "dependencies": {
37
- "@typescript-eslint/scope-manager": "8.44.0",
38
- "@typescript-eslint/type-utils": "^8.44.0",
39
- "@typescript-eslint/utils": "8.44.0"
37
+ "@typescript-eslint/scope-manager": "8.62.1",
38
+ "@typescript-eslint/type-utils": "8.62.1",
39
+ "@typescript-eslint/utils": "8.62.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@antfu/ni": "25.0.0",
43
- "@isentinel/eslint-config": "3.0.0",
44
- "@isentinel/tsconfig": "1.0.0",
42
+ "@antfu/ni": "30.2.0",
43
+ "@isentinel/eslint-config": "5.2.0",
44
+ "@isentinel/tsconfig": "1.2.0",
45
45
  "@types/node": "24.0.13",
46
- "@typescript-eslint/eslint-plugin": "8.44.0",
47
- "@typescript-eslint/parser": "8.44.0",
48
- "@vitest/eslint-plugin": "1.3.4",
49
- "bumpp": "10.2.2",
50
- "eslint": "9.35.0",
51
- "eslint-doc-generator": "2.2.2",
52
- "eslint-plugin-eslint-plugin": "7.0.0",
53
- "eslint-plugin-n": "17.23.1",
54
- "eslint-plugin-pnpm": "1.1.0",
55
- "eslint-vitest-rule-tester": "2.2.1",
56
- "jiti": "2.5.1",
57
- "lint-staged": "16.1.4",
58
- "publint": "0.3.12",
46
+ "@typescript-eslint/eslint-plugin": "8.62.1",
47
+ "@typescript-eslint/parser": "8.62.1",
48
+ "@vitest/eslint-plugin": "1.6.20",
49
+ "bumpp": "11.1.0",
50
+ "eslint": "9.39.4",
51
+ "eslint-doc-generator": "3.6.0",
52
+ "eslint-plugin-eslint-plugin": "7.4.1",
53
+ "eslint-plugin-n": "18.2.1",
54
+ "eslint-plugin-pnpm": "1.6.1",
55
+ "eslint-plugin-yml": "3.5.0",
56
+ "eslint-vitest-rule-tester": "3.1.0",
57
+ "jiti": "2.7.0",
58
+ "lint-staged": "17.0.8",
59
+ "publint": "0.3.21",
59
60
  "simple-git-hooks": "2.13.1",
60
- "ts-api-utils": "2.1.0",
61
- "tsdown": "0.13.3",
62
- "tsx": "4.20.3",
63
- "typescript": "5.8.3",
64
- "unplugin-unused": "0.5.1",
65
- "vitest": "3.2.4"
61
+ "ts-api-utils": "2.5.0",
62
+ "tsdown": "0.22.3",
63
+ "tsx": "4.22.4",
64
+ "typescript": "6.0.3",
65
+ "unplugin-unused": "0.5.7",
66
+ "vitest": "4.1.9",
67
+ "yaml-eslint-parser": "2.0.0"
66
68
  },
67
69
  "peerDependencies": {
68
70
  "eslint": ">=9.15.0"
69
71
  },
70
72
  "engines": {
71
- "node": ">=20.0.0"
73
+ "node": ">=24.12.0"
72
74
  },
73
75
  "publishConfig": {
74
- "access": "public",
75
76
  "provenance": true
76
77
  },
77
78
  "scripts": {