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/.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** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..933b4895837f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,181 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + 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: echo github.ref + run: echo ${{ github.ref }} + + - 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 + + # Note that this command *also* type checks 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: Publish code coverage report + uses: codecov/codecov-action@v1 + with: + yml: ./codecov.yml + token: ${{ secrets.CODECOV_TOKEN }} + 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 + 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 }} + + - 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 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, integration_tests] + if: github.ref == 'refs/heads/master' + steps: + - uses: actions/checkout@v2 + # Fetch all history for all tags and branches in this job because lerna needs it + - run: | + git fetch --prune --unshallow + + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} + uses: actions/setup-node@v1 + with: + 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 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 --loglevel=verbose --canary --exact --force-publish --yes + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 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/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 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' 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-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({ 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/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/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/package.json b/packages/eslint-plugin/package.json index 1083d0b5aa86..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,8 +41,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.24.0", - "eslint-utils": "^1.4.3", + "@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/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/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/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/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/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', 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/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/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/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/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/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'], + }, + ], +}); 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: ` 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: [ 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};`, 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/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/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..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,8 +37,9 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.24.0", - "eslint-scope": "^5.0.0" + "@typescript-eslint/typescript-estree": "2.25.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/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": "*" }, 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, }); 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; } 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" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy