diff --git a/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml b/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml index 3eb5e066206b..59b8783d1e75 100644 --- a/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml +++ b/.github/ISSUE_TEMPLATE/06-bug-report-other.yaml @@ -38,6 +38,7 @@ body: description: Select the package against which you want to report the bug. options: - ast-spec + - eslint-plugin - parser - rule-tester - scope-manager diff --git a/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml b/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml index 7b13e0f95272..604ce5468161 100644 --- a/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml +++ b/.github/ISSUE_TEMPLATE/07-enhancement-other.yaml @@ -24,6 +24,7 @@ body: description: Select the package against which you want to report the bug. options: - ast-spec + - eslint-plugin - parser - rule-tester - scope-manager diff --git a/CHANGELOG.md b/CHANGELOG.md index cf59d2eafd8e..f6bbc3751139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 8.6.0 (2024-09-16) + + +### 🚀 Features + +- add `allow` option for `restrict-template-expressions` ([#8556](https://github.com/typescript-eslint/typescript-eslint/pull/8556)) +- **eslint-plugin:** [no-unnecessary-condition] check switch cases ([#9912](https://github.com/typescript-eslint/typescript-eslint/pull/9912)) +- **eslint-plugin:** [no-misused-promises] check array predicate return ([#9955](https://github.com/typescript-eslint/typescript-eslint/pull/9955)) +- **type-utils:** isNullableType add Void logic ([#9937](https://github.com/typescript-eslint/typescript-eslint/pull/9937)) +- **typescript-estree:** disable plugin loading by default in project service ([#9964](https://github.com/typescript-eslint/typescript-eslint/pull/9964)) + +### 🩹 Fixes + +- **eslint-plugin:** [no-deprecated] don't report recursive types in destructuring assignment twice ([#9969](https://github.com/typescript-eslint/typescript-eslint/pull/9969)) +- **eslint-plugin:** [no-deprecated] report on deprecated variables used in destructuring assignment ([#9978](https://github.com/typescript-eslint/typescript-eslint/pull/9978)) +- **eslint-plugin:** [no-deprecated] report on deprecated properties with function-like types ([#9977](https://github.com/typescript-eslint/typescript-eslint/pull/9977)) +- **eslint-plugin:** [no-unnecessary-condition] properly reflect multiple negations in message ([#9940](https://github.com/typescript-eslint/typescript-eslint/pull/9940)) +- **typescript-estree:** don't throw on missing tsconfig.json by default in project service ([#9989](https://github.com/typescript-eslint/typescript-eslint/pull/9989)) + +### ❤️ Thank You + +- Abraham Guo +- auvred @auvred +- Josh Goldberg ✨ +- Kim Sang Du @developer-bandi +- YeonJuan @yeonjuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) @@ -145,6 +174,8 @@ You can read about our [versioning strategy](https://main--typescript-eslint.net # 8.0.0 (2024-07-31) +> 👉 See [Announcing typescript-eslint v8](https://typescript-eslint.io/blog/announcing-typescript-eslint-v8) for an upgrade guide and the full list of changes. + ### ⚠️ Breaking Changes - **typescript-estree:** split TSMappedType typeParameter into constraint and key ([#7065](https://github.com/typescript-eslint/typescript-eslint/pull/7065)) @@ -824,6 +855,7 @@ You can read about our [versioning strategy](https://main--typescript-eslint.net # 7.0.0 (2024-02-12) +> 👉 See [Announcing typescript-eslint v7](https://typescript-eslint.io/blog/announcing-typescript-eslint-v7) for an upgrade guide and the full list of changes. ### 🚀 Features @@ -1468,6 +1500,8 @@ You can read about our [versioning strategy](https://main--typescript-eslint.net # [6.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.62.0...v6.0.0) (2023-07-10) +> 👉 See [Announcing typescript-eslint v6](https://typescript-eslint.io/blog/announcing-typescript-eslint-v6) for an upgrade guide and the full list of changes. + ### Bug Fixes diff --git a/docs/developers/Custom_Rules.mdx b/docs/developers/Custom_Rules.mdx index 9e51c2b36b05..55587f433d78 100644 --- a/docs/developers/Custom_Rules.mdx +++ b/docs/developers/Custom_Rules.mdx @@ -399,7 +399,7 @@ const ruleTester = new RuleTester({ languageOptions: { parserOptions: { projectService: { - allowDefaultProjectForFiles: ['*.ts*'], + allowDefaultProject: ['*.ts*'], }, tsconfigRootDir: __dirname, }, diff --git a/docs/developers/ESLint_Plugins.mdx b/docs/developers/ESLint_Plugins.mdx index 71b41215f86a..657f5c80b24d 100644 --- a/docs/developers/ESLint_Plugins.mdx +++ b/docs/developers/ESLint_Plugins.mdx @@ -42,6 +42,9 @@ Include the following to enforce the version range allowed without making users' Those are all packages consumers are expected to be using already. +You don't need to declare any dependencies on `typescript-eslint` or `@typescript-eslint/eslint-plugin`. +Our setup guide ensures that the parser is automatically registered when configuring ESLint. + ## `RuleCreator` Usage We recommend including at least the following three properties in your plugin's [`RuleCreator` extra rule docs types](./Custom_Rules.mdx#extra-rule-docs-types): diff --git a/docs/packages/Parser.mdx b/docs/packages/Parser.mdx index da732838e4ca..12ee7c9777cd 100644 --- a/docs/packages/Parser.mdx +++ b/docs/packages/Parser.mdx @@ -369,6 +369,24 @@ It takes in a string path that will be resolved relative to the [`tsconfigRootDi `projectService.defaultProject` only impacts the "out-of-project" files included by [`allowDefaultProject`](#allowdefaultproject). +##### `loadTypeScriptPlugins` + +> Default `false` + +Whether the project service should be allowed to load [TypeScript plugins](https://www.typescriptlang.org/tsconfig/plugins.html). +This is `false` by default to prevent plugins from registering persistent file watchers or other operations that might prevent ESLint processes from exiting when run on the command-line. + +If your project is configured with custom rules that interact with TypeScript plugins, it may be useful to turn this on in your editor. +For example, only enabling this option when running within VS Code: + +```js +parserOptions: { + projectService: { + loadTypeScriptPlugins: !!process.env.VSCODE_PID, + } +} +``` + ##### `maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING` > Default: `8`. diff --git a/docs/packages/Rule_Tester.mdx b/docs/packages/Rule_Tester.mdx index b0cf1a52068a..b5d63077803c 100644 --- a/docs/packages/Rule_Tester.mdx +++ b/docs/packages/Rule_Tester.mdx @@ -123,7 +123,7 @@ ruleTester.run('my-rule', rule, { ### Type-Aware Testing Type-aware rules can be tested in almost exactly the same way as regular code, using `parserOptions.projectService`. -Most rule tests specify `parserOptions.allowDefaultProjectForFiles: ["*.ts*"]` to enable type checking on all test files. +Most rule tests specify `parserOptions.allowDefaultProject: ["*.ts*"]` to enable type checking on all test files. You can then test your rule by providing the type-aware config: diff --git a/docs/packages/TypeScript_ESTree.mdx b/docs/packages/TypeScript_ESTree.mdx index 73e630827786..d865ebb717dc 100644 --- a/docs/packages/TypeScript_ESTree.mdx +++ b/docs/packages/TypeScript_ESTree.mdx @@ -279,6 +279,11 @@ interface ProjectServiceOptions { */ defaultProject?: string; + /** + * Whether to load TypeScript plugins as configured in the TSConfig. + */ + loadTypeScriptPlugins?: boolean; + /** * The maximum number of files {@link allowDefaultProject} may match. * Each file match slows down linting, so if you do need to use this, please diff --git a/knip.ts b/knip.ts index 8c45a68d568c..e1b4d2ced348 100644 --- a/knip.ts +++ b/knip.ts @@ -87,7 +87,7 @@ export default { '@docusaurus/mdx-loader', '@docusaurus/types', '@docusaurus/plugin-content-docs', - '@docusaurus/plugin-content-blog/client', + '@docusaurus/plugin-content-blog', '@docusaurus/theme-search-algolia', '@docusaurus/ExecutionEnvironment', '@docusaurus/Link', diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index c813577a3ae0..cc6fbe1a3982 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for ast-spec to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for ast-spec to align it with other projects, there were no code changes. diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 441379ec07cd..ca0618bdb437 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "8.5.0", + "version": "8.6.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index d249560387b2..9e4a005bbfe1 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,38 @@ +## 8.6.0 (2024-09-16) + + +### 🚀 Features + +- add `allow` option for `restrict-template-expressions` + +- **type-utils:** isNullableType add Void logic + +- **eslint-plugin:** [no-unnecessary-condition] check switch cases + +- **eslint-plugin:** [no-misused-promises] check array predicate return + + +### 🩹 Fixes + +- **eslint-plugin:** [no-deprecated] don't report recursive types in destructuring assignment twice + +- **eslint-plugin:** [no-deprecated] report on deprecated variables used in destructuring assignment + +- **eslint-plugin:** [no-deprecated] report on deprecated properties with function-like types + +- **eslint-plugin:** [no-unnecessary-condition] properly reflect multiple negations in message + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Josh Goldberg ✨ +- Kim Sang Du +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) diff --git a/packages/eslint-plugin/docs/rules/no-misused-promises.mdx b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx index bc8ae0f33b6e..f4fd23fec126 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-misused-promises.mdx @@ -134,6 +134,8 @@ if (promise) { const val = promise ? 123 : 456; +[1, 2, 3].filter(() => promise); + while (promise) { // Do something } @@ -152,6 +154,9 @@ if (await promise) { const val = (await promise) ? 123 : 456; +const returnVal = await promise; +[1, 2, 3].filter(() => returnVal); + while (await promise) { // Do something } diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx index 165388ef3a0f..ec5f234ac981 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-condition.mdx @@ -16,6 +16,7 @@ The following expressions are checked: - Arguments to the `&&`, `||` and `?:` (ternary) operators - Conditions for `if`, `for`, `while`, and `do-while` statements +- `case`s in `switch` statements - Base values of optional chain expressions ## Examples diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 2d6b03041224..60ea75a7c65e 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -85,7 +85,7 @@ const msg2 = `arg = ${arg || 'not truthy'}`; ### `allowAny` -Whether to `any` typed values in template expressions. +Whether to allow `any` typed values in template expressions. Examples of additional **correct** code for this rule with `{ allowAny: true }`: @@ -124,7 +124,7 @@ const msg1 = `arg = ${arg}`; ### `allowNever` -Whether to `never` typed values in template expressions. +Whether to allow `never` typed values in template expressions. Examples of additional **correct** code for this rule with `{ allowNever: true }`: @@ -135,7 +135,7 @@ const msg1 = typeof arg === 'string' ? arg : `arg = ${arg}`; ### `allowArray` -Whether to `Array` typed values in template expressions. +Whether to allow `Array` typed values in template expressions. Examples of additional **correct** code for this rule with `{ allowArray: true }`: @@ -144,6 +144,19 @@ const arg = ['foo', 'bar']; const msg1 = `arg = ${arg}`; ``` +### `allow` + +Whether to allow additional types in template expressions. + +This option takes the shared [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier). + +Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }`: + +```ts showPlaygroundButton +const error = new Error(); +const msg1 = `arg = ${error}`; +``` + ## When Not To Use It If you're not worried about incorrectly stringifying non-string values in template literals, then you likely don't need this rule. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 8098f13e18b2..424409b491db 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "8.5.0", + "version": "8.6.0", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -60,10 +60,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/type-utils": "8.5.0", - "@typescript-eslint/utils": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -74,8 +74,8 @@ "@types/marked": "^5.0.2", "@types/mdast": "^4.0.3", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "8.5.0", - "@typescript-eslint/rule-tester": "8.5.0", + "@typescript-eslint/rule-schema-to-typescript-types": "8.6.0", + "@typescript-eslint/rule-tester": "8.6.0", "ajv": "^6.12.6", "cross-env": "^7.0.3", "cross-fetch": "*", diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 816c7803604a..28d4a5cc78ba 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -3,9 +3,11 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, + getStaticMemberAccessValue, getStaticStringValue, isAssignee, isFunction, + isStaticMemberAccessOfValue, nullThrows, } from '../util'; @@ -79,10 +81,6 @@ export default createRule({ create(context, [style]) { const propertiesInfoStack: PropertiesInfo[] = []; - function getStringValue(node: TSESTree.Node): string { - return getStaticStringValue(node) ?? context.sourceCode.getText(node); - } - function enterClassBody(): void { propertiesInfoStack.push({ properties: [], @@ -102,8 +100,8 @@ export default createRule({ return; } - const name = getStringValue(node.key); - if (excludeSet.has(name)) { + const name = getStaticMemberAccessValue(node, context); + if (name && excludeSet.has(name)) { return; } @@ -167,15 +165,17 @@ export default createRule({ return; } - const name = getStringValue(node.key); - - const hasDuplicateKeySetter = node.parent.body.some(element => { - return ( - element.type === AST_NODE_TYPES.MethodDefinition && - element.kind === 'set' && - getStringValue(element.key) === name - ); - }); + const name = getStaticMemberAccessValue(node, context); + + const hasDuplicateKeySetter = + name && + node.parent.body.some(element => { + return ( + element.type === AST_NODE_TYPES.MethodDefinition && + element.kind === 'set' && + isStaticMemberAccessOfValue(element, context, name) + ); + }); if (hasDuplicateKeySetter) { return; } diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 4f574471e03b..6e27a6229f12 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -5,7 +5,7 @@ import { createRule, getFunctionHeadLoc, getFunctionNameWithKind, - getStaticStringValue, + getStaticMemberAccessValue, } from '../util'; type Options = [ @@ -182,10 +182,7 @@ export default createRule({ const hashIfNeeded = node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : ''; - const name = - node.key.type === AST_NODE_TYPES.Literal - ? getStaticStringValue(node.key) - : node.key.name || ''; + const name = getStaticMemberAccessValue(node, context); return !exceptMethods.has(hashIfNeeded + (name ?? '')); } diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index 2da841b06e82..2bcd6cf4df43 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -2,7 +2,7 @@ import { DefinitionType } from '@typescript-eslint/scope-manager'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { createRule, isFunction } from '../util'; +import { createRule, isFunction, isStaticMemberAccessOfValue } from '../util'; import type { FunctionExpression, FunctionInfo, @@ -268,21 +268,11 @@ export default createRule({ (node.type === AST_NODE_TYPES.Property && node.method) || node.type === AST_NODE_TYPES.PropertyDefinition ) { - if ( - node.key.type === AST_NODE_TYPES.Literal && - typeof node.key.value === 'string' - ) { - return options.allowedNames.includes(node.key.value); - } - if ( - node.key.type === AST_NODE_TYPES.TemplateLiteral && - node.key.expressions.length === 0 - ) { - return options.allowedNames.includes(node.key.quasis[0].value.raw); - } - if (!node.computed && node.key.type === AST_NODE_TYPES.Identifier) { - return options.allowedNames.includes(node.key.name); - } + return isStaticMemberAccessOfValue( + node, + context, + ...options.allowedNames, + ); } return false; diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index 9c9a30cc00bc..ddbe08eb0926 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -52,19 +52,17 @@ export default createRule({ return parent.key === node; case AST_NODE_TYPES.Property: + // foo in "const { foo } = bar" will be processed twice, as parent.key + // and parent.value. The second is treated as a declaration. return ( (parent.shorthand && parent.value === node) || parent.parent.type === AST_NODE_TYPES.ObjectExpression ); case AST_NODE_TYPES.AssignmentPattern: - return ( - parent.left === node && - !( - parent.parent.type === AST_NODE_TYPES.Property && - parent.parent.shorthand - ) - ); + // foo in "const { foo = "" } = bar" will be processed twice, as parent.parent.key + // and parent.left. The second is treated as a declaration. + return parent.left === node; case AST_NODE_TYPES.ArrowFunctionExpression: case AST_NODE_TYPES.FunctionDeclaration: @@ -174,15 +172,32 @@ export default createRule({ const signature = checker.getResolvedSignature( tsNode as ts.CallLikeExpression, ); + const symbol = services.getSymbolAtLocation(node); if (signature) { const signatureDeprecation = getJsDocDeprecation(signature); if (signatureDeprecation !== undefined) { return signatureDeprecation; } + + // Properties with function-like types have "deprecated" jsdoc + // on their symbols, not on their signatures: + // + // interface Props { + // /** @deprecated */ + // property: () => 'foo' + // ^symbol^ ^signature^ + // } + const symbolDeclarationKind = symbol?.declarations?.[0].kind; + if ( + symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration && + symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration && + symbolDeclarationKind !== ts.SyntaxKind.MethodSignature + ) { + return getJsDocDeprecation(symbol); + } } // Or it could be a ClassDeclaration or a variable set to a ClassExpression. - const symbol = services.getSymbolAtLocation(node); const symbolAtLocation = symbol && checker.getTypeOfSymbolAtLocation(symbol, tsNode).getSymbol(); @@ -195,10 +210,7 @@ export default createRule({ function getSymbol( node: IdentifierLike, ): ts.Signature | ts.Symbol | undefined { - if ( - node.parent.type === AST_NODE_TYPES.AssignmentPattern || - node.parent.type === AST_NODE_TYPES.Property - ) { + if (node.parent.type === AST_NODE_TYPES.Property) { return services .getTypeAtLocation(node.parent.parent) .getProperty(node.name); diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 3b0265e1718f..9c1cc46525c4 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -8,11 +8,12 @@ import { createRule, getOperatorPrecedence, getParserServices, + getStaticMemberAccessValue, isBuiltinSymbolLike, OperatorPrecedence, readonlynessOptionsDefaults, readonlynessOptionsSchema, - typeMatchesSpecifier, + typeMatchesSomeSpecifier, } from '../util'; type Options = [ @@ -238,8 +239,10 @@ export default createRule({ const type = services.getTypeAtLocation(node.callee); - return allowForKnownSafeCalls.some(allowedType => - typeMatchesSpecifier(type, allowedType, services.program), + return typeMatchesSomeSpecifier( + type, + allowForKnownSafeCalls, + services.program, ); } @@ -334,27 +337,38 @@ export default createRule({ // If the outer expression is a call, a `.catch()` or `.then()` with // rejection handler handles the promise. - const catchRejectionHandler = getRejectionHandlerFromCatchCall(node); - if (catchRejectionHandler) { - if (isValidRejectionHandler(catchRejectionHandler)) { - return { isUnhandled: false }; + const { callee } = node; + if (callee.type === AST_NODE_TYPES.MemberExpression) { + const methodName = getStaticMemberAccessValue(callee, context); + const catchRejectionHandler = + methodName === 'catch' && node.arguments.length >= 1 + ? node.arguments[0] + : undefined; + if (catchRejectionHandler) { + if (isValidRejectionHandler(catchRejectionHandler)) { + return { isUnhandled: false }; + } + return { isUnhandled: true, nonFunctionHandler: true }; } - return { isUnhandled: true, nonFunctionHandler: true }; - } - const thenRejectionHandler = getRejectionHandlerFromThenCall(node); - if (thenRejectionHandler) { - if (isValidRejectionHandler(thenRejectionHandler)) { - return { isUnhandled: false }; + const thenRejectionHandler = + methodName === 'then' && node.arguments.length >= 2 + ? node.arguments[1] + : undefined; + if (thenRejectionHandler) { + if (isValidRejectionHandler(thenRejectionHandler)) { + return { isUnhandled: false }; + } + return { isUnhandled: true, nonFunctionHandler: true }; } - return { isUnhandled: true, nonFunctionHandler: true }; - } - // `x.finally()` is transparent to resolution of the promise, so check `x`. - // ("object" in this context is the `x` in `x.finally()`) - const promiseFinallyObject = getObjectFromFinallyCall(node); - if (promiseFinallyObject) { - return isUnhandledPromise(checker, promiseFinallyObject); + // `x.finally()` is transparent to resolution of the promise, so check `x`. + // ("object" in this context is the `x` in `x.finally()`) + const promiseFinallyObject = + methodName === 'finally' ? callee.object : undefined; + if (promiseFinallyObject) { + return isUnhandledPromise(checker, promiseFinallyObject); + } } // All other cases are unhandled. @@ -407,8 +421,10 @@ export default createRule({ // The highest priority is to allow anything allowlisted if ( - allowForKnownSafePromises.some(allowedType => - typeMatchesSpecifier(type, allowedType, services.program), + typeMatchesSomeSpecifier( + type, + allowForKnownSafePromises, + services.program, ) ) { return false; @@ -485,41 +501,3 @@ function isFunctionParam( } return false; } - -function getRejectionHandlerFromCatchCall( - expression: TSESTree.CallExpression, -): TSESTree.CallExpressionArgument | undefined { - if ( - expression.callee.type === AST_NODE_TYPES.MemberExpression && - expression.callee.property.type === AST_NODE_TYPES.Identifier && - expression.callee.property.name === 'catch' && - expression.arguments.length >= 1 - ) { - return expression.arguments[0]; - } - return undefined; -} - -function getRejectionHandlerFromThenCall( - expression: TSESTree.CallExpression, -): TSESTree.CallExpressionArgument | undefined { - if ( - expression.callee.type === AST_NODE_TYPES.MemberExpression && - expression.callee.property.type === AST_NODE_TYPES.Identifier && - expression.callee.property.name === 'then' && - expression.arguments.length >= 2 - ) { - return expression.arguments[1]; - } - return undefined; -} - -function getObjectFromFinallyCall( - expression: TSESTree.CallExpression, -): TSESTree.Expression | undefined { - return expression.callee.type === AST_NODE_TYPES.MemberExpression && - expression.callee.property.type === AST_NODE_TYPES.Identifier && - expression.callee.property.name === 'finally' - ? expression.callee.object - : undefined; -} diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index c6e0978f1cb0..9edd31141e0a 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -6,6 +6,7 @@ import * as ts from 'typescript'; import { createRule, getParserServices, + isArrayMethodCallWithPredicate, isFunction, isRestParameterDeclaration, nullThrows, @@ -31,6 +32,7 @@ interface ChecksVoidReturnOptions { type MessageId = | 'conditional' + | 'predicate' | 'spread' | 'voidReturnArgument' | 'voidReturnAttribute' @@ -91,6 +93,7 @@ export default createRule({ voidReturnVariable: 'Promise-returning function provided to variable where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', + predicate: 'Expected a non-Promise value to be returned.', spread: 'Expected a non-Promise value to be spreaded in an object.', }, schema: [ @@ -175,6 +178,7 @@ export default createRule({ checkConditional(node.argument, true); }, WhileStatement: checkTestConditional, + 'CallExpression > MemberExpression': checkArrayPredicates, }; checksVoidReturn = parseChecksVoidReturn(checksVoidReturn); @@ -322,6 +326,25 @@ export default createRule({ } } + function checkArrayPredicates(node: TSESTree.MemberExpression): void { + const parent = node.parent; + if (parent.type === AST_NODE_TYPES.CallExpression) { + const callback = parent.arguments.at(0); + if ( + callback && + isArrayMethodCallWithPredicate(context, services, parent) + ) { + const type = services.esTreeNodeToTSNodeMap.get(callback); + if (returnsThenable(checker, type)) { + context.report({ + messageId: 'predicate', + node: callback, + }); + } + } + } + } + function checkArguments( node: TSESTree.CallExpression | TSESTree.NewExpression, ): void { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 8f193dde47fa..035ba000f33d 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -9,6 +9,7 @@ import { getParserServices, getTypeName, getTypeOfPropertyOfName, + isArrayMethodCallWithPredicate, isIdentifier, isNullableType, isTypeAnyType, @@ -233,21 +234,22 @@ export default createRule({ * if the type of the node is always true or always false, it's not necessary. */ function checkNode( - node: TSESTree.Expression, + expression: TSESTree.Expression, isUnaryNotArgument = false, + node = expression, ): void { // Check if the node is Unary Negation expression and handle it if ( - node.type === AST_NODE_TYPES.UnaryExpression && - node.operator === '!' + expression.type === AST_NODE_TYPES.UnaryExpression && + expression.operator === '!' ) { - return checkNode(node.argument, true); + return checkNode(expression.argument, !isUnaryNotArgument, node); } // 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)) { + if (isArrayIndexExpression(expression)) { return; } @@ -258,13 +260,13 @@ export default createRule({ // boolean checks if we inspect the right here, it'll usually be a constant condition on purpose. // In this case it's better to inspect the type of the expression as a whole. if ( - node.type === AST_NODE_TYPES.LogicalExpression && - node.operator !== '??' + expression.type === AST_NODE_TYPES.LogicalExpression && + expression.operator !== '??' ) { - return checkNode(node.right); + return checkNode(expression.right); } - const type = getConstrainedTypeAtLocation(services, node); + const type = getConstrainedTypeAtLocation(services, expression); // Conditional is always necessary if it involves: // `any` or `unknown` or a naked type variable @@ -362,15 +364,18 @@ export default createRule({ '===', '!=', '!==', - ]); - function checkIfBinaryExpressionIsNecessaryConditional( - node: TSESTree.BinaryExpression, + ] as const); + type BoolOperator = Parameters[0]; + const isBoolOperator = (operator: string): operator is BoolOperator => + (BOOL_OPERATORS as Set).has(operator); + function checkIfBoolExpressionIsNecessaryConditional( + node: TSESTree.Node, + left: TSESTree.Node, + right: TSESTree.Node, + operator: BoolOperator, ): void { - if (!BOOL_OPERATORS.has(node.operator)) { - return; - } - const leftType = getConstrainedTypeAtLocation(services, node.left); - const rightType = getConstrainedTypeAtLocation(services, node.right); + const leftType = getConstrainedTypeAtLocation(services, left); + const rightType = getConstrainedTypeAtLocation(services, right); if (isLiteral(leftType) && isLiteral(rightType)) { context.report({ node, messageId: 'literalBooleanExpression' }); return; @@ -389,7 +394,7 @@ export default createRule({ ts.TypeFlags.TypeVariable; // Allow loose comparison to nullish values. - if (node.operator === '==' || node.operator === '!=') { + if (operator === '==' || operator === '!=') { flag |= NULL | UNDEFINED | VOID; } @@ -457,26 +462,12 @@ export default createRule({ checkNode(node.test); } - const ARRAY_PREDICATE_FUNCTIONS = new Set([ - 'filter', - 'find', - 'some', - 'every', - ]); - function isArrayPredicateFunction(node: TSESTree.CallExpression): boolean { - const { callee } = node; - return ( - // looks like `something.filter` or `something.find` - callee.type === AST_NODE_TYPES.MemberExpression && - 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) || nodeIsTupleType(callee.object)) - ); - } function checkCallExpression(node: TSESTree.CallExpression): void { // If this is something like arr.filter(x => /*condition*/), check `condition` - if (isArrayPredicateFunction(node) && node.arguments.length) { + if ( + isArrayMethodCallWithPredicate(context, services, node) && + node.arguments.length + ) { const callback = node.arguments[0]; // Inline defined functions if ( @@ -644,10 +635,9 @@ export default createRule({ ? !isCallExpressionNullableOriginFromCallee(node) : true; - const possiblyVoid = isTypeFlagSet(type, ts.TypeFlags.Void); return ( isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown) || - (isOwnNullable && (isNullableType(type) || possiblyVoid)) + (isOwnNullable && isNullableType(type)) ); } @@ -719,13 +709,34 @@ export default createRule({ return { AssignmentExpression: checkAssignmentExpression, - BinaryExpression: checkIfBinaryExpressionIsNecessaryConditional, + BinaryExpression(node): void { + const { operator } = node; + if (isBoolOperator(operator)) { + checkIfBoolExpressionIsNecessaryConditional( + node, + node.left, + node.right, + operator, + ); + } + }, CallExpression: checkCallExpression, ConditionalExpression: (node): void => checkNode(node.test), DoWhileStatement: checkIfLoopIsNecessaryConditional, ForStatement: checkIfLoopIsNecessaryConditional, IfStatement: (node): void => checkNode(node.test), LogicalExpression: checkLogicalExpressionForUnnecessaryConditionals, + SwitchCase({ test, parent }): void { + // only check `case ...:`, not `default:` + if (test) { + checkIfBoolExpressionIsNecessaryConditional( + test, + parent.discriminant, + test, + '===', + ); + } + }, WhileStatement: checkIfLoopIsNecessaryConditional, 'MemberExpression[optional = true]': checkOptionalMemberExpression, 'CallExpression[optional = true]': checkOptionalCallExpression, 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 59f216fb1c3e..0d3d4122a859 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -243,7 +243,7 @@ export default createRule({ const type = getConstrainedTypeAtLocation(services, node.expression); - if (!isNullableType(type) && !isTypeFlagSet(type, ts.TypeFlags.Void)) { + if (!isNullableType(type)) { if ( node.expression.type === AST_NODE_TYPES.Identifier && isPossiblyUsedBeforeAssigned(node.expression) diff --git a/packages/eslint-plugin/src/rules/prefer-find.ts b/packages/eslint-plugin/src/rules/prefer-find.ts index 7d1c6fed86dc..389d4f581378 100644 --- a/packages/eslint-plugin/src/rules/prefer-find.ts +++ b/packages/eslint-plugin/src/rules/prefer-find.ts @@ -1,6 +1,6 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import type { RuleFix, Scope } from '@typescript-eslint/utils/ts-eslint'; +import type { RuleFix } from '@typescript-eslint/utils/ts-eslint'; import * as tsutils from 'ts-api-utils'; import type { Type } from 'typescript'; @@ -9,6 +9,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticValue, + isStaticMemberAccessOfValue, nullThrows, } from '../util'; @@ -89,7 +90,7 @@ export default createRule({ // or the optional chaining variants. if (callee.type === AST_NODE_TYPES.MemberExpression) { const isBracketSyntaxForFilter = callee.computed; - if (isStaticMemberAccessOfValue(callee, 'filter', globalScope)) { + if (isStaticMemberAccessOfValue(callee, context, 'filter')) { const filterNode = callee.property; const filteredObjectType = getConstrainedTypeAtLocation( @@ -162,7 +163,7 @@ export default createRule({ if ( callee.type === AST_NODE_TYPES.MemberExpression && !callee.optional && - isStaticMemberAccessOfValue(callee, 'at', globalScope) + isStaticMemberAccessOfValue(callee, context, 'at') ) { const atArgument = getStaticValue(node.arguments[0], globalScope); if (atArgument != null && isTreatedAsZeroByArrayAt(atArgument.value)) { @@ -321,25 +322,3 @@ export default createRule({ }; }, }); - -/** - * Answers whether the member expression looks like - * `x.memberName`, `x['memberName']`, - * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). - */ -function isStaticMemberAccessOfValue( - memberExpression: - | TSESTree.MemberExpressionComputedName - | TSESTree.MemberExpressionNonComputedName, - value: string, - scope?: Scope.Scope, -): boolean { - if (!memberExpression.computed) { - // x.memberName case. - return memberExpression.property.name === value; - } - - // x['memberName'] cases. - const staticValueResult = getStaticValue(memberExpression.property, scope); - return staticValueResult != null && value === staticValueResult.value; -} diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 1a8f705b312f..98a24fcc8917 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -8,6 +8,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticValue, + isStaticMemberAccessOfValue, } from '../util'; export default createRule({ @@ -146,6 +147,9 @@ export default createRule({ node: TSESTree.MemberExpression, allowFixing: boolean, ): void { + if (!isStaticMemberAccessOfValue(node, context, 'indexOf')) { + return; + } // Check if the comparison is equivalent to `includes()`. const callNode = node.parent as TSESTree.CallExpression; const compareNode = ( @@ -204,14 +208,14 @@ export default createRule({ return { // a.indexOf(b) !== 1 - "BinaryExpression > CallExpression.left > MemberExpression.callee[property.name='indexOf'][computed=false]"( + 'BinaryExpression > CallExpression.left > MemberExpression'( node: TSESTree.MemberExpression, ): void { checkArrayIndexOf(node, /* allowFixing */ true); }, // a?.indexOf(b) !== 1 - "BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name='indexOf'][computed=false]"( + 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression'( node: TSESTree.MemberExpression, ): void { checkArrayIndexOf(node, /* allowFixing */ false); diff --git a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts index ae5a344a299e..acad1d6ef8f3 100644 --- a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts +++ b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts @@ -10,6 +10,7 @@ import { isPromiseConstructorLike, isPromiseLike, isReadonlyErrorLike, + isStaticMemberAccessOfValue, } from '../util'; export type MessageIds = 'rejectAnError'; @@ -99,13 +100,8 @@ export default createRule({ return; } - const rejectMethodCalled = callee.computed - ? callee.property.type === AST_NODE_TYPES.Literal && - callee.property.value === 'reject' - : callee.property.name === 'reject'; - if ( - !rejectMethodCalled || + !isStaticMemberAccessOfValue(callee, context, 'reject') || !typeAtLocationIsLikePromise(callee.object) ) { return; diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts index 7eb781ed0d18..a8c820a7b370 100644 --- a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -1,5 +1,4 @@ import type { TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import type * as ts from 'typescript'; @@ -7,6 +6,7 @@ import { createRule, getConstrainedTypeAtLocation, getParserServices, + isStaticMemberAccessOfValue, isTypeAssertion, } from '../util'; @@ -14,23 +14,6 @@ type MemberExpressionWithCallExpressionParent = TSESTree.MemberExpression & { parent: TSESTree.CallExpression; }; -const getMemberExpressionName = ( - member: TSESTree.MemberExpression, -): string | null => { - if (!member.computed) { - return member.property.name; - } - - if ( - member.property.type === AST_NODE_TYPES.Literal && - typeof member.property.value === 'string' - ) { - return member.property.value; - } - - return null; -}; - export default createRule({ name: 'prefer-reduce-type-parameter', meta: { @@ -67,7 +50,7 @@ export default createRule({ 'CallExpression > MemberExpression.callee'( callee: MemberExpressionWithCallExpressionParent, ): void { - if (getMemberExpressionName(callee) !== 'reduce') { + if (!isStaticMemberAccessOfValue(callee, context, 'reduce')) { return; } diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index b19e0305c7bb..9254589a624e 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -9,6 +9,7 @@ import { getStaticValue, getTypeName, getWrappingFixer, + isStaticMemberAccessOfValue, } from '../util'; enum ArgumentType { @@ -98,9 +99,12 @@ export default createRule({ } return { - "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"( + 'CallExpression[arguments.length=1] > MemberExpression'( memberNode: TSESTree.MemberExpression, ): void { + if (!isStaticMemberAccessOfValue(memberNode, context, 'match')) { + return; + } const objectNode = memberNode.object; const callNode = memberNode.parent as TSESTree.CallExpression; const [argumentNode] = callNode.arguments; 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 f6a22152043c..d65fd9736ad5 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 @@ -9,6 +9,7 @@ import { getStaticValue, getTypeName, isNotClosingParenToken, + isStaticMemberAccessOfValue, nullThrows, NullThrowsReasons, } from '../util'; @@ -580,11 +581,12 @@ export default createRule({ // foo.substring(foo.length - 3) === 'bar' // foo.substring(foo.length - 3, foo.length) === 'bar' [[ - 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="slice"][computed=false]', - 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="substring"][computed=false]', - 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name="slice"][computed=false]', - 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression.callee[property.name="substring"][computed=false]', + 'BinaryExpression > CallExpression.left > MemberExpression', + 'BinaryExpression > ChainExpression.left > CallExpression > MemberExpression', ].join(', ')](node: TSESTree.MemberExpression): void { + if (!isStaticMemberAccessOfValue(node, context, 'slice', 'substring')) { + return; + } const callNode = getParent(node) as TSESTree.CallExpression; const parentNode = getParent(callNode); diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index e6f83e800bbb..ec1f1e1fdb15 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -5,6 +5,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getTypeName, + isStaticMemberAccessOfValue, isTypeArrayTypeOrUnionOfArrayTypes, } from '../util'; @@ -66,6 +67,9 @@ export default createRule({ } function checkSortArgument(callee: TSESTree.MemberExpression): void { + if (!isStaticMemberAccessOfValue(callee, context, 'sort', 'toSorted')) { + return; + } const calleeObjType = getConstrainedTypeAtLocation( services, callee.object, @@ -81,9 +85,7 @@ export default createRule({ } return { - "CallExpression[arguments.length=0] > MemberExpression[property.name='sort'][computed=false]": - checkSortArgument, - "CallExpression[arguments.length=0] > MemberExpression[property.name='toSorted'][computed=false]": + 'CallExpression[arguments.length=0] > MemberExpression': checkSortArgument, }; }, diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 5aa90084ac63..54b94aa0b193 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,8 +1,13 @@ +import { + typeMatchesSomeSpecifier, + typeOrValueSpecifiersSchema, +} from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { Type, TypeChecker } from 'typescript'; import { TypeFlags } from 'typescript'; +import type { TypeOrValueSpecifier } from '../util'; import { createRule, getConstrainedTypeAtLocation, @@ -43,14 +48,16 @@ const optionTesters = ( (type, checker): boolean => getTypeName(checker, type) === 'RegExp', ], ['Never', isTypeNeverType], - ] satisfies [string, OptionTester][] + ] as const satisfies [string, OptionTester][] ).map(([type, tester]) => ({ type, option: `allow${type}` as const, tester, })); type Options = [ - { [Type in (typeof optionTesters)[number]['option']]?: boolean }, + { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { + allow?: TypeOrValueSpecifier[]; + }, ]; type MessageId = 'invalidType'; @@ -84,15 +91,21 @@ export default createRule({ { type: 'object', additionalProperties: false, - properties: Object.fromEntries( - optionTesters.map(({ option, type }) => [ - option, - { - description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, - type: 'boolean', - }, - ]), - ), + properties: { + ...Object.fromEntries( + optionTesters.map(({ option, type }) => [ + option, + { + description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, + type: 'boolean', + }, + ]), + ), + allow: { + description: `Types to allow in template expressions.`, + ...typeOrValueSpecifiersSchema, + }, + }, }, ], }, @@ -103,11 +116,13 @@ export default createRule({ allowNullish: true, allowNumber: true, allowRegExp: true, + allow: [{ from: 'lib', name: ['Error', 'URL', 'URLSearchParams'] }], }, ], - create(context, [options]) { + create(context, [{ allow, ...options }]) { const services = getParserServices(context); - const checker = services.program.getTypeChecker(); + const { program } = services; + const checker = program.getTypeChecker(); const enabledOptionTesters = optionTesters.filter( ({ option }) => options[option], ); @@ -147,6 +162,7 @@ export default createRule({ return ( isTypeFlagSet(innerType, TypeFlags.StringLike) || + typeMatchesSomeSpecifier(innerType, allow, program) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType), ) diff --git a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts index 0c9cf63adf2b..a6060cd3f00e 100644 --- a/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts +++ b/packages/eslint-plugin/src/rules/use-unknown-in-catch-callback-variable.ts @@ -1,4 +1,3 @@ -import type { Scope } from '@typescript-eslint/scope-manager'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { ReportDescriptor } from '@typescript-eslint/utils/ts-eslint'; @@ -8,7 +7,7 @@ import type * as ts from 'typescript'; import { createRule, getParserServices, - getStaticValue, + getStaticMemberAccessValue, isParenlessArrowFunction, isRestParameterDeclaration, nullThrows, @@ -26,19 +25,6 @@ type MessageIds = const useUnknownMessageBase = 'Prefer the safe `: unknown` for a `{{method}}`{{append}} callback variable.'; -/** - * `x.memberName` => 'memberKey' - * - * `const mk = 'memberKey'; x[mk]` => 'memberKey' - * - * `const mk = 1234; x[mk]` => 1234 - */ -const getStaticMemberAccessKey = ( - { computed, property }: TSESTree.MemberExpression, - scope: Scope, -): { value: unknown } | null => - computed ? getStaticValue(property, scope) : { value: property.name }; - export default createRule<[], MessageIds>({ name: 'use-unknown-in-catch-callback-variable', meta: { @@ -242,9 +228,9 @@ export default createRule<[], MessageIds>({ return; } - const staticMemberAccessKey = getStaticMemberAccessKey( + const staticMemberAccessKey = getStaticMemberAccessValue( callee, - context.sourceCode.getScope(callee), + context, ); if (!staticMemberAccessKey) { return; @@ -259,7 +245,7 @@ export default createRule<[], MessageIds>({ append: string; argIndexToCheck: number; }[] - ).find(({ method }) => staticMemberAccessKey.value === method); + ).find(({ method }) => staticMemberAccessKey === method); if (!promiseMethodInfo) { return; } diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 58f34597653b..b13b5855231d 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -21,6 +21,7 @@ export * from './scopeUtils'; export * from './types'; export * from './isAssignee'; export * from './getFixOrSuggest'; +export * from './isArrayMethodCallWithPredicate'; // this is done for convenience - saves migrating all of the old rules export * from '@typescript-eslint/type-utils'; diff --git a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts new file mode 100644 index 000000000000..746e9003722c --- /dev/null +++ b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts @@ -0,0 +1,43 @@ +import { getConstrainedTypeAtLocation } from '@typescript-eslint/type-utils'; +import type { + ParserServicesWithTypeInformation, + TSESTree, +} from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; +import * as tsutils from 'ts-api-utils'; + +import { getStaticMemberAccessValue } from './misc'; + +const ARRAY_PREDICATE_FUNCTIONS = new Set([ + 'filter', + 'find', + 'findIndex', + 'findLast', + 'findLastIndex', + 'some', + 'every', +]); + +export function isArrayMethodCallWithPredicate( + context: RuleContext, + services: ParserServicesWithTypeInformation, + node: TSESTree.CallExpression, +): boolean { + if (node.callee.type !== AST_NODE_TYPES.MemberExpression) { + return false; + } + + const staticAccessValue = getStaticMemberAccessValue(node.callee, context); + + if (!staticAccessValue || !ARRAY_PREDICATE_FUNCTIONS.has(staticAccessValue)) { + return false; + } + + const checker = services.program.getTypeChecker(); + const type = getConstrainedTypeAtLocation(services, node.callee.object); + return tsutils + .unionTypeParts(type) + .flatMap(part => tsutils.intersectionTypeParts(part)) + .some(t => checker.isArrayType(t) || checker.isTupleType(t)); +} diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 64f23f2e81bc..aeed9f96d039 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -1,13 +1,13 @@ /** * @fileoverview Really small utility functions that didn't deserve their own files */ - import { requiresQuoting } from '@typescript-eslint/type-utils'; import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type { RuleContext } from '@typescript-eslint/utils/ts-eslint'; import * as ts from 'typescript'; -import { isParenthesized } from './astUtils'; +import { getStaticValue, isParenthesized } from './astUtils'; const DEFINITION_EXTENSIONS = [ ts.Extension.Dts, @@ -232,6 +232,46 @@ function isParenlessArrowFunction( ); } +type NodeWithKey = + | TSESTree.MemberExpression + | TSESTree.MethodDefinition + | TSESTree.Property + | TSESTree.PropertyDefinition + | TSESTree.TSAbstractMethodDefinition + | TSESTree.TSAbstractPropertyDefinition; +function getStaticMemberAccessValue( + node: NodeWithKey, + { sourceCode }: RuleContext, +): string | undefined { + const key = + node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; + if (!node.computed) { + return key.type === AST_NODE_TYPES.Literal + ? `${key.value}` + : (key as TSESTree.Identifier | TSESTree.PrivateIdentifier).name; + } + const value = getStaticValue(key, sourceCode.getScope(node))?.value as + | string + | number + | null + | undefined; + return value == null ? undefined : `${value}`; +} + +/** + * Answers whether the member expression looks like + * `x.memberName`, `x['memberName']`, + * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). + */ +const isStaticMemberAccessOfValue = ( + memberExpression: NodeWithKey, + context: RuleContext, + ...values: string[] +): boolean => + (values as (string | undefined)[]).includes( + getStaticMemberAccessValue(memberExpression, context), + ); + export { arrayGroupByToMap, arraysAreEqual, @@ -240,11 +280,13 @@ export { findFirstResult, formatWordList, getEnumNames, + getStaticMemberAccessValue, getNameFromIndexSignature, getNameFromMember, isDefinitionFile, isRestParameterDeclaration, isParenlessArrowFunction, + isStaticMemberAccessOfValue, MemberNameType, type RequireKeys, typeNodeRequiresParentheses, diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot index 17d39d27c875..8c9f4989d46b 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-misused-promises.shot @@ -14,6 +14,9 @@ if (promise) { const val = promise ? 123 : 456; ~~~~~~~ Expected non-Promise value in a boolean conditional. +[1, 2, 3].filter(() => promise); + ~~~~~~~~~~~~~ Expected a non-Promise value to be returned. + while (promise) { ~~~~~~~ Expected non-Promise value in a boolean conditional. // Do something @@ -34,6 +37,9 @@ if (await promise) { const val = (await promise) ? 123 : 456; +const returnVal = await promise; +[1, 2, 3].filter(() => returnVal); + while (await promise) { // Do something } diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot index 06f982288ebc..18fcf8c5ba31 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot @@ -91,3 +91,11 @@ const arg = ['foo', 'bar']; const msg1 = \`arg = \${arg}\`; " `; + +exports[`Validating rule docs restrict-template-expressions.mdx code examples ESLint output 11`] = ` +" + +const error = new Error(); +const msg1 = \`arg = \${error}\`; +" +`; diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index ee518640efed..fd8405d62833 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -153,6 +153,7 @@ describe('Validating rule docs', () => { const ignoredFiles = new Set([ 'README.md', 'TEMPLATE.md', + 'shared', // These rule docs were left behind on purpose for legacy reasons. See the // comments in the files for more information. 'ban-types.md', diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index fc7bbe927d41..bdfe10135349 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -100,6 +100,32 @@ ruleTester.run('no-deprecated', rule, { a('b'); `, + ` + class A { + a(value: 'b'): void; + /** @deprecated */ + a(value: 'c'): void; + } + declare const foo: A; + foo.a('b'); + `, + ` + type A = { + (value: 'b'): void; + /** @deprecated */ + (value: 'c'): void; + }; + declare const foo: A; + foo('b'); + `, + ` + declare const a: { + new (value: 'b'): void; + /** @deprecated */ + new (value: 'c'): void; + }; + new a('b'); + `, ` namespace assert { export function fail(message?: string | Error): never; @@ -157,6 +183,30 @@ ruleTester.run('no-deprecated', rule, { export type D = A.C | A.D; `, + ` + interface Props { + anchor: 'foo'; + } + declare const x: Props; + const { anchor = '' } = x; + `, + ` + interface Props { + anchor: 'foo'; + } + declare const x: { bar: Props }; + const { + bar: { anchor = '' }, + } = x; + `, + ` + interface Props { + anchor: 'foo'; + } + declare const x: [item: Props]; + const [{ anchor = 'bar' }] = x; + `, + 'function fn(/** @deprecated */ foo = 4) {}', ], invalid: [ { @@ -271,6 +321,38 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + /** @deprecated */ const a = { b: 1 }; + const { c = a } = {}; + `, + errors: [ + { + column: 21, + endColumn: 22, + line: 3, + endLine: 3, + data: { name: 'a' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + /** @deprecated */ const a = { b: 1 }; + const [c = a] = []; + `, + errors: [ + { + column: 20, + endColumn: 21, + line: 3, + endLine: 3, + data: { name: 'a' }, + messageId: 'deprecated', + }, + ], + }, { code: ` /** @deprecated */ const a = { b: 1 }; @@ -595,6 +677,26 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + declare const A: { + /** @deprecated */ + new (): string; + }; + + new A(); + `, + errors: [ + { + column: 13, + endColumn: 14, + line: 7, + endLine: 7, + data: { name: 'A' }, + messageId: 'deprecated', + }, + ], + }, { code: ` /** @deprecated */ @@ -681,6 +783,99 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + declare class A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a.b; + `, + errors: [ + { + column: 11, + endColumn: 12, + line: 9, + endLine: 9, + data: { name: 'b' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + declare class A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a.b(); + `, + only: false, + errors: [ + { + column: 11, + endColumn: 12, + line: 9, + endLine: 9, + data: { name: 'b' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + interface A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a.b(); + `, + only: false, + errors: [ + { + column: 11, + endColumn: 12, + line: 9, + endLine: 9, + data: { name: 'b' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + class A { + /** @deprecated */ + b(): string { + return ''; + } + } + + declare const a: A; + + a.b(); + `, + only: false, + errors: [ + { + column: 11, + endColumn: 12, + line: 11, + endLine: 11, + data: { name: 'b' }, + messageId: 'deprecated', + }, + ], + }, { code: ` declare class A { @@ -1099,6 +1294,27 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + type A = { + (value: 'b'): void; + /** @deprecated */ + (value: 'c'): void; + }; + declare const foo: A; + foo('c'); + `, + errors: [ + { + column: 9, + endColumn: 12, + line: 8, + endLine: 8, + data: { name: 'foo' }, + messageId: 'deprecated', + }, + ], + }, { code: ` function a( @@ -1286,5 +1502,87 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + interface Props { + /** @deprecated */ + anchor: 'foo'; + } + declare const x: Props; + const { anchor = '' } = x; + `, + errors: [ + { + column: 17, + endColumn: 23, + line: 7, + endLine: 7, + data: { name: 'anchor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + interface Props { + /** @deprecated */ + anchor: 'foo'; + } + declare const x: { bar: Props }; + const { + bar: { anchor = '' }, + } = x; + `, + errors: [ + { + column: 18, + endColumn: 24, + line: 8, + endLine: 8, + data: { name: 'anchor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + interface Props { + /** @deprecated */ + anchor: 'foo'; + } + declare const x: [item: Props]; + const [{ anchor = 'bar' }] = x; + `, + errors: [ + { + column: 18, + endColumn: 24, + line: 7, + endLine: 7, + data: { name: 'anchor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + interface Props { + /** @deprecated */ + foo: Props; + } + declare const x: Props; + const { foo = x } = x; + `, + errors: [ + { + column: 17, + endColumn: 20, + line: 7, + endLine: 7, + data: { name: 'foo' }, + messageId: 'deprecated', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index 072e1444fea0..0c6b33582825 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -1047,6 +1047,10 @@ interface MyInterface extends MyCall, MyIndex, MyConstruct, MyMethods { 'const notAFn3: boolean = true;', 'const notAFn4: { prop: 1 } = { prop: 1 };', 'const notAFn5: {} = {};', + ` +const array: number[] = [1, 2, 3]; +array.filter(a => a > 1); + `, ], invalid: [ @@ -2269,5 +2273,54 @@ interface MyInterface extends MyCall, MyIndex, MyConstruct, MyMethods { }, ], }, + { + code: ` +declare function isTruthy(value: unknown): Promise; +[0, 1, 2].filter(isTruthy); + `, + errors: [ + { + line: 3, + messageId: 'predicate', + }, + ], + }, + { + code: ` +const array: number[] = []; +array.every(() => Promise.resolve(true)); + `, + errors: [ + { + line: 3, + messageId: 'predicate', + }, + ], + }, + { + code: ` +const array: (string[] & { foo: 'bar' }) | (number[] & { bar: 'foo' }) = []; +array.every(() => Promise.resolve(true)); + `, + errors: [ + { + line: 3, + messageId: 'predicate', + }, + ], + }, + { + code: ` +const tuple: [number, number, number] = [1, 2, 3]; +tuple.find(() => Promise.resolve(false)); + `, + options: [{ checksConditionals: true }], + errors: [ + { + line: 3, + messageId: 'predicate', + }, + ], + }, ], }); 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 b8a9ab9981d9..9fbd0eb7ac6f 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts @@ -75,6 +75,10 @@ for (let i = 0; b1 && b2; i++) { } const t1 = b1 && b2 ? 'yes' : 'no'; for (;;) {} +switch (b1) { + case true: + default: +} `, ` declare function foo(): number | void; @@ -867,6 +871,15 @@ foo?.()?.toExponential(); type Foo = { [key: string]: () => number | undefined } | null; declare const foo: Foo; foo?.['bar']()?.toExponential(); + `, + ` +declare function foo(): void | { key: string }; +const bar = foo()?.key; + `, + ` +type fn = () => void; +declare function foo(): void | fn; +const bar = foo()?.(); `, { languageOptions: { parserOptions: optionsWithExactOptionalPropertyTypes }, @@ -901,6 +914,10 @@ for (let i = 0; b1 && b2; i++) { } const t1 = b1 && b2 ? 'yes' : 'no'; const t1 = b2 && b1 ? 'yes' : 'no'; +switch (b1) { + case true: + default: +} `, output: null, errors: [ @@ -913,6 +930,7 @@ const t1 = b2 && b1 ? 'yes' : 'no'; ruleError(12, 17, 'alwaysTruthy'), ruleError(15, 12, 'alwaysTruthy'), ruleError(16, 18, 'alwaysTruthy'), + ruleError(18, 8, 'literalBooleanExpression'), ], }, // Ensure that it's complaining about the right things @@ -1282,11 +1300,13 @@ function truthy() { function falsy() {} [1, 3, 5].filter(truthy); [1, 2, 3].find(falsy); +[1, 2, 3].findLastIndex(falsy); `, output: null, errors: [ ruleError(6, 18, 'alwaysTruthyFunc'), ruleError(7, 16, 'alwaysFalsyFunc'), + ruleError(8, 25, 'alwaysFalsyFunc'), ], }, // Supports generics @@ -1909,7 +1929,7 @@ if (!a) { } `, output: null, - errors: [ruleError(3, 6, 'alwaysTruthy')], + errors: [ruleError(3, 5, 'alwaysTruthy')], }, { code: ` @@ -1918,7 +1938,7 @@ if (!a) { } `, output: null, - errors: [ruleError(3, 6, 'alwaysFalsy')], + errors: [ruleError(3, 5, 'alwaysFalsy')], }, { code: ` @@ -1931,7 +1951,7 @@ if (!speech) { } `, output: null, - errors: [ruleError(7, 6, 'never')], + errors: [ruleError(7, 5, 'never')], }, { code: ` @@ -2277,6 +2297,14 @@ foo?.['bar']?.().toExponential(); }, ], }, + { + code: ` + const a = true; + if (!!a) { + } + `, + errors: [ruleError(3, 13, 'alwaysTruthy')], + }, // "branded" types unnecessaryConditionTest('"" & {}', 'alwaysFalsy'), diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts index ebc7a9ec181c..78b4be3022de 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts @@ -420,6 +420,15 @@ declare function sillyFoo( c: Constant, ): (data: T) => SillyFoo; `, + ` +const f = (setValue: (v: T) => void, getValue: () => NoInfer) => {}; + `, + ` +const f = ( + setValue: (v: T) => NoInfer, + getValue: (v: NoInfer) => NoInfer, +) => {}; + `, ], invalid: [ diff --git a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts index c60c2be4b0aa..e7d6872eaf85 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -344,6 +344,12 @@ ruleTester.run('restrict-template-expressions', rule, { } `, }, + // allow + { + options: [{ allow: [{ from: 'lib', name: 'Promise' }] }], + code: 'const msg = `arg = ${Promise.resolve()}`;', + }, + 'const msg = `arg = ${new Error()}`;', 'const msg = `arg = ${false}`;', 'const msg = `arg = ${null}`;', 'const msg = `arg = ${undefined}`;', @@ -422,6 +428,15 @@ ruleTester.run('restrict-template-expressions', rule, { ], options: [{ allowNullish: false, allowArray: true }], }, + { + code: 'const msg = `arg = ${Promise.resolve()}`;', + errors: [{ messageId: 'invalidType' }], + }, + { + code: 'const msg = `arg = ${new Error()}`;', + options: [{ allow: [] }], + errors: [{ messageId: 'invalidType' }], + }, { code: ` declare const arg: [number | undefined, string]; diff --git a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot index 1a4d828e764e..9a2625cd73c3 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot @@ -8,6 +8,101 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { + "allow": { + "description": "Types to allow in template expressions.", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["file"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "path": { + "type": "string" + } + }, + "required": ["from", "name"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["lib"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + } + }, + "required": ["from", "name"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["package"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "package": { + "type": "string" + } + }, + "required": ["from", "name", "package"], + "type": "object" + } + ] + }, + "type": "array" + }, "allowAny": { "description": "Whether to allow \`any\` typed values in template expressions.", "type": "boolean" @@ -46,6 +141,24 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos type Options = [ { + /** Types to allow in template expressions. */ + allow?: ( + | { + from: 'file'; + name: [string, ...string[]] | string; + path?: string; + } + | { + from: 'lib'; + name: [string, ...string[]] | string; + } + | { + from: 'package'; + name: [string, ...string[]] | string; + package: string; + } + | string + )[]; /** Whether to allow \`any\` typed values in template expressions. */ allowAny?: boolean; /** Whether to allow \`array\` typed values in template expressions. */ diff --git a/packages/integration-tests/fixtures/flat-config-types-@types__eslint-v8/package.json b/packages/integration-tests/fixtures/flat-config-types-@types__eslint-v8/package.json index 940026b1558f..19cb9e67084b 100644 --- a/packages/integration-tests/fixtures/flat-config-types-@types__eslint-v8/package.json +++ b/packages/integration-tests/fixtures/flat-config-types-@types__eslint-v8/package.json @@ -6,7 +6,7 @@ "@types/eslint__js": "latest", "@eslint/js": "latest", "@types/eslint": "^8", - "eslint": "latest", + "eslint": "9.9.1", "@stylistic/eslint-plugin": "2.3.0", "eslint-plugin-deprecation": "latest", "eslint-plugin-jest": "latest" diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 00f39c99c202..e4e3b58b7a44 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for parser to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for parser to align it with other projects, there were no code changes. diff --git a/packages/parser/package.json b/packages/parser/package.json index 09d3f75116f2..57c7d2decba8 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "8.5.0", + "version": "8.6.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -52,10 +52,10 @@ "eslint": "^8.57.0 || ^9.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index 0ed8caf454b0..ea8491e34be7 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index 0a1ccae645f8..508958d87e2f 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-schema-to-typescript-types", - "version": "8.5.0", + "version": "8.6.0", "private": true, "type": "commonjs", "exports": { @@ -34,8 +34,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/type-utils": "8.5.0", - "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "natural-compare": "^1.4.0", "prettier": "^3.2.5" }, diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 72b91401f855..80196faf86cf 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for rule-tester to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for rule-tester to align it with other projects, there were no code changes. diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index 40c5823ef6db..e22d75355525 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "8.5.0", + "version": "8.6.0", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -48,8 +48,8 @@ }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", @@ -62,7 +62,7 @@ "@jest/types": "29.6.3", "@types/json-stable-stringify-without-jsonify": "^1.0.2", "@types/lodash.merge": "4.6.9", - "@typescript-eslint/parser": "8.5.0", + "@typescript-eslint/parser": "8.6.0", "chai": "^4.4.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.1", diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 5e1dd0809342..ed90bc47b140 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for scope-manager to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for scope-manager to align it with other projects, there were no code changes. diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index 996d831e7a73..0369a15611f1 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "8.5.0", + "version": "8.6.0", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -46,13 +46,13 @@ "typecheck": "npx nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" }, "devDependencies": { "@jest/types": "29.6.3", "@types/glob": "*", - "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/typescript-estree": "8.6.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index f56ca48108d0..d0b9fc22e94f 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,23 @@ +## 8.6.0 (2024-09-16) + + +### 🚀 Features + +- add `allow` option for `restrict-template-expressions` + +- **type-utils:** isNullableType add Void logic + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Josh Goldberg ✨ +- Kim Sang Du +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for type-utils to align it with other projects, there were no code changes. diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index e5786590b6c9..b630c55ed655 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "8.5.0", + "version": "8.6.0", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -46,14 +46,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "devDependencies": { "@jest/types": "29.6.3", - "@typescript-eslint/parser": "8.5.0", + "@typescript-eslint/parser": "8.6.0", "ajv": "^6.12.6", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 422e83769208..d9c50f315b91 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -66,97 +66,100 @@ export type TypeOrValueSpecifier = | PackageSpecifier | string; -export const typeOrValueSpecifierSchema: JSONSchema4 = { - oneOf: [ - { - type: 'string', - }, - { - type: 'object', - additionalProperties: false, - properties: { - from: { - type: 'string', - enum: ['file'], - }, - name: { - oneOf: [ - { - type: 'string', - }, - { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { +export const typeOrValueSpecifiersSchema = { + type: 'array', + items: { + oneOf: [ + { + type: 'string', + }, + { + type: 'object', + additionalProperties: false, + properties: { + from: { + type: 'string', + enum: ['file'], + }, + name: { + oneOf: [ + { type: 'string', }, - }, - ], - }, - path: { - type: 'string', + { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + }, + }, + ], + }, + path: { + type: 'string', + }, }, + required: ['from', 'name'], }, - required: ['from', 'name'], - }, - { - type: 'object', - additionalProperties: false, - properties: { - from: { - type: 'string', - enum: ['lib'], - }, - name: { - oneOf: [ - { - type: 'string', - }, - { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { + { + type: 'object', + additionalProperties: false, + properties: { + from: { + type: 'string', + enum: ['lib'], + }, + name: { + oneOf: [ + { type: 'string', }, - }, - ], + { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + }, + }, + ], + }, }, + required: ['from', 'name'], }, - required: ['from', 'name'], - }, - { - type: 'object', - additionalProperties: false, - properties: { - from: { - type: 'string', - enum: ['package'], - }, - name: { - oneOf: [ - { - type: 'string', - }, - { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { + { + type: 'object', + additionalProperties: false, + properties: { + from: { + type: 'string', + enum: ['package'], + }, + name: { + oneOf: [ + { type: 'string', }, - }, - ], - }, - package: { - type: 'string', + { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + }, + }, + ], + }, + package: { + type: 'string', + }, }, + required: ['from', 'name', 'package'], }, - required: ['from', 'name', 'package'], - }, - ], -}; + ], + }, +} as const satisfies JSONSchema4; export function typeMatchesSpecifier( type: ts.Type, @@ -191,3 +194,10 @@ export function typeMatchesSpecifier( ); } } + +export const typeMatchesSomeSpecifier = ( + type: ts.Type, + specifiers: TypeOrValueSpecifier[] = [], + program: ts.Program, +): boolean => + specifiers.some(specifier => typeMatchesSpecifier(type, specifier, program)); diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index c8f89ca2fd12..cc44a0f5f252 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -6,8 +6,8 @@ import * as ts from 'typescript'; import { getTypeOfPropertyOfType } from './propertyTypes'; import type { TypeOrValueSpecifier } from './TypeOrValueSpecifier'; import { - typeMatchesSpecifier, - typeOrValueSpecifierSchema, + typeMatchesSomeSpecifier, + typeOrValueSpecifiersSchema, } from './TypeOrValueSpecifier'; const enum Readonlyness { @@ -31,10 +31,7 @@ export const readonlynessOptionsSchema = { treatMethodsAsReadonly: { type: 'boolean', }, - allow: { - type: 'array', - items: typeOrValueSpecifierSchema, - }, + allow: typeOrValueSpecifiersSchema, }, } satisfies JSONSchema4; @@ -232,11 +229,7 @@ function isTypeReadonlyRecurser( const checker = program.getTypeChecker(); seenTypes.add(type); - if ( - options.allow?.some(specifier => - typeMatchesSpecifier(type, specifier, program), - ) - ) { + if (typeMatchesSomeSpecifier(type, options.allow, program)) { return Readonlyness.Readonly; } diff --git a/packages/type-utils/src/predicates.ts b/packages/type-utils/src/predicates.ts index 8927f63a80d9..5c0680c8f7d5 100644 --- a/packages/type-utils/src/predicates.ts +++ b/packages/type-utils/src/predicates.ts @@ -15,7 +15,8 @@ export function isNullableType(type: ts.Type): boolean { ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Null | - ts.TypeFlags.Undefined, + ts.TypeFlags.Undefined | + ts.TypeFlags.Void, ); } diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index fd676a289939..b38b84cbb867 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -4,23 +4,20 @@ import { parseForESLint } from '@typescript-eslint/parser'; import type { TSESTree } from '@typescript-eslint/utils'; import Ajv from 'ajv'; -import type { TypeOrValueSpecifier } from '../src/TypeOrValueSpecifier'; -import { - typeMatchesSpecifier, - typeOrValueSpecifierSchema, -} from '../src/TypeOrValueSpecifier'; +import type { TypeOrValueSpecifier } from '../src'; +import { typeMatchesSpecifier, typeOrValueSpecifiersSchema } from '../src'; describe('TypeOrValueSpecifier', () => { describe('Schema', () => { const ajv = new Ajv(); - const validate = ajv.compile(typeOrValueSpecifierSchema); + const validate = ajv.compile(typeOrValueSpecifiersSchema); - function runTestPositive(data: unknown): void { - expect(validate(data)).toBe(true); + function runTestPositive(typeOrValueSpecifier: unknown): void { + expect(validate([typeOrValueSpecifier])).toBe(true); } - function runTestNegative(data: unknown): void { - expect(validate(data)).toBe(false); + function runTestNegative(typeOrValueSpecifier: unknown): void { + expect(validate([typeOrValueSpecifier])).toBe(false); } it.each([['MyType'], ['myValue'], ['any'], ['void'], ['never']])( @@ -448,7 +445,7 @@ describe('TypeOrValueSpecifier', () => { 'import type {Node as TsNode} from "typescript"; type Test = TsNode;', { from: 'package', name: 'TsNode', package: 'typescript' }, ], - ])("doesn't match a mismatched lib specifier: %s", runTestNegative); + ])("doesn't match a mismatched package specifier: %s", runTestNegative); it.each<[string, TypeOrValueSpecifier]>([ [ diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 81990c940257..b4976ba45fa2 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,21 @@ +## 8.6.0 (2024-09-16) + + +### 🚀 Features + +- **typescript-estree:** disable plugin loading by default in project service + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Josh Goldberg ✨ +- Kim Sang Du +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) diff --git a/packages/types/package.json b/packages/types/package.json index 1c648f812298..786b7b8afd4d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "8.5.0", + "version": "8.6.0", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index b7a856f528c0..e6f3b404c96f 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -54,6 +54,11 @@ interface ProjectServiceOptions { */ defaultProject?: string; + /** + * Whether to allow TypeScript plugins as configured in the TSConfig. + */ + loadTypeScriptPlugins?: boolean; + /** * The maximum number of files {@link allowDefaultProject} may match. * Each file match slows down linting, so if you do need to use this, please diff --git a/packages/typescript-eslint/CHANGELOG.md b/packages/typescript-eslint/CHANGELOG.md index 85a945813be0..946c37edb01b 100644 --- a/packages/typescript-eslint/CHANGELOG.md +++ b/packages/typescript-eslint/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. diff --git a/packages/typescript-eslint/package.json b/packages/typescript-eslint/package.json index 45a2c70c3c61..9628438305e3 100644 --- a/packages/typescript-eslint/package.json +++ b/packages/typescript-eslint/package.json @@ -1,6 +1,6 @@ { "name": "typescript-eslint", - "version": "8.5.0", + "version": "8.6.0", "description": "Tooling which enables you to use TypeScript with ESLint", "files": [ "dist", @@ -52,9 +52,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.5.0", - "@typescript-eslint/parser": "8.5.0", - "@typescript-eslint/utils": "8.5.0" + "@typescript-eslint/eslint-plugin": "8.6.0", + "@typescript-eslint/parser": "8.6.0", + "@typescript-eslint/utils": "8.6.0" }, "devDependencies": { "@jest/types": "29.6.3", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index d8afcf525694..449cd2176f73 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,26 @@ +## 8.6.0 (2024-09-16) + + +### 🚀 Features + +- **typescript-estree:** disable plugin loading by default in project service + + +### 🩹 Fixes + +- **typescript-estree:** don't throw on missing tsconfig.json by default in project service + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Josh Goldberg ✨ +- Kim Sang Du +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index d6cc213c9a4c..7344425bbc58 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "8.5.0", + "version": "8.6.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -54,8 +54,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", diff --git a/packages/typescript-estree/src/create-program/createProjectService.ts b/packages/typescript-estree/src/create-program/createProjectService.ts index a9634985242c..89158d15e068 100644 --- a/packages/typescript-estree/src/create-program/createProjectService.ts +++ b/packages/typescript-estree/src/create-program/createProjectService.ts @@ -42,9 +42,10 @@ export function createProjectService( jsDocParsingMode: ts.JSDocParsingMode | undefined, tsconfigRootDir: string | undefined, ): ProjectServiceSettings { + const optionsRawObject = typeof optionsRaw === 'object' ? optionsRaw : {}; const options = { defaultProject: 'tsconfig.json', - ...(typeof optionsRaw === 'object' && optionsRaw), + ...optionsRawObject, }; validateDefaultProjectForFilesGlob(options.allowDefaultProject); @@ -65,6 +66,18 @@ export function createProjectService( setTimeout, watchDirectory: createStubFileWatcher, watchFile: createStubFileWatcher, + + // We stop loading any TypeScript plugins by default, to prevent them from attaching disk watchers + // See https://github.com/typescript-eslint/typescript-eslint/issues/9905 + ...(!options.loadTypeScriptPlugins && { + require: () => ({ + module: undefined, + error: { + message: + 'TypeScript plugins are not required when using parserOptions.projectService.', + }, + }), + }), }; const logger: ts.server.Logger = { @@ -126,7 +139,7 @@ export function createProjectService( }); log('Enabling default project: %s', options.defaultProject); - let configFile: ts.ParsedCommandLine; + let configFile: ts.ParsedCommandLine | undefined; try { configFile = getParsedConfigFile( @@ -135,18 +148,22 @@ export function createProjectService( tsconfigRootDir, ); } catch (error) { - throw new Error( - `Could not read project service default project '${options.defaultProject}': ${(error as Error).message}`, - ); + if (optionsRawObject.defaultProject) { + throw new Error( + `Could not read project service default project '${options.defaultProject}': ${(error as Error).message}`, + ); + } } - service.setCompilerOptionsForInferredProjects( - // NOTE: The inferred projects API is not intended for source files when a tsconfig - // exists. There is no API that generates an InferredProjectCompilerOptions suggesting - // it is meant for hard coded options passed in. Hard asserting as a work around. - // See https://github.com/microsoft/TypeScript/blob/27bcd4cb5a98bce46c9cdd749752703ead021a4b/src/server/protocol.ts#L1904 - configFile.options as ts.server.protocol.InferredProjectCompilerOptions, - ); + if (configFile) { + service.setCompilerOptionsForInferredProjects( + // NOTE: The inferred projects API is not intended for source files when a tsconfig + // exists. There is no API that generates an InferredProjectCompilerOptions suggesting + // it is meant for hard coded options passed in. Hard asserting as a work around. + // See https://github.com/microsoft/TypeScript/blob/27bcd4cb5a98bce46c9cdd749752703ead021a4b/src/server/protocol.ts#L1904 + configFile.options as ts.server.protocol.InferredProjectCompilerOptions, + ); + } return { allowDefaultProject: options.allowDefaultProject, diff --git a/packages/typescript-estree/tests/lib/createProjectService.test.ts b/packages/typescript-estree/tests/lib/createProjectService.test.ts index d175e3de0b1e..d75b8b5d9a8d 100644 --- a/packages/typescript-estree/tests/lib/createProjectService.test.ts +++ b/packages/typescript-estree/tests/lib/createProjectService.test.ts @@ -14,13 +14,15 @@ jest.mock('typescript/lib/tsserverlibrary', () => ({ server: { ...jest.requireActual('typescript/lib/tsserverlibrary').server, ProjectService: class { - logger: ts.server.Logger; eventHandler: ts.server.ProjectServiceEventHandler | undefined; + host: ts.server.ServerHost; + logger: ts.server.Logger; constructor( ...args: ConstructorParameters ) { - this.logger = args[0].logger; this.eventHandler = args[0].eventHandler; + this.host = args[0].host; + this.logger = args[0].logger; if (this.eventHandler) { this.eventHandler({ eventName: 'projectLoadingStart', @@ -65,7 +67,7 @@ describe('createProjectService', () => { expect(settings.allowDefaultProject).toBeUndefined(); }); - it('throws an error with a local path when options.defaultProject is not provided and getParsedConfigFile throws a diagnostic error', () => { + it('does not throw an error when options.defaultProject is not provided and getParsedConfigFile throws a diagnostic error', () => { mockGetParsedConfigFile.mockImplementation(() => { throw new Error('tsconfig.json(1,1): error TS1234: Oh no!'); }); @@ -78,9 +80,7 @@ describe('createProjectService', () => { undefined, undefined, ), - ).toThrow( - /Could not read project service default project 'tsconfig.json': .+ error TS1234: Oh no!/, - ); + ).not.toThrow(); }); it('throws an error with a relative path when options.defaultProject is set to a relative path and getParsedConfigFile throws a diagnostic error', () => { @@ -132,6 +132,7 @@ describe('createProjectService', () => { createProjectService( { allowDefaultProject: ['file.js'], + defaultProject: 'tsconfig.json', }, undefined, undefined, @@ -311,6 +312,34 @@ describe('createProjectService', () => { expect(process.stderr.write).toHaveBeenCalledTimes(0); }); + it('provides a stub require to the host system when loadTypeScriptPlugins is falsy', () => { + const { service } = createProjectService({}, undefined, undefined); + + const required = service.host.require(); + + expect(required).toEqual({ + module: undefined, + error: { + message: + 'TypeScript plugins are not required when using parserOptions.projectService.', + }, + }); + }); + + it('does not provide a require to the host system when loadTypeScriptPlugins is truthy', () => { + const { service } = createProjectService( + { + loadTypeScriptPlugins: true, + }, + undefined, + undefined, + ); + + expect(service.host.require).toBe( + jest.requireActual('typescript/lib/tsserverlibrary').sys.require, + ); + }); + it('sets a host configuration', () => { const { service } = createProjectService( { diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index d5e25793e8a5..01c5c0820c32 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,21 @@ +## 8.6.0 (2024-09-16) + + +### 🩹 Fixes + +- **eslint-plugin:** [no-deprecated] report on deprecated properties with function-like types + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Josh Goldberg ✨ +- Kim Sang Du +- YeonJuan + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for utils to align it with other projects, there were no code changes. diff --git a/packages/utils/package.json b/packages/utils/package.json index 98ed8e557597..10bd4aec62ba 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "8.5.0", + "version": "8.6.0", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -64,9 +64,9 @@ }, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0" + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" diff --git a/packages/utils/tests/ts-eslint/ESLint.test.ts b/packages/utils/tests/ts-eslint/ESLint.test.ts index f252b7eba28a..8278a0e0568b 100644 --- a/packages/utils/tests/ts-eslint/ESLint.test.ts +++ b/packages/utils/tests/ts-eslint/ESLint.test.ts @@ -13,6 +13,7 @@ describe('ESLint', () => { expect(eslint).toBeInstanceOf(FlatESLint); }); it('legacy', () => { + // eslint-disable-next-line @typescript-eslint/no-deprecated const eslint = new ESLint.LegacyESLint({ // accepts legacy configs baseConfig: { diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 57216e8a8c9c..74b233c65975 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.6.0 (2024-09-16) + +This was a version bump only for visitor-keys to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.5.0 (2024-09-09) This was a version bump only for visitor-keys to align it with other projects, there were no code changes. diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 8dce253f06b9..0fda5d287905 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "8.5.0", + "version": "8.6.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -47,7 +47,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/types": "8.6.0", "eslint-visitor-keys": "^3.4.3" }, "devDependencies": { diff --git a/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md b/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md index 7a17f4a4b90a..d1ccf1faecf8 100644 --- a/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md +++ b/packages/website/blog/2024-07-31-announcing-typescript-eslint-v8.md @@ -14,7 +14,7 @@ We've been working on a set of breaking changes and general features that we're And now, we're excited to say that typescript-eslint v8 is released as stable! 🎉 We'd previously blogged about v8 in [Announcing typescript-eslint v8 Beta](./2024-05-27-announcing-typescript-eslint-v8-beta.mdx). -This blog post contains much of the same information as that one. +This blog post contains much of the same information as that one, and includes the list of steps you'll need to take to upgrade. diff --git a/packages/website/data/sponsors.json b/packages/website/data/sponsors.json index 4d48b83e4688..c1ad55284953 100644 --- a/packages/website/data/sponsors.json +++ b/packages/website/data/sponsors.json @@ -239,7 +239,7 @@ }, { "id": "0+X", - "image": "https://images.opencollective.com/0-x/7239aff/logo.png", + "image": "https://images.opencollective.com/0-x/707287f/logo.png", "name": "0+X", "totalDonations": 21000, "website": "https://www.0x.se" diff --git a/packages/website/package.json b/packages/website/package.json index ee01833b0077..55d3897cfbff 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -60,8 +60,9 @@ "cross-fetch": "*", "history": "^4.9.0", "make-dir": "*", + "mdast-util-from-markdown": "^2.0.1", "mdast-util-mdx": "^3.0.0", - "monaco-editor": "~0.50.0", + "monaco-editor": "~0.51.0", "raw-loader": "^4.0.2", "rimraf": "*", "stylelint": "^16.3.1", diff --git a/packages/website/plugins/generated-rule-docs/RuleDocsPage.ts b/packages/website/plugins/generated-rule-docs/RuleDocsPage.ts index e540c014b133..3c44aae99a0c 100644 --- a/packages/website/plugins/generated-rule-docs/RuleDocsPage.ts +++ b/packages/website/plugins/generated-rule-docs/RuleDocsPage.ts @@ -1,4 +1,5 @@ import type { ESLintPluginRuleModule } from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/rules'; +import { fromMarkdown } from 'mdast-util-from-markdown'; import type * as unist from 'unist'; import type { VFileWithStem } from '../utils/rules'; @@ -57,9 +58,15 @@ export class RuleDocsPage { spliceChildren( start: number, deleteCount: number, - ...items: unist.Node[] + ...items: (string | unist.Node)[] ): void { - this.#children.splice(start, deleteCount, ...items); + this.#children.splice( + start, + deleteCount, + ...items.map(item => + typeof item === 'string' ? fromMarkdown(item) : item, + ), + ); this.#headingIndices = this.#recreateHeadingIndices(); } diff --git a/packages/website/plugins/generated-rule-docs/index.ts b/packages/website/plugins/generated-rule-docs/index.ts index f29c0371748b..85c425cb38e4 100644 --- a/packages/website/plugins/generated-rule-docs/index.ts +++ b/packages/website/plugins/generated-rule-docs/index.ts @@ -6,7 +6,6 @@ import { isESLintPluginRuleModule, isVFileWithStem } from '../utils/rules'; import { addESLintHashToCodeBlocksMeta } from './addESLintHashToCodeBlocksMeta'; import { createRuleDocsPage } from './createRuleDocsPage'; import { insertBaseRuleReferences } from './insertions/insertBaseRuleReferences'; -import { insertFormattingNotice } from './insertions/insertFormattingNotice'; import { insertNewRuleReferences } from './insertions/insertNewRuleReferences'; import { insertResources } from './insertions/insertResources'; import { insertRuleDescription } from './insertions/insertRuleDescription'; @@ -28,7 +27,6 @@ export const generatedRuleDocs: Plugin = () => { removeSourceCodeNotice(page); insertRuleDescription(page); - insertFormattingNotice(page); const eslintrc = rule.meta.docs.extendsBaseRule ? insertBaseRuleReferences(page) diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts b/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts index f88a17a85bc6..b5c1f5604f7e 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertBaseRuleReferences.ts @@ -10,33 +10,11 @@ export function insertBaseRuleReferences(page: RuleDocsPage): string { ? page.rule.meta.docs.extendsBaseRule : page.file.stem; - page.spliceChildren(page.headingIndices.options + 1, 0, { - children: [ - { - value: 'See ', - type: 'text', - }, - { - children: [ - { - type: 'inlineCode', - value: `eslint/${extendsBaseRuleName}`, - }, - { - type: 'text', - value: ' options', - }, - ], - type: 'link', - url: `https://eslint.org/docs/rules/${extendsBaseRuleName}#options`, - }, - { - type: 'text', - value: '.', - }, - ], - type: 'paragraph', - } as mdast.Paragraph); + page.spliceChildren( + page.headingIndices.options + 1, + 0, + `See [\`eslint/${extendsBaseRuleName}\`'s options](https://eslint.org/docs/rules/${extendsBaseRuleName}#options).`, + ); const eslintrc = getEslintrcString( extendsBaseRuleName, diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertFormattingNotice.ts b/packages/website/plugins/generated-rule-docs/insertions/insertFormattingNotice.ts deleted file mode 100644 index a4ff96e120e8..000000000000 --- a/packages/website/plugins/generated-rule-docs/insertions/insertFormattingNotice.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { MdxJsxFlowElement } from 'mdast-util-mdx'; - -import type { RuleDocsPage } from '../RuleDocsPage'; - -export function insertFormattingNotice(page: RuleDocsPage): void { - const replacement = page.rule.meta.replacedBy?.find(e => - e.startsWith('@stylistic/'), - ); - if (!replacement) { - return; - } - - const url = - replacement && - `https://eslint.style/rules/${replacement.replace('@stylistic/', '')}`; - - page.spliceChildren(0, 0, { - children: [ - { - type: 'paragraph', - children: [ - { - type: 'text', - value: 'Formatting rules now live in ', - }, - { - type: 'link', - title: null, - url: 'https://eslint.style', - children: [ - { - type: 'text', - value: 'eslint-stylistic', - }, - ], - }, - { - type: 'text', - value: '. ', - }, - ...(url - ? [ - { - type: 'link', - title: null, - url, - children: [ - { - type: 'text', - value: replacement, - }, - ], - }, - { - type: 'text', - value: ' is the replacement for this rule. ', - }, - ] - : []), - { - type: 'break', - }, - { - type: 'text', - value: 'See ', - }, - { - type: 'link', - title: null, - url: '/blog/deprecating-formatting-rules', - children: [ - { - type: 'text', - value: 'Deprecating Formatting Rules', - }, - ], - }, - { - type: 'text', - value: ' for more information.', - }, - ], - }, - ], - attributes: [ - { - type: 'mdxJsxAttribute', - name: 'title', - value: 'Deprecated', - }, - { - type: 'mdxJsxAttribute', - name: 'type', - value: 'danger', - }, - ], - name: 'Admonition', - type: 'mdxJsxFlowElement', - } as MdxJsxFlowElement); -} diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts index 5b601124ef35..08cca0d8e0d3 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertNewRuleReferences.ts @@ -1,6 +1,7 @@ import { EOL } from 'node:os'; import * as path from 'node:path'; +import type { ESLintPluginDocs } from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/rules'; import { compile } from '@typescript-eslint/rule-schema-to-typescript-types'; import type * as mdast from 'mdast'; import type { MdxJsxFlowElement } from 'mdast-util-mdx'; @@ -62,12 +63,9 @@ export async function insertNewRuleReferences( page.spliceChildren( firstH2Index, 0, - { - lang: 'js', - type: 'code', - meta: 'title=".eslintrc.cjs"', - value: `module.exports = ${eslintrc};`, - } as mdast.Code, + `\`\`\`js title=".eslintrc.cjs" +module.exports = ${eslintrc}; +\`\`\``, { attributes: [ { @@ -97,46 +95,18 @@ export async function insertNewRuleReferences( : Object.keys(page.rule.meta.schema).length === 0; if (hasNoConfig) { - page.spliceChildren(page.headingIndices.options + 1, 0, { - children: [ - { - type: 'text', - value: 'This rule is not configurable.', - }, - ], - type: 'paragraph', - } as mdast.Paragraph); + page.spliceChildren( + page.headingIndices.options + 1, + 0, + 'This rule is not configurable.', + ); } else if (!COMPLICATED_RULE_OPTIONS.has(page.file.stem)) { page.spliceChildren( page.headingIndices.options + 1, 0, - { - children: - typeof page.rule.meta.docs.recommended === 'object' - ? [ - { - type: 'text', - value: - 'This rule accepts the following options, and has more strict settings in the ', - } as mdast.Text, - ...linkToConfigs( - page.rule.meta.docs.requiresTypeChecking - ? ['strict', 'strict-type-checked'] - : ['strict'], - ), - { - type: 'text', - value: ` config${page.rule.meta.docs.requiresTypeChecking ? 's' : ''}.`, - } as mdast.Text, - ] - : [ - { - type: 'text', - value: 'This rule accepts the following options:', - } as mdast.Text, - ], - type: 'paragraph', - } as mdast.Paragraph, + typeof page.rule.meta.docs.recommended === 'object' + ? linkToConfigsForObject(page.rule.meta.docs) + : 'This rule accepts the following options:', { lang: 'ts', type: 'code', @@ -156,30 +126,14 @@ export async function insertNewRuleReferences( return eslintrc; } -function linkToConfigs(configs: string[]): mdast.Node[] { - const links = configs.map( - (config): mdast.Link => ({ - children: [ - { - type: 'inlineCode', - value: config, - } as mdast.InlineCode, - ], - type: 'link', - url: `/users/configs#${config}`, - }), - ); - - return links.length === 1 - ? links - : [ - links[0], - { - type: 'text', - value: ' and ', - } as mdast.Text, - links[1], - ]; +function linkToConfigsForObject(docs: ESLintPluginDocs): string { + return [ + 'This rule accepts the following options, and has more strict settings in the', + (docs.requiresTypeChecking ? ['strict', 'strict-type-checked'] : ['strict']) + .map(config => `[${config}](/users/configs#${config})`) + .join(' and '), + `config${docs.requiresTypeChecking ? 's' : ''}.`, + ].join(' '); } function getRuleDefaultOptions(page: RuleDocsPage): string { diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertResources.ts b/packages/website/plugins/generated-rule-docs/insertions/insertResources.ts index 50efedf8a71c..e6b6eb88f096 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertResources.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertResources.ts @@ -1,4 +1,3 @@ -import type * as mdast from 'mdast'; import type { MdxJsxFlowElement } from 'mdast-util-mdx'; import { getUrlForRuleTest, sourceUrlPrefix } from '../../utils/rules'; @@ -9,61 +8,11 @@ export function insertResources(page: RuleDocsPage): void { page.spliceChildren( page.children.length, 0, - { - children: [ - { - type: 'text', - value: 'Resources', - }, - ], - depth: 2, - type: 'heading', - } as mdast.Heading, - { - children: [ - { - children: [ - { - children: [ - { - type: 'link', - url: `${sourceUrlPrefix}src/rules/${page.file.stem}.ts`, - children: [ - { - type: 'text', - value: 'Rule source', - }, - ], - }, - ], - type: 'paragraph', - }, - ], - type: 'listItem', - }, - { - children: [ - { - children: [ - { - type: 'link', - url: getUrlForRuleTest(page.file.stem), - children: [ - { - type: 'text', - value: 'Test source', - }, - ], - }, - ], - type: 'paragraph', - }, - ], - type: 'listItem', - }, - ], - type: 'list', - } as mdast.List, + `## Resources + +- [Rule source](${sourceUrlPrefix}src/rules/${page.file.stem}.ts) +- [Test source](${getUrlForRuleTest(page.file.stem)}) + `, ); // Also add a notice about coming from ESLint core for extension rules diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertRuleDescription.ts b/packages/website/plugins/generated-rule-docs/insertions/insertRuleDescription.ts index 86d0be107d30..832c35b60264 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertRuleDescription.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertRuleDescription.ts @@ -1,36 +1,17 @@ -import type * as mdast from 'mdast'; import type { MdxJsxFlowElement } from 'mdast-util-mdx'; import type { RuleDocsPage } from '../RuleDocsPage'; export function insertRuleDescription(page: RuleDocsPage): void { - page.spliceChildren( - 0, - 0, - { - children: [ - { - children: page.rule.meta.docs.description - .split(/`(.+?)`/) - .map((value, index, array) => ({ - type: index % 2 === 0 ? 'text' : 'inlineCode', - value: index === array.length - 1 ? `${value}.` : value, - })), - type: 'paragraph', - }, - ], - type: 'blockquote', - } as mdast.Blockquote, - { - attributes: [ - { - type: 'mdxJsxAttribute', - name: 'name', - value: page.file.stem, - }, - ], - name: 'RuleAttributes', - type: 'mdxJsxFlowElement', - } as MdxJsxFlowElement, - ); + page.spliceChildren(0, 0, `> ${page.rule.meta.docs.description}.`, { + attributes: [ + { + type: 'mdxJsxAttribute', + name: 'name', + value: page.file.stem, + }, + ], + name: 'RuleAttributes', + type: 'mdxJsxFlowElement', + } as MdxJsxFlowElement); } diff --git a/packages/website/plugins/generated-rule-docs/insertions/insertWhenNotToUseIt.ts b/packages/website/plugins/generated-rule-docs/insertions/insertWhenNotToUseIt.ts index 7365438bde60..97f03abe2061 100644 --- a/packages/website/plugins/generated-rule-docs/insertions/insertWhenNotToUseIt.ts +++ b/packages/website/plugins/generated-rule-docs/insertions/insertWhenNotToUseIt.ts @@ -1,5 +1,3 @@ -import type * as mdast from 'mdast'; - import { nodeIsHeading } from '../../utils/nodes'; import type { RuleDocsPage } from '../RuleDocsPage'; @@ -23,48 +21,8 @@ export function insertWhenNotToUseIt(page: RuleDocsPage): void { page.spliceChildren( nextHeadingIndex === -1 ? page.children.length : nextHeadingIndex - 1, 0, - { - children: [ - ...(hasExistingText ? [{ type: 'thematicBreak' }] : []), - { - type: 'text', - value: - 'Type checked lint rules are more powerful than traditional lint rules, but also require configuring ', - }, - { - type: 'link', - title: null, - url: `/getting-started/typed-linting`, - children: [ - { - type: 'text', - value: 'type checked linting', - }, - ], - }, - { - type: 'text', - value: '. See ', - }, - { - type: 'link', - title: null, - url: `/troubleshooting/typed-linting/performance`, - children: [ - { - type: 'text', - value: - 'Troubleshooting > Linting with Type Information > Performance', - }, - ], - }, - { - type: 'text', - value: - ' if you experience performance degredations after enabling type checked rules.', - }, - ], - type: 'paragraph', - } as mdast.Paragraph, + ...(hasExistingText ? ['---'] : []), + 'Type checked lint rules are more powerful than traditional lint rules, but also require configuring [type checked linting](/getting-started/typed-linting).', + 'See [Troubleshooting > Linting with Type Information > Performance](/troubleshooting/typed-linting/performance) if you experience performance degredations after enabling type checked rules.', ); } diff --git a/yarn.lock b/yarn.lock index a487669536a2..91ed28cd5251 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4450,6 +4450,13 @@ __metadata: languageName: node linkType: hard +"@rtsao/scc@npm:^1.1.0": + version: 1.1.0 + resolution: "@rtsao/scc@npm:1.1.0" + checksum: 17d04adf404e04c1e61391ed97bca5117d4c2767a76ae3e879390d6dec7b317fcae68afbf9e98badee075d0b64fa60f287729c4942021b4d19cd01db77385c01 + languageName: node + linkType: hard + "@rushstack/node-core-library@npm:5.4.1": version: 5.4.1 resolution: "@rushstack/node-core-library@npm:5.4.1" @@ -5631,7 +5638,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/eslint-plugin@8.5.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": +"@typescript-eslint/eslint-plugin@8.6.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -5640,12 +5647,12 @@ __metadata: "@types/marked": ^5.0.2 "@types/mdast": ^4.0.3 "@types/natural-compare": "*" - "@typescript-eslint/rule-schema-to-typescript-types": 8.5.0 - "@typescript-eslint/rule-tester": 8.5.0 - "@typescript-eslint/scope-manager": 8.5.0 - "@typescript-eslint/type-utils": 8.5.0 - "@typescript-eslint/utils": 8.5.0 - "@typescript-eslint/visitor-keys": 8.5.0 + "@typescript-eslint/rule-schema-to-typescript-types": 8.6.0 + "@typescript-eslint/rule-tester": 8.6.0 + "@typescript-eslint/scope-manager": 8.6.0 + "@typescript-eslint/type-utils": 8.6.0 + "@typescript-eslint/utils": 8.6.0 + "@typescript-eslint/visitor-keys": 8.6.0 ajv: ^6.12.6 cross-env: ^7.0.3 cross-fetch: "*" @@ -5689,16 +5696,16 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/parser@8.5.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": +"@typescript-eslint/parser@8.6.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/scope-manager": 8.5.0 - "@typescript-eslint/types": 8.5.0 - "@typescript-eslint/typescript-estree": 8.5.0 - "@typescript-eslint/visitor-keys": 8.5.0 + "@typescript-eslint/scope-manager": 8.6.0 + "@typescript-eslint/types": 8.6.0 + "@typescript-eslint/typescript-estree": 8.6.0 + "@typescript-eslint/visitor-keys": 8.6.0 debug: ^4.3.4 downlevel-dts: "*" glob: "*" @@ -5714,28 +5721,28 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-schema-to-typescript-types@8.5.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": +"@typescript-eslint/rule-schema-to-typescript-types@8.6.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/type-utils": 8.5.0 - "@typescript-eslint/utils": 8.5.0 + "@typescript-eslint/type-utils": 8.6.0 + "@typescript-eslint/utils": 8.6.0 natural-compare: ^1.4.0 prettier: ^3.2.5 languageName: unknown linkType: soft -"@typescript-eslint/rule-tester@8.5.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": +"@typescript-eslint/rule-tester@8.6.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-tester@workspace:packages/rule-tester" dependencies: "@jest/types": 29.6.3 "@types/json-stable-stringify-without-jsonify": ^1.0.2 "@types/lodash.merge": 4.6.9 - "@typescript-eslint/parser": 8.5.0 - "@typescript-eslint/typescript-estree": 8.5.0 - "@typescript-eslint/utils": 8.5.0 + "@typescript-eslint/parser": 8.6.0 + "@typescript-eslint/typescript-estree": 8.6.0 + "@typescript-eslint/utils": 8.6.0 ajv: ^6.12.6 chai: ^4.4.1 eslint-visitor-keys: ^4.0.0 @@ -5753,15 +5760,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/scope-manager@8.5.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": +"@typescript-eslint/scope-manager@8.6.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": version: 0.0.0-use.local resolution: "@typescript-eslint/scope-manager@workspace:packages/scope-manager" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/types": 8.5.0 - "@typescript-eslint/typescript-estree": 8.5.0 - "@typescript-eslint/visitor-keys": 8.5.0 + "@typescript-eslint/types": 8.6.0 + "@typescript-eslint/typescript-estree": 8.6.0 + "@typescript-eslint/visitor-keys": 8.6.0 glob: "*" jest-specific-snapshot: "*" make-dir: "*" @@ -5780,14 +5787,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@8.5.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": +"@typescript-eslint/type-utils@8.6.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/parser": 8.5.0 - "@typescript-eslint/typescript-estree": 8.5.0 - "@typescript-eslint/utils": 8.5.0 + "@typescript-eslint/parser": 8.6.0 + "@typescript-eslint/typescript-estree": 8.6.0 + "@typescript-eslint/utils": 8.6.0 ajv: ^6.12.6 debug: ^4.3.4 downlevel-dts: "*" @@ -5802,7 +5809,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@8.5.0, @typescript-eslint/types@^8.3.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@8.6.0, @typescript-eslint/types@^8.3.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -5903,13 +5910,13 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/typescript-estree@8.5.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": +"@typescript-eslint/typescript-estree@8.6.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/types": 8.5.0 - "@typescript-eslint/visitor-keys": 8.5.0 + "@typescript-eslint/types": 8.6.0 + "@typescript-eslint/visitor-keys": 8.6.0 debug: ^4.3.4 fast-glob: ^3.3.2 glob: "*" @@ -5946,14 +5953,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@8.5.0, @typescript-eslint/utils@^8.3.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": +"@typescript-eslint/utils@8.6.0, @typescript-eslint/utils@^8.3.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@typescript-eslint/utils@workspace:packages/utils" dependencies: "@eslint-community/eslint-utils": ^4.4.0 - "@typescript-eslint/scope-manager": 8.5.0 - "@typescript-eslint/types": 8.5.0 - "@typescript-eslint/typescript-estree": 8.5.0 + "@typescript-eslint/scope-manager": 8.6.0 + "@typescript-eslint/types": 8.6.0 + "@typescript-eslint/typescript-estree": 8.6.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5 @@ -5982,13 +5989,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@8.5.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": +"@typescript-eslint/visitor-keys@8.6.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": version: 0.0.0-use.local resolution: "@typescript-eslint/visitor-keys@workspace:packages/visitor-keys" dependencies: "@jest/types": 29.6.3 "@types/eslint-visitor-keys": "*" - "@typescript-eslint/types": 8.5.0 + "@typescript-eslint/types": 8.6.0 downlevel-dts: "*" eslint-visitor-keys: ^3.4.3 jest: 29.7.0 @@ -6649,7 +6656,7 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.7, array-includes@npm:^3.1.8": +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": version: 3.1.8 resolution: "array-includes@npm:3.1.8" dependencies: @@ -6691,16 +6698,17 @@ __metadata: languageName: node linkType: hard -"array.prototype.findlastindex@npm:^1.2.3": - version: 1.2.3 - resolution: "array.prototype.findlastindex@npm:1.2.3" +"array.prototype.findlastindex@npm:^1.2.5": + version: 1.2.5 + resolution: "array.prototype.findlastindex@npm:1.2.5" dependencies: - call-bind: ^1.0.2 - define-properties: ^1.2.0 - es-abstract: ^1.22.1 - es-shim-unscopables: ^1.0.0 - get-intrinsic: ^1.2.1 - checksum: 31f35d7b370c84db56484618132041a9af401b338f51899c2e78ef7690fbba5909ee7ca3c59a7192085b328cc0c68c6fd1f6d1553db01a689a589ae510f3966e + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-abstract: ^1.23.2 + es-errors: ^1.3.0 + es-object-atoms: ^1.0.0 + es-shim-unscopables: ^1.0.2 + checksum: 2c81cff2a75deb95bf1ed89b6f5f2bfbfb882211e3b7cc59c3d6b87df774cd9d6b36949a8ae39ac476e092c1d4a4905f5ee11a86a456abb10f35f8211ae4e710 languageName: node linkType: hard @@ -6835,10 +6843,10 @@ __metadata: languageName: node linkType: hard -"axe-core@npm:^4.9.1": - version: 4.9.1 - resolution: "axe-core@npm:4.9.1" - checksum: 41d9227871781f96c2952e2a777fca73624959dd0e98864f6d82806a77602f82b4fc490852082a7e524d8cd864e50d8b4d9931819b4a150112981d8c932110c5 +"axe-core@npm:^4.10.0": + version: 4.10.0 + resolution: "axe-core@npm:4.10.0" + checksum: 7eca827fd8d98d7e4b561df65437be608155c613d8f262ae9e4a6ade02c156c7362dcbc3f71b4b526edce686f7c686280236bcff1d6725e2ef8327def72a8c41 languageName: node linkType: hard @@ -6853,12 +6861,10 @@ __metadata: languageName: node linkType: hard -"axobject-query@npm:~3.1.1": - version: 3.1.1 - resolution: "axobject-query@npm:3.1.1" - dependencies: - deep-equal: ^2.0.5 - checksum: c12a5da10dc7bab75e1cda9b6a3b5fcf10eba426ddf1a17b71ef65a434ed707ede7d1c4f013ba1609e970bc8c0cddac01365080d376204314e9b294719acd8a5 +"axobject-query@npm:^4.1.0": + version: 4.1.0 + resolution: "axobject-query@npm:4.1.0" + checksum: 7d1e87bf0aa7ae7a76cd39ab627b7c48fda3dc40181303d9adce4ba1d5b5ce73b5e5403ee6626ec8e91090448c887294d6144e24b6741a976f5be9347e3ae1df languageName: node linkType: hard @@ -9728,15 +9734,15 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.8.0": - version: 2.8.0 - resolution: "eslint-module-utils@npm:2.8.0" +"eslint-module-utils@npm:^2.9.0": + version: 2.11.0 + resolution: "eslint-module-utils@npm:2.11.0" dependencies: debug: ^3.2.7 peerDependenciesMeta: eslint: optional: true - checksum: 74c6dfea7641ebcfe174be61168541a11a14aa8d72e515f5f09af55cd0d0862686104b0524aa4b8e0ce66418a44aa38a94d2588743db5fd07a6b49ffd16921d2 + checksum: 8c2ecff3484835e031c8f1aa44119be65a058d195cce7b3ac827ad7ccc8bb5f9bcdd85230e2e3398981d07789bf4d90f3b81d106e67faf3cd26e0b34d73093af languageName: node linkType: hard @@ -9765,29 +9771,30 @@ __metadata: linkType: hard "eslint-plugin-import@npm:^2.29.1": - version: 2.29.1 - resolution: "eslint-plugin-import@npm:2.29.1" + version: 2.30.0 + resolution: "eslint-plugin-import@npm:2.30.0" dependencies: - array-includes: ^3.1.7 - array.prototype.findlastindex: ^1.2.3 + "@rtsao/scc": ^1.1.0 + array-includes: ^3.1.8 + array.prototype.findlastindex: ^1.2.5 array.prototype.flat: ^1.3.2 array.prototype.flatmap: ^1.3.2 debug: ^3.2.7 doctrine: ^2.1.0 eslint-import-resolver-node: ^0.3.9 - eslint-module-utils: ^2.8.0 - hasown: ^2.0.0 - is-core-module: ^2.13.1 + eslint-module-utils: ^2.9.0 + hasown: ^2.0.2 + is-core-module: ^2.15.1 is-glob: ^4.0.3 minimatch: ^3.1.2 - object.fromentries: ^2.0.7 - object.groupby: ^1.0.1 - object.values: ^1.1.7 + object.fromentries: ^2.0.8 + object.groupby: ^1.0.3 + object.values: ^1.2.0 semver: ^6.3.1 tsconfig-paths: ^3.15.0 peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: e65159aef808136d26d029b71c8c6e4cb5c628e65e5de77f1eb4c13a379315ae55c9c3afa847f43f4ff9df7e54515c77ffc6489c6a6f81f7dd7359267577468c + checksum: 0ec1ad69c0d22f15bc4a49ee97ae757e4adfc3181996f2c4a1ed4d5028bd99bab38e7623e58ef4477ba1db8425f441e4e986367125273efa4c5f7ad2c4467a9a languageName: node linkType: hard @@ -9829,15 +9836,15 @@ __metadata: linkType: hard "eslint-plugin-jsx-a11y@npm:^6.8.0": - version: 6.9.0 - resolution: "eslint-plugin-jsx-a11y@npm:6.9.0" + version: 6.10.0 + resolution: "eslint-plugin-jsx-a11y@npm:6.10.0" dependencies: aria-query: ~5.1.3 array-includes: ^3.1.8 array.prototype.flatmap: ^1.3.2 ast-types-flow: ^0.0.8 - axe-core: ^4.9.1 - axobject-query: ~3.1.1 + axe-core: ^4.10.0 + axobject-query: ^4.1.0 damerau-levenshtein: ^1.0.8 emoji-regex: ^9.2.2 es-iterator-helpers: ^1.0.19 @@ -9849,8 +9856,8 @@ __metadata: safe-regex-test: ^1.0.3 string.prototype.includes: ^2.0.0 peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 122cbd22bbd8c3e4a37f386ec183ada63a4ecfa7af7d40cd8a110777ac5ad5ff542f60644596a9e2582ed138a1cc6d96c5d5ca934105e29d5245d6c951ebc3ef + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + checksum: 1009deca12ddbe3624586bc5fc3534ca98d00a5841a2563cb6abd9339b984f0a99075dc2a703a517f4087eb84d659c87e60beda17645883de2ba1d86f2b20c96 languageName: node linkType: hard @@ -12187,12 +12194,12 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.13.1": - version: 2.13.1 - resolution: "is-core-module@npm:2.13.1" +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1": + version: 2.15.1 + resolution: "is-core-module@npm:2.15.1" dependencies: - hasown: ^2.0.0 - checksum: 256559ee8a9488af90e4bad16f5583c6d59e92f0742e9e8bb4331e758521ee86b810b93bae44f390766ffbc518a0488b18d9dab7da9a5ff997d499efc9403f7c + hasown: ^2.0.2 + checksum: df134c168115690724b62018c37b2f5bba0d5745fa16960b329c5a00883a8bea6a5632fdb1e3efcce237c201826ba09f93197b7cd95577ea56b0df335be23633 languageName: node linkType: hard @@ -13450,12 +13457,13 @@ __metadata: linkType: hard "knip@npm:^5.9.4": - version: 5.27.0 - resolution: "knip@npm:5.27.0" + version: 5.30.2 + resolution: "knip@npm:5.30.2" dependencies: "@nodelib/fs.walk": 1.2.8 "@snyk/github-codeowners": 1.1.0 easy-table: 1.2.0 + enhanced-resolve: ^5.17.1 fast-glob: ^3.3.2 jiti: ^1.21.6 js-yaml: ^4.1.0 @@ -13463,7 +13471,6 @@ __metadata: picocolors: ^1.0.0 picomatch: ^4.0.1 pretty-ms: ^9.0.0 - resolve: ^1.22.8 smol-toml: ^1.1.4 strip-json-comments: 5.0.1 summary: 2.1.0 @@ -13475,7 +13482,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: b94273d2ae3cfdddcea7037c036412a303b03449a70e40b4f0ba7dbfcbf19810023c3fa1024b98dd62b701662a5c162dcb309faab2d3aebed593832107c16f11 + checksum: fafb6ee078c1e9f24e83c08c6b97c7ad82ef78d043eff6a064639e08354a4b81cbb132d705acc53874d0f0d02dc690d960f75efe8783ac349fde491bcbf1b742 languageName: node linkType: hard @@ -14036,7 +14043,7 @@ __metadata: languageName: node linkType: hard -"mdast-util-from-markdown@npm:^2.0.0": +"mdast-util-from-markdown@npm:^2.0.0, mdast-util-from-markdown@npm:^2.0.1": version: 2.0.1 resolution: "mdast-util-from-markdown@npm:2.0.1" dependencies: @@ -15146,10 +15153,10 @@ __metadata: languageName: node linkType: hard -"monaco-editor@npm:~0.50.0": - version: 0.50.0 - resolution: "monaco-editor@npm:0.50.0" - checksum: 841b047c89060bcd5e8864c671389f7cda322bd8372663ea71f42eb3d780edbbabc41b151791629fec04bbf3c53b407eb9e5b0829ef95292103d685df0994147 +"monaco-editor@npm:~0.51.0": + version: 0.51.0 + resolution: "monaco-editor@npm:0.51.0" + checksum: 86cd6e5a756407bc9564ae4c408cdaecbf3410b5bf88e22783e22e79e1e1d048c1cba7c26249e6b234731ffc1aad3660c27213ce1f3f441b3e04cf01a75b1f8c languageName: node linkType: hard @@ -15585,7 +15592,7 @@ __metadata: languageName: node linkType: hard -"object.fromentries@npm:^2.0.7, object.fromentries@npm:^2.0.8": +"object.fromentries@npm:^2.0.8": version: 2.0.8 resolution: "object.fromentries@npm:2.0.8" dependencies: @@ -15597,19 +15604,18 @@ __metadata: languageName: node linkType: hard -"object.groupby@npm:^1.0.1": - version: 1.0.1 - resolution: "object.groupby@npm:1.0.1" +"object.groupby@npm:^1.0.3": + version: 1.0.3 + resolution: "object.groupby@npm:1.0.3" dependencies: - call-bind: ^1.0.2 - define-properties: ^1.2.0 - es-abstract: ^1.22.1 - get-intrinsic: ^1.2.1 - checksum: d7959d6eaaba358b1608066fc67ac97f23ce6f573dc8fc661f68c52be165266fcb02937076aedb0e42722fdda0bdc0bbf74778196ac04868178888e9fd3b78b5 + call-bind: ^1.0.7 + define-properties: ^1.2.1 + es-abstract: ^1.23.2 + checksum: 0d30693ca3ace29720bffd20b3130451dca7a56c612e1926c0a1a15e4306061d84410bdb1456be2656c5aca53c81b7a3661eceaa362db1bba6669c2c9b6d1982 languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.1.7, object.values@npm:^1.2.0": +"object.values@npm:^1.1.6, object.values@npm:^1.2.0": version: 1.2.0 resolution: "object.values@npm:1.2.0" dependencies: @@ -17553,7 +17559,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2": +"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.4, resolve@npm:~1.22.1, resolve@npm:~1.22.2": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -17579,7 +17585,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.4#~builtin, resolve@patch:resolve@^1.22.8#~builtin, resolve@patch:resolve@~1.22.1#~builtin, resolve@patch:resolve@~1.22.2#~builtin": +"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.4#~builtin, resolve@patch:resolve@~1.22.1#~builtin, resolve@patch:resolve@~1.22.2#~builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -19537,9 +19543,9 @@ __metadata: resolution: "typescript-eslint@workspace:packages/typescript-eslint" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/eslint-plugin": 8.5.0 - "@typescript-eslint/parser": 8.5.0 - "@typescript-eslint/utils": 8.5.0 + "@typescript-eslint/eslint-plugin": 8.6.0 + "@typescript-eslint/parser": 8.6.0 + "@typescript-eslint/utils": 8.6.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5 @@ -20258,8 +20264,9 @@ __metadata: konamimojisplosion: ^0.5.2 lz-string: ^1.5.0 make-dir: "*" + mdast-util-from-markdown: ^2.0.1 mdast-util-mdx: ^3.0.0 - monaco-editor: ~0.50.0 + monaco-editor: ~0.51.0 prettier: ^3.2.5 prism-react-renderer: ^2.3.1 raw-loader: ^4.0.2