Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(eslint-plugin): [no-redundant-type-constituents] incorrectly marks & string as redundant #8282

Merged
merged 6 commits into from Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -272,6 +272,10 @@ export default createRule({
PrimitiveTypeFlag,
TSESTree.TypeNode[]
>();
const seenUnionTypes = new Map<
TSESTree.TypeNode,
TypeFlagsWithName[]
>();

function checkIntersectionBottomAndTopTypes(
{ typeFlags, typeName }: TypeFlagsWithName,
Expand Down Expand Up @@ -323,8 +327,58 @@ export default createRule({
}
}
}
// if any typeNode is TSTypeReference and typePartFlags have more than 1 element, than the referenced type is definitely a union.
if (typePartFlags.length >= 2) {
seenUnionTypes.set(typeNode, typePartFlags);
}
}
/**
* @example
* ```ts
* type F = "a"|2|"b";
* type I = F & string;
* ```
* This function checks if all the union members of `F` are assignable to the other member of `I`. If every member is assignable, then its reported else not.
*/
const checkIfUnionsAreAssignable = (): undefined => {
for (const [typeRef, typeValues] of seenUnionTypes) {
let primitive: number | undefined = undefined;
for (const { typeFlags } of typeValues) {
if (
seenPrimitiveTypes.has(
literalToPrimitiveTypeFlags[
typeFlags as keyof typeof literalToPrimitiveTypeFlags
],
)
) {
primitive =
literalToPrimitiveTypeFlags[
typeFlags as keyof typeof literalToPrimitiveTypeFlags
];
} else {
primitive = undefined;
break;
arka1002 marked this conversation as resolved.
Show resolved Hide resolved
}
}
if (Number.isInteger(primitive)) {
context.report({
data: {
literal: typeValues.map(name => name.typeName).join(' | '),
primitive:
primitiveTypeFlagNames[
primitive as keyof typeof primitiveTypeFlagNames
],
},
messageId: 'primitiveOverridden',
node: typeRef,
});
}
}
};
if (seenUnionTypes.size > 0) {
checkIfUnionsAreAssignable();
return;
}

// For each primitive type of all the seen primitive types,
// if there was a literal type seen that overrides it,
// report each of the primitive type's type nodes
Expand Down
Expand Up @@ -161,6 +161,10 @@ ruleTester.run('no-redundant-type-constituents', rule, {
type B = \`\${string}\`;
type T = B & null;
`,
`
type T = 'a' | 1 | 'b';
type U = T & string;
`,
],

invalid: [
Expand Down Expand Up @@ -785,5 +789,46 @@ ruleTester.run('no-redundant-type-constituents', rule, {
},
],
},
{
code: `
type T = 'a' | 'b';
type U = T & string;
`,
errors: [
{
column: 18,
data: {
literal: '"a" | "b"',
primitive: 'string',
},
messageId: 'primitiveOverridden',
},
],
},
{
code: `
type S = 1 | 2;
type T = 'a' | 'b';
type U = S & T & string & number;
`,
errors: [
{
column: 18,
data: {
literal: '1 | 2',
primitive: 'number',
},
messageId: 'primitiveOverridden',
},
{
column: 22,
data: {
literal: '"a" | "b"',
primitive: 'string',
},
messageId: 'primitiveOverridden',
},
],
},
],
});