eslint-plugin-flawless 0.1.6 → 0.1.8

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,17 +176,17 @@ pnpm eslint-docs
176
176
  💭
177
177
  Requires [type information](https://typescript-eslint.io/linting/typed-linting).
178
178
 
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
- | [no-unnecessary-use-callback](src/rules/no-unnecessary-use-callback/documentation.md) | Disallow unnecessary usage of 'useCallback' | | |
185
- | [no-unnecessary-use-memo](src/rules/no-unnecessary-use-memo/documentation.md) | Disallow unnecessary usage of 'useMemo' | | |
186
- | [prefer-destructuring-assignment](src/rules/prefer-destructuring-assignment/documentation.md) | Enforce destructuring assignment for component props | 🔧 | |
187
- | [purity](src/rules/purity/documentation.md) | Disallow impure calls such as `math.random` or `os.clock` during render | | |
188
- | [toml-sort-keys](src/rules/toml-sort-keys/documentation.md) | Enforce a configured sort order for TOML keys and tables | 🔧 | |
189
- | [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) | Enforce a consistent fragment form: the shorthand `<>...</>` or a named fragment | 🔧 | |
183
+ | [naming-convention](src/rules/naming-convention/documentation.md) | Enforce naming conventions for everything across a codebase | | 💭 |
184
+ | [no-unnecessary-use-callback](src/rules/no-unnecessary-use-callback/documentation.md) | Disallow unnecessary usage of 'useCallback' | | |
185
+ | [no-unnecessary-use-memo](src/rules/no-unnecessary-use-memo/documentation.md) | Disallow unnecessary usage of 'useMemo' | | |
186
+ | [prefer-destructuring-assignment](src/rules/prefer-destructuring-assignment/documentation.md) | Enforce destructuring assignment for component props | 🔧 | |
187
+ | [purity](src/rules/purity/documentation.md) | Disallow impure calls such as `math.random` or `os.clock` during render | | |
188
+ | [toml-sort-keys](src/rules/toml-sort-keys/documentation.md) | Enforce a configured sort order for TOML keys and tables | 🔧 | |
189
+ | [yaml-block-key-blank-lines](src/rules/yaml-block-key-blank-lines/documentation.md) | Enforce blank lines around top-level YAML block collection keys | 🔧 | |
190
190
 
191
191
  <!-- end auto-generated rules list -->
192
192
 
package/dist/index.d.mts CHANGED
@@ -9,7 +9,23 @@ interface PluginDocumentation {
9
9
  }
10
10
  //#endregion
11
11
  //#region src/rules/jsx-shorthand-fragment/rule.d.ts
12
- type Options$3 = [fragmentName?: string];
12
+ declare const MESSAGE_ID_NAMED = "useNamedFragment";
13
+ declare const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
14
+ type MessageIds$3 = typeof MESSAGE_ID_NAMED | typeof MESSAGE_ID_SHORTHAND;
15
+ interface JsxShorthandFragmentOptions {
16
+ /**
17
+ * The identifier used for the named fragment element in `"element"` mode
18
+ * (e.g. `"Fragment"` or `"React.Fragment"`). Defaults to `"Fragment"`.
19
+ */
20
+ readonly fragmentName?: string;
21
+ /**
22
+ * Which form to enforce: `"syntax"` (shorthand `<>...</>`, the default) or
23
+ * `"element"` (a named fragment such as `<Fragment>...</Fragment>`).
24
+ */
25
+ readonly mode?: Mode;
26
+ }
27
+ type Options$3 = [JsxShorthandFragmentOptions?];
28
+ type Mode = "element" | "syntax";
13
29
  //#endregion
14
30
  //#region src/rules/naming-convention/utils/enums.d.ts
15
31
  declare const Selector: {
@@ -166,7 +182,7 @@ declare const plugin: {
166
182
  "jsx-shorthand-boolean": TSESLint.RuleModule<"setAttributeValue", [], PluginDocumentation, TSESLint.RuleListener> & {
167
183
  name: string;
168
184
  };
169
- "jsx-shorthand-fragment": TSESLint.RuleModule<"useNamedFragment", Options$3, PluginDocumentation, TSESLint.RuleListener> & {
185
+ "jsx-shorthand-fragment": TSESLint.RuleModule<MessageIds$3, Options$3, PluginDocumentation, TSESLint.RuleListener> & {
170
186
  name: string;
171
187
  };
172
188
  "naming-convention": TSESLint.RuleModule<MessageIds$2, Options$2, PluginDocumentation, TSESLint.RuleListener> & {
@@ -207,7 +223,7 @@ declare const _default: {
207
223
  "jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
208
224
  name: string;
209
225
  };
210
- "jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
226
+ "jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
211
227
  name: string;
212
228
  };
213
229
  "naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
@@ -245,7 +261,7 @@ declare const _default: {
245
261
  "jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
246
262
  name: string;
247
263
  };
248
- "jsx-shorthand-fragment": import("${configDir}").RuleModule<"useNamedFragment", Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
264
+ "jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
249
265
  name: string;
250
266
  };
251
267
  "naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
package/dist/index.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
2
+ import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
2
3
  import { DefinitionType, ImplicitLibVariable, PatternVisitor, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
3
4
  import { requiresQuoting } from "@typescript-eslint/type-utils";
4
- import { ASTUtils, AST_NODE_TYPES, ESLintUtils, TSESLint } from "@typescript-eslint/utils";
5
5
  import assert from "node:assert";
6
6
  import * as core from "@eslint-react/core";
7
7
  import { findVariable } from "@typescript-eslint/utils/ast-utils";
8
8
  import { getStaticTOMLValue } from "toml-eslint-parser";
9
9
  //#region package.json
10
10
  var name = "eslint-plugin-flawless";
11
- var version = "0.1.6";
11
+ var version = "0.1.8";
12
12
  var repository = {
13
13
  "url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
14
14
  "type": "git"
@@ -36,8 +36,8 @@ function createEslintRule(rule) {
36
36
  //#endregion
37
37
  //#region src/rules/jsx-shorthand-boolean/rule.ts
38
38
  const RULE_NAME$8 = "jsx-shorthand-boolean";
39
- const MESSAGE_ID$4 = "setAttributeValue";
40
- const messages$8 = { [MESSAGE_ID$4]: "Set an explicit value for boolean attribute '{{name}}'." };
39
+ const MESSAGE_ID$3 = "setAttributeValue";
40
+ const messages$8 = { [MESSAGE_ID$3]: "Set an explicit value for boolean attribute '{{name}}'." };
41
41
  function create$8(context) {
42
42
  const { sourceCode } = context;
43
43
  return { JSXAttribute(node) {
@@ -45,7 +45,7 @@ function create$8(context) {
45
45
  context.report({
46
46
  data: { name: sourceCode.getText(node.name) },
47
47
  fix: (fixer) => fixer.insertTextAfter(node.name, "={true}"),
48
- messageId: MESSAGE_ID$4,
48
+ messageId: MESSAGE_ID$3,
49
49
  node
50
50
  });
51
51
  } };
@@ -70,23 +70,79 @@ const jsxShorthandBoolean = createEslintRule({
70
70
  //#endregion
71
71
  //#region src/rules/jsx-shorthand-fragment/rule.ts
72
72
  const RULE_NAME$7 = "jsx-shorthand-fragment";
73
- const MESSAGE_ID$3 = "useNamedFragment";
73
+ const MESSAGE_ID_NAMED = "useNamedFragment";
74
+ const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
75
+ const DEFAULT_MODE = "syntax";
74
76
  const DEFAULT_FRAGMENT_NAME = "Fragment";
75
- const messages$7 = { [MESSAGE_ID$3]: "Use the '{{name}}' component instead of fragment shorthand syntax." };
77
+ const messages$7 = {
78
+ [MESSAGE_ID_NAMED]: "Use the '{{name}}' component instead of fragment shorthand syntax.",
79
+ [MESSAGE_ID_SHORTHAND]: "Use the fragment shorthand syntax '<>...</>' instead of the '{{name}}' component."
80
+ };
76
81
  const schema$2 = [{
77
- description: "The identifier to use for the named fragment element.",
78
- type: "string"
82
+ additionalProperties: false,
83
+ properties: {
84
+ fragmentName: {
85
+ description: "The identifier to use for the named fragment element in \"element\" mode.",
86
+ type: "string"
87
+ },
88
+ mode: {
89
+ description: "Which form to enforce: \"syntax\" (shorthand `<>...</>`, default) or \"element\" (a named fragment).",
90
+ enum: ["element", "syntax"],
91
+ type: "string"
92
+ }
93
+ },
94
+ type: "object"
79
95
  }];
96
+ /**
97
+ * Flattens a JSX element name into a dotted string, e.g. `Fragment` or
98
+ * `React.Fragment`. Returns `null` for namespaced names (`<a:b>`) which cannot
99
+ * be a fragment.
100
+ *
101
+ * @param node - The JSX element name node.
102
+ * @returns The dotted name, or `null` when it is not a plain identifier chain.
103
+ */
104
+ function jsxNameToString(node) {
105
+ if (node.type === AST_NODE_TYPES.JSXIdentifier) return node.name;
106
+ if (node.type === AST_NODE_TYPES.JSXMemberExpression) {
107
+ const object = jsxNameToString(node.object);
108
+ if (object === null) return null;
109
+ return `${object}.${node.property.name}`;
110
+ }
111
+ return null;
112
+ }
80
113
  function create$7(context) {
81
- const name = context.options[0] ?? DEFAULT_FRAGMENT_NAME;
82
- return { JSXFragment(node) {
114
+ const options = context.options[0] ?? {};
115
+ const mode = options.mode ?? DEFAULT_MODE;
116
+ const fragmentName = options.fragmentName ?? DEFAULT_FRAGMENT_NAME;
117
+ if (mode === "element") return { JSXFragment(node) {
83
118
  const { closingFragment, openingFragment } = node;
119
+ context.report({
120
+ data: { name: fragmentName },
121
+ fix: (fixer) => {
122
+ return [fixer.replaceText(openingFragment, `<${fragmentName}>`), fixer.replaceText(closingFragment, `</${fragmentName}>`)];
123
+ },
124
+ messageId: MESSAGE_ID_NAMED,
125
+ node
126
+ });
127
+ } };
128
+ const namedFragments = /* @__PURE__ */ new Set([
129
+ "Fragment",
130
+ fragmentName,
131
+ "React.Fragment"
132
+ ]);
133
+ return { JSXElement(node) {
134
+ const { openingElement } = node;
135
+ const name = jsxNameToString(openingElement.name);
136
+ if (name === null || !namedFragments.has(name)) return;
137
+ if (openingElement.attributes.length > 0) return;
84
138
  context.report({
85
139
  data: { name },
86
140
  fix: (fixer) => {
87
- return [fixer.replaceText(openingFragment, `<${name}>`), fixer.replaceText(closingFragment, `</${name}>`)];
141
+ const { closingElement } = node;
142
+ if (closingElement === null) return fixer.replaceText(node, "<></>");
143
+ return [fixer.replaceText(openingElement, "<>"), fixer.replaceText(closingElement, "</>")];
88
144
  },
89
- messageId: MESSAGE_ID$3,
145
+ messageId: MESSAGE_ID_SHORTHAND,
90
146
  node
91
147
  });
92
148
  } };
@@ -94,11 +150,17 @@ function create$7(context) {
94
150
  const jsxShorthandFragment = createEslintRule({
95
151
  name: RULE_NAME$7,
96
152
  create: create$7,
97
- defaultOptions: [DEFAULT_FRAGMENT_NAME],
153
+ defaultOptions: [{
154
+ fragmentName: DEFAULT_FRAGMENT_NAME,
155
+ mode: DEFAULT_MODE
156
+ }],
98
157
  meta: {
99
- defaultOptions: [DEFAULT_FRAGMENT_NAME],
158
+ defaultOptions: [{
159
+ fragmentName: DEFAULT_FRAGMENT_NAME,
160
+ mode: DEFAULT_MODE
161
+ }],
100
162
  docs: {
101
- description: "Disallow the shorthand fragment syntax in favour of a named fragment",
163
+ description: "Enforce a consistent fragment form: the shorthand `<>...</>` or a named fragment",
102
164
  recommended: false,
103
165
  requiresTypeChecking: false
104
166
  },
@@ -2412,14 +2474,18 @@ function makeFallback(config) {
2412
2474
  /**
2413
2475
  * Builds a comparator for one table body. Explicit-order entries sort first by
2414
2476
  * their listed position, then unlisted entries fall back to a natural/asc sort
2415
- * (mirroring `yaml/sort-keys`). At the top level, bare key-values are always
2416
- * kept before any `[table]` header, since TOML would otherwise re-scope them.
2477
+ * (mirroring `yaml/sort-keys`). The fallback compares keys as written in
2478
+ * source, so quotes participate in the order and quoted keys (e.g.
2479
+ * `"github:owner/repo"`) group before bare keys. At the top level, bare
2480
+ * key-values are always kept before any `[table]` header, since TOML would
2481
+ * otherwise re-scope them.
2417
2482
  *
2418
2483
  * @param order - The order configuration for this table's path.
2419
2484
  * @param isTopLevel - Whether the body is the top-level table.
2485
+ * @param rawName - Returns an entry's key exactly as written in source.
2420
2486
  * @returns A comparator over two entries.
2421
2487
  */
2422
- function makeComparator(order, isTopLevel) {
2488
+ function makeComparator(order, isTopLevel, rawName) {
2423
2489
  const rank = Array.isArray(order) ? makeRank(order) : void 0;
2424
2490
  const fallback = makeFallback(Array.isArray(order) ? {
2425
2491
  natural: true,
@@ -2440,7 +2506,7 @@ function makeComparator(order, isTopLevel) {
2440
2506
  return aRank - bRank;
2441
2507
  }
2442
2508
  }
2443
- return fallback(nameOf(a), nameOf(b));
2509
+ return fallback(rawName(a), rawName(b));
2444
2510
  };
2445
2511
  }
2446
2512
  function create$1(context) {
@@ -2519,7 +2585,7 @@ function create$1(context) {
2519
2585
  const order = resolveOrder(path);
2520
2586
  if (order === void 0) return;
2521
2587
  const entries = [...body];
2522
- const target = [...entries].sort(makeComparator(order, isTopLevel));
2588
+ const target = [...entries].sort(makeComparator(order, isTopLevel, (entry) => sourceCode.getText(entry.key)));
2523
2589
  let outOfPlace;
2524
2590
  for (const [index, entry] of entries.entries()) if (entry !== target[index]) {
2525
2591
  outOfPlace = entry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-flawless",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",