import parser from '@typescript-eslint/parser'; import { RuleTester } from '@typescript-eslint/rule-tester'; import dedent from 'dedent'; import { checkDestructuringCompleteness } from './check-destructuring-completeness.mjs'; const tester = new RuleTester({ languageOptions: { parser, parserOptions: { ecmaVersion: 2020, sourceType: 'module', ecmaFeatures: { jsx: true, }, projectService: { allowDefaultProject: ['*.ts*'], }, tsconfigRootDir: `${import.meta.dirname}/../../../..`, }, }, }); describe('check-destructuring-completeness', () => { tester.run( 'check-destructuring-completeness', checkDestructuringCompleteness, { valid: [ { name: 'ignore incomplete destructuring without directive comment', code: dedent` const obj = { a: 1, b: 2, c: 3 }; const { a } = obj; `, }, { name: 'validates destructuring with default directive - complete', code: dedent` const obj = { a: 1, b: 2, c: 3 }; // @check-destructuring-completeness const { a, b, c } = obj; `, }, { name: 'validates destructuring with unknown directive', code: dedent` const obj: { a: number; b: string; c: boolean } = { a: 1, b: 'hello', c: true }; // @unknown-directive const { a, b } = obj; `, }, { name: 'works with custom directive keyword - custom check', code: dedent` const obj: { a: number; b: string } = { a: 1, b: 'hello' }; // @custom-check const { a, b } = obj; `, options: [{ directiveKeyword: '@custom-check' }], }, { name: 'custom directive ignores default directive', code: dedent` const obj = { a: 1, b: 2, c: 3 }; // @check-destructuring-completeness const { a, b } = obj; `, options: [{ directiveKeyword: '@custom-check' }], }, { name: 'custom directive ignores unknown directive', code: dedent` const obj: { a: number; b: string; c: boolean } = { a: 1, b: 'hello', c: true }; // @unknown-directive const { a, b } = obj; `, options: [{ directiveKeyword: '@custom-check' }], }, { name: 'checks React component props - parameter destructuring', code: dedent` type Props = { a: number; b: string }; const MyComponent = ({ a, b }: Props) =>
{a}{b}
; `, }, { name: 'checks React component props - internal destructuring', code: dedent` type Props = { a: number; b: string }; const MyComponent = (props: Props) => { const { a, b } = props; return
{a}{b}
; }; `, }, { name: 'allows rest parameter without directive comment', code: dedent` const obj = { a: 1, b: 2, c: 3 }; const { a, ...rest } = obj; `, }, { name: 'allows rest parameter with directive comment', code: dedent` const obj = { a: 1, b: 2, c: 3 }; // @check-destructuring-completeness const { a, ...rest } = obj; `, }, { name: 'allows rest parameter in React component props', code: dedent` type Props = { a: number; b: string; c: boolean }; const MyComponent = ({ a, ...rest }: Props) =>
{a}
; `, }, { name: 'allows rest parameter in internal destructuring', code: dedent` type Props = { a: number; b: string; c: boolean }; const MyComponent = (props: Props) => { const { a, ...rest } = props; return
{a}
; }; `, }, { name: 'handles string literal keys with directive - complete', code: dedent` const obj: { a: number; 'data-e2eId': string } = { a: 1, 'data-e2eId': 'test' }; // @check-destructuring-completeness const { a, 'data-e2eId': e2eId } = obj; `, }, { name: 'handles string literal keys in React component props - parameter destructuring', code: dedent` type Props = { a: number; b: string; 'data-e2eId': string }; const MyComponent = ({ a, b, 'data-e2eId': e2eId }: Props) =>
{a}{b}{e2eId}
; `, }, { name: 'handles string literal keys in React component props - internal destructuring', code: dedent` type Props = { a: number; b: string; 'data-e2eId': string }; const MyComponent = (props: Props) => { const { a, b, 'data-e2eId': e2eId } = props; return
{a}{b}{e2eId}
; }; `, }, { name: 'handles number literal keys with directive - complete', code: dedent` const obj: { 0: string; 1: number } = { 0: 'a', 1: 2 }; // @check-destructuring-completeness const { 0: first, 1: second } = obj; `, }, { name: 'skips check when dynamic computed key is present with directive', code: dedent` const key = 'a' as string; const obj: { a: number; b: string } = { a: 1, b: 'hello' }; // @check-destructuring-completeness const { [key]: val } = obj; `, }, { name: 'skips check when dynamic computed key is present in React component props', code: dedent` const key = 'a' as string; type Props = { a: number; b: string }; const MyComponent = ({ [key]: val }: Props) =>
{val}
; `, }, { name: 'handles computed key with static string literal', code: dedent` const obj: { a: number; 'data-id': string } = { a: 1, 'data-id': 'test' }; // @check-destructuring-completeness const { a, ['data-id']: dataId } = obj; `, }, { name: 'does not check React component props when disabled - parameter', code: dedent` type Props = { a: number; b: string; c: boolean }; const MyComponent = ({ a, b }: Props) =>
{a}{b}
; `, options: [{ alwaysCheckReactComponentProps: false }], }, { name: 'does not check React component props when disabled - internal', code: dedent` type Props = { a: number; b: string; c: boolean }; const MyComponent = (props: Props) => { const { a, b } = props; return
{a}{b}
; }; `, options: [{ alwaysCheckReactComponentProps: false }], }, ], invalid: [ { name: 'validates destructuring with default directive - incomplete', code: dedent` const obj: { a: number; b: string; c: boolean } = { a: 1, b: 'hello', c: true }; // @check-destructuring-completeness const { a, b } = obj; `, errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'custom directive catches incomplete', code: dedent` const obj: { a: number; b: string } = { a: 1, b: 'hello' }; // @custom-check const { a } = obj; `, options: [{ directiveKeyword: '@custom-check' }], errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'reports missing string literal key with directive', code: dedent` const obj: { a: number; 'data-e2eId': string } = { a: 1, 'data-e2eId': 'test' }; // @check-destructuring-completeness const { a } = obj; `, errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'reports missing string literal key in React component props - parameter', code: dedent` type Props = { a: number; 'data-e2eId': string }; const MyComponent = ({ a }: Props) =>
{a}
; `, errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'reports missing string literal key in React component props - internal', code: dedent` type Props = { a: number; 'data-e2eId': string }; const MyComponent = (props: Props) => { const { a } = props; return
{a}
; }; `, errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'reports missing number literal key with directive', code: dedent` const obj: { 0: string; 1: number } = { 0: 'a', 1: 2 }; // @check-destructuring-completeness const { 0: first } = obj; `, errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'checks React component props - parameter destructuring incomplete', code: dedent` type Props = { a: number; b: string; c: boolean }; const MyComponent = ({ a, b }: Props) =>
{a}{b}
; `, errors: [{ messageId: 'incompleteDestructuring' }], }, { name: 'checks React component props - internal destructuring incomplete', code: dedent` type Props = { a: number; b: string; c: boolean }; const MyComponent = (props: Props) => { const { a, b } = props; return
{a}{b}
; }; `, errors: [{ messageId: 'incompleteDestructuring' }], }, ], }, ); // Typed RuleTester cases build a TypeScript program per case. On its own // this file finishes in ~6s with coverage, but it is scheduled alongside // the package's other 37 coverage-instrumented files and can starve. }, 60000);