⬅️ Back to Table of Contents
📄 no-base-to-string.ts
📊 Analysis Summary
Metric |
Count |
🔧 Functions |
9 |
📦 Imports |
7 |
📊 Variables & Constants |
6 |
📑 Type Aliases |
2 |
🎯 Enums |
1 |
📚 Table of Contents
🛠️ File Location:
📂 packages/eslint-plugin/src/rules/no-base-to-string.ts
📦 Imports
Name |
Source |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstrainedTypeAtLocation |
../util |
getParserServices |
../util |
getTypeName |
../util |
nullThrows |
../util |
Variables & Constants
Name |
Type |
Kind |
Value |
Exported |
ignoredTypeNames |
any |
const |
option.ignoredTypeNames ?? [] |
✗ |
toString |
any |
const |
`checker.getPropertyOfType(type, 'toString') ?? |
|
checker.getPropertyOfType(type, 'toLocaleString')` |
✗ |
|
|
|
declaration |
any |
const |
declarations[0] |
✗ |
isBaseToString |
boolean |
const |
`ts.isInterfaceDeclaration(declaration.parent) && |
|
declaration.parent.name.text === 'Object'` |
✗ |
|
|
|
memberExpr |
TSESTree.MemberExpression |
const |
node.parent as TSESTree.MemberExpression |
✗ |
memberExpr |
TSESTree.MemberExpression |
const |
node.parent as TSESTree.MemberExpression |
✗ |
Functions
checkExpression(node: TSESTree.Expression, type: ts.Type): void
Code
function checkExpression(node: TSESTree.Expression, type?: ts.Type): void {
if (node.type === AST_NODE_TYPES.Literal) {
return;
}
const certainty = collectToStringCertainty(
type ?? services.getTypeAtLocation(node),
new Set(),
);
if (certainty === Usefulness.Always) {
return;
}
context.report({
node,
messageId: 'baseToString',
data: {
name: context.sourceCode.getText(node),
certainty,
},
});
}
- Parameters:
node: TSESTree.Expression
type: ts.Type
- Return Type:
void
- Calls:
collectToStringCertainty
services.getTypeAtLocation
context.report
context.sourceCode.getText
checkExpressionForArrayJoin(node: TSESTree.Node, type: ts.Type): void
Code
function checkExpressionForArrayJoin(
node: TSESTree.Node,
type: ts.Type,
): void {
const certainty = collectJoinCertainty(type, new Set());
if (certainty === Usefulness.Always) {
return;
}
context.report({
node,
messageId: 'baseArrayJoin',
data: {
name: context.sourceCode.getText(node),
certainty,
},
});
}
- Parameters:
node: TSESTree.Node
type: ts.Type
- Return Type:
void
- Calls:
collectJoinCertainty
context.report
context.sourceCode.getText
collectUnionTypeCertainty(type: ts.UnionType, collectSubTypeCertainty: (type: ts.Type) => Usefulness): Usefulness
Code
function collectUnionTypeCertainty(
type: ts.UnionType,
collectSubTypeCertainty: (type: ts.Type) => Usefulness,
): Usefulness {
const certainties = type.types.map(t => collectSubTypeCertainty(t));
if (certainties.every(certainty => certainty === Usefulness.Never)) {
return Usefulness.Never;
}
if (certainties.every(certainty => certainty === Usefulness.Always)) {
return Usefulness.Always;
}
return Usefulness.Sometimes;
}
- Parameters:
type: ts.UnionType
collectSubTypeCertainty: (type: ts.Type) => Usefulness
- Return Type:
Usefulness
- Calls:
type.types.map
collectSubTypeCertainty
certainties.every
collectIntersectionTypeCertainty(type: ts.IntersectionType, collectSubTypeCertainty: (type: ts.Type) => Usefulness): Usefulness
Code
function collectIntersectionTypeCertainty(
type: ts.IntersectionType,
collectSubTypeCertainty: (type: ts.Type) => Usefulness,
): Usefulness {
for (const subType of type.types) {
const subtypeUsefulness = collectSubTypeCertainty(subType);
if (subtypeUsefulness === Usefulness.Always) {
return Usefulness.Always;
}
}
return Usefulness.Never;
}
- Parameters:
type: ts.IntersectionType
collectSubTypeCertainty: (type: ts.Type) => Usefulness
- Return Type:
Usefulness
- Calls:
collectSubTypeCertainty
collectTupleCertainty(type: ts.TypeReference, visited: Set<ts.Type>): Usefulness
Code
function collectTupleCertainty(
type: ts.TypeReference,
visited: Set<ts.Type>,
): Usefulness {
const typeArgs = checker.getTypeArguments(type);
const certainties = typeArgs.map(t =>
collectToStringCertainty(t, visited),
);
if (certainties.some(certainty => certainty === Usefulness.Never)) {
return Usefulness.Never;
}
if (certainties.some(certainty => certainty === Usefulness.Sometimes)) {
return Usefulness.Sometimes;
}
return Usefulness.Always;
}
- Parameters:
type: ts.TypeReference
visited: Set<ts.Type>
- Return Type:
Usefulness
- Calls:
checker.getTypeArguments
typeArgs.map
collectToStringCertainty
certainties.some
collectArrayCertainty(type: ts.Type, visited: Set<ts.Type>): Usefulness
Code
function collectArrayCertainty(
type: ts.Type,
visited: Set<ts.Type>,
): Usefulness {
const elemType = nullThrows(
type.getNumberIndexType(),
'array should have number index type',
);
return collectToStringCertainty(elemType, visited);
}
- Parameters:
type: ts.Type
visited: Set<ts.Type>
- Return Type:
Usefulness
- Calls:
nullThrows (from ../util)
type.getNumberIndexType
collectToStringCertainty
collectJoinCertainty(type: ts.Type, visited: Set<ts.Type>): Usefulness
Code
function collectJoinCertainty(
type: ts.Type,
visited: Set<ts.Type>,
): Usefulness {
if (tsutils.isUnionType(type)) {
return collectUnionTypeCertainty(type, t =>
collectJoinCertainty(t, visited),
);
}
if (tsutils.isIntersectionType(type)) {
return collectIntersectionTypeCertainty(type, t =>
collectJoinCertainty(t, visited),
);
}
if (checker.isTupleType(type)) {
return collectTupleCertainty(type, visited);
}
if (checker.isArrayType(type)) {
return collectArrayCertainty(type, visited);
}
return Usefulness.Always;
}
- Parameters:
type: ts.Type
visited: Set<ts.Type>
- Return Type:
Usefulness
- Calls:
tsutils.isUnionType
collectUnionTypeCertainty
collectJoinCertainty
tsutils.isIntersectionType
collectIntersectionTypeCertainty
checker.isTupleType
collectTupleCertainty
checker.isArrayType
collectArrayCertainty
collectToStringCertainty(type: ts.Type, visited: Set<ts.Type>): Usefulness
Code
function collectToStringCertainty(
type: ts.Type,
visited: Set<ts.Type>,
): Usefulness {
if (visited.has(type)) {
// don't report if this is a self referencing array or tuple type
return Usefulness.Always;
}
if (tsutils.isTypeParameter(type)) {
const constraint = type.getConstraint();
if (constraint) {
return collectToStringCertainty(constraint, visited);
}
// unconstrained generic means `unknown`
return Usefulness.Always;
}
// the Boolean type definition missing toString()
if (
type.flags & ts.TypeFlags.Boolean ||
type.flags & ts.TypeFlags.BooleanLiteral
) {
return Usefulness.Always;
}
if (ignoredTypeNames.includes(getTypeName(checker, type))) {
return Usefulness.Always;
}
if (type.isIntersection()) {
return collectIntersectionTypeCertainty(type, t =>
collectToStringCertainty(t, visited),
);
}
if (type.isUnion()) {
return collectUnionTypeCertainty(type, t =>
collectToStringCertainty(t, visited),
);
}
if (checker.isTupleType(type)) {
return collectTupleCertainty(type, new Set([...visited, type]));
}
if (checker.isArrayType(type)) {
return collectArrayCertainty(type, new Set([...visited, type]));
}
const toString =
checker.getPropertyOfType(type, 'toString') ??
checker.getPropertyOfType(type, 'toLocaleString');
if (!toString) {
// e.g. any/unknown
return Usefulness.Always;
}
const declarations = toString.getDeclarations();
if (declarations == null || declarations.length !== 1) {
// If there are multiple declarations, at least one of them must not be
// the default object toString.
//
// This may only matter for older versions of TS
// see https://github.com/typescript-eslint/typescript-eslint/issues/8585
return Usefulness.Always;
}
const declaration = declarations[0];
const isBaseToString =
ts.isInterfaceDeclaration(declaration.parent) &&
declaration.parent.name.text === 'Object';
return isBaseToString ? Usefulness.Never : Usefulness.Always;
}
- Parameters:
type: ts.Type
visited: Set<ts.Type>
- Return Type:
Usefulness
- Calls:
visited.has
tsutils.isTypeParameter
type.getConstraint
collectToStringCertainty
ignoredTypeNames.includes
getTypeName (from ../util)
type.isIntersection
collectIntersectionTypeCertainty
type.isUnion
collectUnionTypeCertainty
checker.isTupleType
collectTupleCertainty
checker.isArrayType
collectArrayCertainty
checker.getPropertyOfType
toString.getDeclarations
ts.isInterfaceDeclaration
- Internal Comments:
// don't report if this is a self referencing array or tuple type
// unconstrained generic means `unknown`
// the Boolean type definition missing toString()
// e.g. any/unknown
// If there are multiple declarations, at least one of them must not be
// the default object toString.
//
// This may only matter for older versions of TS
// see https://github.com/typescript-eslint/typescript-eslint/issues/8585
isBuiltInStringCall(node: TSESTree.CallExpression): boolean
Code
function isBuiltInStringCall(node: TSESTree.CallExpression): boolean {
if (
node.callee.type === AST_NODE_TYPES.Identifier &&
// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
node.callee.name === 'String' &&
node.arguments[0]
) {
const scope = context.sourceCode.getScope(node);
// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
const variable = scope.set.get('String');
return !variable?.defs.length;
}
return false;
}
- Parameters:
node: TSESTree.CallExpression
- Return Type:
boolean
- Calls:
context.sourceCode.getScope
scope.set.get
- Internal Comments:
// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum (x6)
Type Aliases
Options
type Options = [
{
ignoredTypeNames?: string[];
},
];
MessageIds
type MessageIds = 'baseArrayJoin' | 'baseToString';
Enums
enum Usefulness
Enum Code
enum Usefulness {
Always = 'always',
Never = 'will',
Sometimes = 'may',
}
Members
Name |
Value |
Description |
Always |
always |
|
Never |
will |
|
Sometimes |
may |
|