no-lonely-if.js 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. /**
  2. * @fileoverview Rule to disallow if as the only statement in an else block
  3. * @author Brandon Mills
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../shared/types').Rule} */
  10. module.exports = {
  11. meta: {
  12. type: "suggestion",
  13. docs: {
  14. description: "Disallow `if` statements as the only statement in `else` blocks",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/no-lonely-if"
  17. },
  18. schema: [],
  19. fixable: "code",
  20. messages: {
  21. unexpectedLonelyIf: "Unexpected if as the only statement in an else block."
  22. }
  23. },
  24. create(context) {
  25. const sourceCode = context.getSourceCode();
  26. return {
  27. IfStatement(node) {
  28. const ancestors = context.getAncestors(),
  29. parent = ancestors.pop(),
  30. grandparent = ancestors.pop();
  31. if (parent && parent.type === "BlockStatement" &&
  32. parent.body.length === 1 && grandparent &&
  33. grandparent.type === "IfStatement" &&
  34. parent === grandparent.alternate) {
  35. context.report({
  36. node,
  37. messageId: "unexpectedLonelyIf",
  38. fix(fixer) {
  39. const openingElseCurly = sourceCode.getFirstToken(parent);
  40. const closingElseCurly = sourceCode.getLastToken(parent);
  41. const elseKeyword = sourceCode.getTokenBefore(openingElseCurly);
  42. const tokenAfterElseBlock = sourceCode.getTokenAfter(closingElseCurly);
  43. const lastIfToken = sourceCode.getLastToken(node.consequent);
  44. const sourceText = sourceCode.getText();
  45. if (sourceText.slice(openingElseCurly.range[1],
  46. node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
  47. // Don't fix if there are any non-whitespace characters interfering (e.g. comments)
  48. return null;
  49. }
  50. if (
  51. node.consequent.type !== "BlockStatement" && lastIfToken.value !== ";" && tokenAfterElseBlock &&
  52. (
  53. node.consequent.loc.end.line === tokenAfterElseBlock.loc.start.line ||
  54. /^[([/+`-]/u.test(tokenAfterElseBlock.value) ||
  55. lastIfToken.value === "++" ||
  56. lastIfToken.value === "--"
  57. )
  58. ) {
  59. /*
  60. * If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing
  61. * the issue would not change semantics due to ASI. If this would happen, don't do a fix.
  62. */
  63. return null;
  64. }
  65. return fixer.replaceTextRange(
  66. [openingElseCurly.range[0], closingElseCurly.range[1]],
  67. (elseKeyword.range[1] === openingElseCurly.range[0] ? " " : "") + sourceCode.getText(node)
  68. );
  69. }
  70. });
  71. }
  72. }
  73. };
  74. }
  75. };