eslint-plugin-react-x 3.0.0-next.33 → 3.0.0-next.36

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.
Files changed (2) hide show
  1. package/dist/index.js +1329 -1591
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -68,7 +68,7 @@ const rules$7 = {
68
68
  //#endregion
69
69
  //#region package.json
70
70
  var name$6 = "eslint-plugin-react-x";
71
- var version = "3.0.0-next.33";
71
+ var version = "3.0.0-next.36";
72
72
 
73
73
  //#endregion
74
74
  //#region src/utils/create-rule.ts
@@ -224,7 +224,7 @@ function getTypeVariants(types) {
224
224
  }
225
225
 
226
226
  //#endregion
227
- //#region src/rules/component-hook-factories.ts
227
+ //#region src/rules/component-hook-factories/component-hook-factories.ts
228
228
  const RULE_NAME$62 = "component-hook-factories";
229
229
  var component_hook_factories_default = createRule({
230
230
  meta: {
@@ -283,7 +283,7 @@ function create$62(context) {
283
283
  }
284
284
 
285
285
  //#endregion
286
- //#region src/rules/error-boundaries.ts
286
+ //#region src/rules/error-boundaries/error-boundaries.ts
287
287
  const RULE_NAME$61 = "error-boundaries";
288
288
  var error_boundaries_default = createRule({
289
289
  meta: {
@@ -332,7 +332,7 @@ function create$61(context) {
332
332
  }
333
333
 
334
334
  //#endregion
335
- //#region src/rules/exhaustive-deps.ts
335
+ //#region src/rules/exhaustive-deps/exhaustive-deps.ts
336
336
  const rule$1 = {
337
337
  meta: {
338
338
  type: "suggestion",
@@ -1238,7 +1238,7 @@ function getUnknownDependenciesMessage(reactiveHookName) {
1238
1238
  }
1239
1239
 
1240
1240
  //#endregion
1241
- //#region src/rules/immutability.ts
1241
+ //#region src/rules/immutability/immutability.ts
1242
1242
  const RULE_NAME$60 = "immutability";
1243
1243
  /**
1244
1244
  * Array methods that mutate the array in place.
@@ -1402,7 +1402,7 @@ function create$60(context) {
1402
1402
  }
1403
1403
 
1404
1404
  //#endregion
1405
- //#region src/rules/jsx-dollar.ts
1405
+ //#region src/rules/jsx-dollar/jsx-dollar.ts
1406
1406
  const RULE_NAME$59 = "jsx-dollar";
1407
1407
  var jsx_dollar_default = createRule({
1408
1408
  meta: {
@@ -1460,7 +1460,7 @@ function create$59(context) {
1460
1460
  }
1461
1461
 
1462
1462
  //#endregion
1463
- //#region src/rules/jsx-key-before-spread.ts
1463
+ //#region src/rules/jsx-key-before-spread/jsx-key-before-spread.ts
1464
1464
  const RULE_NAME$58 = "jsx-key-before-spread";
1465
1465
  var jsx_key_before_spread_default = createRule({
1466
1466
  meta: {
@@ -1496,7 +1496,7 @@ function create$58(context) {
1496
1496
  }
1497
1497
 
1498
1498
  //#endregion
1499
- //#region src/rules/jsx-no-comment-textnodes.ts
1499
+ //#region src/rules/jsx-no-comment-textnodes/jsx-no-comment-textnodes.ts
1500
1500
  const RULE_NAME$57 = "jsx-no-comment-textnodes";
1501
1501
  var jsx_no_comment_textnodes_default = createRule({
1502
1502
  meta: {
@@ -1529,7 +1529,7 @@ function create$57(context) {
1529
1529
  }
1530
1530
 
1531
1531
  //#endregion
1532
- //#region src/rules/jsx-no-duplicate-props.ts
1532
+ //#region src/rules/jsx-no-duplicate-props/jsx-no-duplicate-props.ts
1533
1533
  const RULE_NAME$56 = "jsx-no-duplicate-props";
1534
1534
  var jsx_no_duplicate_props_default = createRule({
1535
1535
  meta: {
@@ -1562,7 +1562,7 @@ function create$56(context) {
1562
1562
  }
1563
1563
 
1564
1564
  //#endregion
1565
- //#region src/rules/jsx-shorthand-boolean.ts
1565
+ //#region src/rules/jsx-shorthand-boolean/jsx-shorthand-boolean.ts
1566
1566
  const RULE_NAME$55 = "jsx-shorthand-boolean";
1567
1567
  const defaultOptions$4 = [1];
1568
1568
  const schema$4 = [{
@@ -1608,7 +1608,7 @@ function create$55(context) {
1608
1608
  }
1609
1609
 
1610
1610
  //#endregion
1611
- //#region src/rules/jsx-shorthand-fragment.ts
1611
+ //#region src/rules/jsx-shorthand-fragment/jsx-shorthand-fragment.ts
1612
1612
  const RULE_NAME$54 = "jsx-shorthand-fragment";
1613
1613
  const defaultOptions$3 = [1];
1614
1614
  const schema$3 = [{
@@ -1661,7 +1661,7 @@ function create$54(context) {
1661
1661
  }
1662
1662
 
1663
1663
  //#endregion
1664
- //#region src/rules/jsx-uses-react.ts
1664
+ //#region src/rules/jsx-uses-react/jsx-uses-react.ts
1665
1665
  const RULE_NAME$53 = "jsx-uses-react";
1666
1666
  var jsx_uses_react_default = createRule({
1667
1667
  meta: {
@@ -1704,7 +1704,7 @@ function debugReport(context, node, name) {
1704
1704
  }
1705
1705
 
1706
1706
  //#endregion
1707
- //#region src/rules/jsx-uses-vars.ts
1707
+ //#region src/rules/jsx-uses-vars/jsx-uses-vars.ts
1708
1708
  const RULE_NAME$52 = "jsx-uses-vars";
1709
1709
  var jsx_uses_vars_default = createRule({
1710
1710
  meta: {
@@ -1734,7 +1734,7 @@ function create$52(context) {
1734
1734
  }
1735
1735
 
1736
1736
  //#endregion
1737
- //#region src/rules/no-access-state-in-setstate.ts
1737
+ //#region src/rules/no-access-state-in-setstate/no-access-state-in-setstate.ts
1738
1738
  const RULE_NAME$51 = "no-access-state-in-setstate";
1739
1739
  function isKeyLiteral$2(node, key) {
1740
1740
  return match(key).with({ type: AST_NODE_TYPES.Literal }, constTrue).with({
@@ -1823,7 +1823,7 @@ function create$51(context) {
1823
1823
  }
1824
1824
 
1825
1825
  //#endregion
1826
- //#region src/rules/no-array-index-key.ts
1826
+ //#region src/rules/no-array-index-key/no-array-index-key.ts
1827
1827
  const RULE_NAME$50 = "no-array-index-key";
1828
1828
  const REACT_CHILDREN_METHOD = ["forEach", "map"];
1829
1829
  function getIndexParamPosition(methodName) {
@@ -1952,7 +1952,7 @@ function create$50(context) {
1952
1952
  }
1953
1953
 
1954
1954
  //#endregion
1955
- //#region src/rules/no-children-count.ts
1955
+ //#region src/rules/no-children-count/no-children-count.ts
1956
1956
  const RULE_NAME$49 = "no-children-count";
1957
1957
  var no_children_count_default = createRule({
1958
1958
  meta: {
@@ -1975,7 +1975,7 @@ function create$49(context) {
1975
1975
  }
1976
1976
 
1977
1977
  //#endregion
1978
- //#region src/rules/no-children-for-each.ts
1978
+ //#region src/rules/no-children-for-each/no-children-for-each.ts
1979
1979
  const RULE_NAME$48 = "no-children-for-each";
1980
1980
  var no_children_for_each_default = createRule({
1981
1981
  meta: {
@@ -1998,7 +1998,7 @@ function create$48(context) {
1998
1998
  }
1999
1999
 
2000
2000
  //#endregion
2001
- //#region src/rules/no-children-map.ts
2001
+ //#region src/rules/no-children-map/no-children-map.ts
2002
2002
  const RULE_NAME$47 = "no-children-map";
2003
2003
  var no_children_map_default = createRule({
2004
2004
  meta: {
@@ -2021,7 +2021,7 @@ function create$47(context) {
2021
2021
  }
2022
2022
 
2023
2023
  //#endregion
2024
- //#region src/rules/no-children-only.ts
2024
+ //#region src/rules/no-children-only/no-children-only.ts
2025
2025
  const RULE_NAME$46 = "no-children-only";
2026
2026
  var no_children_only_default = createRule({
2027
2027
  meta: {
@@ -2044,7 +2044,7 @@ function create$46(context) {
2044
2044
  }
2045
2045
 
2046
2046
  //#endregion
2047
- //#region src/rules/no-children-prop.ts
2047
+ //#region src/rules/no-children-prop/no-children-prop.ts
2048
2048
  const RULE_NAME$45 = "no-children-prop";
2049
2049
  var no_children_prop_default = createRule({
2050
2050
  meta: {
@@ -2068,7 +2068,7 @@ function create$45(context) {
2068
2068
  }
2069
2069
 
2070
2070
  //#endregion
2071
- //#region src/rules/no-children-to-array.ts
2071
+ //#region src/rules/no-children-to-array/no-children-to-array.ts
2072
2072
  const RULE_NAME$44 = "no-children-to-array";
2073
2073
  var no_children_to_array_default = createRule({
2074
2074
  meta: {
@@ -2091,7 +2091,7 @@ function create$44(context) {
2091
2091
  }
2092
2092
 
2093
2093
  //#endregion
2094
- //#region src/rules/no-class-component.ts
2094
+ //#region src/rules/no-class-component/no-class-component.ts
2095
2095
  const RULE_NAME$43 = "no-class-component";
2096
2096
  var no_class_component_default = createRule({
2097
2097
  meta: {
@@ -2120,7 +2120,7 @@ function create$43(context) {
2120
2120
  }
2121
2121
 
2122
2122
  //#endregion
2123
- //#region src/rules/no-clone-element.ts
2123
+ //#region src/rules/no-clone-element/no-clone-element.ts
2124
2124
  const RULE_NAME$42 = "no-clone-element";
2125
2125
  var no_clone_element_default = createRule({
2126
2126
  meta: {
@@ -2143,7 +2143,7 @@ function create$42(context) {
2143
2143
  }
2144
2144
 
2145
2145
  //#endregion
2146
- //#region src/rules/no-component-will-mount.ts
2146
+ //#region src/rules/no-component-will-mount/no-component-will-mount.ts
2147
2147
  const RULE_NAME$41 = "no-component-will-mount";
2148
2148
  var no_component_will_mount_default = createRule({
2149
2149
  meta: {
@@ -2176,7 +2176,7 @@ function create$41(context) {
2176
2176
  }
2177
2177
 
2178
2178
  //#endregion
2179
- //#region src/rules/no-component-will-receive-props.ts
2179
+ //#region src/rules/no-component-will-receive-props/no-component-will-receive-props.ts
2180
2180
  const RULE_NAME$40 = "no-component-will-receive-props";
2181
2181
  var no_component_will_receive_props_default = createRule({
2182
2182
  meta: {
@@ -2209,7 +2209,7 @@ function create$40(context) {
2209
2209
  }
2210
2210
 
2211
2211
  //#endregion
2212
- //#region src/rules/no-component-will-update.ts
2212
+ //#region src/rules/no-component-will-update/no-component-will-update.ts
2213
2213
  const RULE_NAME$39 = "no-component-will-update";
2214
2214
  var no_component_will_update_default = createRule({
2215
2215
  meta: {
@@ -2242,7 +2242,7 @@ function create$39(context) {
2242
2242
  }
2243
2243
 
2244
2244
  //#endregion
2245
- //#region src/rules/no-context-provider.ts
2245
+ //#region src/rules/no-context-provider/no-context-provider.ts
2246
2246
  const RULE_NAME$38 = "no-context-provider";
2247
2247
  var no_context_provider_default = createRule({
2248
2248
  meta: {
@@ -2282,7 +2282,7 @@ function create$38(context) {
2282
2282
  }
2283
2283
 
2284
2284
  //#endregion
2285
- //#region src/rules/no-create-ref.ts
2285
+ //#region src/rules/no-create-ref/no-create-ref.ts
2286
2286
  const RULE_NAME$37 = "no-create-ref";
2287
2287
  var no_create_ref_default = createRule({
2288
2288
  meta: {
@@ -2305,7 +2305,7 @@ function create$37(context) {
2305
2305
  }
2306
2306
 
2307
2307
  //#endregion
2308
- //#region src/rules/no-direct-mutation-state.ts
2308
+ //#region src/rules/no-direct-mutation-state/no-direct-mutation-state.ts
2309
2309
  const RULE_NAME$36 = "no-direct-mutation-state";
2310
2310
  function isConstructorFunction(node) {
2311
2311
  return ast.isOneOf([AST_NODE_TYPES.FunctionDeclaration, AST_NODE_TYPES.FunctionExpression])(node) && ast.isMethodOrProperty(node.parent) && node.parent.key.type === AST_NODE_TYPES.Identifier && node.parent.key.name === "constructor";
@@ -2334,7 +2334,7 @@ function create$36(context) {
2334
2334
  }
2335
2335
 
2336
2336
  //#endregion
2337
- //#region src/rules/no-duplicate-key.ts
2337
+ //#region src/rules/no-duplicate-key/no-duplicate-key.ts
2338
2338
  const RULE_NAME$35 = "no-duplicate-key";
2339
2339
  var no_duplicate_key_default = createRule({
2340
2340
  meta: {
@@ -2401,7 +2401,7 @@ function create$35(context) {
2401
2401
  }
2402
2402
 
2403
2403
  //#endregion
2404
- //#region src/rules/no-forward-ref.ts
2404
+ //#region src/rules/no-forward-ref/no-forward-ref.ts
2405
2405
  const RULE_NAME$34 = "no-forward-ref";
2406
2406
  var no_forward_ref_default = createRule({
2407
2407
  meta: {
@@ -2509,7 +2509,7 @@ function getComponentPropsFixes(context, fixer, node, typeArguments) {
2509
2509
  }
2510
2510
 
2511
2511
  //#endregion
2512
- //#region src/rules/no-implicit-key.ts
2512
+ //#region src/rules/no-implicit-key/no-implicit-key.ts
2513
2513
  const RULE_NAME$33 = "no-implicit-key";
2514
2514
  var no_implicit_key_default = createRule({
2515
2515
  meta: {
@@ -2539,7 +2539,7 @@ function create$33(context) {
2539
2539
  }
2540
2540
 
2541
2541
  //#endregion
2542
- //#region src/rules/no-leaked-conditional-rendering.ts
2542
+ //#region src/rules/no-leaked-conditional-rendering/no-leaked-conditional-rendering.ts
2543
2543
  const RULE_NAME$32 = "no-leaked-conditional-rendering";
2544
2544
  var no_leaked_conditional_rendering_default = createRule({
2545
2545
  meta: {
@@ -2608,7 +2608,7 @@ function create$32(context) {
2608
2608
  }
2609
2609
 
2610
2610
  //#endregion
2611
- //#region src/rules/no-missing-component-display-name.ts
2611
+ //#region src/rules/no-missing-component-display-name/no-missing-component-display-name.ts
2612
2612
  const RULE_NAME$31 = "no-missing-component-display-name";
2613
2613
  var no_missing_component_display_name_default = createRule({
2614
2614
  meta: {
@@ -2642,7 +2642,7 @@ function create$31(context) {
2642
2642
  }
2643
2643
 
2644
2644
  //#endregion
2645
- //#region src/rules/no-missing-context-display-name.ts
2645
+ //#region src/rules/no-missing-context-display-name/no-missing-context-display-name.ts
2646
2646
  const RULE_NAME$30 = "no-missing-context-display-name";
2647
2647
  var no_missing_context_display_name_default = createRule({
2648
2648
  meta: {
@@ -2707,7 +2707,7 @@ function create$30(context) {
2707
2707
  }
2708
2708
 
2709
2709
  //#endregion
2710
- //#region src/rules/no-missing-key.ts
2710
+ //#region src/rules/no-missing-key/no-missing-key.ts
2711
2711
  const RULE_NAME$29 = "no-missing-key";
2712
2712
  var no_missing_key_default = createRule({
2713
2713
  meta: {
@@ -2793,7 +2793,7 @@ function create$29(ctx) {
2793
2793
  }
2794
2794
 
2795
2795
  //#endregion
2796
- //#region src/rules/no-misused-capture-owner-stack.ts
2796
+ //#region src/rules/no-misused-capture-owner-stack/no-misused-capture-owner-stack.ts
2797
2797
  const RULE_NAME$28 = "no-misused-capture-owner-stack";
2798
2798
  var no_misused_capture_owner_stack_default = createRule({
2799
2799
  meta: {
@@ -2839,7 +2839,7 @@ function isDevelopmentOnlyCheck(node) {
2839
2839
  }
2840
2840
 
2841
2841
  //#endregion
2842
- //#region src/rules/no-nested-component-definitions.ts
2842
+ //#region src/rules/no-nested-component-definitions/no-nested-component-definitions.ts
2843
2843
  const RULE_NAME$27 = "no-nested-component-definitions";
2844
2844
  var no_nested_component_definitions_default = createRule({
2845
2845
  meta: {
@@ -2957,7 +2957,7 @@ function isInsideCreateElementProps(context, node) {
2957
2957
  }
2958
2958
 
2959
2959
  //#endregion
2960
- //#region src/rules/no-nested-lazy-component-declarations.ts
2960
+ //#region src/rules/no-nested-lazy-component-declarations/no-nested-lazy-component-declarations.ts
2961
2961
  const RULE_NAME$26 = "no-nested-lazy-component-declarations";
2962
2962
  var no_nested_lazy_component_declarations_default = createRule({
2963
2963
  meta: {
@@ -2998,7 +2998,7 @@ function create$26(context) {
2998
2998
  }
2999
2999
 
3000
3000
  //#endregion
3001
- //#region src/rules/no-redundant-should-component-update.ts
3001
+ //#region src/rules/no-redundant-should-component-update/no-redundant-should-component-update.ts
3002
3002
  const RULE_NAME$25 = "no-redundant-should-component-update";
3003
3003
  function isShouldComponentUpdate(node) {
3004
3004
  return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === "shouldComponentUpdate";
@@ -3031,7 +3031,7 @@ function create$25(context) {
3031
3031
  }
3032
3032
 
3033
3033
  //#endregion
3034
- //#region src/rules/no-set-state-in-component-did-mount.ts
3034
+ //#region src/rules/no-set-state-in-component-did-mount/no-set-state-in-component-did-mount.ts
3035
3035
  const RULE_NAME$24 = "no-set-state-in-component-did-mount";
3036
3036
  var no_set_state_in_component_did_mount_default = createRule({
3037
3037
  meta: {
@@ -3061,7 +3061,7 @@ function create$24(context) {
3061
3061
  }
3062
3062
 
3063
3063
  //#endregion
3064
- //#region src/rules/no-set-state-in-component-did-update.ts
3064
+ //#region src/rules/no-set-state-in-component-did-update/no-set-state-in-component-did-update.ts
3065
3065
  const RULE_NAME$23 = "no-set-state-in-component-did-update";
3066
3066
  var no_set_state_in_component_did_update_default = createRule({
3067
3067
  meta: {
@@ -3091,7 +3091,7 @@ function create$23(context) {
3091
3091
  }
3092
3092
 
3093
3093
  //#endregion
3094
- //#region src/rules/no-set-state-in-component-will-update.ts
3094
+ //#region src/rules/no-set-state-in-component-will-update/no-set-state-in-component-will-update.ts
3095
3095
  const RULE_NAME$22 = "no-set-state-in-component-will-update";
3096
3096
  var no_set_state_in_component_will_update_default = createRule({
3097
3097
  meta: {
@@ -3121,7 +3121,7 @@ function create$22(context) {
3121
3121
  }
3122
3122
 
3123
3123
  //#endregion
3124
- //#region src/rules/no-unnecessary-use-callback.ts
3124
+ //#region src/rules/no-unnecessary-use-callback/no-unnecessary-use-callback.ts
3125
3125
  const RULE_NAME$21 = "no-unnecessary-use-callback";
3126
3126
  var no_unnecessary_use_callback_default = createRule({
3127
3127
  meta: {
@@ -3198,7 +3198,7 @@ function checkForUsageInsideUseEffect$1(sourceCode, node) {
3198
3198
  }
3199
3199
 
3200
3200
  //#endregion
3201
- //#region src/rules/no-unnecessary-use-memo.ts
3201
+ //#region src/rules/no-unnecessary-use-memo/no-unnecessary-use-memo.ts
3202
3202
  const RULE_NAME$20 = "no-unnecessary-use-memo";
3203
3203
  var no_unnecessary_use_memo_default = createRule({
3204
3204
  meta: {
@@ -3279,7 +3279,7 @@ function checkForUsageInsideUseEffect(sourceCode, node) {
3279
3279
  }
3280
3280
 
3281
3281
  //#endregion
3282
- //#region src/rules/no-unnecessary-use-prefix.ts
3282
+ //#region src/rules/no-unnecessary-use-prefix/no-unnecessary-use-prefix.ts
3283
3283
  const RULE_NAME$19 = "no-unnecessary-use-prefix";
3284
3284
  const WELL_KNOWN_HOOKS = ["useMDXComponents"];
3285
3285
  function containsUseComments(context, node) {
@@ -3315,7 +3315,7 @@ function create$19(context) {
3315
3315
  }
3316
3316
 
3317
3317
  //#endregion
3318
- //#region src/rules/no-unsafe-component-will-mount.ts
3318
+ //#region src/rules/no-unsafe-component-will-mount/no-unsafe-component-will-mount.ts
3319
3319
  const RULE_NAME$18 = "no-unsafe-component-will-mount";
3320
3320
  var no_unsafe_component_will_mount_default = createRule({
3321
3321
  meta: {
@@ -3343,7 +3343,7 @@ function create$18(context) {
3343
3343
  }
3344
3344
 
3345
3345
  //#endregion
3346
- //#region src/rules/no-unsafe-component-will-receive-props.ts
3346
+ //#region src/rules/no-unsafe-component-will-receive-props/no-unsafe-component-will-receive-props.ts
3347
3347
  const RULE_NAME$17 = "no-unsafe-component-will-receive-props";
3348
3348
  var no_unsafe_component_will_receive_props_default = createRule({
3349
3349
  meta: {
@@ -3371,7 +3371,7 @@ function create$17(context) {
3371
3371
  }
3372
3372
 
3373
3373
  //#endregion
3374
- //#region src/rules/no-unsafe-component-will-update.ts
3374
+ //#region src/rules/no-unsafe-component-will-update/no-unsafe-component-will-update.ts
3375
3375
  const RULE_NAME$16 = "no-unsafe-component-will-update";
3376
3376
  var no_unsafe_component_will_update_default = createRule({
3377
3377
  meta: {
@@ -3399,7 +3399,7 @@ function create$16(context) {
3399
3399
  }
3400
3400
 
3401
3401
  //#endregion
3402
- //#region src/rules/no-unstable-context-value.ts
3402
+ //#region src/rules/no-unstable-context-value/no-unstable-context-value.ts
3403
3403
  const RULE_NAME$15 = "no-unstable-context-value";
3404
3404
  var no_unstable_context_value_default = createRule({
3405
3405
  meta: {
@@ -3463,7 +3463,7 @@ function isContextName(name, isReact18OrBelow) {
3463
3463
  }
3464
3464
 
3465
3465
  //#endregion
3466
- //#region src/rules/no-unstable-default-props.ts
3466
+ //#region src/rules/no-unstable-default-props/no-unstable-default-props.ts
3467
3467
  const RULE_NAME$14 = "no-unstable-default-props";
3468
3468
  const defaultOptions$2 = [{ safeDefaultProps: [] }];
3469
3469
  const schema$2 = [{
@@ -3538,7 +3538,7 @@ function create$14(context, [options]) {
3538
3538
  }
3539
3539
 
3540
3540
  //#endregion
3541
- //#region src/rules/no-unused-class-component-members.ts
3541
+ //#region src/rules/no-unused-class-component-members/no-unused-class-component-members.ts
3542
3542
  const RULE_NAME$13 = "no-unused-class-component-members";
3543
3543
  const LIFECYCLE_METHODS = new Set([
3544
3544
  "componentDidCatch",
@@ -3654,7 +3654,7 @@ function create$13(context) {
3654
3654
  }
3655
3655
 
3656
3656
  //#endregion
3657
- //#region src/rules/no-unused-props.ts
3657
+ //#region src/rules/no-unused-props/no-unused-props.ts
3658
3658
  const RULE_NAME$12 = "no-unused-props";
3659
3659
  var no_unused_props_default = createRule({
3660
3660
  meta: {
@@ -3766,7 +3766,7 @@ function reportUnusedProp(context, services, prop) {
3766
3766
  }
3767
3767
 
3768
3768
  //#endregion
3769
- //#region src/rules/no-unused-state.ts
3769
+ //#region src/rules/no-unused-state/no-unused-state.ts
3770
3770
  const RULE_NAME$11 = "no-unused-state";
3771
3771
  function isKeyLiteral(node, key) {
3772
3772
  return match(key).with({ type: AST_NODE_TYPES.Literal }, constTrue).with({
@@ -3893,7 +3893,7 @@ function create$11(context) {
3893
3893
  }
3894
3894
 
3895
3895
  //#endregion
3896
- //#region src/rules/no-use-context.ts
3896
+ //#region src/rules/no-use-context/no-use-context.ts
3897
3897
  const RULE_NAME$10 = "no-use-context";
3898
3898
  var no_use_context_default = createRule({
3899
3899
  meta: {
@@ -3971,7 +3971,7 @@ function getCorrelativeTokens(context, node) {
3971
3971
  }
3972
3972
 
3973
3973
  //#endregion
3974
- //#region src/rules/no-useless-fragment.ts
3974
+ //#region src/rules/no-useless-fragment/no-useless-fragment.ts
3975
3975
  const RULE_NAME$9 = "no-useless-fragment";
3976
3976
  const defaultOptions$1 = [{
3977
3977
  allowEmptyFragment: false,
@@ -4111,7 +4111,7 @@ function trimLikeReact(text) {
4111
4111
  }
4112
4112
 
4113
4113
  //#endregion
4114
- //#region src/rules/prefer-destructuring-assignment.ts
4114
+ //#region src/rules/prefer-destructuring-assignment/prefer-destructuring-assignment.ts
4115
4115
  const RULE_NAME$8 = "prefer-destructuring-assignment";
4116
4116
  var prefer_destructuring_assignment_default = createRule({
4117
4117
  meta: {
@@ -4147,7 +4147,7 @@ function create$8(context) {
4147
4147
  }
4148
4148
 
4149
4149
  //#endregion
4150
- //#region src/rules/prefer-namespace-import.ts
4150
+ //#region src/rules/prefer-namespace-import/prefer-namespace-import.ts
4151
4151
  const RULE_NAME$7 = "prefer-namespace-import";
4152
4152
  var prefer_namespace_import_default = createRule({
4153
4153
  meta: {
@@ -4184,7 +4184,7 @@ function create$7(context) {
4184
4184
  }
4185
4185
 
4186
4186
  //#endregion
4187
- //#region src/rules/purity.ts
4187
+ //#region src/rules/purity/purity.ts
4188
4188
  const RULE_NAME$6 = "purity";
4189
4189
  function isImpureMemberCall(node) {
4190
4190
  if (node.callee.type !== AST_NODE_TYPES.MemberExpression) return false;
@@ -4268,7 +4268,7 @@ function create$6(context) {
4268
4268
  }
4269
4269
 
4270
4270
  //#endregion
4271
- //#region src/rules/refs.ts
4271
+ //#region src/rules/refs/refs.ts
4272
4272
  const RULE_NAME$5 = "refs";
4273
4273
  function isWriteAccess(node) {
4274
4274
  const { parent } = node;
@@ -4447,707 +4447,223 @@ function create$5(context) {
4447
4447
  }
4448
4448
 
4449
4449
  //#endregion
4450
- //#region src/rules/rules-of-hooks.ts
4450
+ //#region src/rules/rules-of-hooks/code-path-analyzer.mts
4451
+ function assert(cond, message) {
4452
+ if (!cond) throw new Error(message ?? "Assertion violated.");
4453
+ }
4451
4454
  /**
4452
- * Catch all identifiers that begin with "use" followed by an uppercase Latin
4453
- * character to exclude identifiers like "user".
4455
+ * Checks whether or not a given segment is reachable.
4454
4456
  */
4455
- function isHookName(s) {
4456
- return s === "use" || /^use[A-Z0-9]/.test(s);
4457
+ function isReachable$1(segment) {
4458
+ return segment.reachable;
4457
4459
  }
4458
4460
  /**
4459
- * We consider hooks to be a hook name identifier or a member expression
4460
- * containing a hook name.
4461
+ * A code path segment.
4461
4462
  */
4462
- function isHook(node) {
4463
- if (node.type === "Identifier") return isHookName(node.name);
4464
- else if (node.type === "MemberExpression" && !node.computed && isHook(node.property)) {
4465
- const obj = node.object;
4466
- return obj.type === "Identifier" && /^[A-Z].*/.test(obj.name);
4467
- } else return false;
4468
- }
4463
+ var CodePathSegment = class CodePathSegment {
4464
+ /**
4465
+ * The identifier of this code path.
4466
+ * Rules use it to store additional information of each rule.
4467
+ */
4468
+ id;
4469
+ /** An array of the next reachable segments. */
4470
+ nextSegments;
4471
+ /** An array of the previous reachable segments. */
4472
+ prevSegments;
4473
+ /** An array of all next segments (including unreachable). */
4474
+ allNextSegments;
4475
+ /** An array of all previous segments (including unreachable). */
4476
+ allPrevSegments;
4477
+ /** Whether this segment is reachable. */
4478
+ reachable;
4479
+ /** Internal bookkeeping data. */
4480
+ internal;
4481
+ /**
4482
+ * @param id An identifier.
4483
+ * @param allPrevSegments An array of the previous segments (including unreachable).
4484
+ * @param reachable Whether this segment is reachable.
4485
+ */
4486
+ constructor(id, allPrevSegments, reachable) {
4487
+ this.id = id;
4488
+ this.nextSegments = [];
4489
+ this.prevSegments = allPrevSegments.filter(isReachable$1);
4490
+ this.allNextSegments = [];
4491
+ this.allPrevSegments = allPrevSegments;
4492
+ this.reachable = reachable;
4493
+ this.internal = {
4494
+ used: false,
4495
+ loopedPrevSegments: []
4496
+ };
4497
+ }
4498
+ /**
4499
+ * Checks if a given previous segment is coming from the end of a loop.
4500
+ */
4501
+ isLoopedPrevSegment(segment) {
4502
+ return this.internal.loopedPrevSegments.includes(segment);
4503
+ }
4504
+ /**
4505
+ * Creates the root segment.
4506
+ */
4507
+ static newRoot(id) {
4508
+ return new CodePathSegment(id, [], true);
4509
+ }
4510
+ /**
4511
+ * Creates a segment that follows given segments.
4512
+ */
4513
+ static newNext(id, allPrevSegments) {
4514
+ return new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), allPrevSegments.some(isReachable$1));
4515
+ }
4516
+ /**
4517
+ * Creates an unreachable segment that follows given segments.
4518
+ */
4519
+ static newUnreachable(id, allPrevSegments) {
4520
+ const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
4521
+ CodePathSegment.markUsed(segment);
4522
+ return segment;
4523
+ }
4524
+ /**
4525
+ * Creates a segment that follows given segments.
4526
+ * This factory method does not connect with `allPrevSegments`.
4527
+ * But this inherits `reachable` flag.
4528
+ */
4529
+ static newDisconnected(id, allPrevSegments) {
4530
+ return new CodePathSegment(id, [], allPrevSegments.some(isReachable$1));
4531
+ }
4532
+ /**
4533
+ * Makes a given segment being used.
4534
+ * Also registers the segment into the previous segments as a next.
4535
+ */
4536
+ static markUsed(segment) {
4537
+ if (segment.internal.used) return;
4538
+ segment.internal.used = true;
4539
+ let i;
4540
+ if (segment.reachable) for (i = 0; i < segment.allPrevSegments.length; ++i) {
4541
+ const prevSegment = segment.allPrevSegments[i];
4542
+ prevSegment.allNextSegments.push(segment);
4543
+ prevSegment.nextSegments.push(segment);
4544
+ }
4545
+ else for (i = 0; i < segment.allPrevSegments.length; ++i) segment.allPrevSegments[i].allNextSegments.push(segment);
4546
+ }
4547
+ /**
4548
+ * Marks a previous segment as looped.
4549
+ */
4550
+ static markPrevSegmentAsLooped(segment, prevSegment) {
4551
+ segment.internal.loopedPrevSegments.push(prevSegment);
4552
+ }
4553
+ /**
4554
+ * Replaces unused segments with the previous segments of each unused segment.
4555
+ */
4556
+ static flattenUnusedSegments(segments) {
4557
+ const done = Object.create(null);
4558
+ const retv = [];
4559
+ for (let i = 0; i < segments.length; ++i) {
4560
+ const segment = segments[i];
4561
+ if (done[segment.id]) continue;
4562
+ if (!segment.internal.used) for (let j = 0; j < segment.allPrevSegments.length; ++j) {
4563
+ const prevSegment = segment.allPrevSegments[j];
4564
+ if (!done[prevSegment.id]) {
4565
+ done[prevSegment.id] = true;
4566
+ retv.push(prevSegment);
4567
+ }
4568
+ }
4569
+ else {
4570
+ done[segment.id] = true;
4571
+ retv.push(segment);
4572
+ }
4573
+ }
4574
+ return retv;
4575
+ }
4576
+ };
4469
4577
  /**
4470
- * Checks if the node is a React component name. React component names must
4471
- * always start with an uppercase letter.
4578
+ * Gets whether or not a given segment is reachable.
4472
4579
  */
4473
- function isComponentName(node) {
4474
- return node.type === "Identifier" && /^[A-Z]/.test(node.name);
4475
- }
4476
- function isReactFunction(node, functionName) {
4477
- return "name" in node && node.name === functionName || node.type === "MemberExpression" && "name" in node.object && node.object.name === "React" && "name" in node.property && node.property.name === functionName;
4580
+ function isReachable(segment) {
4581
+ return segment.reachable;
4478
4582
  }
4479
4583
  /**
4480
- * Checks if the node is a callback argument of forwardRef. This render function
4481
- * should follow the rules of hooks.
4584
+ * Creates new segments from the specific range of `context.segmentsList`.
4585
+ *
4586
+ * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
4587
+ * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`.
4588
+ * This `h` is from `b`, `d`, and `f`.
4482
4589
  */
4483
- function isForwardRefCallback(node) {
4484
- return !!(node.parent && "callee" in node.parent && node.parent.callee && isReactFunction(node.parent.callee, "forwardRef"));
4590
+ function makeSegments(context, begin, end, create) {
4591
+ const list = context.segmentsList;
4592
+ const normalizedBegin = begin >= 0 ? begin : list.length + begin;
4593
+ const normalizedEnd = end >= 0 ? end : list.length + end;
4594
+ const segments = [];
4595
+ for (let i = 0; i < context.count; ++i) {
4596
+ const allPrevSegments = [];
4597
+ for (let j = normalizedBegin; j <= normalizedEnd; ++j) allPrevSegments.push(list[j][i]);
4598
+ segments.push(create(context.idGenerator.next(), allPrevSegments));
4599
+ }
4600
+ return segments;
4485
4601
  }
4486
4602
  /**
4487
- * Checks if the node is a callback argument of React.memo. This anonymous
4488
- * functional component should follow the rules of hooks.
4603
+ * `segments` becomes doubly in a `finally` block. Then if a code path exits by a
4604
+ * control statement (such as `break`, `continue`) from the `finally` block, the
4605
+ * destination's segments may be half of the source segments. In that case, this
4606
+ * merges segments.
4489
4607
  */
4490
- function isMemoCallback(node) {
4491
- return !!(node.parent && "callee" in node.parent && node.parent.callee && isReactFunction(node.parent.callee, "memo"));
4492
- }
4493
- function isInsideComponentOrHook(node) {
4494
- while (node) {
4495
- const functionName = getFunctionName(node);
4496
- if (functionName) {
4497
- if (isComponentName(functionName) || isHook(functionName)) return true;
4498
- }
4499
- if (isForwardRefCallback(node) || isMemoCallback(node)) return true;
4500
- node = node.parent;
4608
+ function mergeExtraSegments(context, segments) {
4609
+ let currentSegments = segments;
4610
+ while (currentSegments.length > context.count) {
4611
+ const merged = [];
4612
+ for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) merged.push(CodePathSegment.newNext(context.idGenerator.next(), [currentSegments[i], currentSegments[i + length]]));
4613
+ currentSegments = merged;
4501
4614
  }
4502
- return false;
4615
+ return currentSegments;
4503
4616
  }
4504
- function isInsideDoWhileLoop(node) {
4505
- while (node) {
4506
- if (node.type === "DoWhileStatement") return true;
4507
- node = node.parent;
4617
+ /**
4618
+ * A class to manage forking.
4619
+ */
4620
+ var ForkContext = class ForkContext {
4621
+ idGenerator;
4622
+ upper;
4623
+ count;
4624
+ segmentsList;
4625
+ constructor(idGenerator, upper, count) {
4626
+ this.idGenerator = idGenerator;
4627
+ this.upper = upper;
4628
+ this.count = count;
4629
+ this.segmentsList = [];
4508
4630
  }
4509
- return false;
4510
- }
4511
- function isInsideTryCatch(node) {
4512
- while (node) {
4513
- if (node.type === "TryStatement" || node.type === "CatchClause") return true;
4514
- node = node.parent;
4631
+ /** The head segments. */
4632
+ get head() {
4633
+ const list = this.segmentsList;
4634
+ return list.length === 0 ? [] : list[list.length - 1];
4515
4635
  }
4516
- return false;
4517
- }
4518
- function getNodeWithoutReactNamespace(node) {
4519
- if (node.type === "MemberExpression" && node.object.type === "Identifier" && node.object.name === "React" && node.property.type === "Identifier" && !node.computed) return node.property;
4520
- return node;
4521
- }
4522
- function isEffectIdentifier(node, additionalHooks) {
4523
- if (node.type === "Identifier" && (node.name === "useEffect" || node.name === "useLayoutEffect" || node.name === "useInsertionEffect")) return true;
4524
- if (additionalHooks && node.type === "Identifier") return additionalHooks.test(node.name);
4525
- return false;
4526
- }
4527
- function isUseEffectEventIdentifier(node) {
4528
- return node.type === "Identifier" && node.name === "useEffectEvent";
4529
- }
4530
- function useEffectEventError(fn, called) {
4531
- if (fn === null) return "React Hook \"useEffectEvent\" can only be called at the top level of your component. It cannot be passed down.";
4532
- return `\`${fn}\` is a function created with React Hook "useEffectEvent", and can only be called from Effects and Effect Events in the same component.` + (called ? "" : " It cannot be assigned to a variable or passed down.");
4533
- }
4534
- function isUseIdentifier(node) {
4535
- return isReactFunction(node, "use");
4536
- }
4537
- const rule = {
4538
- meta: {
4539
- type: "problem",
4540
- docs: {
4541
- description: "Enforces the Rules of Hooks.",
4542
- recommended: true,
4543
- url: "https://react.dev/reference/rules/rules-of-hooks"
4544
- },
4545
- schema: [{
4546
- type: "object",
4547
- additionalProperties: false,
4548
- properties: { additionalHooks: { type: "string" } }
4549
- }]
4550
- },
4551
- create(context) {
4552
- context.settings;
4553
- const rawOptions = context.options && context.options[0];
4554
- const additionalEffectHooks = rawOptions && rawOptions.additionalHooks ? new RegExp(rawOptions.additionalHooks) : getSettingsFromContext(context).additionalEffectHooks;
4555
- let lastEffect = null;
4556
- const codePathReactHooksMapStack = [];
4557
- const codePathSegmentStack = [];
4558
- const useEffectEventFunctions = /* @__PURE__ */ new WeakSet();
4559
- function recordAllUseEffectEventFunctions(scope) {
4560
- for (const reference of scope.references) {
4561
- const parent = reference.identifier.parent;
4562
- if (parent?.type === "VariableDeclarator" && parent.init && parent.init.type === "CallExpression" && parent.init.callee && isUseEffectEventIdentifier(parent.init.callee)) {
4563
- if (reference.resolved === null) throw new Error("Unexpected null reference.resolved");
4564
- for (const ref of reference.resolved.references) if (ref !== reference) useEffectEventFunctions.add(ref.identifier);
4565
- }
4566
- }
4567
- }
4568
- /**
4569
- * SourceCode that also works down to ESLint 3.0.0
4570
- */
4571
- const getSourceCode = typeof context.getSourceCode === "function" ? () => {
4572
- return context.getSourceCode();
4573
- } : () => {
4574
- return context.sourceCode;
4575
- };
4576
- /**
4577
- * SourceCode#getScope that also works down to ESLint 3.0.0
4578
- */
4579
- const getScope = typeof context.getScope === "function" ? () => {
4580
- return context.getScope();
4581
- } : (node) => {
4582
- return getSourceCode().getScope(node);
4583
- };
4584
- function hasFlowSuppression(node, suppression) {
4585
- const comments = getSourceCode().getAllComments();
4586
- const flowSuppressionRegex = new RegExp("\\$FlowFixMe\\[" + suppression + "\\]");
4587
- return comments.some((commentNode) => flowSuppressionRegex.test(commentNode.value) && commentNode.loc != null && node.loc != null && commentNode.loc.end.line === node.loc.start.line - 1);
4588
- }
4589
- const analyzer = new CodePathAnalyzer({
4590
- onCodePathSegmentStart: (segment) => codePathSegmentStack.push(segment),
4591
- onCodePathSegmentEnd: () => codePathSegmentStack.pop(),
4592
- onCodePathStart: () => codePathReactHooksMapStack.push(/* @__PURE__ */ new Map()),
4593
- onCodePathEnd(codePath, codePathNode) {
4594
- const reactHooksMap = codePathReactHooksMapStack.pop();
4595
- if (reactHooksMap?.size === 0) return;
4596
- else if (typeof reactHooksMap === "undefined") throw new Error("Unexpected undefined reactHooksMap");
4597
- const cyclic = /* @__PURE__ */ new Set();
4598
- /**
4599
- * Count the number of code paths from the start of the function to this
4600
- * segment. For example:
4601
- *
4602
- * ```js
4603
- * function MyComponent() {
4604
- * if (condition) {
4605
- * // Segment 1
4606
- * } else {
4607
- * // Segment 2
4608
- * }
4609
- * // Segment 3
4610
- * }
4611
- * ```
4612
- *
4613
- * Segments 1 and 2 have one path to the beginning of `MyComponent` and
4614
- * segment 3 has two paths to the beginning of `MyComponent` since we
4615
- * could have either taken the path of segment 1 or segment 2.
4616
- *
4617
- * Populates `cyclic` with cyclic segments.
4618
- */
4619
- function countPathsFromStart(segment, pathHistory) {
4620
- const { cache } = countPathsFromStart;
4621
- let paths = cache.get(segment.id);
4622
- const pathList = new Set(pathHistory);
4623
- if (pathList.has(segment.id)) {
4624
- const pathArray = [...pathList];
4625
- const cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
4626
- for (const cyclicSegment of cyclicSegments) cyclic.add(cyclicSegment);
4627
- return BigInt("0");
4628
- }
4629
- pathList.add(segment.id);
4630
- if (paths !== void 0) return paths;
4631
- if (codePath.thrownSegments.includes(segment)) paths = BigInt("0");
4632
- else if (segment.prevSegments.length === 0) paths = BigInt("1");
4633
- else {
4634
- paths = BigInt("0");
4635
- for (const prevSegment of segment.prevSegments) paths += countPathsFromStart(prevSegment, pathList);
4636
- }
4637
- if (segment.reachable && paths === BigInt("0")) cache.delete(segment.id);
4638
- else cache.set(segment.id, paths);
4639
- return paths;
4640
- }
4641
- /**
4642
- * Count the number of code paths from this segment to the end of the
4643
- * function. For example:
4644
- *
4645
- * ```js
4646
- * function MyComponent() {
4647
- * // Segment 1
4648
- * if (condition) {
4649
- * // Segment 2
4650
- * } else {
4651
- * // Segment 3
4652
- * }
4653
- * }
4654
- * ```
4655
- *
4656
- * Segments 2 and 3 have one path to the end of `MyComponent` and
4657
- * segment 1 has two paths to the end of `MyComponent` since we could
4658
- * either take the path of segment 1 or segment 2.
4659
- *
4660
- * Populates `cyclic` with cyclic segments.
4661
- */
4662
- function countPathsToEnd(segment, pathHistory) {
4663
- const { cache } = countPathsToEnd;
4664
- let paths = cache.get(segment.id);
4665
- const pathList = new Set(pathHistory);
4666
- if (pathList.has(segment.id)) {
4667
- const pathArray = Array.from(pathList);
4668
- const cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
4669
- for (const cyclicSegment of cyclicSegments) cyclic.add(cyclicSegment);
4670
- return BigInt("0");
4671
- }
4672
- pathList.add(segment.id);
4673
- if (paths !== void 0) return paths;
4674
- if (codePath.thrownSegments.includes(segment)) paths = BigInt("0");
4675
- else if (segment.nextSegments.length === 0) paths = BigInt("1");
4676
- else {
4677
- paths = BigInt("0");
4678
- for (const nextSegment of segment.nextSegments) paths += countPathsToEnd(nextSegment, pathList);
4679
- }
4680
- cache.set(segment.id, paths);
4681
- return paths;
4682
- }
4683
- /**
4684
- * Gets the shortest path length to the start of a code path.
4685
- * For example:
4686
- *
4687
- * ```js
4688
- * function MyComponent() {
4689
- * if (condition) {
4690
- * // Segment 1
4691
- * }
4692
- * // Segment 2
4693
- * }
4694
- * ```
4695
- *
4696
- * There is only one path from segment 1 to the code path start. Its
4697
- * length is one so that is the shortest path.
4698
- *
4699
- * There are two paths from segment 2 to the code path start. One
4700
- * through segment 1 with a length of two and another directly to the
4701
- * start with a length of one. The shortest path has a length of one
4702
- * so we would return that.
4703
- */
4704
- function shortestPathLengthToStart(segment) {
4705
- const { cache } = shortestPathLengthToStart;
4706
- let length = cache.get(segment.id);
4707
- if (length === null) return Infinity;
4708
- if (length !== void 0) return length;
4709
- cache.set(segment.id, null);
4710
- if (segment.prevSegments.length === 0) length = 1;
4711
- else {
4712
- length = Infinity;
4713
- for (const prevSegment of segment.prevSegments) {
4714
- const prevLength = shortestPathLengthToStart(prevSegment);
4715
- if (prevLength < length) length = prevLength;
4716
- }
4717
- length += 1;
4718
- }
4719
- cache.set(segment.id, length);
4720
- return length;
4721
- }
4722
- countPathsFromStart.cache = /* @__PURE__ */ new Map();
4723
- countPathsToEnd.cache = /* @__PURE__ */ new Map();
4724
- shortestPathLengthToStart.cache = /* @__PURE__ */ new Map();
4725
- const allPathsFromStartToEnd = countPathsToEnd(codePath.initialSegment);
4726
- const codePathFunctionName = getFunctionName(codePathNode);
4727
- const isSomewhereInsideComponentOrHook = isInsideComponentOrHook(codePathNode);
4728
- const isDirectlyInsideComponentOrHook = codePathFunctionName ? isComponentName(codePathFunctionName) || isHook(codePathFunctionName) : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode);
4729
- let shortestFinalPathLength = Infinity;
4730
- for (const finalSegment of codePath.finalSegments) {
4731
- if (!finalSegment.reachable) continue;
4732
- const length = shortestPathLengthToStart(finalSegment);
4733
- if (length < shortestFinalPathLength) shortestFinalPathLength = length;
4734
- }
4735
- for (const [segment, reactHooks] of reactHooksMap) {
4736
- if (!segment.reachable) continue;
4737
- const possiblyHasEarlyReturn = segment.nextSegments.length === 0 ? shortestFinalPathLength <= shortestPathLengthToStart(segment) : shortestFinalPathLength < shortestPathLengthToStart(segment);
4738
- const pathsFromStartToEnd = countPathsFromStart(segment) * countPathsToEnd(segment);
4739
- const cycled = cyclic.has(segment.id);
4740
- for (const hook of reactHooks) {
4741
- if (hasFlowSuppression(hook, "react-rule-hook")) continue;
4742
- if (isUseIdentifier(hook) && isInsideTryCatch(hook)) context.report({
4743
- node: hook,
4744
- message: `React Hook "${getSourceCode().getText(hook)}" cannot be called in a try/catch block.`
4745
- });
4746
- if ((cycled || isInsideDoWhileLoop(hook)) && !isUseIdentifier(hook)) context.report({
4747
- node: hook,
4748
- message: `React Hook "${getSourceCode().getText(hook)}" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render.`
4749
- });
4750
- if (isDirectlyInsideComponentOrHook) {
4751
- if (codePathNode.async) context.report({
4752
- node: hook,
4753
- message: `React Hook "${getSourceCode().getText(hook)}" cannot be called in an async function.`
4754
- });
4755
- if (!cycled && pathsFromStartToEnd !== allPathsFromStartToEnd && !isUseIdentifier(hook) && !isInsideDoWhileLoop(hook)) {
4756
- const message = `React Hook "${getSourceCode().getText(hook)}" is called conditionally. React Hooks must be called in the exact same order in every component render.` + (possiblyHasEarlyReturn ? " Did you accidentally call a React Hook after an early return?" : "");
4757
- context.report({
4758
- node: hook,
4759
- message
4760
- });
4761
- }
4762
- } else if (codePathNode.parent != null && (codePathNode.parent.type === "MethodDefinition" || codePathNode.parent.type === "ClassProperty" || codePathNode.parent.type === "PropertyDefinition") && codePathNode.parent.value === codePathNode) {
4763
- const message = `React Hook "${getSourceCode().getText(hook)}" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.`;
4764
- context.report({
4765
- node: hook,
4766
- message
4767
- });
4768
- } else if (codePathFunctionName) {
4769
- const message = `React Hook "${getSourceCode().getText(hook)}" is called in function "${getSourceCode().getText(codePathFunctionName)}" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".`;
4770
- context.report({
4771
- node: hook,
4772
- message
4773
- });
4774
- } else if (codePathNode.type === "Program") {
4775
- const message = `React Hook "${getSourceCode().getText(hook)}" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.`;
4776
- context.report({
4777
- node: hook,
4778
- message
4779
- });
4780
- } else if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
4781
- const message = `React Hook "${getSourceCode().getText(hook)}" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.`;
4782
- context.report({
4783
- node: hook,
4784
- message
4785
- });
4786
- }
4787
- }
4788
- }
4789
- }
4790
- });
4791
- return {
4792
- "*"(node) {
4793
- analyzer.enterNode(node);
4794
- },
4795
- "*:exit"(node) {
4796
- analyzer.leaveNode(node);
4797
- },
4798
- CallExpression(node) {
4799
- if (isHook(node.callee)) {
4800
- const reactHooksMap = last(codePathReactHooksMapStack);
4801
- const codePathSegment = last(codePathSegmentStack);
4802
- let reactHooks = reactHooksMap.get(codePathSegment);
4803
- if (!reactHooks) {
4804
- reactHooks = [];
4805
- reactHooksMap.set(codePathSegment, reactHooks);
4806
- }
4807
- reactHooks.push(node.callee);
4808
- }
4809
- const nodeWithoutNamespace = getNodeWithoutReactNamespace(node.callee);
4810
- if ((isEffectIdentifier(nodeWithoutNamespace, additionalEffectHooks) || isUseEffectEventIdentifier(nodeWithoutNamespace)) && node.arguments.length > 0) lastEffect = node;
4811
- if (isUseEffectEventIdentifier(nodeWithoutNamespace) && node.parent?.type !== "VariableDeclarator" && node.parent?.type !== "ExpressionStatement") {
4812
- const message = useEffectEventError(null, false);
4813
- context.report({
4814
- node,
4815
- message
4816
- });
4817
- }
4818
- },
4819
- Identifier(node) {
4820
- if (lastEffect == null && useEffectEventFunctions.has(node)) {
4821
- const message = useEffectEventError(getSourceCode().getText(node), node.parent.type === "CallExpression");
4822
- context.report({
4823
- node,
4824
- message
4825
- });
4826
- }
4827
- },
4828
- "CallExpression:exit"(node) {
4829
- if (node === lastEffect) lastEffect = null;
4830
- },
4831
- FunctionDeclaration(node) {
4832
- if (isInsideComponentOrHook(node)) recordAllUseEffectEventFunctions(getScope(node));
4833
- },
4834
- ArrowFunctionExpression(node) {
4835
- if (isInsideComponentOrHook(node)) recordAllUseEffectEventFunctions(getScope(node));
4836
- },
4837
- ComponentDeclaration(node) {
4838
- recordAllUseEffectEventFunctions(getScope(node));
4839
- },
4840
- HookDeclaration(node) {
4841
- recordAllUseEffectEventFunctions(getScope(node));
4842
- }
4843
- };
4844
- }
4845
- };
4846
- /**
4847
- * Gets the static name of a function AST node. For function declarations it is
4848
- * easy. For anonymous function expressions it is much harder. If you search for
4849
- * `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
4850
- * where JS gives anonymous function expressions names. We roughly detect the
4851
- * same AST nodes with some exceptions to better fit our use case.
4852
- */
4853
- function getFunctionName(node) {
4854
- if (node.type === "ComponentDeclaration" || node.type === "HookDeclaration" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression" && node.id) return node.id;
4855
- else if (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") if (node.parent?.type === "VariableDeclarator" && node.parent.init === node) return node.parent.id;
4856
- else if (node.parent?.type === "AssignmentExpression" && node.parent.right === node && node.parent.operator === "=") return node.parent.left;
4857
- else if (node.parent?.type === "Property" && node.parent.value === node && !node.parent.computed) return node.parent.key;
4858
- else if (node.parent?.type === "AssignmentPattern" && node.parent.right === node && !node.parent.computed) return node.parent.left;
4859
- else return;
4860
- else return;
4861
- }
4862
- /**
4863
- * Convenience function for peeking the last item in a stack.
4864
- */
4865
- function last(array) {
4866
- return array[array.length - 1];
4867
- }
4868
- function assert(cond) {
4869
- if (!cond) throw new Error("Assertion violated.");
4870
- }
4871
- /**
4872
- * Checks whether or not a given segment is reachable.
4873
- * @param {CodePathSegment} segment A segment to check.
4874
- * @returns {boolean} `true` if the segment is reachable.
4875
- */
4876
- function isReachable$1(segment) {
4877
- return segment.reachable;
4878
- }
4879
- /**
4880
- * A code path segment.
4881
- */
4882
- var CodePathSegment = class CodePathSegment {
4883
- /**
4884
- * @param {string} id An identifier.
4885
- * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
4886
- * This array includes unreachable segments.
4887
- * @param {boolean} reachable A flag which shows this is reachable.
4888
- */
4889
- constructor(id, allPrevSegments, reachable) {
4890
- /**
4891
- * The identifier of this code path.
4892
- * Rules use it to store additional information of each rule.
4893
- * @type {string}
4894
- */
4895
- this.id = id;
4896
- /**
4897
- * An array of the next segments.
4898
- * @type {CodePathSegment[]}
4899
- */
4900
- this.nextSegments = [];
4901
- /**
4902
- * An array of the previous segments.
4903
- * @type {CodePathSegment[]}
4904
- */
4905
- this.prevSegments = allPrevSegments.filter(isReachable$1);
4906
- /**
4907
- * An array of the next segments.
4908
- * This array includes unreachable segments.
4909
- * @type {CodePathSegment[]}
4910
- */
4911
- this.allNextSegments = [];
4912
- /**
4913
- * An array of the previous segments.
4914
- * This array includes unreachable segments.
4915
- * @type {CodePathSegment[]}
4916
- */
4917
- this.allPrevSegments = allPrevSegments;
4918
- /**
4919
- * A flag which shows this is reachable.
4920
- * @type {boolean}
4921
- */
4922
- this.reachable = reachable;
4923
- Object.defineProperty(this, "internal", { value: {
4924
- used: false,
4925
- loopedPrevSegments: []
4926
- } });
4927
- }
4928
- /**
4929
- * Checks a given previous segment is coming from the end of a loop.
4930
- * @param {CodePathSegment} segment A previous segment to check.
4931
- * @returns {boolean} `true` if the segment is coming from the end of a loop.
4932
- */
4933
- isLoopedPrevSegment(segment) {
4934
- return this.internal.loopedPrevSegments.includes(segment);
4935
- }
4936
- /**
4937
- * Creates the root segment.
4938
- * @param {string} id An identifier.
4939
- * @returns {CodePathSegment} The created segment.
4940
- */
4941
- static newRoot(id) {
4942
- return new CodePathSegment(id, [], true);
4943
- }
4944
- /**
4945
- * Creates a segment that follows given segments.
4946
- * @param {string} id An identifier.
4947
- * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
4948
- * @returns {CodePathSegment} The created segment.
4949
- */
4950
- static newNext(id, allPrevSegments) {
4951
- return new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), allPrevSegments.some(isReachable$1));
4952
- }
4953
- /**
4954
- * Creates an unreachable segment that follows given segments.
4955
- * @param {string} id An identifier.
4956
- * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
4957
- * @returns {CodePathSegment} The created segment.
4958
- */
4959
- static newUnreachable(id, allPrevSegments) {
4960
- const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
4961
- CodePathSegment.markUsed(segment);
4962
- return segment;
4963
- }
4964
- /**
4965
- * Creates a segment that follows given segments.
4966
- * This factory method does not connect with `allPrevSegments`.
4967
- * But this inherits `reachable` flag.
4968
- * @param {string} id An identifier.
4969
- * @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
4970
- * @returns {CodePathSegment} The created segment.
4971
- */
4972
- static newDisconnected(id, allPrevSegments) {
4973
- return new CodePathSegment(id, [], allPrevSegments.some(isReachable$1));
4974
- }
4975
- /**
4976
- * Makes a given segment being used.
4977
- *
4978
- * And this function registers the segment into the previous segments as a next.
4979
- * @param {CodePathSegment} segment A segment to mark.
4980
- * @returns {void}
4981
- */
4982
- static markUsed(segment) {
4983
- if (segment.internal.used) return;
4984
- segment.internal.used = true;
4985
- let i;
4986
- if (segment.reachable) for (i = 0; i < segment.allPrevSegments.length; ++i) {
4987
- const prevSegment = segment.allPrevSegments[i];
4988
- prevSegment.allNextSegments.push(segment);
4989
- prevSegment.nextSegments.push(segment);
4990
- }
4991
- else for (i = 0; i < segment.allPrevSegments.length; ++i) segment.allPrevSegments[i].allNextSegments.push(segment);
4992
- }
4993
- /**
4994
- * Marks a previous segment as looped.
4995
- * @param {CodePathSegment} segment A segment.
4996
- * @param {CodePathSegment} prevSegment A previous segment to mark.
4997
- * @returns {void}
4998
- */
4999
- static markPrevSegmentAsLooped(segment, prevSegment) {
5000
- segment.internal.loopedPrevSegments.push(prevSegment);
5001
- }
5002
- /**
5003
- * Replaces unused segments with the previous segments of each unused segment.
5004
- * @param {CodePathSegment[]} segments An array of segments to replace.
5005
- * @returns {CodePathSegment[]} The replaced array.
5006
- */
5007
- static flattenUnusedSegments(segments) {
5008
- const done = Object.create(null);
5009
- const retv = [];
5010
- for (let i = 0; i < segments.length; ++i) {
5011
- const segment = segments[i];
5012
- if (done[segment.id]) continue;
5013
- if (!segment.internal.used) for (let j = 0; j < segment.allPrevSegments.length; ++j) {
5014
- const prevSegment = segment.allPrevSegments[j];
5015
- if (!done[prevSegment.id]) {
5016
- done[prevSegment.id] = true;
5017
- retv.push(prevSegment);
5018
- }
5019
- }
5020
- else {
5021
- done[segment.id] = true;
5022
- retv.push(segment);
5023
- }
5024
- }
5025
- return retv;
5026
- }
5027
- };
5028
- /**
5029
- * Gets whether or not a given segment is reachable.
5030
- * @param {CodePathSegment} segment A segment to get.
5031
- * @returns {boolean} `true` if the segment is reachable.
5032
- */
5033
- function isReachable(segment) {
5034
- return segment.reachable;
5035
- }
5036
- /**
5037
- * Creates new segments from the specific range of `context.segmentsList`.
5038
- *
5039
- * When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
5040
- * `end` is `-1`, this creates `[g, h]`. This `g` is from `a`, `c`, and `e`.
5041
- * This `h` is from `b`, `d`, and `f`.
5042
- * @param {ForkContext} context An instance.
5043
- * @param {number} begin The first index of the previous segments.
5044
- * @param {number} end The last index of the previous segments.
5045
- * @param {Function} create A factory function of new segments.
5046
- * @returns {CodePathSegment[]} New segments.
5047
- */
5048
- function makeSegments(context, begin, end, create) {
5049
- const list = context.segmentsList;
5050
- const normalizedBegin = begin >= 0 ? begin : list.length + begin;
5051
- const normalizedEnd = end >= 0 ? end : list.length + end;
5052
- const segments = [];
5053
- for (let i = 0; i < context.count; ++i) {
5054
- const allPrevSegments = [];
5055
- for (let j = normalizedBegin; j <= normalizedEnd; ++j) allPrevSegments.push(list[j][i]);
5056
- segments.push(create(context.idGenerator.next(), allPrevSegments));
5057
- }
5058
- return segments;
5059
- }
5060
- /**
5061
- * `segments` becomes doubly in a `finally` block. Then if a code path exits by a
5062
- * control statement (such as `break`, `continue`) from the `finally` block, the
5063
- * destination's segments may be half of the source segments. In that case, this
5064
- * merges segments.
5065
- * @param {ForkContext} context An instance.
5066
- * @param {CodePathSegment[]} segments Segments to merge.
5067
- * @returns {CodePathSegment[]} The merged segments.
5068
- */
5069
- function mergeExtraSegments(context, segments) {
5070
- let currentSegments = segments;
5071
- while (currentSegments.length > context.count) {
5072
- const merged = [];
5073
- for (let i = 0, length = currentSegments.length / 2 | 0; i < length; ++i) merged.push(CodePathSegment.newNext(context.idGenerator.next(), [currentSegments[i], currentSegments[i + length]]));
5074
- currentSegments = merged;
5075
- }
5076
- return currentSegments;
5077
- }
5078
- /**
5079
- * A class to manage forking.
5080
- */
5081
- var ForkContext = class ForkContext {
5082
- /**
5083
- * @param {IdGenerator} idGenerator An identifier generator for segments.
5084
- * @param {ForkContext|null} upper An upper fork context.
5085
- * @param {number} count A number of parallel segments.
5086
- */
5087
- constructor(idGenerator, upper, count) {
5088
- this.idGenerator = idGenerator;
5089
- this.upper = upper;
5090
- this.count = count;
5091
- this.segmentsList = [];
5092
- }
5093
- /**
5094
- * The head segments.
5095
- * @type {CodePathSegment[]}
5096
- */
5097
- get head() {
5098
- const list = this.segmentsList;
5099
- return list.length === 0 ? [] : list[list.length - 1];
5100
- }
5101
- /**
5102
- * A flag which shows empty.
5103
- * @type {boolean}
5104
- */
4636
+ /** Whether the segments list is empty. */
5105
4637
  get empty() {
5106
4638
  return this.segmentsList.length === 0;
5107
4639
  }
5108
- /**
5109
- * A flag which shows reachable.
5110
- * @type {boolean}
5111
- */
4640
+ /** Whether any head segment is reachable. */
5112
4641
  get reachable() {
5113
4642
  const segments = this.head;
5114
4643
  return segments.length > 0 && segments.some(isReachable);
5115
4644
  }
5116
4645
  /**
5117
4646
  * Creates new segments from this context.
5118
- * @param {number} begin The first index of previous segments.
5119
- * @param {number} end The last index of previous segments.
5120
- * @returns {CodePathSegment[]} New segments.
5121
4647
  */
5122
4648
  makeNext(begin, end) {
5123
4649
  return makeSegments(this, begin, end, CodePathSegment.newNext);
5124
4650
  }
5125
4651
  /**
5126
- * Creates new segments from this context.
5127
- * The new segments is always unreachable.
5128
- * @param {number} begin The first index of previous segments.
5129
- * @param {number} end The last index of previous segments.
5130
- * @returns {CodePathSegment[]} New segments.
4652
+ * Creates new unreachable segments from this context.
5131
4653
  */
5132
4654
  makeUnreachable(begin, end) {
5133
4655
  return makeSegments(this, begin, end, CodePathSegment.newUnreachable);
5134
4656
  }
5135
4657
  /**
5136
- * Creates new segments from this context.
5137
- * The new segments don't have connections for previous segments.
5138
- * But these inherit the reachable flag from this context.
5139
- * @param {number} begin The first index of previous segments.
5140
- * @param {number} end The last index of previous segments.
5141
- * @returns {CodePathSegment[]} New segments.
4658
+ * Creates new disconnected segments from this context.
4659
+ * The new segments don't have connections for previous segments,
4660
+ * but inherit the reachable flag.
5142
4661
  */
5143
4662
  makeDisconnected(begin, end) {
5144
4663
  return makeSegments(this, begin, end, CodePathSegment.newDisconnected);
5145
4664
  }
5146
4665
  /**
5147
- * Adds segments into this context.
5148
- * The added segments become the head.
5149
- * @param {CodePathSegment[]} segments Segments to add.
5150
- * @returns {void}
4666
+ * Adds segments into this context. The added segments become the head.
5151
4667
  */
5152
4668
  add(segments) {
5153
4669
  assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
@@ -5156,8 +4672,6 @@ var ForkContext = class ForkContext {
5156
4672
  /**
5157
4673
  * Replaces the head segments with given segments.
5158
4674
  * The current head segments are removed.
5159
- * @param {CodePathSegment[]} segments Segments to add.
5160
- * @returns {void}
5161
4675
  */
5162
4676
  replaceHead(segments) {
5163
4677
  assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
@@ -5165,8 +4679,6 @@ var ForkContext = class ForkContext {
5165
4679
  }
5166
4680
  /**
5167
4681
  * Adds all segments of a given fork context into this context.
5168
- * @param {ForkContext} context A fork context to add.
5169
- * @returns {void}
5170
4682
  */
5171
4683
  addAll(context) {
5172
4684
  assert(context.count === this.count);
@@ -5175,15 +4687,12 @@ var ForkContext = class ForkContext {
5175
4687
  }
5176
4688
  /**
5177
4689
  * Clears all segments in this context.
5178
- * @returns {void}
5179
4690
  */
5180
4691
  clear() {
5181
4692
  this.segmentsList = [];
5182
4693
  }
5183
4694
  /**
5184
4695
  * Creates the root fork context.
5185
- * @param {IdGenerator} idGenerator An identifier generator for segments.
5186
- * @returns {ForkContext} New fork context.
5187
4696
  */
5188
4697
  static newRoot(idGenerator) {
5189
4698
  const context = new ForkContext(idGenerator, null, 1);
@@ -5192,9 +4701,6 @@ var ForkContext = class ForkContext {
5192
4701
  }
5193
4702
  /**
5194
4703
  * Creates an empty fork context preceded by a given context.
5195
- * @param {ForkContext} parentContext The parent fork context.
5196
- * @param {boolean} forkLeavingPath A flag which shows inside of `finally` block.
5197
- * @returns {ForkContext} New fork context.
5198
4704
  */
5199
4705
  static newEmpty(parentContext, forkLeavingPath) {
5200
4706
  return new ForkContext(parentContext.idGenerator, parentContext, (forkLeavingPath ? 2 : 1) * parentContext.count);
@@ -5202,15 +4708,10 @@ var ForkContext = class ForkContext {
5202
4708
  };
5203
4709
  /**
5204
4710
  * Adds given segments into the `dest` array.
5205
- * If the `others` array does not includes the given segments, adds to the `all`
4711
+ * If the `others` array does not include the given segments, adds to the `all`
5206
4712
  * array as well.
5207
4713
  *
5208
4714
  * This adds only reachable and used segments.
5209
- * @param {CodePathSegment[]} dest A destination array (`returnedSegments` or `thrownSegments`).
5210
- * @param {CodePathSegment[]} others Another destination array (`returnedSegments` or `thrownSegments`).
5211
- * @param {CodePathSegment[]} all The unified destination array (`finalSegments`).
5212
- * @param {CodePathSegment[]} segments Segments to add.
5213
- * @returns {void}
5214
4715
  */
5215
4716
  function addToReturnedOrThrown(dest, others, all, segments) {
5216
4717
  for (let i = 0; i < segments.length; ++i) {
@@ -5221,9 +4722,6 @@ function addToReturnedOrThrown(dest, others, all, segments) {
5221
4722
  }
5222
4723
  /**
5223
4724
  * Gets a loop-context for a `continue` statement.
5224
- * @param {CodePathState} state A state to get.
5225
- * @param {string} label The label of a `continue` statement.
5226
- * @returns {LoopContext} A loop-context for a `continue` statement.
5227
4725
  */
5228
4726
  function getContinueContext(state, label) {
5229
4727
  if (!label) return state.loopContext;
@@ -5237,9 +4735,6 @@ function getContinueContext(state, label) {
5237
4735
  }
5238
4736
  /**
5239
4737
  * Gets a context for a `break` statement.
5240
- * @param {CodePathState} state A state to get.
5241
- * @param {string} label The label of a `break` statement.
5242
- * @returns {LoopContext|SwitchContext} A context for a `break` statement.
5243
4738
  */
5244
4739
  function getBreakContext(state, label) {
5245
4740
  let context = state.breakContext;
@@ -5252,8 +4747,6 @@ function getBreakContext(state, label) {
5252
4747
  }
5253
4748
  /**
5254
4749
  * Gets a context for a `return` statement.
5255
- * @param {CodePathState} state A state to get.
5256
- * @returns {TryContext|CodePathState} A context for a `return` statement.
5257
4750
  */
5258
4751
  function getReturnContext(state) {
5259
4752
  let context = state.tryContext;
@@ -5265,8 +4758,6 @@ function getReturnContext(state) {
5265
4758
  }
5266
4759
  /**
5267
4760
  * Gets a context for a `throw` statement.
5268
- * @param {CodePathState} state A state to get.
5269
- * @returns {TryContext|CodePathState} A context for a `throw` statement.
5270
4761
  */
5271
4762
  function getThrowContext(state) {
5272
4763
  let context = state.tryContext;
@@ -5278,9 +4769,6 @@ function getThrowContext(state) {
5278
4769
  }
5279
4770
  /**
5280
4771
  * Removes a given element from a given array.
5281
- * @param {any[]} xs An array to remove the specific element.
5282
- * @param {any} x An element to be removed.
5283
- * @returns {void}
5284
4772
  */
5285
4773
  function remove(xs, x) {
5286
4774
  xs.splice(xs.indexOf(x), 1);
@@ -5291,9 +4779,6 @@ function remove(xs, x) {
5291
4779
  * This is used in a process for switch statements.
5292
4780
  * If there is the "default" chunk before other cases, the order is different
5293
4781
  * between node's and running's.
5294
- * @param {CodePathSegment[]} prevSegments Forward segments to disconnect.
5295
- * @param {CodePathSegment[]} nextSegments Backward segments to disconnect.
5296
- * @returns {void}
5297
4782
  */
5298
4783
  function removeConnection(prevSegments, nextSegments) {
5299
4784
  for (let i = 0; i < prevSegments.length; ++i) {
@@ -5307,10 +4792,6 @@ function removeConnection(prevSegments, nextSegments) {
5307
4792
  }
5308
4793
  /**
5309
4794
  * Creates looping path.
5310
- * @param {CodePathState} state The instance.
5311
- * @param {CodePathSegment[]} unflattenedFromSegments Segments which are source.
5312
- * @param {CodePathSegment[]} unflattenedToSegments Segments which are destination.
5313
- * @returns {void}
5314
4795
  */
5315
4796
  function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
5316
4797
  const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments);
@@ -5332,10 +4813,6 @@ function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) {
5332
4813
  *
5333
4814
  * - Adds `false` paths to paths which are leaving from the loop.
5334
4815
  * - Sets `true` paths to paths which go to the body.
5335
- * @param {LoopContext} context A loop context to modify.
5336
- * @param {ChoiceContext} choiceContext A choice context of this loop.
5337
- * @param {CodePathSegment[]} head The current head paths.
5338
- * @returns {void}
5339
4816
  */
5340
4817
  function finalizeTestSegmentsOfFor(context, choiceContext, head) {
5341
4818
  if (!choiceContext.processed) {
@@ -5350,11 +4827,20 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) {
5350
4827
  * A class which manages state to analyze code paths.
5351
4828
  */
5352
4829
  var CodePathState = class {
5353
- /**
5354
- * @param {IdGenerator} idGenerator An id generator to generate id for code
5355
- * path segments.
5356
- * @param {Function} onLooped A callback function to notify looping.
5357
- */
4830
+ idGenerator;
4831
+ notifyLooped;
4832
+ forkContext;
4833
+ choiceContext;
4834
+ switchContext;
4835
+ tryContext;
4836
+ loopContext;
4837
+ breakContext;
4838
+ chainContext;
4839
+ currentSegments;
4840
+ initialSegment;
4841
+ finalSegments;
4842
+ returnedForkContext;
4843
+ thrownForkContext;
5358
4844
  constructor(idGenerator, onLooped) {
5359
4845
  this.idGenerator = idGenerator;
5360
4846
  this.notifyLooped = onLooped;
@@ -5367,23 +4853,22 @@ var CodePathState = class {
5367
4853
  this.chainContext = null;
5368
4854
  this.currentSegments = [];
5369
4855
  this.initialSegment = this.forkContext.head[0];
5370
- const final = this.finalSegments = [];
5371
- const returned = this.returnedForkContext = [];
5372
- const thrown = this.thrownForkContext = [];
4856
+ const final = [];
4857
+ this.finalSegments = final;
4858
+ const returned = [];
4859
+ const thrown = [];
4860
+ this.returnedForkContext = returned;
4861
+ this.thrownForkContext = thrown;
5373
4862
  returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final);
5374
4863
  thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final);
5375
4864
  }
5376
- /**
5377
- * The head segments.
5378
- * @type {CodePathSegment[]}
5379
- */
4865
+ /** The head segments. */
5380
4866
  get headSegments() {
5381
4867
  return this.forkContext.head;
5382
4868
  }
5383
4869
  /**
5384
4870
  * The parent forking context.
5385
4871
  * This is used for the root of new forks.
5386
- * @type {ForkContext}
5387
4872
  */
5388
4873
  get parentForkContext() {
5389
4874
  const current = this.forkContext;
@@ -5391,9 +4876,6 @@ var CodePathState = class {
5391
4876
  }
5392
4877
  /**
5393
4878
  * Creates and stacks new forking context.
5394
- * @param {boolean} forkLeavingPath A flag which shows being in a
5395
- * "finally" block.
5396
- * @returns {ForkContext} The created context.
5397
4879
  */
5398
4880
  pushForkContext(forkLeavingPath) {
5399
4881
  this.forkContext = ForkContext.newEmpty(this.forkContext, forkLeavingPath);
@@ -5401,7 +4883,6 @@ var CodePathState = class {
5401
4883
  }
5402
4884
  /**
5403
4885
  * Pops and merges the last forking context.
5404
- * @returns {ForkContext} The last context.
5405
4886
  */
5406
4887
  popForkContext() {
5407
4888
  const lastContext = this.forkContext;
@@ -5411,7 +4892,6 @@ var CodePathState = class {
5411
4892
  }
5412
4893
  /**
5413
4894
  * Creates a new path.
5414
- * @returns {void}
5415
4895
  */
5416
4896
  forkPath() {
5417
4897
  this.forkContext.add(this.parentForkContext.makeNext(-1, -1));
@@ -5419,40 +4899,22 @@ var CodePathState = class {
5419
4899
  /**
5420
4900
  * Creates a bypass path.
5421
4901
  * This is used for such as IfStatement which does not have "else" chunk.
5422
- * @returns {void}
5423
4902
  */
5424
4903
  forkBypassPath() {
5425
4904
  this.forkContext.add(this.parentForkContext.head);
5426
4905
  }
5427
4906
  /**
5428
- * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
5429
- * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
5430
- *
5431
- * LogicalExpressions have cases that it goes different paths between the
5432
- * `true` case and the `false` case.
5433
- *
5434
- * For Example:
5435
- *
5436
- * if (a || b) {
5437
- * foo();
5438
- * } else {
5439
- * bar();
5440
- * }
4907
+ * Creates a context for ConditionalExpression, LogicalExpression,
4908
+ * AssignmentExpression (logical assignments only), IfStatement,
4909
+ * WhileStatement, DoWhileStatement, or ForStatement.
5441
4910
  *
5442
- * In this case, `b` is evaluated always in the code path of the `else`
5443
- * block, but it's not so in the code path of the `if` block.
5444
- * So there are 3 paths.
5445
- *
5446
- * a -> foo();
5447
- * a -> b -> foo();
5448
- * a -> b -> bar();
5449
- * @param {string} kind A kind string.
5450
- * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
4911
+ * @param kind A kind string.
4912
+ * If the new context is LogicalExpression's or AssignmentExpression's,
4913
+ * this is `"&&"` or `"||"` or `"??"`.
5451
4914
  * If it's IfStatement's or ConditionalExpression's, this is `"test"`.
5452
4915
  * Otherwise, this is `"loop"`.
5453
- * @param {boolean} isForkingAsResult A flag that shows that goes different
4916
+ * @param isForkingAsResult A flag that shows that goes different
5454
4917
  * paths between `true` and `false`.
5455
- * @returns {void}
5456
4918
  */
5457
4919
  pushChoiceContext(kind, isForkingAsResult) {
5458
4920
  this.choiceContext = {
@@ -5467,8 +4929,6 @@ var CodePathState = class {
5467
4929
  }
5468
4930
  /**
5469
4931
  * Pops the last choice context and finalizes it.
5470
- * @throws {Error} (Unreachable.)
5471
- * @returns {ChoiceContext} The popped context.
5472
4932
  */
5473
4933
  popChoiceContext() {
5474
4934
  const context = this.choiceContext;
@@ -5513,8 +4973,6 @@ var CodePathState = class {
5513
4973
  /**
5514
4974
  * Makes a code path segment of the right-hand operand of a logical
5515
4975
  * expression.
5516
- * @throws {Error} (Unreachable.)
5517
- * @returns {void}
5518
4976
  */
5519
4977
  makeLogicalRight() {
5520
4978
  const context = this.choiceContext;
@@ -5555,7 +5013,6 @@ var CodePathState = class {
5555
5013
  }
5556
5014
  /**
5557
5015
  * Makes a code path segment of the `if` block.
5558
- * @returns {void}
5559
5016
  */
5560
5017
  makeIfConsequent() {
5561
5018
  const context = this.choiceContext;
@@ -5570,7 +5027,6 @@ var CodePathState = class {
5570
5027
  }
5571
5028
  /**
5572
5029
  * Makes a code path segment of the `else` block.
5573
- * @returns {void}
5574
5030
  */
5575
5031
  makeIfAlternate() {
5576
5032
  const context = this.choiceContext;
@@ -5583,8 +5039,8 @@ var CodePathState = class {
5583
5039
  /**
5584
5040
  * Push a new `ChainExpression` context to the stack.
5585
5041
  * This method is called on entering to each `ChainExpression` node.
5586
- * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
5587
- * @returns {void}
5042
+ * This context is used to count forking in the optional chain then merge
5043
+ * them on exiting from the `ChainExpression` node.
5588
5044
  */
5589
5045
  pushChainContext() {
5590
5046
  this.chainContext = {
@@ -5596,7 +5052,6 @@ var CodePathState = class {
5596
5052
  * Pop a `ChainExpression` context from the stack.
5597
5053
  * This method is called on exiting from each `ChainExpression` node.
5598
5054
  * This merges all forks of the last optional chaining.
5599
- * @returns {void}
5600
5055
  */
5601
5056
  popChainContext() {
5602
5057
  const context = this.chainContext;
@@ -5607,7 +5062,6 @@ var CodePathState = class {
5607
5062
  * Create a choice context for optional access.
5608
5063
  * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
5609
5064
  * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
5610
- * @returns {void}
5611
5065
  */
5612
5066
  makeOptionalNode() {
5613
5067
  if (this.chainContext) {
@@ -5617,18 +5071,14 @@ var CodePathState = class {
5617
5071
  }
5618
5072
  /**
5619
5073
  * Create a fork.
5620
- * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
5621
- * @returns {void}
5074
+ * This method is called on entering to the `arguments|property` property
5075
+ * of each `(Call|Member)Expression` node.
5622
5076
  */
5623
5077
  makeOptionalRight() {
5624
5078
  if (this.chainContext) this.makeLogicalRight();
5625
5079
  }
5626
5080
  /**
5627
5081
  * Creates a context object of SwitchStatement and stacks it.
5628
- * @param {boolean} hasCase `true` if the switch statement has one or more
5629
- * case parts.
5630
- * @param {string|null} label The label text.
5631
- * @returns {void}
5632
5082
  */
5633
5083
  pushSwitchContext(hasCase, label) {
5634
5084
  this.switchContext = {
@@ -5649,7 +5099,6 @@ var CodePathState = class {
5649
5099
  * - Creates the next code path segment from `context.brokenForkContext`.
5650
5100
  * - If the last `SwitchCase` node is not a `default` part, creates a path
5651
5101
  * to the `default` body.
5652
- * @returns {void}
5653
5102
  */
5654
5103
  popSwitchContext() {
5655
5104
  const context = this.switchContext;
@@ -5676,9 +5125,6 @@ var CodePathState = class {
5676
5125
  }
5677
5126
  /**
5678
5127
  * Makes a code path segment for a `SwitchCase` node.
5679
- * @param {boolean} isEmpty `true` if the body is empty.
5680
- * @param {boolean} isDefault `true` if the body is the default case.
5681
- * @returns {void}
5682
5128
  */
5683
5129
  makeSwitchCaseBody(isEmpty, isDefault) {
5684
5130
  const context = this.switchContext;
@@ -5699,9 +5145,6 @@ var CodePathState = class {
5699
5145
  }
5700
5146
  /**
5701
5147
  * Creates a context object of TryStatement and stacks it.
5702
- * @param {boolean} hasFinalizer `true` if the try statement has a
5703
- * `finally` block.
5704
- * @returns {void}
5705
5148
  */
5706
5149
  pushTryContext(hasFinalizer) {
5707
5150
  this.tryContext = {
@@ -5716,7 +5159,6 @@ var CodePathState = class {
5716
5159
  }
5717
5160
  /**
5718
5161
  * Pops the last context of TryStatement and finalizes it.
5719
- * @returns {void}
5720
5162
  */
5721
5163
  popTryContext() {
5722
5164
  const context = this.tryContext;
@@ -5727,19 +5169,18 @@ var CodePathState = class {
5727
5169
  }
5728
5170
  const returned = context.returnedForkContext;
5729
5171
  const thrown = context.thrownForkContext;
5730
- if (returned.empty && thrown.empty) return;
5172
+ if ((returned == null || returned.empty) && thrown.empty) return;
5731
5173
  const headSegments = this.forkContext.head;
5732
5174
  this.forkContext = this.forkContext.upper;
5733
5175
  const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0);
5734
5176
  const leavingSegments = headSegments.slice(headSegments.length / 2 | 0);
5735
- if (!returned.empty) getReturnContext(this).returnedForkContext.add(leavingSegments);
5177
+ if (returned && !returned.empty) getReturnContext(this).returnedForkContext.add(leavingSegments);
5736
5178
  if (!thrown.empty) getThrowContext(this).thrownForkContext.add(leavingSegments);
5737
5179
  this.forkContext.replaceHead(normalSegments);
5738
- if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) this.forkContext.makeUnreachable();
5180
+ if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) this.forkContext.makeUnreachable(-1, -1);
5739
5181
  }
5740
5182
  /**
5741
5183
  * Makes a code path segment for a `catch` block.
5742
- * @returns {void}
5743
5184
  */
5744
5185
  makeCatchBlock() {
5745
5186
  const context = this.tryContext;
@@ -5760,7 +5201,6 @@ var CodePathState = class {
5760
5201
  * In the `finally` block, parallel paths are created. The parallel paths
5761
5202
  * are used as leaving-paths. The leaving-paths are paths from `return`
5762
5203
  * statements and `throw` statements in a `try` block or a `catch` block.
5763
- * @returns {void}
5764
5204
  */
5765
5205
  makeFinallyBlock() {
5766
5206
  const context = this.tryContext;
@@ -5788,7 +5228,6 @@ var CodePathState = class {
5788
5228
  /**
5789
5229
  * Makes a code path segment from the first throwable node to the `catch`
5790
5230
  * block or the `finally` block.
5791
- * @returns {void}
5792
5231
  */
5793
5232
  makeFirstThrowablePathInTryBlock() {
5794
5233
  const forkContext = this.forkContext;
@@ -5800,12 +5239,6 @@ var CodePathState = class {
5800
5239
  }
5801
5240
  /**
5802
5241
  * Creates a context object of a loop statement and stacks it.
5803
- * @param {string} type The type of the node which was triggered. One of
5804
- * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`,
5805
- * and `ForStatement`.
5806
- * @param {string|null} label A label of the node which was triggered.
5807
- * @throws {Error} (Unreachable - unknown type.)
5808
- * @returns {void}
5809
5242
  */
5810
5243
  pushLoopContext(type, label) {
5811
5244
  const forkContext = this.forkContext;
@@ -5815,7 +5248,7 @@ var CodePathState = class {
5815
5248
  this.pushChoiceContext("loop", false);
5816
5249
  this.loopContext = {
5817
5250
  upper: this.loopContext,
5818
- type,
5251
+ type: "WhileStatement",
5819
5252
  label,
5820
5253
  test: void 0,
5821
5254
  continueDestSegments: null,
@@ -5826,7 +5259,7 @@ var CodePathState = class {
5826
5259
  this.pushChoiceContext("loop", false);
5827
5260
  this.loopContext = {
5828
5261
  upper: this.loopContext,
5829
- type,
5262
+ type: "DoWhileStatement",
5830
5263
  label,
5831
5264
  test: void 0,
5832
5265
  entrySegments: null,
@@ -5838,7 +5271,7 @@ var CodePathState = class {
5838
5271
  this.pushChoiceContext("loop", false);
5839
5272
  this.loopContext = {
5840
5273
  upper: this.loopContext,
5841
- type,
5274
+ type: "ForStatement",
5842
5275
  label,
5843
5276
  test: void 0,
5844
5277
  endOfInitSegments: null,
@@ -5868,8 +5301,6 @@ var CodePathState = class {
5868
5301
  }
5869
5302
  /**
5870
5303
  * Pops the last context of a loop statement and finalizes it.
5871
- * @throws {Error} (Unreachable - unknown type.)
5872
- * @returns {void}
5873
5304
  */
5874
5305
  popLoopContext() {
5875
5306
  const context = this.loopContext;
@@ -5905,8 +5336,6 @@ var CodePathState = class {
5905
5336
  }
5906
5337
  /**
5907
5338
  * Makes a code path segment for the test part of a WhileStatement.
5908
- * @param {boolean|undefined} test The test value (only when constant).
5909
- * @returns {void}
5910
5339
  */
5911
5340
  makeWhileTest(test) {
5912
5341
  const context = this.loopContext;
@@ -5918,7 +5347,6 @@ var CodePathState = class {
5918
5347
  }
5919
5348
  /**
5920
5349
  * Makes a code path segment for the body part of a WhileStatement.
5921
- * @returns {void}
5922
5350
  */
5923
5351
  makeWhileBody() {
5924
5352
  const context = this.loopContext;
@@ -5933,7 +5361,6 @@ var CodePathState = class {
5933
5361
  }
5934
5362
  /**
5935
5363
  * Makes a code path segment for the body part of a DoWhileStatement.
5936
- * @returns {void}
5937
5364
  */
5938
5365
  makeDoWhileBody() {
5939
5366
  const context = this.loopContext;
@@ -5944,8 +5371,6 @@ var CodePathState = class {
5944
5371
  }
5945
5372
  /**
5946
5373
  * Makes a code path segment for the test part of a DoWhileStatement.
5947
- * @param {boolean|undefined} test The test value (only when constant).
5948
- * @returns {void}
5949
5374
  */
5950
5375
  makeDoWhileTest(test) {
5951
5376
  const context = this.loopContext;
@@ -5959,8 +5384,6 @@ var CodePathState = class {
5959
5384
  }
5960
5385
  /**
5961
5386
  * Makes a code path segment for the test part of a ForStatement.
5962
- * @param {boolean|undefined} test The test value (only when constant).
5963
- * @returns {void}
5964
5387
  */
5965
5388
  makeForTest(test) {
5966
5389
  const context = this.loopContext;
@@ -5974,7 +5397,6 @@ var CodePathState = class {
5974
5397
  }
5975
5398
  /**
5976
5399
  * Makes a code path segment for the update part of a ForStatement.
5977
- * @returns {void}
5978
5400
  */
5979
5401
  makeForUpdate() {
5980
5402
  const context = this.loopContext;
@@ -5988,7 +5410,6 @@ var CodePathState = class {
5988
5410
  }
5989
5411
  /**
5990
5412
  * Makes a code path segment for the body part of a ForStatement.
5991
- * @returns {void}
5992
5413
  */
5993
5414
  makeForBody() {
5994
5415
  const context = this.loopContext;
@@ -6012,7 +5433,6 @@ var CodePathState = class {
6012
5433
  /**
6013
5434
  * Makes a code path segment for the left part of a ForInStatement and a
6014
5435
  * ForOfStatement.
6015
- * @returns {void}
6016
5436
  */
6017
5437
  makeForInOfLeft() {
6018
5438
  const context = this.loopContext;
@@ -6025,7 +5445,6 @@ var CodePathState = class {
6025
5445
  /**
6026
5446
  * Makes a code path segment for the right part of a ForInStatement and a
6027
5447
  * ForOfStatement.
6028
- * @returns {void}
6029
5448
  */
6030
5449
  makeForInOfRight() {
6031
5450
  const context = this.loopContext;
@@ -6039,7 +5458,6 @@ var CodePathState = class {
6039
5458
  /**
6040
5459
  * Makes a code path segment for the body part of a ForInStatement and a
6041
5460
  * ForOfStatement.
6042
- * @returns {void}
6043
5461
  */
6044
5462
  makeForInOfBody() {
6045
5463
  const context = this.loopContext;
@@ -6053,10 +5471,6 @@ var CodePathState = class {
6053
5471
  }
6054
5472
  /**
6055
5473
  * Creates new context for BreakStatement.
6056
- * @param {boolean} breakable The flag to indicate it can break by
6057
- * an unlabeled BreakStatement.
6058
- * @param {string|null} label The label of this context.
6059
- * @returns {Object} The new context.
6060
5474
  */
6061
5475
  pushBreakContext(breakable, label) {
6062
5476
  this.breakContext = {
@@ -6068,787 +5482,1111 @@ var CodePathState = class {
6068
5482
  return this.breakContext;
6069
5483
  }
6070
5484
  /**
6071
- * Removes the top item of the break context stack.
6072
- * @returns {Object} The removed context.
5485
+ * Removes the top item of the break context stack.
5486
+ */
5487
+ popBreakContext() {
5488
+ const context = this.breakContext;
5489
+ const forkContext = this.forkContext;
5490
+ this.breakContext = context.upper;
5491
+ if (!context.breakable) {
5492
+ const brokenForkContext = context.brokenForkContext;
5493
+ if (!brokenForkContext.empty) {
5494
+ brokenForkContext.add(forkContext.head);
5495
+ forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
5496
+ }
5497
+ }
5498
+ return context;
5499
+ }
5500
+ /**
5501
+ * Makes a path for a `break` statement.
5502
+ *
5503
+ * It registers the head segment to a context of `break`.
5504
+ * It makes new unreachable segment, then it set the head with the segment.
5505
+ */
5506
+ makeBreak(label) {
5507
+ const forkContext = this.forkContext;
5508
+ if (!forkContext.reachable) return;
5509
+ const context = getBreakContext(this, label);
5510
+ if (context) context.brokenForkContext.add(forkContext.head);
5511
+ /* c8 ignore next */
5512
+ forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5513
+ }
5514
+ /**
5515
+ * Makes a path for a `continue` statement.
5516
+ *
5517
+ * It makes a looping path.
5518
+ * It makes new unreachable segment, then it set the head with the segment.
5519
+ */
5520
+ makeContinue(label) {
5521
+ const forkContext = this.forkContext;
5522
+ if (!forkContext.reachable) return;
5523
+ const context = getContinueContext(this, label);
5524
+ if (context) if (context.continueDestSegments != null) {
5525
+ makeLooped(this, forkContext.head, context.continueDestSegments);
5526
+ if (context.type === "ForInStatement" || context.type === "ForOfStatement") context.brokenForkContext.add(forkContext.head);
5527
+ } else context.continueForkContext.add(forkContext.head);
5528
+ forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5529
+ }
5530
+ /**
5531
+ * Makes a path for a `return` statement.
5532
+ *
5533
+ * It registers the head segment to a context of `return`.
5534
+ * It makes new unreachable segment, then it set the head with the segment.
5535
+ */
5536
+ makeReturn() {
5537
+ const forkContext = this.forkContext;
5538
+ if (forkContext.reachable) {
5539
+ getReturnContext(this).returnedForkContext.add(forkContext.head);
5540
+ forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5541
+ }
5542
+ }
5543
+ /**
5544
+ * Makes a path for a `throw` statement.
5545
+ *
5546
+ * It registers the head segment to a context of `throw`.
5547
+ * It makes new unreachable segment, then it set the head with the segment.
5548
+ */
5549
+ makeThrow() {
5550
+ const forkContext = this.forkContext;
5551
+ if (forkContext.reachable) {
5552
+ getThrowContext(this).thrownForkContext.add(forkContext.head);
5553
+ forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5554
+ }
5555
+ }
5556
+ /**
5557
+ * Makes the final path.
5558
+ */
5559
+ makeFinal() {
5560
+ const segments = this.currentSegments;
5561
+ if (segments.length > 0 && segments[0].reachable) this.returnedForkContext.add(segments);
5562
+ }
5563
+ };
5564
+ /**
5565
+ * A generator for unique ids.
5566
+ */
5567
+ var IdGenerator = class {
5568
+ prefix;
5569
+ n;
5570
+ constructor(prefix) {
5571
+ this.prefix = String(prefix);
5572
+ this.n = 0;
5573
+ }
5574
+ /**
5575
+ * Generates id.
5576
+ */
5577
+ next() {
5578
+ this.n = 1 + this.n | 0;
5579
+ /* c8 ignore start */
5580
+ if (this.n < 0) this.n = 1;
5581
+ return this.prefix + this.n;
5582
+ }
5583
+ };
5584
+ /**
5585
+ * A code path.
5586
+ */
5587
+ var CodePath = class {
5588
+ /** The identifier of this code path. */
5589
+ id;
5590
+ /**
5591
+ * The reason that this code path was started. May be "program",
5592
+ * "function", "class-field-initializer", or "class-static-block".
5593
+ */
5594
+ origin;
5595
+ /** The code path of the upper function scope. */
5596
+ upper;
5597
+ /** The code paths of nested function scopes. */
5598
+ childCodePaths;
5599
+ /** Internal state for the code path. */
5600
+ internal;
5601
+ constructor({ id, origin, upper, onLooped }) {
5602
+ this.id = id;
5603
+ this.origin = origin;
5604
+ this.upper = upper;
5605
+ this.childCodePaths = [];
5606
+ this.internal = new CodePathState(new IdGenerator(`${id}_`), onLooped);
5607
+ if (upper) upper.childCodePaths.push(this);
5608
+ }
5609
+ /**
5610
+ * Gets the state of a given code path.
6073
5611
  */
6074
- popBreakContext() {
6075
- const context = this.breakContext;
6076
- const forkContext = this.forkContext;
6077
- this.breakContext = context.upper;
6078
- if (!context.breakable) {
6079
- const brokenForkContext = context.brokenForkContext;
6080
- if (!brokenForkContext.empty) {
6081
- brokenForkContext.add(forkContext.head);
6082
- forkContext.replaceHead(brokenForkContext.makeNext(0, -1));
6083
- }
6084
- }
6085
- return context;
5612
+ static getState(codePath) {
5613
+ return codePath.internal;
5614
+ }
5615
+ /** The initial code path segment. */
5616
+ get initialSegment() {
5617
+ return this.internal.initialSegment;
6086
5618
  }
6087
5619
  /**
6088
- * Makes a path for a `break` statement.
6089
- *
6090
- * It registers the head segment to a context of `break`.
6091
- * It makes new unreachable segment, then it set the head with the segment.
6092
- * @param {string} label A label of the break statement.
6093
- * @returns {void}
5620
+ * Final code path segments.
5621
+ * This array is a mix of `returnedSegments` and `thrownSegments`.
6094
5622
  */
6095
- makeBreak(label) {
6096
- const forkContext = this.forkContext;
6097
- if (!forkContext.reachable) return;
6098
- const context = getBreakContext(this, label);
6099
- if (context) context.brokenForkContext.add(forkContext.head);
6100
- /* c8 ignore next */
6101
- forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5623
+ get finalSegments() {
5624
+ return this.internal.finalSegments;
6102
5625
  }
6103
5626
  /**
6104
- * Makes a path for a `continue` statement.
6105
- *
6106
- * It makes a looping path.
6107
- * It makes new unreachable segment, then it set the head with the segment.
6108
- * @param {string} label A label of the continue statement.
6109
- * @returns {void}
5627
+ * Final code path segments which is with `return` statements.
5628
+ * This array contains the last path segment if it's reachable.
5629
+ * Since the reachable last path returns `undefined`.
6110
5630
  */
6111
- makeContinue(label) {
6112
- const forkContext = this.forkContext;
6113
- if (!forkContext.reachable) return;
6114
- const context = getContinueContext(this, label);
6115
- if (context) if (context.continueDestSegments) {
6116
- makeLooped(this, forkContext.head, context.continueDestSegments);
6117
- if (context.type === "ForInStatement" || context.type === "ForOfStatement") context.brokenForkContext.add(forkContext.head);
6118
- } else context.continueForkContext.add(forkContext.head);
6119
- forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5631
+ get returnedSegments() {
5632
+ return this.internal.returnedForkContext;
5633
+ }
5634
+ /** Final code path segments which is with `throw` statements. */
5635
+ get thrownSegments() {
5636
+ return this.internal.thrownForkContext;
5637
+ }
5638
+ /** Current code path segments. */
5639
+ get currentSegments() {
5640
+ return this.internal.currentSegments;
6120
5641
  }
6121
5642
  /**
6122
- * Makes a path for a `return` statement.
5643
+ * Traverses all segments in this code path.
6123
5644
  *
6124
- * It registers the head segment to a context of `return`.
6125
- * It makes new unreachable segment, then it set the head with the segment.
6126
- * @returns {void}
5645
+ * This method enumerates segments in order from the head.
5646
+ *
5647
+ * The `controller` object has two methods:
5648
+ * - `controller.skip()` - Skip the following segments in this branch.
5649
+ * - `controller.break()` - Skip all following segments.
6127
5650
  */
6128
- makeReturn() {
6129
- const forkContext = this.forkContext;
6130
- if (forkContext.reachable) {
6131
- getReturnContext(this).returnedForkContext.add(forkContext.head);
6132
- forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5651
+ traverseSegments(options, callback) {
5652
+ let resolvedOptions;
5653
+ let resolvedCallback;
5654
+ if (typeof options === "function") {
5655
+ resolvedCallback = options;
5656
+ resolvedOptions = {};
5657
+ } else {
5658
+ resolvedOptions = options || {};
5659
+ resolvedCallback = callback;
5660
+ }
5661
+ const startSegment = resolvedOptions.first || this.internal.initialSegment;
5662
+ const lastSegment = resolvedOptions.last;
5663
+ let item = null;
5664
+ let index = 0;
5665
+ let end = 0;
5666
+ let segment = null;
5667
+ const visited = Object.create(null);
5668
+ const stack = [[startSegment, 0]];
5669
+ let skippedSegment = null;
5670
+ let broken = false;
5671
+ const controller = {
5672
+ skip() {
5673
+ if (stack.length <= 1) broken = true;
5674
+ else skippedSegment = stack[stack.length - 2][0];
5675
+ },
5676
+ break() {
5677
+ broken = true;
5678
+ }
5679
+ };
5680
+ function isVisited(prevSegment) {
5681
+ return visited[prevSegment.id] || segment.isLoopedPrevSegment(prevSegment);
5682
+ }
5683
+ while (stack.length > 0) {
5684
+ item = stack[stack.length - 1];
5685
+ segment = item[0];
5686
+ index = item[1];
5687
+ if (index === 0) {
5688
+ if (visited[segment.id]) {
5689
+ stack.pop();
5690
+ continue;
5691
+ }
5692
+ if (segment !== startSegment && segment.prevSegments.length > 0 && !segment.prevSegments.every(isVisited)) {
5693
+ stack.pop();
5694
+ continue;
5695
+ }
5696
+ if (skippedSegment && segment.prevSegments.includes(skippedSegment)) skippedSegment = null;
5697
+ visited[segment.id] = true;
5698
+ if (!skippedSegment) {
5699
+ resolvedCallback.call(this, segment, controller);
5700
+ if (segment === lastSegment) controller.skip();
5701
+ if (broken) break;
5702
+ }
5703
+ }
5704
+ end = segment.nextSegments.length - 1;
5705
+ if (index < end) {
5706
+ item[1] += 1;
5707
+ stack.push([segment.nextSegments[index], 0]);
5708
+ } else if (index === end) {
5709
+ item[0] = segment.nextSegments[index];
5710
+ item[1] = 0;
5711
+ } else stack.pop();
5712
+ }
5713
+ }
5714
+ };
5715
+ const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u;
5716
+ /**
5717
+ * Checks whether or not a given node is a `case` node (not `default` node).
5718
+ */
5719
+ function isCaseNode(node) {
5720
+ return Boolean(node.test);
5721
+ }
5722
+ /**
5723
+ * Checks if a given node appears as the value of a PropertyDefinition node.
5724
+ */
5725
+ function isPropertyDefinitionValue(node) {
5726
+ const parent = node.parent;
5727
+ return parent && parent.type === "PropertyDefinition" && parent.value === node;
5728
+ }
5729
+ /**
5730
+ * Checks whether the given logical operator is taken into account for the code
5731
+ * path analysis.
5732
+ */
5733
+ function isHandledLogicalOperator(operator) {
5734
+ return operator === "&&" || operator === "||" || operator === "??";
5735
+ }
5736
+ /**
5737
+ * Checks whether the given assignment operator is a logical assignment operator.
5738
+ * Logical assignments are taken into account for the code path analysis
5739
+ * because of their short-circuiting semantics.
5740
+ */
5741
+ function isLogicalAssignmentOperator(operator) {
5742
+ return operator === "&&=" || operator === "||=" || operator === "??=";
5743
+ }
5744
+ /**
5745
+ * Gets the label if the parent node of a given node is a LabeledStatement.
5746
+ */
5747
+ function getLabel(node) {
5748
+ if (node.parent.type === "LabeledStatement") return node.parent.label.name;
5749
+ return null;
5750
+ }
5751
+ /**
5752
+ * Checks whether or not a given logical expression node goes different path
5753
+ * between the `true` case and the `false` case.
5754
+ */
5755
+ function isForkingByTrueOrFalse(node) {
5756
+ const parent = node.parent;
5757
+ switch (parent.type) {
5758
+ case "ConditionalExpression":
5759
+ case "IfStatement":
5760
+ case "WhileStatement":
5761
+ case "DoWhileStatement":
5762
+ case "ForStatement": return parent.test === node;
5763
+ case "LogicalExpression": return isHandledLogicalOperator(parent.operator);
5764
+ case "AssignmentExpression": return isLogicalAssignmentOperator(parent.operator);
5765
+ default: return false;
5766
+ }
5767
+ }
5768
+ /**
5769
+ * Gets the boolean value of a given literal node.
5770
+ *
5771
+ * This is used to detect infinity loops (e.g. `while (true) {}`).
5772
+ * Statements preceded by an infinity loop are unreachable if the loop didn't
5773
+ * have any `break` statement.
5774
+ */
5775
+ function getBooleanValueIfSimpleConstant(node) {
5776
+ if (node.type === "Literal") return Boolean(node.value);
5777
+ }
5778
+ /**
5779
+ * Checks that a given identifier node is a reference or not.
5780
+ *
5781
+ * This is used to detect the first throwable node in a `try` block.
5782
+ */
5783
+ function isIdentifierReference(node) {
5784
+ const parent = node.parent;
5785
+ switch (parent.type) {
5786
+ case "LabeledStatement":
5787
+ case "BreakStatement":
5788
+ case "ContinueStatement":
5789
+ case "ArrayPattern":
5790
+ case "RestElement":
5791
+ case "ImportSpecifier":
5792
+ case "ImportDefaultSpecifier":
5793
+ case "ImportNamespaceSpecifier":
5794
+ case "CatchClause": return false;
5795
+ case "FunctionDeclaration":
5796
+ case "ComponentDeclaration":
5797
+ case "HookDeclaration":
5798
+ case "FunctionExpression":
5799
+ case "ArrowFunctionExpression":
5800
+ case "ClassDeclaration":
5801
+ case "ClassExpression":
5802
+ case "VariableDeclarator": return parent.id !== node;
5803
+ case "Property":
5804
+ case "PropertyDefinition":
5805
+ case "MethodDefinition": return parent.key !== node || parent.computed || parent.shorthand;
5806
+ case "AssignmentPattern": return parent.key !== node;
5807
+ default: return true;
5808
+ }
5809
+ }
5810
+ /**
5811
+ * Updates the current segment with the head segment.
5812
+ * This is similar to local branches and tracking branches of git.
5813
+ *
5814
+ * To separate the current and the head is in order to not make useless segments.
5815
+ *
5816
+ * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
5817
+ * events are fired.
5818
+ */
5819
+ function forwardCurrentToHead(analyzer, node) {
5820
+ const codePath = analyzer.codePath;
5821
+ const state = CodePath.getState(codePath);
5822
+ const currentSegments = state.currentSegments;
5823
+ const headSegments = state.headSegments;
5824
+ const end = Math.max(currentSegments.length, headSegments.length);
5825
+ let i;
5826
+ let currentSegment;
5827
+ let headSegment;
5828
+ for (i = 0; i < end; ++i) {
5829
+ currentSegment = currentSegments[i];
5830
+ headSegment = headSegments[i];
5831
+ if (currentSegment !== headSegment && currentSegment) {
5832
+ if (currentSegment.reachable) analyzer.emitter.emit("onCodePathSegmentEnd", currentSegment, node);
6133
5833
  }
6134
5834
  }
6135
- /**
6136
- * Makes a path for a `throw` statement.
6137
- *
6138
- * It registers the head segment to a context of `throw`.
6139
- * It makes new unreachable segment, then it set the head with the segment.
6140
- * @returns {void}
6141
- */
6142
- makeThrow() {
6143
- const forkContext = this.forkContext;
6144
- if (forkContext.reachable) {
6145
- getThrowContext(this).thrownForkContext.add(forkContext.head);
6146
- forkContext.replaceHead(forkContext.makeUnreachable(-1, -1));
5835
+ state.currentSegments = headSegments;
5836
+ for (i = 0; i < end; ++i) {
5837
+ currentSegment = currentSegments[i];
5838
+ headSegment = headSegments[i];
5839
+ if (currentSegment !== headSegment && headSegment) {
5840
+ CodePathSegment.markUsed(headSegment);
5841
+ if (headSegment.reachable) analyzer.emitter.emit("onCodePathSegmentStart", headSegment, node);
6147
5842
  }
6148
5843
  }
5844
+ }
5845
+ /**
5846
+ * Updates the current segment with empty.
5847
+ * This is called at the last of functions or the program.
5848
+ */
5849
+ function leaveFromCurrentSegment(analyzer, node) {
5850
+ const state = CodePath.getState(analyzer.codePath);
5851
+ const currentSegments = state.currentSegments;
5852
+ for (let i = 0; i < currentSegments.length; ++i) {
5853
+ const currentSegment = currentSegments[i];
5854
+ if (currentSegment.reachable) analyzer.emitter.emit("onCodePathSegmentEnd", currentSegment, node);
5855
+ }
5856
+ state.currentSegments = [];
5857
+ }
5858
+ /**
5859
+ * Updates the code path due to the position of a given node in the parent node
5860
+ * thereof.
5861
+ *
5862
+ * For example, if the node is `parent.consequent`, this creates a fork from the
5863
+ * current path.
5864
+ */
5865
+ function preprocess(analyzer, node) {
5866
+ const codePath = analyzer.codePath;
5867
+ const state = CodePath.getState(codePath);
5868
+ const parent = node.parent;
5869
+ switch (parent.type) {
5870
+ case "CallExpression":
5871
+ if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) state.makeOptionalRight();
5872
+ break;
5873
+ case "MemberExpression":
5874
+ if (parent.optional === true && parent.property === node) state.makeOptionalRight();
5875
+ break;
5876
+ case "LogicalExpression":
5877
+ if (parent.right === node && isHandledLogicalOperator(parent.operator)) state.makeLogicalRight();
5878
+ break;
5879
+ case "AssignmentExpression":
5880
+ if (parent.right === node && isLogicalAssignmentOperator(parent.operator)) state.makeLogicalRight();
5881
+ break;
5882
+ case "ConditionalExpression":
5883
+ case "IfStatement":
5884
+ if (parent.consequent === node) state.makeIfConsequent();
5885
+ else if (parent.alternate === node) state.makeIfAlternate();
5886
+ break;
5887
+ case "SwitchCase":
5888
+ if (parent.consequent[0] === node) state.makeSwitchCaseBody(false, !parent.test);
5889
+ break;
5890
+ case "TryStatement":
5891
+ if (parent.handler === node) state.makeCatchBlock();
5892
+ else if (parent.finalizer === node) state.makeFinallyBlock();
5893
+ break;
5894
+ case "WhileStatement":
5895
+ if (parent.test === node) state.makeWhileTest(getBooleanValueIfSimpleConstant(node));
5896
+ else {
5897
+ assert(parent.body === node);
5898
+ state.makeWhileBody();
5899
+ }
5900
+ break;
5901
+ case "DoWhileStatement":
5902
+ if (parent.body === node) state.makeDoWhileBody();
5903
+ else {
5904
+ assert(parent.test === node);
5905
+ state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node));
5906
+ }
5907
+ break;
5908
+ case "ForStatement":
5909
+ if (parent.test === node) state.makeForTest(getBooleanValueIfSimpleConstant(node));
5910
+ else if (parent.update === node) state.makeForUpdate();
5911
+ else if (parent.body === node) state.makeForBody();
5912
+ break;
5913
+ case "ForInStatement":
5914
+ case "ForOfStatement":
5915
+ if (parent.left === node) state.makeForInOfLeft();
5916
+ else if (parent.right === node) state.makeForInOfRight();
5917
+ else {
5918
+ assert(parent.body === node);
5919
+ state.makeForInOfBody();
5920
+ }
5921
+ break;
5922
+ case "AssignmentPattern":
5923
+ if (parent.right === node) {
5924
+ state.pushForkContext();
5925
+ state.forkBypassPath();
5926
+ state.forkPath();
5927
+ }
5928
+ break;
5929
+ default: break;
5930
+ }
5931
+ }
5932
+ /**
5933
+ * Updates the code path due to the type of a given node in entering.
5934
+ */
5935
+ function processCodePathToEnter(analyzer, node) {
5936
+ let codePath = analyzer.codePath;
5937
+ let state = codePath && CodePath.getState(codePath);
5938
+ const parent = node.parent;
6149
5939
  /**
6150
- * Makes the final path.
6151
- * @returns {void}
5940
+ * Creates a new code path and trigger the onCodePathStart event
5941
+ * based on the currently selected node.
6152
5942
  */
6153
- makeFinal() {
6154
- const segments = this.currentSegments;
6155
- if (segments.length > 0 && segments[0].reachable) this.returnedForkContext.add(segments);
5943
+ function startCodePath(origin) {
5944
+ if (codePath) forwardCurrentToHead(analyzer, node);
5945
+ codePath = analyzer.codePath = new CodePath({
5946
+ id: analyzer.idGenerator.next(),
5947
+ origin,
5948
+ upper: codePath,
5949
+ onLooped: analyzer.onLooped
5950
+ });
5951
+ state = CodePath.getState(codePath);
5952
+ analyzer.emitter.emit("onCodePathStart", codePath, node);
5953
+ }
5954
+ if (isPropertyDefinitionValue(node)) startCodePath("class-field-initializer");
5955
+ switch (node.type) {
5956
+ case "Program":
5957
+ startCodePath("program");
5958
+ break;
5959
+ case "FunctionDeclaration":
5960
+ case "ComponentDeclaration":
5961
+ case "HookDeclaration":
5962
+ case "FunctionExpression":
5963
+ case "ArrowFunctionExpression":
5964
+ startCodePath("function");
5965
+ break;
5966
+ case "StaticBlock":
5967
+ startCodePath("class-static-block");
5968
+ break;
5969
+ case "ChainExpression":
5970
+ state.pushChainContext();
5971
+ break;
5972
+ case "CallExpression":
5973
+ if (node.optional === true) state.makeOptionalNode();
5974
+ break;
5975
+ case "MemberExpression":
5976
+ if (node.optional === true) state.makeOptionalNode();
5977
+ break;
5978
+ case "LogicalExpression":
5979
+ if (isHandledLogicalOperator(node.operator)) state.pushChoiceContext(node.operator, isForkingByTrueOrFalse(node));
5980
+ break;
5981
+ case "AssignmentExpression":
5982
+ if (isLogicalAssignmentOperator(node.operator)) state.pushChoiceContext(node.operator.slice(0, -1), isForkingByTrueOrFalse(node));
5983
+ break;
5984
+ case "ConditionalExpression":
5985
+ case "IfStatement":
5986
+ state.pushChoiceContext("test", false);
5987
+ break;
5988
+ case "SwitchStatement":
5989
+ state.pushSwitchContext(node.cases.some(isCaseNode), getLabel(node));
5990
+ break;
5991
+ case "TryStatement":
5992
+ state.pushTryContext(Boolean(node.finalizer));
5993
+ break;
5994
+ case "SwitchCase":
5995
+ if (parent.discriminant !== node && parent.cases[0] !== node) state.forkPath();
5996
+ break;
5997
+ case "WhileStatement":
5998
+ case "DoWhileStatement":
5999
+ case "ForStatement":
6000
+ case "ForInStatement":
6001
+ case "ForOfStatement":
6002
+ state.pushLoopContext(node.type, getLabel(node));
6003
+ break;
6004
+ case "LabeledStatement":
6005
+ if (!breakableTypePattern.test(node.body.type)) state.pushBreakContext(false, node.label.name);
6006
+ break;
6007
+ default: break;
6008
+ }
6009
+ forwardCurrentToHead(analyzer, node);
6010
+ }
6011
+ /**
6012
+ * Updates the code path due to the type of a given node in leaving.
6013
+ */
6014
+ function processCodePathToExit(analyzer, node) {
6015
+ const codePath = analyzer.codePath;
6016
+ const state = CodePath.getState(codePath);
6017
+ let dontForward = false;
6018
+ switch (node.type) {
6019
+ case "ChainExpression":
6020
+ state.popChainContext();
6021
+ break;
6022
+ case "IfStatement":
6023
+ case "ConditionalExpression":
6024
+ state.popChoiceContext();
6025
+ break;
6026
+ case "LogicalExpression":
6027
+ if (isHandledLogicalOperator(node.operator)) state.popChoiceContext();
6028
+ break;
6029
+ case "AssignmentExpression":
6030
+ if (isLogicalAssignmentOperator(node.operator)) state.popChoiceContext();
6031
+ break;
6032
+ case "SwitchStatement":
6033
+ state.popSwitchContext();
6034
+ break;
6035
+ case "SwitchCase":
6036
+ if (node.consequent.length === 0) state.makeSwitchCaseBody(true, !node.test);
6037
+ if (state.forkContext.reachable) dontForward = true;
6038
+ break;
6039
+ case "TryStatement":
6040
+ state.popTryContext();
6041
+ break;
6042
+ case "BreakStatement":
6043
+ forwardCurrentToHead(analyzer, node);
6044
+ state.makeBreak(node.label && node.label.name);
6045
+ dontForward = true;
6046
+ break;
6047
+ case "ContinueStatement":
6048
+ forwardCurrentToHead(analyzer, node);
6049
+ state.makeContinue(node.label && node.label.name);
6050
+ dontForward = true;
6051
+ break;
6052
+ case "ReturnStatement":
6053
+ forwardCurrentToHead(analyzer, node);
6054
+ state.makeReturn();
6055
+ dontForward = true;
6056
+ break;
6057
+ case "ThrowStatement":
6058
+ forwardCurrentToHead(analyzer, node);
6059
+ state.makeThrow();
6060
+ dontForward = true;
6061
+ break;
6062
+ case "Identifier":
6063
+ if (isIdentifierReference(node)) {
6064
+ state.makeFirstThrowablePathInTryBlock();
6065
+ dontForward = true;
6066
+ }
6067
+ break;
6068
+ case "CallExpression":
6069
+ case "ImportExpression":
6070
+ case "MemberExpression":
6071
+ case "NewExpression":
6072
+ case "YieldExpression":
6073
+ state.makeFirstThrowablePathInTryBlock();
6074
+ break;
6075
+ case "WhileStatement":
6076
+ case "DoWhileStatement":
6077
+ case "ForStatement":
6078
+ case "ForInStatement":
6079
+ case "ForOfStatement":
6080
+ state.popLoopContext();
6081
+ break;
6082
+ case "AssignmentPattern":
6083
+ state.popForkContext();
6084
+ break;
6085
+ case "LabeledStatement":
6086
+ if (!breakableTypePattern.test(node.body.type)) state.popBreakContext();
6087
+ break;
6088
+ default: break;
6156
6089
  }
6157
- };
6090
+ if (!dontForward) forwardCurrentToHead(analyzer, node);
6091
+ }
6158
6092
  /**
6159
- * A generator for unique ids.
6093
+ * Updates the code path to finalize the current code path.
6160
6094
  */
6161
- var IdGenerator = class {
6162
- /**
6163
- * @param {string} prefix Optional. A prefix of generated ids.
6164
- */
6165
- constructor(prefix) {
6166
- this.prefix = String(prefix);
6167
- this.n = 0;
6095
+ function postprocess(analyzer, node) {
6096
+ function endCodePath() {
6097
+ const codePath = analyzer.codePath;
6098
+ CodePath.getState(codePath).makeFinal();
6099
+ leaveFromCurrentSegment(analyzer, node);
6100
+ analyzer.emitter.emit("onCodePathEnd", codePath, node);
6101
+ analyzer.codePath = analyzer.codePath.upper;
6168
6102
  }
6169
- /**
6170
- * Generates id.
6171
- * @returns {string} A generated id.
6172
- */
6173
- next() {
6174
- this.n = 1 + this.n | 0;
6175
- /* c8 ignore start */
6176
- if (this.n < 0) this.n = 1;
6177
- return this.prefix + this.n;
6103
+ switch (node.type) {
6104
+ case "Program":
6105
+ case "FunctionDeclaration":
6106
+ case "ComponentDeclaration":
6107
+ case "HookDeclaration":
6108
+ case "FunctionExpression":
6109
+ case "ArrowFunctionExpression":
6110
+ case "StaticBlock":
6111
+ endCodePath();
6112
+ break;
6113
+ case "CallExpression":
6114
+ if (node.optional === true && node.arguments.length === 0) CodePath.getState(analyzer.codePath).makeOptionalRight();
6115
+ break;
6116
+ default: break;
6178
6117
  }
6179
- };
6118
+ if (isPropertyDefinitionValue(node)) endCodePath();
6119
+ }
6180
6120
  /**
6181
- * A code path.
6121
+ * The class to analyze code paths.
6122
+ * This class implements the EventGenerator interface.
6182
6123
  */
6183
- var CodePath = class {
6184
- /**
6185
- * Creates a new instance.
6186
- * @param {Object} options Options for the function (see below).
6187
- * @param {string} options.id An identifier.
6188
- * @param {string} options.origin The type of code path origin.
6189
- * @param {CodePath|null} options.upper The code path of the upper function scope.
6190
- * @param {Function} options.onLooped A callback function to notify looping.
6191
- */
6192
- constructor({ id, origin, upper, onLooped }) {
6193
- /**
6194
- * The identifier of this code path.
6195
- * Rules use it to store additional information of each rule.
6196
- * @type {string}
6197
- */
6198
- this.id = id;
6199
- /**
6200
- * The reason that this code path was started. May be "program",
6201
- * "function", "class-field-initializer", or "class-static-block".
6202
- * @type {string}
6203
- */
6204
- this.origin = origin;
6205
- /**
6206
- * The code path of the upper function scope.
6207
- * @type {CodePath|null}
6208
- */
6209
- this.upper = upper;
6210
- /**
6211
- * The code paths of nested function scopes.
6212
- * @type {CodePath[]}
6213
- */
6214
- this.childCodePaths = [];
6215
- Object.defineProperty(this, "internal", { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) });
6216
- if (upper) upper.childCodePaths.push(this);
6124
+ var CodePathAnalyzer = class {
6125
+ emitter;
6126
+ codePath;
6127
+ idGenerator;
6128
+ currentNode;
6129
+ onLooped;
6130
+ constructor(emitters) {
6131
+ this.emitter = { emit(event, ...args) {
6132
+ emitters[event]?.(...args);
6133
+ } };
6134
+ this.codePath = null;
6135
+ this.idGenerator = new IdGenerator("s");
6136
+ this.currentNode = null;
6137
+ this.onLooped = this._onLooped.bind(this);
6217
6138
  }
6218
6139
  /**
6219
- * Gets the state of a given code path.
6220
- * @param {CodePath} codePath A code path to get.
6221
- * @returns {CodePathState} The state of the code path.
6140
+ * Does the process to enter a given AST node.
6141
+ * This updates state of analysis and calls `enterNode` of the wrapped.
6222
6142
  */
6223
- static getState(codePath) {
6224
- return codePath.internal;
6143
+ enterNode(node) {
6144
+ this.currentNode = node;
6145
+ if (node.parent) preprocess(this, node);
6146
+ processCodePathToEnter(this, node);
6147
+ this.currentNode = null;
6225
6148
  }
6226
6149
  /**
6227
- * The initial code path segment.
6228
- * @type {CodePathSegment}
6150
+ * Does the process to leave a given AST node.
6151
+ * This updates state of analysis and calls `leaveNode` of the wrapped.
6229
6152
  */
6230
- get initialSegment() {
6231
- return this.internal.initialSegment;
6153
+ leaveNode(node) {
6154
+ this.currentNode = node;
6155
+ processCodePathToExit(this, node);
6156
+ postprocess(this, node);
6157
+ this.currentNode = null;
6232
6158
  }
6233
6159
  /**
6234
- * Final code path segments.
6235
- * This array is a mix of `returnedSegments` and `thrownSegments`.
6236
- * @type {CodePathSegment[]}
6160
+ * This is called on a code path looped.
6161
+ * Then this raises a looped event.
6237
6162
  */
6238
- get finalSegments() {
6239
- return this.internal.finalSegments;
6163
+ _onLooped(fromSegment, toSegment) {
6164
+ if (fromSegment.reachable && toSegment.reachable) this.emitter.emit("onCodePathSegmentLoop", fromSegment, toSegment, this.currentNode);
6240
6165
  }
6241
- /**
6242
- * Final code path segments which is with `return` statements.
6243
- * This array contains the last path segment if it's reachable.
6244
- * Since the reachable last path returns `undefined`.
6245
- * @type {CodePathSegment[]}
6246
- */
6247
- get returnedSegments() {
6248
- return this.internal.returnedForkContext;
6166
+ };
6167
+
6168
+ //#endregion
6169
+ //#region src/rules/rules-of-hooks/rules-of-hooks.ts
6170
+ /**
6171
+ * Catch all identifiers that begin with "use" followed by an uppercase Latin
6172
+ * character to exclude identifiers like "user".
6173
+ */
6174
+ function isHookName(s) {
6175
+ return s === "use" || /^use[A-Z0-9]/.test(s);
6176
+ }
6177
+ /**
6178
+ * We consider hooks to be a hook name identifier or a member expression
6179
+ * containing a hook name.
6180
+ */
6181
+ function isHook(node) {
6182
+ if (node.type === "Identifier") return isHookName(node.name);
6183
+ else if (node.type === "MemberExpression" && !node.computed && isHook(node.property)) {
6184
+ const obj = node.object;
6185
+ return obj.type === "Identifier" && /^[A-Z].*/.test(obj.name);
6186
+ } else return false;
6187
+ }
6188
+ /**
6189
+ * Checks if the node is a React component name. React component names must
6190
+ * always start with an uppercase letter.
6191
+ */
6192
+ function isComponentName(node) {
6193
+ return node.type === "Identifier" && /^[A-Z]/.test(node.name);
6194
+ }
6195
+ function isReactFunction(node, functionName) {
6196
+ return "name" in node && node.name === functionName || node.type === "MemberExpression" && "name" in node.object && node.object.name === "React" && "name" in node.property && node.property.name === functionName;
6197
+ }
6198
+ /**
6199
+ * Checks if the node is a callback argument of forwardRef. This render function
6200
+ * should follow the rules of hooks.
6201
+ */
6202
+ function isForwardRefCallback(node) {
6203
+ return !!(node.parent && "callee" in node.parent && node.parent.callee && isReactFunction(node.parent.callee, "forwardRef"));
6204
+ }
6205
+ /**
6206
+ * Checks if the node is a callback argument of React.memo. This anonymous
6207
+ * functional component should follow the rules of hooks.
6208
+ */
6209
+ function isMemoCallback(node) {
6210
+ return !!(node.parent && "callee" in node.parent && node.parent.callee && isReactFunction(node.parent.callee, "memo"));
6211
+ }
6212
+ function isInsideComponentOrHook(node) {
6213
+ while (node) {
6214
+ const functionName = getFunctionName(node);
6215
+ if (functionName) {
6216
+ if (isComponentName(functionName) || isHook(functionName)) return true;
6217
+ }
6218
+ if (isForwardRefCallback(node) || isMemoCallback(node)) return true;
6219
+ node = node.parent;
6249
6220
  }
6250
- /**
6251
- * Final code path segments which is with `throw` statements.
6252
- * @type {CodePathSegment[]}
6253
- */
6254
- get thrownSegments() {
6255
- return this.internal.thrownForkContext;
6221
+ return false;
6222
+ }
6223
+ function isInsideDoWhileLoop(node) {
6224
+ while (node) {
6225
+ if (node.type === "DoWhileStatement") return true;
6226
+ node = node.parent;
6256
6227
  }
6257
- /**
6258
- * Current code path segments.
6259
- * @type {CodePathSegment[]}
6260
- */
6261
- get currentSegments() {
6262
- return this.internal.currentSegments;
6228
+ return false;
6229
+ }
6230
+ function isInsideTryCatch(node) {
6231
+ while (node) {
6232
+ if (node.type === "TryStatement" || node.type === "CatchClause") return true;
6233
+ node = node.parent;
6263
6234
  }
6264
- /**
6265
- * Traverses all segments in this code path.
6266
- *
6267
- * codePath.traverseSegments(function(segment, controller) {
6268
- * // do something.
6269
- * });
6270
- *
6271
- * This method enumerates segments in order from the head.
6272
- *
6273
- * The `controller` object has two methods.
6274
- *
6275
- * - `controller.skip()` - Skip the following segments in this branch.
6276
- * - `controller.break()` - Skip all following segments.
6277
- * @param {Object} [options] Omittable.
6278
- * @param {CodePathSegment} [options.first] The first segment to traverse.
6279
- * @param {CodePathSegment} [options.last] The last segment to traverse.
6280
- * @param {Function} callback A callback function.
6281
- * @returns {void}
6282
- */
6283
- traverseSegments(options, callback) {
6284
- let resolvedOptions;
6285
- let resolvedCallback;
6286
- if (typeof options === "function") {
6287
- resolvedCallback = options;
6288
- resolvedOptions = {};
6289
- } else {
6290
- resolvedOptions = options || {};
6291
- resolvedCallback = callback;
6292
- }
6293
- const startSegment = resolvedOptions.first || this.internal.initialSegment;
6294
- const lastSegment = resolvedOptions.last;
6295
- let item = null;
6296
- let index = 0;
6297
- let end = 0;
6298
- let segment = null;
6299
- const visited = Object.create(null);
6300
- const stack = [[startSegment, 0]];
6301
- let skippedSegment = null;
6302
- let broken = false;
6303
- const controller = {
6304
- skip() {
6305
- if (stack.length <= 1) broken = true;
6306
- else skippedSegment = stack[stack.length - 2][0];
6307
- },
6308
- break() {
6309
- broken = true;
6235
+ return false;
6236
+ }
6237
+ function getNodeWithoutReactNamespace(node) {
6238
+ if (node.type === "MemberExpression" && node.object.type === "Identifier" && node.object.name === "React" && node.property.type === "Identifier" && !node.computed) return node.property;
6239
+ return node;
6240
+ }
6241
+ function isEffectIdentifier(node, additionalHooks) {
6242
+ if (node.type === "Identifier" && (node.name === "useEffect" || node.name === "useLayoutEffect" || node.name === "useInsertionEffect")) return true;
6243
+ if (additionalHooks && node.type === "Identifier") return additionalHooks.test(node.name);
6244
+ return false;
6245
+ }
6246
+ function isUseEffectEventIdentifier(node) {
6247
+ return node.type === "Identifier" && node.name === "useEffectEvent";
6248
+ }
6249
+ function useEffectEventError(fn, called) {
6250
+ if (fn === null) return "React Hook \"useEffectEvent\" can only be called at the top level of your component. It cannot be passed down.";
6251
+ return `\`${fn}\` is a function created with React Hook "useEffectEvent", and can only be called from Effects and Effect Events in the same component.` + (called ? "" : " It cannot be assigned to a variable or passed down.");
6252
+ }
6253
+ function isUseIdentifier(node) {
6254
+ return isReactFunction(node, "use");
6255
+ }
6256
+ const rule = {
6257
+ meta: {
6258
+ type: "problem",
6259
+ docs: {
6260
+ description: "Enforces the Rules of Hooks.",
6261
+ recommended: true,
6262
+ url: "https://react.dev/reference/rules/rules-of-hooks"
6263
+ },
6264
+ schema: [{
6265
+ type: "object",
6266
+ additionalProperties: false,
6267
+ properties: { additionalHooks: { type: "string" } }
6268
+ }]
6269
+ },
6270
+ create(context) {
6271
+ context.settings;
6272
+ const rawOptions = context.options && context.options[0];
6273
+ const additionalEffectHooks = rawOptions && rawOptions.additionalHooks ? new RegExp(rawOptions.additionalHooks) : getSettingsFromContext(context).additionalEffectHooks;
6274
+ let lastEffect = null;
6275
+ const codePathReactHooksMapStack = [];
6276
+ const codePathSegmentStack = [];
6277
+ const useEffectEventFunctions = /* @__PURE__ */ new WeakSet();
6278
+ function recordAllUseEffectEventFunctions(scope) {
6279
+ for (const reference of scope.references) {
6280
+ const parent = reference.identifier.parent;
6281
+ if (parent?.type === "VariableDeclarator" && parent.init && parent.init.type === "CallExpression" && parent.init.callee && isUseEffectEventIdentifier(parent.init.callee)) {
6282
+ if (reference.resolved === null) throw new Error("Unexpected null reference.resolved");
6283
+ for (const ref of reference.resolved.references) if (ref !== reference) useEffectEventFunctions.add(ref.identifier);
6284
+ }
6310
6285
  }
6286
+ }
6287
+ /**
6288
+ * SourceCode that also works down to ESLint 3.0.0
6289
+ */
6290
+ const getSourceCode = typeof context.getSourceCode === "function" ? () => {
6291
+ return context.getSourceCode();
6292
+ } : () => {
6293
+ return context.sourceCode;
6311
6294
  };
6312
6295
  /**
6313
- * Checks a given previous segment has been visited.
6314
- * @param {CodePathSegment} prevSegment A previous segment to check.
6315
- * @returns {boolean} `true` if the segment has been visited.
6296
+ * SourceCode#getScope that also works down to ESLint 3.0.0
6316
6297
  */
6317
- function isVisited(prevSegment) {
6318
- return visited[prevSegment.id] || segment.isLoopedPrevSegment(prevSegment);
6298
+ const getScope = typeof context.getScope === "function" ? () => {
6299
+ return context.getScope();
6300
+ } : (node) => {
6301
+ return getSourceCode().getScope(node);
6302
+ };
6303
+ function hasFlowSuppression(node, suppression) {
6304
+ const comments = getSourceCode().getAllComments();
6305
+ const flowSuppressionRegex = new RegExp("\\$FlowFixMe\\[" + suppression + "\\]");
6306
+ return comments.some((commentNode) => flowSuppressionRegex.test(commentNode.value) && commentNode.loc != null && node.loc != null && commentNode.loc.end.line === node.loc.start.line - 1);
6319
6307
  }
6320
- while (stack.length > 0) {
6321
- item = stack[stack.length - 1];
6322
- segment = item[0];
6323
- index = item[1];
6324
- if (index === 0) {
6325
- if (visited[segment.id]) {
6326
- stack.pop();
6327
- continue;
6308
+ const analyzer = new CodePathAnalyzer({
6309
+ onCodePathSegmentStart: (segment) => codePathSegmentStack.push(segment),
6310
+ onCodePathSegmentEnd: () => codePathSegmentStack.pop(),
6311
+ onCodePathStart: () => codePathReactHooksMapStack.push(/* @__PURE__ */ new Map()),
6312
+ onCodePathEnd(codePath, codePathNode) {
6313
+ const reactHooksMap = codePathReactHooksMapStack.pop();
6314
+ if (reactHooksMap?.size === 0) return;
6315
+ else if (typeof reactHooksMap === "undefined") throw new Error("Unexpected undefined reactHooksMap");
6316
+ const cyclic = /* @__PURE__ */ new Set();
6317
+ /**
6318
+ * Count the number of code paths from the start of the function to this
6319
+ * segment. For example:
6320
+ *
6321
+ * ```js
6322
+ * function MyComponent() {
6323
+ * if (condition) {
6324
+ * // Segment 1
6325
+ * } else {
6326
+ * // Segment 2
6327
+ * }
6328
+ * // Segment 3
6329
+ * }
6330
+ * ```
6331
+ *
6332
+ * Segments 1 and 2 have one path to the beginning of `MyComponent` and
6333
+ * segment 3 has two paths to the beginning of `MyComponent` since we
6334
+ * could have either taken the path of segment 1 or segment 2.
6335
+ *
6336
+ * Populates `cyclic` with cyclic segments.
6337
+ */
6338
+ function countPathsFromStart(segment, pathHistory) {
6339
+ const { cache } = countPathsFromStart;
6340
+ let paths = cache.get(segment.id);
6341
+ const pathList = new Set(pathHistory);
6342
+ if (pathList.has(segment.id)) {
6343
+ const pathArray = [...pathList];
6344
+ const cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
6345
+ for (const cyclicSegment of cyclicSegments) cyclic.add(cyclicSegment);
6346
+ return BigInt("0");
6347
+ }
6348
+ pathList.add(segment.id);
6349
+ if (paths !== void 0) return paths;
6350
+ if (codePath.thrownSegments.includes(segment)) paths = BigInt("0");
6351
+ else if (segment.prevSegments.length === 0) paths = BigInt("1");
6352
+ else {
6353
+ paths = BigInt("0");
6354
+ for (const prevSegment of segment.prevSegments) paths += countPathsFromStart(prevSegment, pathList);
6355
+ }
6356
+ if (segment.reachable && paths === BigInt("0")) cache.delete(segment.id);
6357
+ else cache.set(segment.id, paths);
6358
+ return paths;
6359
+ }
6360
+ /**
6361
+ * Count the number of code paths from this segment to the end of the
6362
+ * function. For example:
6363
+ *
6364
+ * ```js
6365
+ * function MyComponent() {
6366
+ * // Segment 1
6367
+ * if (condition) {
6368
+ * // Segment 2
6369
+ * } else {
6370
+ * // Segment 3
6371
+ * }
6372
+ * }
6373
+ * ```
6374
+ *
6375
+ * Segments 2 and 3 have one path to the end of `MyComponent` and
6376
+ * segment 1 has two paths to the end of `MyComponent` since we could
6377
+ * either take the path of segment 1 or segment 2.
6378
+ *
6379
+ * Populates `cyclic` with cyclic segments.
6380
+ */
6381
+ function countPathsToEnd(segment, pathHistory) {
6382
+ const { cache } = countPathsToEnd;
6383
+ let paths = cache.get(segment.id);
6384
+ const pathList = new Set(pathHistory);
6385
+ if (pathList.has(segment.id)) {
6386
+ const pathArray = Array.from(pathList);
6387
+ const cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
6388
+ for (const cyclicSegment of cyclicSegments) cyclic.add(cyclicSegment);
6389
+ return BigInt("0");
6390
+ }
6391
+ pathList.add(segment.id);
6392
+ if (paths !== void 0) return paths;
6393
+ if (codePath.thrownSegments.includes(segment)) paths = BigInt("0");
6394
+ else if (segment.nextSegments.length === 0) paths = BigInt("1");
6395
+ else {
6396
+ paths = BigInt("0");
6397
+ for (const nextSegment of segment.nextSegments) paths += countPathsToEnd(nextSegment, pathList);
6398
+ }
6399
+ cache.set(segment.id, paths);
6400
+ return paths;
6401
+ }
6402
+ /**
6403
+ * Gets the shortest path length to the start of a code path.
6404
+ * For example:
6405
+ *
6406
+ * ```js
6407
+ * function MyComponent() {
6408
+ * if (condition) {
6409
+ * // Segment 1
6410
+ * }
6411
+ * // Segment 2
6412
+ * }
6413
+ * ```
6414
+ *
6415
+ * There is only one path from segment 1 to the code path start. Its
6416
+ * length is one so that is the shortest path.
6417
+ *
6418
+ * There are two paths from segment 2 to the code path start. One
6419
+ * through segment 1 with a length of two and another directly to the
6420
+ * start with a length of one. The shortest path has a length of one
6421
+ * so we would return that.
6422
+ */
6423
+ function shortestPathLengthToStart(segment) {
6424
+ const { cache } = shortestPathLengthToStart;
6425
+ let length = cache.get(segment.id);
6426
+ if (length === null) return Infinity;
6427
+ if (length !== void 0) return length;
6428
+ cache.set(segment.id, null);
6429
+ if (segment.prevSegments.length === 0) length = 1;
6430
+ else {
6431
+ length = Infinity;
6432
+ for (const prevSegment of segment.prevSegments) {
6433
+ const prevLength = shortestPathLengthToStart(prevSegment);
6434
+ if (prevLength < length) length = prevLength;
6435
+ }
6436
+ length += 1;
6437
+ }
6438
+ cache.set(segment.id, length);
6439
+ return length;
6440
+ }
6441
+ countPathsFromStart.cache = /* @__PURE__ */ new Map();
6442
+ countPathsToEnd.cache = /* @__PURE__ */ new Map();
6443
+ shortestPathLengthToStart.cache = /* @__PURE__ */ new Map();
6444
+ const allPathsFromStartToEnd = countPathsToEnd(codePath.initialSegment);
6445
+ const codePathFunctionName = getFunctionName(codePathNode);
6446
+ const isSomewhereInsideComponentOrHook = isInsideComponentOrHook(codePathNode);
6447
+ const isDirectlyInsideComponentOrHook = codePathFunctionName ? isComponentName(codePathFunctionName) || isHook(codePathFunctionName) : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode);
6448
+ let shortestFinalPathLength = Infinity;
6449
+ for (const finalSegment of codePath.finalSegments) {
6450
+ if (!finalSegment.reachable) continue;
6451
+ const length = shortestPathLengthToStart(finalSegment);
6452
+ if (length < shortestFinalPathLength) shortestFinalPathLength = length;
6453
+ }
6454
+ for (const [segment, reactHooks] of reactHooksMap) {
6455
+ if (!segment.reachable) continue;
6456
+ const possiblyHasEarlyReturn = segment.nextSegments.length === 0 ? shortestFinalPathLength <= shortestPathLengthToStart(segment) : shortestFinalPathLength < shortestPathLengthToStart(segment);
6457
+ const pathsFromStartToEnd = countPathsFromStart(segment) * countPathsToEnd(segment);
6458
+ const cycled = cyclic.has(segment.id);
6459
+ for (const hook of reactHooks) {
6460
+ if (hasFlowSuppression(hook, "react-rule-hook")) continue;
6461
+ if (isUseIdentifier(hook) && isInsideTryCatch(hook)) context.report({
6462
+ node: hook,
6463
+ message: `React Hook "${getSourceCode().getText(hook)}" cannot be called in a try/catch block.`
6464
+ });
6465
+ if ((cycled || isInsideDoWhileLoop(hook)) && !isUseIdentifier(hook)) context.report({
6466
+ node: hook,
6467
+ message: `React Hook "${getSourceCode().getText(hook)}" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render.`
6468
+ });
6469
+ if (isDirectlyInsideComponentOrHook) {
6470
+ if (codePathNode.async) context.report({
6471
+ node: hook,
6472
+ message: `React Hook "${getSourceCode().getText(hook)}" cannot be called in an async function.`
6473
+ });
6474
+ if (!cycled && pathsFromStartToEnd !== allPathsFromStartToEnd && !isUseIdentifier(hook) && !isInsideDoWhileLoop(hook)) {
6475
+ const message = `React Hook "${getSourceCode().getText(hook)}" is called conditionally. React Hooks must be called in the exact same order in every component render.` + (possiblyHasEarlyReturn ? " Did you accidentally call a React Hook after an early return?" : "");
6476
+ context.report({
6477
+ node: hook,
6478
+ message
6479
+ });
6480
+ }
6481
+ } else if (codePathNode.parent != null && (codePathNode.parent.type === "MethodDefinition" || codePathNode.parent.type === "ClassProperty" || codePathNode.parent.type === "PropertyDefinition") && codePathNode.parent.value === codePathNode) {
6482
+ const message = `React Hook "${getSourceCode().getText(hook)}" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.`;
6483
+ context.report({
6484
+ node: hook,
6485
+ message
6486
+ });
6487
+ } else if (codePathFunctionName) {
6488
+ const message = `React Hook "${getSourceCode().getText(hook)}" is called in function "${getSourceCode().getText(codePathFunctionName)}" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".`;
6489
+ context.report({
6490
+ node: hook,
6491
+ message
6492
+ });
6493
+ } else if (codePathNode.type === "Program") {
6494
+ const message = `React Hook "${getSourceCode().getText(hook)}" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.`;
6495
+ context.report({
6496
+ node: hook,
6497
+ message
6498
+ });
6499
+ } else if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
6500
+ const message = `React Hook "${getSourceCode().getText(hook)}" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.`;
6501
+ context.report({
6502
+ node: hook,
6503
+ message
6504
+ });
6505
+ }
6506
+ }
6507
+ }
6508
+ }
6509
+ });
6510
+ return {
6511
+ "*"(node) {
6512
+ analyzer.enterNode(node);
6513
+ },
6514
+ "*:exit"(node) {
6515
+ analyzer.leaveNode(node);
6516
+ },
6517
+ CallExpression(node) {
6518
+ if (isHook(node.callee)) {
6519
+ const reactHooksMap = last(codePathReactHooksMapStack);
6520
+ const codePathSegment = last(codePathSegmentStack);
6521
+ let reactHooks = reactHooksMap.get(codePathSegment);
6522
+ if (!reactHooks) {
6523
+ reactHooks = [];
6524
+ reactHooksMap.set(codePathSegment, reactHooks);
6525
+ }
6526
+ reactHooks.push(node.callee);
6328
6527
  }
6329
- if (segment !== startSegment && segment.prevSegments.length > 0 && !segment.prevSegments.every(isVisited)) {
6330
- stack.pop();
6331
- continue;
6528
+ const nodeWithoutNamespace = getNodeWithoutReactNamespace(node.callee);
6529
+ if ((isEffectIdentifier(nodeWithoutNamespace, additionalEffectHooks) || isUseEffectEventIdentifier(nodeWithoutNamespace)) && node.arguments.length > 0) lastEffect = node;
6530
+ if (isUseEffectEventIdentifier(nodeWithoutNamespace) && node.parent?.type !== "VariableDeclarator" && node.parent?.type !== "ExpressionStatement") {
6531
+ const message = useEffectEventError(null, false);
6532
+ context.report({
6533
+ node,
6534
+ message
6535
+ });
6332
6536
  }
6333
- if (skippedSegment && segment.prevSegments.includes(skippedSegment)) skippedSegment = null;
6334
- visited[segment.id] = true;
6335
- if (!skippedSegment) {
6336
- resolvedCallback.call(this, segment, controller);
6337
- if (segment === lastSegment) controller.skip();
6338
- if (broken) break;
6537
+ },
6538
+ Identifier(node) {
6539
+ if (lastEffect == null && useEffectEventFunctions.has(node)) {
6540
+ const message = useEffectEventError(getSourceCode().getText(node), node.parent.type === "CallExpression");
6541
+ context.report({
6542
+ node,
6543
+ message
6544
+ });
6339
6545
  }
6546
+ },
6547
+ "CallExpression:exit"(node) {
6548
+ if (node === lastEffect) lastEffect = null;
6549
+ },
6550
+ FunctionDeclaration(node) {
6551
+ if (isInsideComponentOrHook(node)) recordAllUseEffectEventFunctions(getScope(node));
6552
+ },
6553
+ ArrowFunctionExpression(node) {
6554
+ if (isInsideComponentOrHook(node)) recordAllUseEffectEventFunctions(getScope(node));
6555
+ },
6556
+ ComponentDeclaration(node) {
6557
+ recordAllUseEffectEventFunctions(getScope(node));
6558
+ },
6559
+ HookDeclaration(node) {
6560
+ recordAllUseEffectEventFunctions(getScope(node));
6340
6561
  }
6341
- end = segment.nextSegments.length - 1;
6342
- if (index < end) {
6343
- item[1] += 1;
6344
- stack.push([segment.nextSegments[index], 0]);
6345
- } else if (index === end) {
6346
- item[0] = segment.nextSegments[index];
6347
- item[1] = 0;
6348
- } else stack.pop();
6349
- }
6350
- }
6351
- };
6352
- const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u;
6353
- /**
6354
- * Checks whether or not a given node is a `case` node (not `default` node).
6355
- * @param {ASTNode} node A `SwitchCase` node to check.
6356
- * @returns {boolean} `true` if the node is a `case` node (not `default` node).
6357
- */
6358
- function isCaseNode(node) {
6359
- return Boolean(node.test);
6360
- }
6361
- /**
6362
- * Checks if a given node appears as the value of a PropertyDefinition node.
6363
- * @param {ASTNode} node THe node to check.
6364
- * @returns {boolean} `true` if the node is a PropertyDefinition value,
6365
- * false if not.
6366
- */
6367
- function isPropertyDefinitionValue(node) {
6368
- const parent = node.parent;
6369
- return parent && parent.type === "PropertyDefinition" && parent.value === node;
6370
- }
6371
- /**
6372
- * Checks whether the given logical operator is taken into account for the code
6373
- * path analysis.
6374
- * @param {string} operator The operator found in the LogicalExpression node
6375
- * @returns {boolean} `true` if the operator is "&&" or "||" or "??"
6376
- */
6377
- function isHandledLogicalOperator(operator) {
6378
- return operator === "&&" || operator === "||" || operator === "??";
6379
- }
6380
- /**
6381
- * Checks whether the given assignment operator is a logical assignment operator.
6382
- * Logical assignments are taken into account for the code path analysis
6383
- * because of their short-circuiting semantics.
6384
- * @param {string} operator The operator found in the AssignmentExpression node
6385
- * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
6386
- */
6387
- function isLogicalAssignmentOperator(operator) {
6388
- return operator === "&&=" || operator === "||=" || operator === "??=";
6389
- }
6390
- /**
6391
- * Gets the label if the parent node of a given node is a LabeledStatement.
6392
- * @param {ASTNode} node A node to get.
6393
- * @returns {string|null} The label or `null`.
6394
- */
6395
- function getLabel(node) {
6396
- if (node.parent.type === "LabeledStatement") return node.parent.label.name;
6397
- return null;
6398
- }
6399
- /**
6400
- * Checks whether or not a given logical expression node goes different path
6401
- * between the `true` case and the `false` case.
6402
- * @param {ASTNode} node A node to check.
6403
- * @returns {boolean} `true` if the node is a test of a choice statement.
6404
- */
6405
- function isForkingByTrueOrFalse(node) {
6406
- const parent = node.parent;
6407
- switch (parent.type) {
6408
- case "ConditionalExpression":
6409
- case "IfStatement":
6410
- case "WhileStatement":
6411
- case "DoWhileStatement":
6412
- case "ForStatement": return parent.test === node;
6413
- case "LogicalExpression": return isHandledLogicalOperator(parent.operator);
6414
- case "AssignmentExpression": return isLogicalAssignmentOperator(parent.operator);
6415
- default: return false;
6416
- }
6417
- }
6418
- /**
6419
- * Gets the boolean value of a given literal node.
6420
- *
6421
- * This is used to detect infinity loops (e.g. `while (true) {}`).
6422
- * Statements preceded by an infinity loop are unreachable if the loop didn't
6423
- * have any `break` statement.
6424
- * @param {ASTNode} node A node to get.
6425
- * @returns {boolean|undefined} a boolean value if the node is a Literal node,
6426
- * otherwise `undefined`.
6427
- */
6428
- function getBooleanValueIfSimpleConstant(node) {
6429
- if (node.type === "Literal") return Boolean(node.value);
6430
- }
6431
- /**
6432
- * Checks that a given identifier node is a reference or not.
6433
- *
6434
- * This is used to detect the first throwable node in a `try` block.
6435
- * @param {ASTNode} node An Identifier node to check.
6436
- * @returns {boolean} `true` if the node is a reference.
6437
- */
6438
- function isIdentifierReference(node) {
6439
- const parent = node.parent;
6440
- switch (parent.type) {
6441
- case "LabeledStatement":
6442
- case "BreakStatement":
6443
- case "ContinueStatement":
6444
- case "ArrayPattern":
6445
- case "RestElement":
6446
- case "ImportSpecifier":
6447
- case "ImportDefaultSpecifier":
6448
- case "ImportNamespaceSpecifier":
6449
- case "CatchClause": return false;
6450
- case "FunctionDeclaration":
6451
- case "ComponentDeclaration":
6452
- case "HookDeclaration":
6453
- case "FunctionExpression":
6454
- case "ArrowFunctionExpression":
6455
- case "ClassDeclaration":
6456
- case "ClassExpression":
6457
- case "VariableDeclarator": return parent.id !== node;
6458
- case "Property":
6459
- case "PropertyDefinition":
6460
- case "MethodDefinition": return parent.key !== node || parent.computed || parent.shorthand;
6461
- case "AssignmentPattern": return parent.key !== node;
6462
- default: return true;
6463
- }
6464
- }
6465
- /**
6466
- * Updates the current segment with the head segment.
6467
- * This is similar to local branches and tracking branches of git.
6468
- *
6469
- * To separate the current and the head is in order to not make useless segments.
6470
- *
6471
- * In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
6472
- * events are fired.
6473
- * @param {CodePathAnalyzer} analyzer The instance.
6474
- * @param {ASTNode} node The current AST node.
6475
- * @returns {void}
6476
- */
6477
- function forwardCurrentToHead(analyzer, node) {
6478
- const codePath = analyzer.codePath;
6479
- const state = CodePath.getState(codePath);
6480
- const currentSegments = state.currentSegments;
6481
- const headSegments = state.headSegments;
6482
- const end = Math.max(currentSegments.length, headSegments.length);
6483
- let i, currentSegment, headSegment;
6484
- for (i = 0; i < end; ++i) {
6485
- currentSegment = currentSegments[i];
6486
- headSegment = headSegments[i];
6487
- if (currentSegment !== headSegment && currentSegment) {
6488
- if (currentSegment.reachable) analyzer.emitter.emit("onCodePathSegmentEnd", currentSegment, node);
6489
- }
6490
- }
6491
- state.currentSegments = headSegments;
6492
- for (i = 0; i < end; ++i) {
6493
- currentSegment = currentSegments[i];
6494
- headSegment = headSegments[i];
6495
- if (currentSegment !== headSegment && headSegment) {
6496
- CodePathSegment.markUsed(headSegment);
6497
- if (headSegment.reachable) analyzer.emitter.emit("onCodePathSegmentStart", headSegment, node);
6498
- }
6499
- }
6500
- }
6501
- /**
6502
- * Updates the current segment with empty.
6503
- * This is called at the last of functions or the program.
6504
- * @param {CodePathAnalyzer} analyzer The instance.
6505
- * @param {ASTNode} node The current AST node.
6506
- * @returns {void}
6507
- */
6508
- function leaveFromCurrentSegment(analyzer, node) {
6509
- const state = CodePath.getState(analyzer.codePath);
6510
- const currentSegments = state.currentSegments;
6511
- for (let i = 0; i < currentSegments.length; ++i) {
6512
- const currentSegment = currentSegments[i];
6513
- if (currentSegment.reachable) analyzer.emitter.emit("onCodePathSegmentEnd", currentSegment, node);
6514
- }
6515
- state.currentSegments = [];
6516
- }
6517
- /**
6518
- * Updates the code path due to the position of a given node in the parent node
6519
- * thereof.
6520
- *
6521
- * For example, if the node is `parent.consequent`, this creates a fork from the
6522
- * current path.
6523
- * @param {CodePathAnalyzer} analyzer The instance.
6524
- * @param {ASTNode} node The current AST node.
6525
- * @returns {void}
6526
- */
6527
- function preprocess(analyzer, node) {
6528
- const codePath = analyzer.codePath;
6529
- const state = CodePath.getState(codePath);
6530
- const parent = node.parent;
6531
- switch (parent.type) {
6532
- case "CallExpression":
6533
- if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) state.makeOptionalRight();
6534
- break;
6535
- case "MemberExpression":
6536
- if (parent.optional === true && parent.property === node) state.makeOptionalRight();
6537
- break;
6538
- case "LogicalExpression":
6539
- if (parent.right === node && isHandledLogicalOperator(parent.operator)) state.makeLogicalRight();
6540
- break;
6541
- case "AssignmentExpression":
6542
- if (parent.right === node && isLogicalAssignmentOperator(parent.operator)) state.makeLogicalRight();
6543
- break;
6544
- case "ConditionalExpression":
6545
- case "IfStatement":
6546
- if (parent.consequent === node) state.makeIfConsequent();
6547
- else if (parent.alternate === node) state.makeIfAlternate();
6548
- break;
6549
- case "SwitchCase":
6550
- if (parent.consequent[0] === node) state.makeSwitchCaseBody(false, !parent.test);
6551
- break;
6552
- case "TryStatement":
6553
- if (parent.handler === node) state.makeCatchBlock();
6554
- else if (parent.finalizer === node) state.makeFinallyBlock();
6555
- break;
6556
- case "WhileStatement":
6557
- if (parent.test === node) state.makeWhileTest(getBooleanValueIfSimpleConstant(node));
6558
- else {
6559
- assert(parent.body === node);
6560
- state.makeWhileBody();
6561
- }
6562
- break;
6563
- case "DoWhileStatement":
6564
- if (parent.body === node) state.makeDoWhileBody();
6565
- else {
6566
- assert(parent.test === node);
6567
- state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node));
6568
- }
6569
- break;
6570
- case "ForStatement":
6571
- if (parent.test === node) state.makeForTest(getBooleanValueIfSimpleConstant(node));
6572
- else if (parent.update === node) state.makeForUpdate();
6573
- else if (parent.body === node) state.makeForBody();
6574
- break;
6575
- case "ForInStatement":
6576
- case "ForOfStatement":
6577
- if (parent.left === node) state.makeForInOfLeft();
6578
- else if (parent.right === node) state.makeForInOfRight();
6579
- else {
6580
- assert(parent.body === node);
6581
- state.makeForInOfBody();
6582
- }
6583
- break;
6584
- case "AssignmentPattern":
6585
- if (parent.right === node) {
6586
- state.pushForkContext();
6587
- state.forkBypassPath();
6588
- state.forkPath();
6589
- }
6590
- break;
6591
- default: break;
6592
- }
6593
- }
6594
- /**
6595
- * Updates the code path due to the type of a given node in entering.
6596
- * @param {CodePathAnalyzer} analyzer The instance.
6597
- * @param {ASTNode} node The current AST node.
6598
- * @returns {void}
6599
- */
6600
- function processCodePathToEnter(analyzer, node) {
6601
- let codePath = analyzer.codePath;
6602
- let state = codePath && CodePath.getState(codePath);
6603
- const parent = node.parent;
6604
- /**
6605
- * Creates a new code path and trigger the onCodePathStart event
6606
- * based on the currently selected node.
6607
- * @param {string} origin The reason the code path was started.
6608
- * @returns {void}
6609
- */
6610
- function startCodePath(origin) {
6611
- if (codePath) forwardCurrentToHead(analyzer, node);
6612
- codePath = analyzer.codePath = new CodePath({
6613
- id: analyzer.idGenerator.next(),
6614
- origin,
6615
- upper: codePath,
6616
- onLooped: analyzer.onLooped
6617
- });
6618
- state = CodePath.getState(codePath);
6619
- analyzer.emitter.emit("onCodePathStart", codePath, node);
6620
- }
6621
- if (isPropertyDefinitionValue(node)) startCodePath("class-field-initializer");
6622
- switch (node.type) {
6623
- case "Program":
6624
- startCodePath("program");
6625
- break;
6626
- case "FunctionDeclaration":
6627
- case "ComponentDeclaration":
6628
- case "HookDeclaration":
6629
- case "FunctionExpression":
6630
- case "ArrowFunctionExpression":
6631
- startCodePath("function");
6632
- break;
6633
- case "StaticBlock":
6634
- startCodePath("class-static-block");
6635
- break;
6636
- case "ChainExpression":
6637
- state.pushChainContext();
6638
- break;
6639
- case "CallExpression":
6640
- if (node.optional === true) state.makeOptionalNode();
6641
- break;
6642
- case "MemberExpression":
6643
- if (node.optional === true) state.makeOptionalNode();
6644
- break;
6645
- case "LogicalExpression":
6646
- if (isHandledLogicalOperator(node.operator)) state.pushChoiceContext(node.operator, isForkingByTrueOrFalse(node));
6647
- break;
6648
- case "AssignmentExpression":
6649
- if (isLogicalAssignmentOperator(node.operator)) state.pushChoiceContext(node.operator.slice(0, -1), isForkingByTrueOrFalse(node));
6650
- break;
6651
- case "ConditionalExpression":
6652
- case "IfStatement":
6653
- state.pushChoiceContext("test", false);
6654
- break;
6655
- case "SwitchStatement":
6656
- state.pushSwitchContext(node.cases.some(isCaseNode), getLabel(node));
6657
- break;
6658
- case "TryStatement":
6659
- state.pushTryContext(Boolean(node.finalizer));
6660
- break;
6661
- case "SwitchCase":
6662
- if (parent.discriminant !== node && parent.cases[0] !== node) state.forkPath();
6663
- break;
6664
- case "WhileStatement":
6665
- case "DoWhileStatement":
6666
- case "ForStatement":
6667
- case "ForInStatement":
6668
- case "ForOfStatement":
6669
- state.pushLoopContext(node.type, getLabel(node));
6670
- break;
6671
- case "LabeledStatement":
6672
- if (!breakableTypePattern.test(node.body.type)) state.pushBreakContext(false, node.label.name);
6673
- break;
6674
- default: break;
6562
+ };
6675
6563
  }
6676
- forwardCurrentToHead(analyzer, node);
6677
- }
6564
+ };
6678
6565
  /**
6679
- * Updates the code path due to the type of a given node in leaving.
6680
- * @param {CodePathAnalyzer} analyzer The instance.
6681
- * @param {ASTNode} node The current AST node.
6682
- * @returns {void}
6566
+ * Gets the static name of a function AST node. For function declarations it is
6567
+ * easy. For anonymous function expressions it is much harder. If you search for
6568
+ * `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
6569
+ * where JS gives anonymous function expressions names. We roughly detect the
6570
+ * same AST nodes with some exceptions to better fit our use case.
6683
6571
  */
6684
- function processCodePathToExit(analyzer, node) {
6685
- const codePath = analyzer.codePath;
6686
- const state = CodePath.getState(codePath);
6687
- let dontForward = false;
6688
- switch (node.type) {
6689
- case "ChainExpression":
6690
- state.popChainContext();
6691
- break;
6692
- case "IfStatement":
6693
- case "ConditionalExpression":
6694
- state.popChoiceContext();
6695
- break;
6696
- case "LogicalExpression":
6697
- if (isHandledLogicalOperator(node.operator)) state.popChoiceContext();
6698
- break;
6699
- case "AssignmentExpression":
6700
- if (isLogicalAssignmentOperator(node.operator)) state.popChoiceContext();
6701
- break;
6702
- case "SwitchStatement":
6703
- state.popSwitchContext();
6704
- break;
6705
- case "SwitchCase":
6706
- if (node.consequent.length === 0) state.makeSwitchCaseBody(true, !node.test);
6707
- if (state.forkContext.reachable) dontForward = true;
6708
- break;
6709
- case "TryStatement":
6710
- state.popTryContext();
6711
- break;
6712
- case "BreakStatement":
6713
- forwardCurrentToHead(analyzer, node);
6714
- state.makeBreak(node.label && node.label.name);
6715
- dontForward = true;
6716
- break;
6717
- case "ContinueStatement":
6718
- forwardCurrentToHead(analyzer, node);
6719
- state.makeContinue(node.label && node.label.name);
6720
- dontForward = true;
6721
- break;
6722
- case "ReturnStatement":
6723
- forwardCurrentToHead(analyzer, node);
6724
- state.makeReturn();
6725
- dontForward = true;
6726
- break;
6727
- case "ThrowStatement":
6728
- forwardCurrentToHead(analyzer, node);
6729
- state.makeThrow();
6730
- dontForward = true;
6731
- break;
6732
- case "Identifier":
6733
- if (isIdentifierReference(node)) {
6734
- state.makeFirstThrowablePathInTryBlock();
6735
- dontForward = true;
6736
- }
6737
- break;
6738
- case "CallExpression":
6739
- case "ImportExpression":
6740
- case "MemberExpression":
6741
- case "NewExpression":
6742
- case "YieldExpression":
6743
- state.makeFirstThrowablePathInTryBlock();
6744
- break;
6745
- case "WhileStatement":
6746
- case "DoWhileStatement":
6747
- case "ForStatement":
6748
- case "ForInStatement":
6749
- case "ForOfStatement":
6750
- state.popLoopContext();
6751
- break;
6752
- case "AssignmentPattern":
6753
- state.popForkContext();
6754
- break;
6755
- case "LabeledStatement":
6756
- if (!breakableTypePattern.test(node.body.type)) state.popBreakContext();
6757
- break;
6758
- default: break;
6759
- }
6760
- if (!dontForward) forwardCurrentToHead(analyzer, node);
6572
+ function getFunctionName(node) {
6573
+ if (node.type === "ComponentDeclaration" || node.type === "HookDeclaration" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression" && node.id) return node.id;
6574
+ else if (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") if (node.parent?.type === "VariableDeclarator" && node.parent.init === node) return node.parent.id;
6575
+ else if (node.parent?.type === "AssignmentExpression" && node.parent.right === node && node.parent.operator === "=") return node.parent.left;
6576
+ else if (node.parent?.type === "Property" && node.parent.value === node && !node.parent.computed) return node.parent.key;
6577
+ else if (node.parent?.type === "AssignmentPattern" && node.parent.right === node && !node.parent.computed) return node.parent.left;
6578
+ else return;
6579
+ else return;
6761
6580
  }
6762
6581
  /**
6763
- * Updates the code path to finalize the current code path.
6764
- * @param {CodePathAnalyzer} analyzer The instance.
6765
- * @param {ASTNode} node The current AST node.
6766
- * @returns {void}
6582
+ * Convenience function for peeking the last item in a stack.
6767
6583
  */
6768
- function postprocess(analyzer, node) {
6769
- /**
6770
- * Ends the code path for the current node.
6771
- * @returns {void}
6772
- */
6773
- function endCodePath() {
6774
- let codePath = analyzer.codePath;
6775
- CodePath.getState(codePath).makeFinal();
6776
- leaveFromCurrentSegment(analyzer, node);
6777
- analyzer.emitter.emit("onCodePathEnd", codePath, node);
6778
- codePath = analyzer.codePath = analyzer.codePath.upper;
6779
- }
6780
- switch (node.type) {
6781
- case "Program":
6782
- case "FunctionDeclaration":
6783
- case "ComponentDeclaration":
6784
- case "HookDeclaration":
6785
- case "FunctionExpression":
6786
- case "ArrowFunctionExpression":
6787
- case "StaticBlock":
6788
- endCodePath();
6789
- break;
6790
- case "CallExpression":
6791
- if (node.optional === true && node.arguments.length === 0) CodePath.getState(analyzer.codePath).makeOptionalRight();
6792
- break;
6793
- default: break;
6794
- }
6795
- if (isPropertyDefinitionValue(node)) endCodePath();
6584
+ function last(array) {
6585
+ return array[array.length - 1];
6796
6586
  }
6797
- /**
6798
- * The class to analyze code paths.
6799
- * This class implements the EventGenerator interface.
6800
- */
6801
- var CodePathAnalyzer = class {
6802
- /**
6803
- * @param {EventGenerator} eventGenerator An event generator to wrap.
6804
- */
6805
- constructor(emitters) {
6806
- this.emitter = { emit(event, ...args) {
6807
- emitters[event]?.(...args);
6808
- } };
6809
- this.codePath = null;
6810
- this.idGenerator = new IdGenerator("s");
6811
- this.currentNode = null;
6812
- this.onLooped = this.onLooped.bind(this);
6813
- }
6814
- /**
6815
- * Does the process to enter a given AST node.
6816
- * This updates state of analysis and calls `enterNode` of the wrapped.
6817
- * @param {ASTNode} node A node which is entering.
6818
- * @returns {void}
6819
- */
6820
- enterNode(node) {
6821
- this.currentNode = node;
6822
- if (node.parent) preprocess(this, node);
6823
- processCodePathToEnter(this, node);
6824
- this.currentNode = null;
6825
- }
6826
- /**
6827
- * Does the process to leave a given AST node.
6828
- * This updates state of analysis and calls `leaveNode` of the wrapped.
6829
- * @param {ASTNode} node A node which is leaving.
6830
- * @returns {void}
6831
- */
6832
- leaveNode(node) {
6833
- this.currentNode = node;
6834
- processCodePathToExit(this, node);
6835
- postprocess(this, node);
6836
- this.currentNode = null;
6837
- }
6838
- /**
6839
- * This is called on a code path looped.
6840
- * Then this raises a looped event.
6841
- * @param {CodePathSegment} fromSegment A segment of prev.
6842
- * @param {CodePathSegment} toSegment A segment of next.
6843
- * @returns {void}
6844
- */
6845
- onLooped(fromSegment, toSegment) {
6846
- if (fromSegment.reachable && toSegment.reachable) this.emitter.emit("onCodePathSegmentLoop", fromSegment, toSegment, this.currentNode);
6847
- }
6848
- };
6849
6587
 
6850
6588
  //#endregion
6851
- //#region src/rules/set-state-in-effect.ts
6589
+ //#region src/rules/set-state-in-effect/set-state-in-effect.ts
6852
6590
  const RULE_NAME$4 = "set-state-in-effect";
6853
6591
  var set_state_in_effect_default = createRule({
6854
6592
  meta: {
@@ -7066,7 +6804,7 @@ function create$4(context) {
7066
6804
  }
7067
6805
 
7068
6806
  //#endregion
7069
- //#region src/rules/set-state-in-render.ts
6807
+ //#region src/rules/set-state-in-render/set-state-in-render.ts
7070
6808
  const RULE_NAME$3 = "set-state-in-render";
7071
6809
  var set_state_in_render_default = createRule({
7072
6810
  meta: {
@@ -7203,7 +6941,7 @@ function create$3(context) {
7203
6941
  }
7204
6942
 
7205
6943
  //#endregion
7206
- //#region src/rules/unsupported-syntax.ts
6944
+ //#region src/rules/unsupported-syntax/unsupported-syntax.ts
7207
6945
  const RULE_NAME$2 = "unsupported-syntax";
7208
6946
  var unsupported_syntax_default = createRule({
7209
6947
  meta: {
@@ -7284,7 +7022,7 @@ function create$2(context) {
7284
7022
  }
7285
7023
 
7286
7024
  //#endregion
7287
- //#region src/rules/use-memo.ts
7025
+ //#region src/rules/use-memo/use-memo.ts
7288
7026
  const RULE_NAME$1 = "use-memo";
7289
7027
  var use_memo_default = createRule({
7290
7028
  meta: {
@@ -7333,7 +7071,7 @@ function create$1(context) {
7333
7071
  }
7334
7072
 
7335
7073
  //#endregion
7336
- //#region src/rules/use-state.ts
7074
+ //#region src/rules/use-state/use-state.ts
7337
7075
  const RULE_NAME = "use-state";
7338
7076
  const defaultOptions = [{
7339
7077
  enforceAssignment: true,