Fallowing ESLint rules with else if

I have this code else/if code

       if (_.has($scope.item, 'comment_id')) {
          track(one);
        } else if (_.has($scope.item, 'post_id')) {
          track(two);
        } else {
          if ($scope.followers) {
            track(three);
          } else {
            track(four);
          }
        }

but ESlint want’s me to turn it into this

        if (_.has($scope.item, 'comment_id')) {
          track(one);
        } else if (_.has($scope.item, 'post_id')) {
          track(two);
        } else if ($scope.followers) {
          track(three);
        } else {
          track(four);
        }

Are they the same thing?

Answer

Yes, they are equivalent. ESLint is smart enough to detect that and hence the suggestion. The reason is that you only really have four alternatives in the if/else construct – when the code doesn’t match any of the first two conditions, it will always execute the outer

else {
  if ($scope.followers) {
    track(three);
  } else {
    track(four);
  }
}

And that will only ever result in one of two things happening. Extracting the if ($scope.followers) as an else if follows the exact same logic path.

This can be demonstrated very easily by abstracting away the conditions and generating a truth table, then checking the results. It can be done on paper but since you already have the code, it’s also easy to make it as code:

function nested(a, b, c) {
  if (a) {
    return "track(one)";
  } else if (b) {
    return "track(two)";
  } else {
    if (c) {
      return "track(three)";
    } else {
      return "track(four)";
    }
  }
}

function chained(a, b, c) {
  if (a) {
    return "track(one)";
  } else if (b) {
    return "track(two)";
  } else if (c) {
    return "track(three)";
  } else {
    return "track(four)";
  }
}

const truthTable = [
  [false, false, false],
  [false, false, true ],
  [false, true , false],
  [false, true , true ],
  [true , false, false],
  [true , false, true ],
  [true , true , false],
  [true , true , true ],
];

for(const [a, b, c] of truthTable) {
  const nestedResult = nested(a, b, c);
  console.log(`called nested() with 
  a=${a}
  b=${b}
  c=${c}
  result: ${nestedResult}`);
  
  const chainedResult = chained(a, b, c);
  console.log(`called nested() with 
  a=${a}
  b=${b}
  c=${c}
  result: ${chainedResult}`);
  
  console.log(`matching results?: ${nestedResult === chainedResult}`);
  
  console.log(`------------------------`);
}

Alternatively, you can generate an actual truth table to visualise the results:

function nested(a, b, c) {
  if (a) {
    return "track(one)";
  } else if (b) {
    return "track(two)";
  } else {
    if (c) {
      return "track(three)";
    } else {
      return "track(four)";
    }
  }
}

function chained(a, b, c) {
  if (a) {
    return "track(one)";
  } else if (b) {
    return "track(two)";
  } else if (c) {
    return "track(three)";
  } else {
    return "track(four)";
  }
}

const truthTable = [
  [false, false, false],
  [false, false, true ],
  [false, true , false],
  [false, true , true ],
  [true , false, false],
  [true , false, true ],
  [true , true , false],
  [true , true , true ],
];

const enrich = truthTable
  .map(row => row.concat(nested(...row), chained(...row))) //add the results of the two calls
  .map(row => row.concat(row[row.length-1] === row[row.length-2])) //compare the last two

const table = document.querySelector("table");
for (const rowData of enrich) {
  const newRow = table.insertRow(-1);
  for (const rowValue of rowData) {
    const cell = newRow.insertCell(-1);
    cell.textContent = rowValue;
  }
}
table {
  border-collapse: collapse;
}

table, th, td {
  border: 1px solid black;
}
<table>
  <tr>
    <th>a</th>
    <th>b</th>
    <th>c</th>
    <th>nested result</th>
    <th>chained result</th>
    <th>results equal</th>
  </tr>
</table>