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 +7 -4
- package/dist/index.d.mts +47 -4
- package/dist/index.mjs +307 -8
- package/package.json +4 -2
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
|
-
| [
|
|
182
|
-
| [
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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$
|
|
1194
|
-
const messages$
|
|
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$
|
|
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$
|
|
1545
|
-
create: create$
|
|
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$
|
|
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.
|
|
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",
|