β¬ οΈ Back to Table of Contents
π class-methods-use-this¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 7 |
| π¦ Imports | 6 |
| π Type Aliases | 3 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/class-methods-use-this.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'class-methods-use-this' |
meta.type |
'suggestion' |
meta.docs.description |
'Enforce that class methods utilize this' |
meta.docs.extendsBaseRule |
true |
meta.docs.requiresTypeChecking |
false |
meta.messages.missingThis |
"Expected 'this' to be used by class {{name}}." |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { enforceForClassFields: { type: 'boolean', description:... |
defaultOptions |
[ { enforceForClassFields: true, exceptMethods: [], ignoreClassesThatImplementAnInterface: false, ignoreOverrideMetho... |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getFunctionHeadLoc |
../util |
getFunctionNameWithKind |
../util |
getStaticMemberAccessValue |
../util |
Functions¶
create(context: any, [ { enforceForClassFields, exceβ¦: any): { 'AccessorProperty:exit'(): void; 'AccessorProperty > *.ke⦶
Parameters:
contextany[ { enforceForClassFields, exceptMethods: exceptMethodsRaw, ignoreClassesThatImplementAnInterface, ignoreOverrideMethods, }, ]any
Returns: { 'AccessorProperty:exit'(): void; 'AccessorProperty > *.key:exit'(): void; 'PropertyDefinition:exit'(): void; 'PropertyDefinition > *.key:exit'(): void; StaticBlock(): void; 'StaticBlock:exit'(): void; 'ThisExpression, Super'(): void; 'AccessorProperty > ArrowFunctionExpression.value'?: (node: TSESTree.ArrowFunctionExpression) => void; 'AccessorProperty > ArrowFunctionExpression.value:exit'?: (node: TSESTree.ArrowFunctionExpression) => void; 'PropertyDefinition > ArrowFunctionExpression.value'?: (node: TSESTree.ArrowFunctionExpression) => void; 'PropertyDefinition > ArrowFunctionExpression.value:exit'?: (node: TSESTree.ArrowFunctionExpression) => void; FunctionDeclaration(): void; 'FunctionDeclaration:exit'(): void; FunctionExpression(node: any): void; 'FunctionExpression:exit'(node: any): void; }
Calls:
pushContextgetStaticMemberAccessValue (from ../util)exceptMethods.haspopContextisPublicFieldisIncludedInstanceMethodcontext.reportgetFunctionHeadLoc (from ../util)getFunctionNameWithKind (from ../util)enterFunctionexitFunction
Internal Comments:
/**
* Pop `this` used flag from the stack.
*/
/**
* Check if the node is an instance method not excluded by config
*/
/**
* Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
* Static methods and the constructor are exempt.
* Then pops the context off the stack.
*/
// function declarations have their own `this` context (x2)
/*
* Class field value are implicit functions.
*/ (x2)
/*
* Class static blocks are implicit functions. They aren't required to use `this`,
* but we have to push context so that it captures any use of `this` in the static block
* separately from enclosing contexts, because static blocks have their own `this` and it
* shouldn't count as used `this` in enclosing contexts.
*/ (x2)
Code
create(
context,
[
{
enforceForClassFields,
exceptMethods: exceptMethodsRaw,
ignoreClassesThatImplementAnInterface,
ignoreOverrideMethods,
},
],
) {
const exceptMethods = new Set(exceptMethodsRaw);
type Stack =
| {
class: null;
member: null;
parent: Stack | undefined;
usesThis: boolean;
}
| {
class: TSESTree.ClassDeclaration | TSESTree.ClassExpression;
member:
| TSESTree.AccessorProperty
| TSESTree.MethodDefinition
| TSESTree.PropertyDefinition;
parent: Stack | undefined;
usesThis: boolean;
};
let stack: Stack | undefined;
function pushContext(
member?:
| TSESTree.AccessorProperty
| TSESTree.MethodDefinition
| TSESTree.PropertyDefinition,
): void {
if (member?.parent.type === AST_NODE_TYPES.ClassBody) {
stack = {
class: member.parent.parent,
member,
parent: stack,
usesThis: false,
};
} else {
stack = {
class: null,
member: null,
parent: stack,
usesThis: false,
};
}
}
function enterFunction(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): void {
if (
node.parent.type === AST_NODE_TYPES.MethodDefinition ||
node.parent.type === AST_NODE_TYPES.PropertyDefinition ||
node.parent.type === AST_NODE_TYPES.AccessorProperty
) {
pushContext(node.parent);
} else {
pushContext();
}
}
/**
* Pop `this` used flag from the stack.
*/
function popContext(): Stack | undefined {
const oldStack = stack;
stack = stack?.parent;
return oldStack;
}
function isPublicField(
accessibility: TSESTree.Accessibility | undefined,
): boolean {
if (!accessibility || accessibility === 'public') {
return true;
}
return false;
}
/**
* Check if the node is an instance method not excluded by config
*/
function isIncludedInstanceMethod(
node: NonNullable<Stack['member']>,
): boolean {
if (
node.static ||
(node.type === AST_NODE_TYPES.MethodDefinition &&
node.kind === 'constructor') ||
((node.type === AST_NODE_TYPES.PropertyDefinition ||
node.type === AST_NODE_TYPES.AccessorProperty) &&
!enforceForClassFields)
) {
return false;
}
if (node.computed || exceptMethods.size === 0) {
return true;
}
const hashIfNeeded =
node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : '';
const name = getStaticMemberAccessValue(node, context);
return (
typeof name !== 'string' || !exceptMethods.has(hashIfNeeded + name)
);
}
/**
* Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
* Static methods and the constructor are exempt.
* Then pops the context off the stack.
*/
function exitFunction(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): void {
const stackContext = popContext();
if (
stackContext?.member == null ||
stackContext.usesThis ||
(ignoreOverrideMethods && stackContext.member.override) ||
(ignoreClassesThatImplementAnInterface === true &&
stackContext.class.implements.length > 0) ||
(ignoreClassesThatImplementAnInterface === 'public-fields' &&
stackContext.class.implements.length > 0 &&
isPublicField(stackContext.member.accessibility))
) {
return;
}
if (isIncludedInstanceMethod(stackContext.member)) {
context.report({
loc: getFunctionHeadLoc(node, context.sourceCode),
node,
messageId: 'missingThis',
data: {
name: getFunctionNameWithKind(node),
},
});
}
}
return {
// function declarations have their own `this` context
FunctionDeclaration(): void {
pushContext();
},
'FunctionDeclaration:exit'(): void {
popContext();
},
FunctionExpression(node): void {
enterFunction(node);
},
'FunctionExpression:exit'(node): void {
exitFunction(node);
},
...(enforceForClassFields
? {
'AccessorProperty > ArrowFunctionExpression.value'(
node: TSESTree.ArrowFunctionExpression,
): void {
enterFunction(node);
},
'AccessorProperty > ArrowFunctionExpression.value:exit'(
node: TSESTree.ArrowFunctionExpression,
): void {
exitFunction(node);
},
'PropertyDefinition > ArrowFunctionExpression.value'(
node: TSESTree.ArrowFunctionExpression,
): void {
enterFunction(node);
},
'PropertyDefinition > ArrowFunctionExpression.value:exit'(
node: TSESTree.ArrowFunctionExpression,
): void {
exitFunction(node);
},
}
: {}),
/*
* Class field value are implicit functions.
*/
'AccessorProperty:exit'(): void {
popContext();
},
'AccessorProperty > *.key:exit'(): void {
pushContext();
},
'PropertyDefinition:exit'(): void {
popContext();
},
'PropertyDefinition > *.key:exit'(): void {
pushContext();
},
/*
* Class static blocks are implicit functions. They aren't required to use `this`,
* but we have to push context so that it captures any use of `this` in the static block
* separately from enclosing contexts, because static blocks have their own `this` and it
* shouldn't count as used `this` in enclosing contexts.
*/
StaticBlock(): void {
pushContext();
},
'StaticBlock:exit'(): void {
popContext();
},
'ThisExpression, Super'(): void {
if (stack) {
stack.usesThis = true;
}
},
};
}
Internal helpers¶
Declared inside another function in this file.
pushContext(member: | TSESTree.AccessorProperty | TSESTree.β¦): void¶
Parameters:
member| TSESTree.AccessorProperty | TSESTree.MethodDefinition | TSESTree.PropertyDefinition
Returns: void
Code
function pushContext(
member?:
| TSESTree.AccessorProperty
| TSESTree.MethodDefinition
| TSESTree.PropertyDefinition,
): void {
if (member?.parent.type === AST_NODE_TYPES.ClassBody) {
stack = {
class: member.parent.parent,
member,
parent: stack,
usesThis: false,
};
} else {
stack = {
class: null,
member: null,
parent: stack,
usesThis: false,
};
}
}
enterFunction(node: TSESTree.ArrowFunctionExpression | TSESβ¦): void¶
Parameters:
nodeTSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression
Returns: void
Calls:
pushContext
Code
function enterFunction(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): void {
if (
node.parent.type === AST_NODE_TYPES.MethodDefinition ||
node.parent.type === AST_NODE_TYPES.PropertyDefinition ||
node.parent.type === AST_NODE_TYPES.AccessorProperty
) {
pushContext(node.parent);
} else {
pushContext();
}
}
popContext(): Stack | undefined¶
Pop this used flag from the stack.
Code
isPublicField(accessibility: TSESTree.Accessibility | undefined): boolean¶
Parameters:
accessibilityTSESTree.Accessibility | undefined
Returns: boolean
Code
isIncludedInstanceMethod(node: NonNullable<Stack['member']>): boolean¶
Check if the node is an instance method not excluded by config
Calls:
getStaticMemberAccessValue (from ../util)exceptMethods.has
Code
function isIncludedInstanceMethod(
node: NonNullable<Stack['member']>,
): boolean {
if (
node.static ||
(node.type === AST_NODE_TYPES.MethodDefinition &&
node.kind === 'constructor') ||
((node.type === AST_NODE_TYPES.PropertyDefinition ||
node.type === AST_NODE_TYPES.AccessorProperty) &&
!enforceForClassFields)
) {
return false;
}
if (node.computed || exceptMethods.size === 0) {
return true;
}
const hashIfNeeded =
node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : '';
const name = getStaticMemberAccessValue(node, context);
return (
typeof name !== 'string' || !exceptMethods.has(hashIfNeeded + name)
);
}
exitFunction(node: TSESTree.ArrowFunctionExpression | TSESβ¦): void¶
Checks if we are leaving a function that is a method, and reports if 'this' has not been used. Static methods and the constructor are exempt. Then pops the context off the stack.
Raw JSDoc
Calls:
popContextisPublicFieldisIncludedInstanceMethodcontext.reportgetFunctionHeadLoc (from ../util)getFunctionNameWithKind (from ../util)
Code
function exitFunction(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): void {
const stackContext = popContext();
if (
stackContext?.member == null ||
stackContext.usesThis ||
(ignoreOverrideMethods && stackContext.member.override) ||
(ignoreClassesThatImplementAnInterface === true &&
stackContext.class.implements.length > 0) ||
(ignoreClassesThatImplementAnInterface === 'public-fields' &&
stackContext.class.implements.length > 0 &&
isPublicField(stackContext.member.accessibility))
) {
return;
}
if (isIncludedInstanceMethod(stackContext.member)) {
context.report({
loc: getFunctionHeadLoc(node, context.sourceCode),
node,
messageId: 'missingThis',
data: {
name: getFunctionNameWithKind(node),
},
});
}
}
Type Aliases¶
Options¶
type Options = [
{
enforceForClassFields?: boolean;
exceptMethods?: string[];
ignoreClassesThatImplementAnInterface?: boolean | 'public-fields';
ignoreOverrideMethods?: boolean;
},
];
MessageIds¶
Stack¶
type Stack = | {
class: null;
member: null;
parent: Stack | undefined;
usesThis: boolean;
}
| {
class: TSESTree.ClassDeclaration | TSESTree.ClassExpression;
member:
| TSESTree.AccessorProperty
| TSESTree.MethodDefinition
| TSESTree.PropertyDefinition;
parent: Stack | undefined;
usesThis: boolean;
};
Generated by Syntax Scribe