From c96988433e7fda6684a575961203c44f116cd2f3 Mon Sep 17 00:00:00 2001 From: Armano Date: Mon, 16 Mar 2020 18:28:56 +0100 Subject: [PATCH 01/19] chore: add start of github actions (#1383) --- .github/workflows/ci.yml | 106 +++++++++++++++++++++++++++++++++++++++ README.md | 4 +- 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..1be9fde0e338 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - '**' + +jobs: + primary_code_validation_and_tests: + name: Primary code validation and tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 12 + uses: actions/setup-node@v1 + with: + node-version: 12 + + # This also runs a build as part of the postinstall bootstrap + - name: install and build + run: | + yarn --ignore-engines --frozen-lockfile + yarn check-clean-workspace-after-install + + # Note that this command *also* typechecks tests/tools, + # whereas the build only checks src files + - name: Typecheck all packages + run: yarn typecheck + + - name: Check code formatting + run: yarn format-check + + - name: Run linting + run: yarn lint + + - name: Validate spelling + run: yarn check:spelling + + - name: Run unit tests + run: yarn test + env: + CI: true + + - name: Run integrations tests + run: yarn integration-tests + env: + CI: true + + - name: Publish code coverage report + uses: codecov/codecov-action@v1 + with: + yml: ./codecov.yml + token: ${{ secrets.CODECOV_TOKEN }} + flags: unittest + name: codecov + + unit_tests_on_other_node_versions: + name: Run unit tests on other Node.js versions + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [8.x, 10.x] + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + # This also runs a build as part of the postinstall bootstrap + - name: install and build + run: | + yarn --ignore-engines --frozen-lockfile + yarn check-clean-workspace-after-install + + - name: Run unit tests + run: yarn test + env: + CI: true + + publish_canary_version: + name: Publish the latest code as a canary version + runs-on: ubuntu-latest + needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions] + if: github.event_name == 'push' && github.ref == 'refs/head/master' + steps: + - uses: actions/checkout@v1 + - name: Use Node.js 12 + uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + + # This also runs a build as part of the postinstall bootstrap + - name: install and build + run: | + yarn --ignore-engines --frozen-lockfile + yarn check-clean-workspace-after-install + +# - name: Publish all packages to npm +# run: npx lerna publish --canary --exact --force-publish --yes +# env: +# NODE_AUTH_TOKEN: ${{ secrets.npm_token }} diff --git a/README.md b/README.md index c23634df344c..95cfe76c5eb4 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@

Monorepo for all the tooling which enables ESLint to support TypeScript

- Azure Pipelines - + GitHub Workflow Status + Financial Contributors on Open Collective GitHub license NPM Downloads Codecov From d12183883aa880810560ef7a2447d002f440ebc5 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 16 Mar 2020 18:51:10 +0000 Subject: [PATCH 02/19] chore: switch to github actions for ci (#1745) --- .github/workflows/ci.yml | 101 ++++++++++++++++++++++++++++++------- azure-pipelines.yml | 104 --------------------------------------- 2 files changed, 84 insertions(+), 121 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1be9fde0e338..fb2e31ef8eaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,24 +8,39 @@ on: branches: - '**' +env: + PRIMARY_NODE_VERSION: 12 + jobs: primary_code_validation_and_tests: name: Primary code validation and tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Use Node.js 12 + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} uses: actions/setup-node@v1 with: - node-version: 12 + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v1 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- # This also runs a build as part of the postinstall bootstrap - - name: install and build + - name: Install dependencies and build run: | yarn --ignore-engines --frozen-lockfile yarn check-clean-workspace-after-install - # Note that this command *also* typechecks tests/tools, + # Note that this command *also* type checks tests/tools, # whereas the build only checks src files - name: Typecheck all packages run: yarn typecheck @@ -44,11 +59,6 @@ jobs: env: CI: true - - name: Run integrations tests - run: yarn integration-tests - env: - CI: true - - name: Publish code coverage report uses: codecov/codecov-action@v1 with: @@ -57,6 +67,39 @@ jobs: flags: unittest name: codecov + integration_tests: + name: Run integration tests on primary Node.js version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v1 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + # This also runs a build as part of the postinstall bootstrap + - name: Install dependencies and build + run: | + yarn --ignore-engines --frozen-lockfile + yarn check-clean-workspace-after-install + + - name: Run integrations tests + run: yarn integration-tests + env: + CI: true + unit_tests_on_other_node_versions: name: Run unit tests on other Node.js versions runs-on: ubuntu-latest @@ -70,8 +113,20 @@ jobs: with: node-version: ${{ matrix.node-version }} + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v1 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + # This also runs a build as part of the postinstall bootstrap - - name: install and build + - name: Install dependencies and build run: | yarn --ignore-engines --frozen-lockfile yarn check-clean-workspace-after-install @@ -88,19 +143,31 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/head/master' steps: - uses: actions/checkout@v1 - - name: Use Node.js 12 + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} uses: actions/setup-node@v1 with: - node-version: 12 + node-version: ${{ env.PRIMARY_NODE_VERSION }} registry-url: https://registry.npmjs.org/ + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v1 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + # This also runs a build as part of the postinstall bootstrap - - name: install and build + - name: Install dependencies and build run: | yarn --ignore-engines --frozen-lockfile yarn check-clean-workspace-after-install -# - name: Publish all packages to npm -# run: npx lerna publish --canary --exact --force-publish --yes -# env: -# NODE_AUTH_TOKEN: ${{ secrets.npm_token }} + - name: Publish all packages to npm + run: npx lerna publish --canary --exact --force-publish --yes + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 172a64beab75..000000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,104 +0,0 @@ -trigger: - - master - -jobs: - - job: primary_code_validation_and_tests - displayName: Primary code validation and tests - pool: - vmImage: 'Ubuntu-18.04' - steps: - - task: NodeTool@0 - inputs: - versionSpec: 12 - displayName: 'Install Node.js 12' - - - script: | - # This also runs a build as part of the postinstall - # bootstrap - yarn --ignore-engines --frozen-lockfile - yarn check-clean-workspace-after-install - - - script: | - # Note that this command *also* typechecks tests/tools, - # whereas the build only checks src files - yarn typecheck - displayName: 'Typecheck all packages' - - - script: | - yarn format-check - displayName: 'Check code formatting' - - - script: | - yarn lint - displayName: 'Run linting' - - - script: | - yarn check:spelling - displayName: 'Validate documentation spelling' - - - script: | - yarn test - displayName: 'Run unit tests' - - - script: | - yarn integration-tests - displayName: 'Run integrations tests' - - - bash: bash <(curl -s https://codecov.io/bash) -P "$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) - displayName: 'Publish code coverage report' - - - job: unit_tests_on_other_node_versions - displayName: Run unit tests on other Node.js versions - pool: - vmImage: 'Ubuntu-18.04' - strategy: - maxParallel: 3 - matrix: - node_10_x: - node_version: 10.x - node_8_x: - node_version: 8.x - steps: - - task: NodeTool@0 - inputs: - versionSpec: $(node_version) - displayName: 'Install Node.js $(node_version)' - - - script: | - # This also runs a build as part of the postinstall - # bootstrap - yarn --ignore-engines --frozen-lockfile - yarn check-clean-workspace-after-install - - - script: | - yarn test - displayName: 'Run unit tests' - - - job: publish_canary_version - displayName: Publish the latest code as a canary version - dependsOn: - - primary_code_validation_and_tests - - unit_tests_on_other_node_versions - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'), ne(variables['Build.Reason'], 'PullRequest')) - pool: - vmImage: 'Ubuntu-18.04' - steps: - - task: NodeTool@0 - inputs: - versionSpec: 12 - displayName: 'Install Node.js 12' - - - script: | - # This also runs a build as part of the postinstall - # bootstrap - yarn --ignore-engines --frozen-lockfile - yarn check-clean-workspace-after-install - - - script: | - npm config set //registry.npmjs.org/:_authToken=$(NPM_TOKEN) - - - script: | - npx lerna publish --canary --exact --force-publish --yes - displayName: 'Publish all packages to npm' From 4c7270b58ad065a96a9104c0e863dedc2c80ac75 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 16 Mar 2020 19:19:31 +0000 Subject: [PATCH 03/19] chore: fix config for canary releases --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb2e31ef8eaf..e4d4077d14a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,8 +139,8 @@ jobs: publish_canary_version: name: Publish the latest code as a canary version runs-on: ubuntu-latest - needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions] - if: github.event_name == 'push' && github.ref == 'refs/head/master' + needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions, integration_tests] + if: github.ref == 'refs/head/master' steps: - uses: actions/checkout@v1 - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} From 713e9d3b439e7dfaf8bc83055f7f4ea9c1a779e4 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 16 Mar 2020 19:36:39 +0000 Subject: [PATCH 04/19] chore: debug publish_canary_version --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4d4077d14a0..2ad7cf351e5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + + - name: echo github.ref + run: echo ${{ github.ref }} + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} uses: actions/setup-node@v1 with: @@ -140,9 +144,9 @@ jobs: name: Publish the latest code as a canary version runs-on: ubuntu-latest needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions, integration_tests] - if: github.ref == 'refs/head/master' + # if: github.ref == 'refs/head/master' steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} uses: actions/setup-node@v1 with: From 3814d4e3b3c1552c7601b5d722b2a37c5a570841 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 16 Mar 2020 19:48:42 +0000 Subject: [PATCH 05/19] fix: only run publish_canary_version on master --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ad7cf351e5f..8af2a8f28234 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,7 @@ jobs: name: Publish the latest code as a canary version runs-on: ubuntu-latest needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions, integration_tests] - # if: github.ref == 'refs/head/master' + if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} From abf1a2fa5574e41af8070be3d79a886ea2f989cc Mon Sep 17 00:00:00 2001 From: Konstantin Pelepelin Date: Mon, 16 Mar 2020 23:06:32 +0300 Subject: [PATCH 06/19] fix(eslint-plugin-tslint): fix tslintConfig memoization key (#1719) Fixes typescript-eslint#1692 --- packages/eslint-plugin-tslint/src/rules/config.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts index fcfaa7e2b8fd..32198d0fc8c7 100644 --- a/packages/eslint-plugin-tslint/src/rules/config.ts +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -48,10 +48,12 @@ const tslintConfig = memoize( rulesDirectory: tslintRulesDirectory ?? [], }); }, - (lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) => - `${lintFile}_${Object.keys(tslintRules).join(',')}_${ - tslintRulesDirectory.length - }`, + ( + lintFile: string | undefined, + tslintRules = {}, + tslintRulesDirectory: string[] = [], + ) => + `${lintFile}_${JSON.stringify(tslintRules)}_${tslintRulesDirectory.join()}`, ); export default createRule({ From d6e273d9e72abbcccba0e6a725520ec0849b27b4 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 17 Mar 2020 15:19:57 -0700 Subject: [PATCH 07/19] chore: standardise issue templates (#1760) --- .../ISSUE_TEMPLATE/eslint-plugin-tslint.md | 55 +++++++++++++++++-- .../eslint-plugin-typescript.md | 7 ++- .../typescript-eslint-parser.md | 47 ++++++++++++++-- .../ISSUE_TEMPLATE/typescript-eslint-utils.md | 49 ++++++++++++++++- .github/ISSUE_TEMPLATE/typescript-estree.md | 51 +++++++++++++++-- 5 files changed, 189 insertions(+), 20 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md b/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md index 68a68b98575f..b94f19fb5b82 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md @@ -6,15 +6,60 @@ labels: 'package: eslint-plugin-tslint, triage' assignees: '' --- -**What code were you trying to parse?** + + + + +**Repro** + + + +```JSON +{ + "rules": { + "@typescript-eslint/tslint/config": ["warn", { + "rules": { + "rule": "setting", + }, + "rulesDirectory": [ + "node_modules/foo" + ] + }], + } +} +``` + + + +```TS +// your repro code case ``` -**What did you expect to happen?** +**Expected Result** + +**Actual Result** + +**Additional Info** + + **Versions** diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md index ad7d8fb4944f..3c0fb548f139 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md @@ -1,7 +1,7 @@ --- name: '@typescript-eslint/eslint-plugin' about: Report an issue with the '@typescript-eslint/eslint-plugin' package -title: '[rulename] ' +title: '[rulename] issue title' labels: 'package: eslint-plugin, triage' assignees: '' --- @@ -26,6 +26,11 @@ Are you opening an issue because the rule you're trying to use is not found? 3) If ESLint still can't find the rule, then consider reporting an issue. --> + + **Repro** + + + +**Repro** + + + +```JSON +{ + "rules": { + "@typescript-eslint/": [""] + }, + "parserOptions": { + "...": "something" + } +} ``` -**What did you expect to happen?** +```TS +// your repro code case +``` + +**Expected Result** + +**Actual Result** + +**Additional Info** + + **Versions** diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md index 8403568e4ac2..5b365a85ea85 100644 --- a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md @@ -1,14 +1,57 @@ --- name: '@typescript-eslint/experimental-utils' -about: Report an issue with the `@typescript-eslint/experimental-utils` package +about: Report an issue with the '@typescript-eslint/experimental-utils' package title: '' labels: 'package: utils, triage' assignees: '' --- -**What did you expect to happen?** + + + + +**Repro** + + + +```JSON +{ + "rules": { + "@typescript-eslint/": [""] + }, + "parserOptions": { + "...": "something" + } +} +``` + +```TS +// your repro code case +``` + +**Expected Result** + +**Actual Result** + +**Additional Info** + + **Versions** diff --git a/.github/ISSUE_TEMPLATE/typescript-estree.md b/.github/ISSUE_TEMPLATE/typescript-estree.md index fc8cca3d9a39..21f4c800d548 100644 --- a/.github/ISSUE_TEMPLATE/typescript-estree.md +++ b/.github/ISSUE_TEMPLATE/typescript-estree.md @@ -1,20 +1,59 @@ --- name: '@typescript-eslint/typescript-estree' -about: Report an issue with the `@typescript-eslint/typescript-estree` package +about: Report an issue with the '@typescript-eslint/typescript-estree' package title: '' labels: 'package: typescript-estree, triage' assignees: '' --- -**What code were you trying to parse?** + + + + +**Repro** + + + +```JSON +{ + "rules": { + "@typescript-eslint/": [""] + }, + "parserOptions": { + "...": "something" + } +} ``` -**What did you expect to happen?** +```TS +// your repro code case +``` + +**Expected Result** + +**Actual Result** + +**Additional Info** + + **Versions** From 61a779c1828ca8347a74d11e44e2ef94206bac9e Mon Sep 17 00:00:00 2001 From: James Henry Date: Wed, 18 Mar 2020 17:25:01 +0000 Subject: [PATCH 08/19] chore: try fetching all history in canary job --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8af2a8f28234..cb32daf3501a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,12 @@ jobs: if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 # fetch all history for this job, because lerna needs it + - run: | + git status + git log -n 2 + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} uses: actions/setup-node@v1 with: From 19cc9a936e8c0e318606f3ebb92e0170d775490f Mon Sep 17 00:00:00 2001 From: James Henry Date: Wed, 18 Mar 2020 17:47:33 +0000 Subject: [PATCH 09/19] chore: try fetching all tags and history in canary job --- .github/workflows/ci.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb32daf3501a..5c7945fd1798 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,10 +147,16 @@ jobs: if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v2 - with: - fetch-depth: 0 # fetch all history for this job, because lerna needs it + # Fetch all history for all tags and branches in this job because lerna needs it - run: | + git fetch --prune --unshallow + echo "fetched" + git status + echo "end status 1" + git checkout master git status + echo "end status 2" + echo "git log:" git log -n 2 - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} @@ -178,6 +184,6 @@ jobs: yarn check-clean-workspace-after-install - name: Publish all packages to npm - run: npx lerna publish --canary --exact --force-publish --yes + run: npx lerna publish --log-level=verbose --canary --exact --force-publish --yes env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 52b061e44d1148b04b64acc00b477089d3be4267 Mon Sep 17 00:00:00 2001 From: James Henry Date: Wed, 18 Mar 2020 18:04:23 +0000 Subject: [PATCH 10/19] chore: try fetching all tags and history in canary job --- .github/workflows/ci.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c7945fd1798..933b4895837f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,14 +150,6 @@ jobs: # Fetch all history for all tags and branches in this job because lerna needs it - run: | git fetch --prune --unshallow - echo "fetched" - git status - echo "end status 1" - git checkout master - git status - echo "end status 2" - echo "git log:" - git log -n 2 - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} uses: actions/setup-node@v1 @@ -184,6 +176,6 @@ jobs: yarn check-clean-workspace-after-install - name: Publish all packages to npm - run: npx lerna publish --log-level=verbose --canary --exact --force-publish --yes + run: npx lerna publish --loglevel=verbose --canary --exact --force-publish --yes env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 09d8afca684635b5ac604bc1794240484a70ce91 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 18 Mar 2020 12:06:32 -0700 Subject: [PATCH 11/19] fix(typescript-estree): export * regression from 133f622f (#1751) --- packages/typescript-estree/src/convert.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 74c701b7fc4d..c622e11c14f2 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -1602,7 +1602,12 @@ export class Converter { source: this.convertChild(node.moduleSpecifier), exportKind: node.isTypeOnly ? 'type' : 'value', exported: - node.exportClause?.kind === SyntaxKind.NamespaceExport + // note - for compat with 3.7.x, where node.exportClause is always undefined and + // SyntaxKind.NamespaceExport does not exist yet (i.e. is undefined), this + // cannot be shortened to an optional chain, or else you end up with + // undefined === undefined, and the true path will hard error at runtime + node.exportClause && + node.exportClause.kind === SyntaxKind.NamespaceExport ? this.convertChild(node.exportClause.name) : null, }); From f76a1b3e63afda9f239e46f4ad5b36c1d7a6e8da Mon Sep 17 00:00:00 2001 From: Alexander T Date: Wed, 18 Mar 2020 21:06:57 +0200 Subject: [PATCH 12/19] feat(eslint-plugin): [no-unnec-type-assertion] allow const assertions (#1741) --- .../rules/no-unnecessary-type-assertion.md | 4 +++ .../rules/no-unnecessary-type-assertion.ts | 16 ++++++++-- .../no-unnecessary-type-assertion.test.ts | 32 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md index 14819c56bf78..4464b6483ef1 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md @@ -43,6 +43,10 @@ const foo = 3; const foo = 3 as number; ``` +```ts +const foo = 'foo' as const; +``` + ```ts function foo(x: number | undefined): number { return x!; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index dd4395b290b8..4fe9f9a59d25 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,4 +1,7 @@ -import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import { isObjectType, isObjectFlagSet, @@ -122,6 +125,14 @@ export default util.createRule({ return false; } + function isConstAssertion(node: TSESTree.TypeNode): boolean { + return ( + node.type === AST_NODE_TYPES.TSTypeReference && + node.typeName.type === AST_NODE_TYPES.Identifier && + node.typeName.name === 'const' + ); + } + return { TSNonNullExpression(node): void { const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); @@ -201,7 +212,8 @@ export default util.createRule({ if ( options.typesToIgnore?.includes( sourceCode.getText(node.typeAnnotation), - ) + ) || + isConstAssertion(node.typeAnnotation) ) { return; } diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index e7248a9693ef..4d1ae880e840 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -130,6 +130,38 @@ function Test(props: { `, filename: 'react.tsx', }, + { + code: ` +const a = [1, 2]; +const b = [3, 4]; +const c = [...a, ...b] as const; + `, + }, + { + code: `const a = [1, 2] as const;`, + }, + { + code: `const a = 'a' as const;`, + }, + { + code: `const a = { foo: 'foo' } as const`, + }, + { + code: ` +const a = [1, 2]; +const b = [3, 4]; +const c = [...a, ...b]; + `, + }, + { + code: `const a = [1, 2];`, + }, + { + code: `const a = 'a';`, + }, + { + code: `const a = { foo: 'foo' };`, + }, ], invalid: [ From 6646959b255b08afe5175bba6621bad11b9e1d5e Mon Sep 17 00:00:00 2001 From: Susisu Date: Thu, 19 Mar 2020 04:18:46 +0900 Subject: [PATCH 13/19] fix(eslint-plugin): fix message of no-base-to-string (#1755) --- packages/eslint-plugin/src/rules/no-base-to-string.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index fa4042728953..98a37c740849 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -24,7 +24,7 @@ export default util.createRule({ }, messages: { baseToString: - "'{{name}} {{certainty}} evaluate to '[Object object]' when stringified.", + "'{{name}} {{certainty}} evaluate to '[object Object]' when stringified.", }, schema: [], type: 'suggestion', From 199863d35cb36bdb7178b8116d146258506644c7 Mon Sep 17 00:00:00 2001 From: Regev Brody Date: Thu, 19 Mar 2020 03:10:10 +0200 Subject: [PATCH 14/19] fix(eslint-plugin): [quotes] false positive with backtick in import equals statement (#1769) --- packages/eslint-plugin/src/rules/quotes.ts | 1 + packages/eslint-plugin/tests/rules/quotes.test.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/quotes.ts b/packages/eslint-plugin/src/rules/quotes.ts index a266e912fdf3..5d4d44028b14 100644 --- a/packages/eslint-plugin/src/rules/quotes.ts +++ b/packages/eslint-plugin/src/rules/quotes.ts @@ -42,6 +42,7 @@ export default util.createRule({ case AST_NODE_TYPES.TSPropertySignature: case AST_NODE_TYPES.TSModuleDeclaration: case AST_NODE_TYPES.TSLiteralType: + case AST_NODE_TYPES.TSExternalModuleReference: return true; case AST_NODE_TYPES.TSEnumMember: diff --git a/packages/eslint-plugin/tests/rules/quotes.test.ts b/packages/eslint-plugin/tests/rules/quotes.test.ts index c6bf01f3e771..ad1487b8506f 100644 --- a/packages/eslint-plugin/tests/rules/quotes.test.ts +++ b/packages/eslint-plugin/tests/rules/quotes.test.ts @@ -310,7 +310,11 @@ ruleTester.run('quotes', rule, { code: `export * from "a"; export * from 'b';`, options: ['backtick'], }, - + // `backtick` should not warn import with require. + { + code: `import moment = require('moment');`, + options: ['backtick'], + }, // `backtick` should not warn property/method names (not computed). { code: `var obj = {"key0": 0, 'key1': 1};`, From c82d1216ece92e7f099971dd6d669f277314dcc0 Mon Sep 17 00:00:00 2001 From: Maxim Mazurok Date: Thu, 19 Mar 2020 16:40:09 +1100 Subject: [PATCH 15/19] chore(typescript-estree): remove unfinished comment (#1770) First introduced in https://github.com/typescript-eslint/typescript-eslint/commit/25092fdc5bd9d1bd90cf3cb8d37e420b6c2efef3#diff-77692ea88ea59620ee45bd60f573b4bcR150 --- packages/typescript-estree/src/parser-options.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 2317a683abfe..73ea6ba3c04d 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -146,8 +146,6 @@ export interface TSESTreeOptions { * When passed with `project`, this allows the parser to create a catch-all, default program. * This means that if the parser encounters a file not included in any of the provided `project`s, * it will not error, but will instead parse the file and its dependencies in a new program. - * - * This */ createDefaultProgram?: boolean; } From 2b9603d868c57556d8cd6087685e798d74cb6f26 Mon Sep 17 00:00:00 2001 From: Retsam Date: Thu, 19 Mar 2020 23:47:48 -0400 Subject: [PATCH 16/19] feat(eslint-plugin): [no-unnecessary-condition] ignore basic array indexing false positives (#1534) --- .../src/rules/no-unnecessary-condition.ts | 69 ++++++++++++++++++- .../rules/no-unnecessary-condition.test.ts | 64 +++++++++++++++++ 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 45af1e807065..07bb3d56c5c3 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -143,7 +143,25 @@ export default createRule({ function nodeIsArrayType(node: TSESTree.Expression): boolean { const nodeType = getNodeType(node); - return checker.isArrayType(nodeType) || checker.isTupleType(nodeType); + return checker.isArrayType(nodeType); + } + function nodeIsTupleType(node: TSESTree.Expression): boolean { + const nodeType = getNodeType(node); + return checker.isTupleType(nodeType); + } + + function isArrayIndexExpression(node: TSESTree.Expression): boolean { + return ( + // Is an index signature + node.type === AST_NODE_TYPES.MemberExpression && + node.computed && + // ...into an array type + (nodeIsArrayType(node.object) || + // ... or a tuple type + (nodeIsTupleType(node.object) && + // Exception: literal index into a tuple - will have a sound type + node.property.type !== AST_NODE_TYPES.Literal)) + ); } /** @@ -151,6 +169,13 @@ export default createRule({ * if the type of the node is always true or always false, it's not necessary. */ function checkNode(node: TSESTree.Expression): void { + // Since typescript array index signature types don't represent the + // possibility of out-of-bounds access, if we're indexing into an array + // just skip the check, to avoid false positives + if (isArrayIndexExpression(node)) { + return; + } + const type = getNodeType(node); // Conditional is always necessary if it involves: @@ -181,6 +206,12 @@ export default createRule({ } function checkNodeForNullish(node: TSESTree.Expression): void { + // Since typescript array index signature types don't represent the + // possibility of out-of-bounds access, if we're indexing into an array + // just skip the check, to avoid false positives + if (isArrayIndexExpression(node)) { + return; + } const type = getNodeType(node); // Conditional is always necessary if it involves `any` or `unknown` if (isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { @@ -306,7 +337,7 @@ export default createRule({ callee.property.type === AST_NODE_TYPES.Identifier && ARRAY_PREDICATE_FUNCTIONS.has(callee.property.name) && // and the left-hand side is an array, according to the types - nodeIsArrayType(callee.object) + (nodeIsArrayType(callee.object) || nodeIsTupleType(callee.object)) ); } function checkCallExpression(node: TSESTree.CallExpression): void { @@ -361,6 +392,33 @@ export default createRule({ } } + // Recursively searches an optional chain for an array index expression + // Has to search the entire chain, because an array index will "infect" the rest of the types + // Example: + // ``` + // [{x: {y: "z"} }][n] // type is {x: {y: "z"}} + // ?.x // type is {y: "z"} + // ?.y // This access is considered "unnecessary" according to the types + // ``` + function optionChainContainsArrayIndex( + node: TSESTree.OptionalMemberExpression | TSESTree.OptionalCallExpression, + ): boolean { + const lhsNode = + node.type === AST_NODE_TYPES.OptionalCallExpression + ? node.callee + : node.object; + if (isArrayIndexExpression(lhsNode)) { + return true; + } + if ( + lhsNode.type === AST_NODE_TYPES.OptionalMemberExpression || + lhsNode.type === AST_NODE_TYPES.OptionalCallExpression + ) { + return optionChainContainsArrayIndex(lhsNode); + } + return false; + } + function checkOptionalChain( node: TSESTree.OptionalMemberExpression | TSESTree.OptionalCallExpression, beforeOperator: TSESTree.Node, @@ -372,6 +430,13 @@ export default createRule({ return; } + // Since typescript array index signature types don't represent the + // possibility of out-of-bounds access, if we're indexing into an array + // just skip the check, to avoid false positives + if (optionChainContainsArrayIndex(node)) { + return; + } + const type = getNodeType(node); if ( isTypeFlagSet(type, ts.TypeFlags.Any) || diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts index 5af22f00ca02..f3adbc3a8495 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -150,6 +150,39 @@ function test(a: string | null | undefined) { function test(a: unknown) { return a ?? "default"; }`, + // Indexing cases + ` +declare const arr: object[]; +if(arr[42]) {} // looks unnecessary from the types, but isn't + +const tuple = [{}] as [object]; +declare const n: number; +if(tuple[n]) {} +`, + // Optional-chaining indexing + ` +declare const arr: Array<{value: string} & (() => void)>; +if(arr[42]?.value) {} +arr[41]?.(); + +// An array access can "infect" deeper into the chain +declare const arr2: Array<{x: {y: {z: object}}}>; +arr2[42]?.x?.y?.z; + +const tuple = ["foo"] as const; +declare const n: number; +tuple[n]?.toUpperCase(); + `, + `if(arr?.[42]) {}`, + ` +declare const returnsArr: undefined | (() => string[]); +if(returnsArr?.()[42]) {} +returnsArr?.()[42]?.toUpperCase()`, + // nullish + array index + ` +declare const arr: string[][]; +arr[x] ?? []; +`, // Supports ignoring the RHS { code: ` @@ -363,6 +396,37 @@ function nothing3(x: [string, string]) { ruleError(15, 25, 'alwaysFalsy'), ], }, + // Indexing cases + { + // This is an error because 'dict' doesn't represent + // the potential for undefined in its types + code: ` +declare const dict: Record; +if(dict["mightNotExist"]) {} +`, + errors: [ruleError(3, 4, 'alwaysTruthy')], + }, + { + // Should still check tuples when accessed with literal numbers, since they don't have + // unsound index signatures + code: ` +const x = [{}] as [{foo: string}]; +if(x[0]) {} +if(x[0]?.foo) {} +`, + errors: [ + ruleError(3, 4, 'alwaysTruthy'), + ruleError(4, 8, 'neverOptionalChain'), + ], + }, + { + // Shouldn't mistake this for an array indexing case + code: ` +declare const arr: object[]; +if(arr.filter) {} +`, + errors: [ruleError(3, 4, 'alwaysTruthy')], + }, { options: [{ checkArrayPredicates: true }], code: ` From 3eb5d4525e95c8ab990f55588b8d830a02ce5a9c Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 19 Mar 2020 20:50:29 -0700 Subject: [PATCH 17/19] feat(experimental-utils): expose ast utility functions (#1670) --- .cspell.json | 3 +- packages/eslint-plugin/package.json | 1 - .../eslint-plugin/src/rules/comma-spacing.ts | 8 +- .../src/rules/func-call-spacing.ts | 5 +- .../src/rules/indent-new-do-not-use/index.ts | 13 +- .../src/rules/no-array-constructor.ts | 2 +- .../src/rules/prefer-includes.ts | 3 +- .../src/rules/prefer-optional-chain.ts | 3 +- .../src/rules/prefer-regexp-exec.ts | 8 +- .../rules/prefer-string-starts-ends-with.ts | 8 +- .../eslint-plugin/src/rules/require-await.ts | 13 +- .../src/rules/space-before-function-paren.ts | 5 +- .../src/rules/switch-exhaustiveness-check.ts | 5 +- packages/eslint-plugin/src/util/astUtils.ts | 267 +----------------- packages/eslint-plugin/src/util/misc.ts | 5 +- .../eslint-plugin/typings/eslint-utils.d.ts | 180 ------------ packages/experimental-utils/README.md | 23 +- packages/experimental-utils/package.json | 3 +- .../ast-utils/eslint-utils/PatternMatcher.ts | 56 ++++ .../eslint-utils/ReferenceTracker.ts | 94 ++++++ .../ast-utils/eslint-utils/astUtilities.ts | 127 +++++++++ .../src/ast-utils/eslint-utils/index.ts | 5 + .../src/ast-utils/eslint-utils/predicates.ts | 106 +++++++ .../ast-utils/eslint-utils/scopeAnalysis.ts | 27 ++ .../experimental-utils/src/ast-utils/index.ts | 3 + .../experimental-utils/src/ast-utils/misc.ts | 15 + .../src/ast-utils/predicates.ts | 247 ++++++++++++++++ .../src/eslint-utils/RuleCreator.ts | 4 +- .../src/eslint-utils/applyDefault.ts | 4 +- .../src/eslint-utils/deepMerge.ts | 4 +- .../src/eslint-utils/getParserServices.ts | 7 +- packages/experimental-utils/src/index.ts | 19 +- .../src/ts-eslint-scope/Definition.ts | 2 +- .../src/ts-eslint-scope/Options.ts | 2 +- .../src/ts-eslint-scope/PatternVisitor.ts | 2 +- .../src/ts-eslint-scope/Reference.ts | 2 +- .../src/ts-eslint-scope/Referencer.ts | 2 +- .../src/ts-eslint-scope/Scope.ts | 2 +- .../src/ts-eslint-scope/ScopeManager.ts | 2 +- .../src/ts-eslint-scope/Variable.ts | 2 +- .../experimental-utils/src/ts-eslint/AST.ts | 5 +- .../src/ts-eslint/Linter.ts | 2 +- .../src/ts-eslint/ParserOptions.ts | 4 +- .../experimental-utils/src/ts-eslint/Rule.ts | 2 +- .../src/ts-eslint/RuleTester.ts | 5 +- .../experimental-utils/src/ts-eslint/Scope.ts | 2 +- .../src/ts-eslint/SourceCode.ts | 4 +- packages/experimental-utils/src/ts-estree.ts | 12 + .../typings/eslint-utils.d.ts | 40 +++ yarn.lock | 7 + 50 files changed, 835 insertions(+), 537 deletions(-) delete mode 100644 packages/eslint-plugin/typings/eslint-utils.d.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/index.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts create mode 100644 packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts create mode 100644 packages/experimental-utils/src/ast-utils/index.ts create mode 100644 packages/experimental-utils/src/ast-utils/misc.ts create mode 100644 packages/experimental-utils/src/ast-utils/predicates.ts create mode 100644 packages/experimental-utils/src/ts-estree.ts create mode 100644 packages/experimental-utils/typings/eslint-utils.d.ts diff --git a/.cspell.json b/.cspell.json index 33f7b70c5f2b..c65ff294c249 100644 --- a/.cspell.json +++ b/.cspell.json @@ -10,7 +10,8 @@ "**/**/CONTRIBUTORS.md", "**/**/ROADMAP.md", "**/*.{json,snap}", - ".cspell.json" + ".cspell.json", + "yarn.lock" ], "dictionaries": [ "typescript", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 1083d0b5aa86..4855beab6a23 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -42,7 +42,6 @@ }, "dependencies": { "@typescript-eslint/experimental-utils": "2.24.0", - "eslint-utils": "^1.4.3", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts index d8ab4b42b8d9..665eeffd0297 100644 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ b/packages/eslint-plugin/src/rules/comma-spacing.ts @@ -2,8 +2,12 @@ import { TSESTree, AST_TOKEN_TYPES, } from '@typescript-eslint/experimental-utils'; -import { isClosingParenToken, isCommaToken } from 'eslint-utils'; -import { isTokenOnSameLine, createRule } from '../util'; +import { + isClosingParenToken, + isCommaToken, + isTokenOnSameLine, + createRule, +} from '../util'; type Options = [ { diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts index 8e11d58592be..53858ad18ab4 100644 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ b/packages/eslint-plugin/src/rules/func-call-spacing.ts @@ -1,5 +1,4 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; export type Options = [ @@ -79,7 +78,7 @@ export default util.createRule({ | TSESTree.OptionalCallExpression | TSESTree.NewExpression, ): void { - const isOptionalCall = util.isOptionalOptionalChain(node); + const isOptionalCall = util.isOptionalOptionalCallExpression(node); const closingParenToken = sourceCode.getLastToken(node)!; const lastCalleeTokenWithoutPossibleParens = sourceCode.getLastToken( @@ -88,7 +87,7 @@ export default util.createRule({ const openingParenToken = sourceCode.getFirstTokenBetween( lastCalleeTokenWithoutPossibleParens, closingParenToken, - isOpeningParenToken, + util.isOpeningParenToken, ); if (!openingParenToken || openingParenToken.range[1] >= node.range[1]) { // new expression with no parens... diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts index 923423dbc146..3c24f29ad9e5 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -8,7 +8,13 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; + +import { TokenOrComment } from './BinarySearchTree'; +import { OffsetStorage } from './OffsetStorage'; +import { TokenInfo } from './TokenInfo'; import { + createRule, + ExcludeKeys, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, @@ -19,11 +25,8 @@ import { isOpeningBraceToken, isOpeningParenToken, isSemicolonToken, -} from 'eslint-utils'; -import { TokenOrComment } from './BinarySearchTree'; -import { OffsetStorage } from './OffsetStorage'; -import { TokenInfo } from './TokenInfo'; -import { createRule, ExcludeKeys, RequireKeys } from '../../util'; + RequireKeys, +} from '../../util'; const GLOBAL_LINEBREAK_REGEX = /\r\n|[\r\n\u2028\u2029]/gu; const WHITESPACE_REGEX = /\s*$/u; diff --git a/packages/eslint-plugin/src/rules/no-array-constructor.ts b/packages/eslint-plugin/src/rules/no-array-constructor.ts index dddae97f6633..c9465e707996 100644 --- a/packages/eslint-plugin/src/rules/no-array-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-array-constructor.ts @@ -37,7 +37,7 @@ export default util.createRule({ node.callee.type === AST_NODE_TYPES.Identifier && node.callee.name === 'Array' && !node.typeParameters && - !util.isOptionalOptionalChain(node) + !util.isOptionalOptionalCallExpression(node) ) { context.report({ node, diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 0cd54d6c7869..b31280b4d46a 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -2,10 +2,9 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { getStaticValue } from 'eslint-utils'; import { AST as RegExpAST, parseRegExpLiteral } from 'regexpp'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { createRule, getParserServices, getStaticValue } from '../util'; export default createRule({ name: 'prefer-includes', diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts index e0740c2e913c..0ec33df81801 100644 --- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts +++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts @@ -2,7 +2,6 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; type ValidChainTarget = @@ -203,7 +202,7 @@ export default util.createRule({ sourceCode.getFirstTokenBetween( node.callee, closingParenToken, - isOpeningParenToken, + util.isOpeningParenToken, ), util.NullThrowsReasons.MissingToken('opening parenthesis', node.type), ); diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 599e0f2a6da3..dda6f15d3a5c 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,6 +1,10 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import { getStaticValue } from 'eslint-utils'; -import { createRule, getParserServices, getTypeName } from '../util'; +import { + createRule, + getParserServices, + getStaticValue, + getTypeName, +} from '../util'; export default createRule({ name: 'prefer-regexp-exec', diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 2b4946b2a14c..217eada2e85d 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -3,13 +3,15 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; +import { AST as RegExpAST, RegExpParser } from 'regexpp'; import { + createRule, + getParserServices, getPropertyName, getStaticValue, + getTypeName, isNotClosingParenToken, -} from 'eslint-utils'; -import { AST as RegExpAST, RegExpParser } from 'regexpp'; -import { createRule, getParserServices, getTypeName } from '../util'; +} from '../util'; const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 4688373dc383..e7b6a5f5959a 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -3,11 +3,6 @@ import { TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { - isArrowToken, - getFunctionNameWithKind, - isOpeningParenToken, -} from 'eslint-utils'; import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -73,7 +68,7 @@ export default util.createRule({ loc: getFunctionHeadLoc(node, sourceCode), messageId: 'missingAwait', data: { - name: util.upperCaseFirst(getFunctionNameWithKind(node)), + name: util.upperCaseFirst(util.getFunctionNameWithKind(node)), }, }); } @@ -157,8 +152,8 @@ function getOpeningParenOfParams( ): TSESTree.Token { return util.nullThrows( node.id - ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) - : sourceCode.getFirstToken(node, isOpeningParenToken), + ? sourceCode.getTokenAfter(node.id, util.isOpeningParenToken) + : sourceCode.getFirstToken(node, util.isOpeningParenToken), util.NullThrowsReasons.MissingToken('(', node.type), ); } @@ -180,7 +175,7 @@ function getFunctionHeadLoc( if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { const arrowToken = util.nullThrows( - sourceCode.getTokenBefore(node.body, isArrowToken), + sourceCode.getTokenBefore(node.body, util.isArrowToken), util.NullThrowsReasons.MissingToken('=>', node.type), ); diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts index e4b4cb2f960d..bc2ad8b0d047 100644 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ b/packages/eslint-plugin/src/rules/space-before-function-paren.ts @@ -2,7 +2,6 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; -import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; type Option = 'never' | 'always'; @@ -106,7 +105,7 @@ export default util.createRule({ // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar if ( node.async && - isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 })!) + util.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 })!) ) { return overrideConfig.asyncArrow ?? baseConfig; } @@ -143,7 +142,7 @@ export default util.createRule({ leftToken = sourceCode.getLastToken(node.typeParameters)!; rightToken = sourceCode.getTokenAfter(leftToken)!; } else { - rightToken = sourceCode.getFirstToken(node, isOpeningParenToken)!; + rightToken = sourceCode.getFirstToken(node, util.isOpeningParenToken)!; leftToken = sourceCode.getTokenBefore(rightToken)!; } const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken); diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 2cf394d20267..414dbbcdd03d 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -2,11 +2,12 @@ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import * as ts from 'typescript'; import { createRule, - getParserServices, getConstrainedTypeAtLocation, + getParserServices, + isClosingBraceToken, + isOpeningBraceToken, } from '../util'; import { isTypeFlagSet, unionTypeParts } from 'tsutils'; -import { isClosingBraceToken, isOpeningBraceToken } from 'eslint-utils'; export default createRule({ name: 'switch-exhaustiveness-check', diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts index 03f917e4ffbd..12f003e98d0b 100644 --- a/packages/eslint-plugin/src/util/astUtils.ts +++ b/packages/eslint-plugin/src/util/astUtils.ts @@ -1,265 +1,2 @@ -import { - AST_NODE_TYPES, - AST_TOKEN_TYPES, - TSESTree, -} from '@typescript-eslint/experimental-utils'; - -const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/; - -function isOptionalChainPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): token is TSESTree.PunctuatorToken & { value: '?.' } { - return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?.'; -} -function isNotOptionalChainPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): boolean { - return !isOptionalChainPunctuator(token); -} - -function isNonNullAssertionPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): token is TSESTree.PunctuatorToken & { value: '!' } { - return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '!'; -} -function isNotNonNullAssertionPunctuator( - token: TSESTree.Token | TSESTree.Comment, -): boolean { - return !isNonNullAssertionPunctuator(token); -} - -/** - * Returns true if and only if the node represents: foo?.() or foo.bar?.() - */ -function isOptionalOptionalChain( - node: TSESTree.Node, -): node is TSESTree.OptionalCallExpression & { optional: true } { - return ( - node.type === AST_NODE_TYPES.OptionalCallExpression && - // this flag means the call expression itself is option - // i.e. it is foo.bar?.() and not foo?.bar() - node.optional - ); -} - -/** - * Returns true if and only if the node represents logical OR - */ -function isLogicalOrOperator( - node: TSESTree.Node, -): node is TSESTree.LogicalExpression & { operator: '||' } { - return ( - node.type === AST_NODE_TYPES.LogicalExpression && node.operator === '||' - ); -} - -/** - * Determines whether two adjacent tokens are on the same line - */ -function isTokenOnSameLine( - left: TSESTree.Token | TSESTree.Comment, - right: TSESTree.Token | TSESTree.Comment, -): boolean { - return left.loc.end.line === right.loc.start.line; -} - -/** - * Checks if a node is a type assertion: - * ``` - * x as foo - * x - * ``` - */ -function isTypeAssertion( - node: TSESTree.Node | undefined | null, -): node is TSESTree.TSAsExpression | TSESTree.TSTypeAssertion { - if (!node) { - return false; - } - return ( - node.type === AST_NODE_TYPES.TSAsExpression || - node.type === AST_NODE_TYPES.TSTypeAssertion - ); -} - -function isVariableDeclarator( - node: TSESTree.Node | undefined, -): node is TSESTree.VariableDeclarator { - return node?.type === AST_NODE_TYPES.VariableDeclarator; -} - -function isFunction( - node: TSESTree.Node | undefined, -): node is - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression { - if (!node) { - return false; - } - - return [ - AST_NODE_TYPES.ArrowFunctionExpression, - AST_NODE_TYPES.FunctionDeclaration, - AST_NODE_TYPES.FunctionExpression, - ].includes(node.type); -} - -function isFunctionType( - node: TSESTree.Node | undefined, -): node is - | TSESTree.TSCallSignatureDeclaration - | TSESTree.TSConstructorType - | TSESTree.TSConstructSignatureDeclaration - | TSESTree.TSEmptyBodyFunctionExpression - | TSESTree.TSFunctionType - | TSESTree.TSMethodSignature { - if (!node) { - return false; - } - - return [ - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConstructorType, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - AST_NODE_TYPES.TSFunctionType, - AST_NODE_TYPES.TSMethodSignature, - ].includes(node.type); -} - -function isFunctionOrFunctionType( - node: TSESTree.Node | undefined, -): node is - | TSESTree.ArrowFunctionExpression - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.TSCallSignatureDeclaration - | TSESTree.TSConstructorType - | TSESTree.TSConstructSignatureDeclaration - | TSESTree.TSEmptyBodyFunctionExpression - | TSESTree.TSFunctionType - | TSESTree.TSMethodSignature { - return isFunction(node) || isFunctionType(node); -} - -function isTSFunctionType( - node: TSESTree.Node | undefined, -): node is TSESTree.TSFunctionType { - return node?.type === AST_NODE_TYPES.TSFunctionType; -} - -function isTSConstructorType( - node: TSESTree.Node | undefined, -): node is TSESTree.TSConstructorType { - return node?.type === AST_NODE_TYPES.TSConstructorType; -} - -function isClassOrTypeElement( - node: TSESTree.Node | undefined, -): node is TSESTree.ClassElement | TSESTree.TypeElement { - if (!node) { - return false; - } - - return [ - // ClassElement - AST_NODE_TYPES.ClassProperty, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.MethodDefinition, - AST_NODE_TYPES.TSAbstractClassProperty, - AST_NODE_TYPES.TSAbstractMethodDefinition, - AST_NODE_TYPES.TSEmptyBodyFunctionExpression, - AST_NODE_TYPES.TSIndexSignature, - // TypeElement - AST_NODE_TYPES.TSCallSignatureDeclaration, - AST_NODE_TYPES.TSConstructSignatureDeclaration, - // AST_NODE_TYPES.TSIndexSignature, - AST_NODE_TYPES.TSMethodSignature, - AST_NODE_TYPES.TSPropertySignature, - ].includes(node.type); -} - -/** - * Checks if a node is a constructor method. - */ -function isConstructor( - node: TSESTree.Node | undefined, -): node is TSESTree.MethodDefinition { - return ( - node?.type === AST_NODE_TYPES.MethodDefinition && - node.kind === 'constructor' - ); -} - -/** - * Checks if a node is a setter method. - */ -function isSetter( - node: TSESTree.Node | undefined, -): node is TSESTree.MethodDefinition | TSESTree.Property { - return ( - !!node && - (node.type === AST_NODE_TYPES.MethodDefinition || - node.type === AST_NODE_TYPES.Property) && - node.kind === 'set' - ); -} - -function isIdentifier( - node: TSESTree.Node | undefined, -): node is TSESTree.Identifier { - return node?.type === AST_NODE_TYPES.Identifier; -} - -/** - * Checks if a node represents an `await …` expression. - */ -function isAwaitExpression( - node: TSESTree.Node | undefined | null, -): node is TSESTree.AwaitExpression { - return node?.type === AST_NODE_TYPES.AwaitExpression; -} - -/** - * Checks if a possible token is the `await` keyword. - */ -function isAwaitKeyword( - node: TSESTree.Token | TSESTree.Comment | undefined | null, -): node is TSESTree.KeywordToken & { value: 'await' } { - return node?.type === AST_TOKEN_TYPES.Identifier && node.value === 'await'; -} - -function isMemberOrOptionalMemberExpression( - node: TSESTree.Node, -): node is TSESTree.MemberExpression | TSESTree.OptionalMemberExpression { - return ( - node.type === AST_NODE_TYPES.MemberExpression || - node.type === AST_NODE_TYPES.OptionalMemberExpression - ); -} - -export { - isAwaitExpression, - isAwaitKeyword, - isConstructor, - isClassOrTypeElement, - isFunction, - isFunctionOrFunctionType, - isFunctionType, - isIdentifier, - isLogicalOrOperator, - isMemberOrOptionalMemberExpression, - isNonNullAssertionPunctuator, - isNotNonNullAssertionPunctuator, - isNotOptionalChainPunctuator, - isOptionalChainPunctuator, - isOptionalOptionalChain, - isSetter, - isTokenOnSameLine, - isTSConstructorType, - isTSFunctionType, - isTypeAssertion, - isVariableDeclarator, - LINEBREAK_MATCHER, -}; +// deeply re-export, for convenience +export * from '@typescript-eslint/experimental-utils/dist/ast-utils'; diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index c024b6a845cc..e9f50fd2107e 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -82,9 +82,7 @@ function findFirstResult( /** * Gets a string representation of the name of the index signature. */ -export function getNameFromIndexSignature( - node: TSESTree.TSIndexSignature, -): string { +function getNameFromIndexSignature(node: TSESTree.TSIndexSignature): string { const propName: TSESTree.PropertyName | undefined = node.parameters.find( (parameter: TSESTree.Parameter): parameter is TSESTree.Identifier => parameter.type === AST_NODE_TYPES.Identifier, @@ -136,6 +134,7 @@ export { ExcludeKeys, findFirstResult, getEnumNames, + getNameFromIndexSignature, getNameFromMember, InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, diff --git a/packages/eslint-plugin/typings/eslint-utils.d.ts b/packages/eslint-plugin/typings/eslint-utils.d.ts deleted file mode 100644 index f2fc090c7a6f..000000000000 --- a/packages/eslint-plugin/typings/eslint-utils.d.ts +++ /dev/null @@ -1,180 +0,0 @@ -declare module 'eslint-utils' { - import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - - export function getFunctionHeadLocation( - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - sourceCode: TSESLint.SourceCode, - ): TSESTree.SourceLocation; - - export function getFunctionNameWithKind( - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - ): string; - - export function getPropertyName( - node: - | TSESTree.MemberExpression - | TSESTree.OptionalMemberExpression - | TSESTree.Property - | TSESTree.MethodDefinition, - initialScope?: TSESLint.Scope.Scope, - ): string | null; - - export function getStaticValue( - node: TSESTree.Node, - initialScope?: TSESLint.Scope.Scope, - ): { value: unknown } | null; - - export function getStringIfConstant( - node: TSESTree.Node, - initialScope?: TSESLint.Scope.Scope, - ): string | null; - - export function hasSideEffect( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, - options?: { - considerGetters?: boolean; - considerImplicitTypeConversion?: boolean; - }, - ): boolean; - - export function isParenthesized( - node: TSESTree.Node, - sourceCode: TSESLint.SourceCode, - ): boolean; - - export class PatternMatcher { - constructor(pattern: RegExp, options?: { escaped?: boolean }); - execAll(str: string): IterableIterator; - test(str: string): boolean; - } - - export function findVariable( - initialScope: TSESLint.Scope.Scope, - name: string, - ): TSESLint.Scope.Variable | null; - - export function getInnermostScope( - initialScope: TSESLint.Scope.Scope, - node: TSESTree.Node, - ): TSESLint.Scope.Scope; - - export class ReferenceTracker { - static readonly READ: unique symbol; - static readonly CALL: unique symbol; - static readonly CONSTRUCT: unique symbol; - - constructor( - globalScope: TSESLint.Scope.Scope, - options?: { - mode: 'strict' | 'legacy'; - globalObjectNames: readonly string[]; - }, - ); - - iterateGlobalReferences( - traceMap: ReferenceTracker.TraceMap, - ): IterableIterator>; - iterateCjsReferences( - traceMap: ReferenceTracker.TraceMap, - ): IterableIterator>; - iterateEsmReferences( - traceMap: ReferenceTracker.TraceMap, - ): IterableIterator>; - } - - export namespace ReferenceTracker { - export type READ = typeof ReferenceTracker.READ; - export type CALL = typeof ReferenceTracker.READ; - export type CONSTRUCT = typeof ReferenceTracker.READ; - export type ReferenceType = READ | CALL | CONSTRUCT; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - export type TraceMap = Record>; - export interface TraceMapElement { - [ReferenceTracker.READ]?: T; - [ReferenceTracker.CALL]?: T; - [ReferenceTracker.CONSTRUCT]?: T; - [key: string]: TraceMapElement; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - export interface FoundReference { - node: TSESTree.Node; - path: readonly string[]; - type: ReferenceType; - entry: T; - } - } - - export function isArrowToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '=>' }; - export function isNotArrowToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isClosingBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '}' }; - export function isNotClosingBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isClosingBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ']' }; - export function isNotClosingBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isClosingParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ')' }; - export function isNotClosingParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isColonToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ':' }; - export function isNotColonToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isCommaToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ',' }; - export function isNotCommaToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isCommentToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.Comment; - export function isNotCommentToken< - T extends TSESTree.Token | TSESTree.Comment - >(token: T): token is Exclude; - export function isOpeningBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '{' }; - export function isNotOpeningBraceToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isOpeningBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '[' }; - export function isNotOpeningBracketToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isOpeningParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: '(' }; - export function isNotOpeningParenToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; - export function isSemicolonToken( - token: TSESTree.Token | TSESTree.Comment, - ): token is TSESTree.PunctuatorToken & { value: ';' }; - export function isNotSemicolonToken( - token: TSESTree.Token | TSESTree.Comment, - ): boolean; -} diff --git a/packages/experimental-utils/README.md b/packages/experimental-utils/README.md index 2f02ef10615f..97d9d5c057b2 100644 --- a/packages/experimental-utils/README.md +++ b/packages/experimental-utils/README.md @@ -5,23 +5,26 @@ ## Note This package has inherited its version number from the `@typescript-eslint` project. -Meaning that even though this package is `1.x.y`, you shouldn't expect 100% stability between minor version bumps. +Meaning that even though this package is `2.x.y`, you shouldn't expect 100% stability between minor version bumps. i.e. treat it as a `0.x.y` package. Feel free to use it now, and let us know what utilities you need or send us PRs with utilities you build on top of it. -Once it is stable, it will be renamed to `@typescript-eslint/util` for a `2.0.0` release. +Once it is stable, it will be renamed to `@typescript-eslint/util` for a `3.0.0` release. ## Exports -| Name | Description | -| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| [`TSESTree`](../packages/typescript-estree/src/ts-estree/ts-estree.ts) | Types for the TypeScript flavor of ESTree created by `@typescript-eslint/typescript-estree`. | -| [`AST_NODE_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _node_ found in `TSESTree`. | -| [`AST_TOKEN_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _token_ found in `TSESTree`. | -| [`TSESLint`](./src/ts-eslint) | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | -| [`ESLintUtils`](./src/eslint-utils) | Tools for creating ESLint rules with TypeScript. | -| [`ParserServices`](../packages/typescript-estree/src/ts-estree/parser.ts) | The parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | +| Name | Description | +| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`ASTUtils`](./src/ast-utils) | Tools for operating on the ESTree AST. Also includes the [`eslint-utils`](https://www.npmjs.com/package/eslint-utils) package, correctly typed to work with the types found in `TSESTree` | +| [`ESLintUtils`](./src/eslint-utils) | Tools for creating ESLint rules with TypeScript. | +| `JSONSchema` | Types from the [`@types/json-schema`](https://www.npmjs.com/package/@types/json-schema) package, re-exported to save you having to manually import them. Also ensures you're using the same version of the types as this package. | +| [`TSESLint`](./src/ts-eslint) | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | +| [`TSESLintScope`](./src/ts-eslint-scope) | The [`eslint-scope`](https://www.npmjs.com/package/eslint-scope) package, correctly typed to work with the types found in both `TSESTree` and `TSESLint` | +| [`TSESTree`](../packages/typescript-estree/src/ts-estree/ts-estree.ts) | Types for the TypeScript flavor of ESTree created by `@typescript-eslint/typescript-estree`. | +| [`AST_NODE_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _node_ found in `TSESTree`. | +| [`AST_TOKEN_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) | An enum with the names of every single _token_ found in `TSESTree`. | +| [`ParserServices`](../packages/typescript-estree/src/ts-estree/parser.ts) | Typing for the parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | ## Contributing diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index a8797df8a9c9..287b52001c65 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -38,7 +38,8 @@ "dependencies": { "@types/json-schema": "^7.0.3", "@typescript-eslint/typescript-estree": "2.24.0", - "eslint-scope": "^5.0.0" + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" }, "peerDependencies": { "eslint": "*" diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts new file mode 100644 index 000000000000..3f45db7db467 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/PatternMatcher.ts @@ -0,0 +1,56 @@ +import * as eslintUtils from 'eslint-utils'; + +interface PatternMatcher { + /** + * Iterate all matched parts in a given string. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#matcher-execall} + */ + execAll(str: string): IterableIterator; + + /** + * Check whether this pattern matches a given string or not. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#matcher-test} + */ + test(str: string): boolean; + + /** + * Replace all matched parts by a given replacer. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#matcher-symbol-replace} + * @example + * const { PatternMatcher } = require("eslint-utils") + * const matcher = new PatternMatcher(/\\p{Script=Greek}/g) + * + * module.exports = { + * meta: {}, + * create(context) { + * return { + * "Literal[regex]"(node) { + * const replacedPattern = node.regex.pattern.replace( + * matcher, + * "[\\u0370-\\u0373\\u0375-\\u0377\\u037A-\\u037D\\u037F\\u0384\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03E1\\u03F0-\\u03FF\\u1D26-\\u1D2A\\u1D5D-\\u1D61\\u1D66-\\u1D6A\\u1DBF\\u1F00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FC4\\u1FC6-\\u1FD3\\u1FD6-\\u1FDB\\u1FDD-\\u1FEF\\u1FF2-\\u1FF4\\u1FF6-\\u1FFE\\u2126\\uAB65]|\\uD800[\\uDD40-\\uDD8E\\uDDA0]|\\uD834[\\uDE00-\\uDE45]" + * ) + * }, + * } + * }, + * } + */ + [Symbol.replace]( + str: string, + replacer: string | ((...strs: string[]) => string), + ): string; +} + +/** + * The class to find a pattern in strings as handling escape sequences. + * It ignores the found pattern if it's escaped with `\`. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#patternmatcher-class} + */ +const PatternMatcher = eslintUtils.PatternMatcher as { + new (pattern: RegExp, options?: { escaped?: boolean }): PatternMatcher; +}; + +export { PatternMatcher }; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts new file mode 100644 index 000000000000..2298ac1fb1b2 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/ReferenceTracker.ts @@ -0,0 +1,94 @@ +/* eslint-disable @typescript-eslint/no-namespace */ + +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; +import * as TSESLint from '../../ts-eslint'; + +const ReferenceTrackerREAD: unique symbol = eslintUtils.ReferenceTracker.READ; +const ReferenceTrackerCALL: unique symbol = eslintUtils.ReferenceTracker.CALL; +const ReferenceTrackerCONSTRUCT: unique symbol = + eslintUtils.ReferenceTracker.CONSTRUCT; + +interface ReferenceTracker { + /** + * Iterate the references that the given `traceMap` determined. + * This method starts to search from global variables. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#tracker-iterateglobalreferences} + */ + iterateGlobalReferences( + traceMap: ReferenceTracker.TraceMap, + ): IterableIterator>; + + /** + * Iterate the references that the given `traceMap` determined. + * This method starts to search from `require()` expression. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#tracker-iteratecjsreferences} + */ + iterateCjsReferences( + traceMap: ReferenceTracker.TraceMap, + ): IterableIterator>; + + /** + * Iterate the references that the given `traceMap` determined. + * This method starts to search from `import`/`export` declarations. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#tracker-iterateesmreferences} + */ + iterateEsmReferences( + traceMap: ReferenceTracker.TraceMap, + ): IterableIterator>; +} +interface ReferenceTrackerStatic { + new ( + globalScope: TSESLint.Scope.Scope, + options?: { + /** + * The mode which determines how the `tracker.iterateEsmReferences()` method scans CommonJS modules. + * If this is `"strict"`, the method binds CommonJS modules to the default export. Otherwise, the method binds + * CommonJS modules to both the default export and named exports. Optional. Default is `"strict"`. + */ + mode: 'strict' | 'legacy'; + /** + * The name list of Global Object. Optional. Default is `["global", "globalThis", "self", "window"]`. + */ + globalObjectNames: readonly string[]; + }, + ): ReferenceTracker; + + readonly READ: typeof ReferenceTrackerREAD; + readonly CALL: typeof ReferenceTrackerCALL; + readonly CONSTRUCT: typeof ReferenceTrackerCONSTRUCT; +} + +namespace ReferenceTracker { + export type READ = ReferenceTrackerStatic['READ']; + export type CALL = ReferenceTrackerStatic['CALL']; + export type CONSTRUCT = ReferenceTrackerStatic['CONSTRUCT']; + export type ReferenceType = READ | CALL | CONSTRUCT; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export type TraceMap = Record>; + export interface TraceMapElement { + [ReferenceTrackerREAD]?: T; + [ReferenceTrackerCALL]?: T; + [ReferenceTrackerCONSTRUCT]?: T; + [key: string]: TraceMapElement; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + export interface FoundReference { + node: TSESTree.Node; + path: readonly string[]; + type: ReferenceType; + entry: T; + } +} + +/** + * The tracker for references. This provides reference tracking for global variables, CommonJS modules, and ES modules. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#referencetracker-class} + */ +const ReferenceTracker = eslintUtils.ReferenceTracker as ReferenceTrackerStatic; + +export { ReferenceTracker }; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts new file mode 100644 index 000000000000..429b5a59cee6 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/astUtilities.ts @@ -0,0 +1,127 @@ +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; +import * as TSESLint from '../../ts-eslint'; + +/** + * Get the proper location of a given function node to report. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getfunctionheadlocation} + */ +const getFunctionHeadLocation = eslintUtils.getFunctionHeadLocation as ( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, + sourceCode: TSESLint.SourceCode, +) => TSESTree.SourceLocation; + +/** + * Get the name and kind of a given function node. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getfunctionnamewithkind} + */ +const getFunctionNameWithKind = eslintUtils.getFunctionNameWithKind as ( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, +) => string; + +/** + * Get the property name of a given property node. + * If the node is a computed property, this tries to compute the property name by the getStringIfConstant function. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getpropertyname} + * @returns The property name of the node. If the property name is not constant then it returns `null`. + */ +const getPropertyName = eslintUtils.getPropertyName as ( + node: + | TSESTree.MemberExpression + | TSESTree.OptionalMemberExpression + | TSESTree.Property + | TSESTree.MethodDefinition, + initialScope?: TSESLint.Scope.Scope, +) => string | null; + +/** + * Get the value of a given node if it can decide the value statically. + * If the 2nd parameter `initialScope` was given, this function tries to resolve identifier references which are in the + * given node as much as possible. In the resolving way, it does on the assumption that built-in global objects have + * not been modified. + * For example, it considers `Symbol.iterator`, ` String.raw``hello`` `, and `Object.freeze({a: 1}).a` as static. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getstaticvalue} + * @returns The `{ value: any }` shaped object. The `value` property is the static value. If it couldn't compute the + * static value of the node, it returns `null`. + */ +const getStaticValue = eslintUtils.getStaticValue as ( + node: TSESTree.Node, + initialScope?: TSESLint.Scope.Scope, +) => { value: unknown } | null; + +/** + * Get the string value of a given node. + * This function is a tiny wrapper of the getStaticValue function. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#getstringifconstant} + */ +const getStringIfConstant = eslintUtils.getStringIfConstant as ( + node: TSESTree.Node, + initialScope?: TSESLint.Scope.Scope, +) => string | null; + +/** + * Check whether a given node has any side effect or not. + * The side effect means that it may modify a certain variable or object member. This function considers the node which + * contains the following types as the node which has side effects: + * - `AssignmentExpression` + * - `AwaitExpression` + * - `CallExpression` + * - `ImportExpression` + * - `NewExpression` + * - `UnaryExpression([operator = "delete"])` + * - `UpdateExpression` + * - `YieldExpression` + * - When `options.considerGetters` is `true`: + * - `MemberExpression` + * - When `options.considerImplicitTypeConversion` is `true`: + * - `BinaryExpression([operator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "|" | "^" | "&" | "in"])` + * - `MemberExpression([computed = true])` + * - `MethodDefinition([computed = true])` + * - `Property([computed = true])` + * - `UnaryExpression([operator = "-" | "+" | "!" | "~"])` + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#hassideeffect} + */ +const hasSideEffect = eslintUtils.hasSideEffect as ( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, + options?: { + considerGetters?: boolean; + considerImplicitTypeConversion?: boolean; + }, +) => boolean; + +/** + * Check whether a given node is parenthesized or not. + * This function detects it correctly even if it's parenthesized by specific syntax. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/ast-utils.html#isparenthesized} + * @returns `true` if the node is parenthesized. + * If `times` was given, it returns `true` only if the node is parenthesized the `times` times. + * For example, `isParenthesized(2, node, sourceCode)` returns true for `((foo))`, but not for `(foo)`. + */ +const isParenthesized = eslintUtils.isParenthesized as ( + node: TSESTree.Node, + sourceCode: TSESLint.SourceCode, +) => boolean; + +export { + getFunctionHeadLocation, + getFunctionNameWithKind, + getPropertyName, + getStaticValue, + getStringIfConstant, + hasSideEffect, + isParenthesized, +}; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/index.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/index.ts new file mode 100644 index 000000000000..4a41363d1a34 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/index.ts @@ -0,0 +1,5 @@ +export * from './astUtilities'; +export * from './PatternMatcher'; +export * from './predicates'; +export * from './ReferenceTracker'; +export * from './scopeAnalysis'; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts new file mode 100644 index 000000000000..cbf8377127c6 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/predicates.ts @@ -0,0 +1,106 @@ +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; + +const isArrowToken = eslintUtils.isArrowToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '=>' }; +const isNotArrowToken = eslintUtils.isNotArrowToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isClosingBraceToken = eslintUtils.isClosingBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '}' }; +const isNotClosingBraceToken = eslintUtils.isNotClosingBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isClosingBracketToken = eslintUtils.isClosingBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ']' }; +const isNotClosingBracketToken = eslintUtils.isNotClosingBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isClosingParenToken = eslintUtils.isClosingParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ')' }; +const isNotClosingParenToken = eslintUtils.isNotClosingParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isColonToken = eslintUtils.isColonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ':' }; +const isNotColonToken = eslintUtils.isNotColonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isCommaToken = eslintUtils.isCommaToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ',' }; +const isNotCommaToken = eslintUtils.isNotCommaToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isCommentToken = eslintUtils.isCommentToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.Comment; +const isNotCommentToken = eslintUtils.isNotCommentToken as < + T extends TSESTree.Token | TSESTree.Comment +>( + token: T, +) => token is Exclude; + +const isOpeningBraceToken = eslintUtils.isOpeningBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '{' }; +const isNotOpeningBraceToken = eslintUtils.isNotOpeningBraceToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isOpeningBracketToken = eslintUtils.isOpeningBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '[' }; +const isNotOpeningBracketToken = eslintUtils.isNotOpeningBracketToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isOpeningParenToken = eslintUtils.isOpeningParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: '(' }; +const isNotOpeningParenToken = eslintUtils.isNotOpeningParenToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +const isSemicolonToken = eslintUtils.isSemicolonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => token is TSESTree.PunctuatorToken & { value: ';' }; +const isNotSemicolonToken = eslintUtils.isNotSemicolonToken as ( + token: TSESTree.Token | TSESTree.Comment, +) => boolean; + +export { + isArrowToken, + isClosingBraceToken, + isClosingBracketToken, + isClosingParenToken, + isColonToken, + isCommaToken, + isCommentToken, + isNotArrowToken, + isNotClosingBraceToken, + isNotClosingBracketToken, + isNotClosingParenToken, + isNotColonToken, + isNotCommaToken, + isNotCommentToken, + isNotOpeningBraceToken, + isNotOpeningBracketToken, + isNotOpeningParenToken, + isNotSemicolonToken, + isOpeningBraceToken, + isOpeningBracketToken, + isOpeningParenToken, + isSemicolonToken, +}; diff --git a/packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts b/packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts new file mode 100644 index 000000000000..15f9325a582f --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/eslint-utils/scopeAnalysis.ts @@ -0,0 +1,27 @@ +import * as eslintUtils from 'eslint-utils'; +import { TSESTree } from '../../ts-estree'; +import * as TSESLint from '../../ts-eslint'; + +/** + * Get the variable of a given name. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#findvariable} + */ +const findVariable = eslintUtils.findVariable as ( + initialScope: TSESLint.Scope.Scope, + name: string, +) => TSESLint.Scope.Variable | null; + +/** + * Get the innermost scope which contains a given node. + * + * @see {@link https://eslint-utils.mysticatea.dev/api/scope-utils.html#getinnermostscope} + * @returns The innermost scope which contains the given node. + * If such scope doesn't exist then it returns the 1st argument `initialScope`. + */ +const getInnermostScope = eslintUtils.getInnermostScope as ( + initialScope: TSESLint.Scope.Scope, + node: TSESTree.Node, +) => TSESLint.Scope.Scope; + +export { findVariable, getInnermostScope }; diff --git a/packages/experimental-utils/src/ast-utils/index.ts b/packages/experimental-utils/src/ast-utils/index.ts new file mode 100644 index 000000000000..c0ab325299ad --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/index.ts @@ -0,0 +1,3 @@ +export * from './misc'; +export * from './predicates'; +export * from './eslint-utils'; diff --git a/packages/experimental-utils/src/ast-utils/misc.ts b/packages/experimental-utils/src/ast-utils/misc.ts new file mode 100644 index 000000000000..cf20f99a4cc4 --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/misc.ts @@ -0,0 +1,15 @@ +import { TSESTree } from '../ts-estree'; + +const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/; + +/** + * Determines whether two adjacent tokens are on the same line + */ +function isTokenOnSameLine( + left: TSESTree.Token | TSESTree.Comment, + right: TSESTree.Token | TSESTree.Comment, +): boolean { + return left.loc.end.line === right.loc.start.line; +} + +export { isTokenOnSameLine, LINEBREAK_MATCHER }; diff --git a/packages/experimental-utils/src/ast-utils/predicates.ts b/packages/experimental-utils/src/ast-utils/predicates.ts new file mode 100644 index 000000000000..b12f5d9ad6fc --- /dev/null +++ b/packages/experimental-utils/src/ast-utils/predicates.ts @@ -0,0 +1,247 @@ +import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from '../ts-estree'; + +function isOptionalChainPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): token is TSESTree.PunctuatorToken & { value: '?.' } { + return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?.'; +} +function isNotOptionalChainPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): boolean { + return !isOptionalChainPunctuator(token); +} + +function isNonNullAssertionPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): token is TSESTree.PunctuatorToken & { value: '!' } { + return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '!'; +} +function isNotNonNullAssertionPunctuator( + token: TSESTree.Token | TSESTree.Comment, +): boolean { + return !isNonNullAssertionPunctuator(token); +} + +/** + * Returns true if and only if the node represents: foo?.() or foo.bar?.() + */ +function isOptionalOptionalCallExpression( + node: TSESTree.Node, +): node is TSESTree.OptionalCallExpression & { optional: true } { + return ( + node.type === AST_NODE_TYPES.OptionalCallExpression && + // this flag means the call expression itself is option + // i.e. it is foo.bar?.() and not foo?.bar() + node.optional + ); +} + +/** + * Returns true if and only if the node represents logical OR + */ +function isLogicalOrOperator( + node: TSESTree.Node, +): node is TSESTree.LogicalExpression & { operator: '||' } { + return ( + node.type === AST_NODE_TYPES.LogicalExpression && node.operator === '||' + ); +} + +/** + * Checks if a node is a type assertion: + * ``` + * x as foo + * x + * ``` + */ +function isTypeAssertion( + node: TSESTree.Node | undefined | null, +): node is TSESTree.TSAsExpression | TSESTree.TSTypeAssertion { + if (!node) { + return false; + } + return ( + node.type === AST_NODE_TYPES.TSAsExpression || + node.type === AST_NODE_TYPES.TSTypeAssertion + ); +} + +function isVariableDeclarator( + node: TSESTree.Node | undefined, +): node is TSESTree.VariableDeclarator { + return node?.type === AST_NODE_TYPES.VariableDeclarator; +} + +function isFunction( + node: TSESTree.Node | undefined, +): node is + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression { + if (!node) { + return false; + } + + return [ + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + ].includes(node.type); +} + +function isFunctionType( + node: TSESTree.Node | undefined, +): node is + | TSESTree.TSCallSignatureDeclaration + | TSESTree.TSConstructorType + | TSESTree.TSConstructSignatureDeclaration + | TSESTree.TSEmptyBodyFunctionExpression + | TSESTree.TSFunctionType + | TSESTree.TSMethodSignature { + if (!node) { + return false; + } + + return [ + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConstructorType, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSFunctionType, + AST_NODE_TYPES.TSMethodSignature, + ].includes(node.type); +} + +function isFunctionOrFunctionType( + node: TSESTree.Node | undefined, +): node is + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.TSCallSignatureDeclaration + | TSESTree.TSConstructorType + | TSESTree.TSConstructSignatureDeclaration + | TSESTree.TSEmptyBodyFunctionExpression + | TSESTree.TSFunctionType + | TSESTree.TSMethodSignature { + return isFunction(node) || isFunctionType(node); +} + +function isTSFunctionType( + node: TSESTree.Node | undefined, +): node is TSESTree.TSFunctionType { + return node?.type === AST_NODE_TYPES.TSFunctionType; +} + +function isTSConstructorType( + node: TSESTree.Node | undefined, +): node is TSESTree.TSConstructorType { + return node?.type === AST_NODE_TYPES.TSConstructorType; +} + +function isClassOrTypeElement( + node: TSESTree.Node | undefined, +): node is TSESTree.ClassElement | TSESTree.TypeElement { + if (!node) { + return false; + } + + return [ + // ClassElement + AST_NODE_TYPES.ClassProperty, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.MethodDefinition, + AST_NODE_TYPES.TSAbstractClassProperty, + AST_NODE_TYPES.TSAbstractMethodDefinition, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSIndexSignature, + // TypeElement + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + // AST_NODE_TYPES.TSIndexSignature, + AST_NODE_TYPES.TSMethodSignature, + AST_NODE_TYPES.TSPropertySignature, + ].includes(node.type); +} + +/** + * Checks if a node is a constructor method. + */ +function isConstructor( + node: TSESTree.Node | undefined, +): node is TSESTree.MethodDefinition { + return ( + node?.type === AST_NODE_TYPES.MethodDefinition && + node.kind === 'constructor' + ); +} + +/** + * Checks if a node is a setter method. + */ +function isSetter( + node: TSESTree.Node | undefined, +): node is TSESTree.MethodDefinition | TSESTree.Property { + return ( + !!node && + (node.type === AST_NODE_TYPES.MethodDefinition || + node.type === AST_NODE_TYPES.Property) && + node.kind === 'set' + ); +} + +function isIdentifier( + node: TSESTree.Node | undefined, +): node is TSESTree.Identifier { + return node?.type === AST_NODE_TYPES.Identifier; +} + +/** + * Checks if a node represents an `await …` expression. + */ +function isAwaitExpression( + node: TSESTree.Node | undefined | null, +): node is TSESTree.AwaitExpression { + return node?.type === AST_NODE_TYPES.AwaitExpression; +} + +/** + * Checks if a possible token is the `await` keyword. + */ +function isAwaitKeyword( + node: TSESTree.Token | TSESTree.Comment | undefined | null, +): node is TSESTree.KeywordToken & { value: 'await' } { + return node?.type === AST_TOKEN_TYPES.Identifier && node.value === 'await'; +} + +function isMemberOrOptionalMemberExpression( + node: TSESTree.Node, +): node is TSESTree.MemberExpression | TSESTree.OptionalMemberExpression { + return ( + node.type === AST_NODE_TYPES.MemberExpression || + node.type === AST_NODE_TYPES.OptionalMemberExpression + ); +} + +export { + isAwaitExpression, + isAwaitKeyword, + isConstructor, + isClassOrTypeElement, + isFunction, + isFunctionOrFunctionType, + isFunctionType, + isIdentifier, + isLogicalOrOperator, + isMemberOrOptionalMemberExpression, + isNonNullAssertionPunctuator, + isNotNonNullAssertionPunctuator, + isNotOptionalChainPunctuator, + isOptionalChainPunctuator, + isOptionalOptionalCallExpression, + isSetter, + isTSConstructorType, + isTSFunctionType, + isTypeAssertion, + isVariableDeclarator, +}; diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts index 3edb71e5a55b..f9278dc81549 100644 --- a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -13,7 +13,7 @@ type CreateRuleMeta = { docs: CreateRuleMetaDocs; } & Omit, 'docs'>; -export function RuleCreator(urlCreator: (ruleName: string) => string) { +function RuleCreator(urlCreator: (ruleName: string) => string) { // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 // TODO - when the above PR lands; add type checking for the context.report `data` property return function createRule< @@ -52,3 +52,5 @@ export function RuleCreator(urlCreator: (ruleName: string) => string) { }; }; } + +export { RuleCreator }; diff --git a/packages/experimental-utils/src/eslint-utils/applyDefault.ts b/packages/experimental-utils/src/eslint-utils/applyDefault.ts index f9f9c8f3a418..142c8d1a7469 100644 --- a/packages/experimental-utils/src/eslint-utils/applyDefault.ts +++ b/packages/experimental-utils/src/eslint-utils/applyDefault.ts @@ -7,7 +7,7 @@ import { deepMerge, isObjectNotArray } from './deepMerge'; * @param userOptions the user opts * @returns the options with defaults */ -export function applyDefault( +function applyDefault( defaultOptions: TDefault, userOptions: TUser | null, ): TDefault { @@ -32,3 +32,5 @@ export function applyDefault( return options; } + +export { applyDefault }; diff --git a/packages/experimental-utils/src/eslint-utils/deepMerge.ts b/packages/experimental-utils/src/eslint-utils/deepMerge.ts index 3603b662a9cb..5ac6509203f9 100644 --- a/packages/experimental-utils/src/eslint-utils/deepMerge.ts +++ b/packages/experimental-utils/src/eslint-utils/deepMerge.ts @@ -5,7 +5,7 @@ type ObjectLike = Record; * @param obj an object * @returns `true` if obj is an object */ -export function isObjectNotArray( +function isObjectNotArray( obj: unknown | unknown[], ): obj is T { return typeof obj === 'object' && !Array.isArray(obj); @@ -48,3 +48,5 @@ export function deepMerge( return acc; }, {} as ObjectLike); } + +export { isObjectNotArray }; diff --git a/packages/experimental-utils/src/eslint-utils/getParserServices.ts b/packages/experimental-utils/src/eslint-utils/getParserServices.ts index 65607e77290f..57b6bc59f656 100644 --- a/packages/experimental-utils/src/eslint-utils/getParserServices.ts +++ b/packages/experimental-utils/src/eslint-utils/getParserServices.ts @@ -1,4 +1,5 @@ -import { ParserServices, TSESLint } from '../'; +import * as TSESLint from '../ts-eslint'; +import { ParserServices } from '../ts-estree'; const ERROR_MESSAGE = 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.'; @@ -10,7 +11,7 @@ type RequiredParserServices = { /** * Try to retrieve typescript parser service from context */ -export function getParserServices< +function getParserServices< TMessageIds extends string, TOptions extends unknown[] >( @@ -29,3 +30,5 @@ export function getParserServices< } return context.parserServices as RequiredParserServices; } + +export { getParserServices }; diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts index 5a1fdb90bad8..31328386269a 100644 --- a/packages/experimental-utils/src/index.ts +++ b/packages/experimental-utils/src/index.ts @@ -1,19 +1,8 @@ +import * as ASTUtils from './ast-utils'; import * as ESLintUtils from './eslint-utils'; +import * as JSONSchema from './json-schema'; import * as TSESLint from './ts-eslint'; import * as TSESLintScope from './ts-eslint-scope'; -import * as JSONSchema from './json-schema'; - -export { ESLintUtils, JSONSchema, TSESLint, TSESLintScope }; - -// for convenience's sake - export the types directly from here so consumers -// don't need to reference/install both packages in their code -// NOTE - this uses hard links inside ts-estree to avoid initialization of entire package -// via its main file (which imports typescript at runtime). -// Not every eslint-plugin written in typescript requires typescript at runtime. -export { - AST_NODE_TYPES, - AST_TOKEN_TYPES, - TSESTree, -} from '@typescript-eslint/typescript-estree/dist/ts-estree'; -export { ParserServices } from '@typescript-eslint/typescript-estree/dist/parser-options'; +export { ASTUtils, ESLintUtils, JSONSchema, TSESLint, TSESLintScope }; +export * from './ts-estree'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Definition.ts b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts index b2f4b91383e5..15c69bbf86c3 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Definition.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts @@ -1,8 +1,8 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import { Definition as ESLintDefinition, ParameterDefinition as ESLintParameterDefinition, } from 'eslint-scope/lib/definition'; +import { TSESTree } from '../ts-estree'; interface Definition { type: string; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Options.ts b/packages/experimental-utils/src/ts-eslint-scope/Options.ts index f06fe4e42e8d..a450e225b7fd 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Options.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Options.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '../ts-estree'; type PatternVisitorCallback = ( pattern: TSESTree.Identifier, diff --git a/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts index c53ceb69f7a1..8563f6d934bb 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintPatternVisitor from 'eslint-scope/lib/pattern-visitor'; +import { TSESTree } from '../ts-estree'; import { ScopeManager } from './ScopeManager'; import { PatternVisitorCallback, diff --git a/packages/experimental-utils/src/ts-eslint-scope/Reference.ts b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts index 96d2fab4df5c..8d5c295f9107 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Reference.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintReference from 'eslint-scope/lib/reference'; +import { TSESTree } from '../ts-estree'; import { Scope } from './Scope'; import { Variable } from './Variable'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts index 6169d9def9a5..1d1180f21f83 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintReferencer from 'eslint-scope/lib/referencer'; +import { TSESTree } from '../ts-estree'; import { PatternVisitorCallback, PatternVisitorOptions, diff --git a/packages/experimental-utils/src/ts-eslint-scope/Scope.ts b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts index 6c5481aa4d77..49f1e11c7955 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Scope.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import { Scope as ESLintScope, GlobalScope as ESLintGlobalScope, @@ -14,6 +13,7 @@ import { ForScope as ESLintForScope, ClassScope as ESLintClassScope, } from 'eslint-scope/lib/scope'; +import { TSESTree } from '../ts-estree'; import { Definition } from './Definition'; import { Reference, ReferenceFlag } from './Reference'; import { ScopeManager } from './ScopeManager'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts index 6d05fea395f9..d1b469a6d4f1 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintScopeManager from 'eslint-scope/lib/scope-manager'; +import { TSESTree } from '../ts-estree'; import { Scope } from './Scope'; import { Variable } from './Variable'; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Variable.ts b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts index 49bbf3d9b4ab..c9ff75f95d54 100644 --- a/packages/experimental-utils/src/ts-eslint-scope/Variable.ts +++ b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts @@ -1,5 +1,5 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; import ESLintVariable from 'eslint-scope/lib/variable'; +import { TSESTree } from '../ts-estree'; import { Reference } from './Reference'; import { Definition } from './Definition'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/AST.ts b/packages/experimental-utils/src/ts-eslint/AST.ts index 1c77caafedf6..5a0ea09d8432 100644 --- a/packages/experimental-utils/src/ts-eslint/AST.ts +++ b/packages/experimental-utils/src/ts-eslint/AST.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { - TSESTree, - AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; +import { TSESTree, AST_TOKEN_TYPES } from '../ts-estree'; namespace AST { export type TokenType = AST_TOKEN_TYPES; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index 93c5565ff4d6..0233f2f8408d 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; import { Linter as ESLintLinter } from 'eslint'; +import { TSESTree, ParserServices } from '../ts-estree'; import { ParserOptions as TSParserOptions } from './ParserOptions'; import { RuleModule, RuleFix } from './Rule'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts index 12c121989dc5..d6bf57bb585a 100644 --- a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts +++ b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts @@ -1,6 +1,6 @@ import { TSESTreeOptions } from '@typescript-eslint/typescript-estree'; -export interface ParserOptions { +interface ParserOptions { comment?: boolean; ecmaFeatures?: { globalReturn?: boolean; @@ -23,3 +23,5 @@ export interface ParserOptions { useJSXTextNode?: boolean; warnOnUnsupportedTypeScriptVersion?: boolean; } + +export { ParserOptions }; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 6e7733da663a..1f74150292eb 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -1,5 +1,5 @@ -import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; import { JSONSchema4 } from '../json-schema'; +import { ParserServices, TSESTree } from '../ts-estree'; import { AST } from './AST'; import { Linter } from './Linter'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts index ce441603bf1c..9c63709f37f4 100644 --- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -1,8 +1,5 @@ -import { - AST_NODE_TYPES, - AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; import { RuleTester as ESLintRuleTester } from 'eslint'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '../ts-estree'; import { ParserOptions } from './ParserOptions'; import { RuleModule } from './Rule'; diff --git a/packages/experimental-utils/src/ts-eslint/Scope.ts b/packages/experimental-utils/src/ts-eslint/Scope.ts index 03c6bc663952..bb5e5accc516 100644 --- a/packages/experimental-utils/src/ts-eslint/Scope.ts +++ b/packages/experimental-utils/src/ts-eslint/Scope.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '../ts-estree'; namespace Scope { export interface ScopeManager { diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts index eeaa906f37dc..0ea39973a25d 100644 --- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; import { SourceCode as ESLintSourceCode } from 'eslint'; +import { ParserServices, TSESTree } from '../ts-estree'; import { Scope } from './Scope'; -declare interface SourceCode { +interface SourceCode { text: string; ast: SourceCode.Program; lines: string[]; diff --git a/packages/experimental-utils/src/ts-estree.ts b/packages/experimental-utils/src/ts-estree.ts new file mode 100644 index 000000000000..a7f18377e37b --- /dev/null +++ b/packages/experimental-utils/src/ts-estree.ts @@ -0,0 +1,12 @@ +// for convenience's sake - export the types directly from here so consumers +// don't need to reference/install both packages in their code + +// NOTE - this uses hard links inside ts-estree to avoid initialization of entire package +// via its main file (which imports typescript at runtime). +// Not every eslint-plugin written in typescript requires typescript at runtime. +export { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESTree, +} from '@typescript-eslint/typescript-estree/dist/ts-estree'; +export { ParserServices } from '@typescript-eslint/typescript-estree/dist/parser-options'; diff --git a/packages/experimental-utils/typings/eslint-utils.d.ts b/packages/experimental-utils/typings/eslint-utils.d.ts new file mode 100644 index 000000000000..f12326c0b4ac --- /dev/null +++ b/packages/experimental-utils/typings/eslint-utils.d.ts @@ -0,0 +1,40 @@ +declare module 'eslint-utils' { + export const findVariable: unknown; + export const getFunctionHeadLocation: unknown; + export const getFunctionNameWithKind: unknown; + export const getInnermostScope: unknown; + export const getPropertyName: unknown; + export const getStaticValue: unknown; + export const getStringIfConstant: unknown; + export const hasSideEffect: unknown; + export const isArrowToken: unknown; + export const isClosingBraceToken: unknown; + export const isClosingBracketToken: unknown; + export const isClosingParenToken: unknown; + export const isColonToken: unknown; + export const isCommaToken: unknown; + export const isCommentToken: unknown; + export const isNotArrowToken: unknown; + export const isNotClosingBraceToken: unknown; + export const isNotClosingBracketToken: unknown; + export const isNotClosingParenToken: unknown; + export const isNotColonToken: unknown; + export const isNotCommaToken: unknown; + export const isNotCommentToken: unknown; + export const isNotOpeningBraceToken: unknown; + export const isNotOpeningBracketToken: unknown; + export const isNotOpeningParenToken: unknown; + export const isNotSemicolonToken: unknown; + export const isOpeningBraceToken: unknown; + export const isOpeningBracketToken: unknown; + export const isOpeningParenToken: unknown; + export const isParenthesized: unknown; + export const isSemicolonToken: unknown; + export const PatternMatcher: unknown; + export const ReferenceTracker: { + READ: never; + CALL: never; + CONSTRUCT: never; + new (): never; + }; +} diff --git a/yarn.lock b/yarn.lock index 5a972a6cc18f..4e2a78cbd7e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3373,6 +3373,13 @@ eslint-utils@^1.4.3: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" From b2dbd890a5bef81aa6978d68c166457838ee04a1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Mon, 23 Mar 2020 18:10:17 +1300 Subject: [PATCH 18/19] feat(eslint-plugin): add `class-literal-property-style` rule (#1582) --- packages/eslint-plugin/README.md | 1 + .../rules/class-literal-property-style.md | 96 +++++ packages/eslint-plugin/src/configs/all.json | 1 + .../src/rules/class-literal-property-style.ts | 136 +++++++ packages/eslint-plugin/src/rules/index.ts | 2 + .../class-literal-property-style.test.ts | 378 ++++++++++++++++++ 6 files changed, 614 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/class-literal-property-style.md create mode 100644 packages/eslint-plugin/src/rules/class-literal-property-style.ts create mode 100644 packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index b79277579247..fa049b2cfb27 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -99,6 +99,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/ban-ts-comment`](./docs/rules/ban-ts-comment.md) | Bans `// @ts-` comments from being used | | | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/class-literal-property-style`](./docs/rules/class-literal-property-style.md) | Ensures that literals on classes are exposed in a consistent style | | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | :heavy_check_mark: | | | | [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | diff --git a/packages/eslint-plugin/docs/rules/class-literal-property-style.md b/packages/eslint-plugin/docs/rules/class-literal-property-style.md new file mode 100644 index 000000000000..903a695dd76b --- /dev/null +++ b/packages/eslint-plugin/docs/rules/class-literal-property-style.md @@ -0,0 +1,96 @@ +# Ensures that literals on classes are exposed in a consistent style (`class-literal-property-style`) + +When writing TypeScript applications, it's typically safe to store literal values on classes using fields with the `readonly` modifier to prevent them from being reassigned. +When writing TypeScript libraries that could be used by Javascript users however, it's typically safer to expose these literals using `getter`s, since the `readonly` modifier is enforced at compile type. + +## Rule Details + +This rule aims to ensure that literals exposed by classes are done so consistently, in one of the two style described above. +By default this rule prefers the `fields` style as it means JS doesn't have to setup & teardown a function closure. + +Note that this rule only checks for constant _literal_ values (string, template string, number, bigint, boolean, regexp, null). It does not check objects or arrays, because a readonly field behaves differently to a getter in those cases. It also does not check functions, as it is a common pattern to use readonly fields with arrow function values as auto-bound methods. +This is because these types can be mutated and carry with them more complex implications about their usage. + +#### The `fields` style + +This style checks for any getter methods that return literal values, and requires them to be defined using fields with the `readonly` modifier instead. + +Examples of **correct** code with the `fields` style: + +```ts +/* eslint @typescript-eslint/class-literal-property-style: ["error", "fields"] */ + +class Mx { + public readonly myField1 = 1; + + // not a literal + public readonly myField2 = [1, 2, 3]; + + private readonly ['myField3'] = 'hello world'; + + public get myField4() { + return `hello from ${window.location.href}`; + } +} +``` + +Examples of **incorrect** code with the `fields` style: + +```ts +/* eslint @typescript-eslint/class-literal-property-style: ["error", "fields"] */ + +class Mx { + public static get myField1() { + return 1; + } + + private get ['myField2']() { + return 'hello world'; + } +} +``` + +#### The `getters` style + +This style checks for any `readonly` fields that are assigned literal values, and requires them to be defined as getters instead. +This style pairs well with the [`@typescript-eslint/prefer-readonly`](prefer-readonly.md) rule, +as it will identify fields that can be `readonly`, and thus should be made into getters. + +Examples of **correct** code with the `getters` style: + +```ts +/* eslint @typescript-eslint/class-literal-property-style: ["error", "getters"] */ + +class Mx { + // no readonly modifier + public myField1 = 'hello'; + + // not a literal + public readonly myField2 = [1, 2, 3]; + + public static get myField3() { + return 1; + } + + private get ['myField4']() { + return 'hello world'; + } +} +``` + +Examples of **incorrect** code with the `getters` style: + +```ts +/* eslint @typescript-eslint/class-literal-property-style: ["error", "getters"] */ + +class Mx { + readonly myField1 = 1; + readonly myField2 = `hello world`; + private readonly myField3 = 'hello world'; +} +``` + +## When Not To Use It + +When you have no strong preference, or do not wish to enforce a particular style +for how literal values are exposed by your classes. diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 2626767b84cc..c5575d4970d6 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -8,6 +8,7 @@ "@typescript-eslint/ban-types": "error", "brace-style": "off", "@typescript-eslint/brace-style": "error", + "@typescript-eslint/class-literal-property-style": "error", "comma-spacing": "off", "@typescript-eslint/comma-spacing": "error", "@typescript-eslint/consistent-type-assertions": "error", diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts new file mode 100644 index 000000000000..a4500cdfdbf8 --- /dev/null +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -0,0 +1,136 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +type Options = ['fields' | 'getters']; +type MessageIds = 'preferFieldStyle' | 'preferGetterStyle'; + +interface NodeWithModifiers { + accessibility?: TSESTree.Accessibility; + static: boolean; +} + +const printNodeModifiers = ( + node: NodeWithModifiers, + final: 'readonly' | 'get', +): string => + `${node.accessibility ?? ''}${ + node.static ? ' static' : '' + } ${final} `.trimLeft(); + +const isSupportedLiteral = ( + node: TSESTree.Node, +): node is TSESTree.LiteralExpression => { + if ( + node.type === AST_NODE_TYPES.Literal || + node.type === AST_NODE_TYPES.BigIntLiteral + ) { + return true; + } + + if ( + node.type === AST_NODE_TYPES.TaggedTemplateExpression || + node.type === AST_NODE_TYPES.TemplateLiteral + ) { + return ('quasi' in node ? node.quasi.quasis : node.quasis).length === 1; + } + + return false; +}; + +export default util.createRule({ + name: 'class-literal-property-style', + meta: { + type: 'problem', + docs: { + description: + 'Ensures that literals on classes are exposed in a consistent style', + category: 'Best Practices', + recommended: false, + }, + fixable: 'code', + messages: { + preferFieldStyle: 'Literals should be exposed using readonly fields.', + preferGetterStyle: 'Literals should be exposed using getters.', + }, + schema: [{ enum: ['fields', 'getters'] }], + }, + defaultOptions: ['fields'], + create(context, [style]) { + if (style === 'fields') { + return { + MethodDefinition(node: TSESTree.MethodDefinition): void { + if ( + node.kind !== 'get' || + !node.value.body || + !node.value.body.body.length + ) { + return; + } + + const [statement] = node.value.body.body; + + if (statement.type !== AST_NODE_TYPES.ReturnStatement) { + return; + } + + const { argument } = statement; + + if (!argument || !isSupportedLiteral(argument)) { + return; + } + + context.report({ + node: node.key, + messageId: 'preferFieldStyle', + fix(fixer) { + const sourceCode = context.getSourceCode(); + const name = sourceCode.getText(node.key); + + let text = ''; + + text += printNodeModifiers(node, 'readonly'); + text += node.computed ? `[${name}]` : name; + text += ` = ${sourceCode.getText(argument)};`; + + return fixer.replaceText(node, text); + }, + }); + }, + }; + } + + return { + ClassProperty(node: TSESTree.ClassProperty): void { + if (!node.readonly || node.declare) { + return; + } + + const { value } = node; + + if (!value || !isSupportedLiteral(value)) { + return; + } + + context.report({ + node: node.key, + messageId: 'preferGetterStyle', + fix(fixer) { + const sourceCode = context.getSourceCode(); + const name = sourceCode.getText(node.key); + + let text = ''; + + text += printNodeModifiers(node, 'get'); + text += node.computed ? `[${name}]` : name; + text += `() { return ${sourceCode.getText(value)}; }`; + + return fixer.replaceText(node, text); + }, + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 486b8a97945b..29db58b681ab 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -7,6 +7,7 @@ import banTypes from './ban-types'; import braceStyle from './brace-style'; import camelcase from './camelcase'; import classNameCasing from './class-name-casing'; +import classLiteralPropertyStyle from './class-literal-property-style'; import commaSpacing from './comma-spacing'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; @@ -102,6 +103,7 @@ export default { 'brace-style': braceStyle, camelcase: camelcase, 'class-name-casing': classNameCasing, + 'class-literal-property-style': classLiteralPropertyStyle, 'comma-spacing': commaSpacing, 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, diff --git a/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts b/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts new file mode 100644 index 000000000000..f0c5f2afc055 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts @@ -0,0 +1,378 @@ +import rule from '../../src/rules/class-literal-property-style'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('class-literal-property-style', rule, { + valid: [ + 'class Mx { declare readonly p1 = 1; }', + 'class Mx { readonly p1 = "hello world"; }', + 'class Mx { p1 = "hello world"; }', + 'class Mx { static p1 = "hello world"; }', + 'class Mx { p1: string; }', + 'class Mx { get p1(); }', + 'class Mx { get p1() {} }', + 'abstract class Mx { abstract get p1(): string }', + ` + class Mx { + get mySetting() { + if(this._aValue) { + return 'on'; + } + + return 'off'; + } + } + `, + ` + class Mx { + get mySetting() { + return \`build-\${process.env.build}\` + } + } + `, + ` + class Mx { + getMySetting() { + if(this._aValue) { + return 'on'; + } + + return 'off'; + } + } + `, + ` + class Mx { + public readonly myButton = styled.button\` + color: \${props => (props.primary ? 'hotpink' : 'turquoise')}; + \`; + } + `, + { + code: ` + class Mx { + public get myButton() { + return styled.button\` + color: \${props => (props.primary ? 'hotpink' : 'turquoise')}; + \`; + } + } + `, + options: ['fields'], + }, + { + code: 'class Mx { declare public readonly foo = 1; }', + options: ['getters'], + }, + { + code: 'class Mx { get p1() { return "hello world"; } }', + options: ['getters'], + }, + { + code: 'class Mx { p1 = "hello world"; }', + options: ['getters'], + }, + { + code: 'class Mx { p1: string; }', + options: ['getters'], + }, + { + code: 'class Mx { readonly p1 = [1, 2, 3]; }', + options: ['getters'], + }, + { + code: 'class Mx { static p1: string; }', + options: ['getters'], + }, + { + code: 'class Mx { static get p1() { return "hello world"; } }', + options: ['getters'], + }, + { + code: ` + class Mx { + public readonly myButton = styled.button\` + color: \${props => (props.primary ? 'hotpink' : 'turquoise')}; + \`; + } + `, + options: ['getters'], + }, + { + code: ` + class Mx { + public get myButton() { + return styled.button\` + color: \${props => (props.primary ? 'hotpink' : 'turquoise')}; + \`; + } + } + `, + options: ['getters'], + }, + ], + invalid: [ + { + code: 'class Mx { get p1() { return "hello world"; } }', + output: 'class Mx { readonly p1 = "hello world"; }', + errors: [ + { + messageId: 'preferFieldStyle', + column: 16, + line: 1, + }, + ], + }, + { + code: 'class Mx { get p1() { return `hello world`; } }', + output: 'class Mx { readonly p1 = `hello world`; }', + errors: [ + { + messageId: 'preferFieldStyle', + column: 16, + line: 1, + }, + ], + }, + { + code: 'class Mx { static get p1() { return "hello world"; } }', + output: 'class Mx { static readonly p1 = "hello world"; }', + errors: [ + { + messageId: 'preferFieldStyle', + column: 23, + line: 1, + }, + ], + }, + { + code: + 'class Mx { public static readonly static private public protected get foo() { return 1; } }', + output: 'class Mx { public static readonly foo = 1; }', + errors: [ + { + messageId: 'preferFieldStyle', + column: 71, + line: 1, + }, + ], + }, + { + code: ` + class Mx { + public get [myValue]() { + return 'a literal value'; + } + } + `, + output: ` + class Mx { + public readonly [myValue] = 'a literal value'; + } + `, + errors: [ + { + messageId: 'preferFieldStyle', + column: 23, + line: 3, + }, + ], + }, + { + code: ` + class Mx { + public get [myValue]() { + return 12345n; + } + } + `, + output: ` + class Mx { + public readonly [myValue] = 12345n; + } + `, + errors: [ + { + messageId: 'preferFieldStyle', + column: 23, + line: 3, + }, + ], + }, + { + code: ` + class Mx { + public readonly [myValue] = 'a literal value'; + } + `, + output: ` + class Mx { + public get [myValue]() { return 'a literal value'; } + } + `, + errors: [ + { + messageId: 'preferGetterStyle', + column: 28, + line: 3, + }, + ], + options: ['getters'], + }, + { + code: 'class Mx { readonly p1 = "hello world"; }', + output: 'class Mx { get p1() { return "hello world"; } }', + errors: [ + { + messageId: 'preferGetterStyle', + column: 21, + line: 1, + }, + ], + options: ['getters'], + }, + { + code: 'class Mx { readonly p1 = `hello world`; }', + output: 'class Mx { get p1() { return `hello world`; } }', + errors: [ + { + messageId: 'preferGetterStyle', + column: 21, + line: 1, + }, + ], + options: ['getters'], + }, + { + code: 'class Mx { static readonly p1 = "hello world"; }', + output: 'class Mx { static get p1() { return "hello world"; } }', + errors: [ + { + messageId: 'preferGetterStyle', + column: 28, + line: 1, + }, + ], + options: ['getters'], + }, + { + code: 'class Mx { protected get p1() { return "hello world"; } }', + output: 'class Mx { protected readonly p1 = "hello world"; }', + errors: [ + { + messageId: 'preferFieldStyle', + column: 26, + line: 1, + }, + ], + options: ['fields'], + }, + { + code: 'class Mx { protected readonly p1 = "hello world"; }', + output: 'class Mx { protected get p1() { return "hello world"; } }', + errors: [ + { + messageId: 'preferGetterStyle', + column: 31, + line: 1, + }, + ], + options: ['getters'], + }, + { + code: 'class Mx { public static get p1() { return "hello world"; } }', + output: 'class Mx { public static readonly p1 = "hello world"; }', + errors: [ + { + messageId: 'preferFieldStyle', + column: 30, + line: 1, + }, + ], + }, + { + code: 'class Mx { public static readonly p1 = "hello world"; }', + output: 'class Mx { public static get p1() { return "hello world"; } }', + errors: [ + { + messageId: 'preferGetterStyle', + column: 35, + line: 1, + }, + ], + options: ['getters'], + }, + { + code: ` + class Mx { + public get myValue() { + return gql\` + { + user(id: 5) { + firstName + lastName + } + } + \`; + } + } + `, + output: ` + class Mx { + public readonly myValue = gql\` + { + user(id: 5) { + firstName + lastName + } + } + \`; + } + `, + errors: [ + { + messageId: 'preferFieldStyle', + column: 22, + line: 3, + }, + ], + }, + { + code: ` + class Mx { + public readonly myValue = gql\` + { + user(id: 5) { + firstName + lastName + } + } + \`; + } + `, + output: ` + class Mx { + public get myValue() { return gql\` + { + user(id: 5) { + firstName + lastName + } + } + \`; } + } + `, + errors: [ + { + messageId: 'preferGetterStyle', + column: 27, + line: 3, + }, + ], + options: ['getters'], + }, + ], +}); From 9cd3e4fe53c0224c75767a3f127f19b86060e277 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 23 Mar 2020 17:02:04 +0000 Subject: [PATCH 19/19] chore: publish v2.25.0 --- CHANGELOG.md | 23 ++++++++++++++++++++ lerna.json | 2 +- packages/eslint-plugin-internal/CHANGELOG.md | 8 +++++++ packages/eslint-plugin-internal/package.json | 4 ++-- packages/eslint-plugin-tslint/CHANGELOG.md | 11 ++++++++++ packages/eslint-plugin-tslint/package.json | 6 ++--- packages/eslint-plugin/CHANGELOG.md | 20 +++++++++++++++++ packages/eslint-plugin/package.json | 4 ++-- packages/experimental-utils/CHANGELOG.md | 11 ++++++++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 8 +++++++ packages/parser/package.json | 8 +++---- packages/shared-fixtures/CHANGELOG.md | 8 +++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 11 ++++++++++ packages/typescript-estree/package.json | 4 ++-- 16 files changed, 117 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1da108f546..df5d48544946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + + +### Bug Fixes + +* only run publish_canary_version on master ([3814d4e](https://github.com/typescript-eslint/typescript-eslint/commit/3814d4e3b3c1552c7601b5d722b2a37c5a570841)) +* **eslint-plugin:** [quotes] false positive with backtick in import equals statement ([#1769](https://github.com/typescript-eslint/typescript-eslint/issues/1769)) ([199863d](https://github.com/typescript-eslint/typescript-eslint/commit/199863d35cb36bdb7178b8116d146258506644c7)) +* **eslint-plugin:** fix message of no-base-to-string ([#1755](https://github.com/typescript-eslint/typescript-eslint/issues/1755)) ([6646959](https://github.com/typescript-eslint/typescript-eslint/commit/6646959b255b08afe5175bba6621bad11b9e1d5e)) +* **eslint-plugin-tslint:** fix tslintConfig memoization key ([#1719](https://github.com/typescript-eslint/typescript-eslint/issues/1719)) ([abf1a2f](https://github.com/typescript-eslint/typescript-eslint/commit/abf1a2fa5574e41af8070be3d79a886ea2f989cc)), closes [typescript-eslint#1692](https://github.com/typescript-eslint/issues/1692) +* **typescript-estree:** export * regression from 133f622f ([#1751](https://github.com/typescript-eslint/typescript-eslint/issues/1751)) ([09d8afc](https://github.com/typescript-eslint/typescript-eslint/commit/09d8afca684635b5ac604bc1794240484a70ce91)) + + +### Features + +* **eslint-plugin:** [no-unnec-type-assertion] allow const assertions ([#1741](https://github.com/typescript-eslint/typescript-eslint/issues/1741)) ([f76a1b3](https://github.com/typescript-eslint/typescript-eslint/commit/f76a1b3e63afda9f239e46f4ad5b36c1d7a6e8da)) +* **eslint-plugin:** [no-unnecessary-condition] ignore basic array indexing false positives ([#1534](https://github.com/typescript-eslint/typescript-eslint/issues/1534)) ([2b9603d](https://github.com/typescript-eslint/typescript-eslint/commit/2b9603d868c57556d8cd6087685e798d74cb6f26)) +* **eslint-plugin:** add `class-literal-property-style` rule ([#1582](https://github.com/typescript-eslint/typescript-eslint/issues/1582)) ([b2dbd89](https://github.com/typescript-eslint/typescript-eslint/commit/b2dbd890a5bef81aa6978d68c166457838ee04a1)) +* **experimental-utils:** expose ast utility functions ([#1670](https://github.com/typescript-eslint/typescript-eslint/issues/1670)) ([3eb5d45](https://github.com/typescript-eslint/typescript-eslint/commit/3eb5d4525e95c8ab990f55588b8d830a02ce5a9c)) + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) diff --git a/lerna.json b/lerna.json index b6ba0964df8e..f7a5aa4c7317 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.24.0", + "version": "2.25.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index e8e1b2cb8413..42a8af8be871 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index a318b9d4571e..afbc489b9ba1 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "2.24.0", + "version": "2.25.0", "private": true, "main": "dist/index.js", "scripts": { @@ -12,6 +12,6 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.24.0" + "@typescript-eslint/experimental-utils": "2.25.0" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index a2fd2d004120..38770408378e 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + + +### Bug Fixes + +* **eslint-plugin-tslint:** fix tslintConfig memoization key ([#1719](https://github.com/typescript-eslint/typescript-eslint/issues/1719)) ([abf1a2f](https://github.com/typescript-eslint/typescript-eslint/commit/abf1a2fa5574e41af8070be3d79a886ea2f989cc)), closes [typescript-eslint#1692](https://github.com/typescript-eslint/issues/1692) + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 10ea47877265..a10074080b20 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "2.24.0", + "version": "2.25.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,7 +31,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.24.0", + "@typescript-eslint/experimental-utils": "2.25.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -41,6 +41,6 @@ }, "devDependencies": { "@types/lodash": "^4.14.149", - "@typescript-eslint/parser": "2.24.0" + "@typescript-eslint/parser": "2.25.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 56a3265aba70..074bd66c7ca4 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + + +### Bug Fixes + +* **eslint-plugin:** [quotes] false positive with backtick in import equals statement ([#1769](https://github.com/typescript-eslint/typescript-eslint/issues/1769)) ([199863d](https://github.com/typescript-eslint/typescript-eslint/commit/199863d35cb36bdb7178b8116d146258506644c7)) +* **eslint-plugin:** fix message of no-base-to-string ([#1755](https://github.com/typescript-eslint/typescript-eslint/issues/1755)) ([6646959](https://github.com/typescript-eslint/typescript-eslint/commit/6646959b255b08afe5175bba6621bad11b9e1d5e)) + + +### Features + +* **eslint-plugin:** [no-unnec-type-assertion] allow const assertions ([#1741](https://github.com/typescript-eslint/typescript-eslint/issues/1741)) ([f76a1b3](https://github.com/typescript-eslint/typescript-eslint/commit/f76a1b3e63afda9f239e46f4ad5b36c1d7a6e8da)) +* **eslint-plugin:** [no-unnecessary-condition] ignore basic array indexing false positives ([#1534](https://github.com/typescript-eslint/typescript-eslint/issues/1534)) ([2b9603d](https://github.com/typescript-eslint/typescript-eslint/commit/2b9603d868c57556d8cd6087685e798d74cb6f26)) +* **eslint-plugin:** add `class-literal-property-style` rule ([#1582](https://github.com/typescript-eslint/typescript-eslint/issues/1582)) ([b2dbd89](https://github.com/typescript-eslint/typescript-eslint/commit/b2dbd890a5bef81aa6978d68c166457838ee04a1)) +* **experimental-utils:** expose ast utility functions ([#1670](https://github.com/typescript-eslint/typescript-eslint/issues/1670)) ([3eb5d45](https://github.com/typescript-eslint/typescript-eslint/commit/3eb5d4525e95c8ab990f55588b8d830a02ce5a9c)) + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) **Note:** Version bump only for package @typescript-eslint/eslint-plugin diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 4855beab6a23..cb725f5be6d6 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "2.24.0", + "version": "2.25.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -41,7 +41,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.24.0", + "@typescript-eslint/experimental-utils": "2.25.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 33f3bce56c0c..6a731bc439bd 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + + +### Features + +* **experimental-utils:** expose ast utility functions ([#1670](https://github.com/typescript-eslint/typescript-eslint/issues/1670)) ([3eb5d45](https://github.com/typescript-eslint/typescript-eslint/commit/3eb5d4525e95c8ab990f55588b8d830a02ce5a9c)) + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) **Note:** Version bump only for package @typescript-eslint/experimental-utils diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 287b52001c65..52b42129a61e 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "2.24.0", + "version": "2.25.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -37,7 +37,7 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.24.0", + "@typescript-eslint/typescript-estree": "2.25.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 6f5b20e66e88..ce9056408ac5 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) diff --git a/packages/parser/package.json b/packages/parser/package.json index da6fbd2b5e8f..33047db75574 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "2.24.0", + "version": "2.25.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -43,13 +43,13 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.24.0", - "@typescript-eslint/typescript-estree": "2.24.0", + "@typescript-eslint/experimental-utils": "2.25.0", + "@typescript-eslint/typescript-estree": "2.25.0", "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "2.24.0", + "@typescript-eslint/shared-fixtures": "2.25.0", "glob": "*" }, "peerDependenciesMeta": { diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 70196052327f..535e49bbb58e 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 360b466615be..4775ceb0f14f 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "2.24.0", + "version": "2.25.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index b52cddede335..91b7426e23d4 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) + + +### Bug Fixes + +* **typescript-estree:** export * regression from 133f622f ([#1751](https://github.com/typescript-eslint/typescript-eslint/issues/1751)) ([09d8afc](https://github.com/typescript-eslint/typescript-eslint/commit/09d8afca684635b5ac604bc1794240484a70ce91)) + + + + + # [2.24.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.23.0...v2.24.0) (2020-03-16) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 9375c1567871..20ce7e15b5a1 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "2.24.0", + "version": "2.25.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -58,7 +58,7 @@ "@types/lodash": "^4.14.149", "@types/semver": "^6.2.0", "@types/tmp": "^0.1.0", - "@typescript-eslint/shared-fixtures": "2.24.0", + "@typescript-eslint/shared-fixtures": "2.25.0", "tmp": "^0.1.0", "typescript": "*" },