From f6655778c8d7e6615dab81cee9095d3bf8faae50 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Mon, 18 Oct 2021 17:58:21 +1300 Subject: [PATCH] feat: create new package type-utils move isTypeReadonly into it --- .github/ISSUE_TEMPLATE.md | 1 + .../typescript-eslint-type-utils.md | 72 +++++++++++++++++++ .github/workflows/ci.yml | 10 +++ .vscode/launch.json | 46 ++++++++++++ packages/ast-spec/README.md | 2 +- packages/eslint-plugin/package.json | 1 + .../rules/prefer-readonly-parameter-types.ts | 9 +-- .../src/util/collectUnusedVariables.ts | 4 +- .../src/util/explicitReturnTypeUtils.ts | 12 +++- .../src/util/getESLintCoreRule.ts | 4 +- packages/eslint-plugin/src/util/index.ts | 29 ++++++-- packages/eslint-plugin/tsconfig.build.json | 3 +- packages/eslint-plugin/tsconfig.json | 3 +- .../src/eslint-utils/index.ts | 1 + .../src/eslint-utils}/nullThrows.ts | 0 packages/type-utils/CHANGELOG.md | 4 ++ packages/type-utils/LICENSE | 21 ++++++ packages/type-utils/README.md | 27 +++++++ packages/type-utils/jest.config.js | 21 ++++++ packages/type-utils/package.json | 65 +++++++++++++++++ packages/type-utils/project.json | 5 ++ packages/type-utils/src/eslint-utils/index.ts | 2 + .../src/eslint-utils}/isTypeReadonly.ts | 13 ++-- .../src/eslint-utils}/propertyTypes.ts | 0 packages/type-utils/src/index.ts | 1 + .../tests/eslint-utils/index.test.ts | 7 ++ packages/type-utils/tsconfig.build.json | 11 +++ packages/type-utils/tsconfig.json | 9 +++ packages/type-utils/typings/typescript.d.ts | 26 +++++++ workspace.json | 1 + 30 files changed, 386 insertions(+), 24 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/typescript-eslint-type-utils.md rename packages/{eslint-plugin/src/util => experimental-utils/src/eslint-utils}/nullThrows.ts (100%) create mode 100644 packages/type-utils/CHANGELOG.md create mode 100644 packages/type-utils/LICENSE create mode 100644 packages/type-utils/README.md create mode 100644 packages/type-utils/jest.config.js create mode 100644 packages/type-utils/package.json create mode 100644 packages/type-utils/project.json create mode 100644 packages/type-utils/src/eslint-utils/index.ts rename packages/{eslint-plugin/src/util => type-utils/src/eslint-utils}/isTypeReadonly.ts (94%) rename packages/{eslint-plugin/src/util => type-utils/src/eslint-utils}/propertyTypes.ts (100%) create mode 100644 packages/type-utils/src/index.ts create mode 100644 packages/type-utils/tests/eslint-utils/index.test.ts create mode 100644 packages/type-utils/tsconfig.build.json create mode 100644 packages/type-utils/tsconfig.json create mode 100644 packages/type-utils/typings/typescript.d.ts diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 450026bc51f3..5cea18efebe6 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -73,6 +73,7 @@ i.e. eslint --ext ".ts,.js" src --debug | `@typescript-eslint/parser` | `X.Y.Z` | | `@typescript-eslint/typescript-estree` | `X.Y.Z` | | `@typescript-eslint/experimental-utils` | `X.Y.Z` | +| `@typescript-eslint/type-utils` | `X.Y.Z` | | `TypeScript` | `X.Y.Z` | | `node` | `X.Y.Z` | | `npm` | `X.Y.Z` | diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-type-utils.md b/.github/ISSUE_TEMPLATE/typescript-eslint-type-utils.md new file mode 100644 index 000000000000..c2f8229829ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-type-utils.md @@ -0,0 +1,72 @@ +--- +name: '@typescript-eslint/type-utils' +about: Report an issue with the '@typescript-eslint/type-utils' package +title: '' +labels: 'package: type-utils, triage' +assignees: '' +--- + + + + + +- [ ] I have tried restarting my IDE and the issue persists. +- [ ] I have updated to the latest version of the packages. +- [ ] I have [read the FAQ](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md) and my problem is not listed. + +**Repro** + + + +```TS +// your repro code case +``` + +**Expected Result** + + + +**Actual Result** + + + +**Additional Info** + + + +**Versions** + +| package | version | +| ------------------------------- | ------- | +| `@typescript-eslint/type-utils` | `X.Y.Z` | +| `@typescript-eslint/type-utils` | `X.Y.Z` | +| `TypeScript` | `X.Y.Z` | +| `node` | `X.Y.Z` | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ebf7a934156..e53e2e016460 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,11 @@ jobs: env: CI: true + - name: Run unit tests for type-utils + run: npx nx test @typescript-eslint/type-utils + env: + CI: true + - name: Run unit tests for parser run: npx nx test @typescript-eslint/parser env: @@ -282,6 +287,11 @@ jobs: env: CI: true + - name: Run unit tests for type-utils + run: npx nx test @typescript-eslint/type-utils + env: + CI: true + - name: Run unit tests for parser run: npx nx test @typescript-eslint/parser env: diff --git a/.vscode/launch.json b/.vscode/launch.json index 5ad1e2264578..0162b9ae8312 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,6 +24,8 @@ "${workspaceFolder}/packages/experimental-utils/dist/index.js", "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", "${workspaceFolder}/packages/parser/src/index.ts", "${workspaceFolder}/packages/parser/dist/index.js", "${workspaceFolder}/packages/typescript-estree/src/index.ts", @@ -56,6 +58,8 @@ "${workspaceFolder}/packages/experimental-utils/dist/index.js", "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", "${workspaceFolder}/packages/parser/src/index.ts", "${workspaceFolder}/packages/parser/dist/index.js", "${workspaceFolder}/packages/typescript-estree/src/index.ts", @@ -88,6 +92,8 @@ "${workspaceFolder}/packages/experimental-utils/dist/index.js", "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/type-utils/dist/ts-estree.js", "${workspaceFolder}/packages/parser/src/index.ts", "${workspaceFolder}/packages/parser/dist/index.js", "${workspaceFolder}/packages/typescript-estree/src/index.ts", @@ -120,6 +126,8 @@ "${workspaceFolder}/packages/experimental-utils/dist/index.js", "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", "${workspaceFolder}/packages/parser/src/index.ts", "${workspaceFolder}/packages/parser/dist/index.js", "${workspaceFolder}/packages/typescript-estree/src/index.ts", @@ -152,6 +160,42 @@ "${workspaceFolder}/packages/experimental-utils/dist/index.js", "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", + "${workspaceFolder}/packages/parser/src/index.ts", + "${workspaceFolder}/packages/parser/dist/index.js", + "${workspaceFolder}/packages/typescript-estree/src/index.ts", + "${workspaceFolder}/packages/typescript-estree/dist/index.js", + "${workspaceFolder}/packages/types/src/index.ts", + "${workspaceFolder}/packages/types/dist/index.js", + "${workspaceFolder}/packages/visitor-keys/src/index.ts", + "${workspaceFolder}/packages/visitor-keys/dist/index.js", + "${workspaceFolder}/packages/scope-manager/dist/index.js", + "${workspaceFolder}/packages/scope-manager/dist/index.js", + ], + }, + { + "type": "node", + "request": "launch", + "name": "Run currently opened type-utils test", + "cwd": "${workspaceFolder}/packages/type-utils/", + "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", + "args": [ + "--runInBand", + "--no-cache", + "--no-coverage", + "${fileBasenameNoExtension}" + ], + "sourceMaps": true, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "skipFiles": [ + "${workspaceFolder}/packages/experimental-utils/src/index.ts", + "${workspaceFolder}/packages/experimental-utils/dist/index.js", + "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", + "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", "${workspaceFolder}/packages/parser/src/index.ts", "${workspaceFolder}/packages/parser/dist/index.js", "${workspaceFolder}/packages/typescript-estree/src/index.ts", @@ -184,6 +228,8 @@ "${workspaceFolder}/packages/experimental-utils/dist/index.js", "${workspaceFolder}/packages/experimental-utils/src/ts-estree.ts", "${workspaceFolder}/packages/experimental-utils/dist/ts-estree.js", + "${workspaceFolder}/packages/type-utils/src/index.ts", + "${workspaceFolder}/packages/type-utils/dist/index.js", "${workspaceFolder}/packages/parser/src/index.ts", "${workspaceFolder}/packages/parser/dist/index.js", "${workspaceFolder}/packages/typescript-estree/src/index.ts", diff --git a/packages/ast-spec/README.md b/packages/ast-spec/README.md index 7c954398395b..6d29fecd24ec 100644 --- a/packages/ast-spec/README.md +++ b/packages/ast-spec/README.md @@ -16,7 +16,7 @@ It includes: **You probably don't want to use it directly.** -If you're building an ESLint plugin, consider using [`@typescript-eslint/experimental-utils`](../experimental-utils). +If you're building an ESLint plugin, consider using [`@typescript-eslint/experimental-utils`](../experimental-utils) and [`@typescript-eslint/type-utils`](../type-utils). If you're parsing TypeScript code, consider using [`@typescript-eslint/typescript-estree`](../typescript-estree). ## Contributing diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 986b27b213b7..f9ee349d9c22 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -46,6 +46,7 @@ "dependencies": { "@typescript-eslint/experimental-utils": "5.6.0", "@typescript-eslint/scope-manager": "5.6.0", + "@typescript-eslint/type-utils": "5.6.0", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", diff --git a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts index ed3e4ba284b1..7add0987a8ce 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -2,13 +2,14 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; +import { ESLintTypeUtils } from '@typescript-eslint/type-utils'; import * as util from '../util'; type Options = [ { checkParameterProperties?: boolean; ignoreInferredTypes?: boolean; - } & util.ReadonlynessOptions, + } & ESLintTypeUtils.ReadonlynessOptions, ]; type MessageIds = 'shouldBeReadonly'; @@ -33,7 +34,7 @@ export default util.createRule({ ignoreInferredTypes: { type: 'boolean', }, - ...util.readonlynessOptionsSchema.properties, + ...ESLintTypeUtils.readonlynessOptionsSchema.properties, }, }, ], @@ -45,7 +46,7 @@ export default util.createRule({ { checkParameterProperties: true, ignoreInferredTypes: false, - ...util.readonlynessOptionsDefaults, + ...ESLintTypeUtils.readonlynessOptionsDefaults, }, ], create( @@ -97,7 +98,7 @@ export default util.createRule({ const tsNode = esTreeNodeToTSNodeMap.get(actualParam); const type = checker.getTypeAtLocation(tsNode); - const isReadOnly = util.isTypeReadonly(checker, type, { + const isReadOnly = ESLintTypeUtils.isTypeReadonly(checker, type, { treatMethodsAsReadonly: treatMethodsAsReadonly!, }); diff --git a/packages/eslint-plugin/src/util/collectUnusedVariables.ts b/packages/eslint-plugin/src/util/collectUnusedVariables.ts index f62d75887537..fe44354fc30d 100644 --- a/packages/eslint-plugin/src/util/collectUnusedVariables.ts +++ b/packages/eslint-plugin/src/util/collectUnusedVariables.ts @@ -3,10 +3,10 @@ import { TSESLint, ASTUtils, TSESTree, + ESLintUtils, } from '@typescript-eslint/experimental-utils'; import { ImplicitLibVariable } from '@typescript-eslint/scope-manager'; import { Visitor } from '@typescript-eslint/scope-manager/dist/referencer/Visitor'; -import { nullThrows } from './nullThrows'; class UnusedVarsVisitor< TMessageIds extends string, @@ -25,7 +25,7 @@ class UnusedVarsVisitor< visitChildrenEvenIfSelectorExists: true, }); - this.#scopeManager = nullThrows( + this.#scopeManager = ESLintUtils.nullThrows( context.getSourceCode().scopeManager, 'Missing required scope manager', ); diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index fd512749dfb0..c69f8591ae7b 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -1,11 +1,11 @@ import { TSESTree, AST_NODE_TYPES, + ESLintUtils, TSESLint, } from '@typescript-eslint/experimental-utils'; import { isTypeAssertion, isConstructor, isSetter } from './astUtils'; import { getFunctionHeadLoc } from './getFunctionHeadLoc'; -import { nullThrows, NullThrowsReasons } from './nullThrows'; type FunctionExpression = | TSESTree.ArrowFunctionExpression @@ -187,7 +187,10 @@ function isTypedFunctionExpression( node: FunctionExpression, options: Options, ): boolean { - const parent = nullThrows(node.parent, NullThrowsReasons.MissingParent); + const parent = ESLintUtils.nullThrows( + node.parent, + ESLintUtils.NullThrowsReasons.MissingParent, + ); if (!options.allowTypedFunctionExpressions) { return false; @@ -215,7 +218,10 @@ function isValidFunctionExpressionReturnType( return true; } - const parent = nullThrows(node.parent, NullThrowsReasons.MissingParent); + const parent = ESLintUtils.nullThrows( + node.parent, + ESLintUtils.NullThrowsReasons.MissingParent, + ); if ( options.allowExpressions && parent.type !== AST_NODE_TYPES.VariableDeclarator && diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index 1701b4ef1d76..7c2427c8fcb9 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -1,6 +1,6 @@ +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; import { version } from 'eslint/package.json'; import * as semver from 'semver'; -import { nullThrows } from './nullThrows'; const isESLintV8 = semver.major(version) >= 8; @@ -42,7 +42,7 @@ type RuleId = keyof RuleMap; export const getESLintCoreRule: (ruleId: R) => RuleMap[R] = isESLintV8 ? (ruleId: R): RuleMap[R] => - nullThrows( + ESLintUtils.nullThrows( // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call require('eslint/use-at-your-own-risk').builtinRules.get( ruleId, diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 4c0284895235..be002d962957 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -1,4 +1,5 @@ import { ESLintUtils } from '@typescript-eslint/experimental-utils'; +import { ESLintTypeUtils } from '@typescript-eslint/type-utils'; export * from './astUtils'; export * from './collectUnusedVariables'; @@ -6,26 +7,44 @@ export * from './createRule'; export * from './getFunctionHeadLoc'; export * from './getThisExpression'; export * from './getWrappingFixer'; -export * from './isTypeReadonly'; export * from './misc'; -export * from './nullThrows'; export * from './objectIterators'; -export * from './propertyTypes'; export * from './requiresQuoting'; export * from './types'; // this is done for convenience - saves migrating all of the old rules -const { applyDefault, deepMerge, isObjectNotArray, getParserServices } = - ESLintUtils; +const { + applyDefault, + deepMerge, + isObjectNotArray, + getParserServices, + nullThrows, + NullThrowsReasons, +} = ESLintUtils; type InferMessageIdsTypeFromRule = ESLintUtils.InferMessageIdsTypeFromRule; type InferOptionsTypeFromRule = ESLintUtils.InferOptionsTypeFromRule; +const { + getTypeOfPropertyOfName, + isTypeReadonly, + readonlynessOptionsSchema, + readonlynessOptionsDefaults, +} = ESLintTypeUtils; +type ReadonlynessOptions = ESLintTypeUtils.ReadonlynessOptions; + export { applyDefault, deepMerge, isObjectNotArray, getParserServices, + getTypeOfPropertyOfName, + isTypeReadonly, + nullThrows, + readonlynessOptionsDefaults, + readonlynessOptionsSchema, InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, + NullThrowsReasons, + ReadonlynessOptions, }; diff --git a/packages/eslint-plugin/tsconfig.build.json b/packages/eslint-plugin/tsconfig.build.json index bc597c33129b..fc60d9a85a78 100644 --- a/packages/eslint-plugin/tsconfig.build.json +++ b/packages/eslint-plugin/tsconfig.build.json @@ -12,6 +12,7 @@ "references": [ { "path": "../experimental-utils/tsconfig.build.json" }, { "path": "../parser/tsconfig.build.json" }, - { "path": "../scope-manager/tsconfig.build.json" } + { "path": "../scope-manager/tsconfig.build.json" }, + { "path": "../type-utils/tsconfig.build.json" } ] } diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index c7e9c4ecb2b7..31db855dad82 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -8,6 +8,7 @@ "references": [ { "path": "../experimental-utils/tsconfig.build.json" }, { "path": "../parser/tsconfig.build.json" }, - { "path": "../scope-manager/tsconfig.build.json" } + { "path": "../scope-manager/tsconfig.build.json" }, + { "path": "../type-utils/tsconfig.build.json" } ] } diff --git a/packages/experimental-utils/src/eslint-utils/index.ts b/packages/experimental-utils/src/eslint-utils/index.ts index bbbe8df2709f..fc8e410428e6 100644 --- a/packages/experimental-utils/src/eslint-utils/index.ts +++ b/packages/experimental-utils/src/eslint-utils/index.ts @@ -5,3 +5,4 @@ export * from './InferTypesFromRule'; export * from './RuleCreator'; export * from './RuleTester'; export * from './deepMerge'; +export * from './nullThrows'; diff --git a/packages/eslint-plugin/src/util/nullThrows.ts b/packages/experimental-utils/src/eslint-utils/nullThrows.ts similarity index 100% rename from packages/eslint-plugin/src/util/nullThrows.ts rename to packages/experimental-utils/src/eslint-utils/nullThrows.ts diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md new file mode 100644 index 000000000000..e4d87c4d45c4 --- /dev/null +++ b/packages/type-utils/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/type-utils/LICENSE b/packages/type-utils/LICENSE new file mode 100644 index 000000000000..7641edcfd0af --- /dev/null +++ b/packages/type-utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 TypeScript ESLint and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/type-utils/README.md b/packages/type-utils/README.md new file mode 100644 index 000000000000..f7adf78893bf --- /dev/null +++ b/packages/type-utils/README.md @@ -0,0 +1,27 @@ +

Type utils for ESLint Plugins

+ +

Type utilities for working with TypeScript + ESLint together.

+ +

+ CI + NPM Version + NPM Downloads +

+ +## Note + +This package has inherited its version number from the `@typescript-eslint` project. +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. + +## Exports + +| Name | Description | +| ----------------------------------- | ----------------------------------------------------- | +| [`ESLintUtils`](./src/eslint-utils) | Tools for creating ESLint type rules with TypeScript. | + +## Contributing + +[See the contributing guide here](../../CONTRIBUTING.md) diff --git a/packages/type-utils/jest.config.js b/packages/type-utils/jest.config.js new file mode 100644 index 000000000000..bf4e270e3760 --- /dev/null +++ b/packages/type-utils/jest.config.js @@ -0,0 +1,21 @@ +'use strict'; + +// @ts-check +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + resolver: '/../../tests/jest-resolver.js', + globals: { + 'ts-jest': { + isolatedModules: true, + }, + }, + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + testRegex: './tests/.+\\.test\\.ts$', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + coverageReporters: ['text-summary', 'lcov'], +}; diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json new file mode 100644 index 000000000000..f8da1a9f986c --- /dev/null +++ b/packages/type-utils/package.json @@ -0,0 +1,65 @@ +{ + "name": "@typescript-eslint/type-utils", + "version": "5.6.0", + "description": "Type utilities for working with TypeScript + ESLint together", + "keywords": [ + "eslint", + "typescript", + "estree" + ], + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "files": [ + "dist", + "_ts3.4", + "package.json", + "README.md", + "LICENSE" + ], + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/type-utils" + }, + "bugs": { + "url": "https://github.com/typescript-eslint/typescript-eslint/issues" + }, + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc -b tsconfig.build.json", + "postbuild": "downlevel-dts dist _ts3.4/dist", + "clean": "tsc -b tsconfig.build.json --clean", + "postclean": "rimraf dist && rimraf _ts3.4 && rimraf coverage", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'", + "test": "jest --coverage", + "typecheck": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/experimental-utils": "5.4.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "tsutils": "^3.21.0" + }, + "peerDependencies": { + "eslint": "*" + }, + "devDependencies": { + "typescript": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "typesVersions": { + "<3.8": { + "*": [ + "_ts3.4/*" + ] + } + } +} diff --git a/packages/type-utils/project.json b/packages/type-utils/project.json new file mode 100644 index 000000000000..fea4ee94ee4d --- /dev/null +++ b/packages/type-utils/project.json @@ -0,0 +1,5 @@ +{ + "root": "packages/type-utils", + "type": "library", + "implicitDependencies": [] +} diff --git a/packages/type-utils/src/eslint-utils/index.ts b/packages/type-utils/src/eslint-utils/index.ts new file mode 100644 index 000000000000..6ef5291c0781 --- /dev/null +++ b/packages/type-utils/src/eslint-utils/index.ts @@ -0,0 +1,2 @@ +export * from './isTypeReadonly'; +export * from './propertyTypes'; diff --git a/packages/eslint-plugin/src/util/isTypeReadonly.ts b/packages/type-utils/src/eslint-utils/isTypeReadonly.ts similarity index 94% rename from packages/eslint-plugin/src/util/isTypeReadonly.ts rename to packages/type-utils/src/eslint-utils/isTypeReadonly.ts index efb6966d6766..c6bc0f5761a4 100644 --- a/packages/eslint-plugin/src/util/isTypeReadonly.ts +++ b/packages/type-utils/src/eslint-utils/isTypeReadonly.ts @@ -1,3 +1,4 @@ +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; import { isObjectType, isUnionType, @@ -7,7 +8,6 @@ import { isSymbolFlagSet, } from 'tsutils'; import * as ts from 'typescript'; -import { nullThrows, NullThrowsReasons } from './nullThrows'; import { getTypeOfPropertyOfType } from './propertyTypes'; const enum Readonlyness { @@ -75,9 +75,9 @@ function isTypeReadonlyArrayOrTuple( } if (checker.isArrayType(type)) { - const symbol = nullThrows( + const symbol = ESLintUtils.nullThrows( type.getSymbol(), - NullThrowsReasons.MissingToken('symbol', 'array type'), + ESLintUtils.NullThrowsReasons.MissingToken('symbol', 'array type'), ); const escapedName = symbol.getEscapedName(); if (escapedName === 'Array') { @@ -142,9 +142,12 @@ function isTypeReadonlyObject( // as we might be able to bail out early due to a mutable property before // doing this deep, potentially expensive check. for (const property of properties) { - const propertyType = nullThrows( + const propertyType = ESLintUtils.nullThrows( getTypeOfPropertyOfType(checker, type, property), - NullThrowsReasons.MissingToken(`property "${property.name}"`, 'type'), + ESLintUtils.NullThrowsReasons.MissingToken( + `property "${property.name}"`, + 'type', + ), ); // handle recursive types. diff --git a/packages/eslint-plugin/src/util/propertyTypes.ts b/packages/type-utils/src/eslint-utils/propertyTypes.ts similarity index 100% rename from packages/eslint-plugin/src/util/propertyTypes.ts rename to packages/type-utils/src/eslint-utils/propertyTypes.ts diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts new file mode 100644 index 000000000000..2912b6d7de7a --- /dev/null +++ b/packages/type-utils/src/index.ts @@ -0,0 +1 @@ +export * as ESLintTypeUtils from './eslint-utils'; diff --git a/packages/type-utils/tests/eslint-utils/index.test.ts b/packages/type-utils/tests/eslint-utils/index.test.ts new file mode 100644 index 000000000000..81702dffc1a1 --- /dev/null +++ b/packages/type-utils/tests/eslint-utils/index.test.ts @@ -0,0 +1,7 @@ +import { ESLintTypeUtils } from '../../src'; + +describe('index', () => { + it('exists', () => { + expect(ESLintTypeUtils).toBeDefined(); + }); +}); diff --git a/packages/type-utils/tsconfig.build.json b/packages/type-utils/tsconfig.build.json new file mode 100644 index 000000000000..3b5743aaf5d9 --- /dev/null +++ b/packages/type-utils/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true + }, + "include": ["src", "typings"], + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] +} diff --git a/packages/type-utils/tsconfig.json b/packages/type-utils/tsconfig.json new file mode 100644 index 000000000000..9c0c2ac4ba52 --- /dev/null +++ b/packages/type-utils/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "composite": false, + "rootDir": "." + }, + "include": ["src", "typings", "tests", "tools"], + "references": [{ "path": "../experimental-utils/tsconfig.build.json" }] +} diff --git a/packages/type-utils/typings/typescript.d.ts b/packages/type-utils/typings/typescript.d.ts new file mode 100644 index 000000000000..7c9089158b4b --- /dev/null +++ b/packages/type-utils/typings/typescript.d.ts @@ -0,0 +1,26 @@ +import 'typescript'; + +declare module 'typescript' { + interface TypeChecker { + // internal TS APIs + + /** + * @returns `true` if the given type is an array type: + * - `Array` + * - `ReadonlyArray` + * - `foo[]` + * - `readonly foo[]` + */ + isArrayType(type: Type): type is TypeReference; + /** + * @returns `true` if the given type is a tuple type: + * - `[foo]` + * - `readonly [foo]` + */ + isTupleType(type: Type): type is TupleTypeReference; + /** + * Return the type of the given property in the given type, or undefined if no such property exists + */ + getTypeOfPropertyOfType(type: Type, propertyName: string): Type | undefined; + } +} diff --git a/workspace.json b/workspace.json index f6b206e61edb..6fbf76fb0786 100644 --- a/workspace.json +++ b/workspace.json @@ -10,6 +10,7 @@ "@typescript-eslint/scope-manager": "packages/scope-manager", "@typescript-eslint/shared-fixtures": "packages/shared-fixtures", "@typescript-eslint/types": "packages/types", + "@typescript-eslint/type-utils": "packages/type-utils", "@typescript-eslint/typescript-estree": "packages/typescript-estree", "@typescript-eslint/visitor-keys": "packages/visitor-keys", "@typescript-eslint/website-eslint": "packages/website-eslint",