Skip to content
Sarj Code Standards

no-redundant-consumer-union

Keep the covering object contract in a private scalar consumer union.

Why

A private consumer can advertise a redundant object alternative even though native assignability proves that the other alternative accepts every covered value. Dog | Animal keeps Animal; requiring Dog would unnecessarily restrict callers.

Fix

Review retaining the covering alternative, or revise the alternatives if distinct cases were intended. Preserve producer types and runtime objects. The safe fix edits only a proven private parameter annotation; public APIs, producer types and runtime objects remain unchanged.

Examples

Before — flagged The private consumer needs the covering Animal contract
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Dog | Animal): string {
return value.name;
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
After — preferred A public union retains its observable alternatives
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
export function label(value: Dog | Animal): string {
return value.name;
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Autofix output
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Animal): string {
return value.name;
}
label(dog);
Before — flagged A local helper receives only the required name
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function formatName(value: string): string {
return "[" + value + "]";
}
function label(value: Dog | Animal): string {
return formatName(value.name);
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
After — preferred Passing the whole object remains outside the scalar helper proof
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function formatAnimal(value: Animal): string {
return value.name;
}
function label(value: Dog | Animal): string {
return formatAnimal(value);
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Autofix output
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function formatName(value: string): string {
return "[" + value + "]";
}
function label(value: Animal): string {
return formatName(value.name);
}
label(dog);
Before — flagged A nullable name retains the covering Animal contract
src/case.ts
export {};
interface Animal {
readonly name: string | null;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Dog | Animal): string {
return value.name ?? "";
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
After — preferred A literal breed keeps the authored domain contract
src/case.ts
export {};
interface Animal {
readonly name: string | null;
}
interface Dog extends Animal {
readonly breed: "Collie";
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Dog | Animal): string {
return value.name ?? "";
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Autofix output
src/case.ts
export {};
interface Animal {
readonly name: string | null;
}
interface Dog extends Animal {
readonly breed: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Animal): string {
return value.name ?? "";
}
label(dog);
Before — flagged An unread optional breed retains the smaller Animal surface
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed?: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Dog | Animal): string {
return value.name;
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
After — preferred A public optional union retains its observable alternatives
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed?: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
export function label(value: Dog | Animal): string {
return value.name;
}
label(dog);
tsconfig.json
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Autofix output
src/case.ts
export {};
interface Animal {
readonly name: string;
}
interface Dog extends Animal {
readonly breed?: string;
}
export type KeptDog = Dog;
const dog: Dog = { name: "Ada", breed: "Collie" };
function label(value: Animal): string {
return value.name;
}
label(dog);