diff --git a/.cspell.json b/.cspell.json
index cbd5138affa4..36beb3bb328e 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -75,6 +75,7 @@
"pluggable",
"postprocess",
"postprocessor",
+ "preact",
"Premade",
"prettier's",
"recurse",
@@ -88,6 +89,7 @@
"rulesets",
"serializers",
"superset",
+ "transpiling",
"thenables",
"transpiles",
"tsconfigs",
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
index c87e5b4433e2..7af6497c62a6 100644
--- a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
@@ -140,6 +140,65 @@ function predicate(arg: any): asserts arg is T {
}
}
`,
+ {
+ code: `
+function Foo() {}
+;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: `
+type T = 1;
+function Foo() {}
+ />;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: `
+const x = 1;
+function Foo() {}
+;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: `
+const x = {};
+function Foo() {}
+;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: `
+const x = {};
+function Foo() {}
+{x};
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
],
invalid: [
{
@@ -175,5 +234,107 @@ function predicate(arg: any): asserts arg is T {
},
],
},
+ {
+ code: ';',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ errors: [
+ {
+ messageId: 'undef',
+ data: {
+ name: 'Foo',
+ },
+ line: 1,
+ column: 2,
+ },
+ ],
+ },
+ {
+ code: `
+function Foo() {}
+;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ errors: [
+ {
+ messageId: 'undef',
+ data: {
+ name: 'x',
+ },
+ line: 3,
+ column: 12,
+ },
+ ],
+ },
+ {
+ code: `
+function Foo() {}
+;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ errors: [
+ {
+ messageId: 'undef',
+ data: {
+ name: 'x',
+ },
+ line: 3,
+ column: 10,
+ },
+ ],
+ },
+ {
+ code: `
+function Foo() {}
+ />;
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ errors: [
+ {
+ messageId: 'undef',
+ data: {
+ name: 'T',
+ },
+ line: 3,
+ column: 6,
+ },
+ ],
+ },
+ {
+ code: `
+function Foo() {}
+{x};
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ errors: [
+ {
+ messageId: 'undef',
+ data: {
+ name: 'x',
+ },
+ line: 3,
+ column: 7,
+ },
+ ],
+ },
],
});
diff --git a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts
index 13112793f625..88d6db374a04 100644
--- a/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts
+++ b/packages/eslint-plugin/tests/rules/consistent-type-imports.test.ts
@@ -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 Foo Foo
;
+ };
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: `
+ import { h } from 'some-other-jsx-lib';
+
+ export const ComponentFoo: h.FC = () => {
+ return Foo Foo
;
+ };
+ `,
+ 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: [
{
diff --git a/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts b/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts
index bac969579d9f..837246f45835 100644
--- a/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unused-vars.test.ts
@@ -803,6 +803,51 @@ export type Test = 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 Foo Foo
;
+ };
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: `
+ import { h } from 'some-other-jsx-lib';
+
+ export const ComponentFoo: h.FC = () => {
+ return Foo Foo
;
+ };
+ `,
+ 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;
},
],
},
+ // https://github.com/typescript-eslint/typescript-eslint/issues/2455
+ {
+ code: `
+import React from 'react';
+import { Fragment } from 'react';
+
+export const ComponentFoo = () => {
+ return Foo Foo
;
+};
+ `,
+ 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 Foo Foo
;
+};
+ `,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ jsxPragma: 'h',
+ },
+ errors: [
+ {
+ messageId: 'unusedVar',
+ line: 2,
+ data: {
+ varName: 'React',
+ action: 'defined',
+ additional: '',
+ },
+ },
+ ],
+ },
],
});
diff --git a/packages/parser/README.md b/packages/parser/README.md
index 5a949425d9fb..f6417fd58896 100644
--- a/packages/parser/README.md
+++ b/packages/parser/README.md
@@ -54,6 +54,9 @@ interface ParserOptions {
globalReturn?: boolean;
};
ecmaVersion?: number;
+
+ jsxPragma?: string;
+ jsxFragmentName?: string | null;
lib?: string[];
project?: string | string[];
@@ -98,6 +101,27 @@ Accepts any valid ECMAScript version number:
Specifies the version of ECMAScript syntax you want to use. This is used by the parser to determine how to perform scope analysis, and it affects the default
+### `parserOptions.jsxPragma`
+
+Default `'React'`
+
+The identifier that's used for JSX Elements creation (after transpilation).
+If you're using a library other than React (like `preact`), then you should change this value.
+
+This should not be a member expression - just the root identifier (i.e. use `"React"` instead of `"React.createElement"`).
+
+If you provide `parserOptions.project`, you do not need to set this, as it will automatically detected from the compiler.
+
+### `parserOptions.jsxFragmentName`
+
+Default `null`
+
+The identifier that's used for JSX fragment elements (after transpilation).
+If `null`, assumes transpilation will always use a member of the configured `jsxPragma`.
+This should not be a member expression - just the root identifier (i.e. use `"h"` instead of `"h.Fragment"`).
+
+If you provide `parserOptions.project`, you do not need to set this, as it will automatically detected from the compiler.
+
### `parserOptions.lib`
Default `['es2018']`
@@ -105,7 +129,8 @@ Default `['es2018']`
For valid options, see the [TypeScript compiler options](https://www.typescriptlang.org/tsconfig#lib).
Specifies the TypeScript `lib`s that are available. This is used by the scope analyser to ensure there are global variables declared for the types exposed by TypeScript.
-If you provide `parserOptions.project`, you do not need to set this, as it will automatically detected from the compiler
+
+If you provide `parserOptions.project`, you do not need to set this, as it will automatically detected from the compiler.
### `parserOptions.project`
diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts
index 263779207c95..54dbd3997249 100644
--- a/packages/parser/src/parser.ts
+++ b/packages/parser/src/parser.ts
@@ -5,9 +5,13 @@ import {
TSESTreeOptions,
visitorKeys,
} from '@typescript-eslint/typescript-estree';
-import { analyze, ScopeManager } from '@typescript-eslint/scope-manager';
+import {
+ analyze,
+ AnalyzeOptions,
+ ScopeManager,
+} from '@typescript-eslint/scope-manager';
import debug from 'debug';
-import { ScriptTarget } from 'typescript';
+import { CompilerOptions, ScriptTarget } from 'typescript';
const log = debug('typescript-eslint:parser:parser');
@@ -33,8 +37,7 @@ function validateBoolean(
}
const LIB_FILENAME_REGEX = /lib\.(.+)\.d\.ts/;
-function getLib(services: ParserServices): Lib[] {
- const compilerOptions = services.program.getCompilerOptions();
+function getLib(compilerOptions: CompilerOptions): Lib[] {
if (compilerOptions.lib) {
return compilerOptions.lib
.map(lib => {
@@ -100,6 +103,14 @@ function parseForESLint(
useJSXTextNode: validateBoolean(options.useJSXTextNode, true),
jsx: validateBoolean(options.ecmaFeatures.jsx),
});
+ const analyzeOptions: AnalyzeOptions = {
+ ecmaVersion: options.ecmaVersion,
+ globalReturn: options.ecmaFeatures.globalReturn,
+ jsxPragma: options.jsxPragma,
+ jsxFragmentName: options.jsxFragmentName,
+ lib: options.lib,
+ sourceType: options.sourceType,
+ };
if (typeof options.filePath === 'string') {
const tsx = options.filePath.endsWith('.tsx');
@@ -123,18 +134,40 @@ function parseForESLint(
const { ast, services } = parseAndGenerateServices(code, parserOptions);
ast.sourceType = options.sourceType;
- // automatically apply the libs configured for the program
- if (services.hasFullTypeInformation && options.lib == null) {
- options.lib = getLib(services);
- log('Resolved libs from program: %o', options.lib);
+ if (services.hasFullTypeInformation) {
+ // automatically apply the options configured for the program
+ const compilerOptions = services.program.getCompilerOptions();
+ if (analyzeOptions.lib == null) {
+ analyzeOptions.lib = getLib(compilerOptions);
+ log('Resolved libs from program: %o', analyzeOptions.lib);
+ }
+ if (parserOptions.jsx === true) {
+ if (
+ analyzeOptions.jsxPragma === undefined &&
+ compilerOptions.jsxFactory != null
+ ) {
+ // in case the user has specified something like "preact.h"
+ const factory = compilerOptions.jsxFactory.split('.')[0].trim();
+ analyzeOptions.jsxPragma = factory;
+ log('Resolved jsxPragma from program: %s', analyzeOptions.jsxPragma);
+ }
+ if (
+ analyzeOptions.jsxFragmentName === undefined &&
+ compilerOptions.jsxFragmentFactory != null
+ ) {
+ // in case the user has specified something like "preact.Fragment"
+ const fragFactory = compilerOptions.jsxFragmentFactory
+ .split('.')[0]
+ .trim();
+ analyzeOptions.jsxFragmentName = fragFactory;
+ log(
+ 'Resolved jsxFragmentName from program: %s',
+ analyzeOptions.jsxFragmentName,
+ );
+ }
+ }
}
- const analyzeOptions = {
- ecmaVersion: options.ecmaVersion,
- globalReturn: options.ecmaFeatures.globalReturn,
- lib: options.lib,
- sourceType: options.sourceType,
- };
const scopeManager = analyze(ast, analyzeOptions);
return { ast, services, scopeManager, visitorKeys };
diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts
index ba4a1663c8ea..41e22a911f9b 100644
--- a/packages/parser/tests/lib/parser.ts
+++ b/packages/parser/tests/lib/parser.ts
@@ -1,5 +1,6 @@
import { TSESLint } from '@typescript-eslint/experimental-utils';
import * as typescriptESTree from '@typescript-eslint/typescript-estree/dist/parser';
+import * as scopeManager from '@typescript-eslint/scope-manager/dist/analyze';
import { parse, parseForESLint } from '../../src/parser';
describe('parser', () => {
@@ -70,4 +71,43 @@ describe('parser', () => {
warnOnUnsupportedTypeScriptVersion: false,
});
});
+
+ it('analyze() should be called with options', () => {
+ const code = 'const valid = true;';
+ const spy = jest.spyOn(scopeManager, 'analyze');
+ const config: TSESLint.ParserOptions = {
+ loc: false,
+ comment: false,
+ range: false,
+ tokens: false,
+ sourceType: 'module' as const,
+ ecmaVersion: 2018,
+ ecmaFeatures: {
+ globalReturn: false,
+ jsx: false,
+ },
+ // scope-manager specific
+ lib: ['dom.iterable'],
+ jsxPragma: 'Foo',
+ jsxFragmentName: 'Bar',
+ // ts-estree specific
+ filePath: 'isolated-file.src.ts',
+ project: 'tsconfig.json',
+ useJSXTextNode: false,
+ errorOnUnknownASTType: false,
+ errorOnTypeScriptSyntacticAndSemanticIssues: false,
+ tsconfigRootDir: 'tests/fixtures/services',
+ extraFileExtensions: ['.foo'],
+ };
+ parseForESLint(code, config);
+ expect(spy).toHaveBeenCalledTimes(1);
+ expect(spy).toHaveBeenLastCalledWith(expect.anything(), {
+ ecmaVersion: 2018,
+ globalReturn: false,
+ lib: ['dom.iterable'],
+ jsxPragma: 'Foo',
+ jsxFragmentName: 'Bar',
+ sourceType: 'module',
+ });
+ });
});
diff --git a/packages/scope-manager/README.md b/packages/scope-manager/README.md
index 6d389a32f319..24ccd839dff0 100644
--- a/packages/scope-manager/README.md
+++ b/packages/scope-manager/README.md
@@ -55,6 +55,21 @@ interface AnalyzeOptions {
*/
impliedStrict?: boolean;
+ /**
+ * The identifier that's used for JSX Element creation (after transpilation).
+ * This should not be a member expression - just the root identifier (i.e. use "React" instead of "React.createElement").
+ * Defaults to `"React"`.
+ */
+ jsxPragma?: string;
+
+ /**
+ * The identifier that's used for JSX fragment elements (after transpilation).
+ * If `null`, assumes transpilation will always use a member on `jsxFactory` (i.e. React.Fragment).
+ * This should not be a member expression - just the root identifier (i.e. use "h" instead of "h.Fragment").
+ * Defaults to `null`.
+ */
+ jsxFragmentName?: string | null;
+
/**
* The lib used by the project.
* This automatically defines a type variable for any types provided by the configured TS libs.
diff --git a/packages/scope-manager/src/analyze.ts b/packages/scope-manager/src/analyze.ts
index ee3ac77de96a..9e734925da19 100644
--- a/packages/scope-manager/src/analyze.ts
+++ b/packages/scope-manager/src/analyze.ts
@@ -33,6 +33,21 @@ interface AnalyzeOptions {
*/
impliedStrict?: boolean;
+ /**
+ * The identifier that's used for JSX Element creation (after transpilation).
+ * This should not be a member expression - just the root identifier (i.e. use "React" instead of "React.createElement").
+ * Defaults to `"React"`.
+ */
+ jsxPragma?: string;
+
+ /**
+ * The identifier that's used for JSX fragment elements (after transpilation).
+ * If `null`, assumes transpilation will always use a member on `jsxFactory` (i.e. React.Fragment).
+ * This should not be a member expression - just the root identifier (i.e. use "h" instead of "h.Fragment").
+ * Defaults to `null`.
+ */
+ jsxFragmentName?: string | null;
+
/**
* The lib used by the project.
* This automatically defines a type variable for any types provided by the configured TS libs.
@@ -53,6 +68,8 @@ const DEFAULT_OPTIONS: Required = {
ecmaVersion: 2018,
globalReturn: false,
impliedStrict: false,
+ jsxPragma: 'React',
+ jsxFragmentName: null,
lib: ['es2018'],
sourceType: 'script',
};
@@ -78,13 +95,16 @@ function analyze(
const ecmaVersion =
providedOptions?.ecmaVersion ?? DEFAULT_OPTIONS.ecmaVersion;
const options: Required = {
+ childVisitorKeys:
+ providedOptions?.childVisitorKeys ?? DEFAULT_OPTIONS.childVisitorKeys,
+ ecmaVersion,
globalReturn: providedOptions?.globalReturn ?? DEFAULT_OPTIONS.globalReturn,
impliedStrict:
providedOptions?.impliedStrict ?? DEFAULT_OPTIONS.impliedStrict,
+ jsxPragma: providedOptions?.jsxPragma ?? DEFAULT_OPTIONS.jsxPragma,
+ jsxFragmentName:
+ providedOptions?.jsxFragmentName ?? DEFAULT_OPTIONS.jsxFragmentName,
sourceType: providedOptions?.sourceType ?? DEFAULT_OPTIONS.sourceType,
- ecmaVersion,
- childVisitorKeys:
- providedOptions?.childVisitorKeys ?? DEFAULT_OPTIONS.childVisitorKeys,
lib: providedOptions?.lib ?? [mapEcmaVersion(ecmaVersion)],
};
diff --git a/packages/scope-manager/src/referencer/Reference.ts b/packages/scope-manager/src/referencer/Reference.ts
index 5786c5017300..9cf624f82a18 100644
--- a/packages/scope-manager/src/referencer/Reference.ts
+++ b/packages/scope-manager/src/referencer/Reference.ts
@@ -43,7 +43,7 @@ class Reference {
* Identifier syntax node.
* @public
*/
- public readonly identifier: TSESTree.Identifier;
+ public readonly identifier: TSESTree.Identifier | TSESTree.JSXIdentifier;
/**
* `true` if this writing reference is a variable initializer or a default value.
* @public
@@ -82,7 +82,7 @@ class Reference {
}
constructor(
- identifier: TSESTree.Identifier,
+ identifier: TSESTree.Identifier | TSESTree.JSXIdentifier,
scope: Scope,
flag: ReferenceFlag,
writeExpr?: TSESTree.Node | null,
diff --git a/packages/scope-manager/src/referencer/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts
index c0c49ef9728b..bd88b6a59966 100644
--- a/packages/scope-manager/src/referencer/Referencer.ts
+++ b/packages/scope-manager/src/referencer/Referencer.ts
@@ -22,18 +22,26 @@ import { lib as TSLibraries } from '../lib';
import { Scope, GlobalScope } from '../scope';
interface ReferencerOptions extends VisitorOptions {
+ jsxPragma: string;
+ jsxFragmentName: string | null;
lib: Lib[];
}
// Referencing variables and creating bindings.
class Referencer extends Visitor {
#isInnerMethodDefinition: boolean;
+ #jsxPragma: string;
+ #jsxFragmentName: string | null;
+ #hasReferencedJsxFactory = false;
+ #hasReferencedJsxFragmentFactory = false;
#lib: Lib[];
public readonly scopeManager: ScopeManager;
constructor(options: ReferencerOptions, scopeManager: ScopeManager) {
super(options);
this.scopeManager = scopeManager;
+ this.#jsxPragma = options.jsxPragma;
+ this.#jsxFragmentName = options.jsxFragmentName;
this.#lib = options.lib;
this.#isInnerMethodDefinition = false;
}
@@ -99,6 +107,46 @@ class Referencer extends Visitor {
}
}
+ /**
+ * Searches for a variable named "name" in the upper scopes and adds a pseudo-reference from itself to itself
+ */
+ private referenceInSomeUpperScope(name: string): boolean {
+ let scope = this.scopeManager.currentScope;
+ while (scope) {
+ const variable = scope.set.get(name);
+ if (!variable) {
+ scope = scope.upper;
+ continue;
+ }
+
+ scope.referenceValue(variable.identifiers[0]);
+ return true;
+ }
+
+ return false;
+ }
+
+ private referenceJsxPragma(): void {
+ if (this.#hasReferencedJsxFactory) {
+ return;
+ }
+ this.#hasReferencedJsxFactory = this.referenceInSomeUpperScope(
+ this.#jsxPragma,
+ );
+ }
+
+ private referenceJsxFragment(): void {
+ if (
+ this.#jsxFragmentName === null ||
+ this.#hasReferencedJsxFragmentFactory
+ ) {
+ return;
+ }
+ this.#hasReferencedJsxFragmentFactory = this.referenceInSomeUpperScope(
+ this.#jsxFragmentName,
+ );
+ }
+
///////////////////
// Visit helpers //
///////////////////
@@ -498,6 +546,34 @@ class Referencer extends Visitor {
ImportVisitor.visit(this, node);
}
+ protected JSXAttribute(node: TSESTree.JSXAttribute): void {
+ this.visit(node.value);
+ }
+
+ protected JSXFragment(node: TSESTree.JSXFragment): void {
+ this.referenceJsxPragma();
+ this.referenceJsxFragment();
+ this.visitChildren(node);
+ }
+
+ protected JSXIdentifier(node: TSESTree.JSXIdentifier): void {
+ this.currentScope().referenceValue(node);
+ }
+
+ protected JSXMemberExpression(node: TSESTree.JSXMemberExpression): void {
+ this.visit(node.object);
+ // we don't ever reference the property as it's always going to be a property on the thing
+ }
+
+ protected JSXOpeningElement(node: TSESTree.JSXOpeningElement): void {
+ this.referenceJsxPragma();
+ this.visit(node.name);
+ this.visitType(node.typeParameters);
+ for (const attr of node.attributes) {
+ this.visit(attr);
+ }
+ }
+
protected LabeledStatement(node: TSESTree.LabeledStatement): void {
this.visit(node.body);
}
diff --git a/packages/scope-manager/src/scope/ScopeBase.ts b/packages/scope-manager/src/scope/ScopeBase.ts
index cc38cdb3f97d..9b852dcff6ad 100644
--- a/packages/scope-manager/src/scope/ScopeBase.ts
+++ b/packages/scope-manager/src/scope/ScopeBase.ts
@@ -446,7 +446,7 @@ abstract class ScopeBase<
}
public referenceValue(
- node: TSESTree.Identifier,
+ node: TSESTree.Identifier | TSESTree.JSXIdentifier,
assign: ReferenceFlag = ReferenceFlag.Read,
writeExpr?: TSESTree.Expression | null,
maybeImplicitGlobal?: ReferenceImplicitGlobal | null,
diff --git a/packages/scope-manager/tests/fixtures.test.ts b/packages/scope-manager/tests/fixtures.test.ts
index 24bd0fd80f0b..08eea4f4995e 100644
--- a/packages/scope-manager/tests/fixtures.test.ts
+++ b/packages/scope-manager/tests/fixtures.test.ts
@@ -41,6 +41,8 @@ const ALLOWED_OPTIONS: Map = new Map<
['ecmaVersion', ['number']],
['globalReturn', ['boolean']],
['impliedStrict', ['boolean']],
+ ['jsxPragma', ['string']],
+ ['jsxFragmentName', ['string']],
['sourceType', ['string', new Set(['module', 'script'])]],
]);
diff --git a/packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx b/packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx
new file mode 100644
index 000000000000..b501cbc9b17f
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx
@@ -0,0 +1,3 @@
+const x = {};
+
+;
diff --git a/packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx.shot
new file mode 100644
index 000000000000..fd34f473d78d
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/attribute-spread.tsx.shot
@@ -0,0 +1,65 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx attribute-spread 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ VariableDefinition$1 {
+ name: Identifier<"x">,
+ node: VariableDeclarator$1,
+ },
+ ],
+ name: "x",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"x">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$1,
+ writeExpr: ObjectExpression$2,
+ },
+ Reference$3 {
+ identifier: Identifier<"x">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ Reference$2 {
+ identifier: JSXIdentifier$4,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ Reference$3,
+ ],
+ set: Map {
+ "x" => Variable$1,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/attribute.tsx b/packages/scope-manager/tests/fixtures/jsx/attribute.tsx
new file mode 100644
index 000000000000..e915453933d4
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/attribute.tsx
@@ -0,0 +1,4 @@
+const x = 1;
+const attr = 2; // should be unreferenced
+
+;
diff --git a/packages/scope-manager/tests/fixtures/jsx/attribute.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/attribute.tsx.shot
new file mode 100644
index 000000000000..a87647863181
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/attribute.tsx.shot
@@ -0,0 +1,91 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx attribute 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ VariableDefinition$1 {
+ name: Identifier<"x">,
+ node: VariableDeclarator$1,
+ },
+ ],
+ name: "x",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"x">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$1,
+ writeExpr: Literal$2,
+ },
+ Reference$4 {
+ identifier: Identifier<"x">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ Variable$2 {
+ defs: Array [
+ VariableDefinition$2 {
+ name: Identifier<"attr">,
+ node: VariableDeclarator$3,
+ },
+ ],
+ name: "attr",
+ references: Array [
+ Reference$2 {
+ identifier: Identifier<"attr">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$2,
+ writeExpr: Literal$4,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$5,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ Reference$2,
+ Reference$3 {
+ identifier: JSXIdentifier$6,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ Reference$4,
+ ],
+ set: Map {
+ "x" => Variable$1,
+ "attr" => Variable$2,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ Variable$2,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/children.tsx b/packages/scope-manager/tests/fixtures/jsx/children.tsx
new file mode 100644
index 000000000000..eea4145f68d1
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/children.tsx
@@ -0,0 +1,3 @@
+const child = 1;
+
+{child};
diff --git a/packages/scope-manager/tests/fixtures/jsx/children.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/children.tsx.shot
new file mode 100644
index 000000000000..9fc1dfb49539
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/children.tsx.shot
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx children 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ VariableDefinition$1 {
+ name: Identifier<"child">,
+ node: VariableDeclarator$1,
+ },
+ ],
+ name: "child",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"child">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$1,
+ writeExpr: Literal$2,
+ },
+ Reference$3 {
+ identifier: Identifier<"child">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ Reference$2 {
+ identifier: JSXIdentifier$4,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ Reference$3,
+ Reference$4 {
+ identifier: JSXIdentifier$5,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ ],
+ set: Map {
+ "child" => Variable$1,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx b/packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx
new file mode 100644
index 000000000000..fa0a0f915212
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx
@@ -0,0 +1,6 @@
+const X = {
+ Foo() {},
+};
+const Foo = 1; // should be unreferenced
+
+;
diff --git a/packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx.shot
new file mode 100644
index 000000000000..c5bc7eb9d4e3
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/component-namespaced.tsx.shot
@@ -0,0 +1,103 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx component-namespaced 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ VariableDefinition$1 {
+ name: Identifier<"X">,
+ node: VariableDeclarator$1,
+ },
+ ],
+ name: "X",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"X">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$1,
+ writeExpr: ObjectExpression$2,
+ },
+ Reference$3 {
+ identifier: JSXIdentifier$3,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ Variable$2 {
+ defs: Array [],
+ name: "arguments",
+ references: Array [],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ Variable$3 {
+ defs: Array [
+ VariableDefinition$2 {
+ name: Identifier<"Foo">,
+ node: VariableDeclarator$4,
+ },
+ ],
+ name: "Foo",
+ references: Array [
+ Reference$2 {
+ identifier: Identifier<"Foo">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$3,
+ writeExpr: Literal$5,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$6,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ Reference$2,
+ Reference$3,
+ ],
+ set: Map {
+ "X" => Variable$1,
+ "Foo" => Variable$3,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ Variable$3,
+ ],
+ },
+ FunctionScope$2 {
+ block: FunctionExpression$7,
+ isStrict: false,
+ references: Array [],
+ set: Map {
+ "arguments" => Variable$2,
+ },
+ type: "function",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$2,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/component.tsx b/packages/scope-manager/tests/fixtures/jsx/component.tsx
new file mode 100644
index 000000000000..20f898b540ca
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/component.tsx
@@ -0,0 +1,3 @@
+function Foo() {}
+
+;
diff --git a/packages/scope-manager/tests/fixtures/jsx/component.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/component.tsx.shot
new file mode 100644
index 000000000000..a37331e7f7e3
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/component.tsx.shot
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx component 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ FunctionNameDefinition$1 {
+ name: Identifier<"Foo">,
+ node: FunctionDeclaration$1,
+ },
+ ],
+ name: "Foo",
+ references: Array [
+ Reference$1 {
+ identifier: JSXIdentifier$2,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ Variable$2 {
+ defs: Array [],
+ name: "arguments",
+ references: Array [],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ ],
+ set: Map {
+ "Foo" => Variable$1,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ FunctionScope$2 {
+ block: FunctionDeclaration$1,
+ isStrict: false,
+ references: Array [],
+ set: Map {
+ "arguments" => Variable$2,
+ },
+ type: "function",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$2,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx
new file mode 100644
index 000000000000..de94f8168c85
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx
@@ -0,0 +1,6 @@
+//// @sourceType = 'module'
+
+import React from 'react';
+import { Fragment } from 'react'; // should be unreferenced
+
+<>>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx.shot
new file mode 100644
index 000000000000..76e5b24ecc8d
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxFragmentName.tsx.shot
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx factory default-jsxFragmentName 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ ImportBindingDefinition$1 {
+ name: Identifier<"React">,
+ node: ImportDefaultSpecifier$1,
+ },
+ ],
+ name: "React",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"React">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ Variable$2 {
+ defs: Array [
+ ImportBindingDefinition$2 {
+ name: Identifier<"Fragment">,
+ node: ImportSpecifier$2,
+ },
+ ],
+ name: "Fragment",
+ references: Array [],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ModuleScope$2 {
+ block: Program$3,
+ isStrict: true,
+ references: Array [
+ Reference$1,
+ ],
+ set: Map {
+ "React" => Variable$1,
+ "Fragment" => Variable$2,
+ },
+ type: "module",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$1,
+ Variable$2,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx
new file mode 100644
index 000000000000..8fce679e7467
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx
@@ -0,0 +1,5 @@
+//// @sourceType = 'module'
+
+import React from 'react';
+
+<>>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx.shot
new file mode 100644
index 000000000000..4b7ea0925e15
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma-fragment.tsx.shot
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx factory default-jsxPragma-fragment 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ ImportBindingDefinition$1 {
+ name: Identifier<"React">,
+ node: ImportDefaultSpecifier$1,
+ },
+ ],
+ name: "React",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"React">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$2,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ModuleScope$2 {
+ block: Program$2,
+ isStrict: true,
+ references: Array [
+ Reference$1,
+ ],
+ set: Map {
+ "React" => Variable$1,
+ },
+ type: "module",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx
new file mode 100644
index 000000000000..bf3f50888853
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx
@@ -0,0 +1,5 @@
+//// @sourceType = 'module'
+
+import React from 'react';
+
+;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx.shot
new file mode 100644
index 000000000000..4c7c51c772e8
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/default-jsxPragma.tsx.shot
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx factory default-jsxPragma 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ ImportBindingDefinition$1 {
+ name: Identifier<"React">,
+ node: ImportDefaultSpecifier$1,
+ },
+ ],
+ name: "React",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"React">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$2,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ModuleScope$2 {
+ block: Program$2,
+ isStrict: true,
+ references: Array [
+ Reference$1,
+ Reference$2 {
+ identifier: JSXIdentifier$3,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ ],
+ set: Map {
+ "React" => Variable$1,
+ },
+ type: "module",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx b/packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx
new file mode 100644
index 000000000000..22df1a693d41
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx
@@ -0,0 +1,7 @@
+//// @sourceType = 'module'
+//// @jsxFragmentName = 'Fragment'
+
+import React from 'react'; // should be unreferenced
+import { Fragment } from 'preact';
+
+<>>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx.shot
new file mode 100644
index 000000000000..8392610d9a68
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/jsxFragmentName.tsx.shot
@@ -0,0 +1,79 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx factory jsxFragmentName 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ ImportBindingDefinition$1 {
+ name: Identifier<"React">,
+ node: ImportDefaultSpecifier$1,
+ },
+ ],
+ name: "React",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"React">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ Variable$2 {
+ defs: Array [
+ ImportBindingDefinition$2 {
+ name: Identifier<"Fragment">,
+ node: ImportSpecifier$2,
+ },
+ ],
+ name: "Fragment",
+ references: Array [
+ Reference$2 {
+ identifier: Identifier<"Fragment">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$2,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ModuleScope$2 {
+ block: Program$3,
+ isStrict: true,
+ references: Array [
+ Reference$1,
+ Reference$2,
+ ],
+ set: Map {
+ "React" => Variable$1,
+ "Fragment" => Variable$2,
+ },
+ type: "module",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$1,
+ Variable$2,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx
new file mode 100644
index 000000000000..e4bd844170bf
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx
@@ -0,0 +1,8 @@
+//// @sourceType = 'module'
+//// @jsxPragma = 'h'
+//// @jsxFragmentName = 'Fragment'
+
+import React from 'react'; // should be unreferenced
+import { h, Fragment } from 'preact';
+
+<>>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx.shot
new file mode 100644
index 000000000000..5dc43c1d89f7
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma-jsxFragmentName.tsx.shot
@@ -0,0 +1,93 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx factory jsxPragma-jsxFragmentName 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ ImportBindingDefinition$1 {
+ name: Identifier<"React">,
+ node: ImportDefaultSpecifier$1,
+ },
+ ],
+ name: "React",
+ references: Array [],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ Variable$2 {
+ defs: Array [
+ ImportBindingDefinition$2 {
+ name: Identifier<"h">,
+ node: ImportSpecifier$2,
+ },
+ ],
+ name: "h",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"h">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$2,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ Variable$3 {
+ defs: Array [
+ ImportBindingDefinition$3 {
+ name: Identifier<"Fragment">,
+ node: ImportSpecifier$3,
+ },
+ ],
+ name: "Fragment",
+ references: Array [
+ Reference$2 {
+ identifier: Identifier<"Fragment">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$3,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$4,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ModuleScope$2 {
+ block: Program$4,
+ isStrict: true,
+ references: Array [
+ Reference$1,
+ Reference$2,
+ ],
+ set: Map {
+ "React" => Variable$1,
+ "h" => Variable$2,
+ "Fragment" => Variable$3,
+ },
+ type: "module",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$1,
+ Variable$2,
+ Variable$3,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx
new file mode 100644
index 000000000000..61a64b504b97
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx
@@ -0,0 +1,7 @@
+//// @sourceType = 'module'
+//// @jsxPragma = 'h'
+
+import React from 'react'; // should be unreferenced
+import { h } from 'preact';
+
+;
diff --git a/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx.shot
new file mode 100644
index 000000000000..1fb48ba9b059
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/factory/jsxPragma.tsx.shot
@@ -0,0 +1,77 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx factory jsxPragma 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ ImportBindingDefinition$1 {
+ name: Identifier<"React">,
+ node: ImportDefaultSpecifier$1,
+ },
+ ],
+ name: "React",
+ references: Array [],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ Variable$2 {
+ defs: Array [
+ ImportBindingDefinition$2 {
+ name: Identifier<"h">,
+ node: ImportSpecifier$2,
+ },
+ ],
+ name: "h",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"h">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$2,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ModuleScope$2 {
+ block: Program$3,
+ isStrict: true,
+ references: Array [
+ Reference$1,
+ Reference$2 {
+ identifier: JSXIdentifier$4,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ ],
+ set: Map {
+ "React" => Variable$1,
+ "h" => Variable$2,
+ },
+ type: "module",
+ upper: GlobalScope$1,
+ variables: Array [
+ Variable$1,
+ Variable$2,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx b/packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx
new file mode 100644
index 000000000000..eb4c293d13bd
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx
@@ -0,0 +1,3 @@
+const child = 1;
+
+<>{child}>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx.shot
new file mode 100644
index 000000000000..ada53e07e796
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/fragment-children.tsx.shot
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx fragment-children 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ VariableDefinition$1 {
+ name: Identifier<"child">,
+ node: VariableDeclarator$1,
+ },
+ ],
+ name: "child",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"child">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$1,
+ writeExpr: Literal$2,
+ },
+ Reference$2 {
+ identifier: Identifier<"child">,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ Reference$2,
+ ],
+ set: Map {
+ "child" => Variable$1,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/fragment.tsx b/packages/scope-manager/tests/fixtures/jsx/fragment.tsx
new file mode 100644
index 000000000000..ef5e491259c3
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/fragment.tsx
@@ -0,0 +1 @@
+<>>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/fragment.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/fragment.tsx.shot
new file mode 100644
index 000000000000..7bf20feb1aad
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/fragment.tsx.shot
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx fragment 1`] = `
+ScopeManager {
+ variables: Array [],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$1,
+ isStrict: false,
+ references: Array [],
+ set: Map {},
+ type: "global",
+ upper: null,
+ variables: Array [],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx b/packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx
new file mode 100644
index 000000000000..af9a714e5ac4
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx
@@ -0,0 +1,3 @@
+type T = 1;
+
+ />;
diff --git a/packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx.shot
new file mode 100644
index 000000000000..1ea1b8dbca3a
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/generic-type-param.tsx.shot
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx generic-type-param 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ TypeDefinition$1 {
+ name: Identifier<"T">,
+ node: TSTypeAliasDeclaration$1,
+ },
+ ],
+ name: "T",
+ references: Array [
+ Reference$2 {
+ identifier: Identifier<"T">,
+ isRead: true,
+ isTypeReference: true,
+ isValueReference: false,
+ isWrite: false,
+ resolved: Variable$1,
+ },
+ ],
+ isValueVariable: false,
+ isTypeVariable: true,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$2,
+ isStrict: false,
+ references: Array [
+ Reference$1 {
+ identifier: JSXIdentifier$3,
+ isRead: true,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: false,
+ resolved: null,
+ },
+ Reference$2,
+ ],
+ set: Map {
+ "T" => Variable$1,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/scope-manager/tests/fixtures/jsx/text.tsx b/packages/scope-manager/tests/fixtures/jsx/text.tsx
new file mode 100644
index 000000000000..a5bb8d61130c
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/text.tsx
@@ -0,0 +1,2 @@
+const Foo = 1; // should be unreferenced
+<>Foo>;
diff --git a/packages/scope-manager/tests/fixtures/jsx/text.tsx.shot b/packages/scope-manager/tests/fixtures/jsx/text.tsx.shot
new file mode 100644
index 000000000000..b901d67292f1
--- /dev/null
+++ b/packages/scope-manager/tests/fixtures/jsx/text.tsx.shot
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`jsx text 1`] = `
+ScopeManager {
+ variables: Array [
+ Variable$1 {
+ defs: Array [
+ VariableDefinition$1 {
+ name: Identifier<"Foo">,
+ node: VariableDeclarator$1,
+ },
+ ],
+ name: "Foo",
+ references: Array [
+ Reference$1 {
+ identifier: Identifier<"Foo">,
+ init: true,
+ isRead: false,
+ isTypeReference: false,
+ isValueReference: true,
+ isWrite: true,
+ resolved: Variable$1,
+ writeExpr: Literal$2,
+ },
+ ],
+ isValueVariable: true,
+ isTypeVariable: false,
+ },
+ ],
+ scopes: Array [
+ GlobalScope$1 {
+ block: Program$3,
+ isStrict: false,
+ references: Array [
+ Reference$1,
+ ],
+ set: Map {
+ "Foo" => Variable$1,
+ },
+ type: "global",
+ upper: null,
+ variables: Array [
+ Variable$1,
+ ],
+ },
+ ],
+}
+`;
diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts
index c9623688e169..898f6583ef66 100644
--- a/packages/types/src/parser-options.ts
+++ b/packages/types/src/parser-options.ts
@@ -28,6 +28,8 @@ interface ParserOptions {
ecmaVersion?: EcmaVersion;
// scope-manager specific
+ jsxPragma?: string;
+ jsxFragmentName?: string | null;
lib?: Lib[];
// typescript-estree specific