eslint-plugin-flawless 0.1.8 → 0.1.10

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
@@ -184,6 +184,7 @@ Requires [type information](https://typescript-eslint.io/linting/typed-linting).
184
184
  | [no-unnecessary-use-callback](src/rules/no-unnecessary-use-callback/documentation.md) | Disallow unnecessary usage of 'useCallback' | | |
185
185
  | [no-unnecessary-use-memo](src/rules/no-unnecessary-use-memo/documentation.md) | Disallow unnecessary usage of 'useMemo' | | |
186
186
  | [prefer-destructuring-assignment](src/rules/prefer-destructuring-assignment/documentation.md) | Enforce destructuring assignment for component props | 🔧 | |
187
+ | [prefer-parameter-destructuring](src/rules/prefer-parameter-destructuring/documentation.md) | Enforce destructuring parameters in the function signature | 🔧 | |
187
188
  | [purity](src/rules/purity/documentation.md) | Disallow impure calls such as `math.random` or `os.clock` during render | | |
188
189
  | [toml-sort-keys](src/rules/toml-sort-keys/documentation.md) | Enforce a configured sort order for TOML keys and tables | 🔧 | |
189
190
  | [yaml-block-key-blank-lines](src/rules/yaml-block-key-blank-lines/documentation.md) | Enforce blank lines around top-level YAML block collection keys | 🔧 | |
package/dist/index.d.mts CHANGED
@@ -24,7 +24,7 @@ interface JsxShorthandFragmentOptions {
24
24
  */
25
25
  readonly mode?: Mode;
26
26
  }
27
- type Options$3 = [JsxShorthandFragmentOptions?];
27
+ type Options$4 = [JsxShorthandFragmentOptions?];
28
28
  type Mode = "element" | "syntax";
29
29
  //#endregion
30
30
  //#region src/rules/naming-convention/utils/enums.d.ts
@@ -135,7 +135,7 @@ interface NamingSelector {
135
135
  //#endregion
136
136
  //#region src/rules/naming-convention/rule.d.ts
137
137
  type MessageIds$2 = "doesNotMatchFormat" | "doesNotMatchFormatTrimmed" | "missingAffix" | "missingUnderscore" | "satisfyCustom" | "unexpectedUnderscore";
138
- type Options$2 = Array<NamingSelector>;
138
+ type Options$3 = Array<NamingSelector>;
139
139
  //#endregion
140
140
  //#region src/rules/no-unnecessary-use-callback/rule.d.ts
141
141
  declare const MESSAGE_ID_DEFAULT$1 = "default";
@@ -147,6 +147,16 @@ declare const MESSAGE_ID_DEFAULT = "default";
147
147
  declare const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
148
148
  type MessageIds = typeof MESSAGE_ID_DEFAULT | typeof MESSAGE_ID_INSIDE_USE_EFFECT;
149
149
  //#endregion
150
+ //#region src/rules/prefer-parameter-destructuring/rule.d.ts
151
+ type Options$2 = [{
152
+ /**
153
+ * Whether the autofix may hoist pattern defaults and computed keys past
154
+ * earlier statements, reordering their side effects. Defaults to `true`;
155
+ * set to `false` to withhold the fix in those cases.
156
+ */
157
+ allowSideEffectReordering?: boolean;
158
+ }];
159
+ //#endregion
150
160
  //#region src/rules/purity/rule.d.ts
151
161
  interface PurityOptions {
152
162
  /**
@@ -182,10 +192,10 @@ declare const plugin: {
182
192
  "jsx-shorthand-boolean": TSESLint.RuleModule<"setAttributeValue", [], PluginDocumentation, TSESLint.RuleListener> & {
183
193
  name: string;
184
194
  };
185
- "jsx-shorthand-fragment": TSESLint.RuleModule<MessageIds$3, Options$3, PluginDocumentation, TSESLint.RuleListener> & {
195
+ "jsx-shorthand-fragment": TSESLint.RuleModule<MessageIds$3, Options$4, PluginDocumentation, TSESLint.RuleListener> & {
186
196
  name: string;
187
197
  };
188
- "naming-convention": TSESLint.RuleModule<MessageIds$2, Options$2, PluginDocumentation, TSESLint.RuleListener> & {
198
+ "naming-convention": TSESLint.RuleModule<MessageIds$2, Options$3, PluginDocumentation, TSESLint.RuleListener> & {
189
199
  name: string;
190
200
  };
191
201
  "no-unnecessary-use-callback": TSESLint.RuleModule<MessageIds$1, [], PluginDocumentation, TSESLint.RuleListener> & {
@@ -197,6 +207,9 @@ declare const plugin: {
197
207
  "prefer-destructuring-assignment": TSESLint.RuleModule<"default", [], PluginDocumentation, TSESLint.RuleListener> & {
198
208
  name: string;
199
209
  };
210
+ "prefer-parameter-destructuring": TSESLint.RuleModule<"default", Options$2, PluginDocumentation, TSESLint.RuleListener> & {
211
+ name: string;
212
+ };
200
213
  purity: TSESLint.RuleModule<"impureCall", Options$1, PluginDocumentation, TSESLint.RuleListener> & {
201
214
  name: string;
202
215
  };
@@ -223,10 +236,10 @@ declare const _default: {
223
236
  "jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
224
237
  name: string;
225
238
  };
226
- "jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
239
+ "jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$4, PluginDocumentation, import("${configDir}").RuleListener> & {
227
240
  name: string;
228
241
  };
229
- "naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
242
+ "naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
230
243
  name: string;
231
244
  };
232
245
  "no-unnecessary-use-callback": import("${configDir}").RuleModule<MessageIds$1, [], PluginDocumentation, import("${configDir}").RuleListener> & {
@@ -238,6 +251,9 @@ declare const _default: {
238
251
  "prefer-destructuring-assignment": import("${configDir}").RuleModule<"default", [], PluginDocumentation, import("${configDir}").RuleListener> & {
239
252
  name: string;
240
253
  };
254
+ "prefer-parameter-destructuring": import("${configDir}").RuleModule<"default", Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
255
+ name: string;
256
+ };
241
257
  purity: import("${configDir}").RuleModule<"impureCall", Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
242
258
  name: string;
243
259
  };
@@ -261,10 +277,10 @@ declare const _default: {
261
277
  "jsx-shorthand-boolean": import("${configDir}").RuleModule<"setAttributeValue", [], PluginDocumentation, import("${configDir}").RuleListener> & {
262
278
  name: string;
263
279
  };
264
- "jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
280
+ "jsx-shorthand-fragment": import("${configDir}").RuleModule<MessageIds$3, Options$4, PluginDocumentation, import("${configDir}").RuleListener> & {
265
281
  name: string;
266
282
  };
267
- "naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
283
+ "naming-convention": import("${configDir}").RuleModule<MessageIds$2, Options$3, PluginDocumentation, import("${configDir}").RuleListener> & {
268
284
  name: string;
269
285
  };
270
286
  "no-unnecessary-use-callback": import("${configDir}").RuleModule<MessageIds$1, [], PluginDocumentation, import("${configDir}").RuleListener> & {
@@ -276,6 +292,9 @@ declare const _default: {
276
292
  "prefer-destructuring-assignment": import("${configDir}").RuleModule<"default", [], PluginDocumentation, import("${configDir}").RuleListener> & {
277
293
  name: string;
278
294
  };
295
+ "prefer-parameter-destructuring": import("${configDir}").RuleModule<"default", Options$2, PluginDocumentation, import("${configDir}").RuleListener> & {
296
+ name: string;
297
+ };
279
298
  purity: import("${configDir}").RuleModule<"impureCall", Options$1, PluginDocumentation, import("${configDir}").RuleListener> & {
280
299
  name: string;
281
300
  };
package/dist/index.mjs CHANGED
@@ -8,7 +8,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.8";
11
+ var version = "0.1.10";
12
12
  var repository = {
13
13
  "url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
14
14
  "type": "git"
@@ -35,24 +35,24 @@ function createEslintRule(rule) {
35
35
  }
36
36
  //#endregion
37
37
  //#region src/rules/jsx-shorthand-boolean/rule.ts
38
- const RULE_NAME$8 = "jsx-shorthand-boolean";
39
- const MESSAGE_ID$3 = "setAttributeValue";
40
- const messages$8 = { [MESSAGE_ID$3]: "Set an explicit value for boolean attribute '{{name}}'." };
41
- function create$8(context) {
38
+ const RULE_NAME$9 = "jsx-shorthand-boolean";
39
+ const MESSAGE_ID$4 = "setAttributeValue";
40
+ const messages$9 = { [MESSAGE_ID$4]: "Set an explicit value for boolean attribute '{{name}}'." };
41
+ function create$9(context) {
42
42
  const { sourceCode } = context;
43
43
  return { JSXAttribute(node) {
44
44
  if (node.value !== null) return;
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$3,
48
+ messageId: MESSAGE_ID$4,
49
49
  node
50
50
  });
51
51
  } };
52
52
  }
53
53
  const jsxShorthandBoolean = createEslintRule({
54
- name: RULE_NAME$8,
55
- create: create$8,
54
+ name: RULE_NAME$9,
55
+ create: create$9,
56
56
  defaultOptions: [],
57
57
  meta: {
58
58
  docs: {
@@ -62,19 +62,19 @@ const jsxShorthandBoolean = createEslintRule({
62
62
  },
63
63
  fixable: "code",
64
64
  hasSuggestions: false,
65
- messages: messages$8,
65
+ messages: messages$9,
66
66
  schema: [],
67
67
  type: "suggestion"
68
68
  }
69
69
  });
70
70
  //#endregion
71
71
  //#region src/rules/jsx-shorthand-fragment/rule.ts
72
- const RULE_NAME$7 = "jsx-shorthand-fragment";
72
+ const RULE_NAME$8 = "jsx-shorthand-fragment";
73
73
  const MESSAGE_ID_NAMED = "useNamedFragment";
74
74
  const MESSAGE_ID_SHORTHAND = "useShorthandFragment";
75
75
  const DEFAULT_MODE = "syntax";
76
76
  const DEFAULT_FRAGMENT_NAME = "Fragment";
77
- const messages$7 = {
77
+ const messages$8 = {
78
78
  [MESSAGE_ID_NAMED]: "Use the '{{name}}' component instead of fragment shorthand syntax.",
79
79
  [MESSAGE_ID_SHORTHAND]: "Use the fragment shorthand syntax '<>...</>' instead of the '{{name}}' component."
80
80
  };
@@ -110,7 +110,7 @@ function jsxNameToString(node) {
110
110
  }
111
111
  return null;
112
112
  }
113
- function create$7(context) {
113
+ function create$8(context) {
114
114
  const options = context.options[0] ?? {};
115
115
  const mode = options.mode ?? DEFAULT_MODE;
116
116
  const fragmentName = options.fragmentName ?? DEFAULT_FRAGMENT_NAME;
@@ -148,8 +148,8 @@ function create$7(context) {
148
148
  } };
149
149
  }
150
150
  const jsxShorthandFragment = createEslintRule({
151
- name: RULE_NAME$7,
152
- create: create$7,
151
+ name: RULE_NAME$8,
152
+ create: create$8,
153
153
  defaultOptions: [{
154
154
  fragmentName: DEFAULT_FRAGMENT_NAME,
155
155
  mode: DEFAULT_MODE
@@ -166,7 +166,7 @@ const jsxShorthandFragment = createEslintRule({
166
166
  },
167
167
  fixable: "code",
168
168
  hasSuggestions: false,
169
- messages: messages$7,
169
+ messages: messages$8,
170
170
  schema: schema$2,
171
171
  type: "suggestion"
172
172
  }
@@ -1331,8 +1331,8 @@ const SCHEMA = {
1331
1331
  };
1332
1332
  //#endregion
1333
1333
  //#region src/rules/naming-convention/rule.ts
1334
- const RULE_NAME$6 = "naming-convention";
1335
- const messages$6 = {
1334
+ const RULE_NAME$7 = "naming-convention";
1335
+ const messages$7 = {
1336
1336
  doesNotMatchFormat: "{{type}} name `{{name}}` must match one of the following formats: {{formats}}",
1337
1337
  doesNotMatchFormatTrimmed: "{{type}} name `{{name}}` trimmed as `{{processedName}}` must match one of the following formats: {{formats}}",
1338
1338
  missingAffix: "{{type}} name `{{name}}` must have one of the following {{position}}es: {{affixes}}",
@@ -1362,12 +1362,11 @@ const camelCaseNamingConfig = [
1362
1362
  selector: "typeLike"
1363
1363
  }
1364
1364
  ];
1365
- function create$6(contextWithoutDefaults) {
1365
+ function create$7(contextWithoutDefaults) {
1366
1366
  const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
1367
1367
  const validators = parseOptions(context);
1368
1368
  const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
1369
- function handleMember(validator, node, modifiers) {
1370
- const { key } = node;
1369
+ function handleMember(validator, { key }, modifiers) {
1371
1370
  if (requiresQuoting$1(key, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
1372
1371
  validator(key, modifiers);
1373
1372
  }
@@ -1600,8 +1599,7 @@ function create$6(contextWithoutDefaults) {
1600
1599
  validator: validators.enum
1601
1600
  },
1602
1601
  "TSEnumMember": {
1603
- handler: (node, validator) => {
1604
- const { id } = node;
1602
+ handler: ({ id }, validator) => {
1605
1603
  const modifiers = /* @__PURE__ */ new Set();
1606
1604
  if (requiresQuoting$1(id, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
1607
1605
  validator(id, modifiers);
@@ -1682,8 +1680,8 @@ function create$6(contextWithoutDefaults) {
1682
1680
  }));
1683
1681
  }
1684
1682
  const namingConvention = createEslintRule({
1685
- name: RULE_NAME$6,
1686
- create: create$6,
1683
+ name: RULE_NAME$7,
1684
+ create: create$7,
1687
1685
  defaultOptions: camelCaseNamingConfig,
1688
1686
  meta: {
1689
1687
  docs: {
@@ -1693,7 +1691,7 @@ const namingConvention = createEslintRule({
1693
1691
  },
1694
1692
  fixable: void 0,
1695
1693
  hasSuggestions: false,
1696
- messages: messages$6,
1694
+ messages: messages$7,
1697
1695
  schema: SCHEMA,
1698
1696
  type: "suggestion"
1699
1697
  }
@@ -1813,8 +1811,7 @@ function resolve(sourceCode, node) {
1813
1811
  * @returns A rule `create` function.
1814
1812
  * @template MessageIds - The rule's message identifiers.
1815
1813
  */
1816
- function createUnnecessaryHookRule(config) {
1817
- const { hook, messageIds } = config;
1814
+ function createUnnecessaryHookRule({ hook, messageIds }) {
1818
1815
  const skipComputation = hook === "useMemo";
1819
1816
  return (context) => {
1820
1817
  const { sourceCode } = context;
@@ -1961,15 +1958,15 @@ function resolveFactory(sourceCode, node) {
1961
1958
  }
1962
1959
  //#endregion
1963
1960
  //#region src/rules/no-unnecessary-use-callback/rule.ts
1964
- const RULE_NAME$5 = "no-unnecessary-use-callback";
1961
+ const RULE_NAME$6 = "no-unnecessary-use-callback";
1965
1962
  const MESSAGE_ID_DEFAULT$1 = "default";
1966
1963
  const MESSAGE_ID_INSIDE_USE_EFFECT$1 = "noUnnecessaryUseCallbackInsideUseEffect";
1967
- const messages$5 = {
1964
+ const messages$6 = {
1968
1965
  [MESSAGE_ID_DEFAULT$1]: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
1969
1966
  [MESSAGE_ID_INSIDE_USE_EFFECT$1]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
1970
1967
  };
1971
1968
  const noUnnecessaryUseCallback = createEslintRule({
1972
- name: RULE_NAME$5,
1969
+ name: RULE_NAME$6,
1973
1970
  create: createUnnecessaryHookRule({
1974
1971
  hook: "useCallback",
1975
1972
  messageIds: {
@@ -1985,22 +1982,22 @@ const noUnnecessaryUseCallback = createEslintRule({
1985
1982
  requiresTypeChecking: false
1986
1983
  },
1987
1984
  hasSuggestions: false,
1988
- messages: messages$5,
1985
+ messages: messages$6,
1989
1986
  schema: [],
1990
1987
  type: "suggestion"
1991
1988
  }
1992
1989
  });
1993
1990
  //#endregion
1994
1991
  //#region src/rules/no-unnecessary-use-memo/rule.ts
1995
- const RULE_NAME$4 = "no-unnecessary-use-memo";
1992
+ const RULE_NAME$5 = "no-unnecessary-use-memo";
1996
1993
  const MESSAGE_ID_DEFAULT = "default";
1997
1994
  const MESSAGE_ID_INSIDE_USE_EFFECT = "noUnnecessaryUseMemoInsideUseEffect";
1998
- const messages$4 = {
1995
+ const messages$5 = {
1999
1996
  [MESSAGE_ID_DEFAULT]: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
2000
1997
  [MESSAGE_ID_INSIDE_USE_EFFECT]: "'{{name}}' is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
2001
1998
  };
2002
1999
  const noUnnecessaryUseMemo = createEslintRule({
2003
- name: RULE_NAME$4,
2000
+ name: RULE_NAME$5,
2004
2001
  create: createUnnecessaryHookRule({
2005
2002
  hook: "useMemo",
2006
2003
  messageIds: {
@@ -2016,16 +2013,16 @@ const noUnnecessaryUseMemo = createEslintRule({
2016
2013
  requiresTypeChecking: false
2017
2014
  },
2018
2015
  hasSuggestions: false,
2019
- messages: messages$4,
2016
+ messages: messages$5,
2020
2017
  schema: [],
2021
2018
  type: "suggestion"
2022
2019
  }
2023
2020
  });
2024
2021
  //#endregion
2025
2022
  //#region src/rules/prefer-destructuring-assignment/rule.ts
2026
- const RULE_NAME$3 = "prefer-destructuring-assignment";
2027
- const MESSAGE_ID$2 = "default";
2028
- const messages$3 = { [MESSAGE_ID$2]: "Use destructuring assignment for component props." };
2023
+ const RULE_NAME$4 = "prefer-destructuring-assignment";
2024
+ const MESSAGE_ID$3 = "default";
2025
+ const messages$4 = { [MESSAGE_ID$3]: "Use destructuring assignment for component props." };
2029
2026
  /**
2030
2027
  * Identifiers that cannot be used as a binding name in strict-mode module code.
2031
2028
  * A property may be accessed with such a name (`props.default`), but a shorthand
@@ -2151,7 +2148,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
2151
2148
  const propertyNames = collectPropertyNames(memberReferences);
2152
2149
  if (!(!hasNonMemberReference && propertyNames !== null && !wouldShadowExistingBinding(context.sourceCode, scope, propertyVariable, memberReferences))) {
2153
2150
  for (const { member } of memberReferences) context.report({
2154
- messageId: MESSAGE_ID$2,
2151
+ messageId: MESSAGE_ID$3,
2155
2152
  node: member
2156
2153
  });
2157
2154
  return;
@@ -2168,7 +2165,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
2168
2165
  if (index === 0) return [fixer.replaceTextRange([propsParameter.range[0], nameEnd], pattern), replaceAccess];
2169
2166
  return replaceAccess;
2170
2167
  },
2171
- messageId: MESSAGE_ID$2,
2168
+ messageId: MESSAGE_ID$3,
2172
2169
  node: member
2173
2170
  });
2174
2171
  }
@@ -2187,7 +2184,7 @@ function reportComponent(context, scope, propsParameter, propertyVariable) {
2187
2184
  * @param context - The rule context.
2188
2185
  * @returns The rule listener.
2189
2186
  */
2190
- function create$3(context) {
2187
+ function create$4(context) {
2191
2188
  const { api, visitor } = core.getFunctionComponentCollector(context);
2192
2189
  return {
2193
2190
  ...visitor,
@@ -2205,8 +2202,8 @@ function create$3(context) {
2205
2202
  };
2206
2203
  }
2207
2204
  const preferDestructuringAssignment = createEslintRule({
2208
- name: RULE_NAME$3,
2209
- create: create$3,
2205
+ name: RULE_NAME$4,
2206
+ create: create$4,
2210
2207
  defaultOptions: [],
2211
2208
  meta: {
2212
2209
  docs: {
@@ -2216,12 +2213,364 @@ const preferDestructuringAssignment = createEslintRule({
2216
2213
  },
2217
2214
  fixable: "code",
2218
2215
  hasSuggestions: false,
2219
- messages: messages$3,
2216
+ messages: messages$4,
2220
2217
  schema: [],
2221
2218
  type: "problem"
2222
2219
  }
2223
2220
  });
2224
2221
  //#endregion
2222
+ //#region src/rules/prefer-parameter-destructuring/rule.ts
2223
+ const RULE_NAME$3 = "prefer-parameter-destructuring";
2224
+ const MESSAGE_ID$2 = "default";
2225
+ const messages$3 = { [MESSAGE_ID$2]: "Destructure parameter '{{name}}' in the function signature instead of the body." };
2226
+ /**
2227
+ * Collects the binding names introduced by a pattern (or parameter), including
2228
+ * names nested in object/array patterns, defaults, and rest elements.
2229
+ *
2230
+ * @param node - The pattern node to walk.
2231
+ * @param out - Receives every bound name, in source order.
2232
+ */
2233
+ function collectBoundNames(node, out) {
2234
+ switch (node.type) {
2235
+ case AST_NODE_TYPES.ArrayPattern:
2236
+ for (const element of node.elements) if (element !== null) collectBoundNames(element, out);
2237
+ break;
2238
+ case AST_NODE_TYPES.AssignmentPattern:
2239
+ collectBoundNames(node.left, out);
2240
+ break;
2241
+ case AST_NODE_TYPES.Identifier:
2242
+ out.push(node.name);
2243
+ break;
2244
+ case AST_NODE_TYPES.ObjectPattern:
2245
+ for (const property of node.properties) collectBoundNames(property, out);
2246
+ break;
2247
+ case AST_NODE_TYPES.Property:
2248
+ collectBoundNames(node.value, out);
2249
+ break;
2250
+ case AST_NODE_TYPES.RestElement:
2251
+ collectBoundNames(node.argument, out);
2252
+ break;
2253
+ case AST_NODE_TYPES.TSParameterProperty:
2254
+ collectBoundNames(node.parameter, out);
2255
+ break;
2256
+ default: break;
2257
+ }
2258
+ }
2259
+ /**
2260
+ * Collects every top-level-body object destructuring of the parameter, or
2261
+ * bails when the parameter has any other reference (member access, call
2262
+ * argument, reassignment, a destructure inside a nested block or closure, …) —
2263
+ * those mean the parameter is genuinely used and must stay.
2264
+ *
2265
+ * @param variable - The parameter's resolved scope variable.
2266
+ * @param identifier - The parameter identifier (its default-value write
2267
+ * reference is not a use).
2268
+ * @param body - The function body; only its direct child declarations qualify.
2269
+ * @returns The qualifying destructuring statements in source order, or `null`
2270
+ * when the parameter has a non-destructuring reference.
2271
+ */
2272
+ function collectDestructureStatements(variable, identifier, body) {
2273
+ const statements = [];
2274
+ for (const reference of variable.references) {
2275
+ const referenceIdentifier = reference.identifier;
2276
+ if (referenceIdentifier === identifier) continue;
2277
+ const { parent } = referenceIdentifier;
2278
+ if (parent.type === AST_NODE_TYPES.VariableDeclarator && parent.init === referenceIdentifier && parent.id.type === AST_NODE_TYPES.ObjectPattern && parent.parent.parent === body) statements.push({
2279
+ declaration: parent.parent,
2280
+ declarator: parent,
2281
+ pattern: parent.id
2282
+ });
2283
+ else return null;
2284
+ }
2285
+ statements.sort((left, right) => left.declarator.range[0] - right.declarator.range[0]);
2286
+ return statements;
2287
+ }
2288
+ /**
2289
+ * Collects the binding names of every parameter other than the target one, so
2290
+ * the fix can avoid creating a duplicate parameter name (a SyntaxError in a
2291
+ * non-simple parameter list).
2292
+ *
2293
+ * @param node - The function whose parameters are inspected.
2294
+ * @param parameter - The parameter being rewritten (excluded).
2295
+ * @returns The other parameters' bound names.
2296
+ */
2297
+ function collectOtherParameterNames(node, parameter) {
2298
+ const names = [];
2299
+ for (const other of node.params) if (other !== parameter) collectBoundNames(other, names);
2300
+ return new Set(names);
2301
+ }
2302
+ /**
2303
+ * A structural check for AST nodes reached through visitor keys.
2304
+ *
2305
+ * @param value - The child value to test.
2306
+ * @returns `true` when the value is an AST node.
2307
+ */
2308
+ function isNodeLike(value) {
2309
+ return typeof value === "object" && value !== null && typeof value.type === "string";
2310
+ }
2311
+ /**
2312
+ * Checks whether a pattern subtree contains `await` or `yield` (in a computed
2313
+ * key or default value). Parameter initializers may not contain either, so no
2314
+ * signature form exists and the destructuring is not reported.
2315
+ *
2316
+ * @param root - The pattern to walk.
2317
+ * @param visitorKeys - The parser's visitor keys, used to walk without
2318
+ * following `parent` links.
2319
+ * @returns `true` if `await`/`yield` appears anywhere in the pattern.
2320
+ */
2321
+ function containsAwaitOrYield(root, visitorKeys) {
2322
+ const stack = [root];
2323
+ for (let current = stack.pop(); current !== void 0; current = stack.pop()) {
2324
+ if (current.type === AST_NODE_TYPES.AwaitExpression || current.type === AST_NODE_TYPES.YieldExpression) return true;
2325
+ for (const key of visitorKeys[current.type] ?? []) {
2326
+ const child = current[key];
2327
+ const children = Array.isArray(child) ? child : [child];
2328
+ for (const item of children) if (isNodeLike(item)) stack.push(item);
2329
+ }
2330
+ }
2331
+ return false;
2332
+ }
2333
+ /**
2334
+ * Extracts the identifier a parameter binds, unwrapping a default value
2335
+ * (`obj = {}`); patterns, rest parameters, and TS parameter properties yield
2336
+ * `null`.
2337
+ *
2338
+ * @param parameter - The parameter node.
2339
+ * @returns The parameter's identifier, or `null` when it is not a plain one.
2340
+ */
2341
+ function getParameterIdentifier(parameter) {
2342
+ if (parameter.type === AST_NODE_TYPES.Identifier) return parameter;
2343
+ if (parameter.type === AST_NODE_TYPES.AssignmentPattern && parameter.left.type === AST_NODE_TYPES.Identifier) return parameter.left;
2344
+ return null;
2345
+ }
2346
+ /**
2347
+ * Determines whether any expression inside the patterns (computed keys,
2348
+ * default values, moved type annotations) references a binding that would be
2349
+ * out of scope — or in its temporal dead zone — at the parameter position:
2350
+ * anything declared in the function body, a parameter at or after the target,
2351
+ * or this function's own `arguments`.
2352
+ *
2353
+ * @param query - The rewrite being planned.
2354
+ * @param patterns - The object patterns being moved.
2355
+ * @returns `true` when moving the patterns would break a reference.
2356
+ */
2357
+ function hasUnsafePatternReferences({ body, identifier, node, scope }, patterns) {
2358
+ function inPattern(range) {
2359
+ return patterns.some((pattern) => range[0] >= pattern.range[0] && range[1] <= pattern.range[1]);
2360
+ }
2361
+ const stack = [scope];
2362
+ for (let current = stack.pop(); current !== void 0; current = stack.pop()) {
2363
+ stack.push(...current.childScopes);
2364
+ for (const reference of current.references) {
2365
+ if (!inPattern(reference.identifier.range)) continue;
2366
+ const { resolved } = reference;
2367
+ if (resolved === null) continue;
2368
+ if (resolved.defs.length === 0) {
2369
+ const blockRange = resolved.scope.block.range;
2370
+ if (blockRange[0] >= node.range[0] && blockRange[1] <= node.range[1]) return true;
2371
+ continue;
2372
+ }
2373
+ if (resolved.defs.some((definition) => {
2374
+ const { range } = definition.name;
2375
+ if (inPattern(range)) return false;
2376
+ if (range[1] <= node.range[0] || range[0] >= node.range[1]) return false;
2377
+ if (range[0] >= body.range[0]) return true;
2378
+ return range[0] >= identifier.range[0];
2379
+ })) return true;
2380
+ }
2381
+ }
2382
+ return false;
2383
+ }
2384
+ /**
2385
+ * Checks whether a function body opens with a `"use strict"` directive. A
2386
+ * destructured parameter makes the parameter list non-simple, and a function
2387
+ * with a non-simple parameter list may not contain a `"use strict"` directive,
2388
+ * so such functions are skipped entirely.
2389
+ *
2390
+ * @param body - The function body.
2391
+ * @returns `true` if the body has a `"use strict"` directive.
2392
+ */
2393
+ function hasUseStrictDirective(body) {
2394
+ for (const statement of body.body) {
2395
+ if (statement.type !== AST_NODE_TYPES.ExpressionStatement || typeof statement.directive !== "string") break;
2396
+ if (statement.directive === "use strict") return true;
2397
+ }
2398
+ return false;
2399
+ }
2400
+ /**
2401
+ * Checks whether evaluating the pattern can execute arbitrary code — a default
2402
+ * value or a computed key — as opposed to only performing property reads.
2403
+ *
2404
+ * @param node - The pattern node to walk.
2405
+ * @returns `true` when the pattern contains a default value or computed key.
2406
+ */
2407
+ function patternExecutesCode(node) {
2408
+ if (node.type === AST_NODE_TYPES.AssignmentPattern) return true;
2409
+ if (node.type === AST_NODE_TYPES.Property) return node.computed || patternExecutesCode(node.value);
2410
+ if (node.type === AST_NODE_TYPES.ObjectPattern) return node.properties.some((property) => patternExecutesCode(property));
2411
+ if (node.type === AST_NODE_TYPES.ArrayPattern) return node.elements.some((element) => element !== null && patternExecutesCode(element));
2412
+ if (node.type === AST_NODE_TYPES.RestElement) return patternExecutesCode(node.argument);
2413
+ return false;
2414
+ }
2415
+ /**
2416
+ * Computes the removal range for a declaration, swallowing the whole line
2417
+ * (indentation and trailing newline) when the declaration is alone on it.
2418
+ *
2419
+ * @param sourceCode - Provides the raw text around the declaration.
2420
+ * @param statement - The declaration to remove.
2421
+ * @returns The range to delete.
2422
+ */
2423
+ function statementRemovalRange({ text }, statement) {
2424
+ const [start, end] = statement.range;
2425
+ const lineStart = text.lastIndexOf("\n", start - 1) + 1;
2426
+ const newlineIndex = text.indexOf("\n", end);
2427
+ const lineEnd = newlineIndex === -1 ? text.length : newlineIndex + 1;
2428
+ const leadingIsBlank = text.slice(lineStart, start).trim().length === 0;
2429
+ const trailingIsBlank = text.slice(end, newlineIndex === -1 ? text.length : newlineIndex).trim().length === 0;
2430
+ if (leadingIsBlank && trailingIsBlank) return [lineStart, lineEnd];
2431
+ return [start, end];
2432
+ }
2433
+ /**
2434
+ * Builds the autofix, or returns `null` when the rewrite is not unambiguously
2435
+ * safe: unrelated sibling declarators, unmergeable or annotated patterns,
2436
+ * duplicate or colliding binding names, expressions that reference bindings
2437
+ * unavailable at the parameter position, a defused temporal dead zone, or —
2438
+ * with `allowSideEffectReordering: false` — side effects that would be
2439
+ * reordered.
2440
+ *
2441
+ * @param query - The rewrite being planned.
2442
+ * @returns The fix plan, or `null` when only a report should be emitted.
2443
+ */
2444
+ function planFix(query) {
2445
+ const { allowSideEffectReordering, body, identifier, node, otherParameterNames, sourceCode, statements } = query;
2446
+ const declaratorSet = new Set(statements.map((statement) => statement.declarator));
2447
+ const declarations = [...new Set(statements.map((statement) => statement.declaration))];
2448
+ if (!declarations.every((declaration) => {
2449
+ return declaration.declarations.every((declarator) => declaratorSet.has(declarator));
2450
+ })) return null;
2451
+ const patterns = statements.map((statement) => statement.pattern);
2452
+ if (!allowSideEffectReordering && patterns.some((pattern) => patternExecutesCode(pattern))) {
2453
+ const leadingStatements = new Set(body.body.slice(0, declarations.length));
2454
+ if (!declarations.every((declaration) => leadingStatements.has(declaration))) return null;
2455
+ }
2456
+ if (declarations.some((declaration) => {
2457
+ return sourceCode.getDeclaredVariables(declaration).some((declared) => {
2458
+ return declared.references.some((reference) => reference.identifier.range[0] < declaration.range[0]);
2459
+ });
2460
+ })) return null;
2461
+ if (patterns.some((pattern) => pattern.typeAnnotation !== void 0) && (patterns.length > 1 || identifier.typeAnnotation !== void 0)) return null;
2462
+ if (patterns.length > 1) {
2463
+ if (patterns.some((pattern) => {
2464
+ return pattern.properties.some((property) => property.type === AST_NODE_TYPES.RestElement);
2465
+ })) return null;
2466
+ }
2467
+ const boundNames = [];
2468
+ for (const pattern of patterns) collectBoundNames(pattern, boundNames);
2469
+ if (new Set(boundNames).size !== boundNames.length) return null;
2470
+ if (boundNames.some((name) => otherParameterNames.has(name))) return null;
2471
+ if (hasUnsafePatternReferences(query, patterns)) return null;
2472
+ const [firstPattern] = patterns;
2473
+ let parameterText;
2474
+ if (patterns.length === 1 && firstPattern !== void 0) parameterText = sourceCode.getText(firstPattern);
2475
+ else {
2476
+ const innerTexts = patterns.map((pattern) => sourceCode.getText(pattern).slice(1, -1).trim().replace(/,$/u, "").trim()).filter((text) => text.length > 0);
2477
+ parameterText = innerTexts.length === 0 ? "{}" : `{ ${innerTexts.join(", ")} }`;
2478
+ }
2479
+ if (node.type === AST_NODE_TYPES.ArrowFunctionExpression && sourceCode.getTokenAfter(identifier)?.value === "=>") parameterText = `(${parameterText})`;
2480
+ const parameterRange = [identifier.range[0], identifier.typeAnnotation?.range[0] ?? identifier.range[1]];
2481
+ const removalRanges = declarations.map((declaration) => {
2482
+ return statementRemovalRange(sourceCode, declaration);
2483
+ });
2484
+ return {
2485
+ parameterRange,
2486
+ parameterText,
2487
+ removalRanges
2488
+ };
2489
+ }
2490
+ /**
2491
+ * Reports body destructuring statements of parameters that have no other use,
2492
+ * preferring the pattern in the function signature. The autofix rewrites the
2493
+ * parameter (merging multiple statements into one pattern) and removes the
2494
+ * statements; it is withheld when the rewrite is not unambiguously safe, see
2495
+ * {@link planFix}.
2496
+ *
2497
+ * @param context - The rule context.
2498
+ * @param optionsWithDefault - The resolved rule options.
2499
+ * @returns The rule listener.
2500
+ */
2501
+ function create$3(context, optionsWithDefault) {
2502
+ const [{ allowSideEffectReordering = true }] = optionsWithDefault;
2503
+ function checkFunction(node) {
2504
+ const { body, params } = node;
2505
+ if (body.type !== AST_NODE_TYPES.BlockStatement || hasUseStrictDirective(body)) return;
2506
+ const scope = context.sourceCode.getScope(node);
2507
+ for (const parameter of params) {
2508
+ const identifier = getParameterIdentifier(parameter);
2509
+ if (identifier === null || identifier.name === "this") continue;
2510
+ const variable = scope.variables.find((candidate) => {
2511
+ return candidate.defs.some((definition) => definition.name === identifier);
2512
+ });
2513
+ if (variable?.defs.length !== 1) continue;
2514
+ const statements = collectDestructureStatements(variable, identifier, body);
2515
+ if (statements === null || statements.length === 0) continue;
2516
+ if (statements.some(({ pattern }) => {
2517
+ return containsAwaitOrYield(pattern, context.sourceCode.visitorKeys);
2518
+ })) continue;
2519
+ const otherParameterNames = collectOtherParameterNames(node, parameter);
2520
+ if (otherParameterNames.has(identifier.name)) continue;
2521
+ const plan = planFix({
2522
+ allowSideEffectReordering,
2523
+ body,
2524
+ identifier,
2525
+ node,
2526
+ otherParameterNames,
2527
+ scope,
2528
+ sourceCode: context.sourceCode,
2529
+ statements
2530
+ });
2531
+ for (const [index, statement] of statements.entries()) context.report({
2532
+ data: { name: identifier.name },
2533
+ fix(fixer) {
2534
+ if (index !== 0 || plan === null) return null;
2535
+ return [fixer.replaceTextRange(plan.parameterRange, plan.parameterText), ...plan.removalRanges.map((range) => fixer.removeRange(range))];
2536
+ },
2537
+ messageId: MESSAGE_ID$2,
2538
+ node: statement.declarator
2539
+ });
2540
+ }
2541
+ }
2542
+ return {
2543
+ ArrowFunctionExpression: checkFunction,
2544
+ FunctionDeclaration: checkFunction,
2545
+ FunctionExpression: checkFunction
2546
+ };
2547
+ }
2548
+ const preferParameterDestructuring = createEslintRule({
2549
+ name: RULE_NAME$3,
2550
+ create: create$3,
2551
+ defaultOptions: [{ allowSideEffectReordering: true }],
2552
+ meta: {
2553
+ defaultOptions: [{ allowSideEffectReordering: true }],
2554
+ docs: {
2555
+ description: "Enforce destructuring parameters in the function signature",
2556
+ recommended: false,
2557
+ requiresTypeChecking: false
2558
+ },
2559
+ fixable: "code",
2560
+ hasSuggestions: false,
2561
+ messages: messages$3,
2562
+ schema: [{
2563
+ additionalProperties: false,
2564
+ properties: { allowSideEffectReordering: {
2565
+ description: "Whether the autofix may hoist pattern defaults and computed keys past earlier statements, reordering their side effects. Set to false to withhold the fix in those cases.",
2566
+ type: "boolean"
2567
+ } },
2568
+ type: "object"
2569
+ }],
2570
+ type: "suggestion"
2571
+ }
2572
+ });
2573
+ //#endregion
2225
2574
  //#region src/rules/purity/rule.ts
2226
2575
  const RULE_NAME$2 = "purity";
2227
2576
  const MESSAGE_ID$1 = "impureCall";
@@ -2460,8 +2809,7 @@ function makeRank(order) {
2460
2809
  return Number.POSITIVE_INFINITY;
2461
2810
  };
2462
2811
  }
2463
- function makeFallback(config) {
2464
- const { caseSensitive = true, natural = false, type = "asc" } = config;
2812
+ function makeFallback({ caseSensitive = true, natural = false, type = "asc" }) {
2465
2813
  const sensitivity = caseSensitive ? "variant" : "accent";
2466
2814
  return (a, b) => {
2467
2815
  const result = a.localeCompare(b, "en", {
@@ -2709,6 +3057,7 @@ const plugin = {
2709
3057
  "no-unnecessary-use-callback": noUnnecessaryUseCallback,
2710
3058
  "no-unnecessary-use-memo": noUnnecessaryUseMemo,
2711
3059
  "prefer-destructuring-assignment": preferDestructuringAssignment,
3060
+ "prefer-parameter-destructuring": preferParameterDestructuring,
2712
3061
  "purity": purity,
2713
3062
  "toml-sort-keys": tomlSortKeys,
2714
3063
  "yaml-block-key-blank-lines": yamlBlockKeyBlankLines
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-flawless",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",