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.
This commit is contained in:
Brad Zacher
2020-09-05 20:20:48 -07:00
committed by GitHub
parent 81cf2d4d2d
commit 805ad0fc4f
44 changed files with 1618 additions and 21 deletions
+2
View File
@@ -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: '',
},
},
],
},
],
});
+26 -1
View File
@@ -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`
+47 -14
View File
@@ -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 };
+40
View File
@@ -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',
});
});
});
+15
View File
@@ -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.
+23 -3
View File
@@ -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<AnalyzeOptions> = {
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<AnalyzeOptions> = {
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)],
};
@@ -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,
@@ -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);
}
@@ -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,
@@ -41,6 +41,8 @@ const ALLOWED_OPTIONS: Map<string, ALLOWED_VALUE> = new Map<
['ecmaVersion', ['number']],
['globalReturn', ['boolean']],
['impliedStrict', ['boolean']],
['jsxPragma', ['string']],
['jsxFragmentName', ['string']],
['sourceType', ['string', new Set(['module', 'script'])]],
]);
@@ -0,0 +1,3 @@
const x = {};
<Foo {...x} />;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,4 @@
const x = 1;
const attr = 2; // should be unreferenced
<Foo attr={x} />;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,3 @@
const child = 1;
<Foo>{child}</Foo>;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,6 @@
const X = {
Foo() {},
};
const Foo = 1; // should be unreferenced
<X.Foo />;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,3 @@
function Foo() {}
<Foo />;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,6 @@
//// @sourceType = 'module'
import React from 'react';
import { Fragment } from 'react'; // should be unreferenced
<></>;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,5 @@
//// @sourceType = 'module'
import React from 'react';
<></>;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,5 @@
//// @sourceType = 'module'
import React from 'react';
<Foo />;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,7 @@
//// @sourceType = 'module'
//// @jsxFragmentName = 'Fragment'
import React from 'react'; // should be unreferenced
import { Fragment } from 'preact';
<></>;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,8 @@
//// @sourceType = 'module'
//// @jsxPragma = 'h'
//// @jsxFragmentName = 'Fragment'
import React from 'react'; // should be unreferenced
import { h, Fragment } from 'preact';
<></>;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,7 @@
//// @sourceType = 'module'
//// @jsxPragma = 'h'
import React from 'react'; // should be unreferenced
import { h } from 'preact';
<Foo />;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1,3 @@
const child = 1;
<>{child}</>;
@@ -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,
],
},
],
}
`;
@@ -0,0 +1 @@
<></>;
@@ -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 [],
},
],
}
`;
@@ -0,0 +1,3 @@
type T = 1;
<Foo<T> />;
@@ -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,
],
},
],
}
`;
+2
View File
@@ -0,0 +1,2 @@
const Foo = 1; // should be unreferenced
<>Foo</>;
+48
View File
@@ -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,
],
},
],
}
`;
+2
View File
@@ -28,6 +28,8 @@ interface ParserOptions {
ecmaVersion?: EcmaVersion;
// scope-manager specific
jsxPragma?: string;
jsxFragmentName?: string | null;
lib?: Lib[];
// typescript-estree specific