@typescript-eslint/scope-manager
Version:
TypeScript scope analyser for ESLint
268 lines (267 loc) • 9.14 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClassVisitor = void 0;
const types_1 = require("@typescript-eslint/types");
const definition_1 = require("../definition");
const TypeVisitor_1 = require("./TypeVisitor");
const Visitor_1 = require("./Visitor");
class ClassVisitor extends Visitor_1.Visitor {
#classNode;
#referencer;
constructor(referencer, node) {
super(referencer);
this.#referencer = referencer;
this.#classNode = node;
}
static visit(referencer, node) {
const classVisitor = new ClassVisitor(referencer, node);
classVisitor.visitClass(node);
}
visit(node) {
// make sure we only handle the nodes we are designed to handle
if (node && node.type in this) {
super.visit(node);
}
else {
this.#referencer.visit(node);
}
}
///////////////////
// Visit helpers //
///////////////////
visitClass(node) {
if (node.type === types_1.AST_NODE_TYPES.ClassDeclaration && node.id) {
this.#referencer
.currentScope()
.defineIdentifier(node.id, new definition_1.ClassNameDefinition(node.id, node));
}
node.decorators.forEach(d => this.#referencer.visit(d));
this.#referencer.scopeManager.nestClassScope(node);
if (node.id) {
// define the class name again inside the new scope
// references to the class should not resolve directly to the parent class
this.#referencer
.currentScope()
.defineIdentifier(node.id, new definition_1.ClassNameDefinition(node.id, node));
}
this.#referencer.visit(node.superClass);
// visit the type param declarations
this.visitType(node.typeParameters);
// then the usages
this.visitType(node.superTypeArguments);
node.implements.forEach(imp => this.visitType(imp));
this.visit(node.body);
this.#referencer.close(node);
}
visitFunctionParameterTypeAnnotation(node) {
switch (node.type) {
case types_1.AST_NODE_TYPES.AssignmentPattern:
this.visitType(node.left.typeAnnotation);
break;
case types_1.AST_NODE_TYPES.TSParameterProperty:
this.visitFunctionParameterTypeAnnotation(node.parameter);
break;
default:
this.visitType(node.typeAnnotation);
}
}
visitMethod(node) {
if (node.computed) {
this.#referencer.visit(node.key);
}
if (node.value.type === types_1.AST_NODE_TYPES.FunctionExpression) {
this.visitMethodFunction(node.value, node);
}
else {
this.#referencer.visit(node.value);
}
node.decorators.forEach(d => this.#referencer.visit(d));
}
visitMethodFunction(node, methodNode) {
if (node.id) {
// FunctionExpression with name creates its special scope;
// FunctionExpressionNameScope.
this.#referencer.scopeManager.nestFunctionExpressionNameScope(node);
}
node.params.forEach(param => {
param.decorators.forEach(d => this.visit(d));
});
// Consider this function is in the MethodDefinition.
this.#referencer.scopeManager.nestFunctionScope(node, true);
/**
* class A {
* @meta // <--- check this
* foo(a: Type) {}
*
* @meta // <--- check this
* foo(): Type {}
* }
*/
let withMethodDecorators = !!methodNode.decorators.length;
/**
* class A {
* foo(
* @meta // <--- check this
* a: Type
* ) {}
*
* set foo(
* @meta // <--- EXCEPT this. TS do nothing for this
* a: Type
* ) {}
* }
*/
withMethodDecorators ||=
methodNode.kind !== 'set' &&
node.params.some(param => param.decorators.length);
if (!withMethodDecorators && methodNode.kind === 'set') {
const keyName = getLiteralMethodKeyName(methodNode);
/**
* class A {
* @meta // <--- check this
* get a() {}
* set ['a'](v: Type) {}
* }
*/
if (keyName != null &&
this.#classNode.body.body.find((node) => node !== methodNode &&
node.type === types_1.AST_NODE_TYPES.MethodDefinition &&
// Node must both be static or not
node.static === methodNode.static &&
getLiteralMethodKeyName(node) === keyName)?.decorators.length) {
withMethodDecorators = true;
}
}
/**
* @meta // <--- check this
* class A {
* constructor(a: Type) {}
* }
*/
if (!withMethodDecorators &&
methodNode.kind === 'constructor' &&
this.#classNode.decorators.length) {
withMethodDecorators = true;
}
// Process parameter declarations.
for (const param of node.params) {
this.visitPattern(param, (pattern, info) => {
this.#referencer
.currentScope()
.defineIdentifier(pattern, new definition_1.ParameterDefinition(pattern, node, info.rest));
this.#referencer.referencingDefaultValue(pattern, info.assignments, null, true);
}, { processRightHandNodes: true });
this.visitFunctionParameterTypeAnnotation(param);
}
this.visitType(node.returnType);
this.visitType(node.typeParameters);
this.#referencer.visitChildren(node.body);
this.#referencer.close(node);
}
visitPropertyBase(node) {
if (node.computed) {
this.#referencer.visit(node.key);
}
if (node.value) {
if (node.type === types_1.AST_NODE_TYPES.PropertyDefinition ||
node.type === types_1.AST_NODE_TYPES.AccessorProperty) {
this.#referencer.scopeManager.nestClassFieldInitializerScope(node.value);
}
this.#referencer.visit(node.value);
if (node.type === types_1.AST_NODE_TYPES.PropertyDefinition ||
node.type === types_1.AST_NODE_TYPES.AccessorProperty) {
this.#referencer.close(node.value);
}
}
node.decorators.forEach(d => this.#referencer.visit(d));
}
visitPropertyDefinition(node) {
this.visitPropertyBase(node);
/**
* class A {
* @meta // <--- check this
* foo: Type;
* }
*/
this.visitType(node.typeAnnotation);
}
visitType(node) {
if (!node) {
return;
}
TypeVisitor_1.TypeVisitor.visit(this.#referencer, node);
}
/////////////////////
// Visit selectors //
/////////////////////
AccessorProperty(node) {
this.visitPropertyDefinition(node);
}
ClassBody(node) {
// this is here on purpose so that this visitor explicitly declares visitors
// for all nodes it cares about (see the instance visit method above)
this.visitChildren(node);
}
Identifier(node) {
this.#referencer.visit(node);
}
MethodDefinition(node) {
this.visitMethod(node);
}
PrivateIdentifier() {
// intentionally skip
}
PropertyDefinition(node) {
this.visitPropertyDefinition(node);
}
StaticBlock(node) {
this.#referencer.scopeManager.nestClassStaticBlockScope(node);
node.body.forEach(b => this.visit(b));
this.#referencer.close(node);
}
TSAbstractAccessorProperty(node) {
this.visitPropertyDefinition(node);
}
TSAbstractMethodDefinition(node) {
this.visitPropertyBase(node);
}
TSAbstractPropertyDefinition(node) {
this.visitPropertyDefinition(node);
}
TSIndexSignature(node) {
this.visitType(node);
}
}
exports.ClassVisitor = ClassVisitor;
/**
* Only if key is one of [identifier, string, number], ts will combine metadata of accessors .
* class A {
* get a() {}
* set ['a'](v: Type) {}
*
* get [1]() {}
* set [1](v: Type) {}
*
* // Following won't be combined
* get [key]() {}
* set [key](v: Type) {}
*
* get [true]() {}
* set [true](v: Type) {}
*
* get ['a'+'b']() {}
* set ['a'+'b']() {}
* }
*/
function getLiteralMethodKeyName(node) {
if (node.computed && node.key.type === types_1.AST_NODE_TYPES.Literal) {
if (typeof node.key.value === 'string' ||
typeof node.key.value === 'number') {
return node.key.value;
}
}
else if (!node.computed && node.key.type === types_1.AST_NODE_TYPES.Identifier) {
return node.key.name;
}
return null;
}