eslint-plugin-flawless 0.1.1 → 0.1.3

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
@@ -176,10 +176,13 @@ pnpm eslint-docs
176
176
  💭
177
177
  Requires [type information](https://typescript-eslint.io/linting/typed-linting).
178
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 | 🔧 | |
179
+ | Name                       | Description | 🔧 | 💭 |
180
+ | :---------------------------------------------------------------------------------- | :------------------------------------------------------------------- | :-- | :-- |
181
+ | [jsx-shorthand-boolean](src/rules/jsx-shorthand-boolean/documentation.md) | Disallow shorthand boolean JSX attributes | 🔧 | |
182
+ | [jsx-shorthand-fragment](src/rules/jsx-shorthand-fragment/documentation.md) | Disallow the shorthand fragment syntax in favour of a named fragment | 🔧 | |
183
+ | [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase | | 💭 |
184
+ | [toml-sort-keys](src/rules/toml-sort-keys/documentation.md) | Enforce a configured sort order for TOML keys and tables | 🔧 | |
185
+ | [yaml-block-key-blank-lines](src/rules/yaml-block-key-blank-lines/documentation.md) | Enforce blank lines around top-level YAML block collection keys | 🔧 | |
183
186
 
184
187
  <!-- end auto-generated rules list -->
185
188
 
package/dist/index.d.mts CHANGED
@@ -8,6 +8,9 @@ interface PluginDocumentation {
8
8
  requiresTypeChecking: boolean;
9
9
  }
10
10
  //#endregion
11
+ //#region src/rules/jsx-shorthand-fragment/rule.d.ts
12
+ type Options$2 = [fragmentName?: string];
13
+ //#endregion
11
14
  //#region src/rules/naming-convention/utils/enums.d.ts
12
15
  declare const Selector: {
13
16
  readonly variable: 1;
@@ -116,7 +119,20 @@ interface NamingSelector {
116
119
  //#endregion
117
120
  //#region src/rules/naming-convention/rule.d.ts
118
121
  type MessageIds = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
119
- type Options = Array<NamingSelector>;
122
+ type Options$1 = Array<NamingSelector>;
123
+ //#endregion
124
+ //#region src/rules/toml-sort-keys/rule.d.ts
125
+ type Options = Array<SortSpec>;
126
+ interface SortOrderObject {
127
+ caseSensitive?: boolean;
128
+ natural?: boolean;
129
+ type?: "asc" | "desc";
130
+ }
131
+ type SortOrder = Array<string> | SortOrderObject;
132
+ interface SortSpec {
133
+ order: SortOrder;
134
+ pathPattern: string;
135
+ }
120
136
  //#endregion
121
137
  //#region src/plugin.d.ts
122
138
  declare const plugin: {
@@ -125,7 +141,16 @@ declare const plugin: {
125
141
  version: string;
126
142
  };
127
143
  rules: {
128
- "naming-convention": TSESLint.RuleModule<MessageIds, Options, PluginDocumentation, TSESLint.RuleListener> & {
144
+ "jsx-shorthand-boolean": TSESLint.RuleModule<"setAttributeValue", [], PluginDocumentation, TSESLint.RuleListener> & {
145
+ name: string;
146
+ };
147
+ "jsx-shorthand-fragment": TSESLint.RuleModule<"useNamedFragment", Options$2, PluginDocumentation, TSESLint.RuleListener> & {
148
+ name: string;
149
+ };
150
+ "naming-convention": TSESLint.RuleModule<MessageIds, Options$1, PluginDocumentation, TSESLint.RuleListener> & {
151
+ name: string;
152
+ };
153
+ "toml-sort-keys": TSESLint.RuleModule<"unsorted", Options, PluginDocumentation, TSESLint.RuleListener> & {
129
154
  name: string;
130
155
  };
131
156
  "yaml-block-key-blank-lines": TSESLint.RuleModule<"blankLine", [], PluginDocumentation, TSESLint.RuleListener> & {
@@ -145,7 +170,16 @@ declare const _default: {
145
170
  version: string;
146
171
  };
147
172
  rules: {
148
- "naming-convention": import("${configDir}").RuleModule<MessageIds, Options, PluginDocumentation, import("${configDir}").RuleListener> & {
173
+ "jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
174
+ name: string;
175
+ };
176
+ "jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
177
+ name: string;
178
+ };
179
+ "naming-convention": import("${configDir}").RuleModule<MessageIds, Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
180
+ name: string;
181
+ };
182
+ "toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
149
183
  name: string;
150
184
  };
151
185
  "yaml-block-key-blank-lines": import("${configDir}").RuleModule<"blankLine", [], PluginDocumentation, import("${configDir}").RuleListener> & {
@@ -162,7 +196,16 @@ declare const _default: {
162
196
  version: string;
163
197
  };
164
198
  rules: {
165
- "naming-convention": import("${configDir}").RuleModule<MessageIds, Options, PluginDocumentation, import("${configDir}").RuleListener> & {
199
+ "jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
200
+ name: string;
201
+ };
202
+ "jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
203
+ name: string;
204
+ };
205
+ "naming-convention": import("${configDir}").RuleModule<MessageIds, Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
206
+ name: string;
207
+ };
208
+ "toml-sort-keys": import("${configDir}").RuleModule<"unsorted", Options, PluginDocumentation, import("${configDir}").RuleListener> & {
166
209
  name: string;
167
210
  };
168
211
  "yaml-block-key-blank-lines": import("${configDir}").RuleModule<"blankLine", [], PluginDocumentation, import("${configDir}").RuleListener> & {
package/dist/index.mjs CHANGED
@@ -1,11 +1,12 @@
1
+ import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
1
2
  import { DefinitionType, ImplicitLibVariable, PatternVisitor, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
2
3
  import { requiresQuoting } from "@typescript-eslint/type-utils";
3
4
  import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
4
- import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
5
5
  import assert from "node:assert";
6
+ import { getStaticTOMLValue } from "toml-eslint-parser";
6
7
  //#region package.json
7
8
  var name = "eslint-plugin-flawless";
8
- var version = "0.1.1";
9
+ var version = "0.1.3";
9
10
  var repository = {
10
11
  "url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
11
12
  "type": "git"
@@ -31,6 +32,82 @@ function createEslintRule(rule) {
31
32
  return createRule(rule);
32
33
  }
33
34
  //#endregion
35
+ //#region src/rules/jsx-shorthand-boolean/rule.ts
36
+ const RULE_NAME$4 = "jsx-shorthand-boolean";
37
+ const MESSAGE_ID$2 = "setAttributeValue";
38
+ const messages$4 = { [MESSAGE_ID$2]: "Set an explicit value for boolean attribute '{{name}}'." };
39
+ function create$4(context) {
40
+ const { sourceCode } = context;
41
+ return { JSXAttribute(node) {
42
+ if (node.value !== null) return;
43
+ context.report({
44
+ data: { name: sourceCode.getText(node.name) },
45
+ fix: (fixer) => fixer.insertTextAfter(node.name, "={true}"),
46
+ messageId: MESSAGE_ID$2,
47
+ node
48
+ });
49
+ } };
50
+ }
51
+ const jsxShorthandBoolean = createEslintRule({
52
+ name: RULE_NAME$4,
53
+ create: create$4,
54
+ defaultOptions: [],
55
+ meta: {
56
+ docs: {
57
+ description: "Disallow shorthand boolean JSX attributes",
58
+ recommended: false,
59
+ requiresTypeChecking: false
60
+ },
61
+ fixable: "code",
62
+ hasSuggestions: false,
63
+ messages: messages$4,
64
+ schema: [],
65
+ type: "suggestion"
66
+ }
67
+ });
68
+ //#endregion
69
+ //#region src/rules/jsx-shorthand-fragment/rule.ts
70
+ const RULE_NAME$3 = "jsx-shorthand-fragment";
71
+ const MESSAGE_ID$1 = "useNamedFragment";
72
+ const DEFAULT_FRAGMENT_NAME = "Fragment";
73
+ const messages$3 = { [MESSAGE_ID$1]: "Use the '{{name}}' component instead of fragment shorthand syntax." };
74
+ const schema$1 = [{
75
+ description: "The identifier to use for the named fragment element.",
76
+ type: "string"
77
+ }];
78
+ function create$3(context) {
79
+ const name = context.options[0] ?? DEFAULT_FRAGMENT_NAME;
80
+ return { JSXFragment(node) {
81
+ const { closingFragment, openingFragment } = node;
82
+ context.report({
83
+ data: { name },
84
+ fix: (fixer) => {
85
+ return [fixer.replaceText(openingFragment, `<${name}>`), fixer.replaceText(closingFragment, `</${name}>`)];
86
+ },
87
+ messageId: MESSAGE_ID$1,
88
+ node
89
+ });
90
+ } };
91
+ }
92
+ const jsxShorthandFragment = createEslintRule({
93
+ name: RULE_NAME$3,
94
+ create: create$3,
95
+ defaultOptions: [DEFAULT_FRAGMENT_NAME],
96
+ meta: {
97
+ defaultOptions: [DEFAULT_FRAGMENT_NAME],
98
+ docs: {
99
+ description: "Disallow the shorthand fragment syntax in favour of a named fragment",
100
+ recommended: false,
101
+ requiresTypeChecking: false
102
+ },
103
+ fixable: "code",
104
+ hasSuggestions: false,
105
+ messages: messages$3,
106
+ schema: schema$1,
107
+ type: "suggestion"
108
+ }
109
+ });
110
+ //#endregion
34
111
  //#region src/utils/is-type-import.ts
35
112
  /**
36
113
  * Determine whether a variable definition is a type import. E.g.:.
@@ -1190,8 +1267,8 @@ const SCHEMA = {
1190
1267
  };
1191
1268
  //#endregion
1192
1269
  //#region src/rules/naming-convention/rule.ts
1193
- const RULE_NAME$1 = "naming-convention";
1194
- const messages$1 = {
1270
+ const RULE_NAME$2 = "naming-convention";
1271
+ const messages$2 = {
1195
1272
  doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
1196
1273
  doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
1197
1274
  missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
@@ -1221,7 +1298,7 @@ const camelCaseNamingConfig = [
1221
1298
  selector: "typeLike"
1222
1299
  }
1223
1300
  ];
1224
- function create$1(contextWithoutDefaults) {
1301
+ function create$2(contextWithoutDefaults) {
1225
1302
  const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
1226
1303
  const validators = parseOptions(context);
1227
1304
  const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
@@ -1541,8 +1618,8 @@ function create$1(contextWithoutDefaults) {
1541
1618
  }));
1542
1619
  }
1543
1620
  const namingConvention = createEslintRule({
1544
- name: RULE_NAME$1,
1545
- create: create$1,
1621
+ name: RULE_NAME$2,
1622
+ create: create$2,
1546
1623
  defaultOptions: camelCaseNamingConfig,
1547
1624
  meta: {
1548
1625
  docs: {
@@ -1552,7 +1629,7 @@ const namingConvention = createEslintRule({
1552
1629
  },
1553
1630
  fixable: void 0,
1554
1631
  hasSuggestions: false,
1555
- messages: messages$1,
1632
+ messages: messages$2,
1556
1633
  schema: SCHEMA,
1557
1634
  type: "suggestion"
1558
1635
  }
@@ -1582,6 +1659,225 @@ function requiresQuoting$1(node, target) {
1582
1659
  return requiresQuoting(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target);
1583
1660
  }
1584
1661
  //#endregion
1662
+ //#region src/rules/toml-sort-keys/rule.ts
1663
+ const RULE_NAME$1 = "toml-sort-keys";
1664
+ const MESSAGE_ID = "unsorted";
1665
+ const messages$1 = { [MESSAGE_ID]: "Expected {{target}} to follow the configured order." };
1666
+ const schema = {
1667
+ items: {
1668
+ additionalProperties: false,
1669
+ properties: {
1670
+ order: { oneOf: [{
1671
+ items: { type: "string" },
1672
+ type: "array"
1673
+ }, {
1674
+ additionalProperties: false,
1675
+ properties: {
1676
+ caseSensitive: { type: "boolean" },
1677
+ natural: { type: "boolean" },
1678
+ type: {
1679
+ enum: ["asc", "desc"],
1680
+ type: "string"
1681
+ }
1682
+ },
1683
+ type: "object"
1684
+ }] },
1685
+ pathPattern: { type: "string" }
1686
+ },
1687
+ required: ["order", "pathPattern"],
1688
+ type: "object"
1689
+ },
1690
+ type: "array"
1691
+ };
1692
+ function isTable(entry) {
1693
+ return entry.type === "TOMLTable";
1694
+ }
1695
+ /**
1696
+ * The dotted name of an entry, e.g. `settings`, `settings.node`, `_.path`.
1697
+ *
1698
+ * @param entry - The table header or key-value entry.
1699
+ * @returns The dotted key path as a string.
1700
+ */
1701
+ function nameOf(entry) {
1702
+ return getStaticTOMLValue(entry.key).join(".");
1703
+ }
1704
+ /**
1705
+ * Builds a rank function from an explicit order list. An entry matches an order
1706
+ * item when it equals it or is a dotted child of it (`settings.node` matches
1707
+ * `settings`), which keeps sub-tables grouped under their parent's slot.
1708
+ *
1709
+ * @param order - The configured names in their desired order.
1710
+ * @returns A function giving a name's rank, or `Infinity` when unlisted.
1711
+ */
1712
+ function makeRank(order) {
1713
+ return (name) => {
1714
+ for (const [index, item] of order.entries()) if (name === item || name.startsWith(`${item}.`)) return index;
1715
+ return Number.POSITIVE_INFINITY;
1716
+ };
1717
+ }
1718
+ function makeFallback(config) {
1719
+ const { caseSensitive = true, natural = false, type = "asc" } = config;
1720
+ const sensitivity = caseSensitive ? "variant" : "accent";
1721
+ return (a, b) => {
1722
+ const result = a.localeCompare(b, "en", {
1723
+ numeric: natural,
1724
+ sensitivity
1725
+ });
1726
+ return type === "desc" ? -result : result;
1727
+ };
1728
+ }
1729
+ /**
1730
+ * Builds a comparator for one table body. Explicit-order entries sort first by
1731
+ * their listed position, then unlisted entries fall back to a natural/asc sort
1732
+ * (mirroring `yaml/sort-keys`). At the top level, bare key-values are always
1733
+ * kept before any `[table]` header, since TOML would otherwise re-scope them.
1734
+ *
1735
+ * @param order - The order configuration for this table's path.
1736
+ * @param isTopLevel - Whether the body is the top-level table.
1737
+ * @returns A comparator over two entries.
1738
+ */
1739
+ function makeComparator(order, isTopLevel) {
1740
+ const rank = Array.isArray(order) ? makeRank(order) : void 0;
1741
+ const fallback = makeFallback(Array.isArray(order) ? {
1742
+ natural: true,
1743
+ type: "asc"
1744
+ } : order);
1745
+ return (a, b) => {
1746
+ if (isTopLevel) {
1747
+ const aRank = isTable(a) ? 1 : 0;
1748
+ const bRank = isTable(b) ? 1 : 0;
1749
+ if (aRank !== bRank) return aRank - bRank;
1750
+ }
1751
+ if (rank !== void 0) {
1752
+ const aRank = rank(nameOf(a));
1753
+ const bRank = rank(nameOf(b));
1754
+ if (aRank !== bRank) {
1755
+ if (aRank === Number.POSITIVE_INFINITY) return 1;
1756
+ if (bRank === Number.POSITIVE_INFINITY) return -1;
1757
+ return aRank - bRank;
1758
+ }
1759
+ }
1760
+ return fallback(nameOf(a), nameOf(b));
1761
+ };
1762
+ }
1763
+ function create$1(context) {
1764
+ const { options, sourceCode } = context;
1765
+ if (sourceCode.parserServices.isTOML !== true) return {};
1766
+ const specs = options.map(({ order, pathPattern }) => {
1767
+ return {
1768
+ order,
1769
+ pattern: new RegExp(pathPattern, "u")
1770
+ };
1771
+ });
1772
+ if (specs.length === 0) return {};
1773
+ function resolveOrder(path) {
1774
+ for (const spec of specs) if (spec.pattern.test(path)) return spec.order;
1775
+ }
1776
+ /**
1777
+ * A comment counts as attached to an entry when it sits on its own line
1778
+ * directly above it (no blank line, no trailing code). Such comments travel
1779
+ * with the entry when it moves.
1780
+ *
1781
+ * @param comment - The comment to classify.
1782
+ * @returns Whether the comment stands alone on its line.
1783
+ */
1784
+ function isOwnLineComment(comment) {
1785
+ const before = sourceCode.getTokenBefore(comment, { includeComments: true });
1786
+ return before === null || before.loc.end.line < comment.loc.start.line;
1787
+ }
1788
+ function leadingStart(entry) {
1789
+ const comments = sourceCode.getCommentsBefore(entry);
1790
+ let start = entry.range[0];
1791
+ let boundaryLine = entry.loc.start.line;
1792
+ for (let index = comments.length - 1; index >= 0; index -= 1) {
1793
+ const comment = comments[index];
1794
+ if (comment === void 0) break;
1795
+ if (comment.loc.end.line !== boundaryLine - 1 || !isOwnLineComment(comment)) break;
1796
+ start = comment.range[0];
1797
+ boundaryLine = comment.loc.start.line;
1798
+ }
1799
+ return start;
1800
+ }
1801
+ function trailingEnd(entry) {
1802
+ const [comment] = sourceCode.getCommentsAfter(entry);
1803
+ if (comment?.loc.start.line === entry.loc.end.line) return comment.range[1];
1804
+ return entry.range[1];
1805
+ }
1806
+ function buildFix(entries, target) {
1807
+ const text = sourceCode.getText();
1808
+ const blocks = entries.map((entry) => {
1809
+ return {
1810
+ end: trailingEnd(entry),
1811
+ entry,
1812
+ start: leadingStart(entry)
1813
+ };
1814
+ });
1815
+ for (let index = 1; index < blocks.length; index += 1) {
1816
+ const previous = blocks[index - 1];
1817
+ const current = blocks[index];
1818
+ if (previous === void 0 || current === void 0) return;
1819
+ if (/\S/u.test(text.slice(previous.end, current.start))) return;
1820
+ }
1821
+ const first = blocks[0];
1822
+ const last = blocks[blocks.length - 1];
1823
+ if (first === void 0 || last === void 0) return;
1824
+ const textByEntry = new Map(blocks.map((block) => [block.entry, text.slice(block.start, block.end)]));
1825
+ const parts = [];
1826
+ for (const [index, entry] of target.entries()) {
1827
+ const previous = target[index - 1];
1828
+ if (previous !== void 0) parts.push(isTable(entry) || isTable(previous) ? "\n\n" : "\n");
1829
+ parts.push(textByEntry.get(entry) ?? "");
1830
+ }
1831
+ const sortedText = parts.join("");
1832
+ return (fixer) => fixer.replaceTextRange([first.start, last.end], sortedText);
1833
+ }
1834
+ function verify(path, body, isTopLevel) {
1835
+ if (body.length < 2) return;
1836
+ const order = resolveOrder(path);
1837
+ if (order === void 0) return;
1838
+ const entries = [...body];
1839
+ const target = [...entries].sort(makeComparator(order, isTopLevel));
1840
+ let outOfPlace;
1841
+ for (const [index, entry] of entries.entries()) if (entry !== target[index]) {
1842
+ outOfPlace = entry;
1843
+ break;
1844
+ }
1845
+ if (outOfPlace === void 0) return;
1846
+ context.report({
1847
+ data: { target: path === "" ? "top-level tables" : `keys in "${path}"` },
1848
+ fix: buildFix(entries, target),
1849
+ loc: outOfPlace.key.loc,
1850
+ messageId: MESSAGE_ID
1851
+ });
1852
+ }
1853
+ return {
1854
+ TOMLTable(node) {
1855
+ verify(getStaticTOMLValue(node.key).join("."), node.body, false);
1856
+ },
1857
+ TOMLTopLevelTable(node) {
1858
+ verify("", node.body, true);
1859
+ }
1860
+ };
1861
+ }
1862
+ const tomlSortKeys = createEslintRule({
1863
+ name: RULE_NAME$1,
1864
+ create: create$1,
1865
+ defaultOptions: [],
1866
+ meta: {
1867
+ defaultOptions: [],
1868
+ docs: {
1869
+ description: "Enforce a configured sort order for TOML keys and tables",
1870
+ recommended: false,
1871
+ requiresTypeChecking: false
1872
+ },
1873
+ fixable: "code",
1874
+ hasSuggestions: false,
1875
+ messages: messages$1,
1876
+ schema,
1877
+ type: "layout"
1878
+ }
1879
+ });
1880
+ //#endregion
1585
1881
  //#region src/rules/yaml-block-key-blank-lines/rule.ts
1586
1882
  const RULE_NAME = "yaml-block-key-blank-lines";
1587
1883
  const messages = { blankLine: "Expected {{count}} blank line(s) around this top-level key." };
@@ -1658,7 +1954,10 @@ const plugin = {
1658
1954
  version
1659
1955
  },
1660
1956
  rules: {
1957
+ "jsx-shorthand-boolean": jsxShorthandBoolean,
1958
+ "jsx-shorthand-fragment": jsxShorthandFragment,
1661
1959
  "naming-convention": namingConvention,
1960
+ "toml-sort-keys": tomlSortKeys,
1662
1961
  "yaml-block-key-blank-lines": yamlBlockKeyBlankLines
1663
1962
  }
1664
1963
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-flawless",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",
@@ -36,7 +36,8 @@
36
36
  "dependencies": {
37
37
  "@typescript-eslint/scope-manager": "8.62.1",
38
38
  "@typescript-eslint/type-utils": "8.62.1",
39
- "@typescript-eslint/utils": "8.62.1"
39
+ "@typescript-eslint/utils": "8.62.1",
40
+ "toml-eslint-parser": "1.0.3"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@antfu/ni": "30.2.0",
@@ -52,6 +53,7 @@
52
53
  "eslint-plugin-eslint-plugin": "7.4.1",
53
54
  "eslint-plugin-n": "18.2.1",
54
55
  "eslint-plugin-pnpm": "1.6.1",
56
+ "eslint-plugin-toml": "1.4.0",
55
57
  "eslint-plugin-yml": "3.5.0",
56
58
  "eslint-vitest-rule-tester": "3.1.0",
57
59
  "jiti": "2.7.0",