β¬ οΈ Back to Table of Contents
π no-duplicate-type-constituents¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 8 |
| π¦ Imports | 8 |
| π Variables & Constants | 1 |
| π Type Aliases | 3 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/no-duplicate-type-constituents.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'no-duplicate-type-constituents' |
meta.type |
'suggestion' |
meta.docs.description |
'Disallow duplicate constituents of union or intersection types' |
meta.docs.recommended |
'recommended' |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.messages.duplicate |
'{{type}} type constituent is duplicated with {{previous}}.' |
meta.messages.unnecessary |
'Explicit undefined is unnecessary on an optional parameter.' |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { ignoreIntersections: { type: 'boolean', description: '... |
defaultOptions |
[ { ignoreIntersections: false, ignoreUnions: false, }, ] |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
Type |
typescript |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getParserServices |
../util |
isFunctionOrFunctionType |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
astIgnoreKeys |
Set<string> |
const | new Set(['loc', 'parent', 'range']) |
β |
Functions¶
create(context: any, [{ ignoreIntersections, ignoreUβ¦: any): { TSUnionType: (node: any) => void; TSIntersectionType(node⦶
Parameters:
contextany[{ ignoreIntersections, ignoreUnions }]any
Returns: { TSUnionType: (node: any) => void; TSIntersectionType(node: any): void; }
Calls:
getParserServices (from ../util)sourceCode[getTokens${where}](constituentNode, { filter: token => ['&', '|'].includes(token.value) && constituentNode.parent.range[0] <= token.range[0] && token.range[1] <= constituentNode.parent.range[1], }).atgetUnionOrIntersectionTokensourceCode.getTokensBetweensourceCode.getTokensAfternullThrows (from ../util)NullThrowsReasons.MissingTokensourceCode.getTokensBeforecontext.reportbracketAfterTokens.at[ beforeUnionOrIntersectionToken, ...bracketBeforeTokens, constituentNode, ...bracketAfterTokens, afterUnionOrIntersectionToken, ].flatMapfixer.removereportsourceCode.getTextuniqueConstituents.findisSameAstNodereportDuplicateparserServices.getTypeAtLocationtsutils.isIntrinsicErrorTypecachedTypeMap.getforEachNodeTypecachedTypeMap.setuniqueConstituents.pushcheckDuplicateRecursivelycheckDuplicateisFunctionOrFunctionType (from ../util)maybeFunction.params.includestsutils.isTypeFlagSet
Internal Comments:
Code
create(context, [{ ignoreIntersections, ignoreUnions }]) {
const parserServices = getParserServices(context);
const { sourceCode } = context;
function report(
messageId: MessageIds,
constituentNode: TSESTree.TypeNode,
data?: Record<string, unknown>,
): void {
const getUnionOrIntersectionToken = (
where: 'After' | 'Before',
at: number,
): TSESTree.Token | undefined =>
sourceCode[`getTokens${where}`](constituentNode, {
filter: token =>
['&', '|'].includes(token.value) &&
constituentNode.parent.range[0] <= token.range[0] &&
token.range[1] <= constituentNode.parent.range[1],
}).at(at);
const beforeUnionOrIntersectionToken = getUnionOrIntersectionToken(
'Before',
-1,
);
let afterUnionOrIntersectionToken: TSESTree.Token | undefined;
let bracketBeforeTokens;
let bracketAfterTokens;
if (beforeUnionOrIntersectionToken) {
bracketBeforeTokens = sourceCode.getTokensBetween(
beforeUnionOrIntersectionToken,
constituentNode,
);
bracketAfterTokens = sourceCode.getTokensAfter(constituentNode, {
count: bracketBeforeTokens.length,
});
} else {
afterUnionOrIntersectionToken = nullThrows(
getUnionOrIntersectionToken('After', 0),
NullThrowsReasons.MissingToken(
'union or intersection token',
'duplicate type constituent',
),
);
bracketAfterTokens = sourceCode.getTokensBetween(
constituentNode,
afterUnionOrIntersectionToken,
);
bracketBeforeTokens = sourceCode.getTokensBefore(constituentNode, {
count: bracketAfterTokens.length,
});
}
context.report({
loc: {
start: constituentNode.loc.start,
end: (bracketAfterTokens.at(-1) ?? constituentNode).loc.end,
},
node: constituentNode,
messageId,
data,
fix: fixer =>
[
beforeUnionOrIntersectionToken,
...bracketBeforeTokens,
constituentNode,
...bracketAfterTokens,
afterUnionOrIntersectionToken,
].flatMap(token => (token ? fixer.remove(token) : [])),
});
}
function checkDuplicateRecursively(
unionOrIntersection: UnionOrIntersection,
constituentNode: TSESTree.TypeNode,
uniqueConstituents: TSESTree.TypeNode[],
cachedTypeMap: Map<Type, TSESTree.TypeNode>,
forEachNodeType?: (type: Type, node: TSESTree.TypeNode) => void,
): void {
const reportDuplicate = (previous: TSESTree.TypeNode) => {
report('duplicate', constituentNode, {
type: unionOrIntersection,
previous: sourceCode.getText(previous),
});
};
// Check duplicates in the AST before type lookup for better performance.
let duplicatedPrevious = uniqueConstituents.find(ele =>
isSameAstNode(ele, constituentNode),
);
if (duplicatedPrevious) {
reportDuplicate(duplicatedPrevious);
return;
}
const type = parserServices.getTypeAtLocation(constituentNode);
if (tsutils.isIntrinsicErrorType(type)) {
return;
}
duplicatedPrevious = cachedTypeMap.get(type);
if (duplicatedPrevious) {
reportDuplicate(duplicatedPrevious);
return;
}
forEachNodeType?.(type, constituentNode);
cachedTypeMap.set(type, constituentNode);
uniqueConstituents.push(constituentNode);
if (
(unionOrIntersection === 'Union' &&
constituentNode.type === AST_NODE_TYPES.TSUnionType) ||
(unionOrIntersection === 'Intersection' &&
constituentNode.type === AST_NODE_TYPES.TSIntersectionType)
) {
for (const constituent of constituentNode.types) {
checkDuplicateRecursively(
unionOrIntersection,
constituent,
uniqueConstituents,
cachedTypeMap,
forEachNodeType,
);
}
}
}
function checkDuplicate(
node: TSESTree.TSIntersectionType | TSESTree.TSUnionType,
forEachNodeType?: (
constituentNodeType: Type,
constituentNode: TSESTree.TypeNode,
) => void,
): void {
const cachedTypeMap = new Map<Type, TSESTree.TypeNode>();
const uniqueConstituents: TSESTree.TypeNode[] = [];
const unionOrIntersection =
node.type === AST_NODE_TYPES.TSIntersectionType
? 'Intersection'
: 'Union';
for (const type of node.types) {
checkDuplicateRecursively(
unionOrIntersection,
type,
uniqueConstituents,
cachedTypeMap,
forEachNodeType,
);
}
}
return {
...(!ignoreIntersections && {
TSIntersectionType(node) {
if (node.parent.type === AST_NODE_TYPES.TSIntersectionType) {
return;
}
checkDuplicate(node);
},
}),
...(!ignoreUnions && {
TSUnionType: (node): void => {
if (node.parent.type === AST_NODE_TYPES.TSUnionType) {
return;
}
checkDuplicate(node, (constituentNodeType, constituentNode) => {
const maybeTypeAnnotation = node.parent;
if (maybeTypeAnnotation.type === AST_NODE_TYPES.TSTypeAnnotation) {
const maybeIdentifier = maybeTypeAnnotation.parent;
if (
maybeIdentifier.type === AST_NODE_TYPES.Identifier &&
maybeIdentifier.optional
) {
const maybeFunction = maybeIdentifier.parent;
if (
isFunctionOrFunctionType(maybeFunction) &&
maybeFunction.params.includes(maybeIdentifier) &&
tsutils.isTypeFlagSet(
constituentNodeType,
ts.TypeFlags.Undefined,
)
) {
report('unnecessary', constituentNode);
}
}
}
});
},
}),
};
}
isSameAstNode(actualNode: unknown, expectedNode: unknown): boolean¶
Parameters:
actualNodeunknownexpectedNodeunknown
Returns: boolean
Calls:
Array.isArrayactualNode.someisSameAstNodeObject.keys(actualNode).filterastIgnoreKeys.hasObject.keys(expectedNode).filteractualNodeKeys.someObject.hasOwn
Code
(actualNode: unknown, expectedNode: unknown): boolean => {
if (actualNode === expectedNode) {
return true;
}
if (
actualNode &&
expectedNode &&
typeof actualNode === 'object' &&
typeof expectedNode === 'object'
) {
if (Array.isArray(actualNode) && Array.isArray(expectedNode)) {
if (actualNode.length !== expectedNode.length) {
return false;
}
return !actualNode.some(
(nodeEle, index) => !isSameAstNode(nodeEle, expectedNode[index]),
);
}
const actualNodeKeys = Object.keys(actualNode).filter(
key => !astIgnoreKeys.has(key),
);
const expectedNodeKeys = Object.keys(expectedNode).filter(
key => !astIgnoreKeys.has(key),
);
if (actualNodeKeys.length !== expectedNodeKeys.length) {
return false;
}
if (
actualNodeKeys.some(
actualNodeKey => !Object.hasOwn(expectedNode, actualNodeKey),
)
) {
return false;
}
if (
actualNodeKeys.some(
actualNodeKey =>
!isSameAstNode(
actualNode[actualNodeKey as keyof typeof actualNode],
expectedNode[actualNodeKey as keyof typeof expectedNode],
),
)
) {
return false;
}
return true;
}
return false;
}
Internal helpers¶
Declared inside another function in this file.
report(messageId: MessageIds, constituentNode: TSESTree.TypeNode, data: Record<string, unknown>): void¶
Parameters:
messageIdMessageIdsconstituentNodeTSESTree.TypeNodedataRecord<string, unknown>
Returns: void
Calls:
sourceCode[getTokens${where}](constituentNode, { filter: token => ['&', '|'].includes(token.value) && constituentNode.parent.range[0] <= token.range[0] && token.range[1] <= constituentNode.parent.range[1], }).atgetUnionOrIntersectionTokensourceCode.getTokensBetweensourceCode.getTokensAfternullThrows (from ../util)NullThrowsReasons.MissingTokensourceCode.getTokensBeforecontext.reportbracketAfterTokens.at[ beforeUnionOrIntersectionToken, ...bracketBeforeTokens, constituentNode, ...bracketAfterTokens, afterUnionOrIntersectionToken, ].flatMapfixer.remove
Code
function report(
messageId: MessageIds,
constituentNode: TSESTree.TypeNode,
data?: Record<string, unknown>,
): void {
const getUnionOrIntersectionToken = (
where: 'After' | 'Before',
at: number,
): TSESTree.Token | undefined =>
sourceCode[`getTokens${where}`](constituentNode, {
filter: token =>
['&', '|'].includes(token.value) &&
constituentNode.parent.range[0] <= token.range[0] &&
token.range[1] <= constituentNode.parent.range[1],
}).at(at);
const beforeUnionOrIntersectionToken = getUnionOrIntersectionToken(
'Before',
-1,
);
let afterUnionOrIntersectionToken: TSESTree.Token | undefined;
let bracketBeforeTokens;
let bracketAfterTokens;
if (beforeUnionOrIntersectionToken) {
bracketBeforeTokens = sourceCode.getTokensBetween(
beforeUnionOrIntersectionToken,
constituentNode,
);
bracketAfterTokens = sourceCode.getTokensAfter(constituentNode, {
count: bracketBeforeTokens.length,
});
} else {
afterUnionOrIntersectionToken = nullThrows(
getUnionOrIntersectionToken('After', 0),
NullThrowsReasons.MissingToken(
'union or intersection token',
'duplicate type constituent',
),
);
bracketAfterTokens = sourceCode.getTokensBetween(
constituentNode,
afterUnionOrIntersectionToken,
);
bracketBeforeTokens = sourceCode.getTokensBefore(constituentNode, {
count: bracketAfterTokens.length,
});
}
context.report({
loc: {
start: constituentNode.loc.start,
end: (bracketAfterTokens.at(-1) ?? constituentNode).loc.end,
},
node: constituentNode,
messageId,
data,
fix: fixer =>
[
beforeUnionOrIntersectionToken,
...bracketBeforeTokens,
constituentNode,
...bracketAfterTokens,
afterUnionOrIntersectionToken,
].flatMap(token => (token ? fixer.remove(token) : [])),
});
}
getUnionOrIntersectionToken(where: 'After' | 'Before', at: number): TSESTree.Token | undefined¶
Parameters:
where'After' | 'Before'atnumber
Returns: TSESTree.Token | undefined
Calls:
sourceCode[getTokens${where}](constituentNode, { filter: token => ['&', '|'].includes(token.value) && constituentNode.parent.range[0] <= token.range[0] && token.range[1] <= constituentNode.parent.range[1], }).at
Code
checkDuplicateRecursively(β¦): void¶
Parameters:
unionOrIntersectionUnionOrIntersectionconstituentNodeTSESTree.TypeNodeuniqueConstituentsTSESTree.TypeNode[]cachedTypeMapMap<Type, TSESTree.TypeNode>forEachNodeType(type: Type, node: TSESTree.TypeNode) => void
Returns: void
Calls:
reportsourceCode.getTextuniqueConstituents.findisSameAstNodereportDuplicateparserServices.getTypeAtLocationtsutils.isIntrinsicErrorTypecachedTypeMap.getforEachNodeTypecachedTypeMap.setuniqueConstituents.pushcheckDuplicateRecursively
Internal Comments:
Code
function checkDuplicateRecursively(
unionOrIntersection: UnionOrIntersection,
constituentNode: TSESTree.TypeNode,
uniqueConstituents: TSESTree.TypeNode[],
cachedTypeMap: Map<Type, TSESTree.TypeNode>,
forEachNodeType?: (type: Type, node: TSESTree.TypeNode) => void,
): void {
const reportDuplicate = (previous: TSESTree.TypeNode) => {
report('duplicate', constituentNode, {
type: unionOrIntersection,
previous: sourceCode.getText(previous),
});
};
// Check duplicates in the AST before type lookup for better performance.
let duplicatedPrevious = uniqueConstituents.find(ele =>
isSameAstNode(ele, constituentNode),
);
if (duplicatedPrevious) {
reportDuplicate(duplicatedPrevious);
return;
}
const type = parserServices.getTypeAtLocation(constituentNode);
if (tsutils.isIntrinsicErrorType(type)) {
return;
}
duplicatedPrevious = cachedTypeMap.get(type);
if (duplicatedPrevious) {
reportDuplicate(duplicatedPrevious);
return;
}
forEachNodeType?.(type, constituentNode);
cachedTypeMap.set(type, constituentNode);
uniqueConstituents.push(constituentNode);
if (
(unionOrIntersection === 'Union' &&
constituentNode.type === AST_NODE_TYPES.TSUnionType) ||
(unionOrIntersection === 'Intersection' &&
constituentNode.type === AST_NODE_TYPES.TSIntersectionType)
) {
for (const constituent of constituentNode.types) {
checkDuplicateRecursively(
unionOrIntersection,
constituent,
uniqueConstituents,
cachedTypeMap,
forEachNodeType,
);
}
}
}
reportDuplicate(previous: TSESTree.TypeNode): void¶
Parameters:
previousTSESTree.TypeNode
Returns: void
Calls:
reportsourceCode.getText
Code
checkDuplicate(node: TSESTree.TSIntersectionType | TSESTree.β¦, forEachNodeType: ( constituentNodeType: Type, constituenβ¦): void¶
Parameters:
nodeTSESTree.TSIntersectionType | TSESTree.TSUnionTypeforEachNodeType( constituentNodeType: Type, constituentNode: TSESTree.TypeNode, ) => void
Returns: void
Calls:
checkDuplicateRecursively
Code
function checkDuplicate(
node: TSESTree.TSIntersectionType | TSESTree.TSUnionType,
forEachNodeType?: (
constituentNodeType: Type,
constituentNode: TSESTree.TypeNode,
) => void,
): void {
const cachedTypeMap = new Map<Type, TSESTree.TypeNode>();
const uniqueConstituents: TSESTree.TypeNode[] = [];
const unionOrIntersection =
node.type === AST_NODE_TYPES.TSIntersectionType
? 'Intersection'
: 'Union';
for (const type of node.types) {
checkDuplicateRecursively(
unionOrIntersection,
type,
uniqueConstituents,
cachedTypeMap,
forEachNodeType,
);
}
}
TSUnionType(node: any): void¶
Parameters:
nodeany
Returns: void
Calls:
checkDuplicateisFunctionOrFunctionType (from ../util)maybeFunction.params.includestsutils.isTypeFlagSetreport
Code
(node): void => {
if (node.parent.type === AST_NODE_TYPES.TSUnionType) {
return;
}
checkDuplicate(node, (constituentNodeType, constituentNode) => {
const maybeTypeAnnotation = node.parent;
if (maybeTypeAnnotation.type === AST_NODE_TYPES.TSTypeAnnotation) {
const maybeIdentifier = maybeTypeAnnotation.parent;
if (
maybeIdentifier.type === AST_NODE_TYPES.Identifier &&
maybeIdentifier.optional
) {
const maybeFunction = maybeIdentifier.parent;
if (
isFunctionOrFunctionType(maybeFunction) &&
maybeFunction.params.includes(maybeIdentifier) &&
tsutils.isTypeFlagSet(
constituentNodeType,
ts.TypeFlags.Undefined,
)
) {
report('unnecessary', constituentNode);
}
}
}
});
}
Type Aliases¶
Options¶
MessageIds¶
UnionOrIntersection¶
Generated by Syntax Scribe