├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── lint-test.yml │ ├── main.yml │ ├── publish-release.yml │ └── security-code-scanner.yml ├── .gitignore ├── .nvmrc ├── .prettierrc.mjs ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-allow-scripts.cjs └── releases │ └── yarn-4.5.0.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── eslint.config.mjs ├── package.json ├── packages ├── base │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ │ ├── environment.json │ │ ├── index.d.mts │ │ ├── index.mjs │ │ ├── index.test.mjs │ │ ├── utils.mjs │ │ └── utils.test.mjs ├── browser │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ │ ├── environment.json │ │ ├── index.d.mts │ │ ├── index.mjs │ │ └── index.test.mjs ├── commonjs │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ │ ├── environment.json │ │ ├── index.d.mts │ │ ├── index.mjs │ │ └── index.test.mjs ├── jest │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ │ ├── index.d.mts │ │ ├── index.mjs │ │ └── index.test.mjs ├── mocha │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ │ ├── index.d.mts │ │ ├── index.mjs │ │ └── index.test.mjs ├── nodejs │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ │ ├── environment.json │ │ ├── index.d.mts │ │ ├── index.mjs │ │ └── index.test.mjs ├── typescript │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ ├── src │ │ ├── __test__ │ │ │ └── dummy.ts │ │ ├── index.d.mts │ │ ├── index.mjs │ │ └── index.test.mjs │ └── tsconfig.json └── vitest │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rules-snapshot.json │ └── src │ ├── index.d.mts │ ├── index.mjs │ └── index.test.mjs ├── release.config.json ├── scripts ├── generate-configs.mjs ├── update-changelog.sh ├── validate-changelog.sh └── validate-configs.mjs ├── tsconfig.json ├── vitest.config.mts ├── vitest.workspace.mts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | yarn.lock linguist-generated=false 4 | 5 | # yarn v3 6 | # See: https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 7 | /.yarn/releases/** binary 8 | /.yarn/plugins/** binary 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | * @MetaMask/wallet-framework-engineers @MetaMask/snaps-devs 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: 'npm' 7 | directory: '/' 8 | schedule: 9 | interval: 'daily' 10 | time: '06:00' 11 | allow: 12 | - dependency-name: '@metamask/*' 13 | target-branch: 'main' 14 | versioning-strategy: 'increase-if-necessary' 15 | open-pull-requests-limit: 10 16 | -------------------------------------------------------------------------------- /.github/workflows/lint-test.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Test 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | prepare: 8 | name: Prepare 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [18.x, 20.x, 22.x] 13 | outputs: 14 | child-workspace-package-names: ${{ steps.workspace-package-names.outputs.child-workspace-package-names }} 15 | steps: 16 | - name: Checkout and setup environment 17 | uses: MetaMask/action-checkout-and-setup@v1 18 | with: 19 | is-high-risk-environment: false 20 | cache-node-modules: ${{ matrix.node-version == '22.x' }} 21 | - name: Fetch workspace package names 22 | id: workspace-package-names 23 | run: | 24 | echo "child-workspace-package-names=$(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map(.name) | @json')" >> "$GITHUB_OUTPUT" 25 | shell: bash 26 | 27 | lint: 28 | name: Lint 29 | runs-on: ubuntu-latest 30 | needs: prepare 31 | strategy: 32 | matrix: 33 | node-version: [22.x] 34 | steps: 35 | - name: Checkout and setup environment 36 | uses: MetaMask/action-checkout-and-setup@v1 37 | with: 38 | is-high-risk-environment: false 39 | - run: yarn lint 40 | - name: Require clean working directory 41 | shell: bash 42 | run: | 43 | if ! git diff --exit-code; then 44 | echo "Working tree dirty at end of job" 45 | exit 1 46 | fi 47 | 48 | validate-changelog: 49 | name: Validate changelog 50 | runs-on: ubuntu-latest 51 | needs: prepare 52 | strategy: 53 | matrix: 54 | node-version: [22.x] 55 | package-name: ${{ fromJson(needs.prepare.outputs.child-workspace-package-names) }} 56 | steps: 57 | - name: Checkout and setup environment 58 | uses: MetaMask/action-checkout-and-setup@v1 59 | with: 60 | is-high-risk-environment: false 61 | - run: yarn workspace ${{ matrix.package-name }} changelog:validate 62 | - name: Require clean working directory 63 | shell: bash 64 | run: | 65 | if ! git diff --exit-code; then 66 | echo "Working tree dirty at end of job" 67 | exit 1 68 | fi 69 | 70 | test: 71 | name: Test 72 | runs-on: ubuntu-latest 73 | needs: prepare 74 | strategy: 75 | matrix: 76 | node-version: [18.x, 20.x, 22.x] 77 | steps: 78 | - name: Checkout and setup environment 79 | uses: MetaMask/action-checkout-and-setup@v1 80 | with: 81 | is-high-risk-environment: false 82 | - run: yarn test 83 | - name: Require clean working directory 84 | shell: bash 85 | run: | 86 | if ! git diff --exit-code; then 87 | echo "Working tree dirty at end of job" 88 | exit 1 89 | fi 90 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | check-workflows: 10 | name: Check workflows 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Download actionlint 15 | id: download-actionlint 16 | run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/7fdc9630cc360ea1a469eed64ac6d78caeda1234/scripts/download-actionlint.bash) 1.6.25 17 | shell: bash 18 | - name: Check workflow files 19 | run: ${{ steps.download-actionlint.outputs.executable }} -color 20 | shell: bash 21 | 22 | analyse-code: 23 | name: Code scanner 24 | needs: check-workflows 25 | uses: ./.github/workflows/security-code-scanner.yml 26 | permissions: 27 | actions: read 28 | contents: read 29 | security-events: write 30 | secrets: 31 | SECURITY_SCAN_METRICS_TOKEN: ${{ secrets.SECURITY_SCAN_METRICS_TOKEN }} 32 | APPSEC_BOT_SLACK_WEBHOOK: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} 33 | 34 | lint-test: 35 | name: Lint and test 36 | needs: check-workflows 37 | uses: ./.github/workflows/lint-test.yml 38 | 39 | is-release: 40 | name: Determine whether this is a release merge commit 41 | needs: lint-test 42 | if: github.event_name == 'push' 43 | runs-on: ubuntu-latest 44 | outputs: 45 | IS_RELEASE: ${{ steps.is-release.outputs.IS_RELEASE }} 46 | steps: 47 | - id: is-release 48 | uses: MetaMask/action-is-release@v2 49 | with: 50 | commit-starts-with: 'Release [version],Release v[version],Release/[version],Release/v[version],Release `[version]`' 51 | 52 | publish-release: 53 | name: Publish release 54 | needs: is-release 55 | if: needs.is-release.outputs.IS_RELEASE == 'true' 56 | permissions: 57 | contents: write 58 | uses: ./.github/workflows/publish-release.yml 59 | secrets: 60 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 61 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 62 | 63 | all-jobs-complete: 64 | name: All jobs complete 65 | runs-on: ubuntu-latest 66 | needs: 67 | - analyse-code 68 | - lint-test 69 | outputs: 70 | passed: ${{ steps.set-output.outputs.passed }} 71 | steps: 72 | - name: Set passed output 73 | id: set-output 74 | run: echo "passed=true" >> "$GITHUB_OUTPUT" 75 | 76 | all-jobs-pass: 77 | name: All jobs pass 78 | if: ${{ always() }} 79 | runs-on: ubuntu-latest 80 | needs: all-jobs-complete 81 | steps: 82 | - name: Check that all jobs have passed 83 | run: | 84 | passed="${{ needs.all-jobs-complete.outputs.passed }}" 85 | if [[ $passed != "true" ]]; then 86 | exit 1 87 | fi 88 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | NPM_TOKEN: 7 | required: true 8 | SLACK_WEBHOOK_URL: 9 | required: true 10 | 11 | jobs: 12 | publish-release: 13 | permissions: 14 | contents: write 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout and setup environment 18 | uses: MetaMask/action-checkout-and-setup@v1 19 | with: 20 | is-high-risk-environment: true 21 | - uses: MetaMask/action-publish-release@v3 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | - run: yarn build 25 | - name: Upload build artifacts 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: publish-release-artifacts-${{ github.sha }} 29 | include-hidden-files: true 30 | retention-days: 4 31 | path: | 32 | ./packages/**/src 33 | ./node_modules/.yarn-state.yml 34 | 35 | publish-npm-dry-run: 36 | name: Dry run publish to NPM 37 | runs-on: ubuntu-latest 38 | needs: publish-release 39 | steps: 40 | - name: Checkout and setup environment 41 | uses: MetaMask/action-checkout-and-setup@v1 42 | with: 43 | is-high-risk-environment: true 44 | ref: ${{ github.sha }} 45 | - name: Restore build artifacts 46 | uses: actions/download-artifact@v4 47 | with: 48 | name: publish-release-artifacts-${{ github.sha }} 49 | - name: Dry run publish to NPM 50 | uses: MetaMask/action-npm-publish@v5 51 | with: 52 | slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} 53 | subteam: S042S7RE4AE # @metamask-npm-publishers 54 | 55 | publish-npm: 56 | name: Publish to NPM 57 | environment: npm-publish 58 | runs-on: ubuntu-latest 59 | needs: publish-npm-dry-run 60 | steps: 61 | - name: Checkout and setup environment 62 | uses: MetaMask/action-checkout-and-setup@v1 63 | with: 64 | is-high-risk-environment: true 65 | ref: ${{ github.sha }} 66 | - name: Restore build artifacts 67 | uses: actions/download-artifact@v4 68 | with: 69 | name: publish-release-artifacts-${{ github.sha }} 70 | - name: Publish to NPM 71 | uses: MetaMask/action-npm-publish@v5 72 | with: 73 | npm-token: ${{ secrets.NPM_TOKEN }} 74 | -------------------------------------------------------------------------------- /.github/workflows/security-code-scanner.yml: -------------------------------------------------------------------------------- 1 | name: MetaMask Security Code Scanner 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | SECURITY_SCAN_METRICS_TOKEN: 7 | required: false 8 | APPSEC_BOT_SLACK_WEBHOOK: 9 | required: false 10 | workflow_dispatch: 11 | 12 | jobs: 13 | run-security-scan: 14 | name: Run security scan 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | steps: 21 | - name: Analyse code 22 | uses: MetaMask/action-security-code-scanner@v1 23 | with: 24 | repo: ${{ github.repository }} 25 | paths_ignored: | 26 | .storybook/ 27 | '**/__snapshots__/' 28 | '**/*.snap' 29 | '**/*.stories.js' 30 | '**/*.stories.tsx' 31 | '**/*.test.browser.ts*' 32 | '**/*.test.js*' 33 | '**/*.test.ts*' 34 | '**/fixtures/' 35 | '**/jest.config.js' 36 | '**/jest.environment.js' 37 | '**/mocks/' 38 | '**/test*/' 39 | docs/ 40 | e2e/ 41 | merged-packages/ 42 | node_modules 43 | storybook/ 44 | test*/ 45 | rules_excluded: example 46 | project_metrics_token: ${{ secrets.SECURITY_SCAN_METRICS_TOKEN }} 47 | slack_webhook: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }} 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | coverage/ 4 | docs/ 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | 39 | # TypeScript cache 40 | *.tsbuildinfo 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Microbundle cache 49 | .rpt2_cache/ 50 | .rts2_cache_cjs/ 51 | .rts2_cache_es/ 52 | .rts2_cache_umd/ 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | .env.test 66 | 67 | # Stores VSCode versions used for testing VSCode extensions 68 | .vscode-test 69 | 70 | # yarn v3 (w/o zero-install) 71 | # See: https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 72 | .pnp.* 73 | .yarn/* 74 | !.yarn/patches 75 | !.yarn/plugins 76 | !.yarn/releases 77 | !.yarn/sdks 78 | !.yarn/versions 79 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | // All of these are defaults except singleQuote and endOfLine, but we specify 2 | // them for explicitness 3 | const config = { 4 | endOfLine: 'auto', 5 | quoteProps: 'as-needed', 6 | singleQuote: true, 7 | tabWidth: 2, 8 | trailingComma: 'all', 9 | plugins: ['prettier-plugin-packagejson'], 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-allow-scripts", 5 | factory: function (require) { 6 | var plugin=(()=>{var a=Object.create,l=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var p=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty;var u=e=>l(e,"__esModule",{value:!0});var f=e=>{if(typeof require!="undefined")return require(e);throw new Error('Dynamic require of "'+e+'" is not supported')};var g=(e,o)=>{for(var r in o)l(e,r,{get:o[r],enumerable:!0})},m=(e,o,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let t of s(o))!c.call(e,t)&&t!=="default"&&l(e,t,{get:()=>o[t],enumerable:!(r=i(o,t))||r.enumerable});return e},x=e=>m(u(l(e!=null?a(p(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var k={};g(k,{default:()=>d});var n=x(f("@yarnpkg/shell")),y={hooks:{afterAllInstalled:async()=>{let e=await(0,n.execute)("yarn run allow-scripts");e!==0&&process.exit(e)}}},d=y;return k;})(); 7 | return plugin; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | enableScripts: false 6 | 7 | enableTelemetry: false 8 | 9 | logFilters: 10 | - code: YN0004 11 | level: discard 12 | 13 | nodeLinker: node-modules 14 | 15 | plugins: 16 | - path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs 17 | spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js" 18 | 19 | yarnPath: .yarn/releases/yarn-4.5.0.cjs 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config` 2 | 3 | This monorepo contains MetaMask's ESLint configurations as npm packages. 4 | The different configs are split up into individual packages so that we can 5 | correctly specify their peer dependencies. 6 | 7 | ## Contributing 8 | 9 | ### Setup 10 | 11 | - Install the current LTS version of [Node.js](https://nodejs.org) 12 | - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm install` will install the latest version and running `nvm use` will automatically choose the right node version for you. 13 | - Install [Yarn v3](https://yarnpkg.com/getting-started/install) 14 | - Run `yarn install` to install dependencies and run any required post-install scripts 15 | 16 | ### Testing and Linting 17 | 18 | Run `yarn lint` to run the linter, or run `yarn lint:fix` to run the linter and fix any automatically fixable issues. 19 | 20 | ### Updating or Adding Configs 21 | 22 | Configs targeting an entirely new environment should be added in a new package. 23 | Our rule validation script (see `./scripts/validate-rules.js`) forbids the 24 | following rules: 25 | 26 | - Rules that override Prettier's recommended ESLint rules 27 | - Uselessly configured rules, meaning: 28 | - Rules that are disabled but never enabled by an extended config. 29 | - Rules that are configured identically by the package's extended configs. 30 | - For the purpose of determining the "usefulness" of rules, we include our base 31 | config (`@metamask/eslint-config`) in the set of extended configs, since it 32 | should always be extended by the consumer in practice. 33 | 34 | Linting will fail in CI if any of the above conditions are violated in any 35 | config. 36 | 37 | Finally, in order to understand the impact of changing rules or the set of 38 | extended configs, each package has a `rules-snapshot.json` fill which contains 39 | all rules of the particular config and its extended configs in a single 40 | dictionary. When editing a package, always check its rules snapshots after 41 | running `yarn lint:fix` to understand which rules changed. 42 | 43 | ### Release & Publishing 44 | 45 | The project follows the same release process as the other libraries in the MetaMask organization. The GitHub Actions [`action-create-release-pr`](https://github.com/MetaMask/action-create-release-pr) and [`action-publish-release`](https://github.com/MetaMask/action-publish-release) are used to automate the release process; see those repositories for more information about how they work. 46 | 47 | 1. Choose a release version. 48 | 49 | - The release version should be chosen according to SemVer. Analyze the changes to see whether they include any breaking changes, new features, or deprecations, then choose the appropriate SemVer version. See [the SemVer specification](https://semver.org/) for more information. 50 | 51 | 2. If this release is backporting changes onto a previous release, then ensure there is a major version branch for that version (e.g. `1.x` for a `v1` backport release). 52 | 53 | - The major version branch should be set to the most recent release with that major version. For example, when backporting a `v1.0.2` release, you'd want to ensure there was a `1.x` branch that was set to the `v1.0.1` tag. 54 | 55 | 3. Trigger the [`workflow_dispatch`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch) event [manually](https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow) for the `Create Release Pull Request` action to create the release PR. 56 | 57 | - For a backport release, the base branch should be the major version branch that you ensured existed in step 2. For a normal release, the base branch should be the main branch for that repository (which should be the default value). 58 | - This should trigger the [`action-create-release-pr`](https://github.com/MetaMask/action-create-release-pr) workflow to create the release PR. 59 | 60 | 4. Update the changelog to move each change entry into the appropriate change category ([See here](https://keepachangelog.com/en/1.0.0/#types) for the full list of change categories, and the correct ordering), and edit them to be more easily understood by users of the package. 61 | 62 | - Generally any changes that don't affect consumers of the package (e.g. lockfile changes or development environment changes) are omitted. Exceptions may be made for changes that might be of interest despite not having an effect upon the published package (e.g. major test improvements, security improvements, improved documentation, etc.). 63 | - Try to explain each change in terms that users of the package would understand (e.g. avoid referencing internal variables/concepts). 64 | - Consolidate related changes into one change entry if it makes it easier to explain. 65 | - Run `yarn auto-changelog validate --rc` to check that the changelog is correctly formatted. 66 | 67 | 5. Review and QA the release. 68 | 69 | - If changes are made to the base branch, the release branch will need to be updated with these changes and review/QA will need to restart again. As such, it's probably best to avoid merging other PRs into the base branch while review is underway. 70 | 71 | 6. Squash & Merge the release. 72 | 73 | - This should trigger the [`action-publish-release`](https://github.com/MetaMask/action-publish-release) workflow to tag the final release commit and publish the release on GitHub. 74 | 75 | 7. Publish the release on npm. 76 | 77 | - Wait for the `publish-release` GitHub Action workflow to finish. This should trigger a second job (`publish-npm`), which will wait for a run approval by the [`npm publishers`](https://github.com/orgs/MetaMask/teams/npm-publishers) team. 78 | - Approve the `publish-npm` job (or ask somebody on the npm publishers team to approve it for you). 79 | - Once the `publish-npm` job has finished, check npm to verify that it has been published. 80 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import base, { createConfig } from '@metamask/eslint-config'; 4 | import nodejs from '@metamask/eslint-config-nodejs'; 5 | import typescript from '@metamask/eslint-config-typescript'; 6 | import vitest from '@metamask/eslint-config-vitest'; 7 | 8 | const config = createConfig([ 9 | { 10 | ignores: ['.yarn/'], 11 | }, 12 | 13 | ...base, 14 | ...nodejs, 15 | 16 | { 17 | files: [ 18 | '**/*.ts', 19 | '**/*.tsx', 20 | '**/*.mts', 21 | '**/*.cts', 22 | '**/*.mtsx', 23 | '**/*.ctsx', 24 | ], 25 | extends: typescript, 26 | }, 27 | 28 | { 29 | files: ['**/*.test.mjs'], 30 | extends: vitest, 31 | }, 32 | 33 | { 34 | name: 'main', 35 | files: ['**/*.js', '**/*.mjs'], 36 | 37 | languageOptions: { 38 | sourceType: 'module', 39 | }, 40 | 41 | rules: { 42 | 'import-x/extensions': ['error', 'ignorePackages'], 43 | 'import-x/no-dynamic-require': 'off', 44 | 'import-x/no-nodejs-modules': 'off', 45 | 'import-x/no-useless-path-segments': [ 46 | 'error', 47 | { 48 | noUselessIndex: false, 49 | }, 50 | ], 51 | 'jsdoc/check-tag-names': 'off', 52 | 'jsdoc/no-types': 'off', 53 | 'n/global-require': 'off', 54 | 'n/no-process-exit': 'off', 55 | 'n/no-sync': 'off', 56 | 'n/no-unpublished-require': 'off', 57 | }, 58 | }, 59 | ]); 60 | 61 | export default config; 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-root", 3 | "version": "14.0.0", 4 | "private": true, 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/MetaMask/eslint-config.git" 8 | }, 9 | "type": "module", 10 | "workspaces": [ 11 | "packages/*" 12 | ], 13 | "scripts": { 14 | "changelog:update": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:update", 15 | "changelog:validate": "yarn workspaces foreach --all --no-private --parallel --interlaced --verbose run changelog:validate", 16 | "generate": "node scripts/generate-configs.mjs", 17 | "lint": "yarn lint:eslint && yarn lint:misc --check && yarn lint:config-validation", 18 | "lint:config-validation": "node scripts/validate-configs.mjs", 19 | "lint:eslint": "eslint", 20 | "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:config-validation --write", 21 | "lint:misc": "prettier '**/*.json' '!**/rules-snapshot.json' '**/*.md' '!**/CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore", 22 | "test": "vitest" 23 | }, 24 | "devDependencies": { 25 | "@eslint/config-array": "^0.18.0", 26 | "@eslint/js": "^9.11.0", 27 | "@lavamoat/allow-scripts": "^3.0.4", 28 | "@metamask/auto-changelog": "^3.4.4", 29 | "@metamask/create-release-branch": "^4.0.0", 30 | "@metamask/eslint-config": "workspace:^", 31 | "@metamask/eslint-config-nodejs": "workspace:^", 32 | "@metamask/eslint-config-typescript": "workspace:^", 33 | "@metamask/eslint-config-vitest": "workspace:^", 34 | "@metamask/utils": "^9.1.0", 35 | "@types/eslint__js": "^8.42.3", 36 | "@types/node": "^22.5.5", 37 | "eslint": "^9.11.0", 38 | "eslint-config-prettier": "^9.1.0", 39 | "eslint-plugin-import-x": "^4.3.0", 40 | "eslint-plugin-jest": "^28.8.3", 41 | "eslint-plugin-jsdoc": "^50.2.4", 42 | "eslint-plugin-n": "^17.10.3", 43 | "eslint-plugin-prettier": "^5.2.1", 44 | "fast-deep-equal": "^3.1.3", 45 | "globals": "^15.9.0", 46 | "prettier": "^3.3.3", 47 | "prettier-plugin-packagejson": "^2.5.2", 48 | "typescript": "~5.8.0", 49 | "typescript-eslint": "^8.28.0", 50 | "vite": "^5.4.18", 51 | "vitest": "^2.1.9" 52 | }, 53 | "packageManager": "yarn@4.5.0", 54 | "engines": { 55 | "node": "^18.18 || >=20" 56 | }, 57 | "lavamoat": { 58 | "allowScripts": { 59 | "@lavamoat/preinstall-always-fail": false, 60 | "vite>esbuild": true 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/base/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Changed 13 | 14 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 15 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 17 | use flat configs. The legacy config format is no longer supported. 18 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 19 | - **BREAKING:** Bump peer dependency on `eslint-config-prettier` from `^8.5.0` to `^9.1.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 20 | - **BREAKING:** Bump peer dependency on `eslint-plugin-import-x` from `^0.5.1` to `^4.3.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 21 | - **BREAKING:** Bump peer dependency on `eslint-plugin-jsdoc` from `>=43.0.7 <48` to `^50.2.4` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 22 | - **BREAKING:** Bump peer dependency on `eslint-plugin-prettier` from `^4.2.1` to `^5.2.1` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 23 | - **BREAKING:** Bump peer dependency on `eslint-plugin-promise` from `^6.1.1` to `^7.1.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 24 | - **BREAKING:** Bump peer dependency on `prettier` from `^2.7.1` to `^3.3.3` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 25 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 26 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 27 | be pure ESM. This means that the package can no longer be used with CommonJS 28 | `require` syntax. 29 | 30 | ## [13.0.0] 31 | 32 | ### Changed 33 | 34 | - **BREAKING**: Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 35 | - **BREAKING**: Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 36 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 37 | - **BREAKING**: Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 38 | 39 | ## [12.2.0] 40 | 41 | ### Changed 42 | 43 | - Remove deprecated rule `jsdoc/newline-after-description` ([#290](https://github.com/MetaMask/eslint-config/pull/290)) 44 | - This means the plugin can now be used with `eslint-plugin-jsdoc` versions `42` and above. 45 | 46 | ### Fixed 47 | 48 | - Pin `eslint-plugin-import@~2.26.0` to avoid a regression in `eslint-plugin-import@2.27.0` ([#307](https://github.com/MetaMask/eslint-config/pull/307)) 49 | - Change `endOfLine` rules to better support linting on Windows ([#311](https://github.com/MetaMask/eslint-config/pull/311)) 50 | 51 | ## [12.1.0] 52 | 53 | ### Changed 54 | 55 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 56 | 57 | ## [12.0.0] 58 | 59 | ### Added 60 | 61 | - **BREAKING:** Add `eslint-plugin-promise` peer dependency, and enable `no-multiple-resolved` ([#287](https://github.com/MetaMask/eslint-config/pull/287)) 62 | 63 | ## [11.1.0] 64 | 65 | ### Changed 66 | 67 | - Exclude test files from package ([#266](https://github.com/MetaMask/eslint-config/pull/266)) 68 | 69 | ## [11.0.2] 70 | 71 | ### Changed 72 | 73 | - Stop requiring newlines between multiline blocks/expressions ([#263](https://github.com/MetaMask/eslint-config/pull/263)) 74 | 75 | ## [11.0.1] 76 | 77 | ### Fixed 78 | 79 | - Enable function expressions again ([#258](https://github.com/MetaMask/eslint-config/pull/258)) 80 | - We didn't realize this rule would disallow class methods, even class constructors. This was too disruptive. 81 | 82 | ## [11.0.0] 83 | 84 | ### Added 85 | 86 | - **BREAKING:** Enable id-denylist and id-length in base config ([#200](https://github.com/MetaMask/eslint-config/pull/200)) 87 | - **BREAKING:** Add rules for hybrid Node.js and browser environments ([#242](https://github.com/MetaMask/eslint-config/pull/242)) 88 | - The base config now only allows globals and modules that are available in both Node.js and browsers. 89 | - This adds a new `@metamask/eslint-config-browser` package, to be used in browser-only environments. 90 | - The `@metamask/eslint-config-nodejs` package has been updated to allow Node.js-only globals and modules. 91 | 92 | ### Changed 93 | 94 | - **BREAKING:** Remove no-undef in favour of custom environments configuration ([#254](https://github.com/MetaMask/eslint-config/pull/254)) 95 | - **BREAKING:** Bump all ESLint dependencies to the latest version ([#252](https://github.com/MetaMask/eslint-config/pull/252)) 96 | - This includes peer dependencies. 97 | - **BREAKING:** Automatically sort imports ([#248](https://github.com/MetaMask/eslint-config/pull/248)) 98 | - **BREAKING:** Disable more undesired syntax ([#207](https://github.com/MetaMask/eslint-config/pull/207)) 99 | - This disables the `with` statement, function expressions, and the `in` operator. 100 | 101 | ## [10.0.0] 102 | 103 | ### Changed 104 | 105 | - **BREAKING:** Update ESLint from v7 to v8 ([#233](https://github.com/MetaMask/eslint-config/pull/233)) 106 | - This is breaking because `eslint` is a `peerDependency`. 107 | - Four new rules have been added: 108 | - [`no-loss-of-precision`](https://eslint.org/docs/latest/rules/no-loss-of-precision) 109 | - [`no-nonoctal-decimal-escape`](https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape) 110 | - [`no-unsafe-optional-chaining`](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) 111 | - [`no-useless-backreference`](https://eslint.org/docs/latest/rules/no-useless-backreference) 112 | - **BREAKING:** Update `eslint-plugin-prettier` from v3 to v4 ([#231](https://github.com/MetaMask/eslint-config/pull/231)) 113 | - This is breaking beacuse `eslint-plugin-prettier` is a `peerDependency`. 114 | - **BREAKING:** Update minimum Node.js version to v14 ([#225](https://github.com/MetaMask/eslint-config/pull/225)) 115 | - Ignore rest siblings for `no-unused-vars` ([#213](https://github.com/MetaMask/eslint-config/pull/213)) 116 | - This makes the `no-unused-vars` rule more permissive 117 | 118 | ## [9.0.0] 119 | 120 | ### Added 121 | 122 | - **BREAKING:** Add JSDoc ESLint rules ([#203](https://github.com/MetaMask/eslint-config/pull/203)) 123 | 124 | ## [8.0.0] 125 | 126 | ### Changed 127 | 128 | - **BREAKING:** Require newlines between multiline blocks and expressions ([#197](https://github.com/MetaMask/eslint-config/pull/197)) 129 | 130 | ## [7.0.1] 131 | 132 | ### Fixed 133 | 134 | - Restore default `parserOptions` ([#193](https://github.com/MetaMask/eslint-config/pull/193)) 135 | - By extending the recommended `eslint-plugin-import` rules, we accidentally changed the default `parserOptions.sourceType` to `module`. 136 | The `sourceType` is now explicitly set to `script`. 137 | - In some cases, `parserOptions.ecmaVersion` could also be set to an incorrect version. 138 | The `ecmaVersion` is now explicitly set to `2017`, matching the corresponding setting in `env`. 139 | 140 | ## [7.0.0] 141 | 142 | ### Changed 143 | 144 | - **BREAKING:** Update Prettier `quoteProps` rule to `as-needed` ([#181](https://github.com/MetaMask/eslint-config/pull/181)) 145 | - **BREAKING:** Update ESLint `no-shadow` config ([#168](https://github.com/MetaMask/eslint-config/pull/168)) 146 | - Use recommended `eslint-plugin-import` rule sets ([#184](https://github.com/MetaMask/eslint-config/pull/184)) 147 | - This only removed or disabled rules, and is not breaking. 148 | - Update install instructions in readme ([#185](https://github.com/MetaMask/eslint-config/pull/185)) 149 | - Normalize rule config values ([#169](https://github.com/MetaMask/eslint-config/pull/169)) 150 | 151 | ## [6.0.0] - 2021-04-08 152 | 153 | ### Changed 154 | 155 | - **BREAKING:** Set minimum Node.js version to `^12.0.0` ([#144](https://github.com/MetaMask/eslint-config/pull/144)) 156 | - **BREAKING:** Set ECMAScript version to `es2017`/`8` ([#150](https://github.com/MetaMask/eslint-config/pull/150)) 157 | - **BREAKING:** Add the [Prettier](https://prettier.io) ESLint plugin and extend the recommended Prettier ESLint config ([#96](https://github.com/MetaMask/eslint-config/pull/96)) 158 | - See [here](https://github.com/prettier/eslint-plugin-prettier/blob/d993f24/eslint-plugin-prettier.js#L62-L73) for the `eslint-plugin-prettier` rules and [here](https://github.com/prettier/eslint-config-prettier/blob/abf3ba1/index.js) for the rules of `eslint-config-prettier`, which the plugin extends. 159 | - The rules of this config should otherwise be unchanged. 160 | - Update `eslint` and other ESLint peer dependencies ([#151](https://github.com/MetaMask/eslint-config/pull/151)) 161 | 162 | ### Removed 163 | 164 | - **BREAKING:** All configs except the base config ([#141](https://github.com/MetaMask/eslint-config/pull/141)) 165 | - All configs are now published as separate packages, and must be extended by referencing their package names: 166 | - [`@metamask/eslint-config`](https://npmjs.com/package/@metamask/eslint-config) (the base config) 167 | - [`@metamask/eslint-config-jest`](https://npmjs.com/package/@metamask/eslint-config-jest) 168 | - [`@metamask/eslint-config-mocha`](https://npmjs.com/package/@metamask/eslint-config-mocha) 169 | - [`@metamask/eslint-config-nodejs`](https://npmjs.com/package/@metamask/eslint-config-nodejs) 170 | - [`@metamask/eslint-config-typescript`](https://npmjs.com/package/@metamask/eslint-config-typescript) 171 | 172 | ## [5.0.0] - 2021-02-02 173 | 174 | ### Changed 175 | 176 | - **BREAKING:** Enable `semi` in base config ([#101](https://github.com/MetaMask/eslint-config/pull/101)) 177 | - **BREAKING:** Disallow spaces before parentheses of named functions ([#101](https://github.com/MetaMask/eslint-config/pull/101)) 178 | - **BREAKING:** Upgrade to TypeScript v4 and corresponding `@typescript-eslint` dependencies ([#79](https://github.com/MetaMask/eslint-config/pull/79), [#80](https://github.com/MetaMask/eslint-config/pull/80), [#103](https://github.com/MetaMask/eslint-config/pull/103)) 179 | 180 | ## [4.1.0] - 2020-10-21 181 | 182 | ### Changed 183 | 184 | - Disable `node/no-missing-import` ([#75](https://github.com/MetaMask/eslint-config/pull/75)) 185 | - Disable `node/no-missing-require` ([#75](https://github.com/MetaMask/eslint-config/pull/75)) 186 | 187 | ## [4.0.0] - 2020-10-20 188 | 189 | ### Changed 190 | 191 | - **BREAKING:** Update to ESLint v7 (#46, #58, #62, #63) 192 | - Relax `member-delimiter-style` for TypeScript ([#68](https://github.com/MetaMask/eslint-config/pull/68)) 193 | - Disable `space-before-function-paren` for TypeScript ([#65](https://github.com/MetaMask/eslint-config/pull/65)) 194 | 195 | ## [3.2.0] - 2020-08-20 196 | 197 | ### Changed 198 | 199 | - Relax `prefer-destructuring` rules (#57) 200 | 201 | ## [3.1.0] - 2020-08-19 202 | 203 | ### Changed 204 | 205 | - Disable prefer-object-spread (#54) 206 | 207 | ## [3.0.0] - 2020-08-11 208 | 209 | ### Changed 210 | 211 | - Disallow all anonymous default exports (#52) 212 | - Set maximum empty lines to 1 (#51) 213 | 214 | ## [2.2.0] - 2020-07-14 215 | 216 | ### Changed 217 | 218 | - Relax no-plusplus rule (#44) 219 | 220 | ## [2.1.1] - 2020-04-17 221 | 222 | ### Changed 223 | 224 | - Disable `require-await` (#37) 225 | 226 | ## [2.1.0] - 2020-02-24 227 | 228 | ### Changed 229 | 230 | - Disable `@typescript-eslint/no-extra-parens` (#29) 231 | 232 | ## [2.0.0] - 2020-02-20 233 | 234 | ### Added 235 | 236 | - Add import rules to base config (#24) 237 | - Clarified TypeScript config & publishing docs 238 | 239 | ### Changed 240 | 241 | - Explicitly specify all core rules (#17) 242 | - Update TypeScript config (#25) 243 | 244 | ### Removed 245 | 246 | - Remove root flag from TS config (#20) 247 | 248 | ## [1.2.0] - 2020-02-18 249 | 250 | ### Changed 251 | 252 | - Disable Jest lowercase-name for describe blocks (#14) 253 | 254 | ## [1.1.0] - 2020-02-11 255 | 256 | ### Added 257 | 258 | - Add README file 259 | - Add Mocha config (#13) 260 | 261 | ## [1.0.0] - 2020-01-21 262 | 263 | ### Added 264 | 265 | - Add base, TypeScript, and Jest configs (#3) 266 | 267 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@14.0.0...HEAD 268 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@13.0.0...@metamask/eslint-config@14.0.0 269 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@12.2.0...@metamask/eslint-config@13.0.0 270 | [12.2.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@12.1.0...@metamask/eslint-config@12.2.0 271 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@12.0.0...@metamask/eslint-config@12.1.0 272 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@11.1.0...@metamask/eslint-config@12.0.0 273 | [11.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@11.0.2...@metamask/eslint-config@11.1.0 274 | [11.0.2]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@11.0.1...@metamask/eslint-config@11.0.2 275 | [11.0.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@11.0.0...@metamask/eslint-config@11.0.1 276 | [11.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@10.0.0...@metamask/eslint-config@11.0.0 277 | [10.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@9.0.0...@metamask/eslint-config@10.0.0 278 | [9.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@8.0.0...@metamask/eslint-config@9.0.0 279 | [8.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@7.0.1...@metamask/eslint-config@8.0.0 280 | [7.0.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@7.0.0...@metamask/eslint-config@7.0.1 281 | [7.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@6.0.0...@metamask/eslint-config@7.0.0 282 | [6.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@5.0.0...@metamask/eslint-config@6.0.0 283 | [5.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@4.1.0...@metamask/eslint-config@5.0.0 284 | [4.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@4.0.0...@metamask/eslint-config@4.1.0 285 | [4.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@3.2.0...@metamask/eslint-config@4.0.0 286 | [3.2.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@3.1.0...@metamask/eslint-config@3.2.0 287 | [3.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@3.0.0...@metamask/eslint-config@3.1.0 288 | [3.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@2.2.0...@metamask/eslint-config@3.0.0 289 | [2.2.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@2.1.1...@metamask/eslint-config@2.2.0 290 | [2.1.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@2.1.0...@metamask/eslint-config@2.1.1 291 | [2.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@2.0.0...@metamask/eslint-config@2.1.0 292 | [2.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@1.2.0...@metamask/eslint-config@2.0.0 293 | [1.2.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@1.1.0...@metamask/eslint-config@1.2.0 294 | [1.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config@1.0.0...@metamask/eslint-config@1.1.0 295 | [1.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config@1.0.0 296 | -------------------------------------------------------------------------------- /packages/base/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/base/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config` 2 | 3 | MetaMask's base ESLint configuration. 4 | 5 | ## Usage 6 | 7 | Our default export contains a base set of ESLint rules for ES6+: 8 | 9 | ```bash 10 | yarn add --dev \ 11 | @metamask/eslint-config@^14.0.0 \ 12 | eslint@^9.11.0 \ 13 | eslint-config-prettier@^9.1.0 \ 14 | eslint-plugin-import-x@^4.3.0 \ 15 | eslint-plugin-jsdoc@^50.2.4 \ 16 | eslint-plugin-prettier@^5.2.1 \ 17 | eslint-plugin-promise@^7.1.0 \ 18 | prettier@^3.3.3 19 | ``` 20 | 21 | The order in which you extend ESLint rules matters. 22 | The `@metamask/*` eslint configs should be added to the config array _last_, 23 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 24 | order thereafter. 25 | 26 | ```js 27 | import base, { createConfig } from '@metamask/eslint-config'; 28 | 29 | const config = createConfig({ 30 | { 31 | extends: [ 32 | // Any custom shared config should be added here. 33 | // ... 34 | 35 | // This should be added last unless you know what you're doing. 36 | base, 37 | ], 38 | } 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint config.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config", 32 | "publish": "npm publish", 33 | "test": "eslint ." 34 | }, 35 | "dependencies": { 36 | "@eslint/js": "^9.11.0", 37 | "globals": "^15.9.0" 38 | }, 39 | "devDependencies": { 40 | "@jest/globals": "^29.7.0", 41 | "@metamask/auto-changelog": "^3.4.4", 42 | "eslint": "^9.11.0", 43 | "eslint-config-prettier": "^9.1.0", 44 | "eslint-plugin-import-x": "^4.3.0", 45 | "eslint-plugin-jsdoc": "^50.2.4", 46 | "eslint-plugin-prettier": "^5.2.1", 47 | "eslint-plugin-promise": "^7.1.0", 48 | "prettier": "^3.3.3", 49 | "vitest": "^2.1.9" 50 | }, 51 | "peerDependencies": { 52 | "eslint": "^9.11.0", 53 | "eslint-config-prettier": "^9.1.0", 54 | "eslint-plugin-import-x": "^4.3.0", 55 | "eslint-plugin-jsdoc": "^50.2.4", 56 | "eslint-plugin-prettier": "^5.2.1", 57 | "eslint-plugin-promise": "^7.1.0", 58 | "prettier": "^3.3.3" 59 | }, 60 | "engines": { 61 | "node": "^18.18 || >=20" 62 | }, 63 | "publishConfig": { 64 | "access": "public", 65 | "registry": "https://registry.npmjs.org/" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/base/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config' { 2 | import type { Linter } from 'eslint'; 3 | 4 | /** 5 | * An ESLint configuration object. 6 | */ 7 | type Config = Linter.Config; 8 | 9 | /** 10 | * An ESLint configuration object that may extend other configurations. This 11 | * can only be used with the {@link createConfig} function. 12 | */ 13 | type ConfigWithExtends = Config & { 14 | extends?: Config | Config[] | Config[][]; 15 | }; 16 | 17 | /** 18 | * Create a config object that extends other configs. 19 | * 20 | * ESLint 9 removed support for extending arrays of configs, so this function 21 | * provides a workaround. It takes an array of config objects, where each object 22 | * may have an `extends` property that is an array of other config objects. 23 | * 24 | * This function is inspired by the `config` function in the `typescript-eslint` 25 | * package, but to avoid a dependency on that package, this function is 26 | * implemented here. 27 | * 28 | * @param configs - An array of config objects. 29 | * @returns An array of config objects with all `extends` properties 30 | * resolved. 31 | * @example Basic usage. 32 | * import { createConfig } from '@metamask/eslint-config'; 33 | * import typescript from '@metamask/eslint-config-typescript'; 34 | * 35 | * const configs = createConfig([ 36 | * { 37 | * files: ['**\/*.ts'], 38 | * extends: typescript, 39 | * }, 40 | * ]); 41 | * 42 | * export default configs; 43 | * 44 | * @example Multiple extends are supported as well. 45 | * import { createConfig } from '@metamask/eslint-config'; 46 | * import typescript from '@metamask/eslint-config-typescript'; 47 | * import nodejs from '@metamask/eslint-config-nodejs'; 48 | * 49 | * const configs = createConfig([ 50 | * { 51 | * files: ['**\/*.ts'], 52 | * extends: [typescript, nodejs], 53 | * }, 54 | * ]); 55 | * 56 | * export default configs; 57 | */ 58 | export function createConfig( 59 | configs: ConfigWithExtends | ConfigWithExtends[], 60 | ): Config[]; 61 | 62 | const config: Config[]; 63 | export default config; 64 | } 65 | -------------------------------------------------------------------------------- /packages/base/src/index.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import js from '@eslint/js'; 4 | import importX from 'eslint-plugin-import-x'; 5 | import jsdoc from 'eslint-plugin-jsdoc'; 6 | import prettier from 'eslint-plugin-prettier/recommended'; 7 | // @ts-ignore - `eslint-plugin-promise` doesn't have TypeScript types. 8 | import promise from 'eslint-plugin-promise'; 9 | import globals from 'globals'; 10 | import { createRequire } from 'module'; 11 | 12 | import { createConfig } from './utils.mjs'; 13 | 14 | // TODO: Use import attributes when ESLint supports them. 15 | const customRequire = createRequire(import.meta.url); 16 | const environmentRules = customRequire('./environment.json'); 17 | 18 | /** 19 | * @type {import('eslint').Linter.Config[]} 20 | */ 21 | const rules = createConfig({ 22 | name: '@metamask/eslint-config', 23 | 24 | extends: [ 25 | // Recommended ESLint configuration. 26 | js.configs.recommended, 27 | 28 | // Third-party plugin configurations. 29 | importX.flatConfigs.recommended, 30 | jsdoc.configs['flat/recommended-error'], 31 | prettier, 32 | promise.configs['flat/recommended'], 33 | ], 34 | 35 | languageOptions: { 36 | // The `esXXXX` option under `env` is supposed to set the correct 37 | // `ecmaVersion` option here, but we've had issues with it being 38 | // overridden in the past and therefore set it explicitly. 39 | ecmaVersion: 2022, 40 | parserOptions: { 41 | ecmaVersion: 2022, 42 | }, 43 | 44 | // We want to default to 'script' and only use 'module' explicitly. 45 | sourceType: 'script', 46 | 47 | globals: { 48 | ...globals.es2022, 49 | ...globals['shared-node-browser'], 50 | }, 51 | }, 52 | 53 | rules: { 54 | ...environmentRules, 55 | 56 | /* Prettier rules */ 57 | 'prettier/prettier': [ 58 | 'error', 59 | { 60 | // All of these are defaults except singleQuote and endOfLine, but we specify them 61 | // for explicitness 62 | endOfLine: 'auto', 63 | quoteProps: 'as-needed', 64 | singleQuote: true, 65 | tabWidth: 2, 66 | trailingComma: 'all', 67 | }, 68 | { 69 | // Allow consumers to override this Prettier config. 70 | // This is the default, but we specify it for the sake of clarity. 71 | usePrettierrc: true, 72 | }, 73 | ], 74 | 75 | curly: ['error', 'all'], 76 | 'no-tabs': 'error', 77 | 78 | /* Core rules */ 79 | 'accessor-pairs': 'error', 80 | 'array-callback-return': 'error', 81 | 'block-scoped-var': 'error', 82 | camelcase: [ 83 | 'error', 84 | { 85 | properties: 'never', 86 | allow: ['^UNSAFE_'], 87 | }, 88 | ], 89 | 'consistent-return': 'error', 90 | 'consistent-this': ['error', 'self'], 91 | 'default-case': 'error', 92 | 'default-param-last': 'error', 93 | 'dot-notation': 'error', 94 | eqeqeq: ['error', 'allow-null'], 95 | 'func-name-matching': 'error', 96 | 'grouped-accessor-pairs': 'error', 97 | 'guard-for-in': 'error', 98 | 'id-denylist': [ 99 | // This sets this rule to 'error', the rest are the forbidden IDs. 100 | 'error', 101 | // These are basically all useless contractions. 102 | 'buf', 103 | 'cat', 104 | 'err', 105 | 'cb', 106 | 'cfg', 107 | 'hex', 108 | 'int', 109 | 'msg', 110 | 'num', 111 | 'opt', 112 | 'sig', 113 | ], 114 | 'id-length': [ 115 | 'error', 116 | { 117 | min: 2, 118 | properties: 'never', 119 | exceptionPatterns: ['_', 'a', 'b', 'i', 'j', 'k'], 120 | }, 121 | ], 122 | 'lines-between-class-members': 'error', 123 | 'new-cap': [ 124 | 'error', 125 | { 126 | newIsCap: true, 127 | capIsNew: false, 128 | }, 129 | ], 130 | 'no-alert': 'error', 131 | 'no-array-constructor': 'error', 132 | 'no-bitwise': 'error', 133 | 'no-buffer-constructor': 'error', 134 | 'no-caller': 'error', 135 | 'no-constructor-return': 'error', 136 | 'no-div-regex': 'error', 137 | 'no-else-return': 'error', 138 | 'no-empty-function': 'error', 139 | 'no-eq-null': 'error', 140 | 'no-eval': 'error', 141 | 'no-extend-native': 'error', 142 | 'no-extra-bind': 'error', 143 | 'no-extra-label': 'error', 144 | 'no-implicit-coercion': 'error', 145 | 'no-implicit-globals': 'off', 146 | 'no-implied-eval': 'error', 147 | 'no-inner-declarations': ['error', 'functions'], 148 | 'no-invalid-this': 'error', 149 | 'no-iterator': 'error', 150 | 'no-label-var': 'error', 151 | 'no-labels': [ 152 | 'error', 153 | { 154 | allowLoop: false, 155 | allowSwitch: false, 156 | }, 157 | ], 158 | 'no-lone-blocks': 'error', 159 | 'no-lonely-if': 'error', 160 | 'no-loop-func': 'error', 161 | 'no-multi-assign': 'error', 162 | 'no-multi-str': 'error', 163 | 'no-native-reassign': 'error', 164 | 'no-negated-condition': 'error', 165 | 'no-negated-in-lhs': 'error', 166 | 'no-nested-ternary': 'error', 167 | 'no-new': 'error', 168 | 'no-new-func': 'error', 169 | 'no-new-object': 'error', 170 | 'no-new-wrappers': 'error', 171 | 'no-octal-escape': 'error', 172 | 'no-param-reassign': 'error', 173 | 'no-plusplus': [ 174 | 'error', 175 | { 176 | allowForLoopAfterthoughts: true, 177 | }, 178 | ], 179 | 'no-proto': 'error', 180 | 'no-restricted-syntax': [ 181 | 'error', 182 | { 183 | selector: 'WithStatement', 184 | message: 'With statements are not allowed', 185 | }, 186 | { 187 | selector: `BinaryExpression[operator='in']`, 188 | message: 'The "in" operator is not allowed', 189 | }, 190 | // Sequence expressions have potential gotchas with Prettier, and are also 191 | // weird! 192 | { 193 | selector: 'SequenceExpression', 194 | message: 'Sequence expressions are not allowed', 195 | }, 196 | ], 197 | 'no-return-assign': ['error', 'except-parens'], 198 | 'no-script-url': 'error', 199 | 'no-self-compare': 'error', 200 | 'no-shadow': ['error', { builtinGlobals: true }], 201 | 'no-template-curly-in-string': 'error', 202 | 'no-throw-literal': 'error', 203 | 'no-undef-init': 'error', 204 | 'no-unmodified-loop-condition': 'error', 205 | 'no-unneeded-ternary': [ 206 | 'error', 207 | { 208 | defaultAssignment: false, 209 | }, 210 | ], 211 | 'no-unused-expressions': [ 212 | 'error', 213 | { 214 | allowShortCircuit: true, 215 | allowTernary: true, 216 | }, 217 | ], 218 | 'no-unused-vars': [ 219 | 'error', 220 | { 221 | vars: 'all', 222 | args: 'all', 223 | argsIgnorePattern: '[_]+', 224 | ignoreRestSiblings: true, 225 | }, 226 | ], 227 | 'no-use-before-define': [ 228 | 'error', 229 | { 230 | functions: false, 231 | }, 232 | ], 233 | 'no-useless-call': 'error', 234 | 'no-useless-computed-key': 'error', 235 | 'no-useless-concat': 'error', 236 | 'no-useless-constructor': 'error', 237 | 'no-useless-rename': 'error', 238 | 'no-useless-return': 'error', 239 | 'no-var': 'error', 240 | 'no-void': 'error', 241 | 'object-shorthand': 'error', 242 | 'one-var': [ 243 | 'error', 244 | { 245 | initialized: 'never', 246 | }, 247 | ], 248 | 'operator-assignment': 'error', 249 | 'padding-line-between-statements': [ 250 | 'error', 251 | { 252 | blankLine: 'always', 253 | prev: 'directive', 254 | next: '*', 255 | }, 256 | { 257 | blankLine: 'any', 258 | prev: 'directive', 259 | next: 'directive', 260 | }, 261 | ], 262 | 'prefer-const': 'error', 263 | 'prefer-destructuring': [ 264 | 'error', 265 | { 266 | VariableDeclarator: { 267 | array: false, 268 | object: true, 269 | }, 270 | AssignmentExpression: { 271 | array: false, 272 | object: false, 273 | }, 274 | }, 275 | { 276 | enforceForRenamedProperties: false, 277 | }, 278 | ], 279 | 'prefer-numeric-literals': 'error', 280 | 'prefer-promise-reject-errors': 'error', 281 | 'prefer-regex-literals': 'error', 282 | 'prefer-rest-params': 'error', 283 | 'prefer-spread': 'error', 284 | 'prefer-template': 'error', 285 | radix: 'error', 286 | 'require-atomic-updates': 'error', 287 | 'require-unicode-regexp': 'error', 288 | 'spaced-comment': [ 289 | 'error', 290 | 'always', 291 | { 292 | markers: [ 293 | 'global', 294 | 'globals', 295 | 'eslint', 296 | 'eslint-disable', 297 | '*package', 298 | '!', 299 | ',', 300 | ], 301 | exceptions: ['=', '-'], 302 | }, 303 | ], 304 | 'symbol-description': 'error', 305 | yoda: ['error', 'never'], 306 | 307 | /* import plugin rules */ 308 | 'import-x/extensions': [ 309 | 'error', 310 | 'never', 311 | { 312 | json: 'always', 313 | }, 314 | ], 315 | 'import-x/first': 'error', 316 | 'import-x/newline-after-import': 'error', 317 | 'import-x/no-absolute-path': 'error', 318 | 'import-x/no-amd': 'error', 319 | 'import-x/no-anonymous-default-export': 'error', 320 | 'import-x/no-duplicates': 'error', 321 | 'import-x/no-dynamic-require': 'error', 322 | 'import-x/no-extraneous-dependencies': 'error', 323 | 'import-x/no-mutable-exports': 'error', 324 | 'import-x/no-named-as-default': 'error', 325 | 'import-x/no-named-as-default-member': 'error', 326 | 'import-x/no-named-default': 'error', 327 | 'import-x/no-nodejs-modules': 'error', 328 | 'import-x/no-self-import': 'error', 329 | 'import-x/no-unassigned-import': 'error', 330 | 'import-x/no-unresolved': [ 331 | 'error', 332 | { 333 | commonjs: true, 334 | }, 335 | ], 336 | 'import-x/no-useless-path-segments': [ 337 | 'error', 338 | { 339 | commonjs: true, 340 | noUselessIndex: true, 341 | }, 342 | ], 343 | 'import-x/no-webpack-loader-syntax': 'error', 344 | 'import-x/order': [ 345 | 'error', 346 | { 347 | // This means that there will always be a newline between the import 348 | // groups as defined below. 349 | 'newlines-between': 'always', 350 | 351 | groups: [ 352 | // "builtin" is Node.js modules that are built into the runtime, and 353 | // "external" is everything else from node_modules. 354 | ['builtin', 'external'], 355 | 356 | // "internal" is unused, but could be used for absolute imports from 357 | // the project root. 358 | ['internal', 'parent', 'sibling', 'index'], 359 | ], 360 | 361 | // Alphabetically sort the imports within each group. 362 | alphabetize: { 363 | order: 'asc', 364 | caseInsensitive: true, 365 | }, 366 | }, 367 | ], 368 | 'import-x/unambiguous': 'error', 369 | 370 | /* jsdoc plugin rules */ 371 | 'jsdoc/check-access': 'error', 372 | 'jsdoc/check-alignment': 'error', 373 | 'jsdoc/check-line-alignment': 'error', 374 | 'jsdoc/check-param-names': 'error', 375 | 'jsdoc/check-property-names': 'error', 376 | 'jsdoc/check-tag-names': 'error', 377 | 'jsdoc/check-types': 'error', 378 | 'jsdoc/check-values': 'error', 379 | 'jsdoc/empty-tags': 'error', 380 | 'jsdoc/implements-on-classes': 'error', 381 | 'jsdoc/match-description': [ 382 | 'error', 383 | { tags: { param: true, returns: true } }, 384 | ], 385 | 'jsdoc/multiline-blocks': 'error', 386 | 'jsdoc/no-bad-blocks': 'error', 387 | 'jsdoc/no-defaults': 'error', 388 | 'jsdoc/no-multi-asterisks': 'error', 389 | 'jsdoc/require-asterisk-prefix': 'error', 390 | 'jsdoc/require-description': 'error', 391 | 'jsdoc/require-hyphen-before-param-description': [ 392 | 'error', 393 | 'always', 394 | { tags: { returns: 'never', template: 'always', throws: 'never' } }, 395 | ], 396 | 'jsdoc/require-jsdoc': 'error', 397 | 'jsdoc/require-param-name': 'error', 398 | 'jsdoc/require-param': ['error', { unnamedRootBase: ['options'] }], 399 | 'jsdoc/require-param-description': 'error', 400 | 'jsdoc/require-param-type': 'error', 401 | 'jsdoc/require-property': 'error', 402 | 'jsdoc/require-property-description': 'error', 403 | 'jsdoc/require-property-name': 'error', 404 | 'jsdoc/require-property-type': 'error', 405 | 'jsdoc/require-returns': 'error', 406 | 'jsdoc/require-returns-check': 'error', 407 | 'jsdoc/require-returns-description': 'error', 408 | 'jsdoc/require-returns-type': 'error', 409 | 'jsdoc/require-yields': 'error', 410 | 'jsdoc/require-yields-check': 'error', 411 | 'jsdoc/tag-lines': [ 412 | 'error', 413 | 'any', 414 | { 415 | startLines: 1, 416 | }, 417 | ], 418 | 'jsdoc/valid-types': 'error', 419 | 420 | /* promise plugin rules */ 421 | 'promise/catch-or-return': [ 422 | 'error', 423 | { 424 | allowFinally: true, 425 | }, 426 | ], 427 | 'promise/param-names': [ 428 | 'error', 429 | { 430 | resolvePattern: '^_?resolve', 431 | rejectPattern: '^_?reject', 432 | }, 433 | ], 434 | }, 435 | }); 436 | 437 | export { createConfig }; 438 | export default rules; 439 | -------------------------------------------------------------------------------- /packages/base/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/base/src/utils.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('eslint').Linter.Config} Config 3 | * @typedef {Config & { extends?: Config | Config[] | Config[][] }} ConfigWithExtends 4 | */ 5 | 6 | /** 7 | * Get an array from a value. If the value is already an array, it is returned 8 | * as is. Otherwise, the value is wrapped in an array. 9 | * 10 | * @template Type 11 | * @param {Type | Type[]} value - The value to convert to an array. 12 | * @returns {Type[]} The value as an array. 13 | */ 14 | function getArray(value) { 15 | return Array.isArray(value) ? value : [value]; 16 | } 17 | 18 | /** 19 | * Get a config object that extends another config object. 20 | * 21 | * @param {Config | Config[]} baseConfig - The base config object. 22 | * @param {{ files?: string[]; ignores?: string[] }} extension - The extension 23 | * object. 24 | * @returns {Config | Config[]} The extended config object. 25 | */ 26 | function getExtendedConfig(baseConfig, extension) { 27 | if (Array.isArray(baseConfig)) { 28 | return baseConfig.map((base) => ({ ...base, ...extension })); 29 | } 30 | 31 | return { ...baseConfig, ...extension }; 32 | } 33 | 34 | /** 35 | * Create a config object that extends other configs. 36 | * 37 | * ESLint 9 removed support for extending arrays of configs, so this function 38 | * provides a workaround. It takes an array of config objects, where each object 39 | * may have an `extends` property that is an array of other config objects. 40 | * 41 | * This function is inspired by the `config` function in the `typescript-eslint` 42 | * package, but to avoid a dependency on that package, this function is 43 | * implemented here. 44 | * 45 | * @param {ConfigWithExtends | ConfigWithExtends[]} configs - An array of config 46 | * objects. 47 | * @returns {Config[]} An array of config objects with all `extends` properties 48 | * resolved. 49 | * @example Basic usage. 50 | * import { createConfig } from '@metamask/eslint-config'; 51 | * import typescript from '@metamask/eslint-config-typescript'; 52 | * 53 | * const configs = createConfig([ 54 | * { 55 | * files: ['**\/*.ts'], 56 | * extends: typescript, 57 | * }, 58 | * ]); 59 | * 60 | * export default configs; 61 | * 62 | * @example Multiple extends are supported as well. 63 | * import { createConfig } from '@metamask/eslint-config'; 64 | * import typescript from '@metamask/eslint-config-typescript'; 65 | * import nodejs from '@metamask/eslint-config-nodejs'; 66 | * 67 | * const configs = createConfig([ 68 | * { 69 | * files: ['**\/*.ts'], 70 | * extends: [typescript, nodejs], 71 | * }, 72 | * ]); 73 | * 74 | * export default configs; 75 | */ 76 | export function createConfig(configs) { 77 | const configsArray = getArray(configs); 78 | 79 | return configsArray.flatMap((configWithExtends) => { 80 | const { extends: extendsValue, ...originalConfig } = configWithExtends; 81 | if (extendsValue === undefined) { 82 | return originalConfig; 83 | } 84 | 85 | const extension = { 86 | ...(originalConfig.files && { files: originalConfig.files }), 87 | ...(originalConfig.ignores && { ignores: originalConfig.ignores }), 88 | }; 89 | 90 | if (Array.isArray(extendsValue)) { 91 | if (extendsValue.length === 0) { 92 | return originalConfig; 93 | } 94 | 95 | return [ 96 | ...extendsValue.flatMap((baseConfig) => 97 | getExtendedConfig(baseConfig, extension), 98 | ), 99 | originalConfig, 100 | ]; 101 | } 102 | 103 | return [getExtendedConfig(extendsValue, extension), originalConfig]; 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /packages/base/src/utils.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { createConfig } from './utils.mjs'; 4 | 5 | describe('createConfig', () => { 6 | it('returns a config array for a single config', () => { 7 | const configs = { files: ['**/*.js'] }; 8 | 9 | const result = createConfig(configs); 10 | expect(result).toStrictEqual([configs]); 11 | }); 12 | 13 | it('returns a config array for an array of configs', () => { 14 | const configs = [{ files: ['**/*.js'] }, { ignores: ['node_modules'] }]; 15 | 16 | const result = createConfig(configs); 17 | expect(result).toStrictEqual(configs); 18 | }); 19 | 20 | it('adds the `files` value to extended configurations', () => { 21 | const baseConfig = { rules: {} }; 22 | const extendedConfig = { extends: [baseConfig], files: ['**/*.js'] }; 23 | 24 | const result = createConfig(extendedConfig); 25 | expect(result).toStrictEqual([ 26 | { files: ['**/*.js'], rules: {} }, 27 | { files: ['**/*.js'] }, 28 | ]); 29 | }); 30 | 31 | it('adds the `ignore` value to extended configurations', () => { 32 | const baseConfig = { files: ['**/*.js'] }; 33 | const extendedConfig = { extends: [baseConfig], ignores: ['node_modules'] }; 34 | 35 | const result = createConfig(extendedConfig); 36 | expect(result).toStrictEqual([ 37 | { files: ['**/*.js'], ignores: ['node_modules'] }, 38 | { ignores: ['node_modules'] }, 39 | ]); 40 | }); 41 | 42 | it('supports a config object as `extends` value', () => { 43 | const baseConfig = { rules: {} }; 44 | const extendedConfig = { extends: baseConfig, files: ['**/*.js'] }; 45 | 46 | const result = createConfig(extendedConfig); 47 | expect(result).toStrictEqual([ 48 | { files: ['**/*.js'], rules: {} }, 49 | { files: ['**/*.js'] }, 50 | ]); 51 | }); 52 | 53 | it('supports a nested config array as `extends` value', () => { 54 | const baseConfig = [ 55 | { rules: { 'foo/bar': 'error' } }, 56 | { languageOptions: {} }, 57 | ]; 58 | 59 | const extendedConfig = { extends: [baseConfig], files: ['**/*.js'] }; 60 | 61 | const result = createConfig(extendedConfig); 62 | expect(result).toStrictEqual([ 63 | { files: ['**/*.js'], rules: { 'foo/bar': 'error' } }, 64 | { files: ['**/*.js'], languageOptions: {} }, 65 | { files: ['**/*.js'] }, 66 | ]); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/browser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Changed 13 | 14 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 15 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 17 | use flat configs. The legacy config format is no longer supported. 18 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 19 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 20 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 21 | be pure ESM. This means that the package can no longer be used with CommonJS 22 | `require` syntax. 23 | 24 | ## [13.0.0] 25 | 26 | ### Changed 27 | 28 | - **BREAKING**: Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 29 | - **BREAKING**: Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 30 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 31 | - **BREAKING**: Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 32 | 33 | ## [12.1.0] 34 | 35 | ### Changed 36 | 37 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 38 | 39 | ## [12.0.0] 40 | 41 | ### Changed 42 | 43 | - **BREAKING:** Update peer dependency `@metamask/eslint-config` to v12 44 | 45 | ## [11.1.0] 46 | 47 | ### Changed 48 | 49 | - Exclude test files from package ([#266](https://github.com/MetaMask/eslint-config/pull/266)) 50 | 51 | ## [11.0.0] 52 | 53 | ### Added 54 | 55 | - Initial release of this package. 56 | 57 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-browser@14.0.0...HEAD 58 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-browser@13.0.0...@metamask/eslint-config-browser@14.0.0 59 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-browser@12.1.0...@metamask/eslint-config-browser@13.0.0 60 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-browser@12.0.0...@metamask/eslint-config-browser@12.1.0 61 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-browser@11.1.0...@metamask/eslint-config-browser@12.0.0 62 | [11.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-browser@11.0.0...@metamask/eslint-config-browser@11.1.0 63 | [11.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-browser@11.0.0 64 | -------------------------------------------------------------------------------- /packages/browser/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/browser/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-browser` 2 | 3 | MetaMask's ESLint configuration for browser environments. 4 | 5 | ## Usage 6 | 7 | Our default export contains a base set of ESLint rules for ES6+: 8 | 9 | ```bash 10 | yarn add --dev \ 11 | @metamask/eslint-config@^14.0.0 \ 12 | @metamask/eslint-config-browser@^14.0.0 \ 13 | eslint@^9.11.0 \ 14 | eslint-config-prettier@^9.1.0 \ 15 | eslint-plugin-import-x@^4.3.0 \ 16 | eslint-plugin-jsdoc@^50.2.4 \ 17 | eslint-plugin-prettier@^5.2.1 \ 18 | eslint-plugin-promise@^7.1.0 \ 19 | prettier@^3.3.3 20 | ``` 21 | 22 | The order in which you extend ESLint rules matters. 23 | The `@metamask/*` eslint configs should be added to the config array _last_, 24 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 25 | order thereafter. 26 | 27 | ```js 28 | import base, { createConfig } from '@metamask/eslint-config'; 29 | import browser from '@metamask/eslint-config-browser'; 30 | 31 | const config = createConfig({ 32 | { 33 | extends: [ 34 | // Any custom shared config should be added here. 35 | // ... 36 | 37 | // This should be added last unless you know what you're doing. 38 | base, 39 | browser, 40 | ], 41 | } 42 | }); 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-browser", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint plugin for browser environments.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-browser", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-browser", 32 | "publish": "npm publish", 33 | "test": "eslint ." 34 | }, 35 | "dependencies": { 36 | "@eslint/js": "^9.11.0", 37 | "globals": "^15.9.0" 38 | }, 39 | "devDependencies": { 40 | "@jest/globals": "^29.7.0", 41 | "@metamask/auto-changelog": "^3.4.4", 42 | "eslint": "^9.11.0", 43 | "eslint-config-prettier": "^9.1.0", 44 | "eslint-plugin-import-x": "^4.3.0", 45 | "eslint-plugin-jsdoc": "^50.2.4", 46 | "eslint-plugin-prettier": "^5.2.1", 47 | "prettier": "^3.3.3", 48 | "vitest": "^2.1.9" 49 | }, 50 | "peerDependencies": { 51 | "@metamask/eslint-config": "workspace:^", 52 | "eslint": "^9.11.0" 53 | }, 54 | "engines": { 55 | "node": "^18.18 || >=20" 56 | }, 57 | "publishConfig": { 58 | "access": "public", 59 | "registry": "https://registry.npmjs.org/" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/browser/rules-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "no-restricted-globals": [ 3 | "error", 4 | { 5 | "name": "__dirname", 6 | "message": "This global is not available in the browser environment." 7 | }, 8 | { 9 | "name": "__filename", 10 | "message": "This global is not available in the browser environment." 11 | }, 12 | { 13 | "name": "Buffer", 14 | "message": "This global is not available in the browser environment." 15 | }, 16 | { 17 | "name": "clearImmediate", 18 | "message": "This global is not available in the browser environment." 19 | }, 20 | { 21 | "name": "exports", 22 | "message": "This global is not available in the browser environment." 23 | }, 24 | { 25 | "name": "global", 26 | "message": "This global is not available in the browser environment." 27 | }, 28 | { 29 | "name": "module", 30 | "message": "This global is not available in the browser environment." 31 | }, 32 | { 33 | "name": "process", 34 | "message": "This global is not available in the browser environment." 35 | }, 36 | { 37 | "name": "require", 38 | "message": "This global is not available in the browser environment." 39 | }, 40 | { 41 | "name": "setImmediate", 42 | "message": "This global is not available in the browser environment." 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/browser/src/environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "no-restricted-globals": [ 3 | "error", 4 | { 5 | "name": "__dirname", 6 | "message": "This global is not available in the browser environment." 7 | }, 8 | { 9 | "name": "__filename", 10 | "message": "This global is not available in the browser environment." 11 | }, 12 | { 13 | "name": "Buffer", 14 | "message": "This global is not available in the browser environment." 15 | }, 16 | { 17 | "name": "clearImmediate", 18 | "message": "This global is not available in the browser environment." 19 | }, 20 | { 21 | "name": "exports", 22 | "message": "This global is not available in the browser environment." 23 | }, 24 | { 25 | "name": "global", 26 | "message": "This global is not available in the browser environment." 27 | }, 28 | { 29 | "name": "module", 30 | "message": "This global is not available in the browser environment." 31 | }, 32 | { 33 | "name": "process", 34 | "message": "This global is not available in the browser environment." 35 | }, 36 | { 37 | "name": "require", 38 | "message": "This global is not available in the browser environment." 39 | }, 40 | { 41 | "name": "setImmediate", 42 | "message": "This global is not available in the browser environment." 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/browser/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-browser' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/browser/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import globals from 'globals'; 3 | import { createRequire } from 'module'; 4 | 5 | // TODO: Use import attributes when ESLint supports them. 6 | const customRequire = createRequire(import.meta.url); 7 | const environmentRules = customRequire('./environment.json'); 8 | 9 | /** 10 | * @type {import('eslint').Linter.Config[]} 11 | */ 12 | const config = createConfig({ 13 | name: '@metamask/eslint-config-browser', 14 | 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | }, 19 | }, 20 | 21 | rules: { 22 | ...environmentRules, 23 | }, 24 | }); 25 | 26 | export default config; 27 | -------------------------------------------------------------------------------- /packages/browser/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/commonjs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Changed 13 | 14 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 15 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 17 | use flat configs. The legacy config format is no longer supported. 18 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 19 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 20 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 21 | be pure ESM. This means that the package can no longer be used with CommonJS 22 | `require` syntax. 23 | 24 | ## [13.0.0] 25 | 26 | ### Changed 27 | 28 | - **BREAKING**: Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 29 | - **BREAKING**: Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 30 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 31 | - **BREAKING**: Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 32 | 33 | ## [12.1.0] 34 | 35 | ### Changed 36 | 37 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 38 | 39 | ## [12.0.0] 40 | 41 | ### Changed 42 | 43 | - **BREAKING:** Add peer dependency `@metamask/eslint-config` and remove ESLint plugin and prettier peer dependencies ([#301](https://github.com/MetaMask/eslint-config/pull/301)) 44 | - In the initial release, this package had the wrong list of peer dependencies. This brings this package back into alignment with the others. 45 | 46 | ## [11.1.0] 47 | 48 | ### Added 49 | 50 | - Initial release of this package ([#267](https://github.com/MetaMask/eslint-config/pull/267)) 51 | 52 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-commonjs@14.0.0...HEAD 53 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-commonjs@13.0.0...@metamask/eslint-config-commonjs@14.0.0 54 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-commonjs@12.1.0...@metamask/eslint-config-commonjs@13.0.0 55 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-commonjs@12.0.0...@metamask/eslint-config-commonjs@12.1.0 56 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-commonjs@11.1.0...@metamask/eslint-config-commonjs@12.0.0 57 | [11.1.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-commonjs@11.1.0 58 | -------------------------------------------------------------------------------- /packages/commonjs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/commonjs/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-commonjs` 2 | 3 | MetaMask's ESLint configuration for projects using CommonJS. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | yarn add --dev \ 9 | @metamask/eslint-config@^13.0.0 \ 10 | @metamask/eslint-config-commonjs@^14.0.0 \ 11 | eslint@^9.11.0 \ 12 | eslint-config-prettier@^9.1.0 \ 13 | eslint-plugin-import-x@^4.3.0 \ 14 | eslint-plugin-jsdoc@^50.2.4 \ 15 | eslint-plugin-prettier@^5.2.1 \ 16 | eslint-plugin-promise@^7.1.0 \ 17 | prettier@^3.3.3 18 | ``` 19 | 20 | The order in which you extend ESLint rules matters. 21 | The `@metamask/*` eslint configs should be added to the config array _last_, 22 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 23 | order thereafter. 24 | 25 | ```js 26 | import base, { createConfig } from '@metamask/eslint-config'; 27 | import commonjs from '@metamask/eslint-config-commonjs'; 28 | 29 | const config = createConfig({ 30 | { 31 | extends: [ 32 | // Any custom shared config should be added here. 33 | // ... 34 | 35 | // This should be added last unless you know what you're doing. 36 | base, 37 | commonjs, 38 | ], 39 | } 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/commonjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-commonjs", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint config for CommonJS projects.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-commonjs", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-commonjs", 32 | "publish": "npm publish", 33 | "test": "eslint ." 34 | }, 35 | "dependencies": { 36 | "@eslint/js": "^9.11.0", 37 | "globals": "^15.9.0" 38 | }, 39 | "devDependencies": { 40 | "@jest/globals": "^29.7.0", 41 | "@metamask/auto-changelog": "^3.4.4", 42 | "eslint": "^9.11.0", 43 | "eslint-config-prettier": "^9.1.0", 44 | "eslint-plugin-import-x": "^4.3.0", 45 | "eslint-plugin-jsdoc": "^50.2.4", 46 | "eslint-plugin-prettier": "^5.2.1", 47 | "prettier": "^3.3.3", 48 | "vitest": "^2.1.9" 49 | }, 50 | "peerDependencies": { 51 | "@metamask/eslint-config": "workspace:^", 52 | "eslint": "^9.11.0" 53 | }, 54 | "engines": { 55 | "node": "^18.18 || >=20" 56 | }, 57 | "publishConfig": { 58 | "access": "public", 59 | "registry": "https://registry.npmjs.org/" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/commonjs/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-commonjs' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/commonjs/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import globals from 'globals'; 3 | import { createRequire } from 'module'; 4 | 5 | // TODO: Use import attributes when ESLint supports them. 6 | const customRequire = createRequire(import.meta.url); 7 | const environmentRules = customRequire('./environment.json'); 8 | 9 | /** 10 | * @type {import('eslint').Linter.Config[]} 11 | */ 12 | const config = createConfig({ 13 | name: '@metamask/eslint-config-commonjs', 14 | 15 | languageOptions: { 16 | globals: { 17 | ...globals.commonjs, 18 | }, 19 | }, 20 | 21 | rules: { 22 | ...environmentRules, 23 | }, 24 | }); 25 | 26 | export default config; 27 | -------------------------------------------------------------------------------- /packages/commonjs/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/jest/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Changed 13 | 14 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 15 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 17 | use flat configs. The legacy config format is no longer supported. 18 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 19 | - **BREAKING:** Bump peer dependency on `eslint-plugin-jest` from `^27.9.0` to `^28.8.3` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 20 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 21 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 22 | be pure ESM. This means that the package can no longer be used with CommonJS 23 | `require` syntax. 24 | 25 | ## [13.0.0] 26 | 27 | ### Changed 28 | 29 | - **BREAKING**: Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 30 | - **BREAKING**: Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 31 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 32 | - **BREAKING**: Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 33 | 34 | ## [12.1.0] 35 | 36 | ### Changed 37 | 38 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 39 | 40 | ## [12.0.0] 41 | 42 | ### Changed 43 | 44 | - **BREAKING:** Update peer dependency `@metamask/eslint-config` to v12 45 | 46 | ## [11.1.0] 47 | 48 | ### Changed 49 | 50 | - Exclude test files from package ([#266](https://github.com/MetaMask/eslint-config/pull/266)) 51 | 52 | ## [11.0.0] 53 | 54 | ### Changed 55 | 56 | - **BREAKING:** Bump all ESLint dependencies to the latest version ([#252](https://github.com/MetaMask/eslint-config/pull/252)) 57 | - This includes peer dependencies. 58 | 59 | ## [10.0.0] 60 | 61 | ### Changed 62 | 63 | - **BREAKING:** Update ESLint from v7 to v8 ([#233](https://github.com/MetaMask/eslint-config/pull/233)) 64 | - This is breaking because `eslint` is a `peerDependency`. 65 | - Four new rules have been added: 66 | - [`no-loss-of-precision`](https://eslint.org/docs/latest/rules/no-loss-of-precision) 67 | - [`no-nonoctal-decimal-escape`](https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape) 68 | - [`no-unsafe-optional-chaining`](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) 69 | - [`no-useless-backreference`](https://eslint.org/docs/latest/rules/no-useless-backreference) 70 | - **BREAKING:** Bump eslint-plugin-jest to ^26.x ([#228](https://github.com/MetaMask/eslint-config/pull/228)) 71 | - This is breaking because `eslint-plugin-jest` is a `peerDependency` 72 | - The rule [`jest/prefer-to-be`](https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/prefer-to-be.md) has replaced the old rules `jest/prefer-to-be-null` and `jest/prefer-to-be-undefined`. This is breaking because it is more broadly applicable than the two it replaces, and may force us to use `toBe` for all primatives (over `toBeEqual` or equivalent). 73 | - Two rules were renamed (`jest/valid-describe` => `jest/valid-describe-callback`, and `jest/lowercase-name` => `jest/prefer-lowercase-title`) 74 | - **BREAKING:** Update minimum Node.js version to v14 ([#225](https://github.com/MetaMask/eslint-config/pull/225)) 75 | 76 | ## [9.0.0] 77 | 78 | ### Added 79 | 80 | - **BREAKING:** Add JSDoc ESLint rules ([#203](https://github.com/MetaMask/eslint-config/pull/203)) 81 | 82 | ## [8.0.0] 83 | 84 | ### Changed 85 | 86 | - **BREAKING:** The peer dependency `@metamask/eslint-config` has been updated from v7 to v8. 87 | 88 | ## [7.0.0] 89 | 90 | ### Changed 91 | 92 | - Update install instructions in readme ([#185](https://github.com/MetaMask/eslint-config/pull/185)) 93 | 94 | ### Fixed 95 | 96 | - Add `@metamask/eslint-config` as a peer dependency ([#186](https://github.com/MetaMask/eslint-config/pull/186)) 97 | - This package is designed to be used in conjunction with the MetaMask base ESLint config, so this should always have been a peer dependency. 98 | 99 | ## [6.0.0] - 2021-04-08 100 | 101 | ### Changed 102 | 103 | - **BREAKING:** Set minimum Node.js version to `^12.0.0` ([#144](https://github.com/MetaMask/eslint-config/pull/144)) 104 | - Publish this config as its own package ([#141](https://github.com/MetaMask/eslint-config/pull/141)) 105 | - The contents of this package were previously published as part of [`@metamask/eslint-config`](https://npmjs.com/package/@metamask/eslint-config). 106 | For changes prior to version `6.0.0`, please refer to the changelog of that package. 107 | - To continue extending this config, install this package and update your `.eslintrc.js` `extends` array to include `@metamask/eslint-config-jest` instead of `@metamask/eslint-config/jest`. 108 | - Update `eslint` and other ESLint peer dependencies ([#151](https://github.com/MetaMask/eslint-config/pull/151)) 109 | 110 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@14.0.0...HEAD 111 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@13.0.0...@metamask/eslint-config-jest@14.0.0 112 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@12.1.0...@metamask/eslint-config-jest@13.0.0 113 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@12.0.0...@metamask/eslint-config-jest@12.1.0 114 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@11.1.0...@metamask/eslint-config-jest@12.0.0 115 | [11.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@11.0.0...@metamask/eslint-config-jest@11.1.0 116 | [11.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@10.0.0...@metamask/eslint-config-jest@11.0.0 117 | [10.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@9.0.0...@metamask/eslint-config-jest@10.0.0 118 | [9.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@8.0.0...@metamask/eslint-config-jest@9.0.0 119 | [8.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@7.0.0...@metamask/eslint-config-jest@8.0.0 120 | [7.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-jest@6.0.0...@metamask/eslint-config-jest@7.0.0 121 | [6.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-jest@6.0.0 122 | -------------------------------------------------------------------------------- /packages/jest/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/jest/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-jest` 2 | 3 | MetaMask's [Jest](https://jestjs.io/) ESLint configuration. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | yarn add --dev \ 9 | @metamask/eslint-config@^14.0.0 \ 10 | @metamask/eslint-config-jest@^14.0.0 \ 11 | eslint@^9.11.0 \ 12 | eslint-config-prettier@^9.1.0 \ 13 | eslint-plugin-import-x@^4.3.0 \ 14 | eslint-plugin-jsdoc@^50.2.4 \ 15 | eslint-plugin-jest@^28.8.3 \ 16 | eslint-plugin-prettier@^5.2.1 \ 17 | eslint-plugin-promise@^7.1.0 \ 18 | prettier@^3.3.3 19 | ``` 20 | 21 | The order in which you extend ESLint rules matters. 22 | The `@metamask/*` eslint configs should be added to the config array _last_, 23 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 24 | order thereafter. 25 | 26 | ```js 27 | import base, { createConfig } from '@metamask/eslint-config'; 28 | import jest from '@metamask/eslint-config-jest'; 29 | 30 | const config = createConfig({ 31 | { 32 | extends: [ 33 | // Any custom shared config should be added here. 34 | // ... 35 | 36 | // This should be added last unless you know what you're doing. 37 | base, 38 | jest, 39 | ], 40 | } 41 | }); 42 | ``` 43 | -------------------------------------------------------------------------------- /packages/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-jest", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint config for Jest.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-jest", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-jest", 32 | "lint:changelog": "auto-changelog validate", 33 | "publish": "npm publish", 34 | "test": "eslint ." 35 | }, 36 | "dependencies": { 37 | "@eslint/js": "^9.11.0", 38 | "globals": "^15.9.0" 39 | }, 40 | "devDependencies": { 41 | "@jest/globals": "^29.7.0", 42 | "@metamask/auto-changelog": "^3.4.4", 43 | "@metamask/eslint-config": "workspace:^", 44 | "eslint": "^9.11.0", 45 | "eslint-config-prettier": "^9.1.0", 46 | "eslint-plugin-import-x": "^4.3.0", 47 | "eslint-plugin-jest": "^28.8.3", 48 | "eslint-plugin-jsdoc": "^50.2.4", 49 | "eslint-plugin-prettier": "^5.2.1", 50 | "jest": "^29.7.0", 51 | "prettier": "^3.3.3", 52 | "vitest": "^2.1.9" 53 | }, 54 | "peerDependencies": { 55 | "@metamask/eslint-config": "workspace:^", 56 | "eslint": "^9.11.0", 57 | "eslint-plugin-jest": "^28.8.3" 58 | }, 59 | "engines": { 60 | "node": "^18.18 || >=20" 61 | }, 62 | "publishConfig": { 63 | "access": "public", 64 | "registry": "https://registry.npmjs.org/" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/jest/rules-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "jest/consistent-test-it": ["error", { "fn": "it" }], 3 | "jest/expect-expect": "warn", 4 | "jest/no-alias-methods": "warn", 5 | "jest/no-commented-out-tests": "warn", 6 | "jest/no-conditional-expect": "error", 7 | "jest/no-deprecated-functions": "error", 8 | "jest/no-disabled-tests": "warn", 9 | "jest/no-done-callback": "error", 10 | "jest/no-duplicate-hooks": "error", 11 | "jest/no-export": "error", 12 | "jest/no-focused-tests": "error", 13 | "jest/no-identical-title": "error", 14 | "jest/no-interpolation-in-snapshots": "error", 15 | "jest/no-jasmine-globals": "error", 16 | "jest/no-mocks-import": "error", 17 | "jest/no-restricted-matchers": [ 18 | "error", 19 | { 20 | "resolves": "Use `expect(await promise)` instead.", 21 | "toBeFalsy": "Avoid `toBeFalsy`", 22 | "toBeTruthy": "Avoid `toBeTruthy`", 23 | "toMatchSnapshot": "Use `toMatchInlineSnapshot()` instead", 24 | "toThrowErrorMatchingSnapshot": "Use `toThrowErrorMatchingInlineSnapshot()` instead" 25 | } 26 | ], 27 | "jest/no-standalone-expect": "error", 28 | "jest/no-test-prefixes": "error", 29 | "jest/no-test-return-statement": "error", 30 | "jest/prefer-hooks-on-top": "error", 31 | "jest/prefer-lowercase-title": ["error", { "ignore": ["describe"] }], 32 | "jest/prefer-spy-on": "error", 33 | "jest/prefer-strict-equal": "error", 34 | "jest/prefer-to-be": "error", 35 | "jest/prefer-to-contain": "error", 36 | "jest/prefer-to-have-length": "error", 37 | "jest/prefer-todo": "error", 38 | "jest/require-to-throw-message": "error", 39 | "jest/require-top-level-describe": "error", 40 | "jest/valid-describe-callback": "error", 41 | "jest/valid-expect": ["error", { "alwaysAwait": true }], 42 | "jest/valid-expect-in-promise": "error", 43 | "jest/valid-title": "error" 44 | } 45 | -------------------------------------------------------------------------------- /packages/jest/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-jest' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/jest/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import jest from 'eslint-plugin-jest'; 3 | import globals from 'globals'; 4 | 5 | /** 6 | * @type {import('eslint').Linter.Config[]} 7 | */ 8 | const config = createConfig({ 9 | name: '@metamask/eslint-config-jest', 10 | 11 | extends: [jest.configs['flat/recommended'], jest.configs['flat/style']], 12 | 13 | languageOptions: { 14 | globals: { 15 | ...globals.jest, 16 | }, 17 | }, 18 | 19 | rules: { 20 | 'jest/consistent-test-it': ['error', { fn: 'it' }], 21 | 'jest/no-duplicate-hooks': 'error', 22 | 'jest/no-test-return-statement': 'error', 23 | 'jest/prefer-hooks-on-top': 'error', 24 | 'jest/prefer-lowercase-title': ['error', { ignore: ['describe'] }], 25 | 'jest/prefer-spy-on': 'error', 26 | 'jest/prefer-strict-equal': 'error', 27 | 'jest/prefer-todo': 'error', 28 | 'jest/require-top-level-describe': 'error', 29 | 'jest/require-to-throw-message': 'error', 30 | 'jest/valid-expect': ['error', { alwaysAwait: true }], 31 | 'jest/no-restricted-matchers': [ 32 | 'error', 33 | { 34 | resolves: 'Use `expect(await promise)` instead.', 35 | toBeFalsy: 'Avoid `toBeFalsy`', 36 | toBeTruthy: 'Avoid `toBeTruthy`', 37 | toMatchSnapshot: 'Use `toMatchInlineSnapshot()` instead', 38 | toThrowErrorMatchingSnapshot: 39 | 'Use `toThrowErrorMatchingInlineSnapshot()` instead', 40 | }, 41 | ], 42 | }, 43 | }); 44 | 45 | export default config; 46 | -------------------------------------------------------------------------------- /packages/jest/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/mocha/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Changed 13 | 14 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 15 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 17 | use flat configs. The legacy config format is no longer supported. 18 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 19 | - **BREAKING:** Bump peer dependency on `eslint-plugin-mocha` from `^10.4.1` to `^10.5.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 20 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 21 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 22 | be pure ESM. This means that the package can no longer be used with CommonJS 23 | `require` syntax. 24 | 25 | ## [13.0.0] 26 | 27 | ### Changed 28 | 29 | - **BREAKING**: Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 30 | - **BREAKING**: Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 31 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 32 | - **BREAKING**: Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 33 | 34 | ## [12.1.0] 35 | 36 | ### Changed 37 | 38 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 39 | 40 | ## [12.0.0] 41 | 42 | ### Changed 43 | 44 | - **BREAKING:** Update peer dependency `@metamask/eslint-config` to v12 45 | 46 | ## [11.1.0] 47 | 48 | ### Changed 49 | 50 | - Exclude test files from package ([#266](https://github.com/MetaMask/eslint-config/pull/266)) 51 | 52 | ## [11.0.0] 53 | 54 | ### Changed 55 | 56 | - **BREAKING:** Bump all ESLint dependencies to the latest version ([#252](https://github.com/MetaMask/eslint-config/pull/252)) 57 | - This includes peer dependencies. 58 | 59 | ## [10.0.0] 60 | 61 | ### Changed 62 | 63 | - **BREAKING:** Update ESLint from v7 to v8 ([#233](https://github.com/MetaMask/eslint-config/pull/233)) 64 | - This is breaking because `eslint` is a `peerDependency`. 65 | - Four new rules have been added: 66 | - [`no-loss-of-precision`](https://eslint.org/docs/latest/rules/no-loss-of-precision) 67 | - [`no-nonoctal-decimal-escape`](https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape) 68 | - [`no-unsafe-optional-chaining`](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) 69 | - [`no-useless-backreference`](https://eslint.org/docs/latest/rules/no-useless-backreference) 70 | - **BREAKING:** Update `eslint-plugin-mocha` from v8 to v10 ([#232](https://github.com/MetaMask/eslint-config/pull/232)) 71 | - This is breaking because `eslint-plugin-mocha` is a `peerDependency`. 72 | - The rule [`mocha/no-empty-description`](https://github.com/lo1tuma/eslint-plugin-mocha/blob/master/docs/rules/no-empty-description.md) was added. 73 | - **BREAKING:** Update minimum Node.js version to v14 ([#225](https://github.com/MetaMask/eslint-config/pull/225)) 74 | - Update Mocha ecmaVersion ([#218](https://github.com/MetaMask/eslint-config/pull/218)) 75 | - This lets us use newer JavaScript features in our Mocha tests. 76 | 77 | ## [9.0.0] 78 | 79 | ### Added 80 | 81 | - **BREAKING:** Add JSDoc ESLint rules ([#203](https://github.com/MetaMask/eslint-config/pull/203)) 82 | 83 | ## [8.0.0] 84 | 85 | ### Changed 86 | 87 | - **BREAKING:** The peer dependency `@metamask/eslint-config` has been updated from v7 to v8. 88 | 89 | ## [7.0.0] 90 | 91 | ### Changed 92 | 93 | - Update install instructions in readme ([#185](https://github.com/MetaMask/eslint-config/pull/185)) 94 | 95 | ### Fixed 96 | 97 | - Add `@metamask/eslint-config` as a peer dependency ([#186](https://github.com/MetaMask/eslint-config/pull/186)) 98 | - This package is designed to be used in conjunction with the MetaMask base ESLint config, so this should always have been a peer dependency. 99 | 100 | ## [6.0.0] - 2021-04-08 101 | 102 | ### Changed 103 | 104 | - **BREAKING:** Set minimum Node.js version to `^12.0.0` ([#144](https://github.com/MetaMask/eslint-config/pull/144)) 105 | - **BREAKING:** Enable [`mocha/no-exports`](https://github.com/lo1tuma/eslint-plugin-mocha/blob/bb203bc/docs/rules/no-exports.md) ([#156](https://github.com/MetaMask/eslint-config/pull/156)) 106 | - Publish this config as its own package ([#141](https://github.com/MetaMask/eslint-config/pull/141)) 107 | - The contents of this package were previously published as part of [`@metamask/eslint-config`](https://npmjs.com/package/@metamask/eslint-config). 108 | For changes prior to version `6.0.0`, please refer to the changelog of that package. 109 | - To continue extending this config, install this package and update your `.eslintrc.js` `extends` array to include `@metamask/eslint-config-mocha` instead of `@metamask/eslint-config/mocha`. 110 | - Update `eslint` and other ESLint peer dependencies ([#151](https://github.com/MetaMask/eslint-config/pull/151)) 111 | 112 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@14.0.0...HEAD 113 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@13.0.0...@metamask/eslint-config-mocha@14.0.0 114 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@12.1.0...@metamask/eslint-config-mocha@13.0.0 115 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@12.0.0...@metamask/eslint-config-mocha@12.1.0 116 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@11.1.0...@metamask/eslint-config-mocha@12.0.0 117 | [11.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@11.0.0...@metamask/eslint-config-mocha@11.1.0 118 | [11.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@10.0.0...@metamask/eslint-config-mocha@11.0.0 119 | [10.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@9.0.0...@metamask/eslint-config-mocha@10.0.0 120 | [9.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@8.0.0...@metamask/eslint-config-mocha@9.0.0 121 | [8.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@7.0.0...@metamask/eslint-config-mocha@8.0.0 122 | [7.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-mocha@6.0.0...@metamask/eslint-config-mocha@7.0.0 123 | [6.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-mocha@6.0.0 124 | -------------------------------------------------------------------------------- /packages/mocha/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/mocha/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-mocha` 2 | 3 | MetaMask's [Mocha](https://mochajs.org/) ESLint configuration. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | yarn add --dev \ 9 | @metamask/eslint-config@^14.0.0 \ 10 | @metamask/eslint-config-mocha@^14.0.0 \ 11 | eslint@^9.11.0 \ 12 | eslint-config-prettier@^9.1.0 \ 13 | eslint-plugin-import-x@^4.3.0 \ 14 | eslint-plugin-jsdoc@^50.2.4 \ 15 | eslint-plugin-mocha@^10.5.0 \ 16 | eslint-plugin-prettier@^5.2.1 \ 17 | eslint-plugin-promise@^7.1.0 \ 18 | prettier@^3.3.3 19 | ``` 20 | 21 | The order in which you extend ESLint rules matters. 22 | The `@metamask/*` eslint configs should be added to the config array _last_, 23 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 24 | order thereafter. 25 | 26 | ```js 27 | import base, { createConfig } from '@metamask/eslint-config'; 28 | import mocha from '@metamask/eslint-config-mocha'; 29 | 30 | const config = createConfig({ 31 | { 32 | extends: [ 33 | // Any custom shared config should be added here. 34 | // ... 35 | 36 | // This should be added last unless you know what you're doing. 37 | base, 38 | mocha, 39 | ], 40 | } 41 | }); 42 | ``` 43 | 44 | If your project has `prefer-arrow-callback` you will need to disable that and 45 | replace it with `mocha/prefer-arrow-callback`. 46 | -------------------------------------------------------------------------------- /packages/mocha/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-mocha", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint config for Mocha.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-mocha", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-mocha", 32 | "publish": "npm publish", 33 | "test": "eslint ." 34 | }, 35 | "dependencies": { 36 | "@eslint/js": "^9.11.0" 37 | }, 38 | "devDependencies": { 39 | "@jest/globals": "^29.7.0", 40 | "@metamask/auto-changelog": "^3.4.4", 41 | "@metamask/eslint-config": "workspace:^", 42 | "eslint": "^9.11.0", 43 | "eslint-config-prettier": "^9.1.0", 44 | "eslint-plugin-import-x": "^4.3.0", 45 | "eslint-plugin-jsdoc": "^50.2.4", 46 | "eslint-plugin-mocha": "^10.5.0", 47 | "eslint-plugin-prettier": "^5.2.1", 48 | "globals": "^15.9.0", 49 | "prettier": "^3.3.3", 50 | "vitest": "^2.1.9" 51 | }, 52 | "peerDependencies": { 53 | "@metamask/eslint-config": "workspace:^", 54 | "eslint": "^9.11.0", 55 | "eslint-plugin-mocha": "^10.5.0" 56 | }, 57 | "engines": { 58 | "node": "^18.18 || >=20" 59 | }, 60 | "publishConfig": { 61 | "access": "public", 62 | "registry": "https://registry.npmjs.org/" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/mocha/rules-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "mocha/consistent-spacing-between-blocks": "error", 3 | "mocha/handle-done-callback": "error", 4 | "mocha/max-top-level-suites": ["error", { "limit": 1 }], 5 | "mocha/no-async-describe": "error", 6 | "mocha/no-empty-description": "error", 7 | "mocha/no-exclusive-tests": "error", 8 | "mocha/no-exports": "error", 9 | "mocha/no-global-tests": "error", 10 | "mocha/no-hooks": "off", 11 | "mocha/no-hooks-for-single-case": "error", 12 | "mocha/no-identical-title": "error", 13 | "mocha/no-mocha-arrows": "error", 14 | "mocha/no-nested-tests": "error", 15 | "mocha/no-pending-tests": "error", 16 | "mocha/no-return-and-callback": "error", 17 | "mocha/no-return-from-async": "error", 18 | "mocha/no-setup-in-describe": "error", 19 | "mocha/no-sibling-hooks": "error", 20 | "mocha/no-skipped-tests": "error", 21 | "mocha/no-synchronous-tests": "off", 22 | "mocha/no-top-level-hooks": "error", 23 | "mocha/prefer-arrow-callback": "off", 24 | "mocha/valid-suite-description": [ 25 | "error", 26 | "^[#_]{0,2}[A-Za-z0-9]", 27 | ["describe", "context", "suite"], 28 | "Invalid test suite description found" 29 | ], 30 | "mocha/valid-test-description": [ 31 | "error", 32 | "^[#_]{0,2}[A-Za-z0-9]", 33 | ["it", "test", "specify"], 34 | "Invalid test description found" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/mocha/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-mocha' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/mocha/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import mocha from 'eslint-plugin-mocha'; 3 | 4 | const SIMPLE_TEST_NAME_REGEX = '^[#_]{0,2}[A-Za-z0-9]'; 5 | 6 | /** 7 | * @type {import('eslint').Linter.Config[]} 8 | */ 9 | const config = createConfig({ 10 | name: '@metamask/eslint-config-mocha', 11 | 12 | extends: [mocha.configs.flat.recommended], 13 | 14 | rules: { 15 | 'mocha/no-exclusive-tests': 'error', 16 | 'mocha/no-hooks-for-single-case': 'error', 17 | 'mocha/no-pending-tests': 'error', 18 | 'mocha/no-return-from-async': 'error', 19 | 'mocha/no-skipped-tests': 'error', 20 | 'mocha/no-top-level-hooks': 'error', 21 | 'mocha/valid-suite-description': [ 22 | 'error', 23 | SIMPLE_TEST_NAME_REGEX, 24 | ['describe', 'context', 'suite'], 25 | 'Invalid test suite description found', 26 | ], 27 | 'mocha/valid-test-description': [ 28 | 'error', 29 | SIMPLE_TEST_NAME_REGEX, 30 | ['it', 'test', 'specify'], 31 | 'Invalid test description found', 32 | ], 33 | }, 34 | }); 35 | 36 | export default config; 37 | -------------------------------------------------------------------------------- /packages/mocha/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/nodejs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Changed 13 | 14 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 15 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 17 | use flat configs. The legacy config format is no longer supported. 18 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 19 | - **BREAKING:** Bump peer dependency on `eslint-plugin-n` from `^16.6.2` to `^17.10.3` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 20 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 21 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 22 | be pure ESM. This means that the package can no longer be used with CommonJS 23 | `require` syntax. 24 | 25 | ## [13.0.0] 26 | 27 | ### Changed 28 | 29 | - **BREAKING**: Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 30 | - **BREAKING**: Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 31 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 32 | - **BREAKING**: Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 33 | 34 | ## [12.1.0] 35 | 36 | ### Changed 37 | 38 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 39 | 40 | ## [12.0.0] 41 | 42 | ### Changed 43 | 44 | - **BREAKING:** Update peer dependency `@metamask/eslint-config` to v12 45 | - **BREAKING:** Replace `eslint-plugin-node` with `eslint-plugin-n` ([#297](https://github.com/MetaMask/eslint-config/pull/297)) 46 | 47 | ## [11.1.0] 48 | 49 | ### Changed 50 | 51 | - Exclude test files from package ([#266](https://github.com/MetaMask/eslint-config/pull/266)) 52 | 53 | ## [11.0.1] 54 | 55 | ### Fixed 56 | 57 | - Disable import/no-nodejs-modules in Node.js config ([#257](https://github.com/MetaMask/eslint-config/pull/257)) 58 | - This rule was added to the base config, but we accidentally forgot to disable it here. 59 | 60 | ## [11.0.0] 61 | 62 | ### Changed 63 | 64 | - **BREAKING:** Remove no-undef in favour of custom environments configuration ([#254](https://github.com/MetaMask/eslint-config/pull/254)) 65 | - This config now only allows globals that are available in Node.js. 66 | - **BREAKING:** Bump all ESLint dependencies to the latest version ([#252](https://github.com/MetaMask/eslint-config/pull/252)) 67 | - This includes peer dependencies. 68 | 69 | ## [10.0.0] 70 | 71 | ### Changed 72 | 73 | - **BREAKING:** Update ESLint from v7 to v8 ([#233](https://github.com/MetaMask/eslint-config/pull/233)) 74 | - This is breaking because `eslint` is a `peerDependency`. 75 | - Four new rules have been added: 76 | - [`no-loss-of-precision`](https://eslint.org/docs/latest/rules/no-loss-of-precision) 77 | - [`no-nonoctal-decimal-escape`](https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape) 78 | - [`no-unsafe-optional-chaining`](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) 79 | - [`no-useless-backreference`](https://eslint.org/docs/latest/rules/no-useless-backreference) 80 | - **BREAKING:** Update minimum Node.js version to v14 ([#225](https://github.com/MetaMask/eslint-config/pull/225)) 81 | 82 | ## [9.0.0] 83 | 84 | ### Added 85 | 86 | - **BREAKING:** Add JSDoc ESLint rules ([#203](https://github.com/MetaMask/eslint-config/pull/203)) 87 | 88 | ## [8.0.0] 89 | 90 | ### Changed 91 | 92 | - **BREAKING:** The peer dependency `@metamask/eslint-config` has been updated from v7 to v8. 93 | 94 | ## [7.0.1] 95 | 96 | ### Fixed 97 | 98 | - Restore default `parserOptions` ([#193](https://github.com/MetaMask/eslint-config/pull/193)) 99 | - By extending the recommended `eslint-plugin-import` rules, we accidentally changed the default `parserOptions.sourceType` to `module`. 100 | The `sourceType` is now explicitly set to `script`. 101 | - In some cases, `parserOptions.ecmaVersion` could also be set to an incorrect version. 102 | The `ecmaVersion` is now explicitly set to `2017`, matching the corresponding setting in `env`. 103 | 104 | ## [7.0.0] 105 | 106 | ### Changed 107 | 108 | - Update install instructions in readme ([#185](https://github.com/MetaMask/eslint-config/pull/185)) 109 | 110 | ### Fixed 111 | 112 | - Add `@metamask/eslint-config` as a peer dependency ([#186](https://github.com/MetaMask/eslint-config/pull/186)) 113 | - This package is designed to be used in conjunction with the MetaMask base ESLint config, so this should always have been a peer dependency. 114 | 115 | ## [6.0.0] - 2021-04-08 116 | 117 | ### Changed 118 | 119 | - **BREAKING:** Set minimum Node.js version to `^12.0.0` ([#144](https://github.com/MetaMask/eslint-config/pull/144)) 120 | - Publish this config as its own package ([#141](https://github.com/MetaMask/eslint-config/pull/141)) 121 | - The contents of this package were previously published as part of [`@metamask/eslint-config`](https://npmjs.com/package/@metamask/eslint-config). 122 | For changes prior to version `6.0.0`, please refer to the changelog of that package. 123 | - To continue extending this config, install this package and update your `.eslintrc.js` `extends` array to include `@metamask/eslint-config-nodejs` instead of `@metamask/eslint-config/nodejs`. 124 | - Update `eslint` and other ESLint peer dependencies ([#151](https://github.com/MetaMask/eslint-config/pull/151)) 125 | 126 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@14.0.0...HEAD 127 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@13.0.0...@metamask/eslint-config-nodejs@14.0.0 128 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@12.1.0...@metamask/eslint-config-nodejs@13.0.0 129 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@12.0.0...@metamask/eslint-config-nodejs@12.1.0 130 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@11.1.0...@metamask/eslint-config-nodejs@12.0.0 131 | [11.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@11.0.1...@metamask/eslint-config-nodejs@11.1.0 132 | [11.0.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@11.0.0...@metamask/eslint-config-nodejs@11.0.1 133 | [11.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@10.0.0...@metamask/eslint-config-nodejs@11.0.0 134 | [10.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@9.0.0...@metamask/eslint-config-nodejs@10.0.0 135 | [9.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@8.0.0...@metamask/eslint-config-nodejs@9.0.0 136 | [8.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@7.0.1...@metamask/eslint-config-nodejs@8.0.0 137 | [7.0.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@7.0.0...@metamask/eslint-config-nodejs@7.0.1 138 | [7.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-nodejs@6.0.0...@metamask/eslint-config-nodejs@7.0.0 139 | [6.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-nodejs@6.0.0 140 | -------------------------------------------------------------------------------- /packages/nodejs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/nodejs/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-nodejs` 2 | 3 | MetaMask's [Node.js](https://nodejs.org) ESLint configuration. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | yarn add --dev \ 9 | @metamask/eslint-config@^14.0.0 \ 10 | @metamask/eslint-config-nodejs@^14.0.0 \ 11 | eslint@^9.11.0 \ 12 | eslint-config-prettier@^9.1.0 \ 13 | eslint-plugin-import-x@^4.3.0 \ 14 | eslint-plugin-jsdoc@^50.2.4 \ 15 | eslint-plugin-n@^17.10.3 \ 16 | eslint-plugin-prettier@^5.2.1 \ 17 | eslint-plugin-promise@^7.1.0 \ 18 | prettier@^3.3.3 19 | ``` 20 | 21 | The order in which you extend ESLint rules matters. 22 | The `@metamask/*` eslint configs should be added to the config array _last_, 23 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 24 | order thereafter. 25 | 26 | ```js 27 | import base, { createConfig } from '@metamask/eslint-config'; 28 | import nodejs from '@metamask/eslint-config-nodejs'; 29 | 30 | const config = createConfig({ 31 | { 32 | extends: [ 33 | // Any custom shared config should be added here. 34 | // ... 35 | 36 | // This should be added last unless you know what you're doing. 37 | base, 38 | nodejs, 39 | ], 40 | } 41 | }); 42 | ``` 43 | 44 | To lint the `.eslintrc.js` file itself, you will **need** to add this config in addition to the base config. 45 | -------------------------------------------------------------------------------- /packages/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-nodejs", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint config for Node.js.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-nodejs", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-nodejs", 32 | "publish": "npm publish", 33 | "test": "eslint ." 34 | }, 35 | "dependencies": { 36 | "@eslint/js": "^9.11.0", 37 | "globals": "^15.9.0" 38 | }, 39 | "devDependencies": { 40 | "@jest/globals": "^29.7.0", 41 | "@metamask/auto-changelog": "^3.4.4", 42 | "@metamask/eslint-config": "workspace:^", 43 | "eslint": "^9.11.0", 44 | "eslint-config-prettier": "^9.1.0", 45 | "eslint-plugin-import-x": "^4.3.0", 46 | "eslint-plugin-jsdoc": "^50.2.4", 47 | "eslint-plugin-n": "^17.10.3", 48 | "eslint-plugin-prettier": "^5.2.1", 49 | "prettier": "^3.3.3", 50 | "vitest": "^2.1.9" 51 | }, 52 | "peerDependencies": { 53 | "@metamask/eslint-config": "workspace:^", 54 | "eslint": "^9.11.0", 55 | "eslint-plugin-n": "^17.10.3" 56 | }, 57 | "engines": { 58 | "node": "^18.18 || >=20" 59 | }, 60 | "publishConfig": { 61 | "access": "public", 62 | "registry": "https://registry.npmjs.org/" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/nodejs/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-nodejs' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/nodejs/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import node from 'eslint-plugin-n'; 3 | import globals from 'globals'; 4 | import { createRequire } from 'module'; 5 | 6 | // TODO: Use import attributes when ESLint supports them. 7 | const customRequire = createRequire(import.meta.url); 8 | const environmentRules = customRequire('./environment.json'); 9 | 10 | /** 11 | * @type {import('eslint').Linter.Config[]} 12 | */ 13 | const config = createConfig({ 14 | name: '@metamask/eslint-config-nodejs', 15 | 16 | extends: [node.configs['flat/recommended']], 17 | 18 | languageOptions: { 19 | globals: { 20 | // See comment below. 21 | ...globals.es2022, 22 | ...globals.node, 23 | }, 24 | 25 | // The EcmaScript version option here and for `env` above need to be set 26 | // to the same values as in the base config, or they will be overwritten 27 | // by the recommended Node.js plugin rules. 28 | ecmaVersion: 2022, 29 | }, 30 | 31 | rules: { 32 | ...environmentRules, 33 | 34 | // Possible Errors 35 | 'n/handle-callback-err': ['error', '^(err|error)$'], 36 | 'n/no-callback-literal': 'error', 37 | 'n/no-missing-import': 'off', // Duplicates `import-x/no-unresolved` 38 | 'n/no-missing-require': 'off', // Duplicates `import-x/no-unresolved` 39 | 'n/no-new-require': 'error', 40 | 'n/no-path-concat': 'error', 41 | 'n/no-unsupported-features/es-syntax': 'off', 42 | 43 | // Stylistic rules 44 | 'n/callback-return': 'error', 45 | 'n/exports-style': 'error', 46 | 'n/global-require': 'error', 47 | 'n/no-mixed-requires': 'error', 48 | 'n/no-process-env': 'error', 49 | 'n/no-restricted-import': 'error', 50 | 'n/no-restricted-require': 'error', 51 | 'n/no-sync': 'error', 52 | 'n/prefer-global/buffer': 'error', 53 | 'n/prefer-global/console': 'error', 54 | 'n/prefer-global/process': 'error', 55 | 'n/prefer-global/text-decoder': 'error', 56 | 'n/prefer-global/text-encoder': 'error', 57 | 'n/prefer-global/url-search-params': 'error', 58 | 'n/prefer-global/url': 'error', 59 | 'n/prefer-promises/dns': 'error', 60 | 'n/prefer-promises/fs': 'error', 61 | 62 | // Enabled in the base config, but this should be allowed in Node.js 63 | // projects. 64 | 'import-x/no-nodejs-modules': 'off', 65 | }, 66 | }); 67 | 68 | export default config; 69 | -------------------------------------------------------------------------------- /packages/nodejs/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/typescript/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [14.0.0] 11 | 12 | ### Added 13 | 14 | - **BREAKING:** Add peer dependency on `typescript-eslint@^8.6.0`. ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 15 | - **BREAKING:** Add peer dependency on `eslint-import-resolver-typescript@^3.6.3` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 16 | - **BREAKING:** Add peer dependency on `eslint-plugin-import-x@^4.3.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 17 | - **BREAKING:** Add peer dependency on `eslint-plugin-jsdoc@^50.2.4` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 18 | 19 | ### Changed 20 | 21 | - **BREAKING:** Bump minimum Node.js version from 16 to 18.18 ([#371](https://github.com/MetaMask/eslint-config/pull/371)) 22 | - **BREAKING:** Bump peer dependency on ESLint from `^8.57.0` to `^9.11.0` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 23 | - ESLint 9 requires flat configs, so this change also rewrites the configs to 24 | use flat configs. The legacy config format is no longer supported. 25 | - **BREAKING:** Bump peer dependency on `@metamask/eslint-config` from `^13.0.0` to `^14.0.0` ([#377](https://github.com/MetaMask/eslint-config/pull/377)) 26 | - **BREAKING:** Change package to be pure ESM ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 27 | - ESLint 9 supports ESM out-of-the-box, so this change updates the package to 28 | be pure ESM. This means that the package can no longer be used with CommonJS 29 | `require` syntax. 30 | 31 | ### Removed 32 | 33 | - Remove peer dependency on `@typescript-eslint/eslint-plugin` and `@typescript-eslint/parser` ([#370](https://github.com/MetaMask/eslint-config/pull/370)) 34 | 35 | ## [13.0.0] 36 | 37 | ### Changed 38 | 39 | - **BREAKING:** Bump TypeScript to v5.5 ([#364](https://github.com/MetaMask/eslint-config/pull/364)) 40 | - Also bump relevant `@typescript-eslint` packages. 41 | - **BREAKING:** Replace `eslint-plugin-import` with `eslint-plugin-import-x` ([#366](https://github.com/MetaMask/eslint-config/pull/366)) 42 | - **BREAKING:** Bump all ESLint dependencies ([#351](https://github.com/MetaMask/eslint-config/pull/351)) 43 | - Bumps all ESLint dependencies to the latest version compatible with Node.js 16. 44 | - **BREAKING:** Bump minimum Node.js version from 14 to 16 ([#332](https://github.com/MetaMask/eslint-config/pull/332), [#339](https://github.com/MetaMask/eslint-config/pull/339)) 45 | 46 | ## [12.1.0] 47 | 48 | ### Changed 49 | 50 | - Add support for typescript 5.0.x, 5.1.x ([#288](https://github.com/MetaMask/eslint-config/pull/288)) 51 | 52 | ## [12.0.0] 53 | 54 | ### Added 55 | 56 | - **BREAKING:** Add rule to enforce generic parameters have a length of at least 3 characters ([#292](https://github.com/MetaMask/eslint-config/pull/292)) 57 | - **BREAKING:** Enable `@typescript-eslint/consistent-type-imports` rule ([#284](https://github.com/MetaMask/eslint-config/pull/284)) 58 | - **BREAKING:** Enable `@typescript-eslint/prefer-enum-initializers` rule ([#269](https://github.com/MetaMask/eslint-config/pull/269)) 59 | 60 | ### Changed 61 | 62 | - **BREAKING:** Update peer dependency `@metamask/eslint-config` to v12 63 | - Disable naming convention for properties that require quotes ([#293](https://github.com/MetaMask/eslint-config/pull/293)) 64 | 65 | ## [11.1.0] 66 | 67 | ### Changed 68 | 69 | - Exclude test files from package ([#266](https://github.com/MetaMask/eslint-config/pull/266)) 70 | 71 | ## [11.0.2] 72 | 73 | ### Changed 74 | 75 | - Allow async functions without any 'await' ([#262](https://github.com/MetaMask/eslint-config/pull/262)) 76 | - Allow parameters to use PascalCase ([#264](https://github.com/MetaMask/eslint-config/pull/264)) 77 | 78 | ## [11.0.0] 79 | 80 | ### Added 81 | 82 | - **BREAKING:** Add rules that require type information ([#250](https://github.com/MetaMask/eslint-config/pull/250)) 83 | - This requires setting some parser options. See the README for more details. 84 | - **BREAKING:** Bump all ESLint dependencies to the latest version ([#252](https://github.com/MetaMask/eslint-config/pull/252)) 85 | - This includes peer dependencies. 86 | - **BREAKING:** Forbid TypeScript's private modifier in favour of hash names ([#244](https://github.com/MetaMask/eslint-config/pull/244)) 87 | 88 | ## [10.0.0] 89 | 90 | ### Changed 91 | 92 | - **BREAKING:** Update ESLint from v7 to v8 ([#233](https://github.com/MetaMask/eslint-config/pull/233)) 93 | - This is breaking because `eslint` is a `peerDependency`. 94 | - Four new rules have been added: 95 | - [`no-loss-of-precision`](https://eslint.org/docs/latest/rules/no-loss-of-precision) 96 | - [`no-nonoctal-decimal-escape`](https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape) 97 | - [`no-unsafe-optional-chaining`](https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining) 98 | - [`no-useless-backreference`](https://eslint.org/docs/latest/rules/no-useless-backreference) 99 | - **BREAKING:** Update `@typescript-eslint` parser and plugin ([#230](https://github.com/MetaMask/eslint-config/pull/230)) 100 | - This is breaking because these two packages are `peerDependencies` 101 | - There are two new rules: 102 | - [`@typescript-eslint/no-loss-of-precision`](https://typescript-eslint.io/rules/no-loss-of-precision) 103 | - [`@typescript-eslint/no-unnecessary-type-constraint`](https://typescript-eslint.io/rules/no-unnecessary-type-constraint). 104 | - **BREAKING:** Update minimum Node.js version to v14 ([#225](https://github.com/MetaMask/eslint-config/pull/225)) 105 | - **BREAKING:** Forbid TypeScript interfaces ([#216](https://github.com/MetaMask/eslint-config/pull/216)) 106 | - Ignore rest siblings for `no-unused-vars` ([#213](https://github.com/MetaMask/eslint-config/pull/213)) 107 | - This makes the `no-unused-vars` rule more permissive 108 | 109 | ## [9.0.1] 110 | 111 | ### Changed 112 | 113 | - Disable `@typescript-eslint/no-throw-literal` ([#210](https://github.com/MetaMask/eslint-config/pull/210)) 114 | - The introduction of this rule was one of the two breaking changes in the previous release. It was included unintentionally, and has now been removed to make updating to v9 easier. 115 | 116 | ## [9.0.0] 117 | 118 | ### Added 119 | 120 | - **BREAKING:** Add JSDoc ESLint rules ([#203](https://github.com/MetaMask/eslint-config/pull/203)) 121 | 122 | ### Changed 123 | 124 | - **BREAKING:** Disable `no-throw-literal` and enable `@typescript-eslint/no-throw-literal` ([#201](https://github.com/MetaMask/eslint-config/pull/201)) 125 | - This rule requires type information, which requires [additional project setup](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md). 126 | - Note: This change has been undone in v9.0.1. You can ignore this change if you're updating to v9.0.1 or greater. 127 | 128 | ## [8.0.0] 129 | 130 | ### Changed 131 | 132 | - **BREAKING:** The peer dependency `@metamask/eslint-config` has been updated from v7 to v8. 133 | 134 | ## [7.0.1] 135 | 136 | ### Fixed 137 | 138 | - Restore default `parserOptions.ecmaVersion` ([#193](https://github.com/MetaMask/eslint-config/pull/193)) 139 | - In some cases, `parserOptions.ecmaVersion` could be set to an incorrect version. 140 | The `ecmaVersion` is now explicitly set to `2020`, matching the corresponding setting in `env`. 141 | 142 | ## [7.0.0] 143 | 144 | ### Changed 145 | 146 | - **BREAKING:** Update `@typescript/no-shadow` config ([#168](https://github.com/MetaMask/eslint-config/pull/168)) 147 | - Use recommended `eslint-plugin-import` rule sets ([#184](https://github.com/MetaMask/eslint-config/pull/184)) 148 | - This only removed or disabled rules, and is not breaking. 149 | - Update install instructions in readme ([#185](https://github.com/MetaMask/eslint-config/pull/185)) 150 | 151 | ### Fixed 152 | 153 | - Add `@metamask/eslint-config` as a peer dependency ([#186](https://github.com/MetaMask/eslint-config/pull/186)) 154 | - This package is designed to be used in conjunction with the MetaMask base ESLint config, so this should always have been a peer dependency. 155 | 156 | ## [6.0.0] - 2021-04-08 157 | 158 | ### Changed 159 | 160 | - **BREAKING:** Set minimum Node.js version to `^12.0.0` ([#144](https://github.com/MetaMask/eslint-config/pull/144)) 161 | - **BREAKING:** Set ECMAScript version to `es2020`/`11` ([#150](https://github.com/MetaMask/eslint-config/pull/150)) 162 | - **BREAKING:** Enable all rules recommended by the `@typescript-eslint` plugin ([#156](https://github.com/MetaMask/eslint-config/pull/156)) 163 | - This amounted to setting the following core ESLint rules to `error`: 164 | - [no-var](https://eslint.org/docs/7.0.0/rules/no-var) 165 | - [prefer-const](https://eslint.org/docs/7.0.0/rules/prefer-const) 166 | - [prefer-rest-params](https://eslint.org/docs/7.0.0/rules/prefer-rest-params) 167 | - [prefer-spread](https://eslint.org/docs/7.0.0/rules/prefer-spread) 168 | - Publish this config as its own package ([#141](https://github.com/MetaMask/eslint-config/pull/141)) 169 | - The contents of this package were previously published as part of [`@metamask/eslint-config`](https://npmjs.com/package/@metamask/eslint-config). 170 | For changes prior to version `6.0.0`, please refer to the changelog of that package. 171 | - To continue extending this config, install this package and update your `.eslintrc.js` `extends` array to include `@metamask/eslint-config-typescript` instead of `@metamask/eslint-config/typescript`. 172 | - Update `eslint` and other ESLint peer dependencies ([#151](https://github.com/MetaMask/eslint-config/pull/151)) 173 | 174 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@14.0.0...HEAD 175 | [14.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@13.0.0...@metamask/eslint-config-typescript@14.0.0 176 | [13.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@12.1.0...@metamask/eslint-config-typescript@13.0.0 177 | [12.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@12.0.0...@metamask/eslint-config-typescript@12.1.0 178 | [12.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@11.1.0...@metamask/eslint-config-typescript@12.0.0 179 | [11.1.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@11.0.2...@metamask/eslint-config-typescript@11.1.0 180 | [11.0.2]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@11.0.0...@metamask/eslint-config-typescript@11.0.2 181 | [11.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@10.0.0...@metamask/eslint-config-typescript@11.0.0 182 | [10.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@9.0.1...@metamask/eslint-config-typescript@10.0.0 183 | [9.0.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@9.0.0...@metamask/eslint-config-typescript@9.0.1 184 | [9.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@8.0.0...@metamask/eslint-config-typescript@9.0.0 185 | [8.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@7.0.1...@metamask/eslint-config-typescript@8.0.0 186 | [7.0.1]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@7.0.0...@metamask/eslint-config-typescript@7.0.1 187 | [7.0.0]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-typescript@6.0.0...@metamask/eslint-config-typescript@7.0.0 188 | [6.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-typescript@6.0.0 189 | -------------------------------------------------------------------------------- /packages/typescript/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/typescript/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-typescript` 2 | 3 | MetaMask's [TypeScript](https://www.typescriptlang.org) ESLint configuration. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | yarn add --dev \ 9 | @metamask/eslint-config@^14.0.0 \ 10 | @metamask/eslint-config-typescript@^14.0.0 \ 11 | eslint@^9.11.0 \ 12 | eslint-config-prettier@^9.1.0 \ 13 | eslint-plugin-import-x@^4.3.0 \ 14 | eslint-plugin-jsdoc@^50.2.4 \ 15 | eslint-plugin-prettier@^5.2.1 \ 16 | eslint-plugin-promise@^7.1.0 \ 17 | prettier@^3.3.3 \ 18 | typescript@~5.8.0 \ 19 | typescript-eslint@^8.28.0 20 | ``` 21 | 22 | The order in which you extend ESLint rules matters. 23 | The `@metamask/*` eslint configs should be added to the config array _last_, 24 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 25 | order thereafter. 26 | 27 | ```js 28 | import base, { createConfig } from '@metamask/eslint-config'; 29 | import typescript from '@metamask/eslint-config-typescript'; 30 | 31 | const config = createConfig({ 32 | { 33 | // The TypeScript config disables certain rules that you want to keep for 34 | // non-TypeScript files, so it should be added in an override. 35 | files: ['**/*.ts', '**/*.mts', '**/*.cts'], 36 | 37 | extends: [ 38 | // Any custom shared config should be added here. 39 | // ... 40 | 41 | // This should be added last unless you know what you're doing. 42 | ...base, 43 | ...typescript, 44 | ], 45 | 46 | languageOptions: { 47 | parserOptions: { 48 | // This is required for rules that use type information. 49 | // See here for more information: https://typescript-eslint.io/getting-started/typed-linting 50 | tsconfigRootDir: import.meta.dirname, 51 | }, 52 | }, 53 | } 54 | }); 55 | ``` 56 | -------------------------------------------------------------------------------- /packages/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-typescript", 3 | "version": "14.0.0", 4 | "description": "Shareable MetaMask ESLint config for TypeScript.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs", 28 | "!src/**/__test__" 29 | ], 30 | "scripts": { 31 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-typescript", 32 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-typescript", 33 | "publish": "npm publish", 34 | "test": "eslint ." 35 | }, 36 | "dependencies": { 37 | "@eslint/js": "^9.11.0" 38 | }, 39 | "devDependencies": { 40 | "@jest/globals": "^29.7.0", 41 | "@metamask/auto-changelog": "^3.4.4", 42 | "@metamask/eslint-config": "workspace:^", 43 | "eslint": "^9.11.0", 44 | "eslint-config-prettier": "^9.1.0", 45 | "eslint-import-resolver-typescript": "^3.6.3", 46 | "eslint-plugin-import-x": "^4.3.0", 47 | "eslint-plugin-jsdoc": "^50.2.4", 48 | "eslint-plugin-prettier": "^5.2.1", 49 | "globals": "^15.9.0", 50 | "prettier": "^3.3.3", 51 | "typescript": "~5.8.0", 52 | "typescript-eslint": "^8.28.0", 53 | "vitest": "^2.1.9" 54 | }, 55 | "peerDependencies": { 56 | "@metamask/eslint-config": "workspace:^", 57 | "eslint": "^9.11.0", 58 | "eslint-import-resolver-typescript": "^3.6.3", 59 | "eslint-plugin-import-x": "^4.3.0", 60 | "eslint-plugin-jsdoc": "^50.2.4", 61 | "typescript": ">=4.8.4 <5.9.0", 62 | "typescript-eslint": "^8.24" 63 | }, 64 | "engines": { 65 | "node": "^18.18 || >=20" 66 | }, 67 | "publishConfig": { 68 | "access": "public", 69 | "registry": "https://registry.npmjs.org/" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/typescript/rules-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "@typescript-eslint/array-type": "error", 3 | "@typescript-eslint/await-thenable": "error", 4 | "@typescript-eslint/ban-ts-comment": "error", 5 | "@typescript-eslint/consistent-type-assertions": "error", 6 | "@typescript-eslint/consistent-type-definitions": ["error", "type"], 7 | "@typescript-eslint/consistent-type-exports": "error", 8 | "@typescript-eslint/default-param-last": "error", 9 | "@typescript-eslint/explicit-function-return-type": "error", 10 | "@typescript-eslint/naming-convention": [ 11 | "error", 12 | { 13 | "selector": "default", 14 | "format": ["camelCase"], 15 | "leadingUnderscore": "allow", 16 | "trailingUnderscore": "forbid" 17 | }, 18 | { "selector": "enumMember", "format": ["PascalCase"] }, 19 | { 20 | "selector": "import", 21 | "format": ["camelCase", "PascalCase", "snake_case", "UPPER_CASE"] 22 | }, 23 | { 24 | "selector": "interface", 25 | "format": ["PascalCase"], 26 | "custom": { "regex": "^I[A-Z]", "match": false } 27 | }, 28 | { 29 | "selector": "objectLiteralMethod", 30 | "format": ["camelCase", "PascalCase", "UPPER_CASE"] 31 | }, 32 | { 33 | "selector": "objectLiteralProperty", 34 | "format": ["camelCase", "PascalCase", "UPPER_CASE"] 35 | }, 36 | { "selector": "typeLike", "format": ["PascalCase"] }, 37 | { 38 | "selector": "typeParameter", 39 | "format": ["PascalCase"], 40 | "custom": { "regex": "^.{3,}", "match": true } 41 | }, 42 | { 43 | "selector": "variable", 44 | "format": ["camelCase", "UPPER_CASE", "PascalCase"], 45 | "leadingUnderscore": "allow" 46 | }, 47 | { 48 | "selector": "parameter", 49 | "format": ["camelCase", "PascalCase"], 50 | "leadingUnderscore": "allow" 51 | }, 52 | { 53 | "selector": [ 54 | "classProperty", 55 | "objectLiteralProperty", 56 | "typeProperty", 57 | "classMethod", 58 | "objectLiteralMethod", 59 | "typeMethod", 60 | "accessor", 61 | "enumMember" 62 | ], 63 | "format": null, 64 | "modifiers": ["requiresQuotes"] 65 | } 66 | ], 67 | "@typescript-eslint/no-array-constructor": "error", 68 | "@typescript-eslint/no-array-delete": "error", 69 | "@typescript-eslint/no-base-to-string": "error", 70 | "@typescript-eslint/no-dupe-class-members": "error", 71 | "@typescript-eslint/no-duplicate-enum-values": "error", 72 | "@typescript-eslint/no-duplicate-type-constituents": "off", 73 | "@typescript-eslint/no-empty-object-type": "error", 74 | "@typescript-eslint/no-explicit-any": "off", 75 | "@typescript-eslint/no-extra-non-null-assertion": "error", 76 | "@typescript-eslint/no-floating-promises": "error", 77 | "@typescript-eslint/no-for-in-array": "error", 78 | "@typescript-eslint/no-implied-eval": "error", 79 | "@typescript-eslint/no-meaningless-void-operator": "error", 80 | "@typescript-eslint/no-misused-new": "error", 81 | "@typescript-eslint/no-misused-promises": "error", 82 | "@typescript-eslint/no-namespace": [ 83 | "error", 84 | { "allowDefinitionFiles": true } 85 | ], 86 | "@typescript-eslint/no-non-null-asserted-optional-chain": "error", 87 | "@typescript-eslint/no-non-null-assertion": "error", 88 | "@typescript-eslint/no-redundant-type-constituents": "off", 89 | "@typescript-eslint/no-require-imports": "error", 90 | "@typescript-eslint/no-shadow": ["error", { "builtinGlobals": true }], 91 | "@typescript-eslint/no-this-alias": "error", 92 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 93 | "@typescript-eslint/no-unnecessary-qualifier": "error", 94 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 95 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 96 | "@typescript-eslint/no-unnecessary-type-constraint": "error", 97 | "@typescript-eslint/no-unsafe-argument": "off", 98 | "@typescript-eslint/no-unsafe-assignment": "off", 99 | "@typescript-eslint/no-unsafe-call": "off", 100 | "@typescript-eslint/no-unsafe-declaration-merging": "error", 101 | "@typescript-eslint/no-unsafe-enum-comparison": "off", 102 | "@typescript-eslint/no-unsafe-function-type": "error", 103 | "@typescript-eslint/no-unsafe-member-access": "off", 104 | "@typescript-eslint/no-unsafe-return": "off", 105 | "@typescript-eslint/no-unsafe-unary-minus": "error", 106 | "@typescript-eslint/no-unused-expressions": [ 107 | "error", 108 | { "allowShortCircuit": true, "allowTernary": true } 109 | ], 110 | "@typescript-eslint/no-unused-vars": [ 111 | "error", 112 | { 113 | "vars": "all", 114 | "args": "all", 115 | "argsIgnorePattern": "[_]+", 116 | "ignoreRestSiblings": true 117 | } 118 | ], 119 | "@typescript-eslint/no-use-before-define": ["error", { "functions": false }], 120 | "@typescript-eslint/no-useless-constructor": "error", 121 | "@typescript-eslint/no-wrapper-object-types": "error", 122 | "@typescript-eslint/only-throw-error": "error", 123 | "@typescript-eslint/parameter-properties": "error", 124 | "@typescript-eslint/prefer-as-const": "error", 125 | "@typescript-eslint/prefer-enum-initializers": "error", 126 | "@typescript-eslint/prefer-for-of": "error", 127 | "@typescript-eslint/prefer-function-type": "error", 128 | "@typescript-eslint/prefer-includes": "error", 129 | "@typescript-eslint/prefer-namespace-keyword": "error", 130 | "@typescript-eslint/prefer-nullish-coalescing": "error", 131 | "@typescript-eslint/prefer-optional-chain": "error", 132 | "@typescript-eslint/prefer-promise-reject-errors": [ 133 | "error", 134 | { "allowThrowingUnknown": true } 135 | ], 136 | "@typescript-eslint/prefer-readonly": "error", 137 | "@typescript-eslint/prefer-reduce-type-parameter": "error", 138 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 139 | "@typescript-eslint/promise-function-async": "error", 140 | "@typescript-eslint/require-await": "off", 141 | "@typescript-eslint/restrict-plus-operands": "error", 142 | "@typescript-eslint/restrict-template-expressions": [ 143 | "error", 144 | { "allowBoolean": true, "allowNumber": true } 145 | ], 146 | "@typescript-eslint/switch-exhaustiveness-check": [ 147 | "error", 148 | { "considerDefaultExhaustiveForUnions": true } 149 | ], 150 | "@typescript-eslint/triple-slash-reference": "error", 151 | "@typescript-eslint/unbound-method": "error", 152 | "@typescript-eslint/unified-signatures": "error", 153 | "constructor-super": "off", 154 | "default-param-last": "off", 155 | "getter-return": "off", 156 | "import-x/consistent-type-specifier-style": ["error", "prefer-top-level"], 157 | "import-x/named": "off", 158 | "import-x/no-unresolved": "off", 159 | "jsdoc/check-access": "error", 160 | "jsdoc/check-alignment": "error", 161 | "jsdoc/check-examples": "off", 162 | "jsdoc/check-indentation": "error", 163 | "jsdoc/check-line-alignment": "off", 164 | "jsdoc/check-param-names": "error", 165 | "jsdoc/check-property-names": "error", 166 | "jsdoc/check-syntax": "error", 167 | "jsdoc/check-tag-names": ["error", { "typed": true }], 168 | "jsdoc/check-template-names": "off", 169 | "jsdoc/check-types": "error", 170 | "jsdoc/check-values": "error", 171 | "jsdoc/convert-to-jsdoc-comments": "off", 172 | "jsdoc/empty-tags": "error", 173 | "jsdoc/implements-on-classes": "error", 174 | "jsdoc/imports-as-dependencies": "off", 175 | "jsdoc/informative-docs": "off", 176 | "jsdoc/lines-before-block": "off", 177 | "jsdoc/match-description": "off", 178 | "jsdoc/match-name": "off", 179 | "jsdoc/multiline-blocks": "error", 180 | "jsdoc/no-bad-blocks": "off", 181 | "jsdoc/no-blank-block-descriptions": "off", 182 | "jsdoc/no-blank-blocks": "off", 183 | "jsdoc/no-defaults": "error", 184 | "jsdoc/no-missing-syntax": "off", 185 | "jsdoc/no-multi-asterisks": "error", 186 | "jsdoc/no-restricted-syntax": "off", 187 | "jsdoc/no-types": "error", 188 | "jsdoc/no-undefined-types": "off", 189 | "jsdoc/require-asterisk-prefix": "off", 190 | "jsdoc/require-description": "off", 191 | "jsdoc/require-description-complete-sentence": "off", 192 | "jsdoc/require-example": "off", 193 | "jsdoc/require-file-overview": "off", 194 | "jsdoc/require-hyphen-before-param-description": "off", 195 | "jsdoc/require-jsdoc": "error", 196 | "jsdoc/require-param": "error", 197 | "jsdoc/require-param-description": "error", 198 | "jsdoc/require-param-name": "error", 199 | "jsdoc/require-param-type": "off", 200 | "jsdoc/require-property": "error", 201 | "jsdoc/require-property-description": "error", 202 | "jsdoc/require-property-name": "error", 203 | "jsdoc/require-property-type": "off", 204 | "jsdoc/require-returns": "error", 205 | "jsdoc/require-returns-check": "error", 206 | "jsdoc/require-returns-description": "error", 207 | "jsdoc/require-returns-type": "off", 208 | "jsdoc/require-template": "off", 209 | "jsdoc/require-throws": "off", 210 | "jsdoc/require-yields": "error", 211 | "jsdoc/require-yields-check": "error", 212 | "jsdoc/sort-tags": "off", 213 | "jsdoc/tag-lines": "error", 214 | "jsdoc/text-escaping": "off", 215 | "jsdoc/valid-types": "off", 216 | "no-array-constructor": "off", 217 | "no-class-assign": "off", 218 | "no-const-assign": "off", 219 | "no-dupe-args": "off", 220 | "no-dupe-class-members": "off", 221 | "no-dupe-keys": "off", 222 | "no-func-assign": "off", 223 | "no-implied-eval": "off", 224 | "no-import-assign": "off", 225 | "no-new-native-nonconstructor": "off", 226 | "no-new-symbol": "off", 227 | "no-obj-calls": "off", 228 | "no-redeclare": "off", 229 | "no-restricted-syntax": [ 230 | "error", 231 | { 232 | "selector": "PropertyDefinition[accessibility='private'], MethodDefinition[accessibility='private'], TSParameterProperty[accessibility='private']", 233 | "message": "Use a hash name instead." 234 | } 235 | ], 236 | "no-setter-return": "off", 237 | "no-shadow": "off", 238 | "no-this-before-super": "off", 239 | "no-throw-literal": "off", 240 | "no-undef": "off", 241 | "no-unreachable": "off", 242 | "no-unsafe-negation": "off", 243 | "no-unused-expressions": "off", 244 | "no-unused-vars": "off", 245 | "no-use-before-define": "off", 246 | "no-useless-constructor": "off", 247 | "no-var": "error", 248 | "prefer-const": "error", 249 | "prefer-promise-reject-errors": "off", 250 | "prefer-rest-params": "error", 251 | "prefer-spread": "error", 252 | "require-await": "off" 253 | } 254 | -------------------------------------------------------------------------------- /packages/typescript/src/__test__/dummy.ts: -------------------------------------------------------------------------------- 1 | // This file is only used to test the config. 2 | console.log('Hello, world!'); 3 | -------------------------------------------------------------------------------- /packages/typescript/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-typescript' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/typescript/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import * as resolver from 'eslint-import-resolver-typescript'; 3 | import importX from 'eslint-plugin-import-x'; 4 | import jsdoc from 'eslint-plugin-jsdoc'; 5 | // TODO: Look into why this doesn't resolve. 6 | // eslint-disable-next-line import-x/no-unresolved 7 | import typescript from 'typescript-eslint'; 8 | 9 | const config = createConfig({ 10 | name: '@metamask/eslint-config-typescript', 11 | 12 | plugins: { 13 | '@typescript-eslint': typescript.plugin, 14 | }, 15 | 16 | extends: [ 17 | typescript.configs.recommended, 18 | typescript.configs.recommendedTypeChecked, 19 | importX.flatConfigs.typescript, 20 | jsdoc.configs['flat/recommended-typescript-error'], 21 | ], 22 | 23 | languageOptions: { 24 | sourceType: 'module', 25 | parserOptions: { 26 | // This option requires `tsconfigRootDir` to be set, but this needs to 27 | // be set on a per-project basis. 28 | projectService: true, 29 | ecmaVersion: 2022, 30 | }, 31 | }, 32 | 33 | settings: { 34 | 'import-x/resolver': { 35 | name: 'typescript', 36 | resolver, 37 | }, 38 | }, 39 | 40 | rules: { 41 | // Our rules 42 | '@typescript-eslint/array-type': 'error', 43 | '@typescript-eslint/consistent-type-assertions': 'error', 44 | '@typescript-eslint/consistent-type-definitions': ['error', 'type'], 45 | '@typescript-eslint/explicit-function-return-type': 'error', 46 | '@typescript-eslint/no-explicit-any': 'off', 47 | '@typescript-eslint/no-namespace': [ 48 | 'error', 49 | { allowDefinitionFiles: true }, 50 | ], 51 | '@typescript-eslint/no-non-null-assertion': 'error', 52 | '@typescript-eslint/parameter-properties': 'error', 53 | '@typescript-eslint/prefer-for-of': 'error', 54 | '@typescript-eslint/prefer-function-type': 'error', 55 | '@typescript-eslint/prefer-optional-chain': 'error', 56 | '@typescript-eslint/unified-signatures': 'error', 57 | '@typescript-eslint/no-dupe-class-members': 'error', 58 | '@typescript-eslint/no-unused-vars': [ 59 | 'error', 60 | { 61 | vars: 'all', 62 | args: 'all', 63 | argsIgnorePattern: '[_]+', 64 | ignoreRestSiblings: true, 65 | }, 66 | ], 67 | 68 | // Recommended rules that require type information 69 | '@typescript-eslint/no-unsafe-argument': 'off', 70 | '@typescript-eslint/no-unsafe-assignment': 'off', 71 | '@typescript-eslint/no-unsafe-call': 'off', 72 | '@typescript-eslint/no-unsafe-member-access': 'off', 73 | '@typescript-eslint/no-unsafe-return': 'off', 74 | 75 | // Recommended rules that we do not want to use 76 | '@typescript-eslint/no-duplicate-type-constituents': 'off', 77 | '@typescript-eslint/no-redundant-type-constituents': 'off', 78 | '@typescript-eslint/no-unsafe-enum-comparison': 'off', 79 | '@typescript-eslint/require-await': 'off', 80 | 81 | // Our rules that require type information 82 | '@typescript-eslint/consistent-type-exports': 'error', 83 | '@typescript-eslint/naming-convention': [ 84 | 'error', 85 | { 86 | selector: 'default', 87 | format: ['camelCase'], 88 | leadingUnderscore: 'allow', 89 | trailingUnderscore: 'forbid', 90 | }, 91 | { 92 | selector: 'enumMember', 93 | format: ['PascalCase'], 94 | }, 95 | { 96 | selector: 'import', 97 | format: ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE'], 98 | }, 99 | { 100 | selector: 'interface', 101 | format: ['PascalCase'], 102 | custom: { 103 | regex: '^I[A-Z]', 104 | match: false, 105 | }, 106 | }, 107 | { 108 | selector: 'objectLiteralMethod', 109 | format: ['camelCase', 'PascalCase', 'UPPER_CASE'], 110 | }, 111 | { 112 | selector: 'objectLiteralProperty', 113 | format: ['camelCase', 'PascalCase', 'UPPER_CASE'], 114 | }, 115 | { 116 | selector: 'typeLike', 117 | format: ['PascalCase'], 118 | }, 119 | { 120 | selector: 'typeParameter', 121 | format: ['PascalCase'], 122 | custom: { 123 | regex: '^.{3,}', 124 | match: true, 125 | }, 126 | }, 127 | { 128 | selector: 'variable', 129 | format: ['camelCase', 'UPPER_CASE', 'PascalCase'], 130 | leadingUnderscore: 'allow', 131 | }, 132 | { 133 | selector: 'parameter', 134 | format: ['camelCase', 'PascalCase'], 135 | leadingUnderscore: 'allow', 136 | }, 137 | { 138 | selector: [ 139 | 'classProperty', 140 | 'objectLiteralProperty', 141 | 'typeProperty', 142 | 'classMethod', 143 | 'objectLiteralMethod', 144 | 'typeMethod', 145 | 'accessor', 146 | 'enumMember', 147 | ], 148 | format: null, 149 | modifiers: ['requiresQuotes'], 150 | }, 151 | ], 152 | '@typescript-eslint/no-meaningless-void-operator': 'error', 153 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', 154 | '@typescript-eslint/no-unnecessary-qualifier': 'error', 155 | '@typescript-eslint/no-unnecessary-type-arguments': 'error', 156 | '@typescript-eslint/prefer-enum-initializers': 'error', 157 | '@typescript-eslint/prefer-includes': 'error', 158 | '@typescript-eslint/prefer-nullish-coalescing': 'error', 159 | '@typescript-eslint/prefer-promise-reject-errors': [ 160 | 'error', 161 | { allowThrowingUnknown: true }, 162 | ], 163 | '@typescript-eslint/prefer-readonly': 'error', 164 | '@typescript-eslint/prefer-reduce-type-parameter': 'error', 165 | '@typescript-eslint/prefer-string-starts-ends-with': 'error', 166 | '@typescript-eslint/promise-function-async': 'error', 167 | '@typescript-eslint/restrict-template-expressions': [ 168 | 'error', 169 | { 170 | allowBoolean: true, 171 | allowNumber: true, 172 | }, 173 | ], 174 | '@typescript-eslint/switch-exhaustiveness-check': [ 175 | 'error', 176 | { 177 | considerDefaultExhaustiveForUnions: true, 178 | }, 179 | ], 180 | 181 | 'default-param-last': 'off', 182 | '@typescript-eslint/default-param-last': 'error', 183 | 184 | 'no-shadow': 'off', 185 | '@typescript-eslint/no-shadow': ['error', { builtinGlobals: true }], 186 | 187 | '@typescript-eslint/no-unused-expressions': [ 188 | 'error', 189 | { allowShortCircuit: true, allowTernary: true }, 190 | ], 191 | 192 | 'no-use-before-define': 'off', 193 | '@typescript-eslint/no-use-before-define': ['error', { functions: false }], 194 | 195 | 'no-useless-constructor': 'off', 196 | '@typescript-eslint/no-useless-constructor': 'error', 197 | 198 | /* import-x plugin rules */ 199 | 200 | // Handled by TypeScript 201 | 'import-x/no-unresolved': 'off', 202 | 203 | // Combined with the "verbatimModuleSyntax" tsconfig option, a better option than 204 | // @typescript-eslint/consistent-type-imports 205 | 'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'], 206 | 207 | /* jsdoc plugin rules */ 208 | 209 | 'jsdoc/check-syntax': 'error', 210 | 211 | // This is enabled here rather than in the base config because it doesn't play nicely with 212 | // multi-line JSDoc types. 213 | 'jsdoc/check-indentation': 'error', 214 | 215 | // Use TypeScript types rather than JSDoc types. 216 | 'jsdoc/no-types': 'error', 217 | 218 | // These all conflict with `jsdoc/no-types`. 219 | 'jsdoc/require-param-type': 'off', 220 | 'jsdoc/require-property-type': 'off', 221 | 'jsdoc/require-returns-type': 'off', 222 | 'jsdoc/valid-types': 'off', 223 | 224 | // Prefer hash names over TypeScript's `private` modifier. 225 | 'no-restricted-syntax': [ 226 | 'error', 227 | { 228 | selector: 229 | "PropertyDefinition[accessibility='private'], MethodDefinition[accessibility='private'], TSParameterProperty[accessibility='private']", 230 | message: 'Use a hash name instead.', 231 | }, 232 | ], 233 | }, 234 | }); 235 | 236 | export default config; 237 | -------------------------------------------------------------------------------- /packages/typescript/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import globals from 'globals'; 3 | import { resolve } from 'path'; 4 | import { describe, it, expect } from 'vitest'; 5 | 6 | import config from './index.mjs'; 7 | 8 | describe('index', () => { 9 | it('is a valid ESLint config', async () => { 10 | const api = new ESLint({ 11 | baseConfig: config, 12 | overrideConfig: { 13 | languageOptions: { 14 | globals: { 15 | ...globals.node, 16 | }, 17 | parserOptions: { 18 | tsconfigRootDir: resolve(import.meta.dirname, '..'), 19 | project: 'tsconfig.json', 20 | }, 21 | }, 22 | }, 23 | }); 24 | 25 | // In order to test rules that require type information, we need to actually 26 | // compile the file with TypeScript, so rather than using `api.lintText()`, 27 | // we use `api.lintFiles()` and pass in a file that we know will pass. 28 | const result = await api.lintFiles( 29 | resolve(import.meta.dirname, '__test__/dummy.ts'), 30 | ); 31 | 32 | expect(result[0].messages).toStrictEqual([]); 33 | expect(result[0].warningCount).toBe(0); 34 | expect(result[0].errorCount).toBe(0); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "verbatimModuleSyntax": true 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/vitest/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.0.0] 11 | 12 | ### Added 13 | 14 | - Add Vitest config ([#373](https://github.com/MetaMask/eslint-config/pull/373)) 15 | - This config is based on the `@metamask/eslint-config-jest` config, but uses 16 | the Vitest plugin instead of Jest. 17 | 18 | [Unreleased]: https://github.com/MetaMask/eslint-config/compare/@metamask/eslint-config-vitest@1.0.0...HEAD 19 | [1.0.0]: https://github.com/MetaMask/eslint-config/releases/tag/@metamask/eslint-config-vitest@1.0.0 20 | -------------------------------------------------------------------------------- /packages/vitest/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MetaMask 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vitest/README.md: -------------------------------------------------------------------------------- 1 | # `@metamask/eslint-config-vitest` 2 | 3 | MetaMask's [Vitest](https://vitest.dev/) ESLint configuration. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | yarn add --dev \ 9 | @metamask/eslint-config@^14.0.0 \ 10 | @metamask/eslint-config-vitest@^1.0.0 \ 11 | @vitest/eslint-plugin@^1.1.4 \ 12 | eslint@^9.11.0 \ 13 | eslint-config-prettier@^9.1.0 \ 14 | eslint-plugin-import-x@^4.3.0 \ 15 | eslint-plugin-jsdoc@^50.2.4 \ 16 | eslint-plugin-prettier@^5.2.1 \ 17 | eslint-plugin-promise@^7.1.0 \ 18 | prettier@^3.3.3 19 | ``` 20 | 21 | The order in which you extend ESLint rules matters. 22 | The `@metamask/*` eslint configs should be added to the config array _last_, 23 | with `@metamask/eslint-config` first, and `@metamask/eslint-config-*` in any 24 | order thereafter. 25 | 26 | ```js 27 | import base, { createConfig } from '@metamask/eslint-config'; 28 | import vitest from '@metamask/eslint-config-vitest'; 29 | 30 | const config = createConfig({ 31 | { 32 | extends: [ 33 | // Any custom shared config should be added here. 34 | // ... 35 | 36 | // This should be added last unless you know what you're doing. 37 | base, 38 | vitest, 39 | ], 40 | } 41 | }); 42 | ``` 43 | -------------------------------------------------------------------------------- /packages/vitest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@metamask/eslint-config-vitest", 3 | "version": "1.0.0", 4 | "description": "Shareable MetaMask ESLint config for Vitest.", 5 | "homepage": "https://github.com/MetaMask/eslint-config#readme", 6 | "bugs": { 7 | "url": "https://github.com/MetaMask/eslint-config/issues" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/MetaMask/eslint-config.git" 12 | }, 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | ".": { 17 | "import": { 18 | "types": "./src/index.d.mts", 19 | "default": "./src/index.mjs" 20 | } 21 | } 22 | }, 23 | "main": "./src/index.mjs", 24 | "types": "./src/index.d.mts", 25 | "files": [ 26 | "src/", 27 | "!src/**/*.test.mjs" 28 | ], 29 | "scripts": { 30 | "changelog:update": "../../scripts/update-changelog.sh @metamask/eslint-config-vitest", 31 | "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eslint-config-vitest", 32 | "publish": "npm publish", 33 | "test": "eslint ." 34 | }, 35 | "dependencies": { 36 | "@eslint/js": "^9.11.0", 37 | "globals": "^15.9.0" 38 | }, 39 | "devDependencies": { 40 | "@jest/globals": "^29.7.0", 41 | "@metamask/auto-changelog": "^3.4.4", 42 | "@metamask/eslint-config": "workspace:^", 43 | "@vitest/eslint-plugin": "^1.1.4", 44 | "eslint": "^9.11.0", 45 | "eslint-config-prettier": "^9.1.0", 46 | "eslint-plugin-import-x": "^4.3.0", 47 | "eslint-plugin-jsdoc": "^50.2.4", 48 | "eslint-plugin-prettier": "^5.2.1", 49 | "jest": "^29.7.0", 50 | "prettier": "^3.3.3", 51 | "vitest": "^2.1.9" 52 | }, 53 | "peerDependencies": { 54 | "@metamask/eslint-config": "workspace:^", 55 | "@vitest/eslint-plugin": "^1.1.4", 56 | "eslint": "^9.11.0" 57 | }, 58 | "engines": { 59 | "node": "^18.18 || >=20" 60 | }, 61 | "publishConfig": { 62 | "access": "public", 63 | "registry": "https://registry.npmjs.org/" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/vitest/rules-snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest/consistent-test-it": ["error", { "fn": "it" }], 3 | "vitest/expect-expect": "error", 4 | "vitest/no-alias-methods": "error", 5 | "vitest/no-commented-out-tests": "error", 6 | "vitest/no-conditional-expect": "error", 7 | "vitest/no-conditional-in-test": "error", 8 | "vitest/no-disabled-tests": "error", 9 | "vitest/no-duplicate-hooks": "error", 10 | "vitest/no-focused-tests": "error", 11 | "vitest/no-identical-title": "error", 12 | "vitest/no-import-node-test": "error", 13 | "vitest/no-interpolation-in-snapshots": "error", 14 | "vitest/no-mocks-import": "error", 15 | "vitest/no-restricted-matchers": [ 16 | "error", 17 | { 18 | "resolves": "Use `expect(await promise)` instead.", 19 | "toBeFalsy": "Avoid `toBeFalsy`.", 20 | "toBeTruthy": "Avoid `toBeTruthy`.", 21 | "toMatchSnapshot": "Use `toMatchInlineSnapshot()` instead.", 22 | "toThrowErrorMatchingSnapshot": "Use `toThrowErrorMatchingInlineSnapshot()` instead." 23 | } 24 | ], 25 | "vitest/no-standalone-expect": "error", 26 | "vitest/no-test-prefixes": "error", 27 | "vitest/no-test-return-statement": "error", 28 | "vitest/prefer-hooks-on-top": "error", 29 | "vitest/prefer-lowercase-title": ["error", { "ignore": ["describe"] }], 30 | "vitest/prefer-spy-on": "error", 31 | "vitest/prefer-strict-equal": "error", 32 | "vitest/prefer-to-be": "error", 33 | "vitest/prefer-to-contain": "error", 34 | "vitest/prefer-to-have-length": "error", 35 | "vitest/prefer-todo": "error", 36 | "vitest/require-local-test-context-for-concurrent-snapshots": "error", 37 | "vitest/require-to-throw-message": "error", 38 | "vitest/require-top-level-describe": "error", 39 | "vitest/valid-describe-callback": "error", 40 | "vitest/valid-expect": ["error", { "alwaysAwait": true }], 41 | "vitest/valid-title": "error" 42 | } 43 | -------------------------------------------------------------------------------- /packages/vitest/src/index.d.mts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/eslint-config-vitest' { 2 | import type { Linter } from 'eslint'; 3 | 4 | const config: Linter.Config[]; 5 | export default config; 6 | } 7 | -------------------------------------------------------------------------------- /packages/vitest/src/index.mjs: -------------------------------------------------------------------------------- 1 | import { createConfig } from '@metamask/eslint-config'; 2 | import vitest from '@vitest/eslint-plugin'; 3 | 4 | /** 5 | * @type {import('eslint').Linter.Config[]} 6 | */ 7 | const config = createConfig({ 8 | name: '@metamask/eslint-config-vitest', 9 | 10 | extends: [vitest.configs.recommended], 11 | 12 | rules: { 13 | 'vitest/consistent-test-it': ['error', { fn: 'it' }], 14 | 'vitest/no-alias-methods': 'error', 15 | 'vitest/no-commented-out-tests': 'error', 16 | 'vitest/no-conditional-expect': 'error', 17 | 'vitest/no-conditional-in-test': 'error', 18 | 'vitest/no-disabled-tests': 'error', 19 | 'vitest/no-duplicate-hooks': 'error', 20 | 'vitest/no-focused-tests': 'error', 21 | 'vitest/no-interpolation-in-snapshots': 'error', 22 | 'vitest/no-mocks-import': 'error', 23 | 'vitest/no-standalone-expect': 'error', 24 | 'vitest/no-test-prefixes': 'error', 25 | 'vitest/no-test-return-statement': 'error', 26 | 'vitest/prefer-hooks-on-top': 'error', 27 | 'vitest/prefer-lowercase-title': ['error', { ignore: ['describe'] }], 28 | 'vitest/prefer-spy-on': 'error', 29 | 'vitest/prefer-strict-equal': 'error', 30 | 'vitest/prefer-to-be': 'error', 31 | 'vitest/prefer-to-contain': 'error', 32 | 'vitest/prefer-to-have-length': 'error', 33 | 'vitest/prefer-todo': 'error', 34 | 'vitest/require-to-throw-message': 'error', 35 | 'vitest/require-top-level-describe': 'error', 36 | 'vitest/valid-expect': ['error', { alwaysAwait: true }], 37 | 'vitest/no-restricted-matchers': [ 38 | 'error', 39 | { 40 | resolves: 'Use `expect(await promise)` instead.', 41 | toBeFalsy: 'Avoid `toBeFalsy`.', 42 | toBeTruthy: 'Avoid `toBeTruthy`.', 43 | toMatchSnapshot: 'Use `toMatchInlineSnapshot()` instead.', 44 | toThrowErrorMatchingSnapshot: 45 | 'Use `toThrowErrorMatchingInlineSnapshot()` instead.', 46 | }, 47 | ], 48 | }, 49 | }); 50 | 51 | export default config; 52 | -------------------------------------------------------------------------------- /packages/vitest/src/index.test.mjs: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import { describe, it, expect } from 'vitest'; 3 | 4 | import config from './index.mjs'; 5 | 6 | describe('index', () => { 7 | it('is a valid ESLint config', async () => { 8 | const api = new ESLint({ 9 | baseConfig: config, 10 | }); 11 | 12 | const result = await api.lintText(`export {};\n`); 13 | 14 | expect(result[0].messages).toStrictEqual([]); 15 | expect(result[0].warningCount).toBe(0); 16 | expect(result[0].errorCount).toBe(0); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /release.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "versioningStrategy": "independent" 3 | } 4 | -------------------------------------------------------------------------------- /scripts/generate-configs.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import fs from 'fs/promises'; 4 | import globals from 'globals'; 5 | import { resolve } from 'path'; 6 | 7 | /** 8 | * @typedef {keyof import('globals')} Globals 9 | */ 10 | 11 | /** 12 | * @type {Record} 13 | */ 14 | const RULES_CONFIG = { 15 | 'shared-node-browser': { 16 | location: '../packages/base/src/environment.json', 17 | environments: ['shared-node-browser'], 18 | name: 'Node.js and browser', 19 | }, 20 | browser: { 21 | location: '../packages/browser/src/environment.json', 22 | environments: ['browser'], 23 | name: 'browser', 24 | }, 25 | node: { 26 | location: '../packages/nodejs/src/environment.json', 27 | environments: ['node'], 28 | name: 'Node.js', 29 | }, 30 | commonjs: { 31 | location: '../packages/commonjs/src/environment.json', 32 | environments: ['shared-node-browser', 'commonjs'], 33 | name: 'Node.js and browser (CommonJS)', 34 | }, 35 | }; 36 | 37 | /** 38 | * @type {string[]} 39 | */ 40 | const ALL_RULES = [ 41 | ...Object.values(RULES_CONFIG).reduce((set, { environments }) => { 42 | for (const environment of environments) { 43 | Object.keys(globals[environment]).forEach((key) => set.add(key)); 44 | } 45 | 46 | return set; 47 | }, new Set()), 48 | ]; 49 | 50 | /** 51 | * Generate rules for a specific ESLint configuration package. 52 | * 53 | * @param {object} options - Options. 54 | * @param {Globals[]} options.environments - The environments to 55 | * generate rules for. 56 | * @param {string} options.name - The name of the ESLint configuration. 57 | * @returns {Record} The generated 58 | * rules. 59 | */ 60 | const generateRules = ({ environments, name }) => { 61 | /** 62 | * @type {string[]} 63 | */ 64 | const environmentGlobals = []; 65 | 66 | for (const environment of environments) { 67 | environmentGlobals.push(...Object.keys(globals[environment])); 68 | } 69 | const restrictedGlobals = ALL_RULES.filter( 70 | (rule) => !environmentGlobals.includes(rule), 71 | ).map((rule) => ({ 72 | name: rule, 73 | message: `This global is not available in the ${name} environment.`, 74 | })); 75 | 76 | return { 77 | 'no-restricted-globals': ['error', ...restrictedGlobals], 78 | }; 79 | }; 80 | 81 | /** 82 | * Write the rules to the rules files as JSON. 83 | * 84 | * @returns {Promise} 85 | */ 86 | const writeRules = async () => { 87 | for (const { location, environments, name } of Object.values(RULES_CONFIG)) { 88 | const rules = generateRules({ environments, name }); 89 | 90 | await fs.writeFile( 91 | resolve(import.meta.dirname, location), 92 | `${JSON.stringify(rules, null, 2)}\n`, 93 | ); 94 | } 95 | }; 96 | 97 | await writeRules(); 98 | -------------------------------------------------------------------------------- /scripts/update-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # Get the current package name 6 | if [[ $# -eq 0 ]]; then 7 | echo "Missing package name." 8 | exit 1 9 | fi 10 | 11 | package_name="$1" 12 | shift # remove package name from arguments 13 | 14 | # Get the current git branch 15 | branch=$(git rev-parse --abbrev-ref HEAD) 16 | 17 | if [[ $branch =~ ^release/ ]]; then 18 | yarn auto-changelog update --prettier --tag-prefix "${package_name}@" --rc "$@" 19 | else 20 | yarn auto-changelog update --prettier --tag-prefix "${package_name}@" "$@" 21 | fi 22 | -------------------------------------------------------------------------------- /scripts/validate-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [[ $# -eq 0 ]]; then 6 | echo "Missing package name." 7 | exit 1 8 | fi 9 | 10 | package_name="$1" 11 | shift # remove package name from arguments 12 | 13 | if [[ "${GITHUB_REF:-}" =~ '^release/' ]]; then 14 | yarn auto-changelog validate --prettier --tag-prefix "${package_name}@" --rc "$@" 15 | else 16 | yarn auto-changelog validate --prettier --tag-prefix "${package_name}@" "$@" 17 | fi 18 | -------------------------------------------------------------------------------- /scripts/validate-configs.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { ConfigArray } from '@eslint/config-array'; 4 | import { hasProperty } from '@metamask/utils'; 5 | import fs from 'fs/promises'; 6 | import { fileURLToPath } from 'node:url'; 7 | import pathUtils, { dirname, join } from 'path'; 8 | import { format } from 'prettier'; 9 | 10 | /** 11 | * @typedef {import('eslint').Linter.Config[]} Config 12 | * @typedef {Record>} Rules 13 | */ 14 | 15 | // `import.meta.dirname` but with support for Node.js 18. 16 | const DIRNAME = dirname(fileURLToPath(import.meta.url)); 17 | 18 | // The path to the monorepo packages directory 19 | const PACKAGES_DIR_PATH = pathUtils.join(DIRNAME, '../packages'); 20 | 21 | // The path to the rules snapshot file, relative to a package root. 22 | const RULES_SNAPSHOT_PATH = 'rules-snapshot.json'; 23 | 24 | // Whether this program was configured to be in write mode. 25 | const WRITE_MODE = 26 | process.argv[2] && ['--write', '-w'].includes(process.argv[2]); 27 | 28 | // For logging 29 | const TAB = ' '; 30 | 31 | /** 32 | * Given an ESLint rule config value, convert it from numerical (0, 1, 2) to 33 | * string (off, warn, error) notation, or just returns the given value. 34 | * 35 | * @param {unknown} configValue - The rule config value to normalize. 36 | * @returns {string | unknown} The normalized rule config value. 37 | */ 38 | function normalizeRuleConfigValue(configValue) { 39 | if (typeof configValue !== 'number' && typeof configValue !== 'string') { 40 | return configValue; 41 | } 42 | 43 | switch (String(configValue)) { 44 | case '0': 45 | return 'off'; 46 | case '1': 47 | return 'warn'; 48 | case '2': 49 | return 'error'; 50 | default: 51 | return configValue; 52 | } 53 | } 54 | 55 | /** 56 | * Normalize a rules object, converting numerical rule values to string rule 57 | * values. 58 | * 59 | * @param {Rules} rules - The rules object to normalize. 60 | * @returns {Rules} The normalized rules object. 61 | */ 62 | function normalizeRules(rules) { 63 | // @ts-expect-error - `Object.fromEntries` doesn't infer the return type. 64 | return Object.fromEntries( 65 | Object.entries(rules).map(([ruleName, ruleConfig]) => [ 66 | ruleName, 67 | Array.isArray(ruleConfig) 68 | ? [normalizeRuleConfigValue(ruleConfig[0]), ...ruleConfig.slice(1)] 69 | : normalizeRuleConfigValue(ruleConfig), 70 | ]), 71 | ); 72 | } 73 | 74 | /** 75 | * Flatten a {@link ConfigArray} into a record of rule names to rule values. 76 | * 77 | * @param {ConfigArray} configArray - The config array to flatten. 78 | * @returns {Rules} The flattened rule set. 79 | */ 80 | function flattenConfigArray(configArray) { 81 | /** 82 | * @type {Rules} 83 | */ 84 | const ruleSet = configArray.reduce((flatConfig, rule) => { 85 | if (hasProperty(rule, 'rules')) { 86 | Object.assign(flatConfig, normalizeRules(rule.rules)); 87 | } 88 | 89 | return flatConfig; 90 | }, {}); 91 | 92 | return Object.fromEntries( 93 | Object.entries(ruleSet).sort(([a], [b]) => a.localeCompare(b)), 94 | ); 95 | } 96 | 97 | /** 98 | * Iterates over the packages in this monorepo and returns an object of package 99 | * name keys with object values containing: 100 | * - The raw config. 101 | * - Its flattened, complete rule set. 102 | * - The path to the package. 103 | * 104 | * @returns {Promise>} The 105 | * config map. 106 | */ 107 | async function getMetaMaskConfigs() { 108 | const packages = await fs.readdir(PACKAGES_DIR_PATH); 109 | 110 | /** 111 | * @type {Map} 112 | */ 113 | const allConfigs = new Map(); 114 | 115 | for (const packageName of packages) { 116 | const packagePath = pathUtils.join(PACKAGES_DIR_PATH, packageName); 117 | const manifestPath = pathUtils.join(packagePath, 'package.json'); 118 | const { name } = JSON.parse(await fs.readFile(manifestPath, 'utf-8')); 119 | 120 | const { default: config } = await import(name); 121 | const normalizedConfig = await new ConfigArray(config).normalize(); 122 | 123 | allConfigs.set(name, { 124 | ruleSet: flattenConfigArray(normalizedConfig), 125 | packagePath, 126 | }); 127 | } 128 | 129 | return allConfigs; 130 | } 131 | 132 | /** 133 | * Return the requested number of tabs. 134 | * 135 | * @param {number} numTabs - The number of tabs to return. 136 | * @returns {string} A string consisting of numTabs 4-space "tabs". 137 | */ 138 | function tabs(numTabs) { 139 | if (numTabs < 1 || !Number.isInteger(numTabs)) { 140 | throw new Error('Expected positive integer.'); 141 | } 142 | return numTabs === 1 ? TAB : TAB + new Array(numTabs).join(TAB); 143 | } 144 | 145 | /** 146 | * Assuming the given array contains offending packages, print them to the 147 | * console in a readable format. 148 | * 149 | * @param {string[]} snapshotViolations - A map containing snapshot violations. 150 | */ 151 | function logSnapshotViolations(snapshotViolations) { 152 | console.error( 153 | `\nError: Computed snapshot differs from the existing snapshot for the following package(s). Take a new snapshot and try again.\n\n${tabs( 154 | 1, 155 | )}${snapshotViolations.join(`\n${tabs(1)}`)}\n`, 156 | ); 157 | } 158 | 159 | const configs = await getMetaMaskConfigs(); 160 | const snapshotViolations = []; 161 | 162 | for (const [name, { packagePath, ruleSet }] of configs.entries()) { 163 | const snapshotPath = join(packagePath, RULES_SNAPSHOT_PATH); 164 | const formattedRules = await format(JSON.stringify(ruleSet), { 165 | parser: 'json', 166 | }); 167 | 168 | if (WRITE_MODE) { 169 | await fs.writeFile(snapshotPath, formattedRules); 170 | continue; 171 | } 172 | 173 | const snapshot = await fs.readFile( 174 | join(packagePath, RULES_SNAPSHOT_PATH), 175 | 'utf-8', 176 | ); 177 | 178 | if (snapshot !== formattedRules) { 179 | snapshotViolations.push(name); 180 | process.exitCode = 1; 181 | } 182 | } 183 | 184 | if (snapshotViolations.length > 0) { 185 | logSnapshotViolations(snapshotViolations); 186 | } 187 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "esModuleInterop": true, 5 | "exactOptionalPropertyTypes": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["ES2023"], 8 | "module": "Node16", 9 | "moduleResolution": "Node16", 10 | "noEmit": true, 11 | "noErrorTruncation": true, 12 | "noUncheckedIndexedAccess": true, 13 | "resolveJsonModule": true, 14 | "strict": true, 15 | "target": "ES2022", 16 | "verbatimModuleSyntax": true 17 | }, 18 | "include": ["**/*.mjs", "**/*.mts"], 19 | "exclude": ["./dist", "**/node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | watch: false, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /vitest.workspace.mts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config'; 2 | 3 | export default defineWorkspace(['packages/*']); 4 | --------------------------------------------------------------------------------