DEV Community

Cover image for The Wilson Else: A Linter for Branches That Pretend Nothing Happened
Regis Wilson
Regis Wilson

Posted on

The Wilson Else: A Linter for Branches That Pretend Nothing Happened

The Wilson Else: A Linter for Branches That Pretend Nothing Happened

Introduction

Every codebase has little lies in it. Lies are like bugs, but without the harmful side effects (usually). Some lies are obvious: a variable named safeConfig that is not safe, a function called validate() that mostly logs something and keeps going, or a deployment step named cleanup that creates three resources and deletes none.

But some lies are quieter. They are almost polite.

if (cached) {
  cache.delete(key);
}
Enter fullscreen mode Exit fullscreen mode

That code is not wrong. It is not even suspicious by itself. If the cache entry exists, delete it. If it does not exist, continue. Every programmer on Earth understands that.

We must however remember the genius of Newton, restated as:

For every if-then, there is an equal and opposite else.

Lies and the Hurtful Truths

If you stare at enough production systems, deployment code, authorization logic, cloud cleanup workflows, and similar machinery, this style starts to itch. Not because the code is hard to read, but because the code is easy to read too quickly.

There is a hidden branch in the code above.

if (cached) {
  cache.delete(key);
} else {
  // nothing happens
}
Enter fullscreen mode Exit fullscreen mode

Most of the time, that hidden branch is fine. Sometimes it is the whole bug. I call it the "unrealized else" because I'm a bit of an outlier. Even among outliers, I'm an outlier. I'm out lying in the street right now. That is, I'm lying down in a bed in the middle of the physical street, not spouting counter factual arguments in a public venue.

The Wilson Else

This is the idea behind what I am calling the Wilson Else, mostly because naming your own bad ideas is one of the few remaining joys in software engineering.

I am proposing it as an experiment, not announcing it as a law. I want to know whether other people who work in stateful, side-effecting systems recognize the same review problem, whether this rule finds useful examples of it, and whether they can see better ways to detect or express it.

The Wilson Else says:

Every meaningful branch should either handle the other case, return before the other case matters, or explicitly admit that nothing happens.

This is not a universal law. It should not be tattooed on your forearm unless you pay me a nominal license fee. It should not be used to turn every tiny one-line condition into a notarized document.

But in the right places, it is fire.

The Problem With the Lonely if

What is an "unrealized else"? To start with, the lonely if is everywhere:

if (user.isAdmin) {
  permissions.push('admin');
}
Enter fullscreen mode Exit fullscreen mode

Sometimes that is completely reasonable. We are accumulating a list. If the condition is true, add something. If it is false, do not add it.

But now look at this:

if (deployment.phase === 'delete') {
  await deleteResources(deployment);
}

await applyResources(deployment);
Enter fullscreen mode Exit fullscreen mode

Maybe that is correct.

Maybe delete is a pre-phase before apply.

Maybe it is a catastrophic bug.

The code does not tell you. The reader has to infer whether the missing else is intentional. In business logic, that inference is often manageable. In deployment systems, state machines, authorization checks, schema rendering, workflow orchestration, and cloud mutation code, that inference gets expensive.

The problem is not that an if without an else is bad.

The problem is that some if statements represent a fork in reality, and only one side of reality is written down.

That is the "unrealized else." So how do we fix it?

The First Bad Rule

The obvious linter rule for the Wilson Else is simple:

Flag every if without an else.

This is also how you make everyone hate you before lunch. I mean, even before you get to the office, turn on the computer, drink coffee at the water cooler, complain about Mondays, and then decide where to go for lunch.

Because this gets flagged:

if (!caller) {
  throw new UnauthorizedError();
}

return caller;
Enter fullscreen mode Exit fullscreen mode

But that code is good. The false branch is the rest of the function. The guard clause is explicit. It says: if this impossible or invalid condition happens, stop now. Otherwise, continue.

This also gets flagged:

if (!message.endsWith(':')) {
  message += ':';
}

