switch-case-braces.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. 'use strict';
  2. const {isColonToken} = require('@eslint-community/eslint-utils');
  3. const getSwitchCaseHeadLocation = require('./utils/get-switch-case-head-location.js');
  4. const getIndentString = require('./utils/get-indent-string.js');
  5. const {replaceNodeOrTokenAndSpacesBefore} = require('./fix/index.js');
  6. const MESSAGE_ID_EMPTY_CLAUSE = 'switch-case-braces/empty';
  7. const MESSAGE_ID_MISSING_BRACES = 'switch-case-braces/missing';
  8. const MESSAGE_ID_UNNECESSARY_BRACES = 'switch-case-braces/unnecessary';
  9. const messages = {
  10. [MESSAGE_ID_EMPTY_CLAUSE]: 'Unexpected braces in empty case clause.',
  11. [MESSAGE_ID_MISSING_BRACES]: 'Missing braces in case clause.',
  12. [MESSAGE_ID_UNNECESSARY_BRACES]: 'Unnecessary braces in case clause.',
  13. };
  14. function * removeBraces(fixer, node, sourceCode) {
  15. const [blockStatement] = node.consequent;
  16. const openingBraceToken = sourceCode.getFirstToken(blockStatement);
  17. yield * replaceNodeOrTokenAndSpacesBefore(openingBraceToken, '', fixer, sourceCode);
  18. const closingBraceToken = sourceCode.getLastToken(blockStatement);
  19. yield fixer.remove(closingBraceToken);
  20. }
  21. function * addBraces(fixer, node, sourceCode) {
  22. const colonToken = sourceCode.getTokenAfter(
  23. node.test || sourceCode.getFirstToken(node),
  24. isColonToken,
  25. );
  26. yield fixer.insertTextAfter(colonToken, ' {');
  27. const lastToken = sourceCode.getLastToken(node);
  28. const indent = getIndentString(node, sourceCode);
  29. yield fixer.insertTextAfter(lastToken, `\n${indent}}`);
  30. }
  31. /** @param {import('eslint').Rule.RuleContext} context */
  32. const create = context => {
  33. const isBracesRequired = context.options[0] !== 'avoid';
  34. const sourceCode = context.getSourceCode();
  35. return {
  36. SwitchCase(node) {
  37. const {consequent} = node;
  38. if (consequent.length === 0) {
  39. return;
  40. }
  41. if (
  42. consequent.length === 1
  43. && consequent[0].type === 'BlockStatement'
  44. && consequent[0].body.length === 0
  45. ) {
  46. return {
  47. node,
  48. loc: sourceCode.getFirstToken(consequent[0]).loc,
  49. messageId: MESSAGE_ID_EMPTY_CLAUSE,
  50. fix: fixer => removeBraces(fixer, node, sourceCode),
  51. };
  52. }
  53. if (
  54. isBracesRequired
  55. && !(
  56. consequent.length === 1
  57. && consequent[0].type === 'BlockStatement'
  58. )
  59. ) {
  60. return {
  61. node,
  62. loc: getSwitchCaseHeadLocation(node, sourceCode),
  63. messageId: MESSAGE_ID_MISSING_BRACES,
  64. fix: fixer => addBraces(fixer, node, sourceCode),
  65. };
  66. }
  67. if (
  68. !isBracesRequired
  69. && consequent.length === 1
  70. && consequent[0].type === 'BlockStatement'
  71. && consequent[0].body.every(node =>
  72. node.type !== 'VariableDeclaration'
  73. && node.type !== 'FunctionDeclaration',
  74. )
  75. ) {
  76. return {
  77. node,
  78. loc: sourceCode.getFirstToken(consequent[0]).loc,
  79. messageId: MESSAGE_ID_UNNECESSARY_BRACES,
  80. fix: fixer => removeBraces(fixer, node, sourceCode),
  81. };
  82. }
  83. },
  84. };
  85. };
  86. /** @type {import('eslint').Rule.RuleModule} */
  87. module.exports = {
  88. create,
  89. meta: {
  90. type: 'layout',
  91. docs: {
  92. description: 'Enforce consistent brace style for `case` clauses.',
  93. },
  94. fixable: 'code',
  95. schema: [{enum: ['always', 'avoid']}],
  96. messages,
  97. },
  98. };