📄 no-unsafe-member-access¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 2 |
| 📦 Imports | 7 |
| 📑 Type Aliases | 2 |
| 🎯 Enums | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-unsafe-member-access.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'no-unsafe-member-access' |
meta.type |
'problem' |
meta.docs.description |
'Disallow member access on a value with type any' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.messages.errorComputedMemberAccess |
'The type of computed name {{property}} cannot be resolved.' |
meta.messages.errorMemberExpression |
'Unsafe member access {{property}} on a type that cannot be resolved.' |
meta.messages.errorThisMemberExpression |
[ 'Unsafe member access {{property}}. The type of this cannot be resolved.', 'You can try to fix this by turning on... |
meta.messages.unsafeComputedMemberAccess |
'Computed name {{property}} resolves to an any value.' |
meta.messages.unsafeMemberExpression |
'Unsafe member access {{property}} on an any value.' |
meta.messages.unsafeThisMemberExpression |
[ 'Unsafe member access {{property}} on an any value. this is typed as any.', 'You can try to fix this by turni... |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { allowOptionalChaining: { type: 'boolean', description:... |
defaultOptions |
[ { allowOptionalChaining: false, }, ] |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getParserServices |
../util |
getThisExpression |
../util |
isTypeAnyType |
../util |
Functions¶
create(context: any, [{ allowOptionalChaining }]: any): { 'MemberExpression:not(TSClassImplements MemberExpression,…¶
Parameters:
contextany[{ allowOptionalChaining }]any
Returns: { 'MemberExpression:not(TSClassImplements MemberExpression, TSInterfaceHeritage MemberExpression)': (node: TSESTree.MemberExpression) => State; 'MemberExpression[computed = true] > *.property'(node: TSESTree.Expression): void; }
Calls:
getParserServices (from ../util)services.program.getCompilerOptionstsutils.isStrictCompilerOptionEnabledstateCache.setstateCache.getcheckMemberExpressionservices.getTypeAtLocationisTypeAnyType (from ../util)context.sourceCode.getTextgetThisExpression (from ../util)getConstrainedTypeAtLocation (from ../util)tsutils.isIntrinsicErrorTypecontext.report
Internal Comments:
// Case notes:
// value?.outer.middle.inner
// The ChainExpression is a child of the root expression, and a parent of all the MemberExpressions.
// But the left-most expression is what we want to report on: the inner-most expressions.
// In fact, this is true even if the chain is on the inside!
// value.outer.middle?.inner;
// It was already true that every `object` (MemberExpression) has optional: boolean
// if the object is unsafe, we know this will be unsafe as well (x4)
// we don't need to report, as we have already reported on the inner member expr (x4)
// `this.foo` or `this.foo[bar]` (x2)
// ignore MemberExpressions with ancestors of type `TSClassImplements` or `TSInterfaceHeritage` (x2)
// x[1] (x4)
// x[1++] x[++x] etc (x3)
// FUN FACT - **all** update expressions return type number, regardless of the argument's type, (x3)
// because JS engines return NaN if there the argument is not a number. (x3)
// perf optimizations - literals can obviously never be `any`
Code
create(context, [{ allowOptionalChaining }]) {
const services = getParserServices(context);
const compilerOptions = services.program.getCompilerOptions();
const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'noImplicitThis',
);
const stateCache = new Map<TSESTree.Node, State>();
// Case notes:
// value?.outer.middle.inner
// The ChainExpression is a child of the root expression, and a parent of all the MemberExpressions.
// But the left-most expression is what we want to report on: the inner-most expressions.
// In fact, this is true even if the chain is on the inside!
// value.outer.middle?.inner;
// It was already true that every `object` (MemberExpression) has optional: boolean
function checkMemberExpression(node: TSESTree.MemberExpression): State {
if (allowOptionalChaining && node.optional) {
stateCache.set(node, State.Chained);
return State.Chained;
}
const cachedState = stateCache.get(node);
if (cachedState) {
return cachedState;
}
if (node.object.type === AST_NODE_TYPES.MemberExpression) {
const objectState = checkMemberExpression(node.object);
if (objectState === State.Unsafe) {
// if the object is unsafe, we know this will be unsafe as well
// we don't need to report, as we have already reported on the inner member expr
stateCache.set(node, objectState);
return objectState;
}
}
const type = services.getTypeAtLocation(node.object);
const state = isTypeAnyType(type) ? State.Unsafe : State.Safe;
stateCache.set(node, state);
if (state === State.Unsafe) {
const propertyName = context.sourceCode.getText(node.property);
let messageId: MessageIds | undefined;
if (!isNoImplicitThis) {
// `this.foo` or `this.foo[bar]`
const thisExpression = getThisExpression(node);
if (thisExpression) {
const thisType = getConstrainedTypeAtLocation(
services,
thisExpression,
);
if (isTypeAnyType(thisType)) {
messageId = tsutils.isIntrinsicErrorType(thisType)
? 'errorThisMemberExpression'
: 'unsafeThisMemberExpression';
}
}
}
if (!messageId) {
messageId = tsutils.isIntrinsicErrorType(type)
? 'errorMemberExpression'
: 'unsafeMemberExpression';
}
context.report({
node: node.property,
messageId,
data: {
property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
},
});
}
return state;
}
return {
// ignore MemberExpressions with ancestors of type `TSClassImplements` or `TSInterfaceHeritage`
'MemberExpression:not(TSClassImplements MemberExpression, TSInterfaceHeritage MemberExpression)':
checkMemberExpression,
'MemberExpression[computed = true] > *.property'(
node: TSESTree.Expression,
): void {
if (
allowOptionalChaining &&
(node.parent as TSESTree.MemberExpression).optional
) {
return;
}
if (
// x[1]
node.type === AST_NODE_TYPES.Literal ||
// x[1++] x[++x] etc
// FUN FACT - **all** update expressions return type number, regardless of the argument's type,
// because JS engines return NaN if there the argument is not a number.
node.type === AST_NODE_TYPES.UpdateExpression
) {
// perf optimizations - literals can obviously never be `any`
return;
}
const type = services.getTypeAtLocation(node);
if (isTypeAnyType(type)) {
const propertyName = context.sourceCode.getText(node);
context.report({
node,
messageId: tsutils.isIntrinsicErrorType(type)
? 'errorComputedMemberAccess'
: 'unsafeComputedMemberAccess',
data: {
property: `[${propertyName}]`,
},
});
}
},
};
}
Internal helpers¶
Declared inside another function in this file.
checkMemberExpression(node: TSESTree.MemberExpression): State¶
Parameters:
nodeTSESTree.MemberExpression
Returns: State
Calls:
stateCache.setstateCache.getcheckMemberExpressionservices.getTypeAtLocationisTypeAnyType (from ../util)context.sourceCode.getTextgetThisExpression (from ../util)getConstrainedTypeAtLocation (from ../util)tsutils.isIntrinsicErrorTypecontext.report
Internal Comments:
// if the object is unsafe, we know this will be unsafe as well (x4)
// we don't need to report, as we have already reported on the inner member expr (x4)
// `this.foo` or `this.foo[bar]` (x2)
Code
function checkMemberExpression(node: TSESTree.MemberExpression): State {
if (allowOptionalChaining && node.optional) {
stateCache.set(node, State.Chained);
return State.Chained;
}
const cachedState = stateCache.get(node);
if (cachedState) {
return cachedState;
}
if (node.object.type === AST_NODE_TYPES.MemberExpression) {
const objectState = checkMemberExpression(node.object);
if (objectState === State.Unsafe) {
// if the object is unsafe, we know this will be unsafe as well
// we don't need to report, as we have already reported on the inner member expr
stateCache.set(node, objectState);
return objectState;
}
}
const type = services.getTypeAtLocation(node.object);
const state = isTypeAnyType(type) ? State.Unsafe : State.Safe;
stateCache.set(node, state);
if (state === State.Unsafe) {
const propertyName = context.sourceCode.getText(node.property);
let messageId: MessageIds | undefined;
if (!isNoImplicitThis) {
// `this.foo` or `this.foo[bar]`
const thisExpression = getThisExpression(node);
if (thisExpression) {
const thisType = getConstrainedTypeAtLocation(
services,
thisExpression,
);
if (isTypeAnyType(thisType)) {
messageId = tsutils.isIntrinsicErrorType(thisType)
? 'errorThisMemberExpression'
: 'unsafeThisMemberExpression';
}
}
}
if (!messageId) {
messageId = tsutils.isIntrinsicErrorType(type)
? 'errorMemberExpression'
: 'unsafeMemberExpression';
}
context.report({
node: node.property,
messageId,
data: {
property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
},
});
}
return state;
}
Type Aliases¶
Options¶
MessageIds¶
type MessageIds = | 'errorComputedMemberAccess'
| 'errorMemberExpression'
| 'errorThisMemberExpression'
| 'unsafeComputedMemberAccess'
| 'unsafeMemberExpression'
| 'unsafeThisMemberExpression';
Enums¶
const enum State¶
Members¶
| Name | Value | Description |
|---|---|---|
Unsafe |
1 |
not shown |
Safe |
2 |
not shown |
Chained |
3 |
not shown |
Generated by Syntax Scribe