Skip to content

Commit d8506dd

Browse files
fix(eslint): Disable attribute checking for void promises
1 parent 1b401d0 commit d8506dd

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed

.changeset/many-numbers-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tcd-devkit/eslint-config-ts': patch
3+
---
4+
5+
Disable attribute checking for void promises
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import type { Linter } from 'eslint';
2+
import { config as defineConfig } from 'typescript-eslint';
3+
import { describe, expect, it } from 'vitest';
4+
5+
import { getLintMessagesForRule } from '@tcd-devkit/internal-utils/test';
6+
import type { GetLintMessagesOptions } from '@tcd-devkit/internal-utils/test';
7+
8+
import { tsConfig } from '#ts.linter';
9+
import type { CustomRule } from '#ts.rules';
10+
11+
const safeTsConfig = defineConfig(tsConfig, {
12+
languageOptions: {
13+
parserOptions: {
14+
projectService: true,
15+
tsconfigRootDir: process.cwd(),
16+
},
17+
},
18+
}) as Linter.Config[];
19+
20+
const ruleId: CustomRule = '@typescript-eslint/no-misused-promises';
21+
22+
const lintTextOptions: GetLintMessagesOptions = {
23+
eslintOptions: {
24+
cwd: import.meta.dirname,
25+
},
26+
lintTextOptions: {
27+
filePath: './fixtures/fixture.ts',
28+
},
29+
};
30+
31+
describe(`${ruleId} rule`, () => {
32+
it('should FAIL when using as a conditional (checksConditionals: true)', async () => {
33+
const code = `
34+
const promise = Promise.resolve('value');
35+
36+
const val = promise ? 123 : 456;
37+
`;
38+
const messages = await getLintMessagesForRule(
39+
safeTsConfig,
40+
code,
41+
ruleId,
42+
lintTextOptions,
43+
);
44+
45+
expect(messages).toHaveLength(1);
46+
});
47+
48+
it('should FAIL when using as spread (checksSpreads: true)', async () => {
49+
const code = `
50+
const getData = () => fetch('/');
51+
52+
console.log({ foo: 42, ...getData() });
53+
`;
54+
const messages = await getLintMessagesForRule(
55+
safeTsConfig,
56+
code,
57+
ruleId,
58+
lintTextOptions,
59+
);
60+
61+
expect(messages).toHaveLength(1);
62+
});
63+
64+
it('should FAIL when promises are void (checksVoidReturn: true)', async () => {
65+
const code = `
66+
new Promise<void>(async (resolve, reject) => {
67+
await fetch('/');
68+
resolve();
69+
});
70+
`;
71+
const messages = await getLintMessagesForRule(
72+
safeTsConfig,
73+
code,
74+
ruleId,
75+
lintTextOptions,
76+
);
77+
78+
expect(messages).toHaveLength(1);
79+
});
80+
81+
it('should PASS when using properly with conditional (checksConditionals: true)', async () => {
82+
const code = `
83+
const promise = Promise.resolve('value');
84+
85+
const val = (await promise) ? 123 : 456;
86+
`;
87+
const messages = await getLintMessagesForRule(
88+
safeTsConfig,
89+
code,
90+
ruleId,
91+
lintTextOptions,
92+
);
93+
94+
expect(messages).toHaveLength(0);
95+
});
96+
97+
it('should PASS when using properly with spread (checksSpreads: true)', async () => {
98+
const code = `
99+
const getData = () => fetch('/');
100+
101+
console.log({ foo: 42, ...(await getData()) });
102+
`;
103+
const messages = await getLintMessagesForRule(
104+
safeTsConfig,
105+
code,
106+
ruleId,
107+
lintTextOptions,
108+
);
109+
110+
expect(messages).toHaveLength(0);
111+
});
112+
113+
it('should PASS when properly using void promises (checksVoidReturn: true)', async () => {
114+
const code = `
115+
new Promise((resolve, reject) => {
116+
void (async () => {
117+
await doSomething();
118+
resolve();
119+
})();
120+
});
121+
`;
122+
const messages = await getLintMessagesForRule(
123+
safeTsConfig,
124+
code,
125+
ruleId,
126+
lintTextOptions,
127+
);
128+
129+
expect(messages).toHaveLength(0);
130+
});
131+
132+
it('should PASS when using void promise in an attribute (checksVoidReturn: { attributes: false })', async () => {
133+
const code = `
134+
const promise = async (): Promise<void> => {
135+
await doSomething();
136+
};
137+
138+
const MyComponent = () => {
139+
return (
140+
<form onSubmit={promise}>
141+
<button type="submit">Submit</button>
142+
</form>
143+
);
144+
};
145+
`;
146+
const messages = await getLintMessagesForRule(
147+
safeTsConfig,
148+
code,
149+
ruleId,
150+
lintTextOptions,
151+
);
152+
153+
expect(messages).toHaveLength(0);
154+
});
155+
});

packages/eslint/eslint-config-ts/src/ts.rules.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ export const tsRules = {
1111
'@typescript-eslint/naming-convention': ['off'],
1212
'@typescript-eslint/unbound-method': ['off'],
1313
'@typescript-eslint/no-loop-func': ['error'],
14+
'@typescript-eslint/no-misused-promises': [
15+
'error',
16+
{
17+
checksVoidReturn: {
18+
attributes: false,
19+
},
20+
},
21+
],
1422
'@typescript-eslint/no-use-before-define': ['error'],
1523
'@typescript-eslint/return-await': ['error'],
1624
'@typescript-eslint/switch-exhaustiveness-check': [

0 commit comments

Comments
 (0)