From 4598ca80547698c1da4a4f4d2b34a1170011d791 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 27 Oct 2022 14:44:58 +1030 Subject: [PATCH 1/9] tests: add dependency constraints to eslint-plugin tests --- .../rules/consistent-type-exports.test.ts | 21 +++++ .../rules/consistent-type-imports.test.ts | 25 ++++-- .../tests/rules/member-ordering.test.ts | 24 ++++++ ...habetically-case-insensitive-order.test.ts | 3 + ...mber-ordering-alphabetically-order.test.ts | 3 + .../rules/method-signature-style.test.ts | 44 ++++++++-- .../tests/rules/no-empty-function.test.ts | 6 ++ .../tests/rules/no-magic-numbers.test.ts | 3 + .../no-redundant-type-constituents.test.ts | 29 +++++-- .../tests/rules/no-shadow/no-shadow.test.ts | 15 ++++ .../tests/rules/no-type-alias.test.ts | 54 ++++++++++++ .../no-unnecessary-type-arguments.test.ts | 10 ++- .../no-unused-vars/no-unused-vars.test.ts | 57 +++++++++--- .../non-nullable-type-assertion-style.test.ts | 86 +++++++++++-------- .../restrict-template-expressions.test.ts | 5 ++ .../eslint-utils/rule-tester/RuleTester.ts | 5 +- 16 files changed, 322 insertions(+), 68 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts index 518050bebcf..e5f7bfbc2d3 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts @@ -266,6 +266,9 @@ export { type T, T }; type T = 1; export type { T, T }; `, + dependencyConstraints: { + typescript: '4.5', + }, errors: [ { messageId: 'typeOverValue', @@ -283,6 +286,9 @@ export { type/* */T, type /* */T, T }; type T = 1; export type { /* */T, /* */T, T }; `, + dependencyConstraints: { + typescript: '4.5', + }, errors: [ { messageId: 'typeOverValue', @@ -303,6 +309,9 @@ const x = 1; export type { T, T }; export { x }; `, + dependencyConstraints: { + typescript: '4.5', + }, errors: [ { messageId: 'singleExportIsType', @@ -322,6 +331,9 @@ type T = 1; const x = 1; export { type T, x }; `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ fixMixedExportsWithInlineTypeSpecifier: true }], errors: [ { @@ -340,6 +352,9 @@ export { type T, T }; type T = 1; export type { T, T }; `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ fixMixedExportsWithInlineTypeSpecifier: true }], errors: [ { @@ -362,6 +377,9 @@ export { export type { AnalyzeOptions, Definition as Foo, BlockScope as BScope } from '@typescript-eslint/scope-manager'; export { CatchScope as CScope } from '@typescript-eslint/scope-manager'; `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ fixMixedExportsWithInlineTypeSpecifier: false }], errors: [ { @@ -388,6 +406,9 @@ export { CatchScope as CScope, } from '@typescript-eslint/scope-manager'; `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ fixMixedExportsWithInlineTypeSpecifier: true }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts index b1389b26e6b..fa958adc0d9 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts @@ -7,6 +7,10 @@ const ruleTester = new RuleTester({ ecmaVersion: 2020, sourceType: 'module', }, + // type-only imports were first added in TS3.8 + dependencyConstraints: { + typescript: '3.8', + }, }); const withMetaParserOptions = { @@ -118,11 +122,16 @@ ruleTester.run('consistent-type-imports', rule, { `, options: [{ prefer: 'no-type-imports' }], }, - ` - import { type A, B } from 'foo'; - type T = A; - const b = B; - `, + { + code: ` + import { type A, B } from 'foo'; + type T = A; + const b = B; + `, + dependencyConstraints: { + typescript: '4.5', + }, + }, // exports ` import Type from 'foo'; @@ -1804,6 +1813,9 @@ import { A, B } from 'foo'; type T = A; const b = B; `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ prefer: 'no-type-imports' }], errors: [ { @@ -1824,6 +1836,9 @@ import { B, type C } from 'foo'; type T = A | C; const b = B; `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ prefer: 'type-imports' }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index 538cfa733db..189b1ff6a69 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1250,6 +1250,9 @@ class Foo { f = 1; } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['static-initialization', 'method', 'field'] }], }, { @@ -1260,6 +1263,9 @@ class Foo { static {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['method', 'field', 'static-initialization'] }], }, { @@ -1270,6 +1276,9 @@ class Foo { m() {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['field', 'static-initialization', 'method'] }], }, ` @@ -4029,6 +4038,9 @@ class Foo { f = 1; } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['method', 'field', 'static-initialization'] }], errors: [ { @@ -4059,6 +4071,9 @@ class Foo { static {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['static-initialization', 'method', 'field'] }], errors: [ { @@ -4080,6 +4095,9 @@ class Foo { m() {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['static-initialization', 'field', 'method'] }], errors: [ { @@ -4101,6 +4119,9 @@ class Foo { m() {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [{ default: ['field', 'static-initialization', 'method'] }], errors: [ { @@ -4124,6 +4145,9 @@ class Foo { md() {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [ { default: ['decorated-method', 'static-initialization', 'method'] }, ], diff --git a/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-case-insensitive-order.test.ts b/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-case-insensitive-order.test.ts index 07db08e0e86..fd10c55fe91 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-case-insensitive-order.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-case-insensitive-order.test.ts @@ -499,6 +499,9 @@ class Foo { static {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [ { default: { diff --git a/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-order.test.ts b/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-order.test.ts index b7b5da85910..338b3a50ee9 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-order.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering/member-ordering-alphabetically-order.test.ts @@ -1694,6 +1694,9 @@ class Foo { static {} } `, + dependencyConstraints: { + typescript: '4.4', + }, options: [ { default: { diff --git a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts index d2a88e9c02b..d9db8f5d6eb 100644 --- a/packages/eslint-plugin/tests/rules/method-signature-style.test.ts +++ b/packages/eslint-plugin/tests/rules/method-signature-style.test.ts @@ -32,22 +32,42 @@ interface Test { 'f!': (/* b */ x: any /* c */) => void; } `, - ` + { + code: ` interface Test { get f(): number; } - `, - ` + `, + dependencyConstraints: { + typescript: '4.3', + }, + }, + { + code: ` interface Test { set f(value: number): void; } - `, + `, + dependencyConstraints: { + typescript: '4.3', + }, + }, 'type Test = { readonly f: (a: string) => number };', "type Test = { ['f']?: (a: boolean) => void };", 'type Test = { readonly f?: (a?: T) => T };', "type Test = { readonly ['f']?: (a: T, b: T) => T };", - 'type Test = { get f(): number };', - 'type Test = { set f(value: number): void };', + { + code: 'type Test = { get f(): number };', + dependencyConstraints: { + typescript: '4.3', + }, + }, + { + code: 'type Test = { set f(value: number): void };', + dependencyConstraints: { + typescript: '4.3', + }, + }, ...batchedSingleLineTests({ options: ['method'], code: noFormat` @@ -56,15 +76,23 @@ interface Test { interface Test { f(a: T): T } interface Test { ['f'](a: T, b: T): T } interface Test { 'f!'(/* b */ x: any /* c */): void } - interface Test { get f(): number } - interface Test { set f(value: number): void } type Test = { readonly f(a: string): number } type Test = { ['f']?(a: boolean): void } type Test = { readonly f?(a?: T): T } type Test = { readonly ['f']?(a: T, b: T): T } + `, + }), + ...batchedSingleLineTests({ + options: ['method'], + code: noFormat` + interface Test { get f(): number } + interface Test { set f(value: number): void } type Test = { get f(): number } type Test = { set f(value: number): void } `, + dependencyConstraints: { + typescript: '4.3', + }, }), ], invalid: [ diff --git a/packages/eslint-plugin/tests/rules/no-empty-function.test.ts b/packages/eslint-plugin/tests/rules/no-empty-function.test.ts index 7d29b0fa5a2..7f35c79852c 100644 --- a/packages/eslint-plugin/tests/rules/no-empty-function.test.ts +++ b/packages/eslint-plugin/tests/rules/no-empty-function.test.ts @@ -78,6 +78,9 @@ class Foo extends Base { override foo() {} } `, + dependencyConstraints: { + typescript: '4.3', + }, options: [{ allow: ['overrideMethods'] }], }, ], @@ -206,6 +209,9 @@ class Foo extends Base { override foo() {} } `, + dependencyConstraints: { + typescript: '4.3', + }, errors: [ { messageId: 'unexpected', diff --git a/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts index 3be7590742a..d6aa21a799e 100644 --- a/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts +++ b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts @@ -536,6 +536,9 @@ type Foo = { [K in keyof Other]: \`\${K & number}\`; }; `, + dependencyConstraints: { + typescript: '4.1', + }, options: [{ ignoreTypeIndexes: true }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts b/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts index 454fb255f0e..29259f4b3f0 100644 --- a/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts +++ b/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts @@ -154,11 +154,21 @@ ruleTester.run('no-redundant-type-constituents', rule, { type B = string; type T = B & null; `, - 'type T = `${string}` & null;', - ` - type B = \`\${string}\`; - type T = B & null; - `, + { + code: 'type T = `${string}` & null;', + dependencyConstraints: { + typescript: '4.1', + }, + }, + { + code: ` + type B = \`\${string}\`; + type T = B & null; + `, + dependencyConstraints: { + typescript: '4.1', + }, + }, ], invalid: [ @@ -442,6 +452,9 @@ ruleTester.run('no-redundant-type-constituents', rule, { }, { code: 'type T = `a${number}c` | string;', + dependencyConstraints: { + typescript: '4.1', + }, errors: [ { column: 10, @@ -458,6 +471,9 @@ ruleTester.run('no-redundant-type-constituents', rule, { type B = \`a\${number}c\`; type T = B | string; `, + dependencyConstraints: { + typescript: '4.1', + }, errors: [ { column: 18, @@ -471,6 +487,9 @@ ruleTester.run('no-redundant-type-constituents', rule, { }, { code: 'type T = `${number}` | string;', + dependencyConstraints: { + typescript: '4.1', + }, errors: [ { column: 10, diff --git a/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts b/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts index 1097f466a18..fc81bd1df75 100644 --- a/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts +++ b/packages/eslint-plugin/tests/rules/no-shadow/no-shadow.test.ts @@ -201,6 +201,9 @@ import { type foo } from './foo'; // 'foo' is already declared in the upper scope function doThing(foo: number) {} `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ ignoreTypeValueShadow: true }], }, { @@ -494,6 +497,9 @@ function doThing(foo: number) {} import { type foo } from './foo'; function doThing(foo: number) {} `, + dependencyConstraints: { + typescript: '4.5', + }, options: [{ ignoreTypeValueShadow: false }], errors: [ { @@ -621,6 +627,9 @@ declare module 'baz' { } } `, + dependencyConstraints: { + typescript: '4.5', + }, errors: [ { messageId: 'noShadow', @@ -641,6 +650,9 @@ declare module 'bar' { export type Foo = string; } `, + dependencyConstraints: { + typescript: '4.5', + }, errors: [ { messageId: 'noShadow', @@ -663,6 +675,9 @@ declare module 'bar' { } } `, + dependencyConstraints: { + typescript: '4.5', + }, errors: [ { messageId: 'noShadow', diff --git a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts index 6a307454f51..ed2c00c9991 100644 --- a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts @@ -133,62 +133,107 @@ ruleTester.run('no-type-alias', rule, { }, { code: 'type Foo = `a-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'always' }], }, { code: 'type Foo = `a-${number}` | `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'always' }], }, { code: 'type Foo = `a-${number}` | `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions-and-intersections' }], }, { code: 'type Foo = `a-${number}` | `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions' }], }, { code: 'type Foo = `a-${number}` | `b-${number}` | `c-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'always' }], }, { code: 'type Foo = `a-${number}` | `b-${number}` | `c-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions-and-intersections' }], }, { code: 'type Foo = `a-${number}` | `b-${number}` | `c-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions' }], }, { code: 'type Foo = `a-${number}` & `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'always' }], }, { code: 'type Foo = `a-${number}` & `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions-and-intersections' }], }, { code: 'type Foo = `a-${number}` & `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-intersections' }], }, { code: 'type Foo = `a-${number}` & `b-${number}` & `c-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'always' }], }, { code: 'type Foo = `a-${number}` & `b-${number}` & `c-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions-and-intersections' }], }, { code: 'type Foo = `a-${number}` & `b-${number}` & `c-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-intersections' }], }, { code: 'type Foo = `a-${number}` | (`b-${number}` & `c-${number}`);', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'always' }], }, { code: 'type Foo = `a-${number}` | (`b-${number}` & `c-${number}`);', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'in-unions-and-intersections' }], }, { @@ -3402,6 +3447,9 @@ type Foo = { }, { code: 'type Foo = `foo-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, errors: [ { messageId: 'noTypeAlias', @@ -3415,6 +3463,9 @@ type Foo = { }, { code: 'type Foo = `a-${number}` | `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'never' }], errors: [ { @@ -3439,6 +3490,9 @@ type Foo = { }, { code: 'type Foo = `a-${number}` & `b-${number}`;', + dependencyConstraints: { + typescript: '4.1', + }, options: [{ allowAliases: 'never' }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts index 512407474e6..abedc24d274 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-arguments.test.ts @@ -127,11 +127,17 @@ class Foo extends Bar {} interface Bar {} class Foo implements Bar {} `, - ` + { + code: ` import { F } from './missing'; function bar() {} bar>(); - `, + `, + dependencyConstraints: { + // TS 4.5 improved type resolution for unresolved generics + typescript: '4.5', + }, + }, ` type A = T; type B = A; diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts index 715d2e94385..ee2191a3f4c 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts @@ -402,7 +402,8 @@ export const map: { [name in Foo]: Bar } = { }; `, // 4.1 remapped mapped type - noFormat` + { + code: noFormat` type Foo = 'a' | 'b' | 'c'; type Bar = number; @@ -411,7 +412,11 @@ export const map: { [name in Foo as string]: Bar } = { b: 2, c: 3, }; - `, + `, + dependencyConstraints: { + typescript: '4.1', + }, + }, ` import { Nullable } from 'nullable'; class A { @@ -747,6 +752,9 @@ function foo(value: T): T { } export type Foo = typeof foo; `, + dependencyConstraints: { + typescript: '4.7', + }, }, // https://github.com/typescript-eslint/typescript-eslint/issues/2331 { @@ -922,7 +930,8 @@ export declare namespace Foo { } } `, - noFormat` + { + code: noFormat` class Foo { value: T; } @@ -930,7 +939,11 @@ class Bar { foo = Foo; } new Bar(); - `, + `, + dependencyConstraints: { + typescript: '4.7', + }, + }, { code: ` declare namespace A { @@ -946,21 +959,36 @@ declare function A(A: string): string; filename: 'foo.d.ts', }, // 4.1 template literal types - noFormat` + { + code: noFormat` type Color = 'red' | 'blue'; type Quantity = 'one' | 'two'; export type SeussFish = \`\${Quantity | Color} fish\`; - `, - noFormat` + `, + dependencyConstraints: { + typescript: '4.1', + }, + }, + { + code: noFormat` type VerticalAlignment = "top" | "middle" | "bottom"; type HorizontalAlignment = "left" | "center" | "right"; export declare function setAlignment(value: \`\${VerticalAlignment}-\${HorizontalAlignment}\`): void; - `, - noFormat` + `, + dependencyConstraints: { + typescript: '4.1', + }, + }, + { + code: noFormat` type EnthusiasticGreeting = \`\${Uppercase} - \${Lowercase} - \${Capitalize} - \${Uncapitalize}\`; export type HELLO = EnthusiasticGreeting<"heLLo">; - `, + `, + dependencyConstraints: { + typescript: '4.1', + }, + }, // https://github.com/typescript-eslint/typescript-eslint/issues/2714 { code: ` @@ -1028,7 +1056,8 @@ export class Foo { } } `, - ` + { + code: ` function foo() {} export class Foo { @@ -1038,7 +1067,11 @@ export class Foo { foo(); } } - `, + `, + dependencyConstraints: { + typescript: '4.4', + }, + }, ], invalid: [ diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index 4b0750c8c84..009790a5415 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -1,14 +1,11 @@ -import path from 'path'; - import rule from '../../src/rules/non-nullable-type-assertion-style'; -import { RuleTester } from '../RuleTester'; +import { getFixturesRootDir, RuleTester } from '../RuleTester'; -const rootDir = path.resolve(__dirname, '../fixtures/'); const ruleTester = new RuleTester({ parserOptions: { sourceType: 'module', - tsconfigRootDir: rootDir, - project: './tsconfig.noUncheckedIndexedAccess.json', + tsconfigRootDir: getFixturesRootDir(), + project: './tsconfig.json', }, parser: '@typescript-eslint/parser', }); @@ -62,35 +59,6 @@ const x = 1 as 1; declare function foo(): T; const bar = foo() as number; `, - ` -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -function first( - array: ArrayLike, -): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -type A = 'a' | 'A'; -type B = 'b' | 'B'; -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, ], invalid: [ @@ -229,6 +197,54 @@ declare const x: T; const y = x!; `, }, + ], +}); + +const ruleTesterWithNoUncheckedIndexAccess = new RuleTester({ + parserOptions: { + sourceType: 'module', + tsconfigRootDir: getFixturesRootDir(), + project: './tsconfig.noUncheckedIndexedAccess.json', + }, + parser: '@typescript-eslint/parser', + dependencyConstraints: { + typescript: '4.1', + }, +}); + +ruleTesterWithNoUncheckedIndexAccess.run('', rule, { + valid: [ + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first( + array: ArrayLike, +): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +type A = 'a' | 'A'; +type B = 'b' | 'B'; +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ], + invalid: [ { code: ` function first(array: ArrayLike): T | null { 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 b0322b290ab..7e80bdbdf5d 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -358,6 +358,11 @@ ruleTester.run('restrict-template-expressions', rule, { return \`arg = \${arg}\`; } `, + dependencyConstraints: { + // TS 4.5 improved type printing to print the type T as `T` + // before that it was printed as `any` + typescript: '4.5', + }, errors: [ { messageId: 'invalidType', diff --git a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts index 54a645ccf25..940b261b0c3 100644 --- a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts +++ b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts @@ -87,8 +87,11 @@ class RuleTester extends BaseRuleTester.RuleTester { } constructor(baseOptions: RuleTesterConfig) { + // eslint will hard-error if you include non-standard top-level properties + const { dependencyConstraints: _, ...baseOptionsSafeForESLint } = + baseOptions; super({ - ...baseOptions, + ...baseOptionsSafeForESLint, parserOptions: { ...baseOptions.parserOptions, warnOnUnsupportedTypeScriptVersion: From b4dd439cb519e397452203d0d9cbb9720f74f5ee Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 13 Nov 2022 18:30:40 -0500 Subject: [PATCH 2/9] Update packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts --- .../tests/rules/non-nullable-type-assertion-style.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index 009790a5415..b9b8fe3153e 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -212,7 +212,7 @@ const ruleTesterWithNoUncheckedIndexAccess = new RuleTester({ }, }); -ruleTesterWithNoUncheckedIndexAccess.run('', rule, { +ruleTesterWithNoUncheckedIndexAccess.run('non-nullable-type-assertion-style', rule, { valid: [ ` function first(array: ArrayLike): T | null { From ef2105a457b03ecc082f9e595e8ab492e34cfcd6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 13 Nov 2022 19:13:27 -0500 Subject: [PATCH 3/9] yarn format --- .../non-nullable-type-assertion-style.test.ts | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index b9b8fe3153e..088d3aa9a35 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -212,57 +212,61 @@ const ruleTesterWithNoUncheckedIndexAccess = new RuleTester({ }, }); -ruleTesterWithNoUncheckedIndexAccess.run('non-nullable-type-assertion-style', rule, { - valid: [ - ` +ruleTesterWithNoUncheckedIndexAccess.run( + 'non-nullable-type-assertion-style', + rule, + { + valid: [ + ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - ` + ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - ` + ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - ` + ` function first( array: ArrayLike, ): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - ` + ` type A = 'a' | 'A'; type B = 'b' | 'B'; function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - ], - invalid: [ - { - code: ` + ], + invalid: [ + { + code: ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - errors: [ - { - column: 30, - line: 3, - messageId: 'preferNonNullAssertion', - }, - ], - output: ` + errors: [ + { + column: 30, + line: 3, + messageId: 'preferNonNullAssertion', + }, + ], + output: ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0]!) : null; } `, - }, - ], -}); + }, + ], + }, +); From 3965014b54ad07ba80a6f0ca691e2bd6fb41fd03 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 13 Nov 2022 19:45:04 -0500 Subject: [PATCH 4/9] Fix lint issues with formatting --- .../rules/non-nullable-type-assertion-style.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index 088d3aa9a35..5d567f804a9 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -221,31 +221,31 @@ ruleTesterWithNoUncheckedIndexAccess.run( function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } - `, + `, ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } - `, + `, ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } - `, + `, ` function first( array: ArrayLike, ): T | null { return array.length > 0 ? (array[0] as T) : null; } - `, + `, ` type A = 'a' | 'A'; type B = 'b' | 'B'; function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } - `, + `, ], invalid: [ { @@ -253,7 +253,7 @@ function first(array: ArrayLike): T | null { function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } - `, + `, errors: [ { column: 30, From 6bee1096acd48f5501b7bee18119deb7da7d5367 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 14 Nov 2022 17:27:18 -0500 Subject: [PATCH 5/9] Remove dependencyConstraints from test cases too --- .../tests/rules/member-ordering.test.ts | 3 +++ .../src/eslint-utils/rule-tester/RuleTester.ts | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index 189b1ff6a69..cfff3691476 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -29,6 +29,9 @@ interface Foo { } `, { + dependencyConstraints: { + typescript: '4.5', + }, code: ` // no accessibility === public interface Foo { diff --git a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts index 940b261b0c3..7350de3a975 100644 --- a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts +++ b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts @@ -207,14 +207,16 @@ class RuleTester extends BaseRuleTester.RuleTester { single test case. Hugely helps with the string-based valid test cases as it means they don't need to be made objects! + Also removes dependencyConstraints, which we support but ESLint core doesn't. */ - const addFilename = < + const normalizeTest = < T extends | ValidTestCase | InvalidTestCase, - >( - test: T, - ): T => { + >({ + dependencyConstraints: _, + ...test + }: T): Omit => { if (test.parser === TS_ESLINT_PARSER) { throw new Error(ERROR_MESSAGE); } @@ -226,8 +228,8 @@ class RuleTester extends BaseRuleTester.RuleTester { } return test; }; - tests.valid = tests.valid.map(addFilename); - tests.invalid = tests.invalid.map(addFilename); + tests.valid = tests.valid.map(normalizeTest); + tests.invalid = tests.invalid.map(normalizeTest); const hasOnly = ((): boolean => { for (const test of allTestsIterator) { From 7bb7eee346b142b06f7bba0560c958e8c8f6b149 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 14 Nov 2022 17:48:27 -0500 Subject: [PATCH 6/9] Update snapshots --- .../rule-tester/RuleTester.test.ts | 87 ++----------------- 1 file changed, 9 insertions(+), 78 deletions(-) diff --git a/packages/utils/tests/eslint-utils/rule-tester/RuleTester.test.ts b/packages/utils/tests/eslint-utils/rule-tester/RuleTester.test.ts index 2e620332942..57ac48b38c1 100644 --- a/packages/utils/tests/eslint-utils/rule-tester/RuleTester.test.ts +++ b/packages/utils/tests/eslint-utils/rule-tester/RuleTester.test.ts @@ -365,56 +365,32 @@ describe('RuleTester', () => { "invalid": [ { "code": "failing - major", - "dependencyConstraints": { - "totally-real-dependency": "999", - }, "errors": [], "filename": "file.ts", - "only": false, }, { "code": "failing - major.minor", - "dependencyConstraints": { - "totally-real-dependency": "999.0", - }, "errors": [], "filename": "file.ts", - "only": false, }, { "code": "failing - major.minor.patch", - "dependencyConstraints": { - "totally-real-dependency": "999.0.0", - }, "errors": [], "filename": "file.ts", - "only": false, }, ], "valid": [ { "code": "passing - major", - "dependencyConstraints": { - "totally-real-dependency": "10", - }, "filename": "file.ts", - "only": true, }, { "code": "passing - major.minor", - "dependencyConstraints": { - "totally-real-dependency": "10.0", - }, "filename": "file.ts", - "only": true, }, { "code": "passing - major.minor.patch", - "dependencyConstraints": { - "totally-real-dependency": "10.0.0", - }, "filename": "file.ts", - "only": true, }, ], } @@ -485,61 +461,28 @@ describe('RuleTester', () => { "invalid": [ { "code": "failing - major", - "dependencyConstraints": { - "totally-real-dependency": { - "range": "^999", - }, - }, "errors": [], "filename": "file.ts", - "only": false, }, { "code": "failing - major.minor", - "dependencyConstraints": { - "totally-real-dependency": { - "range": ">=999.0", - }, - }, "errors": [], "filename": "file.ts", - "only": false, }, { "code": "failing with options", - "dependencyConstraints": { - "totally-real-dependency-prerelease": { - "options": { - "includePrerelease": false, - }, - "range": "^10", - }, - }, "errors": [], "filename": "file.ts", - "only": false, }, ], "valid": [ { "code": "passing - major", - "dependencyConstraints": { - "totally-real-dependency": { - "range": "^10", - }, - }, "filename": "file.ts", - "only": true, }, { "code": "passing - major.minor", - "dependencyConstraints": { - "totally-real-dependency": { - "range": "<999", - }, - }, "filename": "file.ts", - "only": true, }, ], } @@ -595,49 +538,34 @@ describe('RuleTester', () => { "code": "no constraints is always run", "errors": [], "filename": "file.ts", - "only": true, }, { "code": "empty object is always run", - "dependencyConstraints": {}, "errors": [], "filename": "file.ts", - "only": true, }, { "code": "failing constraint", - "dependencyConstraints": { - "totally-real-dependency": "99999", - }, "errors": [], "filename": "file.ts", - "only": false, }, ], "valid": [ { "code": "string based is always run", "filename": "file.ts", - "only": true, }, { "code": "no constraints is always run", "filename": "file.ts", - "only": true, }, { "code": "empty object is always run", - "dependencyConstraints": {}, "filename": "file.ts", - "only": true, }, { "code": "passing constraint", - "dependencyConstraints": { - "totally-real-dependency": "10", - }, "filename": "file.ts", - "only": true, }, ], } @@ -690,11 +618,13 @@ describe('RuleTester', () => { expect(runSpy.mock.lastCall?.[2]).toMatchInlineSnapshot(` { "invalid": [ + { + "code": "failing", + "errors": [], + "filename": "file.ts", + }, { "code": "passing", - "dependencyConstraints": { - "totally-real-dependency": "10", - }, "errors": [], "filename": "file.ts", }, @@ -704,11 +634,12 @@ describe('RuleTester', () => { "code": "always passing string test", "filename": "file.ts", }, + { + "code": "failing", + "filename": "file.ts", + }, { "code": "passing", - "dependencyConstraints": { - "totally-real-dependency": "10", - }, "filename": "file.ts", }, ], From d81c4e43e3179a7e8c6331f33ffb8064d5bc4dd1 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 16 Nov 2022 18:18:00 +1030 Subject: [PATCH 7/9] fix failing test --- .../non-nullable-type-assertion-style.test.ts | 4 +- .../eslint-utils/rule-tester/RuleTester.ts | 48 ++++++++++++++----- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index 5d567f804a9..6826230b4fd 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -213,7 +213,7 @@ const ruleTesterWithNoUncheckedIndexAccess = new RuleTester({ }); ruleTesterWithNoUncheckedIndexAccess.run( - 'non-nullable-type-assertion-style', + 'non-nullable-type-assertion-style - noUncheckedIndexedAccess', rule, { valid: [ @@ -265,7 +265,7 @@ function first(array: ArrayLike): T | null { function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0]!) : null; } - `, + `, }, ], }, diff --git a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts index 7350de3a975..6c82f8aba85 100644 --- a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts +++ b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts @@ -81,6 +81,41 @@ class RuleTester extends BaseRuleTester.RuleTester { this.#afterAll = value; } + static get describe(): RuleTesterTestFrameworkFunction { + const baseDescribe = super.describe; + return (text, callback) => { + return baseDescribe(text, (...args) => { + callback(...args); + + // make sure that the parser doesn't hold onto file handles between tests + // on linux (i.e. our CI env), there can be very a limited number of watch handles available + + // we do this here by wrapping the describe function so that we can be certain + // that this is run after the block of tests described by a given rule tester + // run. + // This allows users to do things like this in the same file: + // ``` + // const ruleTester1 = new RuleTester({... some config ...}); + // ruleTester1.run( ... ); + // + // const ruleTester2 = new RuleTester({... some OTHER config ...}); + // ruleTester2.run( ... ); + // ``` + this.afterAll(() => { + try { + // instead of creating a hard dependency, just use a soft require + // a bit weird, but if they're using this tooling, it'll be installed + const parser = + require(TS_ESLINT_PARSER) as typeof TSESLintParserType; + parser.clearCaches(); + } catch { + // ignored on purpose + } + }); + }); + }; + } + private get staticThis(): typeof RuleTester { // the cast here is due to https://github.com/microsoft/TypeScript/issues/3841 return this.constructor as typeof RuleTester; @@ -105,19 +140,6 @@ class RuleTester extends BaseRuleTester.RuleTester { }); this.#baseOptions = baseOptions; - - // make sure that the parser doesn't hold onto file handles between tests - // on linux (i.e. our CI env), there can be very a limited number of watch handles available - this.staticThis.afterAll(() => { - try { - // instead of creating a hard dependency, just use a soft require - // a bit weird, but if they're using this tooling, it'll be installed - const parser = require(TS_ESLINT_PARSER) as typeof TSESLintParserType; - parser.clearCaches(); - } catch { - // ignored on purpose - } - }); } private getFilename(testOptions?: ParserOptions): string { const resolvedOptions = deepMerge( From 56bd530ad8554a8605096ff759ec09239b92df33 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 16 Nov 2022 18:22:19 +1030 Subject: [PATCH 8/9] revert rule tester change --- .../eslint-utils/rule-tester/RuleTester.ts | 48 +++++-------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts index 6c82f8aba85..7350de3a975 100644 --- a/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts +++ b/packages/utils/src/eslint-utils/rule-tester/RuleTester.ts @@ -81,41 +81,6 @@ class RuleTester extends BaseRuleTester.RuleTester { this.#afterAll = value; } - static get describe(): RuleTesterTestFrameworkFunction { - const baseDescribe = super.describe; - return (text, callback) => { - return baseDescribe(text, (...args) => { - callback(...args); - - // make sure that the parser doesn't hold onto file handles between tests - // on linux (i.e. our CI env), there can be very a limited number of watch handles available - - // we do this here by wrapping the describe function so that we can be certain - // that this is run after the block of tests described by a given rule tester - // run. - // This allows users to do things like this in the same file: - // ``` - // const ruleTester1 = new RuleTester({... some config ...}); - // ruleTester1.run( ... ); - // - // const ruleTester2 = new RuleTester({... some OTHER config ...}); - // ruleTester2.run( ... ); - // ``` - this.afterAll(() => { - try { - // instead of creating a hard dependency, just use a soft require - // a bit weird, but if they're using this tooling, it'll be installed - const parser = - require(TS_ESLINT_PARSER) as typeof TSESLintParserType; - parser.clearCaches(); - } catch { - // ignored on purpose - } - }); - }); - }; - } - private get staticThis(): typeof RuleTester { // the cast here is due to https://github.com/microsoft/TypeScript/issues/3841 return this.constructor as typeof RuleTester; @@ -140,6 +105,19 @@ class RuleTester extends BaseRuleTester.RuleTester { }); this.#baseOptions = baseOptions; + + // make sure that the parser doesn't hold onto file handles between tests + // on linux (i.e. our CI env), there can be very a limited number of watch handles available + this.staticThis.afterAll(() => { + try { + // instead of creating a hard dependency, just use a soft require + // a bit weird, but if they're using this tooling, it'll be installed + const parser = require(TS_ESLINT_PARSER) as typeof TSESLintParserType; + parser.clearCaches(); + } catch { + // ignored on purpose + } + }); } private getFilename(testOptions?: ParserOptions): string { const resolvedOptions = deepMerge( From 3f684bd67aeb7c2154f2d4e1714b72a6f9510870 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 16 Nov 2022 18:29:50 +1030 Subject: [PATCH 9/9] fix(typescript-estree): don't consider a cached program unless it's specified in the current `parserOptions.project` config --- .../non-nullable-type-assertion-style.test.ts | 120 ++++++++++-------- .../src/create-program/createWatchProgram.ts | 10 ++ 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index 4b0750c8c84..6826230b4fd 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -1,14 +1,11 @@ -import path from 'path'; - import rule from '../../src/rules/non-nullable-type-assertion-style'; -import { RuleTester } from '../RuleTester'; +import { getFixturesRootDir, RuleTester } from '../RuleTester'; -const rootDir = path.resolve(__dirname, '../fixtures/'); const ruleTester = new RuleTester({ parserOptions: { sourceType: 'module', - tsconfigRootDir: rootDir, - project: './tsconfig.noUncheckedIndexedAccess.json', + tsconfigRootDir: getFixturesRootDir(), + project: './tsconfig.json', }, parser: '@typescript-eslint/parser', }); @@ -62,35 +59,6 @@ const x = 1 as 1; declare function foo(): T; const bar = foo() as number; `, - ` -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -function first( - array: ArrayLike, -): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, - ` -type A = 'a' | 'A'; -type B = 'b' | 'B'; -function first(array: ArrayLike): T | null { - return array.length > 0 ? (array[0] as T) : null; -} - `, ], invalid: [ @@ -229,24 +197,76 @@ declare const x: T; const y = x!; `, }, - { - code: ` -function first(array: ArrayLike): T | null { + ], +}); + +const ruleTesterWithNoUncheckedIndexAccess = new RuleTester({ + parserOptions: { + sourceType: 'module', + tsconfigRootDir: getFixturesRootDir(), + project: './tsconfig.noUncheckedIndexedAccess.json', + }, + parser: '@typescript-eslint/parser', + dependencyConstraints: { + typescript: '4.1', + }, +}); + +ruleTesterWithNoUncheckedIndexAccess.run( + 'non-nullable-type-assertion-style - noUncheckedIndexedAccess', + rule, + { + valid: [ + ` +function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0] as T) : null; } `, - errors: [ - { - column: 30, - line: 3, - messageId: 'preferNonNullAssertion', - }, - ], - output: ` + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first( + array: ArrayLike, +): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +type A = 'a' | 'A'; +type B = 'b' | 'B'; +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ], + invalid: [ + { + code: ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + errors: [ + { + column: 30, + line: 3, + messageId: 'preferNonNullAssertion', + }, + ], + output: ` function first(array: ArrayLike): T | null { return array.length > 0 ? (array[0]!) : null; } - `, - }, - ], -}); + `, + }, + ], + }, +); diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 0e32f8ec1e5..d17835fff3c 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -159,11 +159,21 @@ function getProgramsForProjects(parseSettings: ParseSettings): ts.Program[] { ); } + const currentProjectsFromSettings = new Set(parseSettings.projects); + /* * before we go into the process of attempting to find and update every program * see if we know of a program that contains this file */ for (const [tsconfigPath, existingWatch] of knownWatchProgramMap.entries()) { + if (!currentProjectsFromSettings.has(tsconfigPath)) { + // the current parser run doesn't specify this tsconfig in parserOptions.project + // so we don't want to consider it for caching purposes. + // + // if we did consider it we might return a program for a project + // that wasn't specified in the current parser run (which is obv bad!). + continue; + } let fileList = programFileListCache.get(tsconfigPath); let updatedProgram: ts.Program | null = null; if (!fileList) {