diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 8b2be7f8..f3015a40 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -13,6 +13,8 @@ body:
required: false
- label: "`eslint-scope`"
required: false
+ - label: "`eslint-visitor-keys`"
+ required: false
- type: textarea
attributes:
label: Environment
diff --git a/.github/ISSUE_TEMPLATE/change.yml b/.github/ISSUE_TEMPLATE/change.yml
index fb658fa9..3ad2bd5b 100644
--- a/.github/ISSUE_TEMPLATE/change.yml
+++ b/.github/ISSUE_TEMPLATE/change.yml
@@ -12,6 +12,8 @@ body:
required: false
- label: "`eslint-scope`"
required: false
+ - label: "`eslint-visitor-keys`"
+ required: false
- type: textarea
attributes:
label: What problem do you want to solve?
diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml
index a5f6954f..45aef633 100644
--- a/.github/ISSUE_TEMPLATE/docs.yml
+++ b/.github/ISSUE_TEMPLATE/docs.yml
@@ -12,6 +12,8 @@ body:
required: false
- label: "`eslint-scope`"
required: false
+ - label: "`eslint-visitor-keys`"
+ required: false
- type: textarea
attributes:
label: What documentation issue do you want to solve?
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index 518537bb..fa6bf7af 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -46,6 +46,32 @@ jobs:
# fewest internal dependencies are released first and the packages with the
# most internal dependencies are released last.
#-----------------------------------------------------------------------------
+
+ #-----------------------------------------------------------------------------
+ # eslint-visitor-keys
+ #-----------------------------------------------------------------------------
+
+ - name: Publish eslint-visitor-keys package to npm
+ run: npm publish -w packages/eslint-visitor-keys --provenance
+ if: ${{ steps.release.outputs['packages/eslint-visitor-keys--release_created'] }}
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+
+ - name: Tweet Release Announcement
+ run: npx @humanwhocodes/tweet "eslint-visitor-keys v${{ steps.release.outputs['packages/eslint-visitor-keys--major'] }}.${{ steps.release.outputs['packages/eslint-visitor-keys--minor'] }}.${{ steps.release.outputs['packages/eslint-visitor-keys--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/eslint-visitor-keys--tag_name'] }}"
+ if: ${{ steps.release.outputs['packages/eslint-visitor-keys--release_created'] }}
+ env:
+ TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }}
+ TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }}
+ TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }}
+ TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
+
+ - name: Toot Release Announcement
+ run: npx @humanwhocodes/toot "eslint-visitor-keys v${{ steps.release.outputs['packages/eslint-visitor-keys--major'] }}.${{ steps.release.outputs['packages/eslint-visitor-keys--minor'] }}.${{ steps.release.outputs['packages/eslint-visitor-keys--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/eslint-visitor-keys--tag_name'] }}"
+ if: ${{ steps.release.outputs['packages/eslint-visitor-keys--release_created'] }}
+ env:
+ MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
+ MASTODON_HOST: ${{ secrets.MASTODON_HOST }}
#-----------------------------------------------------------------------------
# espree
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 629fd483..f5d4e6f3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,4 +1,5 @@
{
"packages/espree": "10.1.0",
- "packages/eslint-scope": "8.0.2"
+ "packages/eslint-scope": "8.0.2",
+ "packages/eslint-visitor-keys": "4.0.0"
}
diff --git a/README.md b/README.md
index fef29aee..3d45d23b 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ This repository is the home of the following packages:
- [`espree`](packages/espree)
- [`eslint-scope`](packages/eslint-scope)
+- [`eslint-visitor-keys`](packages/eslint-visitor-keys)
## Security Policy
diff --git a/eslint.config.js b/eslint.config.js
index 63018ca0..9cc3bc37 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -15,7 +15,7 @@ export default [
...eslintConfigESLint,
eslintConfigESLintFormatting,
{
- files: ["packages/espree/tests/lib/**"],
+ files: ["packages/*/tests/lib/**"],
languageOptions: {
globals: {
...globals.mocha
@@ -55,6 +55,20 @@ export default [
"no-console": "off"
}
},
+ {
+ files: ["packages/eslint-scope/**"],
+ linterOptions: {
+ reportUnusedDisableDirectives: "error"
+ },
+ settings: {
+ jsdoc: {
+ preferredTypes: {
+ Object: "object",
+ "object<>": "Object"
+ }
+ }
+ }
+ },
{
files: ["**/tools/**"],
rules: {
diff --git a/packages/eslint-visitor-keys/CHANGELOG.md b/packages/eslint-visitor-keys/CHANGELOG.md
new file mode 100644
index 00000000..a150f8aa
--- /dev/null
+++ b/packages/eslint-visitor-keys/CHANGELOG.md
@@ -0,0 +1,124 @@
+# Changelog
+
+## [4.0.0](https://github.com/eslint/eslint-visitor-keys/compare/v3.4.3...v4.0.0) (2024-02-08)
+
+
+### ⚠ BREAKING CHANGES
+
+* Require Node.js `^18.18.0 || ^20.9.0 || >=21.1.0` ([#63](https://github.com/eslint/eslint-visitor-keys/issues/63))
+
+### Features
+
+* Require Node.js `^18.18.0 || ^20.9.0 || >=21.1.0` ([#63](https://github.com/eslint/eslint-visitor-keys/issues/63)) ([388b2ac](https://github.com/eslint/eslint-visitor-keys/commit/388b2acedbe3881edd52b45f217db393731feb48))
+
+
+### Chores
+
+* run tests in Node.js 21 ([#61](https://github.com/eslint/eslint-visitor-keys/issues/61)) ([b23bfb7](https://github.com/eslint/eslint-visitor-keys/commit/b23bfb7f938d6559dfff8f02203c866a2fb26618))
+
+## [3.4.3](https://github.com/eslint/eslint-visitor-keys/compare/v3.4.2...v3.4.3) (2023-08-08)
+
+
+### Chores
+
+* Add back add-to-triage ([#59](https://github.com/eslint/eslint-visitor-keys/issues/59)) ([5ea8b12](https://github.com/eslint/eslint-visitor-keys/commit/5ea8b120d73f1dd6db92427d025c6805df43397d))
+* Remove add-to-triage ([#56](https://github.com/eslint/eslint-visitor-keys/issues/56)) ([45d4c17](https://github.com/eslint/eslint-visitor-keys/commit/45d4c17b63d26ef486c92cfb60283991e36d6db0))
+* standardize npm script names ([#55](https://github.com/eslint/eslint-visitor-keys/issues/55)) ([0461695](https://github.com/eslint/eslint-visitor-keys/commit/0461695b730821c04c20d46f5cff9195509f865b))
+* update `typedef` in build keys template ([#58](https://github.com/eslint/eslint-visitor-keys/issues/58)) ([eb6c66d](https://github.com/eslint/eslint-visitor-keys/commit/eb6c66dbaf6389d253d10dd74d22915d7e33d651))
+
+## [3.4.2](https://github.com/eslint/eslint-visitor-keys/compare/v3.4.1...v3.4.2) (2023-07-27)
+
+
+### Documentation
+
+* fix spelling mistakes ([#50](https://github.com/eslint/eslint-visitor-keys/issues/50)) ([4ce1c17](https://github.com/eslint/eslint-visitor-keys/commit/4ce1c1777181b87f5dcd3f10a3d8aef0710f8d0e))
+* remove `release` script reference from README ([#52](https://github.com/eslint/eslint-visitor-keys/issues/52)) ([4640146](https://github.com/eslint/eslint-visitor-keys/commit/46401465ff5bb08bf793219d399c11434fd163be))
+
+
+### Chores
+
+* Add PRs to triage ([#54](https://github.com/eslint/eslint-visitor-keys/issues/54)) ([a7b64b4](https://github.com/eslint/eslint-visitor-keys/commit/a7b64b4ea0a4548f92cb41428d3e23b30f0cf8de))
+* generate provenance statements when release ([#53](https://github.com/eslint/eslint-visitor-keys/issues/53)) ([7b09698](https://github.com/eslint/eslint-visitor-keys/commit/7b09698fa51bbd9fcace50cb1014eec87abde140))
+
+## [3.4.1](https://github.com/eslint/eslint-visitor-keys/compare/v3.4.0...v3.4.1) (2023-05-05)
+
+
+### Bug Fixes
+
+* correct types for node16 resolution ([#47](https://github.com/eslint/eslint-visitor-keys/issues/47)) ([7bd1fc1](https://github.com/eslint/eslint-visitor-keys/commit/7bd1fc1d483c2d0fdd5e0eddb2702f177372889c))
+
+
+### Chores
+
+* run tests on Node.js v20 ([#45](https://github.com/eslint/eslint-visitor-keys/issues/45)) ([c982093](https://github.com/eslint/eslint-visitor-keys/commit/c982093329f12c02dc87569930a6042f4095026b))
+* set up release-please ([#48](https://github.com/eslint/eslint-visitor-keys/issues/48)) ([a7fb4c7](https://github.com/eslint/eslint-visitor-keys/commit/a7fb4c7eb5d122e89bc6c24779ea06c487242c87))
+
+v3.4.0 - March 27, 2023
+
+* [`e9a070f`](https://github.com/eslint/eslint-visitor-keys/commit/e9a070fcbf53c14374e17801799016ce21d0c0ff) fix: remove useless sourcemap url (fixes #43) (#44) (余腾靖)
+* [`0398109`](https://github.com/eslint/eslint-visitor-keys/commit/0398109f1f751c58be3bd3206d22ae9c1b269219) chore: add triage action (#42) (Milos Djermanovic)
+* [`bcffbe5`](https://github.com/eslint/eslint-visitor-keys/commit/bcffbe52989bf726475c6b86eba3003275317f45) ci: add Node v19 (#41) (Milos Djermanovic)
+* [`c24f2e4`](https://github.com/eslint/eslint-visitor-keys/commit/c24f2e45cc59dbdeb8c2b48782d3599fbef9cbcb) chore: update github actions and add funding field (#40) (Deepshika S)
+* [`81c0732`](https://github.com/eslint/eslint-visitor-keys/commit/81c0732aa4086ad75f0adf4512823e4c8c584493) build: add node v18 (#39) (唯然)
+* [`6ece4bd`](https://github.com/eslint/eslint-visitor-keys/commit/6ece4bd4086965bdaf92d95b6a03d8d122468b4e) feat: add `JSXSpreadChild` and tool to build keys out of AST definitions (#36) (Brett Zamir)
+* [`4beb7a7`](https://github.com/eslint/eslint-visitor-keys/commit/4beb7a7be5fd7d25e5572c3dfee3e127edd8cadb) docs: update badges (#37) (Milos Djermanovic)
+
+v3.3.0 - February 11, 2022
+
+* [`7f10327`](https://github.com/eslint/eslint-visitor-keys/commit/7f103276844fb131cfad115ee78eb19f798d5fc8) feat: Bundle JSDoc-built TypeScript declaration file (#34) (Brett Zamir)
+
+v3.2.0 - January 15, 2022
+
+* [`5c53218`](https://github.com/eslint/eslint-visitor-keys/commit/5c532184e05440d3c883b3d7864f84eb1b11dc90) feat: add missing JSXOpeningFragment and JSXClosingFragment (#33) (Richard Huang)
+* [`2a5c9a6`](https://github.com/eslint/eslint-visitor-keys/commit/2a5c9a622d8cb09df9d40a320d146b0941081e11) docs: readme add syntax highlighting (#32) (coderaiser)
+
+v3.1.0 - November 8, 2021
+
+* [`5e3e687`](https://github.com/eslint/eslint-visitor-keys/commit/5e3e68779560a1b2edef7923d30165396bce9602) build: upgrade eslint-release to v3.2.0 to support conventional commits (#31) (Milos Djermanovic)
+* [`53d3939`](https://github.com/eslint/eslint-visitor-keys/commit/53d39390d3560c179cffd08638b50343b0841a30) Build: add node v17 (#30) (唯然)
+* [`5f5b232`](https://github.com/eslint/eslint-visitor-keys/commit/5f5b232386bd7e217dd61d08aa27c3a1e2a4665e) Update: add StaticBlock (#29) (Milos Djermanovic)
+* [`e89bff9`](https://github.com/eslint/eslint-visitor-keys/commit/e89bff9fd6a5929b1e8f4d5f9cedec45aa966074) Chore: use actions/setup-node@v2 (薛定谔的猫)
+* [`7b756cd`](https://github.com/eslint/eslint-visitor-keys/commit/7b756cd37cd28089dfee6015c001fd860e21aead) Docs: Update the minimum Node.js version requirement (#26) (0uep)
+
+v3.0.0 - June 24, 2021
+
+* [`701b67d`](https://github.com/eslint/eslint-visitor-keys/commit/701b67de7216cabebc03e7c6205fe47ce3177aa3) Breaking: drop node v10/v13/v15 (refs eslint/eslint#14023) (#23) (薛定谔的猫)
+* [`f584b12`](https://github.com/eslint/eslint-visitor-keys/commit/f584b121421ceb6c4e034b79943f3c32aaa0541d) Breaking: Switch to ESM (#24) (Brett Zamir)
+* [`7279e73`](https://github.com/eslint/eslint-visitor-keys/commit/7279e7304e95030a854408191b8fde3c01876451) Build: Update branch reference in CI (#25) (Milos Djermanovic)
+* [`c6846d6`](https://github.com/eslint/eslint-visitor-keys/commit/c6846d69271c73041b797b7de9c8254dcf439a2e) Upgrade: eslint-release (#21) (Nicholas C. Zakas)
+
+v2.1.0 - May 3, 2021
+
+* [`908fdf8`](https://github.com/eslint/eslint-visitor-keys/commit/908fdf8c0d9a352c696c8c1f4901280d1a0795f7) Update: add PrivateIdentifier and PropertyDefinition (#20) (Toru Nagashima)
+* [`2d7be11`](https://github.com/eslint/eslint-visitor-keys/commit/2d7be11e4d13ac702c9fe3c529cadbd75b370146) Chore: No longer test in Node.js 13 (#17) (Michaël De Boey)
+* [`b41b509`](https://github.com/eslint/eslint-visitor-keys/commit/b41b509b153ecd8d47af46a421122f64e93d4c67) Docs: Update required Node.js version (#15) (Michaël De Boey)
+
+v2.0.0 - August 14, 2020
+
+* [`fb86ca3`](https://github.com/eslint/eslint-visitor-keys/commit/fb86ca315daafc84e23ed9005db40b0892b972a6) Breaking: drop support for Node <10 (#13) (Kai Cataldo)
+* [`69383b3`](https://github.com/eslint/eslint-visitor-keys/commit/69383b372915e33ada094880ecc6b6e8f8c7ca4e) Chore: move to GitHub Actions (#14) (Kai Cataldo)
+
+v1.3.0 - June 19, 2020
+
+* [`c92dd7f`](https://github.com/eslint/eslint-visitor-keys/commit/c92dd7ff96f0044dba12d681406a025b92b4c437) Update: add `ChainExpression` node (#12) (Toru Nagashima)
+
+v1.2.0 - June 4, 2020
+
+* [`21f28bf`](https://github.com/eslint/eslint-visitor-keys/commit/21f28bf11be5329d740a8bf6bdbcd0ef13bbf1a2) Update: added exported in exportAllDeclaration key (#10) (Anix)
+
+v1.1.0 - August 13, 2019
+
+* [`9331cc0`](https://github.com/eslint/eslint-visitor-keys/commit/9331cc09e756e65b9044c9186445a474b037fac6) Update: add ImportExpression (#8) (Toru Nagashima)
+* [`5967f58`](https://github.com/eslint/eslint-visitor-keys/commit/5967f583b04f17fba9226aaa394e45d476d2b8af) Chore: add supported Node.js versions to CI (#7) (Kai Cataldo)
+* [`6f7c60f`](https://github.com/eslint/eslint-visitor-keys/commit/6f7c60fef2ceec9f6323202df718321cec45cab0) Upgrade: eslint-release@1.0.0 (#5) (Teddy Katz)
+
+v1.0.0 - December 18, 2017
+
+* 1f6bd38 Breaking: update keys (#4) (Toru Nagashima)
+
+v0.1.0 - November 17, 2017
+
+* 17b4a88 Chore: update `repository` field in package.json (#3) (Toru Nagashima)
+* a5a026b New: eslint-visitor-keys (#1) (Toru Nagashima)
+* a1a48b8 Update: Change license to Apache 2 (#2) (Ilya Volodin)
+* 2204715 Initial commit (Toru Nagashima)
diff --git a/packages/eslint-visitor-keys/LICENSE b/packages/eslint-visitor-keys/LICENSE
new file mode 100644
index 00000000..17a25538
--- /dev/null
+++ b/packages/eslint-visitor-keys/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright contributors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/eslint-visitor-keys/README.md b/packages/eslint-visitor-keys/README.md
new file mode 100644
index 00000000..fbbfeefd
--- /dev/null
+++ b/packages/eslint-visitor-keys/README.md
@@ -0,0 +1,123 @@
+# eslint-visitor-keys
+
+[![npm version](https://img.shields.io/npm/v/eslint-visitor-keys.svg)](https://www.npmjs.com/package/eslint-visitor-keys)
+[![Downloads/month](https://img.shields.io/npm/dm/eslint-visitor-keys.svg)](http://www.npmtrends.com/eslint-visitor-keys)
+[![Build Status](https://github.com/eslint/js/workflows/CI/badge.svg)](https://github.com/eslint/js/actions)
+
+Constants and utilities about visitor keys to traverse AST.
+
+## 💿 Installation
+
+Use [npm] to install.
+
+```bash
+$ npm install eslint-visitor-keys
+```
+
+### Requirements
+
+- [Node.js] `^18.18.0`, `^20.9.0`, or `>=21.1.0`
+
+
+## 📖 Usage
+
+To use in an ESM file:
+
+```js
+import * as evk from "eslint-visitor-keys"
+```
+
+To use in a CommonJS file:
+
+```js
+const evk = require("eslint-visitor-keys")
+```
+
+### evk.KEYS
+
+> type: `{ [type: string]: string[] | undefined }`
+
+Visitor keys. This keys are frozen.
+
+This is an object. Keys are the type of [ESTree] nodes. Their values are an array of property names which have child nodes.
+
+For example:
+
+```
+console.log(evk.KEYS.AssignmentExpression) // → ["left", "right"]
+```
+
+### evk.getKeys(node)
+
+> type: `(node: object) => string[]`
+
+Get the visitor keys of a given AST node.
+
+This is similar to `Object.keys(node)` of ES Standard, but some keys are excluded: `parent`, `leadingComments`, `trailingComments`, and names which start with `_`.
+
+This will be used to traverse unknown nodes.
+
+For example:
+
+```js
+const node = {
+ type: "AssignmentExpression",
+ left: { type: "Identifier", name: "foo" },
+ right: { type: "Literal", value: 0 }
+}
+console.log(evk.getKeys(node)) // → ["type", "left", "right"]
+```
+
+### evk.unionWith(additionalKeys)
+
+> type: `(additionalKeys: object) => { [type: string]: string[] | undefined }`
+
+Make the union set with `evk.KEYS` and the given keys.
+
+- The order of keys is, `additionalKeys` is at first, then `evk.KEYS` is concatenated after that.
+- It removes duplicated keys as keeping the first one.
+
+For example:
+
+```js
+console.log(evk.unionWith({
+ MethodDefinition: ["decorators"]
+})) // → { ..., MethodDefinition: ["decorators", "key", "value"], ... }
+```
+
+## 📰 Change log
+
+See [GitHub releases](https://github.com/eslint/eslint-visitor-keys/releases).
+
+## 🍻 Contributing
+
+Welcome. See [ESLint contribution guidelines](https://eslint.org/docs/developer-guide/contributing/).
+
+### Development commands
+
+- `npm test` runs tests and measures code coverage.
+- `npm run lint` checks source codes with ESLint.
+- `npm run test:open-coverage` opens the code coverage report of the previous test with your default browser.
+
+
+[npm]: https://www.npmjs.com/
+[Node.js]: https://nodejs.org/
+[ESTree]: https://github.com/estree/estree
+
+## Sponsors
+
+The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate) to get your logo on our README and website.
+
+
+
+
Platinum Sponsors
+
Gold Sponsors
+
Silver Sponsors
+
Bronze Sponsors
+
+
+
+
+Technology Sponsors
+
+
diff --git a/packages/eslint-visitor-keys/lib/index.js b/packages/eslint-visitor-keys/lib/index.js
new file mode 100644
index 00000000..1fc89b43
--- /dev/null
+++ b/packages/eslint-visitor-keys/lib/index.js
@@ -0,0 +1,67 @@
+/**
+ * @author Toru Nagashima
+ * See LICENSE file in root directory for full license.
+ */
+import KEYS from "./visitor-keys.js";
+
+/**
+ * @typedef {import('./visitor-keys.js').VisitorKeys} VisitorKeys
+ */
+
+// List to ignore keys.
+const KEY_BLACKLIST = new Set([
+ "parent",
+ "leadingComments",
+ "trailingComments"
+]);
+
+/**
+ * Check whether a given key should be used or not.
+ * @param {string} key The key to check.
+ * @returns {boolean} `true` if the key should be used.
+ */
+function filterKey(key) {
+ return !KEY_BLACKLIST.has(key) && key[0] !== "_";
+}
+
+
+/* eslint-disable jsdoc/valid-types -- doesn't allow `readonly`.
+ TODO: remove eslint-disable when https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/164 is fixed
+*/
+/**
+ * Get visitor keys of a given node.
+ * @param {Object} node The AST node to get keys.
+ * @returns {readonly string[]} Visitor keys of the node.
+ */
+export function getKeys(node) {
+ return Object.keys(node).filter(filterKey);
+}
+/* eslint-enable jsdoc/valid-types -- doesn't allow `readonly` */
+
+/**
+ * Make the union set with `KEYS` and given keys.
+ * @param {VisitorKeys} additionalKeys The additional keys.
+ * @returns {VisitorKeys} The union set.
+ */
+export function unionWith(additionalKeys) {
+ const retv = /** @type {{ [type: string]: ReadonlyArray }} */
+ (Object.assign({}, KEYS));
+
+ for (const type of Object.keys(additionalKeys)) {
+ if (Object.hasOwn(retv, type)) {
+ const keys = new Set(additionalKeys[type]);
+
+ for (const key of retv[type]) {
+ keys.add(key);
+ }
+
+ retv[type] = Object.freeze(Array.from(keys));
+ } else {
+ retv[type] = Object.freeze(Array.from(additionalKeys[type]));
+ }
+ }
+
+ return Object.freeze(retv);
+}
+
+export { KEYS };
diff --git a/packages/eslint-visitor-keys/lib/visitor-keys.js b/packages/eslint-visitor-keys/lib/visitor-keys.js
new file mode 100644
index 00000000..f54a3474
--- /dev/null
+++ b/packages/eslint-visitor-keys/lib/visitor-keys.js
@@ -0,0 +1,319 @@
+/* eslint-disable jsdoc/valid-types -- doesn't allow `readonly`.
+ TODO: remove eslint-disable when https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/164 is fixed
+*/
+/**
+ * @typedef {{ readonly [type: string]: ReadonlyArray }} VisitorKeys
+ */
+/* eslint-enable jsdoc/valid-types -- doesn't allow `readonly string[]`. TODO: check why */
+
+/**
+ * @type {VisitorKeys}
+ */
+const KEYS = {
+ ArrayExpression: [
+ "elements"
+ ],
+ ArrayPattern: [
+ "elements"
+ ],
+ ArrowFunctionExpression: [
+ "params",
+ "body"
+ ],
+ AssignmentExpression: [
+ "left",
+ "right"
+ ],
+ AssignmentPattern: [
+ "left",
+ "right"
+ ],
+ AwaitExpression: [
+ "argument"
+ ],
+ BinaryExpression: [
+ "left",
+ "right"
+ ],
+ BlockStatement: [
+ "body"
+ ],
+ BreakStatement: [
+ "label"
+ ],
+ CallExpression: [
+ "callee",
+ "arguments"
+ ],
+ CatchClause: [
+ "param",
+ "body"
+ ],
+ ChainExpression: [
+ "expression"
+ ],
+ ClassBody: [
+ "body"
+ ],
+ ClassDeclaration: [
+ "id",
+ "superClass",
+ "body"
+ ],
+ ClassExpression: [
+ "id",
+ "superClass",
+ "body"
+ ],
+ ConditionalExpression: [
+ "test",
+ "consequent",
+ "alternate"
+ ],
+ ContinueStatement: [
+ "label"
+ ],
+ DebuggerStatement: [],
+ DoWhileStatement: [
+ "body",
+ "test"
+ ],
+ EmptyStatement: [],
+ ExperimentalRestProperty: [
+ "argument"
+ ],
+ ExperimentalSpreadProperty: [
+ "argument"
+ ],
+ ExportAllDeclaration: [
+ "exported",
+ "source"
+ ],
+ ExportDefaultDeclaration: [
+ "declaration"
+ ],
+ ExportNamedDeclaration: [
+ "declaration",
+ "specifiers",
+ "source"
+ ],
+ ExportSpecifier: [
+ "exported",
+ "local"
+ ],
+ ExpressionStatement: [
+ "expression"
+ ],
+ ForInStatement: [
+ "left",
+ "right",
+ "body"
+ ],
+ ForOfStatement: [
+ "left",
+ "right",
+ "body"
+ ],
+ ForStatement: [
+ "init",
+ "test",
+ "update",
+ "body"
+ ],
+ FunctionDeclaration: [
+ "id",
+ "params",
+ "body"
+ ],
+ FunctionExpression: [
+ "id",
+ "params",
+ "body"
+ ],
+ Identifier: [],
+ IfStatement: [
+ "test",
+ "consequent",
+ "alternate"
+ ],
+ ImportDeclaration: [
+ "specifiers",
+ "source"
+ ],
+ ImportDefaultSpecifier: [
+ "local"
+ ],
+ ImportExpression: [
+ "source"
+ ],
+ ImportNamespaceSpecifier: [
+ "local"
+ ],
+ ImportSpecifier: [
+ "imported",
+ "local"
+ ],
+ JSXAttribute: [
+ "name",
+ "value"
+ ],
+ JSXClosingElement: [
+ "name"
+ ],
+ JSXClosingFragment: [],
+ JSXElement: [
+ "openingElement",
+ "children",
+ "closingElement"
+ ],
+ JSXEmptyExpression: [],
+ JSXExpressionContainer: [
+ "expression"
+ ],
+ JSXFragment: [
+ "openingFragment",
+ "children",
+ "closingFragment"
+ ],
+ JSXIdentifier: [],
+ JSXMemberExpression: [
+ "object",
+ "property"
+ ],
+ JSXNamespacedName: [
+ "namespace",
+ "name"
+ ],
+ JSXOpeningElement: [
+ "name",
+ "attributes"
+ ],
+ JSXOpeningFragment: [],
+ JSXSpreadAttribute: [
+ "argument"
+ ],
+ JSXSpreadChild: [
+ "expression"
+ ],
+ JSXText: [],
+ LabeledStatement: [
+ "label",
+ "body"
+ ],
+ Literal: [],
+ LogicalExpression: [
+ "left",
+ "right"
+ ],
+ MemberExpression: [
+ "object",
+ "property"
+ ],
+ MetaProperty: [
+ "meta",
+ "property"
+ ],
+ MethodDefinition: [
+ "key",
+ "value"
+ ],
+ NewExpression: [
+ "callee",
+ "arguments"
+ ],
+ ObjectExpression: [
+ "properties"
+ ],
+ ObjectPattern: [
+ "properties"
+ ],
+ PrivateIdentifier: [],
+ Program: [
+ "body"
+ ],
+ Property: [
+ "key",
+ "value"
+ ],
+ PropertyDefinition: [
+ "key",
+ "value"
+ ],
+ RestElement: [
+ "argument"
+ ],
+ ReturnStatement: [
+ "argument"
+ ],
+ SequenceExpression: [
+ "expressions"
+ ],
+ SpreadElement: [
+ "argument"
+ ],
+ StaticBlock: [
+ "body"
+ ],
+ Super: [],
+ SwitchCase: [
+ "test",
+ "consequent"
+ ],
+ SwitchStatement: [
+ "discriminant",
+ "cases"
+ ],
+ TaggedTemplateExpression: [
+ "tag",
+ "quasi"
+ ],
+ TemplateElement: [],
+ TemplateLiteral: [
+ "quasis",
+ "expressions"
+ ],
+ ThisExpression: [],
+ ThrowStatement: [
+ "argument"
+ ],
+ TryStatement: [
+ "block",
+ "handler",
+ "finalizer"
+ ],
+ UnaryExpression: [
+ "argument"
+ ],
+ UpdateExpression: [
+ "argument"
+ ],
+ VariableDeclaration: [
+ "declarations"
+ ],
+ VariableDeclarator: [
+ "id",
+ "init"
+ ],
+ WhileStatement: [
+ "test",
+ "body"
+ ],
+ WithStatement: [
+ "object",
+ "body"
+ ],
+ YieldExpression: [
+ "argument"
+ ]
+};
+
+// Types.
+const NODE_TYPES = Object.keys(KEYS);
+
+// Freeze the keys.
+for (const type of NODE_TYPES) {
+ Object.freeze(KEYS[type]);
+}
+Object.freeze(KEYS);
+
+export default KEYS;
diff --git a/packages/eslint-visitor-keys/package.json b/packages/eslint-visitor-keys/package.json
new file mode 100644
index 00000000..316c29e4
--- /dev/null
+++ b/packages/eslint-visitor-keys/package.json
@@ -0,0 +1,69 @@
+{
+ "name": "eslint-visitor-keys",
+ "version": "4.0.0",
+ "description": "Constants and utilities about visitor keys to traverse AST.",
+ "type": "module",
+ "main": "dist/eslint-visitor-keys.cjs",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": [
+ {
+ "import": "./lib/index.js",
+ "require": "./dist/eslint-visitor-keys.cjs"
+ },
+ "./dist/eslint-visitor-keys.cjs"
+ ],
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "dist/index.d.ts",
+ "dist/visitor-keys.d.ts",
+ "dist/eslint-visitor-keys.cjs",
+ "dist/eslint-visitor-keys.d.cts",
+ "lib"
+ ],
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "devDependencies": {
+ "@types/estree": "^0.0.51",
+ "@types/estree-jsx": "^0.0.1",
+ "@typescript-eslint/parser": "^5.14.0",
+ "c8": "^7.11.0",
+ "chai": "^4.3.6",
+ "eslint-release": "^3.2.0",
+ "esquery": "^1.4.0",
+ "json-diff": "^0.7.3",
+ "mocha": "^9.2.1",
+ "opener": "^1.5.2",
+ "rollup": "^2.70.0",
+ "rollup-plugin-dts": "^4.2.3",
+ "tsd": "^0.19.1",
+ "typescript": "^4.9.5"
+ },
+ "scripts": {
+ "build": "npm run build:cjs && npm run build:types",
+ "build:cjs": "rollup -c",
+ "build:debug": "npm run build:cjs -- -m && npm run build:types",
+ "build:keys": "node tools/build-keys-from-ts",
+ "build:types": "tsc",
+ "prepare": "npm run build",
+ "release:generate:latest": "eslint-generate-release",
+ "release:generate:alpha": "eslint-generate-prerelease alpha",
+ "release:generate:beta": "eslint-generate-prerelease beta",
+ "release:generate:rc": "eslint-generate-prerelease rc",
+ "release:publish": "eslint-publish-release",
+ "test": "mocha tests/lib/**/*.cjs && c8 mocha tests/lib/**/*.js && npm run test:types",
+ "test:open-coverage": "c8 report --reporter lcov && opener coverage/lcov-report/index.html",
+ "test:types": "tsd"
+ },
+ "repository": "eslint/js",
+ "funding": "https://opencollective.com/eslint",
+ "keywords": [],
+ "author": "Toru Nagashima (https://github.com/mysticatea)",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/eslint/js/issues"
+ },
+ "homepage": "https://github.com/eslint/js/blob/main/packages/eslint-visitor-keys/README.md"
+}
diff --git a/packages/eslint-visitor-keys/rollup.config.js b/packages/eslint-visitor-keys/rollup.config.js
new file mode 100644
index 00000000..9f627c9d
--- /dev/null
+++ b/packages/eslint-visitor-keys/rollup.config.js
@@ -0,0 +1,21 @@
+import dts from "rollup-plugin-dts";
+
+export default [
+ {
+ input: "./lib/index.js",
+ treeshake: false,
+ output: {
+ format: "cjs",
+ file: "dist/eslint-visitor-keys.cjs"
+ }
+ },
+ {
+ plugins: [dts()],
+ input: "./lib/index.js",
+ treeshake: false,
+ output: {
+ format: "cjs",
+ file: "dist/eslint-visitor-keys.d.cts"
+ }
+ }
+];
diff --git a/packages/eslint-visitor-keys/test-d/index.test-d.ts b/packages/eslint-visitor-keys/test-d/index.test-d.ts
new file mode 100644
index 00000000..8ff69368
--- /dev/null
+++ b/packages/eslint-visitor-keys/test-d/index.test-d.ts
@@ -0,0 +1,54 @@
+import { expectType, expectAssignable, expectError } from 'tsd';
+
+import { KEYS, getKeys, unionWith, VisitorKeys } from "../";
+
+const assignmentExpression = {
+ type: "AssignmentExpression",
+ operator: "=",
+ left: {
+ type: "Identifier",
+ name: "a",
+ range: [
+ 0,
+ 1
+ ]
+ },
+ right: {
+ type: "Literal",
+ value: 5,
+ raw: "5",
+ range: [
+ 4,
+ 5
+ ]
+ },
+ range: [
+ 0,
+ 5
+ ]
+};
+
+expectType<{readonly [type: string]: readonly string[]}>(KEYS);
+
+expectType(getKeys(assignmentExpression));
+
+expectType<{readonly [type: string]: readonly string[]}>(unionWith({
+ TestInterface1: ["left", "right"],
+ TestInterface2: ["expression"]
+}));
+
+const readonlyKeys: {
+ readonly [type: string]: readonly string[]
+} = {
+ TestInterface1: ["left", "right"]
+};
+
+expectAssignable(readonlyKeys);
+
+// https://github.com/SamVerschueren/tsd/issues/143
+// expectError(() => {
+// const erring: VisitorKeys = {
+// TestInterface1: ["left", "right"]
+// };
+// erring.TestInterface1 = ["badAttemptOverwrite"];
+// });
diff --git a/packages/eslint-visitor-keys/tests/lib/commonjs.cjs b/packages/eslint-visitor-keys/tests/lib/commonjs.cjs
new file mode 100644
index 00000000..0e1d0d0b
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/commonjs.cjs
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview Tests for checking that the commonjs entry points are still accessible
+ * @author Mike Reinstein
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("node:assert");
+const eslintVisitorKeys = require("../../dist/eslint-visitor-keys.cjs");
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+describe("commonjs", () => {
+ it("is an object", () => {
+ assert.strictEqual(typeof eslintVisitorKeys, "object");
+ });
+
+ it("has exported keys object", () => {
+ assert.strictEqual(typeof eslintVisitorKeys.KEYS, "object");
+ });
+
+ it("has key array with AST type", () => {
+ assert.ok(Array.isArray(eslintVisitorKeys.KEYS.ArrayExpression));
+ });
+
+ it("has getKeys function", () => {
+ assert.strictEqual(typeof eslintVisitorKeys.getKeys, "function");
+ });
+
+ it("should have getKeys which returns keys", () => {
+ assert.deepStrictEqual(eslintVisitorKeys.getKeys({ a: 1, b: 2 }), ["a", "b"]);
+ });
+
+ it("has unionWith function", () => {
+ assert.strictEqual(typeof eslintVisitorKeys.unionWith, "function");
+ });
+
+ it("should have unionWith which includes all additional keys", () => {
+ const additionalKeys = { Program: ["body", "a"], AssignmentExpression: ["b"], additional: ["c"], MethodDefinition: ["a", "key", "b"] };
+ const unionKeys = eslintVisitorKeys.unionWith(additionalKeys);
+
+ for (const type of Object.keys(additionalKeys)) {
+ for (const key of additionalKeys[type]) {
+ assert(unionKeys[type].includes(key), `'${key}' should be included in '${type}'.`);
+ }
+ }
+ });
+});
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/bad-extends-type-reference.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-extends-type-reference.d.ts
new file mode 100644
index 00000000..1457d679
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-extends-type-reference.d.ts
@@ -0,0 +1,3 @@
+export interface Something extends BadSomething {
+ type: "Something";
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-parameters.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-parameters.d.ts
new file mode 100644
index 00000000..0095c9c2
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-parameters.d.ts
@@ -0,0 +1,5 @@
+export interface Statement {}
+
+export interface StaticBlock extends BadTypeParam {
+ type: "StaticBlock";
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-reference.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-reference.d.ts
new file mode 100644
index 00000000..9c226f18
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-reference.d.ts
@@ -0,0 +1,3 @@
+export interface StaticBlock extends Omit {
+ type: "StaticBlock";
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-value.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-value.d.ts
new file mode 100644
index 00000000..973aa2bb
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type-value.d.ts
@@ -0,0 +1,8 @@
+interface BadExpression {
+ type: undefined;
+}
+
+export interface NewFangledExpression {
+ type: "NewFangledExpression";
+ right: BadExpression;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type.d.ts
new file mode 100644
index 00000000..ce4b9830
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/bad-type.d.ts
@@ -0,0 +1,4 @@
+export interface SomeExpression {
+ type: "SomeExpression";
+ someProperty: any;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-bad.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-bad.d.ts
new file mode 100644
index 00000000..99389eac
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-bad.d.ts
@@ -0,0 +1,4 @@
+export interface NewFangledExpression {
+ type: "NewFangledExpression";
+ right: BadExpression;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old-order-switched.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old-order-switched.d.ts
new file mode 100644
index 00000000..dc0141da
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old-order-switched.d.ts
@@ -0,0 +1,20 @@
+export type AssignmentOperator = "=";
+interface Pattern {
+ type: "Pattern"
+};
+interface MemberExpression {
+ type: "MemberExpression"
+};
+interface Expression {
+ type: "Expression"
+};
+
+export interface AssignmentExpression {
+ type: "AssignmentExpression";
+ operator: AssignmentOperator;
+ down: Expression;
+ up: Expression;
+ left: Pattern | MemberExpression;
+ right: Expression;
+ nontraversable: RegExp;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old-other-order.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old-other-order.d.ts
new file mode 100644
index 00000000..3f9d5d34
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old-other-order.d.ts
@@ -0,0 +1,20 @@
+export type AssignmentOperator = "=";
+interface Pattern {
+ type: "Pattern"
+};
+interface MemberExpression {
+ type: "MemberExpression"
+};
+interface Expression {
+ type: "Expression"
+};
+
+export interface AssignmentExpression {
+ type: "AssignmentExpression";
+ operator: AssignmentOperator;
+ up: Expression;
+ left: Pattern | MemberExpression;
+ down: Expression;
+ right: Expression;
+ nontraversable: RegExp;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old.d.ts
new file mode 100644
index 00000000..1ac742c0
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys-on-old.d.ts
@@ -0,0 +1,35 @@
+export type AssignmentOperator = "=";
+
+interface IgnoreBase {
+ type: "Line";
+}
+
+type AnotherIgnore = IgnoreBase;
+
+interface BasePattern {
+ type: "Pattern"
+};
+interface IgnoreChild extends Omit {
+};
+
+interface Pattern {
+ type: "Pattern"
+};
+interface MemberExpression {
+ type: "MemberExpression"
+};
+interface Expression {
+ type: "Expression"
+};
+
+export interface AssignmentExpression {
+ type: "AssignmentExpression";
+ ignore: IgnoreChild;
+ anotherIgnore: AnotherIgnore;
+ operator: AssignmentOperator;
+ up: Expression;
+ down: Expression;
+ left: Pattern | MemberExpression;
+ right: Expression;
+ nontraversable: RegExp;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys.d.ts
new file mode 100644
index 00000000..6ff0aa89
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/new-keys.d.ts
@@ -0,0 +1,19 @@
+export type AssignmentOperator = "=";
+interface Pattern {
+ type: "Pattern"
+};
+interface MemberExpression {
+ type: "MemberExpression"
+};
+interface Expression {
+ type: "Expression"
+};
+
+export interface NewFangledExpression {
+ type: "NewFangledExpression";
+ operator: AssignmentOperator;
+ up: Expression;
+ down: Expression;
+ left: Pattern | MemberExpression;
+ right: Expression;
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/fixtures/union-omit.d.ts b/packages/eslint-visitor-keys/tests/lib/fixtures/union-omit.d.ts
new file mode 100644
index 00000000..d6088df3
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/fixtures/union-omit.d.ts
@@ -0,0 +1,11 @@
+export interface IgnoredStatement {
+ type: "IgnoredStatement"
+}
+export interface AnotherStatement {
+ type: "AnotherStatement";
+ anotherToIgnore: IgnoredStatement;
+}
+
+export interface StaticBlock extends Omit {
+ type: "StaticBlock";
+}
diff --git a/packages/eslint-visitor-keys/tests/lib/get-keys-from-ts.js b/packages/eslint-visitor-keys/tests/lib/get-keys-from-ts.js
new file mode 100644
index 00000000..9b32795d
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/get-keys-from-ts.js
@@ -0,0 +1,201 @@
+/**
+ * @fileoverview Tests for checking that our build tool can retrieve keys out of TypeScript AST.
+ * @author Brett Zamir
+ */
+
+import { diffString } from "json-diff";
+import { expect } from "chai";
+import { alphabetizeKeyInterfaces, getKeysFromTsFile } from "../../tools/get-keys-from-ts.js";
+import { KEYS } from "../../lib/index.js";
+import backwardCompatibleKeys from "../../tools/backward-compatible-keys.js";
+
+describe("getKeysFromTsFile", () => {
+ it("gets keys", async () => {
+ const { keys, tsInterfaceDeclarations } = await getKeysFromTsFile(
+ "../../node_modules/@types/estree/index.d.ts"
+ );
+ const { keys: jsxKeys } = await getKeysFromTsFile(
+ "../../node_modules/@types/estree-jsx/index.d.ts",
+ {
+ supplementaryDeclarations: tsInterfaceDeclarations
+ }
+ );
+
+ const actual = alphabetizeKeyInterfaces({ ...keys, ...jsxKeys, ...backwardCompatibleKeys });
+
+ const expected = KEYS;
+
+ // eslint-disable-next-line no-console -- Mocha's may drop diffs so show with json-diff
+ console.log("JSON Diffs:", diffString(actual, expected) || "(none)");
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("gets keys minus explicitly omitted ones", async () => {
+ const { keys: actual } = await getKeysFromTsFile(
+ "./tests/lib/fixtures/union-omit.d.ts"
+ );
+
+ const expected = {
+ AnotherStatement: [
+ "anotherToIgnore"
+ ],
+ IgnoredStatement: [],
+ StaticBlock: []
+ };
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("sorts keys alphabetically if new", async () => {
+ const { keys: actual } = await getKeysFromTsFile(
+ "./tests/lib/fixtures/new-keys.d.ts"
+ );
+
+ const expected = {
+ NewFangledExpression: [
+ "down",
+ "left",
+ "right",
+ "up"
+ ]
+ };
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("sorts extra keys at end alphabetically", async () => {
+ const { keys: actual } = await getKeysFromTsFile(
+ "./tests/lib/fixtures/new-keys-on-old.d.ts"
+ );
+
+ const expected = {
+ AssignmentExpression: [
+ "left",
+ "right",
+ "down",
+ "up"
+ ]
+ };
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("sorts extra keys at end alphabetically (other order)", async () => {
+ const { keys: actual } = await getKeysFromTsFile(
+ "./tests/lib/fixtures/new-keys-on-old-other-order.d.ts"
+ );
+
+ const expected = {
+ AssignmentExpression: [
+ "left",
+ "right",
+ "down",
+ "up"
+ ]
+ };
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("sorts extra keys at end alphabetically (switched order)", async () => {
+ const { keys: actual } = await getKeysFromTsFile(
+ "./tests/lib/fixtures/new-keys-on-old-order-switched.d.ts"
+ );
+
+ const expected = {
+ AssignmentExpression: [
+ "left",
+ "right",
+ "down",
+ "up"
+ ]
+ };
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("throws with unhandled TS type reference", async () => {
+ let error;
+
+ try {
+ await getKeysFromTsFile(
+ "./tests/lib/fixtures/bad-type-reference.d.ts"
+ );
+ } catch (err) {
+ error = err;
+ }
+
+ expect(error.message).to.contain("Unhandled TypeScript type reference");
+ });
+
+ it("throws with unhandled extends TS type reference", async () => {
+ let error;
+
+ try {
+ await getKeysFromTsFile(
+ "./tests/lib/fixtures/bad-extends-type-reference.d.ts"
+ );
+ } catch (err) {
+ error = err;
+ }
+
+ expect(error.message).to.contain("Unhandled TypeScript type reference");
+ });
+
+ it("throws with unhandled TS type", async () => {
+ let error;
+
+ try {
+ await getKeysFromTsFile(
+ "./tests/lib/fixtures/bad-type.d.ts"
+ );
+ } catch (err) {
+ error = err;
+ }
+
+ expect(error.message).to.contain("Unhandled TypeScript type;");
+ });
+
+ it("throws with unhandled TS typeParameters", async () => {
+ let error;
+
+ try {
+ await getKeysFromTsFile(
+ "./tests/lib/fixtures/bad-type-parameters.d.ts"
+ );
+ } catch (err) {
+ error = err;
+ }
+
+ expect(error.message).to.contain("Unknown type parameter");
+ });
+
+ it("throws with bad key", async () => {
+ let error;
+
+ try {
+ await getKeysFromTsFile(
+ "./tests/lib/fixtures/new-keys-bad.d.ts"
+ );
+ } catch (err) {
+ error = err;
+ }
+
+ expect(error.message).to.equal("Type unknown as to traversability: BadExpression");
+ });
+
+ it("throws with bad type value", async () => {
+ let error;
+
+ try {
+ await getKeysFromTsFile(
+ "./tests/lib/fixtures/bad-type-value.d.ts"
+ );
+ } catch (err) {
+ error = err;
+ }
+
+ expect(error.message).to.equal("Unexpected `type` value property type TSUndefinedKeyword");
+ });
+});
diff --git a/packages/eslint-visitor-keys/tests/lib/index.js b/packages/eslint-visitor-keys/tests/lib/index.js
new file mode 100644
index 00000000..15922127
--- /dev/null
+++ b/packages/eslint-visitor-keys/tests/lib/index.js
@@ -0,0 +1,66 @@
+/**
+ * @author Toru Nagashima
+ * See LICENSE file in root directory for full license.
+ */
+import assert from "node:assert";
+import * as evk from "../../lib/index.js";
+import keys from "../../lib/visitor-keys.js";
+
+describe("eslint-visitor-keys", () => {
+ describe("KEYS", () => {
+ it("should be same as lib/visitor-keys.js", () => {
+ assert.deepStrictEqual(evk.KEYS, keys);
+ });
+ });
+
+ describe("getKeys()", () => {
+ it("should return keys", () => {
+ assert.deepStrictEqual(evk.getKeys({ a: 1, b: 2 }), ["a", "b"]);
+ });
+
+ it("should not include 'parent' in the result", () => {
+ assert.deepStrictEqual(evk.getKeys({ a: 1, b: 2, parent: 3 }), ["a", "b"]);
+ });
+
+ it("should not include 'leadingComments' in the result", () => {
+ assert.deepStrictEqual(evk.getKeys({ a: 1, b: 2, leadingComments: 3 }), ["a", "b"]);
+ });
+
+ it("should not include 'trailingComments' in the result", () => {
+ assert.deepStrictEqual(evk.getKeys({ a: 1, b: 2, trailingComments: 3 }), ["a", "b"]);
+ });
+
+ it("should not include '_foo' in the result", () => {
+ assert.deepStrictEqual(evk.getKeys({ a: 1, b: 2, _foo: 3 }), ["a", "b"]);
+ });
+ });
+
+ describe("unionWith()", () => {
+ const additionalKeys = { Program: ["body", "a"], AssignmentExpression: ["b"], additional: ["c"], MethodDefinition: ["a", "key", "b"] };
+ const unionKeys = evk.unionWith(additionalKeys);
+
+ it("should include all keys of lib/visitor-keys.js", () => {
+ for (const type of Object.keys(keys)) {
+ for (const key of keys[type]) {
+ assert(unionKeys[type].includes(key), `'${key}' should be included in '${type}'.`);
+ }
+ }
+ });
+
+ it("should include all additional keys", () => {
+ for (const type of Object.keys(additionalKeys)) {
+ for (const key of additionalKeys[type]) {
+ assert(unionKeys[type].includes(key), `'${key}' should be included in '${type}'.`);
+ }
+ }
+ });
+
+ it("should not have duplicate", () => {
+ assert(unionKeys.Program.filter(key => key === "body").length === 1);
+ });
+
+ it("should add additional keys, then concatenate original keys", () => {
+ assert.deepStrictEqual(unionKeys.MethodDefinition, ["a", "key", "b", "value"]);
+ });
+ });
+});
diff --git a/packages/eslint-visitor-keys/tools/backward-compatible-keys.js b/packages/eslint-visitor-keys/tools/backward-compatible-keys.js
new file mode 100644
index 00000000..ead9e70a
--- /dev/null
+++ b/packages/eslint-visitor-keys/tools/backward-compatible-keys.js
@@ -0,0 +1,10 @@
+const backwardCompatibleKeys = {
+ ExperimentalRestProperty: [
+ "argument"
+ ],
+ ExperimentalSpreadProperty: [
+ "argument"
+ ]
+};
+
+export default backwardCompatibleKeys;
diff --git a/packages/eslint-visitor-keys/tools/build-keys-from-ts.js b/packages/eslint-visitor-keys/tools/build-keys-from-ts.js
new file mode 100644
index 00000000..7d7e8a73
--- /dev/null
+++ b/packages/eslint-visitor-keys/tools/build-keys-from-ts.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview Script to build our visitor keys based on TypeScript AST.
+ *
+ * Uses `get-keys-from-ts.js` to read the files and build the keys and then
+ * merges them in alphabetical order of Node type before writing to file.
+ *
+ * @author Brett Zamir
+ */
+
+import fs from "node:fs";
+import { alphabetizeKeyInterfaces, getKeysFromTsFile } from "./get-keys-from-ts.js";
+import backwardCompatibleKeys from "./backward-compatible-keys.js";
+
+const { promises: { writeFile } } = fs;
+
+(async () => {
+ const { keys, tsInterfaceDeclarations } = await getKeysFromTsFile("./node_modules/@types/estree/index.d.ts");
+ const { keys: jsxKeys } = await getKeysFromTsFile(
+ "./node_modules/@types/estree-jsx/index.d.ts",
+ {
+ supplementaryDeclarations: tsInterfaceDeclarations
+ }
+ );
+
+ const mergedKeys = alphabetizeKeyInterfaces({ ...keys, ...jsxKeys, ...backwardCompatibleKeys });
+
+
+ console.log("keys", mergedKeys);
+
+ writeFile(
+ "./lib/visitor-keys.js",
+ // eslint-disable-next-line indent -- Readability
+`/**
+ * @typedef {{ readonly [type: string]: ReadonlyArray }} VisitorKeys
+ */
+
+/**
+ * @type {VisitorKeys}
+ */
+const KEYS = ${JSON.stringify(mergedKeys, null, 4).replace(/"(.*?)":/gu, "$1:")};
+
+// Types.
+const NODE_TYPES = Object.keys(KEYS);
+
+// Freeze the keys.
+for (const type of NODE_TYPES) {
+ Object.freeze(KEYS[type]);
+}
+Object.freeze(KEYS);
+
+export default KEYS;
+`
+ );
+
+})();
diff --git a/packages/eslint-visitor-keys/tools/get-keys-from-ts.js b/packages/eslint-visitor-keys/tools/get-keys-from-ts.js
new file mode 100644
index 00000000..1902fb39
--- /dev/null
+++ b/packages/eslint-visitor-keys/tools/get-keys-from-ts.js
@@ -0,0 +1,546 @@
+/**
+ * @fileoverview Script to build our visitor keys based on TypeScript AST.
+ *
+ * Uses `get-keys-from-ts.js` to read the files and build the keys and then
+ * merges them in alphabetical order of Node type before writing to file.
+ *
+ * @author Brett Zamir
+ */
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+import { promises } from "node:fs";
+import { parseForESLint } from "@typescript-eslint/parser";
+import esquery from "esquery";
+
+import { getKeys, KEYS } from "../lib/index.js";
+
+const { readFile } = promises;
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const knownTypes = new Set([
+ "TSUndefinedKeyword",
+ "TSNullKeyword",
+ "TSUnknownKeyword",
+ "TSBooleanKeyword",
+ "TSNumberKeyword",
+ "TSStringKeyword",
+ "TSLiteralType", // E.g., `true`
+
+ // Apparently used for primitives, so exempting
+ "TSTypeLiteral", // E.g., `{value: {cooked, raw}}`
+
+ "TSUnionType", // I.e., `|`
+ "TSTypeReference"
+]);
+
+const notTraversableTypes = new Set([
+ "RegExp"
+]);
+
+const notTraversableTSTypes = new Set([
+ "TSUndefinedKeyword",
+ "TSNullKeyword",
+ "TSBooleanKeyword",
+ "TSNumberKeyword",
+ "TSStringKeyword",
+ "TSBigIntKeyword",
+ "TSLiteralType"
+]);
+
+const commentTypes = new Set([
+ "Line",
+ "Block"
+]);
+
+/**
+ * Get the literal names out of AST
+ * @param {Node} excludedItem Excluded node
+ * @returns {string[]} The literal names
+ */
+function findOmitTypes(excludedItem) {
+ if (excludedItem.type === "TSUnionType") {
+ return excludedItem.types.map(typeNode => findOmitTypes(typeNode));
+ }
+ return excludedItem.literal.value;
+}
+
+/**
+ * Checks whether property should be excluded
+ * @param {string} property Property to check
+ * @param {string[]} excludedProperties Properties not to allow
+ * @returns {boolean} Whether or not to be excluded
+ */
+function isPropertyExcluded(property, excludedProperties) {
+ return excludedProperties && excludedProperties.includes(property);
+}
+
+//------------------------------------------------------------------------------
+// Public APIs
+//------------------------------------------------------------------------------
+
+/**
+ * Returns alphabetized keys
+ * @param {KeysStrict} initialNodes Initial node list to sort
+ * @returns {KeysStrict} The keys
+ */
+function alphabetizeKeyInterfaces(initialNodes) {
+
+ /**
+ * Alphabetize
+ * @param {string} typeA The first type to compare
+ * @param {string} typeB The second type to compare
+ * @returns {1|-1} The sorting index
+ */
+ function alphabetize([typeA], [typeB]) {
+ return typeA < typeB ? -1 : 1;
+ }
+ const sortedNodeEntries = Object.entries(initialNodes).sort(alphabetize);
+
+ /**
+ * Get the key sorter for a given type
+ * @param {string} type The type
+ * @returns {(string, string) => -1|1} The sorter
+ */
+ function getKeySorter(type) {
+ const sequence = KEYS[type];
+
+ /**
+ * Alphabetize
+ * @param {string} typeA The first type to compare
+ * @param {string} typeB The second type to compare
+ * @returns {1|-1} The sorting index
+ */
+ return function sortKeys(typeA, typeB) {
+ if (!sequence) {
+ return typeA < typeB ? -1 : 1;
+ }
+
+ const idxA = sequence.indexOf(typeA);
+ const idxB = sequence.indexOf(typeB);
+
+ if (idxA === -1 && idxB === -1) {
+ return typeA < typeB ? -1 : 1;
+ }
+ if (idxA === -1) {
+ return 1;
+ }
+ if (idxB === -1) {
+ return -1;
+ }
+
+ return idxA < idxB ? -1 : 1;
+ };
+ }
+
+ for (const [type, keys] of sortedNodeEntries) {
+ keys.sort(getKeySorter(type));
+ }
+
+ return Object.fromEntries(sortedNodeEntries);
+}
+
+/**
+ * Traverse interface `extends`
+ * @param {Node} declNode The TS declaration node
+ * @param {Function} handler The callback
+ * @returns {any[]} Return value of handler
+ * @throws {Error} If it finds an unknown type parameter.
+ */
+function traverseExtends(declNode, handler) {
+ const ret = [];
+
+ for (const extension of declNode.extends || []) {
+ const { typeParameters, expression } = extension;
+ const innerInterfaceName = expression.name;
+
+ let res;
+
+ if (typeParameters) {
+ if (innerInterfaceName !== "Omit") {
+ throw new Error("Unknown type parameter");
+ }
+
+ const [param, ...excludedAST] = typeParameters.params;
+ const paramInterfaceName = param.typeName.name;
+ const excluded = excludedAST.flatMap(findOmitTypes);
+
+ res = handler({ iName: paramInterfaceName, excluded });
+ } else {
+ res = handler({ iName: innerInterfaceName });
+ }
+
+ ret.push(res);
+ }
+
+ return ret;
+}
+
+/**
+ * Traverse the properties of a declaration node.
+ * @param {Node} tsDeclarationNode The declaration node
+ * @param {(string) => void} handler Passed the property
+ * @returns {any[]} The return values of the callback
+ */
+function traverseProperties(tsDeclarationNode, handler) {
+ const tsPropertySignatures = tsDeclarationNode.body.body;
+
+ const ret = [];
+
+ for (const tsPropertySignature of tsPropertySignatures) {
+ const property = tsPropertySignature.key.name;
+
+ const tsAnnotation = tsPropertySignature.typeAnnotation.typeAnnotation;
+
+ const res = handler({ property, tsAnnotation });
+
+ ret.push(res);
+ }
+
+ return ret;
+}
+
+/**
+ * Builds visitor keys based on TypeScript declaration.
+ * @param {string} code TypeScript declaration file as code to parse.
+ * @param {{supplementaryDeclarations: Node[]}} [options] The options
+ * @returns {VisitorKeysExport} The built visitor keys
+ * @throws {Error} If it finds an unknown type.
+ */
+function getKeysFromTs(code, {
+
+ // Todo: Ideally we'd just get these from the import
+ supplementaryDeclarations = {
+ allTsInterfaceDeclarations: [],
+ exportedTsInterfaceDeclarations: [],
+ tsTypeDeclarations: []
+ }
+} = {}) {
+ const unrecognizedTSTypeReferences = new Set();
+ const unrecognizedTSTypes = new Set();
+
+ const parsedTSDeclaration = parseForESLint(code);
+
+ const allTsInterfaceDeclarations = [...esquery.query(
+ parsedTSDeclaration.ast,
+ "TSInterfaceDeclaration",
+ {
+
+ // TypeScript keys here to find our *.d.ts nodes (not for the ESTree
+ // ones we want)
+ visitorKeys: parsedTSDeclaration.visitorKeys
+ }
+ ), ...supplementaryDeclarations.allTsInterfaceDeclarations];
+
+ const exportedTsInterfaceDeclarations = [...esquery.query(
+ parsedTSDeclaration.ast,
+ "ExportNamedDeclaration > TSInterfaceDeclaration",
+ {
+
+ // TypeScript keys here to find our *.d.ts nodes (not for the ESTree
+ // ones we want)
+ visitorKeys: parsedTSDeclaration.visitorKeys
+ }
+ ), ...supplementaryDeclarations.exportedTsInterfaceDeclarations];
+
+ const tsTypeDeclarations = [...esquery.query(
+ parsedTSDeclaration.ast,
+ "TSTypeAliasDeclaration",
+ {
+
+ // TypeScript keys here to find our *.d.ts nodes (not for the ESTree
+ // ones we want)
+ visitorKeys: parsedTSDeclaration.visitorKeys
+ }
+ ), ...supplementaryDeclarations.tsTypeDeclarations];
+
+ const initialNodes = {};
+
+ /**
+ * Finds a TypeScript interface declaration.
+ * @param {string} interfaceName The type name.
+ * @returns {Node} The interface declaration node
+ */
+ function findTsInterfaceDeclaration(interfaceName) {
+ return allTsInterfaceDeclarations.find(
+ innerTsDeclaration => innerTsDeclaration.id.name === interfaceName
+ );
+ }
+
+ /**
+ * Finds a TypeScript type declaration.
+ * @param {string} typeName A type name
+ * @returns {Node} The type declaration node
+ */
+ function findTsTypeDeclaration(typeName) {
+ return tsTypeDeclarations.find(typeDecl => typeDecl.id.name === typeName);
+ }
+
+ /**
+ * Whether has a valid (non-comment) type
+ * @param {Object} cfg Config object
+ * @param {string} cfg.property The property name
+ * @param {Node} cfg.tsAnnotation The annotation node
+ * @returns {boolean} Whether has a traverseable type
+ * @throws {Error} If it finds an unknown type.
+ */
+ function hasValidType({ property, tsAnnotation }) {
+ const tsPropertyType = tsAnnotation.type;
+
+ if (property !== "type") {
+ return false;
+ }
+
+ switch (tsPropertyType) {
+ case "TSLiteralType":
+ return typeof tsAnnotation.literal.value === "string" &&
+ !commentTypes.has(tsAnnotation.literal.value);
+ case "TSStringKeyword":
+
+ // Ok, but not sufficient
+ return false;
+ case "TSUnionType":
+ return tsAnnotation.types.some(annType => hasValidType({
+ property: "type",
+ tsAnnotation: annType
+ }));
+ default:
+ throw new Error(`Unexpected \`type\` value property type ${tsPropertyType}`);
+ }
+ }
+
+ /**
+ * Whether the interface has a valid type ancestor
+ * @param {string} interfaceName The interface to check
+ * @returns {void}
+ * @throws {Error} If it finds an unknown type.
+ */
+ function hasValidTypeAncestor(interfaceName) {
+ let decl = findTsInterfaceDeclaration(interfaceName);
+
+ if (decl) {
+ if (traverseProperties(decl, hasValidType).some(hasValid => hasValid)) {
+ return true;
+ }
+ }
+
+ if (!decl) {
+ decl = findTsTypeDeclaration(interfaceName);
+ if (decl) {
+ if (!decl.typeAnnotation.types) {
+ return notTraversableTSTypes.has(decl.typeAnnotation.type)
+ ? false
+ : hasValidTypeAncestor(decl.typeAnnotation.typeName.name);
+ }
+
+ return decl.typeAnnotation.types.some(type => {
+ if (!type.typeName) {
+
+ // Literal
+ return false;
+ }
+
+ return hasValidTypeAncestor(type.typeName.name);
+ });
+ }
+ }
+
+ if (!decl) {
+ throw new Error(`Type unknown as to traversability: ${interfaceName}`);
+ }
+
+ if (traverseExtends(decl, ({ iName, excluded }) => {
+
+ // We don't want to look at this ancestor's `type` if being excluded
+ if (excluded && excluded.includes("type")) {
+ return false;
+ }
+
+ return hasValidTypeAncestor(iName);
+ }).some(hasValid => hasValid)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine whether the Node is traversable
+ * @param {Node} annotationType The annotation type Node
+ * @param {string} property The property name
+ * @returns {boolean} Whether the node is traversable
+ */
+ function checkTraversability(annotationType, property) {
+ if (
+ notTraversableTSTypes.has(annotationType.type)
+ ) {
+ return false;
+ }
+
+ if (annotationType.type === "TSTupleType") {
+ return annotationType.elementTypes.some(annType => checkTraversability(annType, property));
+ }
+
+ if (annotationType.type === "TSUnionType") {
+ return annotationType.types.some(annType => checkTraversability(annType, property));
+ }
+
+ if (annotationType.typeName.name === "Array") {
+ return annotationType.typeParameters.params.some(annType => checkTraversability(annType, property));
+ }
+
+ if (
+ notTraversableTypes.has(annotationType.typeName.name)
+ ) {
+ return false;
+ }
+
+ if (hasValidTypeAncestor(annotationType.typeName.name)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds a property to a node based on a type declaration node's contents.
+ * @param {Node} tsDeclarationNode TypeScript declaration node
+ * @param {Node} node The Node on which to build
+ * @param {string[]} excludedProperties Excluded properties
+ * @returns {void}
+ */
+ function addPropertyToNodeForDeclaration(tsDeclarationNode, node, excludedProperties) {
+
+ traverseProperties(tsDeclarationNode, ({ property, tsAnnotation }) => {
+ if (isPropertyExcluded(property, excludedProperties)) {
+ return;
+ }
+
+ const tsPropertyType = tsAnnotation.type;
+
+ if (property === "type" && tsPropertyType === "TSLiteralType") {
+
+ // console.log('tsAnnotation', tsAnnotation);
+ // node[property] = tsAnnotation.literal.value;
+ // return;
+ }
+
+ // For sanity-checking
+ if (!knownTypes.has(tsPropertyType)) {
+ unrecognizedTSTypes.add(tsPropertyType);
+ return;
+ }
+
+ switch (tsPropertyType) {
+ case "TSUnionType":
+ if (tsAnnotation.types.some(annType => checkTraversability(annType, property))) {
+ break;
+ }
+ return;
+ case "TSTypeReference": {
+ if (checkTraversability(tsAnnotation, property)) {
+ break;
+ }
+
+ return;
+ } default:
+ return;
+ }
+
+ node[property] = null;
+ });
+
+ traverseExtends(tsDeclarationNode, ({ iName, excluded }) => {
+ const innerTsDeclarationNode = findTsInterfaceDeclaration(iName);
+
+ if (!innerTsDeclarationNode) {
+ unrecognizedTSTypeReferences.add(iName);
+ return;
+ }
+
+ addPropertyToNodeForDeclaration(innerTsDeclarationNode, node, excluded);
+ });
+ }
+
+ for (const tsDeclarationNode of exportedTsInterfaceDeclarations) {
+ const bodyType = tsDeclarationNode.body.body.find(
+ prop => prop.key.name === "type"
+ );
+
+ const typeName = bodyType && bodyType.typeAnnotation &&
+ bodyType.typeAnnotation.typeAnnotation &&
+ bodyType.typeAnnotation.typeAnnotation.literal &&
+ bodyType.typeAnnotation.typeAnnotation.literal.value;
+
+ if (!typeName) {
+ continue;
+ }
+
+ const node = {};
+
+ addPropertyToNodeForDeclaration(tsDeclarationNode, node);
+
+ initialNodes[typeName] = [...new Set(getKeys(node), ...(initialNodes[typeName] || []))];
+ }
+
+ const nodes = alphabetizeKeyInterfaces(initialNodes);
+
+ if (unrecognizedTSTypes.size) {
+ throw new Error(
+ "Unhandled TypeScript type; please update the code to " +
+ "handle the type or if not relevant, add it to " +
+ "`unrecognizedTSTypes`; see\n\n " +
+ `${[...unrecognizedTSTypes].join(", ")}\n`
+ );
+ }
+ if (unrecognizedTSTypeReferences.size) {
+ throw new Error(
+ "Unhandled TypeScript type reference; please update the code to " +
+ "handle the type reference or if not relevant, add it to " +
+ "`unrecognizedTSTypeReferences`; see\n\n " +
+ `${[...unrecognizedTSTypeReferences].join(", ")}\n`
+ );
+ }
+
+ return {
+ keys: nodes,
+ tsInterfaceDeclarations: {
+ allTsInterfaceDeclarations,
+ exportedTsInterfaceDeclarations,
+ tsTypeDeclarations
+ }
+ };
+}
+
+/**
+ * @typedef {{
+ * keys: KeysStrict,
+ * tsInterfaceDeclarations: {
+ * allTsInterfaceDeclarations: Node[],
+ * exportedTsInterfaceDeclarations: Node[]
+ * }
+ * }} VisitorKeysExport
+ */
+
+/**
+ * Builds visitor keys based on TypeScript declaration.
+ * @param {string} file TypeScript declaration file to parse.
+ * @param {{supplementaryDeclarations: Object}} options The options
+ * @returns {Promise} The built visitor keys
+ */
+async function getKeysFromTsFile(file, options) {
+ const code = await readFile(file);
+
+ return getKeysFromTs(code, options);
+}
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+export { alphabetizeKeyInterfaces, getKeysFromTs, getKeysFromTsFile };
diff --git a/packages/eslint-visitor-keys/tsconfig.json b/packages/eslint-visitor-keys/tsconfig.json
new file mode 100644
index 00000000..57d4de12
--- /dev/null
+++ b/packages/eslint-visitor-keys/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "lib": ["es2022"],
+ "moduleResolution": "node",
+ "module": "esnext",
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "checkJs": true,
+ "noEmit": false,
+ "declaration": true,
+ "declarationMap": true,
+ "emitDeclarationOnly": true,
+ "strict": true,
+ "target": "es6",
+ "outDir": "dist"
+ },
+ "include": ["lib/**/*.js"],
+ "exclude": ["node_modules"]
+}
diff --git a/release-please-config.json b/release-please-config.json
index cdf3e031..d21c0155 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -8,6 +8,9 @@
},
"packages/eslint-scope": {
"release-type": "node"
+ },
+ "packages/eslint-visitor-keys": {
+ "release-type": "node"
}
}
}