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

+

Automattic Airbnb

Gold Sponsors

+

Eli Schleifer Siemens

Silver Sponsors

+

JetBrains Liftoff American Express Workleap

Bronze Sponsors

+

notion Anagram Solver Icons8 Discord Ignition Nx HeroCoders Nextbase Starter Kit

+ + + +

Technology Sponsors

+

Netlify Algolia 1Password

+ 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" } } }