Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

prefer-object-spread.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /**
  2. * @fileoverview Prefers object spread property over Object.assign
  3. * @author Sharmila Jesupaul
  4. * See LICENSE file in root directory for full license.
  5. */
  6. "use strict";
  7. const { CALL, ReferenceTracker } = require("eslint-utils");
  8. const {
  9. isCommaToken,
  10. isOpeningParenToken,
  11. isClosingParenToken,
  12. isParenthesised
  13. } = require("../util/ast-utils");
  14. const ANY_SPACE = /\s/;
  15. /**
  16. * Helper that checks if the Object.assign call has array spread
  17. * @param {ASTNode} node - The node that the rule warns on
  18. * @returns {boolean} - Returns true if the Object.assign call has array spread
  19. */
  20. function hasArraySpread(node) {
  21. return node.arguments.some(arg => arg.type === "SpreadElement");
  22. }
  23. /**
  24. * Helper that checks if the node needs parentheses to be valid JS.
  25. * The default is to wrap the node in parentheses to avoid parsing errors.
  26. * @param {ASTNode} node - The node that the rule warns on
  27. * @param {Object} sourceCode - in context sourcecode object
  28. * @returns {boolean} - Returns true if the node needs parentheses
  29. */
  30. function needsParens(node, sourceCode) {
  31. const parent = node.parent;
  32. switch (parent.type) {
  33. case "VariableDeclarator":
  34. case "ArrayExpression":
  35. case "ReturnStatement":
  36. case "CallExpression":
  37. case "Property":
  38. return false;
  39. case "AssignmentExpression":
  40. return parent.left === node && !isParenthesised(sourceCode, node);
  41. default:
  42. return !isParenthesised(sourceCode, node);
  43. }
  44. }
  45. /**
  46. * Determines if an argument needs parentheses. The default is to not add parens.
  47. * @param {ASTNode} node - The node to be checked.
  48. * @param {Object} sourceCode - in context sourcecode object
  49. * @returns {boolean} True if the node needs parentheses
  50. */
  51. function argNeedsParens(node, sourceCode) {
  52. switch (node.type) {
  53. case "AssignmentExpression":
  54. case "ArrowFunctionExpression":
  55. case "ConditionalExpression":
  56. return !isParenthesised(sourceCode, node);
  57. default:
  58. return false;
  59. }
  60. }
  61. /**
  62. * Get the parenthesis tokens of a given ObjectExpression node.
  63. * This incldues the braces of the object literal and enclosing parentheses.
  64. * @param {ASTNode} node The node to get.
  65. * @param {Token} leftArgumentListParen The opening paren token of the argument list.
  66. * @param {SourceCode} sourceCode The source code object to get tokens.
  67. * @returns {Token[]} The parenthesis tokens of the node. This is sorted by the location.
  68. */
  69. function getParenTokens(node, leftArgumentListParen, sourceCode) {
  70. const parens = [sourceCode.getFirstToken(node), sourceCode.getLastToken(node)];
  71. let leftNext = sourceCode.getTokenBefore(node);
  72. let rightNext = sourceCode.getTokenAfter(node);
  73. // Note: don't include the parens of the argument list.
  74. while (
  75. leftNext &&
  76. rightNext &&
  77. leftNext.range[0] > leftArgumentListParen.range[0] &&
  78. isOpeningParenToken(leftNext) &&
  79. isClosingParenToken(rightNext)
  80. ) {
  81. parens.push(leftNext, rightNext);
  82. leftNext = sourceCode.getTokenBefore(leftNext);
  83. rightNext = sourceCode.getTokenAfter(rightNext);
  84. }
  85. return parens.sort((a, b) => a.range[0] - b.range[0]);
  86. }
  87. /**
  88. * Get the range of a given token and around whitespaces.
  89. * @param {Token} token The token to get range.
  90. * @param {SourceCode} sourceCode The source code object to get tokens.
  91. * @returns {number} The end of the range of the token and around whitespaces.
  92. */
  93. function getStartWithSpaces(token, sourceCode) {
  94. const text = sourceCode.text;
  95. let start = token.range[0];
  96. // If the previous token is a line comment then skip this step to avoid commenting this token out.
  97. {
  98. const prevToken = sourceCode.getTokenBefore(token, { includeComments: true });
  99. if (prevToken && prevToken.type === "Line") {
  100. return start;
  101. }
  102. }
  103. // Detect spaces before the token.
  104. while (ANY_SPACE.test(text[start - 1] || "")) {
  105. start -= 1;
  106. }
  107. return start;
  108. }
  109. /**
  110. * Get the range of a given token and around whitespaces.
  111. * @param {Token} token The token to get range.
  112. * @param {SourceCode} sourceCode The source code object to get tokens.
  113. * @returns {number} The start of the range of the token and around whitespaces.
  114. */
  115. function getEndWithSpaces(token, sourceCode) {
  116. const text = sourceCode.text;
  117. let end = token.range[1];
  118. // Detect spaces after the token.
  119. while (ANY_SPACE.test(text[end] || "")) {
  120. end += 1;
  121. }
  122. return end;
  123. }
  124. /**
  125. * Autofixes the Object.assign call to use an object spread instead.
  126. * @param {ASTNode|null} node - The node that the rule warns on, i.e. the Object.assign call
  127. * @param {string} sourceCode - sourceCode of the Object.assign call
  128. * @returns {Function} autofixer - replaces the Object.assign with a spread object.
  129. */
  130. function defineFixer(node, sourceCode) {
  131. return function *(fixer) {
  132. const leftParen = sourceCode.getTokenAfter(node.callee, isOpeningParenToken);
  133. const rightParen = sourceCode.getLastToken(node);
  134. // Remove the callee `Object.assign`
  135. yield fixer.remove(node.callee);
  136. // Replace the parens of argument list to braces.
  137. if (needsParens(node, sourceCode)) {
  138. yield fixer.replaceText(leftParen, "({");
  139. yield fixer.replaceText(rightParen, "})");
  140. } else {
  141. yield fixer.replaceText(leftParen, "{");
  142. yield fixer.replaceText(rightParen, "}");
  143. }
  144. // Process arguments.
  145. for (const argNode of node.arguments) {
  146. const innerParens = getParenTokens(argNode, leftParen, sourceCode);
  147. const left = innerParens.shift();
  148. const right = innerParens.pop();
  149. if (argNode.type === "ObjectExpression") {
  150. const maybeTrailingComma = sourceCode.getLastToken(argNode, 1);
  151. const maybeArgumentComma = sourceCode.getTokenAfter(right);
  152. /*
  153. * Make bare this object literal.
  154. * And remove spaces inside of the braces for better formatting.
  155. */
  156. for (const innerParen of innerParens) {
  157. yield fixer.remove(innerParen);
  158. }
  159. const leftRange = [left.range[0], getEndWithSpaces(left, sourceCode)];
  160. const rightRange = [
  161. Math.max(getStartWithSpaces(right, sourceCode), leftRange[1]), // Ensure ranges don't overlap
  162. right.range[1]
  163. ];
  164. yield fixer.removeRange(leftRange);
  165. yield fixer.removeRange(rightRange);
  166. // Remove the comma of this argument if it's duplication.
  167. if (
  168. (argNode.properties.length === 0 || isCommaToken(maybeTrailingComma)) &&
  169. isCommaToken(maybeArgumentComma)
  170. ) {
  171. yield fixer.remove(maybeArgumentComma);
  172. }
  173. } else {
  174. // Make spread.
  175. if (argNeedsParens(argNode, sourceCode)) {
  176. yield fixer.insertTextBefore(left, "...(");
  177. yield fixer.insertTextAfter(right, ")");
  178. } else {
  179. yield fixer.insertTextBefore(left, "...");
  180. }
  181. }
  182. }
  183. };
  184. }
  185. module.exports = {
  186. meta: {
  187. type: "suggestion",
  188. docs: {
  189. description:
  190. "disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.",
  191. category: "Stylistic Issues",
  192. recommended: false,
  193. url: "https://eslint.org/docs/rules/prefer-object-spread"
  194. },
  195. schema: [],
  196. fixable: "code",
  197. messages: {
  198. useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`",
  199. useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`"
  200. }
  201. },
  202. create(context) {
  203. const sourceCode = context.getSourceCode();
  204. return {
  205. Program() {
  206. const scope = context.getScope();
  207. const tracker = new ReferenceTracker(scope);
  208. const trackMap = {
  209. Object: {
  210. assign: { [CALL]: true }
  211. }
  212. };
  213. // Iterate all calls of `Object.assign` (only of the global variable `Object`).
  214. for (const { node } of tracker.iterateGlobalReferences(trackMap)) {
  215. if (
  216. node.arguments.length >= 1 &&
  217. node.arguments[0].type === "ObjectExpression" &&
  218. !hasArraySpread(node)
  219. ) {
  220. const messageId = node.arguments.length === 1
  221. ? "useLiteralMessage"
  222. : "useSpreadMessage";
  223. const fix = defineFixer(node, sourceCode);
  224. context.report({ node, messageId, fix });
  225. }
  226. }
  227. }
  228. };
  229. }
  230. };