;
+}
diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock
index a471aadd96..93756d4901 100644
--- a/fixtures/eslint-v9/yarn.lock
+++ b/fixtures/eslint-v9/yarn.lock
@@ -2,212 +2,209 @@
# yarn lockfile v1
-"@ampproject/remapping@^2.2.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
- integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
+"@babel/code-frame@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be"
+ integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
dependencies:
- "@jridgewell/gen-mapping" "^0.3.5"
- "@jridgewell/trace-mapping" "^0.3.24"
-
-"@babel/code-frame@^7.26.2":
- version "7.26.2"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
- integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
- dependencies:
- "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.27.1"
js-tokens "^4.0.0"
- picocolors "^1.0.0"
+ picocolors "^1.1.1"
-"@babel/compat-data@^7.26.5":
- version "7.26.8"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367"
- integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
+"@babel/compat-data@^7.27.2":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.4.tgz#96fdf1af1b8859c8474ab39c295312bfb7c24b04"
+ integrity sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==
"@babel/core@^7.24.4":
- version "7.26.10"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9"
- integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496"
+ integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==
dependencies:
- "@ampproject/remapping" "^2.2.0"
- "@babel/code-frame" "^7.26.2"
- "@babel/generator" "^7.26.10"
- "@babel/helper-compilation-targets" "^7.26.5"
- "@babel/helper-module-transforms" "^7.26.0"
- "@babel/helpers" "^7.26.10"
- "@babel/parser" "^7.26.10"
- "@babel/template" "^7.26.9"
- "@babel/traverse" "^7.26.10"
- "@babel/types" "^7.26.10"
+ "@babel/code-frame" "^7.27.1"
+ "@babel/generator" "^7.28.3"
+ "@babel/helper-compilation-targets" "^7.27.2"
+ "@babel/helper-module-transforms" "^7.28.3"
+ "@babel/helpers" "^7.28.4"
+ "@babel/parser" "^7.28.4"
+ "@babel/template" "^7.27.2"
+ "@babel/traverse" "^7.28.4"
+ "@babel/types" "^7.28.4"
+ "@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
-"@babel/generator@^7.26.10":
- version "7.26.10"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7"
- integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==
+"@babel/generator@^7.28.3":
+ version "7.28.3"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.3.tgz#9626c1741c650cbac39121694a0f2d7451b8ef3e"
+ integrity sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==
dependencies:
- "@babel/parser" "^7.26.10"
- "@babel/types" "^7.26.10"
- "@jridgewell/gen-mapping" "^0.3.5"
- "@jridgewell/trace-mapping" "^0.3.25"
+ "@babel/parser" "^7.28.3"
+ "@babel/types" "^7.28.2"
+ "@jridgewell/gen-mapping" "^0.3.12"
+ "@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
-"@babel/helper-annotate-as-pure@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
- integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
+"@babel/helper-annotate-as-pure@^7.27.3":
+ version "7.27.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5"
+ integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==
dependencies:
- "@babel/types" "^7.25.9"
+ "@babel/types" "^7.27.3"
-"@babel/helper-compilation-targets@^7.26.5":
- version "7.26.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8"
- integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==
+"@babel/helper-compilation-targets@^7.27.2":
+ version "7.27.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d"
+ integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==
dependencies:
- "@babel/compat-data" "^7.26.5"
- "@babel/helper-validator-option" "^7.25.9"
+ "@babel/compat-data" "^7.27.2"
+ "@babel/helper-validator-option" "^7.27.1"
browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
-"@babel/helper-create-class-features-plugin@^7.25.9":
- version "7.26.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
- integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
+"@babel/helper-create-class-features-plugin@^7.18.6":
+ version "7.28.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz#3e747434ea007910c320c4d39a6b46f20f371d46"
+ integrity sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==
dependencies:
- "@babel/helper-annotate-as-pure" "^7.25.9"
- "@babel/helper-member-expression-to-functions" "^7.25.9"
- "@babel/helper-optimise-call-expression" "^7.25.9"
- "@babel/helper-replace-supers" "^7.26.5"
- "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
- "@babel/traverse" "^7.26.9"
+ "@babel/helper-annotate-as-pure" "^7.27.3"
+ "@babel/helper-member-expression-to-functions" "^7.27.1"
+ "@babel/helper-optimise-call-expression" "^7.27.1"
+ "@babel/helper-replace-supers" "^7.27.1"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1"
+ "@babel/traverse" "^7.28.3"
semver "^6.3.1"
-"@babel/helper-member-expression-to-functions@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3"
- integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==
+"@babel/helper-globals@^7.28.0":
+ version "7.28.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674"
+ integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
+
+"@babel/helper-member-expression-to-functions@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44"
+ integrity sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==
dependencies:
- "@babel/traverse" "^7.25.9"
- "@babel/types" "^7.25.9"
+ "@babel/traverse" "^7.27.1"
+ "@babel/types" "^7.27.1"
-"@babel/helper-module-imports@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
- integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
+"@babel/helper-module-imports@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204"
+ integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==
dependencies:
- "@babel/traverse" "^7.25.9"
- "@babel/types" "^7.25.9"
+ "@babel/traverse" "^7.27.1"
+ "@babel/types" "^7.27.1"
-"@babel/helper-module-transforms@^7.26.0":
- version "7.26.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
- integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
+"@babel/helper-module-transforms@^7.28.3":
+ version "7.28.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6"
+ integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==
dependencies:
- "@babel/helper-module-imports" "^7.25.9"
- "@babel/helper-validator-identifier" "^7.25.9"
- "@babel/traverse" "^7.25.9"
+ "@babel/helper-module-imports" "^7.27.1"
+ "@babel/helper-validator-identifier" "^7.27.1"
+ "@babel/traverse" "^7.28.3"
-"@babel/helper-optimise-call-expression@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e"
- integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==
+"@babel/helper-optimise-call-expression@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200"
+ integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==
dependencies:
- "@babel/types" "^7.25.9"
+ "@babel/types" "^7.27.1"
-"@babel/helper-plugin-utils@^7.25.9":
- version "7.26.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
- integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
+"@babel/helper-plugin-utils@^7.18.6":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c"
+ integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==
-"@babel/helper-replace-supers@^7.26.5":
- version "7.26.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
- integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
+"@babel/helper-replace-supers@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0"
+ integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==
dependencies:
- "@babel/helper-member-expression-to-functions" "^7.25.9"
- "@babel/helper-optimise-call-expression" "^7.25.9"
- "@babel/traverse" "^7.26.5"
+ "@babel/helper-member-expression-to-functions" "^7.27.1"
+ "@babel/helper-optimise-call-expression" "^7.27.1"
+ "@babel/traverse" "^7.27.1"
-"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9"
- integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==
+"@babel/helper-skip-transparent-expression-wrappers@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56"
+ integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==
dependencies:
- "@babel/traverse" "^7.25.9"
- "@babel/types" "^7.25.9"
+ "@babel/traverse" "^7.27.1"
+ "@babel/types" "^7.27.1"
-"@babel/helper-string-parser@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
- integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
+"@babel/helper-string-parser@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
+ integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
-"@babel/helper-validator-identifier@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
- integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+"@babel/helper-validator-identifier@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
+ integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
-"@babel/helper-validator-option@^7.25.9":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
- integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
+"@babel/helper-validator-option@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
+ integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
-"@babel/helpers@^7.26.10":
- version "7.26.10"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384"
- integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
+"@babel/helpers@^7.28.4":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827"
+ integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==
dependencies:
- "@babel/template" "^7.26.9"
- "@babel/types" "^7.26.10"
+ "@babel/template" "^7.27.2"
+ "@babel/types" "^7.28.4"
-"@babel/parser@^7.24.4", "@babel/parser@^7.26.10", "@babel/parser@^7.26.9":
- version "7.26.10"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
- integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
+"@babel/parser@^7.24.4", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3", "@babel/parser@^7.28.4":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8"
+ integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==
dependencies:
- "@babel/types" "^7.26.10"
+ "@babel/types" "^7.28.4"
-"@babel/plugin-transform-private-methods@^7.24.4":
- version "7.25.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57"
- integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==
+"@babel/plugin-proposal-private-methods@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea"
+ integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.25.9"
- "@babel/helper-plugin-utils" "^7.25.9"
+ "@babel/helper-create-class-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
-"@babel/template@^7.26.9":
- version "7.26.9"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
- integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
+"@babel/template@^7.27.2":
+ version "7.27.2"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d"
+ integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==
dependencies:
- "@babel/code-frame" "^7.26.2"
- "@babel/parser" "^7.26.9"
- "@babel/types" "^7.26.9"
+ "@babel/code-frame" "^7.27.1"
+ "@babel/parser" "^7.27.2"
+ "@babel/types" "^7.27.1"
-"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9":
- version "7.26.10"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380"
- integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==
+"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b"
+ integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==
dependencies:
- "@babel/code-frame" "^7.26.2"
- "@babel/generator" "^7.26.10"
- "@babel/parser" "^7.26.10"
- "@babel/template" "^7.26.9"
- "@babel/types" "^7.26.10"
+ "@babel/code-frame" "^7.27.1"
+ "@babel/generator" "^7.28.3"
+ "@babel/helper-globals" "^7.28.0"
+ "@babel/parser" "^7.28.4"
+ "@babel/template" "^7.27.2"
+ "@babel/types" "^7.28.4"
debug "^4.3.1"
- globals "^11.1.0"
-"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9":
- version "7.26.10"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
- integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
+"@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4":
+ version "7.28.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a"
+ integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==
dependencies:
- "@babel/helper-string-parser" "^7.25.9"
- "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/helper-string-parser" "^7.27.1"
+ "@babel/helper-validator-identifier" "^7.27.1"
"@eslint-community/eslint-utils@^4.2.0":
version "4.4.1"
@@ -303,13 +300,20 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161"
integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==
-"@jridgewell/gen-mapping@^0.3.5":
- version "0.3.8"
- resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142"
- integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==
+"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.13"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
+ integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
dependencies:
- "@jridgewell/set-array" "^1.2.1"
- "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/remapping@^2.3.5":
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
+ integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
@@ -317,20 +321,15 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
-"@jridgewell/set-array@^1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
- integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
+ integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
-"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
- integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
-
-"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
- version "0.3.25"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
- integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
+ version "0.3.31"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
+ integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
@@ -387,6 +386,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+baseline-browser-mapping@^2.8.9:
+ version "2.8.10"
+ resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz#32eb5e253d633fa3fa3ffb1685fabf41680d9e8a"
+ integrity sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -396,24 +400,25 @@ brace-expansion@^1.1.7:
concat-map "0.0.1"
browserslist@^4.24.0:
- version "4.24.4"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
- integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
+ version "4.26.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.26.3.tgz#40fbfe2d1cd420281ce5b1caa8840049c79afb56"
+ integrity sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==
dependencies:
- caniuse-lite "^1.0.30001688"
- electron-to-chromium "^1.5.73"
- node-releases "^2.0.19"
- update-browserslist-db "^1.1.1"
+ baseline-browser-mapping "^2.8.9"
+ caniuse-lite "^1.0.30001746"
+ electron-to-chromium "^1.5.227"
+ node-releases "^2.0.21"
+ update-browserslist-db "^1.1.3"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-caniuse-lite@^1.0.30001688:
- version "1.0.30001703"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz#977cb4920598c158f491ecf4f4f2cfed9e354718"
- integrity sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==
+caniuse-lite@^1.0.30001746:
+ version "1.0.30001746"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz#199d20f04f5369825e00ff7067d45d5dfa03aee7"
+ integrity sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==
chalk@^4.0.0:
version "4.1.2"
@@ -454,7 +459,14 @@ cross-spawn@^7.0.6:
shebang-command "^2.0.0"
which "^2.0.1"
-debug@^4.1.0, debug@^4.3.1, debug@^4.3.2:
+debug@^4.1.0:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
+debug@^4.3.1, debug@^4.3.2:
version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
@@ -466,10 +478,10 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
-electron-to-chromium@^1.5.73:
- version "1.5.114"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz#f2bb4fda80a7db4ea273565e75b0ebbe19af0ac3"
- integrity sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA==
+electron-to-chromium@^1.5.227:
+ version "1.5.228"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz#38b849bc8714bd21fb64f5ad56bf8cfd8638e1e9"
+ integrity sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==
escalade@^3.2.0:
version "3.2.0"
@@ -646,11 +658,6 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
-globals@^11.1.0:
- version "11.12.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
- integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
@@ -801,10 +808,10 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
-node-releases@^2.0.19:
- version "2.0.19"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314"
- integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
+node-releases@^2.0.21:
+ version "2.0.21"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c"
+ integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==
optionator@^0.9.3:
version "0.9.4"
@@ -849,7 +856,7 @@ path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-picocolors@^1.0.0, picocolors@^1.1.1:
+picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
@@ -905,7 +912,7 @@ type-check@^0.4.0, type-check@~0.4.0:
dependencies:
prelude-ls "^1.2.1"
-update-browserslist-db@^1.1.1:
+update-browserslist-db@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
@@ -943,11 +950,11 @@ yocto-queue@^0.1.0:
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod-validation-error@^3.0.3:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6"
- integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.5.3.tgz#85ba33290200d8db9f043621e284f40dddefb7e5"
+ integrity sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==
zod@^3.22.4:
- version "3.24.2"
- resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3"
- integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==
+ version "3.25.76"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34"
+ integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md
index 20d32fe9fd..2e965464c1 100644
--- a/packages/eslint-plugin-react-hooks/README.md
+++ b/packages/eslint-plugin-react-hooks/README.md
@@ -1,8 +1,6 @@
# `eslint-plugin-react-hooks`
-This ESLint plugin enforces the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks).
-
-It is a part of the [Hooks API](https://react.dev/reference/react/hooks) for React.
+The official ESLint plugin for [React](https://react.dev) which enforces the [Rules of React](https://react.dev/reference/eslint-plugin-react-hooks) and other best practices.
## Installation
diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts
index c01e22fb4b..81c21a22e0 100644
--- a/packages/eslint-plugin-react-hooks/src/index.ts
+++ b/packages/eslint-plugin-react-hooks/src/index.ts
@@ -7,7 +7,11 @@
import type {Linter, Rule} from 'eslint';
import ExhaustiveDeps from './rules/ExhaustiveDeps';
-import {allRules} from './shared/ReactCompiler';
+import {
+ allRules,
+ mapErrorSeverityToESlint,
+ recommendedRules,
+} from './shared/ReactCompiler';
import RulesOfHooks from './rules/RulesOfHooks';
// All rules
@@ -19,24 +23,56 @@ const rules = {
),
} satisfies Record;
-// Config rules
-const ruleConfigs = {
+// Basic hooks rules (for recommended config)
+const basicRuleConfigs = {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
-} satisfies Linter.RulesRecord;
+} as const satisfies Linter.RulesRecord;
+
+const compilerRuleConfigs = Object.fromEntries(
+ Object.entries(recommendedRules).map(([name, ruleConfig]) => {
+ return [
+ `react-hooks/${name}` as const,
+ mapErrorSeverityToESlint(ruleConfig.severity),
+ ] as const;
+ }),
+) as Record<`react-hooks/${string}`, Linter.RuleEntry>;
+
+// All rules including compiler rules (for recommended-latest config)
+const allRuleConfigs: Linter.RulesRecord = {
+ ...basicRuleConfigs,
+ ...compilerRuleConfigs,
+};
const plugin = {
meta: {
name: 'eslint-plugin-react-hooks',
},
- configs: {},
rules,
+ configs: {} as {
+ 'recommended-legacy': {
+ plugins: Array;
+ rules: Linter.RulesRecord;
+ };
+ 'recommended-latest-legacy': {
+ plugins: Array;
+ rules: Linter.RulesRecord;
+ };
+ 'flat/recommended': Array;
+ 'recommended-latest': Array;
+ recommended: Array;
+ },
};
Object.assign(plugin.configs, {
'recommended-legacy': {
plugins: ['react-hooks'],
- rules: ruleConfigs,
+ rules: basicRuleConfigs,
+ },
+
+ 'recommended-latest-legacy': {
+ plugins: ['react-hooks'],
+ rules: allRuleConfigs,
},
'flat/recommended': [
@@ -44,7 +80,7 @@ Object.assign(plugin.configs, {
plugins: {
'react-hooks': plugin,
},
- rules: ruleConfigs,
+ rules: basicRuleConfigs,
},
],
@@ -53,14 +89,18 @@ Object.assign(plugin.configs, {
plugins: {
'react-hooks': plugin,
},
- rules: ruleConfigs,
+ rules: allRuleConfigs,
},
],
- recommended: {
- plugins: ['react-hooks'],
- rules: ruleConfigs,
- },
+ recommended: [
+ {
+ plugins: {
+ 'react-hooks': plugin,
+ },
+ rules: basicRuleConfigs,
+ },
+ ],
});
export default plugin;
diff --git a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
index dec46457cc..cdb3af3848 100644
--- a/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
+++ b/packages/eslint-plugin-react-hooks/src/shared/ReactCompiler.ts
@@ -160,38 +160,6 @@ function makeRule(rule: LintRule): Rule.RuleModule {
};
}
-export const NoUnusedDirectivesRule: Rule.RuleModule = {
- meta: {
- type: 'suggestion',
- docs: {
- recommended: true,
- },
- fixable: 'code',
- hasSuggestions: true,
- // validation is done at runtime with zod
- schema: [{type: 'object', additionalProperties: true}],
- },
- create(context: Rule.RuleContext): Rule.RuleListener {
- const results = getReactCompilerResult(context);
-
- for (const directive of results.unusedOptOutDirectives) {
- context.report({
- message: `Unused '${directive.directive}' directive`,
- loc: directive.loc,
- suggest: [
- {
- desc: 'Remove the directive',
- fix(fixer): Rule.Fix {
- return fixer.removeRange(directive.range);
- },
- },
- ],
- });
- }
- return {};
- },
-};
-
type RulesConfig = {
[name: string]: {rule: Rule.RuleModule; severity: ErrorSeverity};
};
@@ -201,12 +169,7 @@ export const allRules: RulesConfig = LintRules.reduce(
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
return acc;
},
- {
- 'no-unused-directives': {
- rule: NoUnusedDirectivesRule,
- severity: ErrorSeverity.Error,
- },
- } as RulesConfig,
+ {} as RulesConfig,
);
export const recommendedRules: RulesConfig = LintRules.filter(
@@ -216,12 +179,7 @@ export const recommendedRules: RulesConfig = LintRules.filter(
acc[rule.name] = {rule: makeRule(rule), severity: rule.severity};
return acc;
},
- {
- 'no-unused-directives': {
- rule: NoUnusedDirectivesRule,
- severity: ErrorSeverity.Error,
- },
- } as RulesConfig,
+ {} as RulesConfig,
);
export function mapErrorSeverityToESlint(
diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts
index f46528cd60..fc9067d274 100644
--- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts
+++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts
@@ -6,21 +6,19 @@
*/
/* eslint-disable no-for-of-loops/no-for-of-loops */
-import {transformFromAstSync, traverse} from '@babel/core';
+import {transformFromAstSync} from '@babel/core';
import {parse as babelParse} from '@babel/parser';
-import {Directive, File} from '@babel/types';
+import {File} from '@babel/types';
// @ts-expect-error: no types available
import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods';
import BabelPluginReactCompiler, {
parsePluginOptions,
validateEnvironmentConfig,
- OPT_OUT_DIRECTIVES,
type PluginOptions,
Logger,
LoggerEvent,
} from 'babel-plugin-react-compiler';
import type {SourceCode} from 'eslint';
-import {SourceLocation} from 'estree';
import * as HermesParser from 'hermes-parser';
import {isDeepStrictEqual} from 'util';
import type {ParseResult} from '@babel/parser';
@@ -46,17 +44,11 @@ const COMPILER_OPTIONS: PluginOptions = {
},
};
-export type UnusedOptOutDirective = {
- loc: SourceLocation;
- range: [number, number];
- directive: string;
-};
export type RunCacheEntry = {
sourceCode: string;
filename: string;
userOpts: PluginOptions;
flowSuppressions: Array<{line: number; code: string}>;
- unusedOptOutDirectives: Array;
events: Array;
};
@@ -88,24 +80,6 @@ function getFlowSuppressions(
return results;
}
-function filterUnusedOptOutDirectives(
- directives: ReadonlyArray,
-): Array {
- const results: Array = [];
- for (const directive of directives) {
- if (
- OPT_OUT_DIRECTIVES.has(directive.value.value) &&
- directive.loc != null
- ) {
- results.push({
- loc: directive.loc,
- directive: directive.value.value,
- range: [directive.start!, directive.end!],
- });
- }
- }
- return results;
-}
function runReactCompilerImpl({
sourceCode,
@@ -126,7 +100,6 @@ function runReactCompilerImpl({
filename,
userOpts,
flowSuppressions: [],
- unusedOptOutDirectives: [],
events: [],
};
const userLogger: Logger | null = options.logger;
@@ -182,28 +155,6 @@ function runReactCompilerImpl({
configFile: false,
babelrc: false,
});
-
- if (results.events.filter(e => e.kind === 'CompileError').length === 0) {
- traverse(babelAST, {
- FunctionDeclaration(path) {
- results.unusedOptOutDirectives.push(
- ...filterUnusedOptOutDirectives(path.node.body.directives),
- );
- },
- ArrowFunctionExpression(path) {
- if (path.node.body.type === 'BlockStatement') {
- results.unusedOptOutDirectives.push(
- ...filterUnusedOptOutDirectives(path.node.body.directives),
- );
- }
- },
- FunctionExpression(path) {
- results.unusedOptOutDirectives.push(
- ...filterUnusedOptOutDirectives(path.node.body.directives),
- );
- },
- });
- }
} catch (err) {
/* errors handled by injected logger */
}
diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
index 2c20bbcd1c..be2e0377d4 100644
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
@@ -850,6 +850,7 @@ describe('Timeline profiler', () => {
`);
});
+ // @reactVersion >= 19.1
// @reactVersion < 19.2
it('should process a sample createRoot render sequence', async () => {
function App() {
@@ -2158,6 +2159,7 @@ describe('Timeline profiler', () => {
`);
});
+ // @reactVersion >= 19.1
// @reactVersion < 19.2
it('should process a sample createRoot render sequence', async () => {
function App() {
diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js
index 9de9b564e9..d5280c091c 100644
--- a/packages/react-devtools-shared/src/__tests__/store-test.js
+++ b/packages/react-devtools-shared/src/__tests__/store-test.js
@@ -974,12 +974,8 @@ describe('Store', () => {
`);
- const rendererID = getRendererID();
- const rootID = store.getRootIDForElement(store.getElementIDAtIndex(0));
await actAsync(() => {
agent.overrideSuspenseMilestone({
- rendererID,
- rootID,
suspendedSet: [
store.getElementIDAtIndex(4),
store.getElementIDAtIndex(8),
@@ -1009,8 +1005,6 @@ describe('Store', () => {
await actAsync(() => {
agent.overrideSuspenseMilestone({
- rendererID,
- rootID,
suspendedSet: [],
});
});
diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js
index 5a7d6eb836..fce8fe626d 100644
--- a/packages/react-devtools-shared/src/backend/agent.js
+++ b/packages/react-devtools-shared/src/backend/agent.js
@@ -8,7 +8,11 @@
*/
import EventEmitter from '../events';
-import {SESSION_STORAGE_LAST_SELECTION_KEY, __DEBUG__} from '../constants';
+import {
+ SESSION_STORAGE_LAST_SELECTION_KEY,
+ UNKNOWN_SUSPENDERS_NONE,
+ __DEBUG__,
+} from '../constants';
import setupHighlighter from './views/Highlighter';
import {
initialize as setupTraceUpdates,
@@ -26,8 +30,13 @@ import type {
RendererID,
RendererInterface,
DevToolsHookSettings,
+ InspectedElement,
} from './types';
-import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
+import type {
+ ComponentFilter,
+ DehydratedData,
+ ElementType,
+} from 'react-devtools-shared/src/frontend/types';
import type {GroupItem} from './views/TraceUpdates/canvas';
import {gte, isReactNativeEnvironment} from './utils';
import {
@@ -73,6 +82,13 @@ type InspectElementParams = {
requestID: number,
};
+type InspectScreenParams = {
+ forceFullData: boolean,
+ id: number,
+ path: Array | null,
+ requestID: number,
+};
+
type OverrideHookParams = {
id: number,
hookID: number,
@@ -131,8 +147,6 @@ type OverrideSuspenseParams = {
};
type OverrideSuspenseMilestoneParams = {
- rendererID: number,
- rootID: number,
suspendedSet: Array,
};
@@ -141,6 +155,111 @@ type PersistedSelection = {
path: Array,
};
+function createEmptyInspectedScreen(
+ arbitraryRootID: number,
+ type: ElementType,
+): InspectedElement {
+ const suspendedBy: DehydratedData = {
+ cleaned: [],
+ data: [],
+ unserializable: [],
+ };
+ return {
+ // invariants
+ id: arbitraryRootID,
+ type: type,
+ // Properties we merge
+ isErrored: false,
+ errors: [],
+ warnings: [],
+ suspendedBy,
+ suspendedByRange: null,
+ // TODO: How to merge these?
+ unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
+ // Properties where merging doesn't make sense so we ignore them entirely in the UI
+ rootType: null,
+ plugins: {stylex: null},
+ nativeTag: null,
+ env: null,
+ source: null,
+ stack: null,
+ rendererPackageName: null,
+ rendererVersion: null,
+ // These don't make sense for a Root. They're just bottom values.
+ key: null,
+ canEditFunctionProps: false,
+ canEditHooks: false,
+ canEditFunctionPropsDeletePaths: false,
+ canEditFunctionPropsRenamePaths: false,
+ canEditHooksAndDeletePaths: false,
+ canEditHooksAndRenamePaths: false,
+ canToggleError: false,
+ canToggleSuspense: false,
+ isSuspended: false,
+ hasLegacyContext: false,
+ context: null,
+ hooks: null,
+ props: null,
+ state: null,
+ owners: null,
+ };
+}
+
+function mergeRoots(
+ left: InspectedElement,
+ right: InspectedElement,
+ suspendedByOffset: number,
+): void {
+ const leftSuspendedByRange = left.suspendedByRange;
+ const rightSuspendedByRange = right.suspendedByRange;
+
+ if (right.isErrored) {
+ left.isErrored = true;
+ }
+ for (let i = 0; i < right.errors.length; i++) {
+ left.errors.push(right.errors[i]);
+ }
+ for (let i = 0; i < right.warnings.length; i++) {
+ left.warnings.push(right.warnings[i]);
+ }
+
+ const leftSuspendedBy: DehydratedData = left.suspendedBy;
+ const {data, cleaned, unserializable} = (right.suspendedBy: DehydratedData);
+ const leftSuspendedByData = ((leftSuspendedBy.data: any): Array);
+ const rightSuspendedByData = ((data: any): Array);
+ for (let i = 0; i < rightSuspendedByData.length; i++) {
+ leftSuspendedByData.push(rightSuspendedByData[i]);
+ }
+ for (let i = 0; i < cleaned.length; i++) {
+ leftSuspendedBy.cleaned.push(
+ [suspendedByOffset + cleaned[i][0]].concat(cleaned[i].slice(1)),
+ );
+ }
+ for (let i = 0; i < unserializable.length; i++) {
+ leftSuspendedBy.unserializable.push(
+ [suspendedByOffset + unserializable[i][0]].concat(
+ unserializable[i].slice(1),
+ ),
+ );
+ }
+
+ if (rightSuspendedByRange !== null) {
+ if (leftSuspendedByRange === null) {
+ left.suspendedByRange = [
+ rightSuspendedByRange[0],
+ rightSuspendedByRange[1],
+ ];
+ } else {
+ if (rightSuspendedByRange[0] < leftSuspendedByRange[0]) {
+ leftSuspendedByRange[0] = rightSuspendedByRange[0];
+ }
+ if (rightSuspendedByRange[1] > leftSuspendedByRange[1]) {
+ leftSuspendedByRange[1] = rightSuspendedByRange[1];
+ }
+ }
+ }
+}
+
export default class Agent extends EventEmitter<{
hideNativeHighlight: [],
showNativeHighlight: [HostInstance],
@@ -201,6 +320,7 @@ export default class Agent extends EventEmitter<{
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
bridge.addListener('getOwnersList', this.getOwnersList);
bridge.addListener('inspectElement', this.inspectElement);
+ bridge.addListener('inspectScreen', this.inspectScreen);
bridge.addListener('logElementToConsole', this.logElementToConsole);
bridge.addListener('overrideError', this.overrideError);
bridge.addListener('overrideSuspense', this.overrideSuspense);
@@ -531,6 +651,138 @@ export default class Agent extends EventEmitter<{
}
};
+ inspectScreen: InspectScreenParams => void = ({
+ requestID,
+ id,
+ forceFullData,
+ path: screenPath,
+ }) => {
+ let inspectedScreen: InspectedElement | null = null;
+ let found = false;
+ // the suspendedBy index will be from the previously merged roots.
+ // We need to keep track of how many suspendedBy we've already seen to know
+ // to which renderer the index belongs.
+ let suspendedByOffset = 0;
+ let suspendedByPathIndex: number | null = null;
+ // The path to hydrate for a specific renderer
+ let rendererPath: InspectElementParams['path'] = null;
+ if (screenPath !== null && screenPath.length > 1) {
+ const secondaryCategory = screenPath[0];
+ if (secondaryCategory !== 'suspendedBy') {
+ throw new Error(
+ 'Only hydrating suspendedBy paths is supported. This is a bug.',
+ );
+ }
+ if (typeof screenPath[1] !== 'number') {
+ throw new Error(
+ `Expected suspendedBy index to be a number. Received '${screenPath[1]}' instead. This is a bug.`,
+ );
+ }
+ suspendedByPathIndex = screenPath[1];
+ rendererPath = screenPath.slice(2);
+ }
+
+ for (const rendererID in this._rendererInterfaces) {
+ const renderer = ((this._rendererInterfaces[
+ (rendererID: any)
+ ]: any): RendererInterface);
+ let path: InspectElementParams['path'] = null;
+ if (suspendedByPathIndex !== null && rendererPath !== null) {
+ const suspendedByPathRendererIndex =
+ suspendedByPathIndex - suspendedByOffset;
+ const rendererHasRequestedSuspendedByPath =
+ renderer.getElementAttributeByPath(id, [
+ 'suspendedBy',
+ suspendedByPathRendererIndex,
+ ]) !== undefined;
+ if (rendererHasRequestedSuspendedByPath) {
+ path = ['suspendedBy', suspendedByPathRendererIndex].concat(
+ rendererPath,
+ );
+ }
+ }
+
+ const inspectedRootsPayload = renderer.inspectElement(
+ requestID,
+ id,
+ path,
+ forceFullData,
+ );
+ switch (inspectedRootsPayload.type) {
+ case 'hydrated-path':
+ // The path will be relative to the Roots of this renderer. We adjust it
+ // to be relative to all Roots of this implementation.
+ inspectedRootsPayload.path[1] += suspendedByOffset;
+ // TODO: Hydration logic is flawed since the Frontend path is not based
+ // on the original backend data but rather its own representation of it (e.g. due to reorder).
+ // So we can receive null here instead when hydration fails
+ if (inspectedRootsPayload.value !== null) {
+ for (
+ let i = 0;
+ i < inspectedRootsPayload.value.cleaned.length;
+ i++
+ ) {
+ inspectedRootsPayload.value.cleaned[i][1] += suspendedByOffset;
+ }
+ }
+ this._bridge.send('inspectedScreen', inspectedRootsPayload);
+ // If we hydrated a path, it must've been in a specific renderer so we can stop here.
+ return;
+ case 'full-data':
+ const inspectedRoots = inspectedRootsPayload.value;
+ if (inspectedScreen === null) {
+ inspectedScreen = createEmptyInspectedScreen(
+ inspectedRoots.id,
+ inspectedRoots.type,
+ );
+ }
+ mergeRoots(inspectedScreen, inspectedRoots, suspendedByOffset);
+ const dehydratedSuspendedBy: DehydratedData =
+ inspectedRoots.suspendedBy;
+ const suspendedBy = ((dehydratedSuspendedBy.data: any): Array);
+ suspendedByOffset += suspendedBy.length;
+ found = true;
+ break;
+ case 'no-change':
+ found = true;
+ const rootsSuspendedBy: Array =
+ (renderer.getElementAttributeByPath(id, ['suspendedBy']): any);
+ suspendedByOffset += rootsSuspendedBy.length;
+ break;
+ case 'not-found':
+ break;
+ case 'error':
+ // bail out and show the error
+ // TODO: aggregate errors
+ this._bridge.send('inspectedScreen', inspectedRootsPayload);
+ return;
+ }
+ }
+
+ if (inspectedScreen === null) {
+ if (found) {
+ this._bridge.send('inspectedScreen', {
+ type: 'no-change',
+ responseID: requestID,
+ id,
+ });
+ } else {
+ this._bridge.send('inspectedScreen', {
+ type: 'not-found',
+ responseID: requestID,
+ id,
+ });
+ }
+ } else {
+ this._bridge.send('inspectedScreen', {
+ type: 'full-data',
+ responseID: requestID,
+ id,
+ value: inspectedScreen,
+ });
+ }
+ };
+
logElementToConsole: ElementAndRendererID => void = ({id, rendererID}) => {
const renderer = this._rendererInterfaces[rendererID];
if (renderer == null) {
@@ -567,17 +819,15 @@ export default class Agent extends EventEmitter<{
};
overrideSuspenseMilestone: OverrideSuspenseMilestoneParams => void = ({
- rendererID,
- rootID,
suspendedSet,
}) => {
- const renderer = this._rendererInterfaces[rendererID];
- if (renderer == null) {
- console.warn(
- `Invalid renderer id "${rendererID}" to override suspense milestone`,
- );
- } else {
- renderer.overrideSuspenseMilestone(rootID, suspendedSet);
+ for (const rendererID in this._rendererInterfaces) {
+ const renderer = ((this._rendererInterfaces[
+ (rendererID: any)
+ ]: any): RendererInterface);
+ if (renderer.supportsTogglingSuspense) {
+ renderer.overrideSuspenseMilestone(suspendedSet);
+ }
}
};
@@ -739,7 +989,7 @@ export default class Agent extends EventEmitter<{
if (renderer !== null) {
const devRenderer = renderer.bundleType === 1;
const enableSuspenseTab =
- devRenderer && gte(renderer.version, '19.2.0-canary');
+ devRenderer && gte(renderer.version, '19.3.0-canary');
if (enableSuspenseTab) {
this._bridge.send('enableSuspenseTab');
}
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 031a18b74d..ba214db05c 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -2420,7 +2420,6 @@ export function attach(
!isProductionBuildOfRenderer && StrictModeBits !== 0 ? 1 : 0,
);
pushOperation(hasOwnerMetadata ? 1 : 0);
- pushOperation(supportsTogglingSuspense ? 1 : 0);
if (isProfiling) {
if (displayNamesByRootID !== null) {
@@ -4902,7 +4901,11 @@ export function attach(
fiberInstance.data = nextFiber;
if (
mostRecentlyInspectedElement !== null &&
- mostRecentlyInspectedElement.id === fiberInstance.id &&
+ (mostRecentlyInspectedElement.id === fiberInstance.id ||
+ // If we're inspecting a Root, we inspect the Screen.
+ // Invalidating any Root invalidates the Screen too.
+ (mostRecentlyInspectedElement.type === ElementTypeRoot &&
+ nextFiber.tag === HostRoot)) &&
didFiberRender(prevFiber, nextFiber)
) {
// If this Fiber has updated, clear cached inspected data.
@@ -6422,7 +6425,10 @@ export function attach(
return inspectVirtualInstanceRaw(devtoolsInstance);
}
if (devtoolsInstance.kind === FIBER_INSTANCE) {
- return inspectFiberInstanceRaw(devtoolsInstance);
+ const isRoot = devtoolsInstance.parent === null;
+ return isRoot
+ ? inspectRootsRaw(devtoolsInstance.id)
+ : inspectFiberInstanceRaw(devtoolsInstance);
}
(devtoolsInstance: FilteredFiberInstance); // assert exhaustive
throw new Error('Unsupported instance kind');
@@ -6590,9 +6596,6 @@ export function attach(
rootType = fiberRoot._debugRootType;
}
- const isTimedOutSuspense =
- tag === SuspenseComponent && memoizedState !== null;
-
let isErrored = false;
if (isErrorBoundary(fiber)) {
// if the current inspected element is an error boundary,
@@ -6664,7 +6667,7 @@ export function attach(
if (
fiberInstance.suspenseNode !== null &&
fiberInstance.suspenseNode.hasUnknownSuspenders &&
- !isTimedOutSuspense
+ !isSuspended
) {
// Something unknown threw to suspended this boundary. Let's figure out why that might be.
if (renderer.bundleType === 0) {
@@ -6702,7 +6705,7 @@ export function attach(
supportsTogglingSuspense &&
hasSuspenseBoundary &&
// If it's showing the real content, we can always flip fallback.
- (!isTimedOutSuspense ||
+ (!isSuspended ||
// If it's showing fallback because we previously forced it to,
// allow toggling it back to remove the fallback override.
forceFallbackForFibers.has(fiber) ||
@@ -6875,10 +6878,24 @@ export function attach(
let currentlyInspectedPaths: Object = {};
function isMostRecentlyInspectedElement(id: number): boolean {
- return (
- mostRecentlyInspectedElement !== null &&
- mostRecentlyInspectedElement.id === id
- );
+ if (mostRecentlyInspectedElement === null) {
+ return false;
+ }
+ if (mostRecentlyInspectedElement.id === id) {
+ return true;
+ }
+
+ if (mostRecentlyInspectedElement.type === ElementTypeRoot) {
+ // we inspected the screen recently. If we're inspecting another root, we're
+ // still inspecting the screen.
+ const instance = idToDevToolsInstanceMap.get(id);
+ return (
+ instance !== undefined &&
+ instance.kind === FIBER_INSTANCE &&
+ instance.parent === null
+ );
+ }
+ return false;
}
function isMostRecentlyInspectedElementCurrent(id: number): boolean {
@@ -7060,8 +7077,8 @@ export function attach(
if (!hasElementUpdatedSinceLastInspected) {
if (path !== null) {
let secondaryCategory: 'suspendedBy' | 'hooks' | null = null;
- if (path[0] === 'hooks') {
- secondaryCategory = 'hooks';
+ if (path[0] === 'hooks' || path[0] === 'suspendedBy') {
+ secondaryCategory = path[0];
}
// If this element has not been updated since it was last inspected,
@@ -7209,6 +7226,99 @@ export function attach(
};
}
+ function inspectRootsRaw(arbitraryRootID: number): InspectedElement | null {
+ const roots = hook.getFiberRoots(rendererID);
+ if (roots.size === 0) {
+ return null;
+ }
+
+ const inspectedRoots: InspectedElement = {
+ // invariants
+ id: arbitraryRootID,
+ type: ElementTypeRoot,
+ // Properties we merge
+ isErrored: false,
+ errors: [],
+ warnings: [],
+ suspendedBy: [],
+ suspendedByRange: null,
+ // TODO: How to merge these?
+ unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
+ // Properties where merging doesn't make sense so we ignore them entirely in the UI
+ rootType: null,
+ plugins: {stylex: null},
+ nativeTag: null,
+ env: null,
+ source: null,
+ stack: null,
+ rendererPackageName: null,
+ rendererVersion: null,
+ // These don't make sense for a Root. They're just bottom values.
+ key: null,
+ canEditFunctionProps: false,
+ canEditHooks: false,
+ canEditFunctionPropsDeletePaths: false,
+ canEditFunctionPropsRenamePaths: false,
+ canEditHooksAndDeletePaths: false,
+ canEditHooksAndRenamePaths: false,
+ canToggleError: false,
+ canToggleSuspense: false,
+ isSuspended: false,
+ hasLegacyContext: false,
+ context: null,
+ hooks: null,
+ props: null,
+ state: null,
+ owners: null,
+ };
+
+ let minSuspendedByRange = Infinity;
+ let maxSuspendedByRange = -Infinity;
+ roots.forEach(root => {
+ const rootInstance = rootToFiberInstanceMap.get(root);
+ if (rootInstance === undefined) {
+ throw new Error(
+ 'Expected a root instance to exist for this Fiber root',
+ );
+ }
+ const inspectedRoot = inspectFiberInstanceRaw(rootInstance);
+ if (inspectedRoot === null) {
+ return;
+ }
+
+ if (inspectedRoot.isErrored) {
+ inspectedRoots.isErrored = true;
+ }
+ for (let i = 0; i < inspectedRoot.errors.length; i++) {
+ inspectedRoots.errors.push(inspectedRoot.errors[i]);
+ }
+ for (let i = 0; i < inspectedRoot.warnings.length; i++) {
+ inspectedRoots.warnings.push(inspectedRoot.warnings[i]);
+ }
+ for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) {
+ inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]);
+ }
+ const suspendedByRange = inspectedRoot.suspendedByRange;
+ if (suspendedByRange !== null) {
+ if (suspendedByRange[0] < minSuspendedByRange) {
+ minSuspendedByRange = suspendedByRange[0];
+ }
+ if (suspendedByRange[1] > maxSuspendedByRange) {
+ maxSuspendedByRange = suspendedByRange[1];
+ }
+ }
+ });
+
+ if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) {
+ inspectedRoots.suspendedByRange = [
+ minSuspendedByRange,
+ maxSuspendedByRange,
+ ];
+ }
+
+ return inspectedRoots;
+ }
+
function logElementToConsole(id: number) {
const result = isMostRecentlyInspectedElementCurrent(id)
? mostRecentlyInspectedElement
@@ -7867,13 +7977,9 @@ export function attach(
/**
* Resets the all other roots of this renderer.
- * @param rootID The root that contains this milestone
* @param suspendedSet List of IDs of SuspenseComponent Fibers
*/
- function overrideSuspenseMilestone(
- rootID: FiberInstance['id'],
- suspendedSet: Array,
- ) {
+ function overrideSuspenseMilestone(suspendedSet: Array) {
if (
typeof setSuspenseHandler !== 'function' ||
typeof scheduleUpdate !== 'function'
@@ -7883,8 +7989,6 @@ export function attach(
);
}
- // TODO: Allow overriding the timeline for the specified root.
-
const unsuspendedSet: Set = new Set(forceFallbackForFibers);
let resuspended = false;
diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js
index 8a245155ef..46709c9a80 100644
--- a/packages/react-devtools-shared/src/backend/legacy/renderer.js
+++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js
@@ -412,7 +412,6 @@ export function attach(
pushOperation(0); // Profiling flag
pushOperation(0); // StrictMode supported?
pushOperation(hasOwnerMetadata ? 1 : 0);
- pushOperation(supportsTogglingSuspense ? 1 : 0);
pushOperation(SUSPENSE_TREE_OPERATION_ADD);
pushOperation(id);
@@ -800,6 +799,20 @@ export function attach(
return null;
}
+ const rootID = internalInstanceToRootIDMap.get(internalInstance);
+ if (rootID === undefined) {
+ throw new Error('Expected to find root ID.');
+ }
+ const isRoot = rootID === id;
+ return isRoot
+ ? inspectRootsRaw(rootID)
+ : inspectInternalInstanceRaw(id, internalInstance);
+ }
+
+ function inspectInternalInstanceRaw(
+ id: number,
+ internalInstance: InternalInstance,
+ ): InspectedElement | null {
const {key} = getData(internalInstance);
const type = getElementType(internalInstance);
@@ -903,6 +916,98 @@ export function attach(
};
}
+ function inspectRootsRaw(arbitraryRootID: number): InspectedElement | null {
+ const roots =
+ renderer.Mount._instancesByReactRootID ||
+ renderer.Mount._instancesByContainerID;
+
+ const inspectedRoots: InspectedElement = {
+ // invariants
+ id: arbitraryRootID,
+ type: ElementTypeRoot,
+ // Properties we merge
+ isErrored: false,
+ errors: [],
+ warnings: [],
+ suspendedBy: [],
+ suspendedByRange: null,
+ // TODO: How to merge these?
+ unknownSuspenders: UNKNOWN_SUSPENDERS_NONE,
+ // Properties where merging doesn't make sense so we ignore them entirely in the UI
+ rootType: null,
+ plugins: {stylex: null},
+ nativeTag: null,
+ env: null,
+ source: null,
+ stack: null,
+ // TODO: We could make the Frontend accept an array to display
+ // a list of unique renderers contributing to this Screen.
+ rendererPackageName: null,
+ rendererVersion: null,
+ // These don't make sense for a Root. They're just bottom values.
+ key: null,
+ canEditFunctionProps: false,
+ canEditHooks: false,
+ canEditFunctionPropsDeletePaths: false,
+ canEditFunctionPropsRenamePaths: false,
+ canEditHooksAndDeletePaths: false,
+ canEditHooksAndRenamePaths: false,
+ canToggleError: false,
+ canToggleSuspense: false,
+ isSuspended: false,
+ hasLegacyContext: false,
+ context: null,
+ hooks: null,
+ props: null,
+ state: null,
+ owners: null,
+ };
+
+ let minSuspendedByRange = Infinity;
+ let maxSuspendedByRange = -Infinity;
+
+ for (const rootKey in roots) {
+ const internalInstance = roots[rootKey];
+ const id = getID(internalInstance);
+ const inspectedRoot = inspectInternalInstanceRaw(id, internalInstance);
+
+ if (inspectedRoot === null) {
+ return null;
+ }
+
+ if (inspectedRoot.isErrored) {
+ inspectedRoots.isErrored = true;
+ }
+ for (let i = 0; i < inspectedRoot.errors.length; i++) {
+ inspectedRoots.errors.push(inspectedRoot.errors[i]);
+ }
+ for (let i = 0; i < inspectedRoot.warnings.length; i++) {
+ inspectedRoots.warnings.push(inspectedRoot.warnings[i]);
+ }
+ for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) {
+ inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]);
+ }
+ const suspendedByRange = inspectedRoot.suspendedByRange;
+ if (suspendedByRange !== null) {
+ if (suspendedByRange[0] < minSuspendedByRange) {
+ minSuspendedByRange = suspendedByRange[0];
+ }
+ if (suspendedByRange[1] > maxSuspendedByRange) {
+ maxSuspendedByRange = suspendedByRange[1];
+ }
+ }
+ }
+
+ if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) {
+ inspectedRoots.suspendedByRange = [
+ minSuspendedByRange,
+ maxSuspendedByRange,
+ ];
+ }
+
+ return inspectedRoots;
+ }
+
function logElementToConsole(id: number): void {
const result = inspectElementRaw(id);
if (result === null) {
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index e002740cb6..1052dc9d75 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -450,10 +450,7 @@ export type RendererInterface = {
onErrorOrWarning?: OnErrorOrWarning,
overrideError: (id: number, forceError: boolean) => void,
overrideSuspense: (id: number, forceFallback: boolean) => void,
- overrideSuspenseMilestone: (
- rootID: number,
- suspendedSet: Array,
- ) => void,
+ overrideSuspenseMilestone: (suspendedSet: Array) => void,
overrideValueAtPath: (
type: Type,
id: number,
diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js
index 155717e5d8..6fd93d3519 100644
--- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js
+++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js
@@ -10,6 +10,7 @@
import Agent from 'react-devtools-shared/src/backend/agent';
import {hideOverlay, showOverlay} from './Highlighter';
+import type {HostInstance} from 'react-devtools-shared/src/backend/types';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {RendererInterface} from '../../types';
@@ -26,6 +27,7 @@ export default function setupHighlighter(
): void {
bridge.addListener('clearHostInstanceHighlight', clearHostInstanceHighlight);
bridge.addListener('highlightHostInstance', highlightHostInstance);
+ bridge.addListener('highlightHostInstances', highlightHostInstances);
bridge.addListener('scrollToHostInstance', scrollToHostInstance);
bridge.addListener('shutdown', stopInspectingHost);
bridge.addListener('startInspectingHost', startInspectingHost);
@@ -157,6 +159,52 @@ export default function setupHighlighter(
hideOverlay(agent);
}
+ function highlightHostInstances({
+ displayName,
+ hideAfterTimeout,
+ elements,
+ scrollIntoView,
+ }: {
+ displayName: string | null,
+ hideAfterTimeout: boolean,
+ elements: Array<{rendererID: number, id: number}>,
+ scrollIntoView: boolean,
+ }) {
+ const nodes: Array = [];
+ for (let i = 0; i < elements.length; i++) {
+ const {id, rendererID} = elements[i];
+ const renderer = agent.rendererInterfaces[rendererID];
+ if (renderer == null) {
+ console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
+ continue;
+ }
+
+ // In some cases fiber may already be unmounted
+ if (!renderer.hasElementWithId(id)) {
+ continue;
+ }
+
+ const hostInstances = renderer.findHostInstancesForElementID(id);
+ if (hostInstances !== null) {
+ for (let j = 0; j < hostInstances.length; j++) {
+ nodes.push(hostInstances[j]);
+ }
+ }
+ }
+
+ if (nodes.length > 0) {
+ const node = nodes[0];
+ // $FlowFixMe[method-unbinding]
+ if (scrollIntoView && typeof node.scrollIntoView === 'function') {
+ // If the node isn't visible show it before highlighting it.
+ // We may want to reconsider this; it might be a little disruptive.
+ node.scrollIntoView({block: 'nearest', inline: 'nearest'});
+ }
+ }
+
+ showOverlay(nodes, displayName, agent, hideAfterTimeout);
+ }
+
function attemptScrollToHostInstance(
renderer: RendererInterface,
id: number,
diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js
index f6c11fb10d..c332393d2a 100644
--- a/packages/react-devtools-shared/src/backendAPI.js
+++ b/packages/react-devtools-shared/src/backendAPI.js
@@ -95,7 +95,7 @@ export function inspectElement(
id: number,
path: InspectedElementPath | null,
rendererID: number,
- shouldListenToPauseEvents: boolean = false,
+ shouldListenToPauseEvents: boolean,
): Promise {
const requestID = requestCounter++;
const promise = getPromiseForRequestID(
@@ -117,6 +117,32 @@ export function inspectElement(
return promise;
}
+export function inspectScreen(
+ bridge: FrontendBridge,
+ forceFullData: boolean,
+ arbitraryRootID: number,
+ path: InspectedElementPath | null,
+ shouldListenToPauseEvents: boolean,
+): Promise {
+ const requestID = requestCounter++;
+ const promise = getPromiseForRequestID(
+ requestID,
+ 'inspectedScreen',
+ bridge,
+ `Timed out while inspecting screen.`,
+ shouldListenToPauseEvents,
+ );
+
+ bridge.send('inspectScreen', {
+ requestID,
+ id: arbitraryRootID,
+ path,
+ forceFullData,
+ });
+
+ return promise;
+}
+
let storeAsGlobalCount = 0;
export function storeAsGlobal({
diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js
index aa9c867e1f..b6229192c2 100644
--- a/packages/react-devtools-shared/src/bridge.js
+++ b/packages/react-devtools-shared/src/bridge.js
@@ -65,12 +65,6 @@ export const BRIDGE_PROTOCOL: Array = [
{
version: 2,
minNpmVersion: '4.22.0',
- maxNpmVersion: '6.2.0',
- },
- // Version 3 adds supports-toggling-suspense bit to add-root
- {
- version: 3,
- minNpmVersion: '6.2.0',
maxNpmVersion: null,
},
];
@@ -92,6 +86,12 @@ type HighlightHostInstance = {
openBuiltinElementsPanel: boolean,
scrollIntoView: boolean,
};
+type HighlightHostInstances = {
+ elements: Array,
+ displayName: string | null,
+ hideAfterTimeout: boolean,
+ scrollIntoView: boolean,
+};
type ScrollToHostInstance = {
...ElementAndRendererID,
@@ -145,8 +145,6 @@ type OverrideSuspense = {
};
type OverrideSuspenseMilestone = {
- rendererID: number,
- rootID: number,
suspendedSet: Array,
};
@@ -167,6 +165,13 @@ type InspectElementParams = {
requestID: number,
};
+type InspectScreenParams = {
+ requestID: number,
+ id: number,
+ forceFullData: boolean,
+ path: Array | null,
+};
+
type StoreAsGlobalParams = {
...ElementAndRendererID,
count: number,
@@ -199,6 +204,7 @@ export type BackendEvents = {
fastRefreshScheduled: [],
getSavedPreferences: [],
inspectedElement: [InspectedElementPayload],
+ inspectedScreen: [InspectedElementPayload],
isReloadAndProfileSupportedByBackend: [boolean],
operations: [Array],
ownersList: [OwnersList],
@@ -243,7 +249,9 @@ type FrontendEvents = {
getProfilingData: [{rendererID: RendererID}],
getProfilingStatus: [],
highlightHostInstance: [HighlightHostInstance],
+ highlightHostInstances: [HighlightHostInstances],
inspectElement: [InspectElementParams],
+ inspectScreen: [InspectScreenParams],
logElementToConsole: [ElementAndRendererID],
overrideError: [OverrideError],
overrideSuspense: [OverrideSuspense],
diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js
index 0e3373e15e..99971f6a1a 100644
--- a/packages/react-devtools-shared/src/devtools/store.js
+++ b/packages/react-devtools-shared/src/devtools/store.js
@@ -96,7 +96,6 @@ export type Capabilities = {
supportsBasicProfiling: boolean,
hasOwnerMetadata: boolean,
supportsStrictMode: boolean,
- supportsTogglingSuspense: boolean,
supportsAdvancedProfiling: AdvancedProfiling,
};
@@ -506,14 +505,6 @@ export default class Store extends EventEmitter<{
);
}
- supportsTogglingSuspense(rootID: Element['id']): boolean {
- const capabilities = this._rootIDToCapabilities.get(rootID);
- if (capabilities === undefined) {
- throw new Error(`No capabilities registered for root ${rootID}`);
- }
- return capabilities.supportsTogglingSuspense;
- }
-
// This build of DevTools supports the Timeline profiler.
// This is a static flag, controlled by the Store config.
get supportsTimeline(): boolean {
@@ -898,38 +889,48 @@ export default class Store extends EventEmitter<{
* @param uniqueSuspendersOnly Filters out boundaries without unique suspenders
*/
getSuspendableDocumentOrderSuspense(
- rootID: Element['id'] | void,
uniqueSuspendersOnly: boolean,
): $ReadOnlyArray {
- if (rootID === undefined) {
- return [];
- }
- const root = this.getElementByID(rootID);
- if (root === null) {
- return [];
- }
- if (!this.supportsTogglingSuspense(rootID)) {
+ const roots = this.roots;
+ if (roots.length === 0) {
return [];
}
+
const list: SuspenseNode['id'][] = [];
- const suspense = this.getSuspenseByID(rootID);
- if (suspense !== null) {
- const stack = [suspense];
- while (stack.length > 0) {
- const current = stack.pop();
- if (current === undefined) {
- continue;
+ for (let i = 0; i < roots.length; i++) {
+ const rootID = roots[i];
+ const root = this.getElementByID(rootID);
+ if (root === null) {
+ continue;
+ }
+ // TODO: This includes boundaries that can't be suspended due to no support from the renderer.
+
+ const suspense = this.getSuspenseByID(rootID);
+ if (suspense !== null) {
+ if (list.length === 0) {
+ // start with an arbitrary root that will allow inspection of the Screen
+ list.push(suspense.id);
}
- // Include the root even if we won't show it suspended (because that's just blank).
- // You should be able to see what suspended the shell.
- if (!uniqueSuspendersOnly || current.hasUniqueSuspenders) {
- list.push(current.id);
- }
- // Add children in reverse order to maintain document order
- for (let j = current.children.length - 1; j >= 0; j--) {
- const childSuspense = this.getSuspenseByID(current.children[j]);
- if (childSuspense !== null) {
- stack.push(childSuspense);
+
+ const stack = [suspense];
+ while (stack.length > 0) {
+ const current = stack.pop();
+ if (current === undefined) {
+ continue;
+ }
+ if (
+ (!uniqueSuspendersOnly || current.hasUniqueSuspenders) &&
+ // Roots are already included as part of the Screen
+ current.id !== rootID
+ ) {
+ list.push(current.id);
+ }
+ // Add children in reverse order to maintain document order
+ for (let j = current.children.length - 1; j >= 0; j--) {
+ const childSuspense = this.getSuspenseByID(current.children[j]);
+ if (childSuspense !== null) {
+ stack.push(childSuspense);
+ }
}
}
}
@@ -1191,7 +1192,6 @@ export default class Store extends EventEmitter<{
let supportsStrictMode = false;
let hasOwnerMetadata = false;
- let supportsTogglingSuspense = false;
// If we don't know the bridge protocol, guess that we're dealing with the latest.
// If we do know it, we can take it into consideration when parsing operations.
@@ -1204,9 +1204,6 @@ export default class Store extends EventEmitter<{
hasOwnerMetadata = operations[i] > 0;
i++;
-
- supportsTogglingSuspense = operations[i] > 0;
- i++;
}
this._roots = this._roots.concat(id);
@@ -1215,7 +1212,6 @@ export default class Store extends EventEmitter<{
supportsBasicProfiling,
hasOwnerMetadata,
supportsStrictMode,
- supportsTogglingSuspense,
supportsAdvancedProfiling,
});
@@ -1561,7 +1557,12 @@ export default class Store extends EventEmitter<{
if (name === null) {
// The boundary isn't explicitly named.
// Pick a sensible default.
- name = this._guessSuspenseName(element);
+ if (parentID === 0) {
+ // For Roots we use their display name.
+ name = element.displayName;
+ } else {
+ name = this._guessSuspenseName(element);
+ }
}
}
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
index 08bba4f9da..88aeab7bfd 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
@@ -269,18 +269,21 @@ export default function InspectedElementWrapper(_: Props): React.Node {
)}
- {canToggleSuspense && (
+ {canToggleSuspense || isSuspended ? (
- )}
+ ) : null}
{store.supportsInspectMatchingDOMElement && (
+ );
+ }
return null;
}
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js
deleted file mode 100644
index ebb4b5bcd5..0000000000
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspenseToggle.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import * as React from 'react';
-import {OptionsContext} from '../context';
-import EditableValue from './EditableValue';
-import Store from '../../store';
-import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types';
-import styles from './InspectedElementSharedStyles.css';
-
-import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
-import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
-
-type Props = {
- bridge: FrontendBridge,
- inspectedElement: InspectedElement,
- store: Store,
-};
-
-export default function InspectedElementSuspenseToggle({
- bridge,
- inspectedElement,
- store,
-}: Props): React.Node {
- const {readOnly} = React.useContext(OptionsContext);
-
- const {id, isSuspended, type} = inspectedElement;
- const canToggleSuspense = !readOnly && inspectedElement.canToggleSuspense;
-
- if (type !== ElementTypeSuspense) {
- return null;
- }
-
- const toggleSuspense = (path: any, value: boolean) => {
- const rendererID = store.getRendererIDForElement(id);
- if (rendererID !== null) {
- bridge.send('overrideSuspense', {
- id,
- rendererID,
- forceFallback: value,
- });
- }
- };
-
- return (
-
-
-
suspense
-
-
- Suspended
- {canToggleSuspense ? (
- // key is required to keep and header row toggle button in sync
-
- ) : (
- {isSuspended ? 'true' : 'false'}
- )}
-
-
- );
-}
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js
index 7e2175db74..6d3d0d604f 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js
@@ -17,7 +17,6 @@ import InspectedElementHooksTree from './InspectedElementHooksTree';
import InspectedElementPropsTree from './InspectedElementPropsTree';
import InspectedElementStateTree from './InspectedElementStateTree';
import InspectedElementStyleXPlugin from './InspectedElementStyleXPlugin';
-import InspectedElementSuspenseToggle from './InspectedElementSuspenseToggle';
import InspectedElementSuspendedBy from './InspectedElementSuspendedBy';
import NativeStyleEditor from './NativeStyleEditor';
import {enableStyleXFeatures} from 'react-devtools-feature-flags';
@@ -25,6 +24,10 @@ import InspectedElementSourcePanel from './InspectedElementSourcePanel';
import StackTraceView from './StackTraceView';
import OwnerView from './OwnerView';
import Skeleton from './Skeleton';
+import {
+ ElementTypeSuspense,
+ ElementTypeActivity,
+} from 'react-devtools-shared/src/frontend/types';
import styles from './InspectedElementView.css';
@@ -61,6 +64,7 @@ export default function InspectedElementView({
rootType,
source,
nativeTag,
+ type,
} = inspectedElement;
const bridge = useContext(BridgeContext);
@@ -75,6 +79,17 @@ export default function InspectedElementView({
const showRenderedBy =
showStack || showOwnersList || rendererLabel !== null || rootType !== null;
+ const propsSection = (
+
+
+
+ );
+
return (
@@ -86,22 +101,12 @@ export default function InspectedElementView({
/>
-
-
-
-
-
-
-
+ {
+ // For Suspense and Activity we show the props further down.
+ type !== ElementTypeSuspense && type !== ElementTypeActivity
+ ? propsSection
+ : null
+ }
+ {
+ // For Suspense and Activity we show the props below suspended by to give that more priority.
+ type !== ElementTypeSuspense && type !== ElementTypeActivity
+ ? null
+ : propsSection
+ }
+
{showRenderedBy && (
+ label={
+ index === min
+ ? // The first step in the timeline is always a Transition (Initial Paint).
+ // TODO: Support multiple environments.
+ 'Initial Paint'
+ : // TODO: Consider adding the name of this specific boundary if this step has only one.
+ 'Suspense'
+ }>
-
+ ,
);
}
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js
index b4ed7ec1f9..f698f0ae16 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js
@@ -34,13 +34,13 @@ import {
SuspenseTreeStateContext,
} from './SuspenseTreeContext';
import {StoreContext, OptionsContext} from '../context';
-import {TreeDispatcherContext} from '../Components/TreeContext';
+import {
+ TreeDispatcherContext,
+ TreeStateContext,
+} from '../Components/TreeContext';
import Button from '../Button';
import Toggle from '../Toggle';
-import typeof {
- SyntheticEvent,
- SyntheticPointerEvent,
-} from 'react-dom-bindings/src/events/SyntheticEvent';
+import typeof {SyntheticPointerEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';
@@ -71,20 +71,14 @@ function ToggleUniqueSuspenders() {
const store = useContext(StoreContext);
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
- const {selectedRootID: rootID, uniqueSuspendersOnly} = useContext(
- SuspenseTreeStateContext,
- );
+ const {uniqueSuspendersOnly} = useContext(SuspenseTreeStateContext);
function handleToggleUniqueSuspenders() {
const nextUniqueSuspendersOnly = !uniqueSuspendersOnly;
- const nextTimeline =
- rootID === null
- ? []
- : // TODO: Handle different timeline modes (e.g. random order)
- store.getSuspendableDocumentOrderSuspense(
- rootID,
- nextUniqueSuspendersOnly,
- );
+ // TODO: Handle different timeline modes (e.g. random order)
+ const nextTimeline = store.getSuspendableDocumentOrderSuspense(
+ nextUniqueSuspendersOnly,
+ );
suspenseTreeDispatch({
type: 'SET_SUSPENSE_TIMELINE',
payload: [nextTimeline, null, nextUniqueSuspendersOnly],
@@ -101,55 +95,6 @@ function ToggleUniqueSuspenders() {
);
}
-function SelectRoot() {
- const store = useContext(StoreContext);
- const {roots, selectedRootID, uniqueSuspendersOnly} = useContext(
- SuspenseTreeStateContext,
- );
- const treeDispatch = useContext(TreeDispatcherContext);
- const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
-
- function handleChange(event: SyntheticEvent) {
- const newRootID = +event.currentTarget.value;
- // TODO: scrollIntoView both suspense rects and host instance.
- const nextTimeline = store.getSuspendableDocumentOrderSuspense(
- newRootID,
- uniqueSuspendersOnly,
- );
- suspenseTreeDispatch({
- type: 'SET_SUSPENSE_TIMELINE',
- payload: [nextTimeline, newRootID, uniqueSuspendersOnly],
- });
- if (nextTimeline.length > 0) {
- const milestone = nextTimeline[nextTimeline.length - 1];
- treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone});
- }
- }
- return (
- roots.length > 0 && (
-
- )
- );
-}
-
function ToggleTreeList({
dispatch,
state,
@@ -240,6 +185,18 @@ function SuspenseTab(_: {}) {
treeListHorizontalFraction,
} = state;
+ const {inspectedElementID} = useContext(TreeStateContext);
+ const {timeline} = useContext(SuspenseTreeStateContext);
+ const treeDispatch = useContext(TreeDispatcherContext);
+ useLayoutEffect(() => {
+ // If the inspected element is still null and we've loaded a timeline, we can set the initial selection.
+ // TODO: This tab should use its own source of truth instead so we only show suspense boundaries.
+ if (inspectedElementID === null && timeline.length > 0) {
+ const milestone = timeline[timeline.length - 1];
+ treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone});
+ }
+ }, [timeline, inspectedElementID]);
+
useLayoutEffect(() => {
const wrapperElement = wrapperTreeRef.current;
@@ -427,7 +384,6 @@ function SuspenseTab(_: {}) {
-
{!hideSettings && }
diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js
index b7340da915..30ca21476f 100644
--- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js
+++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js
@@ -9,7 +9,7 @@
import * as React from 'react';
import {useContext, useEffect, useRef} from 'react';
-import {BridgeContext, StoreContext} from '../context';
+import {BridgeContext} from '../context';
import {TreeDispatcherContext} from '../Components/TreeContext';
import {useHighlightHostInstance, useScrollToHostInstance} from '../hooks';
import {
@@ -23,20 +23,15 @@ import ButtonIcon from '../ButtonIcon';
function SuspenseTimelineInput() {
const bridge = useContext(BridgeContext);
- const store = useContext(StoreContext);
const treeDispatch = useContext(TreeDispatcherContext);
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
const {highlightHostInstance, clearHighlightHostInstance} =
useHighlightHostInstance();
const scrollToHostInstance = useScrollToHostInstance();
- const {
- selectedRootID: rootID,
- timeline,
- timelineIndex,
- hoveredTimelineIndex,
- playing,
- } = useContext(SuspenseTreeStateContext);
+ const {timeline, timelineIndex, hoveredTimelineIndex, playing} = useContext(
+ SuspenseTreeStateContext,
+ );
const min = 0;
const max = timeline.length > 0 ? timeline.length - 1 : 0;
@@ -112,24 +107,12 @@ function SuspenseTimelineInput() {
// For now we just exclude it from deps since we don't lint those anyway.
function changeTimelineIndex(newIndex: number) {
// Synchronize timeline index with what is resuspended.
- if (rootID === null) {
- return;
- }
- const rendererID = store.getRendererIDForElement(rootID);
- if (rendererID === null) {
- console.error(
- `No renderer ID found for root element ${rootID} in suspense timeline.`,
- );
- return;
- }
// We suspend everything after the current selection. The root isn't showing
// anything suspended in the root. The step after that should have one less
// thing suspended. I.e. the first suspense boundary should be unsuspended
// when it's selected. This also lets you show everything in the last step.
const suspendedSet = timeline.slice(timelineIndex + 1);
bridge.send('overrideSuspenseMilestone', {
- rendererID,
- rootID,
suspendedSet,
});
if (isInitialMount.current) {
@@ -164,20 +147,6 @@ function SuspenseTimelineInput() {
};
}, [playing]);
- if (rootID === null) {
- return (
-