return message;
Enter fullscreen mode Exit fullscreen mode

And yes, you could write:

if (!message.endsWith(':')) {
  message += ':';
} else {
  // do nothing
}

return message;
Enter fullscreen mode Exit fullscreen mode

But now the code has become annoying. It is not more correct. It is just a whole lot louder and a whole lot worse.

This is the lifecycle of every opinionated lint rule: the first five findings feel like moral clarity; the next ten feel like maybe we are overdoing it; and the next twenty feel like you invented paperwork, and you even start to wonder about the person who invented this--and it was yourself.

A global rule against a missing else is too blunt. The idea is good, because all my ideas are good. The blast radius is the problem.

Guard Clauses Are Not the Enemy

A good rule needs to understand termination.

This should be allowed:

if (!token) {
  throw new UnauthorizedError();
}

return authenticate(token);
Enter fullscreen mode Exit fullscreen mode

So should this:

if (!environment) {
  return null;
}

return renderEnvironment(environment);
Enter fullscreen mode Exit fullscreen mode

And this:

if (phase === 'delete') {
  return deleteResources();
} else if (phase === 'apply') {
  return applyResources();
} else {
  return assertNever(phase);
}
Enter fullscreen mode Exit fullscreen mode

In all three examples, the edge is visible. There is no unrealized branch drifting into the next statement. The code either stops, returns, throws, or exhaustively handles the alternatives. Also, I wrote all of them, so of course they're fine.

That is the heart of the rule: it is not "every if needs an else."

It is instead:

If the true branch does work and then falls through, the false branch deserves a moment of honesty.

Conditional Logging Is a Separate Crime

While experimenting with this rule, one pattern showed up immediately:

if (debug) {
  logger.debug({ deploymentId }, 'Deployment started');
}
Enter fullscreen mode Exit fullscreen mode

This is usually policy leaking into the call site.

The call site should say what happened:

logger.debug({ deploymentId }, 'Deployment started');
Enter fullscreen mode Exit fullscreen mode

The logger should decide whether debug logs are emitted.

That distinction matters. This is application logic:

if (!response.ok) {
  logger.warn(
    { status: response.status },
    'GitHub returned a non-2xx response',
  );
}
Enter fullscreen mode Exit fullscreen mode

The condition changes the meaning of the event. We only want the warning if something notable happened.

This is logging policy:

if (debug) {
  logger.debug('Some debug event happened');
}
Enter fullscreen mode Exit fullscreen mode

That condition does not describe the domain. It describes whether the program feels like talking today.

A linter can detect some of this, but it should be careful. I would not autofix conditional logging unless the logging API guarantees lazy evaluation or the arguments are cheap. Still, as a smell, it is real.

Call logger.debug(). Configure log filtering somewhere else.

Ruby Has a Pressure Valve

Ruby has a little escape hatch for this style:

message += ':' if !message.ends_with?(':')
message
Enter fullscreen mode Exit fullscreen mode

I understand why people like it. It is compact. It avoids ceremony. It says, "Make this tiny adjustment if needed, then return the value."

I also hate it, not because it is unreadable--it is very readable.

I hate it because the transformation and the result are split across two statements. The first line mutates the value. The second line quietly relies on Ruby's implicit return. The code has a little shrug built into it.

In TypeScript, I would rather write:

return message.endsWith(':') ? message : `${message}:`;
Enter fullscreen mode Exit fullscreen mode

Both outcomes are visible at the point where the value is decided.

That is really what this whole argument is about. Not else as a sacred keyword. Not braces for the sake of braces. The point is to make the decision visible where the decision matters.

The Nested Case Is Where It Gets Interesting

The first version of the linter was obnoxious.

It reported hundreds of warnings. Many were technically defensible. Many were also the kind of thing that makes developers turn off rules and never look back. If we don't have an easy and defensible way to explain every single instance of the flagged code, or an autofixer for the hundreds, thousands, or possibly hundreds of thousands of instances, then we will have failed.

Finally, the useful version appeared:

