📄 no-mixed-enums.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 5 |
📦 Imports | 6 |
📊 Variables & Constants | 3 |
📐 Interfaces | 1 |
🎯 Enums | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-mixed-enums.ts
📦 Imports¶
Name | Source |
---|---|
Scope |
@typescript-eslint/scope-manager |
TSESTree |
@typescript-eslint/utils |
DefinitionType |
@typescript-eslint/scope-manager |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getParserServices |
../util |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
found |
CollectedDefinitions |
const | `{ | |
imports: [], | ||||
previousSibling: undefined, | ||||
}` | ✗ | |||
valueDeclaration |
any |
const | type.getSymbol()?.valueDeclaration |
✗ |
declarations |
any |
const | `typeChecker | |
.getSymbolAtLocation(tsNode)! | ||||
.getDeclarations()!` | ✗ |
Functions¶
collectNodeDefinitions(node: TSESTree.TSEnumDeclaration): CollectedDefinitions
¶
Code
function collectNodeDefinitions(
node: TSESTree.TSEnumDeclaration,
): CollectedDefinitions {
const { name } = node.id;
const found: CollectedDefinitions = {
imports: [],
previousSibling: undefined,
};
let scope: Scope | null = context.sourceCode.getScope(node);
for (const definition of scope.upper?.set.get(name)?.defs ?? []) {
if (
definition.node.type === AST_NODE_TYPES.TSEnumDeclaration &&
definition.node.range[0] < node.range[0] &&
definition.node.body.members.length > 0
) {
found.previousSibling = definition.node;
break;
}
}
while (scope) {
scope.set.get(name)?.defs.forEach(definition => {
if (definition.type === DefinitionType.ImportBinding) {
found.imports.push(definition.node);
}
});
scope = scope.upper;
}
return found;
}
- Parameters:
node: TSESTree.TSEnumDeclaration
- Return Type:
CollectedDefinitions
- Calls:
context.sourceCode.getScope
scope.upper?.set.get
scope.set.get(name)?.defs.forEach
found.imports.push
getAllowedTypeForNode(node: ts.Node): AllowedType
¶
Code
- Parameters:
node: ts.Node
- Return Type:
AllowedType
- Calls:
tsutils.isTypeFlagSet
typeChecker.getTypeAtLocation
getTypeFromImported(imported: TSESTree.Node): AllowedType | undefined
¶
Code
function getTypeFromImported(
imported: TSESTree.Node,
): AllowedType | undefined {
const type = typeChecker.getTypeAtLocation(
parserServices.esTreeNodeToTSNodeMap.get(imported),
);
const valueDeclaration = type.getSymbol()?.valueDeclaration;
if (
!valueDeclaration ||
!ts.isEnumDeclaration(valueDeclaration) ||
valueDeclaration.members.length === 0
) {
return undefined;
}
return getAllowedTypeForNode(valueDeclaration.members[0]);
}
- Parameters:
imported: TSESTree.Node
- Return Type:
AllowedType | undefined
- Calls:
typeChecker.getTypeAtLocation
parserServices.esTreeNodeToTSNodeMap.get
type.getSymbol
ts.isEnumDeclaration
getAllowedTypeForNode
getMemberType(member: TSESTree.TSEnumMember): AllowedType
¶
Code
function getMemberType(member: TSESTree.TSEnumMember): AllowedType {
if (!member.initializer) {
return AllowedType.Number;
}
switch (member.initializer.type) {
case AST_NODE_TYPES.Literal:
switch (typeof member.initializer.value) {
case 'number':
return AllowedType.Number;
case 'string':
return AllowedType.String;
default:
return AllowedType.Unknown;
}
case AST_NODE_TYPES.TemplateLiteral:
return AllowedType.String;
default:
return getAllowedTypeForNode(
parserServices.esTreeNodeToTSNodeMap.get(member.initializer),
);
}
}
- Parameters:
member: TSESTree.TSEnumMember
- Return Type:
AllowedType
- Calls:
getAllowedTypeForNode
parserServices.esTreeNodeToTSNodeMap.get
getDesiredTypeForDefinition(node: TSESTree.TSEnumDeclaration): AllowedType | ts.TypeFlags.Unknown | undefined
¶
Code
function getDesiredTypeForDefinition(
node: TSESTree.TSEnumDeclaration,
): AllowedType | ts.TypeFlags.Unknown | undefined {
const { imports, previousSibling } = collectNodeDefinitions(node);
// Case: Merged ambiently via module augmentation
// import { MyEnum } from 'other-module';
// declare module 'other-module' {
// enum MyEnum { A }
// }
for (const imported of imports) {
const typeFromImported = getTypeFromImported(imported);
if (typeFromImported != null) {
return typeFromImported;
}
}
// Case: Multiple enum declarations in the same file
// enum MyEnum { A }
// enum MyEnum { B }
if (previousSibling) {
return getMemberType(previousSibling.body.members[0]);
}
// Case: Namespace declaration merging
// namespace MyNamespace {
// export enum MyEnum { A }
// }
// namespace MyNamespace {
// export enum MyEnum { B }
// }
if (
node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration &&
node.parent.parent.type === AST_NODE_TYPES.TSModuleBlock
) {
// https://github.com/typescript-eslint/typescript-eslint/issues/8352
// TODO: We don't need to dip into the TypeScript type checker here!
// Merged namespaces must all exist in the same file.
// We could instead compare this file's nodes to find the merges.
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node.id);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const declarations = typeChecker
.getSymbolAtLocation(tsNode)!
.getDeclarations()!;
const [{ initializer }] = (declarations[0] as ts.EnumDeclaration)
.members;
return initializer &&
tsutils.isTypeFlagSet(
typeChecker.getTypeAtLocation(initializer),
ts.TypeFlags.StringLike,
)
? AllowedType.String
: AllowedType.Number;
}
// Finally, we default to the type of the first enum member
return getMemberType(node.body.members[0]);
}
- Parameters:
node: TSESTree.TSEnumDeclaration
- Return Type:
AllowedType | ts.TypeFlags.Unknown | undefined
- Calls:
collectNodeDefinitions
getTypeFromImported
getMemberType
parserServices.esTreeNodeToTSNodeMap.get
typeChecker .getSymbolAtLocation(tsNode)! .getDeclarations
tsutils.isTypeFlagSet
typeChecker.getTypeAtLocation
- Internal Comments:
// Case: Merged ambiently via module augmentation // import { MyEnum } from 'other-module'; // declare module 'other-module' { // enum MyEnum { A } // } (x3) // Case: Multiple enum declarations in the same file // enum MyEnum { A } // enum MyEnum { B } // Case: Namespace declaration merging // namespace MyNamespace { (x2) // export enum MyEnum { A } // export enum MyEnum { B } // https://github.com/typescript-eslint/typescript-eslint/issues/8352 (x2) // TODO: We don't need to dip into the TypeScript type checker here! (x2) // Merged namespaces must all exist in the same file. (x2) // We could instead compare this file's nodes to find the merges. (x2) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2) // Finally, we default to the type of the first enum member
Interfaces¶
CollectedDefinitions
¶
Interface Code
Properties¶
Name | Type | Optional | Description |
---|---|---|---|
imports |
TSESTree.Node[] |
✗ | |
previousSibling |
TSESTree.TSEnumDeclaration | undefined |
✗ |
Enums¶
enum AllowedType
¶
Members¶
Name | Value | Description |
---|---|---|
Number |
auto | |
String |
auto | |
Unknown |
auto |