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);