Skip to content

Commit

Permalink
feat(scope-manager): add support for JSX scope analysis (#2498)
Browse files Browse the repository at this point in the history
Fixes #2455
And part of #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 #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.
  • Loading branch information
bradzacher committed Sep 6, 2020
1 parent 95f6bf4 commit f887ab5
Show file tree
Hide file tree
Showing 44 changed files with 1,618 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Expand Up @@ -75,6 +75,7 @@
"pluggable",
"postprocess",
"postprocessor",
"preact",
"Premade",
"prettier's",
"recurse",
Expand All @@ -88,6 +89,7 @@
"rulesets",
"serializers",
"superset",
"transpiling",
"thenables",
"transpiles",
"tsconfigs",
Expand Down
161 changes: 161 additions & 0 deletions packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
Expand Up @@ -140,6 +140,65 @@ function predicate(arg: any): asserts arg is T {
}
}
`,
{
code: `
function Foo() {}
<Foo />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
{
code: `
type T = 1;
function Foo() {}
<Foo<T> />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
{
code: `
const x = 1;
function Foo() {}
<Foo attr={x} />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
{
code: `
const x = {};
function Foo() {}
<Foo {...x} />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
{
code: `
const x = {};
function Foo() {}
<Foo>{x}</Foo>;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
],
invalid: [
{
Expand Down Expand Up @@ -175,5 +234,107 @@ function predicate(arg: any): asserts arg is T {
},
],
},
{
code: '<Foo />;',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
errors: [
{
messageId: 'undef',
data: {
name: 'Foo',
},
line: 1,
column: 2,
},
],
},
{
code: `
function Foo() {}
<Foo attr={x} />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
errors: [
{
messageId: 'undef',
data: {
name: 'x',
},
line: 3,
column: 12,
},
],
},
{
code: `
function Foo() {}
<Foo {...x} />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
errors: [
{
messageId: 'undef',
data: {
name: 'x',
},
line: 3,
column: 10,
},
],
},
{
code: `
function Foo() {}
<Foo<T> />;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
errors: [
{
messageId: 'undef',
data: {
name: 'T',
},
line: 3,
column: 6,
},
],
},
{
code: `
function Foo() {}
<Foo>{x}</Foo>;
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
errors: [
{
messageId: 'undef',
data: {
name: 'x',
},
line: 3,
column: 7,
},
],
},
],
});
45 changes: 45 additions & 0 deletions packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts
Expand Up @@ -180,6 +180,51 @@ ruleTester.run('consistent-type-imports', rule, {
`,
options: [{ prefer: 'no-type-imports' }],
},
// https://github.com/typescript-eslint/typescript-eslint/issues/2455
{
code: `
import React from 'react';
export const ComponentFoo: React.FC = () => {
return <div>Foo Foo</div>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
{
code: `
import { h } from 'some-other-jsx-lib';
export const ComponentFoo: h.FC = () => {
return <div>Foo Foo</div>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxPragma: 'h',
},
},
{
code: `
import { Fragment } from 'react';
export const ComponentFoo: Fragment = () => {
return <>Foo Foo</>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxFragmentName: 'Fragment',
},
},
],
invalid: [
{
Expand Down
99 changes: 99 additions & 0 deletions packages/eslint-plugin/tests/rules/no-unused-vars.test.ts
Expand Up @@ -803,6 +803,51 @@ export type Test<U> = U extends (arg: {
? I
: never;
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/2455
{
code: `
import React from 'react';
export const ComponentFoo: React.FC = () => {
return <div>Foo Foo</div>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
{
code: `
import { h } from 'some-other-jsx-lib';
export const ComponentFoo: h.FC = () => {
return <div>Foo Foo</div>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxPragma: 'h',
},
},
{
code: `
import { Fragment } from 'react';
export const ComponentFoo: Fragment = () => {
return <>Foo Foo</>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxFragmentName: 'Fragment',
},
},
],

invalid: [
Expand Down Expand Up @@ -1325,5 +1370,59 @@ type Foo = Array<Foo>;
},
],
},
// https://github.com/typescript-eslint/typescript-eslint/issues/2455
{
code: `
import React from 'react';
import { Fragment } from 'react';
export const ComponentFoo = () => {
return <div>Foo Foo</div>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
errors: [
{
messageId: 'unusedVar',
line: 3,
data: {
varName: 'Fragment',
action: 'defined',
additional: '',
},
},
],
},
{
code: `
import React from 'react';
import { h } from 'some-other-jsx-lib';
export const ComponentFoo = () => {
return <div>Foo Foo</div>;
};
`,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
jsxPragma: 'h',
},
errors: [
{
messageId: 'unusedVar',
line: 2,
data: {
varName: 'React',
action: 'defined',
additional: '',
},
},
],
},
],
});

0 comments on commit f887ab5

Please sign in to comment.