function-call-argument-newline.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /**
  2. * @fileoverview Rule to enforce line breaks between arguments of a function call
  3. * @author Alexey Gonchar <https://github.com/finico>
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../shared/types').Rule} */
  10. module.exports = {
  11. meta: {
  12. type: "layout",
  13. docs: {
  14. description: "Enforce line breaks between arguments of a function call",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/function-call-argument-newline"
  17. },
  18. fixable: "whitespace",
  19. schema: [
  20. {
  21. enum: ["always", "never", "consistent"]
  22. }
  23. ],
  24. messages: {
  25. unexpectedLineBreak: "There should be no line break here.",
  26. missingLineBreak: "There should be a line break after this argument."
  27. }
  28. },
  29. create(context) {
  30. const sourceCode = context.getSourceCode();
  31. const checkers = {
  32. unexpected: {
  33. messageId: "unexpectedLineBreak",
  34. check: (prevToken, currentToken) => prevToken.loc.end.line !== currentToken.loc.start.line,
  35. createFix: (token, tokenBefore) => fixer =>
  36. fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ")
  37. },
  38. missing: {
  39. messageId: "missingLineBreak",
  40. check: (prevToken, currentToken) => prevToken.loc.end.line === currentToken.loc.start.line,
  41. createFix: (token, tokenBefore) => fixer =>
  42. fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n")
  43. }
  44. };
  45. /**
  46. * Check all arguments for line breaks in the CallExpression
  47. * @param {CallExpression} node node to evaluate
  48. * @param {{ messageId: string, check: Function }} checker selected checker
  49. * @returns {void}
  50. * @private
  51. */
  52. function checkArguments(node, checker) {
  53. for (let i = 1; i < node.arguments.length; i++) {
  54. const prevArgToken = sourceCode.getLastToken(node.arguments[i - 1]);
  55. const currentArgToken = sourceCode.getFirstToken(node.arguments[i]);
  56. if (checker.check(prevArgToken, currentArgToken)) {
  57. const tokenBefore = sourceCode.getTokenBefore(
  58. currentArgToken,
  59. { includeComments: true }
  60. );
  61. const hasLineCommentBefore = tokenBefore.type === "Line";
  62. context.report({
  63. node,
  64. loc: {
  65. start: tokenBefore.loc.end,
  66. end: currentArgToken.loc.start
  67. },
  68. messageId: checker.messageId,
  69. fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore)
  70. });
  71. }
  72. }
  73. }
  74. /**
  75. * Check if open space is present in a function name
  76. * @param {CallExpression} node node to evaluate
  77. * @returns {void}
  78. * @private
  79. */
  80. function check(node) {
  81. if (node.arguments.length < 2) {
  82. return;
  83. }
  84. const option = context.options[0] || "always";
  85. if (option === "never") {
  86. checkArguments(node, checkers.unexpected);
  87. } else if (option === "always") {
  88. checkArguments(node, checkers.missing);
  89. } else if (option === "consistent") {
  90. const firstArgToken = sourceCode.getLastToken(node.arguments[0]);
  91. const secondArgToken = sourceCode.getFirstToken(node.arguments[1]);
  92. if (firstArgToken.loc.end.line === secondArgToken.loc.start.line) {
  93. checkArguments(node, checkers.unexpected);
  94. } else {
  95. checkArguments(node, checkers.missing);
  96. }
  97. }
  98. }
  99. return {
  100. CallExpression: check,
  101. NewExpression: check
  102. };
  103. }
  104. };