no-useless-concat.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /**
  2. * @fileoverview disallow unnecessary concatenation of template strings
  3. * @author Henry Zhu
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Checks whether or not a given node is a concatenation.
  15. * @param {ASTNode} node A node to check.
  16. * @returns {boolean} `true` if the node is a concatenation.
  17. */
  18. function isConcatenation(node) {
  19. return node.type === "BinaryExpression" && node.operator === "+";
  20. }
  21. /**
  22. * Checks if the given token is a `+` token or not.
  23. * @param {Token} token The token to check.
  24. * @returns {boolean} `true` if the token is a `+` token.
  25. */
  26. function isConcatOperatorToken(token) {
  27. return token.value === "+" && token.type === "Punctuator";
  28. }
  29. /**
  30. * Get's the right most node on the left side of a BinaryExpression with + operator.
  31. * @param {ASTNode} node A BinaryExpression node to check.
  32. * @returns {ASTNode} node
  33. */
  34. function getLeft(node) {
  35. let left = node.left;
  36. while (isConcatenation(left)) {
  37. left = left.right;
  38. }
  39. return left;
  40. }
  41. /**
  42. * Get's the left most node on the right side of a BinaryExpression with + operator.
  43. * @param {ASTNode} node A BinaryExpression node to check.
  44. * @returns {ASTNode} node
  45. */
  46. function getRight(node) {
  47. let right = node.right;
  48. while (isConcatenation(right)) {
  49. right = right.left;
  50. }
  51. return right;
  52. }
  53. //------------------------------------------------------------------------------
  54. // Rule Definition
  55. //------------------------------------------------------------------------------
  56. /** @type {import('../types').Rule.RuleModule} */
  57. module.exports = {
  58. meta: {
  59. type: "suggestion",
  60. docs: {
  61. description:
  62. "Disallow unnecessary concatenation of literals or template literals",
  63. recommended: false,
  64. frozen: true,
  65. url: "https://eslint.org/docs/latest/rules/no-useless-concat",
  66. },
  67. schema: [],
  68. messages: {
  69. unexpectedConcat: "Unexpected string concatenation of literals.",
  70. },
  71. },
  72. create(context) {
  73. const sourceCode = context.sourceCode;
  74. return {
  75. BinaryExpression(node) {
  76. // check if not concatenation
  77. if (node.operator !== "+") {
  78. return;
  79. }
  80. // account for the `foo + "a" + "b"` case
  81. const left = getLeft(node);
  82. const right = getRight(node);
  83. if (
  84. astUtils.isStringLiteral(left) &&
  85. astUtils.isStringLiteral(right) &&
  86. astUtils.isTokenOnSameLine(left, right)
  87. ) {
  88. const operatorToken = sourceCode.getFirstTokenBetween(
  89. left,
  90. right,
  91. isConcatOperatorToken,
  92. );
  93. context.report({
  94. node,
  95. loc: operatorToken.loc,
  96. messageId: "unexpectedConcat",
  97. });
  98. }
  99. },
  100. };
  101. },
  102. };