📄 classScopeAnalyzer¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 27 |
| 🧱 Classes | 4 |
| 📦 Imports | 15 |
| 📐 Interfaces | 1 |
| 📑 Type Aliases | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/util/class-scope-analyzer/classScopeAnalyzer.ts
📦 Imports¶
| Name | Source |
|---|---|
Scope |
@typescript-eslint/scope-manager |
ScopeManager |
@typescript-eslint/scope-manager |
Variable |
@typescript-eslint/scope-manager |
TSESTree |
@typescript-eslint/utils |
Visitor |
@typescript-eslint/scope-manager |
AST_NODE_TYPES |
@typescript-eslint/utils |
ClassNode |
./types |
Key |
./types |
MemberNode |
./types |
nullThrows |
.. |
NullThrowsReasons |
.. |
extractNameForMember |
./extractComputedName |
extractNameForMemberExpression |
./extractComputedName |
privateKey |
./types |
publicKey |
./types |
Functions¶
Member.create(node: MemberNode): Member | null¶
Parameters:
nodeMemberNode
Returns: Member | null
Calls:
extractNameForMember (from ./extractComputedName)
Code
Member.isAccessor(): boolean¶
Returns: boolean
Code
public isAccessor(): boolean {
if (
this.node.type === AST_NODE_TYPES.MethodDefinition ||
this.node.type === AST_NODE_TYPES.TSAbstractMethodDefinition
) {
return this.node.kind === 'set' || this.node.kind === 'get';
}
return (
this.node.type === AST_NODE_TYPES.AccessorProperty ||
this.node.type === AST_NODE_TYPES.TSAbstractAccessorProperty
);
}
Member.isHashPrivate(): boolean¶
Returns: boolean
Code
Member.isPrivate(): boolean¶
Returns: boolean
Member.isStatic(): boolean¶
Returns: boolean
Member.isUsed(): boolean¶
Returns: boolean
Calls:
this.isAccessor
Internal Comments:
Code
analyzeClassMemberUsage(program: TSESTree.Program, scopeManager: ScopeManager): ReadonlyMap<ClassNode, ClassScopeResult>¶
Parameters:
programTSESTree.ProgramscopeManagerScopeManager
Returns: ReadonlyMap<ClassNode, ClassScopeResult>
Calls:
rootScope.visittraverseScopes
Code
isWriteOnlyUsage(node: TSESTree.Node, parent: TSESTree.Node): boolean¶
Parameters:
nodeTSESTree.NodeparentTSESTree.Node
Returns: boolean
Internal Comments:
// If it's on the right then it's a read not a write
// For any other operator (such as '+=') we still consider it a read operation (x3)
// if the read operation is "discarded" in an empty statement, then it is write only.
Code
function isWriteOnlyUsage(node: TSESTree.Node, parent: TSESTree.Node): boolean {
if (
parent.type !== AST_NODE_TYPES.AssignmentExpression &&
parent.type !== AST_NODE_TYPES.ForInStatement &&
parent.type !== AST_NODE_TYPES.ForOfStatement &&
parent.type !== AST_NODE_TYPES.AssignmentPattern
) {
return false;
}
// If it's on the right then it's a read not a write
if (parent.left !== node) {
return false;
}
if (
parent.type === AST_NODE_TYPES.AssignmentExpression &&
// For any other operator (such as '+=') we still consider it a read operation
parent.operator !== '='
) {
// if the read operation is "discarded" in an empty statement, then it is write only.
return parent.parent.type === AST_NODE_TYPES.ExpressionStatement;
}
return true;
}
countReference(identifierParent: TSESTree.Node, member: Member): void¶
Parameters:
identifierParentTSESTree.NodememberMember
Returns: void
Calls:
nullThrows (from ..)isWriteOnlyUsage
Internal Comments:
// A statement which only increments (`this.#x++;`)
/*
* ({ x: this.#usedInDestructuring } = bar);
*
* But should treat the following as a read:
* ({ [this.#x]: a } = foo);
*/
// [...this.#unusedInRestPattern] = bar;
// [this.#unusedInAssignmentPattern] = bar;
Code
function countReference(identifierParent: TSESTree.Node, member: Member) {
const identifierGrandparent = nullThrows(
identifierParent.parent,
NullThrowsReasons.MissingParent,
);
if (isWriteOnlyUsage(identifierParent, identifierGrandparent)) {
member.writeCount += 1;
return;
}
const identifierGreatGrandparent = identifierGrandparent.parent;
// A statement which only increments (`this.#x++;`)
if (
identifierGrandparent.type === AST_NODE_TYPES.UpdateExpression &&
identifierGreatGrandparent?.type === AST_NODE_TYPES.ExpressionStatement
) {
member.writeCount += 1;
return;
}
/*
* ({ x: this.#usedInDestructuring } = bar);
*
* But should treat the following as a read:
* ({ [this.#x]: a } = foo);
*/
if (
identifierGrandparent.type === AST_NODE_TYPES.Property &&
identifierGreatGrandparent?.type === AST_NODE_TYPES.ObjectPattern &&
identifierGrandparent.value === identifierParent
) {
member.writeCount += 1;
return;
}
// [...this.#unusedInRestPattern] = bar;
if (identifierGrandparent.type === AST_NODE_TYPES.RestElement) {
member.writeCount += 1;
return;
}
// [this.#unusedInAssignmentPattern] = bar;
if (identifierGrandparent.type === AST_NODE_TYPES.ArrayPattern) {
member.writeCount += 1;
return;
}
member.readCount += 1;
}
ThisScope.findNearestScope(node: TSESTree.Node): Scope | null¶
Parameters:
nodeTSESTree.Node
Returns: Scope | null
Calls:
this.scopeManager.acquire
Code
private findNearestScope(node: TSESTree.Node): Scope | null {
let currentScope: Scope | null | undefined;
let currentNode: TSESTree.Node | undefined = node;
do {
currentScope = this.scopeManager.acquire(currentNode);
if (currentNode.parent == null) {
break;
}
currentNode = currentNode.parent;
} while (currentScope == null);
return currentScope;
}
ThisScope.findVariableInScope(node: TSESTree.Node, name: string): Variable | null¶
Parameters:
nodeTSESTree.Nodenamestring
Returns: Variable | null
Calls:
this.findNearestScopecurrentScope.set.get
Code
private findVariableInScope(
node: TSESTree.Node,
name: string,
): Variable | null {
let currentScope = this.findNearestScope(node);
let variable = null;
while (currentScope != null) {
variable = currentScope.set.get(name) ?? null;
if (variable != null) {
break;
}
currentScope = currentScope.upper;
}
return variable;
}
ThisScope.getObjectClass(node: TSESTree.MemberExpression): { thisContext: ThisScope['thisContext']; type: 'instance' |…¶
Parameters:
nodeTSESTree.MemberExpression
Returns: {
thisContext: ThisScope['thisContext'];
type: 'instance' | 'static';
} | null
Calls:
this.findClassScopeWithNamethis.findVariableInScopevariable.references.someref.isWritecomplex_call_9763
Internal Comments:
// the following code does some very rudimentary scope analysis to handle some trivial cases (x2)
// detect simple reassignment of `this`
// ``` (x4)
// class Foo { (x2)
// private prop: number; (x2)
// method(thing: Foo) { (x2)
// const self = this;
// return self.prop;
// } (x2)
// } (x2)
// variable is assigned to multiple times so we can't be sure that it's still the same class
// we have a case like `const self = this` or `let self = this` that is not reassigned
// so we can safely assume that it's still the same class!
// Look for variables typed as the current class:
// // this references the private instance member but not via `this` so we can't see it
// thing.prop = 1;
// Cases like `method(thing: Foo) { ... }`
// Cases like `method(thing: typeof Foo) { ... }`
// TODO - we could probably recurse here to do some more complex analysis and support like `foo.bar.baz` nested references
Code
private getObjectClass(node: TSESTree.MemberExpression): {
thisContext: ThisScope['thisContext'];
type: 'instance' | 'static';
} | null {
switch (node.object.type) {
case AST_NODE_TYPES.ThisExpression: {
if (this.thisContext == null) {
return null;
}
return {
thisContext: this.thisContext,
type: this.isStaticThisContext ? 'static' : 'instance',
};
}
case AST_NODE_TYPES.Identifier: {
const thisContext = this.findClassScopeWithName(node.object.name);
if (thisContext != null) {
return { thisContext, type: 'static' };
}
// the following code does some very rudimentary scope analysis to handle some trivial cases
const variable = this.findVariableInScope(node, node.object.name);
if (variable == null || variable.defs.length === 0) {
return null;
}
const firstDef = variable.defs[0];
switch (firstDef.node.type) {
// detect simple reassignment of `this`
// ```
// class Foo {
// private prop: number;
// method(thing: Foo) {
// const self = this;
// return self.prop;
// }
// }
// ```
case AST_NODE_TYPES.VariableDeclarator: {
const value = firstDef.node.init;
if (value?.type !== AST_NODE_TYPES.ThisExpression) {
return null;
}
if (
variable.references.some(
ref => ref.isWrite() && ref.init !== true,
)
) {
// variable is assigned to multiple times so we can't be sure that it's still the same class
return null;
}
// we have a case like `const self = this` or `let self = this` that is not reassigned
// so we can safely assume that it's still the same class!
return {
thisContext: this.thisContext,
type: this.isStaticThisContext ? 'static' : 'instance',
};
}
// Look for variables typed as the current class:
// ```
// class Foo {
// private prop: number;
// method(thing: Foo) {
// // this references the private instance member but not via `this` so we can't see it
// thing.prop = 1;
// }
// }
// ```
default: {
const typeAnnotation = (() => {
if (
'typeAnnotation' in firstDef.name &&
firstDef.name.typeAnnotation != null
) {
return firstDef.name.typeAnnotation.typeAnnotation;
}
return null;
})();
if (typeAnnotation == null) {
return null;
}
// Cases like `method(thing: Foo) { ... }`
if (
typeAnnotation.type === AST_NODE_TYPES.TSTypeReference &&
typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier
) {
const typeName = typeAnnotation.typeName.name;
const typeScope = this.findClassScopeWithName(typeName);
if (typeScope != null) {
return { thisContext: typeScope, type: 'instance' };
}
}
// Cases like `method(thing: typeof Foo) { ... }`
if (
typeAnnotation.type === AST_NODE_TYPES.TSTypeQuery &&
typeAnnotation.exprName.type === AST_NODE_TYPES.Identifier
) {
const exprName = typeAnnotation.exprName.name;
const exprScope = this.findClassScopeWithName(exprName);
if (exprScope != null) {
return { thisContext: exprScope, type: 'static' };
}
}
}
}
return null;
}
case AST_NODE_TYPES.MemberExpression:
// TODO - we could probably recurse here to do some more complex analysis and support like `foo.bar.baz` nested references
return null;
default:
return null;
}
}
ThisScope.visitClass(node: ClassNode): void¶
Parameters:
nodeClassNode
Returns: void
Calls:
this.childScopes.pushclassScope.visitChildren
Code
ThisScope.visitIntermediate(node: IntermediateNode): void¶
Parameters:
nodeIntermediateNode
Returns: void
Calls:
this.childScopes.pushintermediateScope.visitChildren
Code
ThisScope.findClassScopeWithName(name: string): ClassScope | null¶
Gets the nearest class scope with the given name.
Code
ThisScope.AssignmentExpression(node: TSESTree.AssignmentExpression): void¶
Parameters:
nodeTSESTree.AssignmentExpression
Returns: void
Calls:
this.visitChildrenthis.handleThisDestructuring
Code
ThisScope.AssignmentPattern(node: TSESTree.AssignmentPattern): void¶
Parameters:
nodeTSESTree.AssignmentPattern
Returns: void
Calls:
this.visitChildrenthis.handleThisDestructuring
Code
ThisScope.ClassDeclaration(node: TSESTree.ClassDeclaration): void¶
Parameters:
nodeTSESTree.ClassDeclaration
Returns: void
Calls:
this.visitClass
ThisScope.ClassExpression(node: TSESTree.ClassExpression): void¶
Parameters:
nodeTSESTree.ClassExpression
Returns: void
Calls:
this.visitClass
ThisScope.FunctionDeclaration(node: TSESTree.FunctionDeclaration): void¶
Parameters:
nodeTSESTree.FunctionDeclaration
Returns: void
Calls:
this.visitIntermediate
Code
ThisScope.FunctionExpression(node: TSESTree.FunctionExpression): void¶
Parameters:
nodeTSESTree.FunctionExpression
Returns: void
Calls:
this.visitIntermediate
Code
ThisScope.MemberExpression(node: TSESTree.MemberExpression): void¶
Parameters:
nodeTSESTree.MemberExpression
Returns: void
Calls:
this.visitChildrenextractNameForMemberExpression (from ./extractComputedName)this.getObjectClassmembers.getcountReference
Internal Comments:
Code
protected MemberExpression(node: TSESTree.MemberExpression): void {
this.visitChildren(node);
if (node.property.type === AST_NODE_TYPES.PrivateIdentifier) {
// will be handled by the PrivateIdentifier visitor
return;
}
const propertyName = extractNameForMemberExpression(node);
if (propertyName == null) {
return;
}
const objectClassName = this.getObjectClass(node);
if (objectClassName == null) {
return;
}
if (objectClassName.thisContext == null) {
return;
}
const members =
objectClassName.type === 'instance'
? objectClassName.thisContext.members.instance
: objectClassName.thisContext.members.static;
const member = members.get(propertyName.key);
if (member == null) {
return;
}
countReference(node, member);
}
ThisScope.PrivateIdentifier(node: TSESTree.PrivateIdentifier): void¶
Parameters:
nodeTSESTree.PrivateIdentifier
Returns: void
Calls:
this.visitChildrenprivateKey (from ./types)currentScope.thisContext.members.instance.getcurrentScope.thisContext.members.static.getcountReference
Internal Comments:
// ignore the member definition
// We can actually be pretty loose with our code here thanks to how private (x2)
// members are designed. (x2)
// (x4)
// 1) classes CANNOT have a static and instance private member with the (x2)
// same name, so we don't need to match up static access. (x2)
// 2) nested classes CANNOT access a private member of their parent class if (x2)
// the member has the same name as a private member of the nested class. (x2)
// together this means that we can just look for the member upwards until we (x2)
// find a match and we know that will be the correct match! (x2)
Code
protected PrivateIdentifier(node: TSESTree.PrivateIdentifier): void {
this.visitChildren(node);
if (
(node.parent.type === AST_NODE_TYPES.MethodDefinition ||
node.parent.type === AST_NODE_TYPES.PropertyDefinition) &&
node.parent.key === node
) {
// ignore the member definition
return;
}
// We can actually be pretty loose with our code here thanks to how private
// members are designed.
//
// 1) classes CANNOT have a static and instance private member with the
// same name, so we don't need to match up static access.
// 2) nested classes CANNOT access a private member of their parent class if
// the member has the same name as a private member of the nested class.
//
// together this means that we can just look for the member upwards until we
// find a match and we know that will be the correct match!
let currentScope: ThisScope | null = this;
const key = privateKey(node);
while (currentScope != null) {
if (currentScope.thisContext != null) {
const member =
currentScope.thisContext.members.instance.get(key) ??
currentScope.thisContext.members.static.get(key);
if (member != null) {
countReference(node.parent, member);
return;
}
}
currentScope = currentScope.upper;
}
}
ThisScope.StaticBlock(node: TSESTree.StaticBlock): void¶
Parameters:
nodeTSESTree.StaticBlock
Returns: void
Calls:
this.visitIntermediate
ThisScope.VariableDeclarator(node: TSESTree.VariableDeclarator): void¶
Parameters:
nodeTSESTree.VariableDeclarator
Returns: void
Calls:
this.visitChildrenthis.handleThisDestructuring
Code
ThisScope.handleThisDestructuring(pattern: TSESTree.ObjectPattern): void¶
Handles destructuring from this in ObjectPattern.
Example: const { property } = this;
Raw JSDoc
Calls:
publicKey (from ./types)members.getcountReference
Code
private handleThisDestructuring(pattern: TSESTree.ObjectPattern): void {
if (this.thisContext == null) {
return;
}
for (const prop of pattern.properties) {
if (prop.type !== AST_NODE_TYPES.Property) {
continue;
}
if (prop.key.type !== AST_NODE_TYPES.Identifier || prop.computed) {
continue;
}
const memberKey = publicKey(prop.key.name);
const members = this.isStaticThisContext
? this.thisContext.members.static
: this.thisContext.members.instance;
const member = members.get(memberKey);
if (member == null) {
continue;
}
countReference(prop.key, member);
}
}
traverseScopes(currentScope: ThisScope, analysisResults: Map<any, ClassScopeResult>): Map<any, ClassScopeResult>¶
Parameters:
currentScopeThisScopeanalysisResultsMap<any, ClassScopeResult>
Returns: Map<any, ClassScopeResult>
Calls:
analysisResults.settraverseScopes
Code
function traverseScopes(
currentScope: ThisScope,
analysisResults = new Map<ClassNode, ClassScopeResult>(),
) {
if (currentScope instanceof ClassScope) {
analysisResults.set(currentScope.theClass, currentScope);
}
for (const childScope of currentScope.childScopes) {
traverseScopes(childScope, analysisResults);
}
return analysisResults;
}
Classes¶
Member¶
Class Code
export class Member {
/**
* The node that declares this member
*/
public readonly node: MemberNode;
/**
* The resolved, unique key for this member.
*/
public readonly key: Key;
/**
* The member name, as given in the source code.
*/
public readonly name: string;
/**
* The node that represents the member name in the source code.
* Used for reporting errors.
*/
public readonly nameNode: TSESTree.Node;
/**
* The number of writes to this member.
*/
public writeCount = 0;
/**
* The number of reads from this member.
*/
public readCount = 0;
private constructor(
node: MemberNode,
key: Key,
name: string,
nameNode: TSESTree.Node,
) {
this.node = node;
this.key = key;
this.name = name;
this.nameNode = nameNode;
}
public static create(node: MemberNode): Member | null {
const name = extractNameForMember(node);
if (name == null) {
return null;
}
return new Member(node, name.key, name.codeName, name.nameNode);
}
public isAccessor(): boolean {
if (
this.node.type === AST_NODE_TYPES.MethodDefinition ||
this.node.type === AST_NODE_TYPES.TSAbstractMethodDefinition
) {
return this.node.kind === 'set' || this.node.kind === 'get';
}
return (
this.node.type === AST_NODE_TYPES.AccessorProperty ||
this.node.type === AST_NODE_TYPES.TSAbstractAccessorProperty
);
}
public isHashPrivate(): boolean {
return (
'key' in this.node &&
this.node.key.type === AST_NODE_TYPES.PrivateIdentifier
);
}
public isPrivate(): boolean {
return this.node.accessibility === 'private';
}
public isStatic(): boolean {
return this.node.static;
}
public isUsed(): boolean {
return (
this.readCount > 0 ||
// any usage of an accessor is considered a usage as accessor can have side effects
(this.writeCount > 0 && this.isAccessor())
);
}
}
Methods (6) — full entries under Functions
| Method | Signature |
|---|---|
create |
(node: MemberNode): Member \| null |
isAccessor |
(): boolean |
isHashPrivate |
(): boolean |
isPrivate |
(): boolean |
isStatic |
(): boolean |
isUsed |
(): boolean |
ThisScope¶
Extends: Visitor
Methods (17) — full entries under Functions
| Method | Signature |
|---|---|
findNearestScope |
(node: TSESTree.Node): Scope \| null |
findVariableInScope |
(node: TSESTree.Node, name: string): Variable \| null |
getObjectClass |
(node: TSESTree.MemberExpression): { thisContext: ThisScope['thisContext']; type: 'instance' \| '... |
visitClass |
(node: ClassNode): void |
visitIntermediate |
(node: IntermediateNode): void |
findClassScopeWithName |
(name: string): ClassScope \| null |
AssignmentExpression |
(node: TSESTree.AssignmentExpression): void |
AssignmentPattern |
(node: TSESTree.AssignmentPattern): void |
ClassDeclaration |
(node: TSESTree.ClassDeclaration): void |
ClassExpression |
(node: TSESTree.ClassExpression): void |
FunctionDeclaration |
(node: TSESTree.FunctionDeclaration): void |
FunctionExpression |
(node: TSESTree.FunctionExpression): void |
MemberExpression |
(node: TSESTree.MemberExpression): void |
PrivateIdentifier |
(node: TSESTree.PrivateIdentifier): void |
StaticBlock |
(node: TSESTree.StaticBlock): void |
VariableDeclarator |
(node: TSESTree.VariableDeclarator): void |
handleThisDestructuring |
(pattern: TSESTree.ObjectPattern): void |
IntermediateScope¶
Any other scope that is not a class scope
When we visit a function declaration/expression the this reference is
rebound so it no longer refers to the class.
This also supports a function's this parameter.
Extends: ThisScope
Class Code
class IntermediateScope extends ThisScope {
constructor(
scopeManager: ScopeManager,
upper: ThisScope | null,
node: IntermediateNode,
) {
if (node.type === AST_NODE_TYPES.Program) {
super(scopeManager, upper, 'none', false);
return;
}
if (node.type === AST_NODE_TYPES.StaticBlock) {
if (upper == null || !(upper instanceof ClassScope)) {
throw new Error(
'Cannot have a static block without an upper ClassScope',
);
}
super(scopeManager, upper, upper, true);
return;
}
// method definition
if (
(node.parent.type === AST_NODE_TYPES.MethodDefinition ||
node.parent.type === AST_NODE_TYPES.PropertyDefinition) &&
node.parent.value === node
) {
if (upper == null || !(upper instanceof ClassScope)) {
throw new Error(
'Cannot have a class method/property without an upper ClassScope',
);
}
super(scopeManager, upper, upper, node.parent.static);
return;
}
// function with a `this` parameter
if (
upper != null &&
node.params.length > 0 &&
node.params[0].type === AST_NODE_TYPES.Identifier &&
node.params[0].name === 'this'
) {
const thisType = node.params[0].typeAnnotation?.typeAnnotation;
if (
thisType?.type === AST_NODE_TYPES.TSTypeReference &&
thisType.typeName.type === AST_NODE_TYPES.Identifier
) {
const thisContext = upper.findClassScopeWithName(
thisType.typeName.name,
);
if (thisContext != null) {
super(scopeManager, upper, thisContext, false);
return;
}
}
}
super(scopeManager, upper, 'none', false);
}
}
ClassScope¶
Extends: ThisScope
Implements: ClassScopeResult
Class Code
class ClassScope extends ThisScope implements ClassScopeResult {
public readonly className: string | null;
/**
* The class's members, keyed by their name
*/
public readonly members: ClassScopeResult['members'] = {
instance: new Map(),
static: new Map(),
};
/**
* The node that declares this class.
*/
public readonly theClass: ClassNode;
public constructor(
theClass: ClassNode,
upper: ClassScope | IntermediateScope | null,
scopeManager: ScopeManager,
) {
super(scopeManager, upper, 'self', false);
this.theClass = theClass;
this.className = theClass.id?.name ?? null;
for (const memberNode of theClass.body.body) {
switch (memberNode.type) {
case AST_NODE_TYPES.MethodDefinition:
if (memberNode.kind === 'constructor') {
for (const parameter of memberNode.value.params) {
if (parameter.type !== AST_NODE_TYPES.TSParameterProperty) {
continue;
}
const member = Member.create(parameter);
if (member == null) {
continue;
}
this.members.instance.set(member.key, member);
}
// break instead of falling through because the constructor is not a "member" we track
break;
}
// intentional fallthrough
case AST_NODE_TYPES.AccessorProperty:
case AST_NODE_TYPES.PropertyDefinition:
case AST_NODE_TYPES.TSAbstractAccessorProperty:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
case AST_NODE_TYPES.TSAbstractPropertyDefinition: {
const member = Member.create(memberNode);
if (member == null) {
continue;
}
if (member.isStatic()) {
this.members.static.set(member.key, member);
} else {
this.members.instance.set(member.key, member);
}
break;
}
case AST_NODE_TYPES.StaticBlock:
// static blocks declare no members
continue;
case AST_NODE_TYPES.TSIndexSignature:
// index signatures are type signatures only and are fully computed
continue;
}
}
}
}
Interfaces¶
ClassScopeResult¶
Interface Code
export interface ClassScopeResult {
/**
* The classes name as given in the source code.
* If this is `null` then the class is an anonymous class.
*/
readonly className: string | null;
/**
* The class's members, keyed by their name
*/
readonly members: {
readonly instance: Map<Key, Member>;
readonly static: Map<Key, Member>;
};
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
className |
string \| null |
✗ | not shown |
members |
{ readonly instance: Map<Key, Member>; readonly static: Map<Key, Member>; } |
✗ | not shown |
Type Aliases¶
IntermediateNode¶
type IntermediateNode = | TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression
| TSESTree.Program
| TSESTree.StaticBlock;
Generated by Syntax Scribe