no-useless-concat.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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('../shared/types').Rule} */
  57. module.exports = {
  58. meta: {
  59. type: "suggestion",
  60. docs: {
  61. description: "Disallow unnecessary concatenation of literals or template literals",
  62. recommended: false,
  63. url: "https://eslint.org/docs/rules/no-useless-concat"
  64. },
  65. schema: [],
  66. messages: {
  67. unexpectedConcat: "Unexpected string concatenation of literals."
  68. }
  69. },
  70. create(context) {
  71. const sourceCode = context.getSourceCode();
  72. return {
  73. BinaryExpression(node) {
  74. // check if not concatenation
  75. if (node.operator !== "+") {
  76. return;
  77. }
  78. // account for the `foo + "a" + "b"` case
  79. const left = getLeft(node);
  80. const right = getRight(node);
  81. if (astUtils.isStringLiteral(left) &&
  82. astUtils.isStringLiteral(right) &&
  83. astUtils.isTokenOnSameLine(left, right)
  84. ) {
  85. const operatorToken = sourceCode.getFirstTokenBetween(left, right, isConcatOperatorToken);
  86. context.report({
  87. node,
  88. loc: operatorToken.loc,
  89. messageId: "unexpectedConcat"
  90. });
  91. }
  92. }
  93. };
  94. }
  95. };