Only error on nested unrealized else branches.

That changes everything.

This is mildly suspicious:

if (cached) {
  cache.delete(key);
}
Enter fullscreen mode Exit fullscreen mode

This is worse:

if (!skipCache) {
  const cached = cache.get(key);

  if (cached && cached.expiresAt > Date.now()) {
    return cached.value;
  }

  if (cached) {
    cache.delete(key);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now the reader is inside a decision tree. We are already in a conditional world. A nested if without an else forces the reader to simulate the missing path while also remembering the outer path.

That is where the unrealized else gets brutal.

The nested version is not just a missing branch. It is a missing branch inside another branch. The mental stack is already loaded. The code should help.

If the missing case is intentional, write it down:

if (!skipCache) {
  const cached = cache.get(key);

  if (cached && cached.expiresAt > Date.now()) {
    return cached.value;
  }

  if (cached) {
    cache.delete(key);
  } else {
    // TODO: nothing
  }
}
Enter fullscreen mode Exit fullscreen mode

Is that obnoxious?

Yes.

Is it clear?

Also yes.

And the obnoxiousness is part of the point. The comment is a little accusation left in the code. It says: "We considered this branch, and for now, doing nothing is the decision."

Why TODO: nothing Is Better Than do nothing

The autofix could add:

else {
  // do nothing
}
Enter fullscreen mode Exit fullscreen mode

That is polite, but too polite.

I prefer:

else {
  // TODO: nothing
}
Enter fullscreen mode Exit fullscreen mode

That is funnier, but it is also more honest.

"Do nothing" sounds final. It sounds like a decision.

"TODO: nothing" sounds like the code knows it got away with something. It leaves a little grit in the file. It invites the next person to ask whether nothing is truly the right behavior.

Maybe it is. Great. Leave it.

Maybe it is not. Even better. The linter found a real design gap.

The point of an autofix is not always to produce beautiful code. Sometimes the point is to make the implicit thing explicit enough that it becomes reviewable.

What the Rule Looks Like

The useful rule has a few constraints:

  • Ignore normal, unnested if statements.
  • Ignore guard clauses that terminate with return, throw, break, or continue.
  • Ignore existing else branches.
  • Treat else if chains as part of the same decision.
  • Error only on nested, fallthrough if statements.
  • Autofix only those errors.
  • Add the explicit no-op branch.

The result is much less philosophical and much more useful.

Instead of hundreds of warnings, you get a smaller set of errors in places where the code is already asking the reader to keep track of multiple paths.

That matters. A linter rule should not merely express taste. It should find places where taste and correctness overlap.

Nested conditionals are one of those places.

Where This Belongs

I would not enforce this everywhere.

For example, UI code often has little conditional additions:

if (showSubtitle) {
  parts.push(subtitle);
}
Enter fullscreen mode Exit fullscreen mode

That does not need a Wilson Else. Please do not make frontend developers write:

else {
  // TODO: nothing
}
Enter fullscreen mode Exit fullscreen mode

for every conditional render, unless you want them to unionize against you.

But there are places where I do want this pressure:

  • Authorization
  • Deployment orchestration
  • Cloud resource deletion
  • Schema generation
  • State machines
  • Workflow transitions
  • Billing logic
  • Cleanup jobs
  • Security-sensitive routing
  • Anything that mutates infrastructure

In these areas, the cost of being explicit is smaller than the cost of assuming everyone reads your mind.

Deployment code especially benefits from this. Deployments are full of phases: create, apply, patch, delete, rollback, cleanup, skip, and retry. Missing one branch can leave stale resources behind or apply a thing that should have been deleted.

A nested unrealized else in deployment code is rarely just style. It is often a question the code forgot to answer.

The Rule Is a Flashlight, Not a Religion

This is where engineering judgment matters.

The rule is not saying every condition needs ceremony. It is saying some conditions create unspoken behavior, and unspoken behavior is where bugs like to live.

The rule is a flashlight.

Run it. Look at the findings. Decide whether the code is missing an actual branch or whether the branch is intentionally empty.

If it is intentionally empty, the autofix gives you:

else {
  // TODO: nothing
}
Enter fullscreen mode Exit fullscreen mode

If that makes the code look ridiculous, maybe the rule is too aggressive for that file.

If it makes the code look safer, maybe the code was relying on vibes.

This is the sweet spot for custom linting. Not a universal mandate. A local pressure tool for a codebase with known failure modes.

The best lint rules are born from irritation. Not abstract best practice. Not "some famous person said never do this." Real irritation. A pattern keeps showing up in real code and making real review harder.

The Wilson Else came from that kind of irritation. Eventually, I hope that review comments on complex platform codebases will include lines like, "Check your unrealized else here," and "I think this needs a Wilson Else."

Why This Matters More in Platform Code

Platform code is different from product code.

Product code can often fail visibly. A button does not work. A page renders incorrectly. A request returns a bad response. The feedback loop is painful but obvious.

Platform code can fail quietly.

A cleanup branch does not run. A stale DNS record stays alive. A deleted service still has a policy attached. A deployment applies after a delete. A cache is invalidated in one path but not another. A permission check falls through and relies on a default nobody remembers.

These are exactly the places where "nothing happens" should be a written decision.

Platform systems accumulate invisible state. The code is not just transforming data. It is coordinating reality outside the process: clusters, cloud APIs, databases, environments, routes, secrets, service accounts, jobs, controllers, and retries.

When code like that has a nested conditional, every missing branch is a tiny operational assumption.

Write the assumption down.

The Wilson Else, in Human Form

Here is the rule in human form:

A nested if that falls through without an else must either become a guard clause, handle the alternative, or explicitly say that nothing happens.

That is the Wilson Else.

It is a little obnoxious. Good.

It is not for every line of code. Also good.

It is fixable, unlike its author. Very good.

The autofix is intentionally plain:

else {
  // TODO: nothing
}
Enter fullscreen mode Exit fullscreen mode

You can hate it. You are supposed to hate it a little. That is how you know the linter is applying pressure.

The trick is to apply pressure in the right place.

Too broad, and the rule becomes noise.

Too narrow, and it never catches anything.

Nested-only feels like the right starting point. It catches the places where the reader is already inside a branch and the missing alternative is most likely to cause confusion.

That is not a manifesto.

That is a useful annoyance.

What I Am Actually Proposing

The Wilson Else is not yet a rule I can prove that anyone should enforce. It is a name for a recurring review problem and a deliberately crude instrument for making that problem visible.

The instrument may be wrong. Nested conditionals may be too weak a proxy. TODO: nothing may be the wrong marker. The useful signal may instead be some combination of external effects, mutation, fallthrough, and incomplete state transitions. An autofix also cannot prove that a human considered the false path; at most, it can put that path in front of one.

That is why I am publishing the rule. I want to find out whether like-minded people encounter the same problem, whether its findings survive contact with real code, and whether someone has a more precise solution.

A reasonable experiment would be:

  1. Run the rule in report-only mode on platform code.
  2. Classify each finding as harmless accumulation, intentional no-op, unclear intent, or genuine defect.
  3. Record what distinguished useful findings: nesting, side effects, mutation, domain, or something else.
  4. Refine the rule around those observations--or discard it if there is no reliable signal.

If the experiment produces nothing but ceremony, that is an answer. If it identifies a recurring class of omitted decisions, then the obnoxious prototype has done its job.

Conclusion

Most code-style arguments are boring because they pretend to be about beauty when they are actually about risk.

The Wilson Else is not beautiful. An autofixed else { /* TODO: nothing */ } block is not going to win any poetry contests. It might even make you angry the first time you see it.

But it does something valuable: it turns an implicit path into an explicit artifact.

Now the reviewer can ask:

  • Is nothing really correct here?
  • Should this be a guard clause?
  • Should this be an else if chain?
  • Should this throw?
  • Should this return?
  • Did we forget a deployment phase?
  • Did we forget a cleanup path?

That is the whole game.

Software does not need more rules for the sake of rules. It needs better ways to expose assumptions before they become incidents.

The Wilson Else might be one of those ways. For now, it is a proposal, a probe, and an invitation to improve the idea.

Let the h8rs h8.

A Scholarly View--and the Strongest Cases on Both Sides

The Wilson Else can be understood as a small intervention in cognitive complexity. McCabe's classic work on cyclomatic complexity counts independent paths through a program; later work on cognitive complexity asks how difficult those paths are for a human to hold in mind. A nested, one-sided conditional adds a path while leaving one outcome implicit. The rule's strongest intellectual case is therefore not that an else is inherently virtuous, but that high-risk code should externalize decisions that would otherwise live only in the reader's working memory.

Steelman: the case for the rule

The strongest case for the Wilson Else is in stateful, side-effecting systems. In deployment, authorization, billing, and cleanup code, inaction is often an observable policy choice. An explicit no-op branch exposes that choice for review, though it cannot by itself prove that the author considered it. Scoped to nested conditionals, the rule also targets code where readers already bear a higher cognitive load, rather than taxing every ordinary conditional.

The rule may also improve maintenance as a form of lightweight design pressure. If // TODO: nothing looks absurd, that discomfort can reveal that the conditional should instead be flattened, returned from early, expressed as an exhaustive state transition, or decomposed into a named function. In this steelman, the awkward autofix is diagnostic rather than stylistic.

Steelman: the case against the rule

The strongest objection is that explicit syntax is not the same as explicit understanding. An empty else can become ritualized noise, and an autofixed comment may falsely imply that a human considered the branch. Worse, TODO: nothing can manufacture permanent, unactionable debt. Readers may spend more effort filtering boilerplate than reasoning about behavior--the very cognitive cost the rule intends to reduce.

There is also a semantic limitation. Syntactic nesting is only a proxy for risk. A deeply nested pure-data transformation may be harmless, while a top-level one-sided conditional that deletes production resources may be critical. Likewise, correctly deciding whether a branch terminates requires control-flow analysis; a syntax-only approximation can misclassify helper calls, exceptions, labeled breaks, loops, and language-specific constructs.

Most charitable view

The most defensible form of the Wilson Else is therefore not "every missing else is a defect." It is: in code where omitted action carries operational meaning, make the omission reviewable. That narrower claim preserves the idea's practical force while taking its critics seriously.

Further research on this topic and design in an existing or new programming language (especialy in the era of AI coders) maybe be fruitful.

Appendix: Photo source

Photo by Sophia Kunkel on Unsplash

Appendix: Experimental ESLint Rule

// SPDX-License-Identifier: MIT
// Copyright (c) 2026 We, The People

/**
 * Experimental rule to flag if statements that handle the true branch and then
 * fall through without saying what the false branch means.
 */

/** @type {import('eslint').Rule.RuleModule} */
const LOGGER_METHODS = new Set([
  'trace',
  'debug',
  'info',
  'warn',
  'error',
  'fatal',
]);

export const rule = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'flag if statements with fallthrough true branches and no explicit else',
      recommended: false,
    },
    fixable: 'code',
    schema: [
      {
        type: 'object',
        properties: {
          mode: {
            enum: ['all', 'nested'],
          },
          conditionalLogging: {
            type: 'boolean',
          },
        },
        additionalProperties: false,
      },
    ],
    messages: {
      unrealizedElse:
        'Unrealized else: this if handles only the true branch and then continues. Add an else, return early, or leave an explicit false-case marker.',
      conditionalLogging:
        'Conditional logging: call logger.{{method}}() directly and configure log filtering in the logger.',
    },
  },
  create(context) {
    const sourceCode = context.sourceCode ?? context.getSourceCode?.();
    const options = context.options[0] ?? {};
    const mode = options.mode ?? 'all';
    const conditionalLogging = options.conditionalLogging ?? true;
    const ifStatementStack = [];

    function checkIfStatement(node, isNested) {
      if (node.alternate) {
        return;
      }

      if (statementAlwaysTerminates(node.consequent)) {
        return;
      }

      if (sourceCode && hasExplicitFalseCaseMarker(sourceCode, node)) {
        return;
      }

      if (mode === 'nested' && !isNested) {
        return;
      }

      const loggerMethod = getOnlyLoggerMethod(node.consequent);
      if (loggerMethod) {
        if (conditionalLogging && isLoggingPolicyCheck(node.test)) {
          context.report({
            node,
            messageId: 'conditionalLogging',
            data: { method: loggerMethod },
          });
        }
        return;
      }

      context.report({
        node,
        messageId: 'unrealizedElse',
        fix(fixer) {
          if (!sourceCode) {
            return null;
          }

          const token = sourceCode.getLastToken(node.consequent);
          if (!token) {
            return null;
          }

          const indent = getNodeIndent(sourceCode, node);
          return fixer.insertTextAfter(
            token,
            ` else {\n${indent}  // TODO: nothing\n${indent}}`,
          );
        },
      });
    }

    return {
      IfStatement(node) {
        const isNested = isNestedIfStatement(node, ifStatementStack);
        ifStatementStack.push(node);
        checkIfStatement(node, isNested);
      },
      'IfStatement:exit'() {
        ifStatementStack.pop();
      },
    };
  },
};

