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.
- package/dist/index.js +1329 -1591
- 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.
|
|
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.
|
|
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
|
-
*
|
|
4453
|
-
* character to exclude identifiers like "user".
|
|
4455
|
+
* Checks whether or not a given segment is reachable.
|
|
4454
4456
|
*/
|
|
4455
|
-
function
|
|
4456
|
-
return
|
|
4457
|
+
function isReachable$1(segment) {
|
|
4458
|
+
return segment.reachable;
|
|
4457
4459
|
}
|
|
4458
4460
|
/**
|
|
4459
|
-
*
|
|
4460
|
-
* containing a hook name.
|
|
4461
|
+
* A code path segment.
|
|
4461
4462
|
*/
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
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
|
-
*
|
|
4471
|
-
* always start with an uppercase letter.
|
|
4578
|
+
* Gets whether or not a given segment is reachable.
|
|
4472
4579
|
*/
|
|
4473
|
-
function
|
|
4474
|
-
return
|
|
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
|
-
*
|
|
4481
|
-
*
|
|
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
|
|
4484
|
-
|
|
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
|
-
*
|
|
4488
|
-
*
|
|
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
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
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
|
|
4615
|
+
return currentSegments;
|
|
4503
4616
|
}
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
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
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
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 =
|
|
5371
|
-
|
|
5372
|
-
const
|
|
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,
|
|
5429
|
-
*
|
|
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
|
-
*
|
|
5443
|
-
*
|
|
5444
|
-
*
|
|
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
|
|
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
|
|
5587
|
-
*
|
|
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
|
|
5621
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
6096
|
-
|
|
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
|
-
*
|
|
6105
|
-
*
|
|
6106
|
-
*
|
|
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
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
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
|
-
*
|
|
5643
|
+
* Traverses all segments in this code path.
|
|
6123
5644
|
*
|
|
6124
|
-
*
|
|
6125
|
-
*
|
|
6126
|
-
*
|
|
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
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
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
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
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
|
-
*
|
|
6151
|
-
*
|
|
5940
|
+
* Creates a new code path and trigger the onCodePathStart event
|
|
5941
|
+
* based on the currently selected node.
|
|
6152
5942
|
*/
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
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
|
-
*
|
|
6093
|
+
* Updates the code path to finalize the current code path.
|
|
6160
6094
|
*/
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
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
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
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
|
-
*
|
|
6121
|
+
* The class to analyze code paths.
|
|
6122
|
+
* This class implements the EventGenerator interface.
|
|
6182
6123
|
*/
|
|
6183
|
-
var
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
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
|
-
*
|
|
6220
|
-
*
|
|
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
|
-
|
|
6224
|
-
|
|
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
|
-
*
|
|
6228
|
-
*
|
|
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
|
-
|
|
6231
|
-
|
|
6153
|
+
leaveNode(node) {
|
|
6154
|
+
this.currentNode = node;
|
|
6155
|
+
processCodePathToExit(this, node);
|
|
6156
|
+
postprocess(this, node);
|
|
6157
|
+
this.currentNode = null;
|
|
6232
6158
|
}
|
|
6233
6159
|
/**
|
|
6234
|
-
*
|
|
6235
|
-
*
|
|
6236
|
-
* @type {CodePathSegment[]}
|
|
6160
|
+
* This is called on a code path looped.
|
|
6161
|
+
* Then this raises a looped event.
|
|
6237
6162
|
*/
|
|
6238
|
-
|
|
6239
|
-
|
|
6163
|
+
_onLooped(fromSegment, toSegment) {
|
|
6164
|
+
if (fromSegment.reachable && toSegment.reachable) this.emitter.emit("onCodePathSegmentLoop", fromSegment, toSegment, this.currentNode);
|
|
6240
6165
|
}
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
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
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
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
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
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
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
}
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
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
|
-
*
|
|
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
|
|
6318
|
-
return
|
|
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
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
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
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
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
|
-
|
|
6334
|
-
|
|
6335
|
-
if (
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6677
|
-
}
|
|
6564
|
+
};
|
|
6678
6565
|
/**
|
|
6679
|
-
*
|
|
6680
|
-
*
|
|
6681
|
-
*
|
|
6682
|
-
*
|
|
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
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
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
|
-
*
|
|
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
|
|
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,
|