delimiterDangle.js.flow 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import _ from 'lodash';
  2. const schema = [
  3. {
  4. enum: ['always', 'always-multiline', 'only-multiline', 'never'],
  5. type: 'string',
  6. },
  7. {
  8. enum: ['always', 'always-multiline', 'only-multiline', 'never'],
  9. type: 'string',
  10. },
  11. {
  12. enum: ['always', 'always-multiline', 'only-multiline', 'never'],
  13. type: 'string',
  14. },
  15. ];
  16. // required for reporting the correct position
  17. const getLast = (property, indexer) => {
  18. if (!property) {
  19. return indexer;
  20. }
  21. if (!indexer) {
  22. return property;
  23. }
  24. if (property.loc.end.line > indexer.loc.end.line) {
  25. return property;
  26. }
  27. if (indexer.loc.end.line > property.loc.end.line) {
  28. return indexer;
  29. }
  30. if (property.loc.end.column > indexer.loc.end.column) {
  31. return property;
  32. }
  33. return indexer;
  34. };
  35. const create = (context) => {
  36. const option = context.options[0] || 'never';
  37. const interfaceOption = context.options[1] || option;
  38. const inexactNotationOption = context.options[2] || 'never';
  39. const sourceCode = context.getSourceCode();
  40. const getNodeOption = (node) => {
  41. if (node.parent.type === 'InterfaceDeclaration') {
  42. return interfaceOption;
  43. }
  44. if (node.inexact) {
  45. return inexactNotationOption;
  46. }
  47. return option;
  48. };
  49. const reporter = (node, message, fix) => () => {
  50. context.report({
  51. fix,
  52. message,
  53. node,
  54. });
  55. };
  56. const makeReporters = (node, tokenToFix) => ({
  57. dangle: reporter(node, 'Unexpected trailing delimiter', (fixer) => fixer.replaceText(tokenToFix, '')),
  58. noDangle: reporter(node, 'Missing trailing delimiter', (fixer) => fixer.insertTextAfter(tokenToFix, ',')),
  59. });
  60. const evaluate = (node, lastChildNode) => {
  61. if (!lastChildNode && !node.inexact) {
  62. return;
  63. }
  64. const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
  65. const isDangling = [';', ','].includes(penultimateToken.value);
  66. const isMultiLine = penultimateToken.loc.start.line !== lastToken.loc.start.line;
  67. // Use the object node if it's inexact since there's no child node for the inexact notation
  68. const report = makeReporters(node.inexact ? node : lastChildNode, penultimateToken);
  69. const nodeOption = getNodeOption(node);
  70. if (nodeOption === 'always' && !isDangling) {
  71. report.noDangle();
  72. return;
  73. }
  74. if (nodeOption === 'never' && isDangling) {
  75. report.dangle();
  76. return;
  77. }
  78. if (nodeOption === 'always-multiline' && !isDangling && isMultiLine) {
  79. report.noDangle();
  80. return;
  81. }
  82. if (nodeOption === 'always-multiline' && isDangling && !isMultiLine) {
  83. report.dangle();
  84. return;
  85. }
  86. if (nodeOption === 'only-multiline' && isDangling && !isMultiLine) {
  87. report.dangle();
  88. }
  89. };
  90. return {
  91. ObjectTypeAnnotation(node) {
  92. evaluate(node, getLast(_.last(node.properties), _.last(node.indexers)));
  93. },
  94. TupleTypeAnnotation(node) {
  95. evaluate(node, _.last(node.types));
  96. },
  97. };
  98. };
  99. export default {
  100. create,
  101. meta: {
  102. fixable: 'code',
  103. },
  104. schema,
  105. };