function statementAlwaysTerminates(node) {
  switch (node.type) {
    case 'ReturnStatement':
    case 'ThrowStatement':
    case 'ContinueStatement':
    case 'BreakStatement':
      return true;
    case 'BlockStatement':
      return node.body.some((statement) =>
        statementAlwaysTerminates(statement),
      );
    case 'IfStatement':
      return Boolean(
        node.alternate &&
        statementAlwaysTerminates(node.consequent) &&
        statementAlwaysTerminates(node.alternate),
      );
    case 'TryStatement':
      if (node.finalizer && statementAlwaysTerminates(node.finalizer)) {
        return true;
      }

      return Boolean(
        statementAlwaysTerminates(node.block) &&
        node.handler &&
        statementAlwaysTerminates(node.handler.body),
      );
    default:
      return false;
  }
}

function getOnlyLoggerMethod(statement) {
  const expressionStatement = getOnlyExpressionStatement(statement);
  if (!expressionStatement) {
    return null;
  }

  const expression = expressionStatement.expression;
  if (
    expression.type !== 'CallExpression' ||
    expression.callee.type !== 'MemberExpression' ||
    expression.callee.computed ||
    expression.callee.object.type !== 'Identifier' ||
    expression.callee.object.name !== 'logger' ||
    expression.callee.property.type !== 'Identifier'
  ) {
    return null;
  }

  const method = expression.callee.property.name;
  return LOGGER_METHODS.has(method) ? method : null;
}

