Skip to content

Commit f887ab5

Browse files
authored
feat(scope-manager): add support for JSX scope analysis (typescript-eslint#2498)
Fixes typescript-eslint#2455 And part of typescript-eslint#2477 JSX is a first-class citizen of TS, so we should really support it as well. I was going to just rely upon `eslint-plugin-react`'s patch lint rules (`react/jsx-uses-react` and `react/jsx-uses-vars`), but that leaves gaps in our tooling. For example typescript-eslint#2455, `consistent-type-imports` makes assumptions and can create invalid fixes for react without this change. We could add options to that lint rule for the factory, but that is kind-of a sub-par experience and future rule authors will likely run into similar problems. - Adds full scope analysis support for JSX. - Adds two new `parserOption`: - `jsxPragma` - the name to use for constructing JSX elements. Defaults to `"React"`. Will be auto detected from the tsconfig. - `jsxFragmentName` - the name that unnamed JSX fragments use. Defaults to `null` (i.e. assumes `React.Fragment`). Will be auto detected from the tsconfig.
1 parent 95f6bf4 commit f887ab5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1618
-21
lines changed

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"pluggable",
7676
"postprocess",
7777
"postprocessor",
78+
"preact",
7879
"Premade",
7980
"prettier's",
8081
"recurse",
@@ -88,6 +89,7 @@
8889
"rulesets",
8990
"serializers",
9091
"superset",
92+
"transpiling",
9193
"thenables",
9294
"transpiles",
9395
"tsconfigs",

packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,65 @@ function predicate(arg: any): asserts arg is T {
140140
}
141141
}
142142
`,
143+
{
144+
code: `
145+
function Foo() {}
146+
<Foo />;
147+
`,
148+
parserOptions: {
149+
ecmaFeatures: {
150+
jsx: true,
151+
},
152+
},
153+
},
154+
{
155+
code: `
156+
type T = 1;
157+
function Foo() {}
158+
<Foo<T> />;
159+
`,
160+
parserOptions: {
161+
ecmaFeatures: {
162+
jsx: true,
163+
},
164+
},
165+
},
166+
{
167+
code: `
168+
const x = 1;
169+
function Foo() {}
170+
<Foo attr={x} />;
171+
`,
172+
parserOptions: {
173+
ecmaFeatures: {
174+
jsx: true,
175+
},
176+
},
177+
},
178+
{
179+
code: `
180+
const x = {};
181+
function Foo() {}
182+
<Foo {...x} />;
183+
`,
184+
parserOptions: {
185+
ecmaFeatures: {
186+
jsx: true,
187+
},
188+
},
189+
},
190+
{
191+
code: `
192+
const x = {};
193+
function Foo() {}
194+
<Foo>{x}</Foo>;
195+
`,
196+
parserOptions: {
197+
ecmaFeatures: {
198+
jsx: true,
199+
},
200+
},
201+
},
143202
],
144203
invalid: [
145204
{
@@ -175,5 +234,107 @@ function predicate(arg: any): asserts arg is T {
175234
},
176235
],
177236
},
237+
{
238+
code: '<Foo />;',
239+
parserOptions: {
240+
ecmaFeatures: {
241+
jsx: true,
242+
},
243+
},
244+
errors: [
245+
{
246+
messageId: 'undef',
247+
data: {
248+
name: 'Foo',
249+
},
250+
line: 1,
251+
column: 2,
252+
},
253+
],
254+
},
255+
{
256+
code: `
257+
function Foo() {}
258+
<Foo attr={x} />;
259+
`,
260+
parserOptions: {
261+
ecmaFeatures: {
262+
jsx: true,
263+
},
264+
},
265+
errors: [
266+
{
267+
messageId: 'undef',
268+
data: {
269+
name: 'x',
270+
},
271+
line: 3,
272+
column: 12,
273+
},
274+
],
275+
},
276+
{
277+
code: `
278+
function Foo() {}
279+
<Foo {...x} />;
280+
`,
281+
parserOptions: {
282+
ecmaFeatures: {
283+
jsx: true,
284+
},
285+
},
286+
errors: [
287+
{
288+
messageId: 'undef',
289+
data: {
290+
name: 'x',
291+
},
292+
line: 3,
293+
column: 10,
294+
},
295+
],
296+
},
297+
{
298+
code: `
299+
function Foo() {}
300+
<Foo<T> />;
301+
`,
302+
parserOptions: {
303+
ecmaFeatures: {
304+
jsx: true,
305+
},
306+
},
307+
errors: [
308+
{
309+
messageId: 'undef',
310+
data: {
311+
name: 'T',
312+
},
313+
line: 3,
314+
column: 6,
315+
},
316+
],
317+
},
318+
{
319+
code: `
320+
function Foo() {}
321+
<Foo>{x}</Foo>;
322+
`,
323+
parserOptions: {
324+
ecmaFeatures: {
325+
jsx: true,
326+
},
327+
},
328+
errors: [
329+
{
330+
messageId: 'undef',
331+
data: {
332+
name: 'x',
333+
},
334+
line: 3,
335+
column: 7,
336+
},
337+
],
338+
},
178339
],
179340
});

packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,51 @@ ruleTester.run('consistent-type-imports', rule, {
180180
`,
181181
options: [{ prefer: 'no-type-imports' }],
182182
},
183+
// https://github.com/typescript-eslint/typescript-eslint/issues/2455
184+
{
185+
code: `
186+
import React from 'react';
187+
188+
export const ComponentFoo: React.FC = () => {
189+
return <div>Foo Foo</div>;
190+
};
191+
`,
192+
parserOptions: {
193+
ecmaFeatures: {
194+
jsx: true,
195+
},
196+
},
197+
},
198+
{
199+
code: `
200+
import { h } from 'some-other-jsx-lib';
201+
202+
export const ComponentFoo: h.FC = () => {
203+
return <div>Foo Foo</div>;
204+
};
205+
`,
206+
parserOptions: {
207+
ecmaFeatures: {
208+
jsx: true,
209+
},
210+
jsxPragma: 'h',
211+
},
212+
},
213+
{
214+
code: `
215+
import { Fragment } from 'react';
216+
217+
export const ComponentFoo: Fragment = () => {
218+
return <>Foo Foo</>;
219+
};
220+
`,
221+
parserOptions: {
222+
ecmaFeatures: {
223+
jsx: true,
224+
},
225+
jsxFragmentName: 'Fragment',
226+
},
227+
},
183228
],
184229
invalid: [
185230
{

packages/eslint-plugin/tests/rules/no-unused-vars.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,51 @@ export type Test<U> = U extends (arg: {
803803
? I
804804
: never;
805805
`,
806+
// https://github.com/typescript-eslint/typescript-eslint/issues/2455
807+
{
808+
code: `
809+
import React from 'react';
810+
811+
export const ComponentFoo: React.FC = () => {
812+
return <div>Foo Foo</div>;
813+
};
814+
`,
815+
parserOptions: {
816+
ecmaFeatures: {
817+
jsx: true,
818+
},
819+
},
820+
},
821+
{
822+
code: `
823+
import { h } from 'some-other-jsx-lib';
824+
825+
export const ComponentFoo: h.FC = () => {
826+
return <div>Foo Foo</div>;
827+
};
828+
`,
829+
parserOptions: {
830+
ecmaFeatures: {
831+
jsx: true,
832+
},
833+
jsxPragma: 'h',
834+
},
835+
},
836+
{
837+
code: `
838+
import { Fragment } from 'react';
839+
840+
export const ComponentFoo: Fragment = () => {
841+
return <>Foo Foo</>;
842+
};
843+
`,
844+
parserOptions: {
845+
ecmaFeatures: {
846+
jsx: true,
847+
},
848+
jsxFragmentName: 'Fragment',
849+
},
850+
},
806851
],
807852

808853
invalid: [
@@ -1325,5 +1370,59 @@ type Foo = Array<Foo>;
13251370
},
13261371
],
13271372
},
1373+
// https://github.com/typescript-eslint/typescript-eslint/issues/2455
1374+
{
1375+
code: `
1376+
import React from 'react';
1377+
import { Fragment } from 'react';
1378+
1379+
export const ComponentFoo = () => {
1380+
return <div>Foo Foo</div>;
1381+
};
1382+
`,
1383+
parserOptions: {
1384+
ecmaFeatures: {
1385+
jsx: true,
1386+
},
1387+
},
1388+
errors: [
1389+
{
1390+
messageId: 'unusedVar',
1391+
line: 3,
1392+
data: {
1393+
varName: 'Fragment',
1394+
action: 'defined',
1395+
additional: '',
1396+
},
1397+
},
1398+
],
1399+
},
1400+
{
1401+
code: `
1402+
import React from 'react';
1403+
import { h } from 'some-other-jsx-lib';
1404+
1405+
export const ComponentFoo = () => {
1406+
return <div>Foo Foo</div>;
1407+
};
1408+
`,
1409+
parserOptions: {
1410+
ecmaFeatures: {
1411+
jsx: true,
1412+
},
1413+
jsxPragma: 'h',
1414+
},
1415+
errors: [
1416+
{
1417+
messageId: 'unusedVar',
1418+
line: 2,
1419+
data: {
1420+
varName: 'React',
1421+
action: 'defined',
1422+
additional: '',
1423+
},
1424+
},
1425+
],
1426+
},
13281427
],
13291428
});

0 commit comments

Comments
 (0)