Skip to content
Permalink
Browse files

feat(scope-manager): add support for JSX scope analysis (#2498)

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 f887ab51f58c1b3571f9a14832864bc0ca59623f
Showing with 1,618 additions and 21 deletions.
  1. +2 −0 .cspell.json
  2. +161 −0 packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
  3. +45 −0 packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts
  4. +99 −0 packages/eslint-plugin/tests/rules/no-unused-vars.test.ts
  5. +26 −1 packages/parser/README.md
  6. +47 −14 packages/parser/src/parser.ts
  7. +40 −0 packages/parser/tests/lib/parser.ts
  8. +15 −0 packages/scope-manager/README.md
  9. +23 −3 packages/scope-manager/src/analyze.ts
  10. +2 −2 packages/scope-manager/src/referencer/Reference.ts
  11. +76 −0 packages/scope-manager/src/referencer/Referencer.ts
  12. +1 −1 packages/scope-manager/src/scope/ScopeBase.ts
  13. +2 −0 packages/scope-manager/tests/fixtures.test.ts
  14. +3 −0 packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx
  15. +65 −0 packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx.shot
  16. +4 −0 packages/scope-manager/tests/fixtures/jsx/attribute.tsx
  17. +91 −0 packages/scope-manager/tests/fixtures/jsx/attribute.tsx.shot
  18. +3 −0 packages/scope-manager/tests/fixtures/jsx/children.tsx
  19. +73 −0 packages/scope-manager/tests/fixtures/jsx/children.tsx.shot
  20. +6 −0 packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx
  21. +103 −0 packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx.shot
  22. +3 −0 packages/scope-manager/tests/fixtures/jsx/component.tsx
  23. +66 −0 packages/scope-manager/tests/fixtures/jsx/component.tsx.shot
  24. +6 −0 packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx
  25. +69 −0 packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx.shot
  26. +5 −0 packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx
  27. +55 −0 packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx.shot
  28. +5 −0 packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx
  29. +63 −0 packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx.shot
  30. +7 −0 packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx
  31. +79 −0 packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx.shot
  32. +8 −0 packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx
  33. +93 −0 packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx.shot
  34. +7 −0 packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx
  35. +77 −0 packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx.shot
  36. +3 −0 packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx
  37. +57 −0 packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx.shot
  38. +1 −0 packages/scope-manager/tests/fixtures/jsx/fragment.tsx
  39. +18 −0 packages/scope-manager/tests/fixtures/jsx/fragment.tsx.shot
  40. +3 −0 packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx
  41. +54 −0 packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx.shot
  42. +2 −0 packages/scope-manager/tests/fixtures/jsx/text.tsx
  43. +48 −0 packages/scope-manager/tests/fixtures/jsx/text.tsx.shot
  44. +2 −0 packages/types/src/parser-options.ts
@@ -75,6 +75,7 @@
"pluggable",
"postprocess",
"postprocessor",
"preact",
"Premade",
"prettier's",
"recurse",
@@ -88,6 +89,7 @@
"rulesets",
"serializers",
"superset",
"transpiling",
"thenables",
"transpiles",
"tsconfigs",
@@ -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: [
{
@@ -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,
},
],
},
],
});
@@ -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: [
{
@@ -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: [
@@ -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.