function isLoggingPolicyCheck(node) {
  if (node.type === 'Identifier') {
    return /^(debug|verbose)$/iu.test(node.name);
  }

  if (node.type === 'MemberExpression' && node.property.type === 'Identifier') {
    return /^(debug|verbose|is(Debug|Trace|Info|Warn|Error|Fatal|Verbose)Enabled)$/u.test(
      node.property.name,
    );
  }

  if (node.type !== 'CallExpression') {
    return false;
  }

  const callee = node.callee;
  if (callee.type === 'Identifier') {
    return /^(is(Debug|Trace|Info|Warn|Error|Fatal|Verbose)Enabled)$/u.test(
      callee.name,
    );
  }

  if (
    callee.type !== 'MemberExpression' ||
    callee.property.type !== 'Identifier'
  ) {
    return false;
  }

  return /^(isLevelEnabled|is(Debug|Trace|Info|Warn|Error|Fatal|Verbose)Enabled)$/u.test(
    callee.property.name,
  );
}

function getOnlyExpressionStatement(statement) {
  if (statement.type === 'ExpressionStatement') {
    return statement;
  }

  if (statement.type !== 'BlockStatement' || statement.body.length !== 1) {
    return null;
  }

  const [onlyStatement] = statement.body;
  return onlyStatement.type === 'ExpressionStatement' ? onlyStatement : null;
}

function hasExplicitFalseCaseMarker(sourceCode, node) {
  const comments = [
    ...sourceCode.getCommentsBefore(node).slice(-1),
    ...sourceCode.getCommentsAfter(node).slice(0, 1),
  ];

  return comments.some((comment) =>
    /\b(no-unrealized-else|unrealized else|false case|else intentionally omitted|intentionally no else)\b/iu.test(
      comment.value,
    ),
  );
}

function getNodeIndent(sourceCode, node) {
  const lineStartIndex =
    sourceCode.text.lastIndexOf('\n', node.range[0] - 1) + 1;
  const linePrefix = sourceCode.text.slice(lineStartIndex, node.range[0]);
  return linePrefix.match(/^\s*/u)?.[0] ?? '';
}

function isNestedIfStatement(node, ancestorIfStatements) {
  return ancestorIfStatements.some((ancestor) => ancestor.alternate !== node);
}

export default {
  rules: {
    'unrealized-else': rule,
  },
};
Enter fullscreen mode Exit fullscreen mode

Appendix: ESLint Configuration

// SPDX-License-Identifier: MIT
// Copyright (c) 2026 We, The People

// @ts-check

import tseslint from 'typescript-eslint';
import unrealizedElse from './eslint-rules/unrealized-else.js';

const config = [
  {
    ignores: [
      'src/lib/schemas/k8s_kinds/v1.29.7-standalone-strict/',
      'src/generated/**',
      'src/test/**',
      'src/**/*.test.ts',
      '*.test.ts',
    ],
  },
  {
    linterOptions: {
      reportUnusedDisableDirectives: 'off',
    },
  },
  {
    files: ['src/**/*.ts'],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
      },
    },
    plugins: /** @type {Record<string, any>} */ ({
      'unrealized-else': unrealizedElse,
    }),
    rules: {
      'unrealized-else/unrealized-else': [
        'error',
        {
          mode: 'nested',
          conditionalLogging: false,
        },
      ],
    },
  },
];

export default config;
Enter fullscreen mode Exit fullscreen mode

Disclosure and Licensing

This article was co-written with generative AI. The author supplied the central idea, examples, code, arguments, and editorial direction; AI assisted with organization, light copyediting, Markdown conversion, counterarguments, and refinement. The author reviewed and takes responsibility for the published result.

Unless otherwise noted, the original code examples in this article are offered under the MIT License. The prose and other non-code material are not included in that code license. Third-party packages and APIs mentioned or imported by the examples remain subject to their own licenses. This notice is provided for clarity and is not legal advice.

Full MIT License for the original code examples

MIT License

Copyright (c) 2026 We, The People

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
mihai_leanzero profile image
Mihai Perdum •

Regis, the nested-only scoping is what saves this from being a linter everyone disables in week one. Hit almost exactly this shape of bug in an agent's phase-transition handler once - a nested branch that quietly skipped a cleanup step, no exception, no log line, just state that drifted for days before anyone noticed. The conditional-logging distinction is sharp too, I've caught myself writing "if (debug)" guards that should've just called debug() and let the logger decide. Does the nesting rule treat a switch statement without a default case the same way, or is that exempted for now?

Collapse
 
regis-ud profile image
Regis Wilson •

Currently the Wilson Else only applies to if but expansion into case statements is definitely "on the roadmap". Default cases are an exceptional start (I believe there might already be such a linter for it too already and if so it should be enabled!). I also turned on the linter for enforcing a break during case statements and you have to override it if you want the "fall through" behaviour. Also things like if () {} else if () {} should probably be converted to case statements maybe. I am still kind of ambivalent on this topic and have seen a lot of pushback against it. Thanks for the ideas!