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.

key-spacing.js 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /**
  2. * @fileoverview Rule to specify spacing of object literal keys and values
  3. * @author Brandon Mills
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("../util/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Checks whether a string contains a line terminator as defined in
  15. * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
  16. * @param {string} str String to test.
  17. * @returns {boolean} True if str contains a line terminator.
  18. */
  19. function containsLineTerminator(str) {
  20. return astUtils.LINEBREAK_MATCHER.test(str);
  21. }
  22. /**
  23. * Gets the last element of an array.
  24. * @param {Array} arr An array.
  25. * @returns {any} Last element of arr.
  26. */
  27. function last(arr) {
  28. return arr[arr.length - 1];
  29. }
  30. /**
  31. * Checks whether a node is contained on a single line.
  32. * @param {ASTNode} node AST Node being evaluated.
  33. * @returns {boolean} True if the node is a single line.
  34. */
  35. function isSingleLine(node) {
  36. return (node.loc.end.line === node.loc.start.line);
  37. }
  38. /**
  39. * Initializes a single option property from the configuration with defaults for undefined values
  40. * @param {Object} toOptions Object to be initialized
  41. * @param {Object} fromOptions Object to be initialized from
  42. * @returns {Object} The object with correctly initialized options and values
  43. */
  44. function initOptionProperty(toOptions, fromOptions) {
  45. toOptions.mode = fromOptions.mode || "strict";
  46. // Set value of beforeColon
  47. if (typeof fromOptions.beforeColon !== "undefined") {
  48. toOptions.beforeColon = +fromOptions.beforeColon;
  49. } else {
  50. toOptions.beforeColon = 0;
  51. }
  52. // Set value of afterColon
  53. if (typeof fromOptions.afterColon !== "undefined") {
  54. toOptions.afterColon = +fromOptions.afterColon;
  55. } else {
  56. toOptions.afterColon = 1;
  57. }
  58. // Set align if exists
  59. if (typeof fromOptions.align !== "undefined") {
  60. if (typeof fromOptions.align === "object") {
  61. toOptions.align = fromOptions.align;
  62. } else { // "string"
  63. toOptions.align = {
  64. on: fromOptions.align,
  65. mode: toOptions.mode,
  66. beforeColon: toOptions.beforeColon,
  67. afterColon: toOptions.afterColon
  68. };
  69. }
  70. }
  71. return toOptions;
  72. }
  73. /**
  74. * Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values
  75. * @param {Object} toOptions Object to be initialized
  76. * @param {Object} fromOptions Object to be initialized from
  77. * @returns {Object} The object with correctly initialized options and values
  78. */
  79. function initOptions(toOptions, fromOptions) {
  80. if (typeof fromOptions.align === "object") {
  81. // Initialize the alignment configuration
  82. toOptions.align = initOptionProperty({}, fromOptions.align);
  83. toOptions.align.on = fromOptions.align.on || "colon";
  84. toOptions.align.mode = fromOptions.align.mode || "strict";
  85. toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));
  86. toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));
  87. } else { // string or undefined
  88. toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));
  89. toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));
  90. // If alignment options are defined in multiLine, pull them out into the general align configuration
  91. if (toOptions.multiLine.align) {
  92. toOptions.align = {
  93. on: toOptions.multiLine.align.on,
  94. mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode,
  95. beforeColon: toOptions.multiLine.align.beforeColon,
  96. afterColon: toOptions.multiLine.align.afterColon
  97. };
  98. }
  99. }
  100. return toOptions;
  101. }
  102. //------------------------------------------------------------------------------
  103. // Rule Definition
  104. //------------------------------------------------------------------------------
  105. const messages = {
  106. key: "{{error}} space after {{computed}}key '{{key}}'.",
  107. value: "{{error}} space before value for {{computed}}key '{{key}}'."
  108. };
  109. module.exports = {
  110. meta: {
  111. type: "layout",
  112. docs: {
  113. description: "enforce consistent spacing between keys and values in object literal properties",
  114. category: "Stylistic Issues",
  115. recommended: false,
  116. url: "https://eslint.org/docs/rules/key-spacing"
  117. },
  118. fixable: "whitespace",
  119. schema: [{
  120. anyOf: [
  121. {
  122. type: "object",
  123. properties: {
  124. align: {
  125. anyOf: [
  126. {
  127. enum: ["colon", "value"]
  128. },
  129. {
  130. type: "object",
  131. properties: {
  132. mode: {
  133. enum: ["strict", "minimum"]
  134. },
  135. on: {
  136. enum: ["colon", "value"]
  137. },
  138. beforeColon: {
  139. type: "boolean"
  140. },
  141. afterColon: {
  142. type: "boolean"
  143. }
  144. },
  145. additionalProperties: false
  146. }
  147. ]
  148. },
  149. mode: {
  150. enum: ["strict", "minimum"]
  151. },
  152. beforeColon: {
  153. type: "boolean"
  154. },
  155. afterColon: {
  156. type: "boolean"
  157. }
  158. },
  159. additionalProperties: false
  160. },
  161. {
  162. type: "object",
  163. properties: {
  164. singleLine: {
  165. type: "object",
  166. properties: {
  167. mode: {
  168. enum: ["strict", "minimum"]
  169. },
  170. beforeColon: {
  171. type: "boolean"
  172. },
  173. afterColon: {
  174. type: "boolean"
  175. }
  176. },
  177. additionalProperties: false
  178. },
  179. multiLine: {
  180. type: "object",
  181. properties: {
  182. align: {
  183. anyOf: [
  184. {
  185. enum: ["colon", "value"]
  186. },
  187. {
  188. type: "object",
  189. properties: {
  190. mode: {
  191. enum: ["strict", "minimum"]
  192. },
  193. on: {
  194. enum: ["colon", "value"]
  195. },
  196. beforeColon: {
  197. type: "boolean"
  198. },
  199. afterColon: {
  200. type: "boolean"
  201. }
  202. },
  203. additionalProperties: false
  204. }
  205. ]
  206. },
  207. mode: {
  208. enum: ["strict", "minimum"]
  209. },
  210. beforeColon: {
  211. type: "boolean"
  212. },
  213. afterColon: {
  214. type: "boolean"
  215. }
  216. },
  217. additionalProperties: false
  218. }
  219. },
  220. additionalProperties: false
  221. },
  222. {
  223. type: "object",
  224. properties: {
  225. singleLine: {
  226. type: "object",
  227. properties: {
  228. mode: {
  229. enum: ["strict", "minimum"]
  230. },
  231. beforeColon: {
  232. type: "boolean"
  233. },
  234. afterColon: {
  235. type: "boolean"
  236. }
  237. },
  238. additionalProperties: false
  239. },
  240. multiLine: {
  241. type: "object",
  242. properties: {
  243. mode: {
  244. enum: ["strict", "minimum"]
  245. },
  246. beforeColon: {
  247. type: "boolean"
  248. },
  249. afterColon: {
  250. type: "boolean"
  251. }
  252. },
  253. additionalProperties: false
  254. },
  255. align: {
  256. type: "object",
  257. properties: {
  258. mode: {
  259. enum: ["strict", "minimum"]
  260. },
  261. on: {
  262. enum: ["colon", "value"]
  263. },
  264. beforeColon: {
  265. type: "boolean"
  266. },
  267. afterColon: {
  268. type: "boolean"
  269. }
  270. },
  271. additionalProperties: false
  272. }
  273. },
  274. additionalProperties: false
  275. }
  276. ]
  277. }]
  278. },
  279. create(context) {
  280. /**
  281. * OPTIONS
  282. * "key-spacing": [2, {
  283. * beforeColon: false,
  284. * afterColon: true,
  285. * align: "colon" // Optional, or "value"
  286. * }
  287. */
  288. const options = context.options[0] || {},
  289. ruleOptions = initOptions({}, options),
  290. multiLineOptions = ruleOptions.multiLine,
  291. singleLineOptions = ruleOptions.singleLine,
  292. alignmentOptions = ruleOptions.align || null;
  293. const sourceCode = context.getSourceCode();
  294. /**
  295. * Checks whether a property is a member of the property group it follows.
  296. * @param {ASTNode} lastMember The last Property known to be in the group.
  297. * @param {ASTNode} candidate The next Property that might be in the group.
  298. * @returns {boolean} True if the candidate property is part of the group.
  299. */
  300. function continuesPropertyGroup(lastMember, candidate) {
  301. const groupEndLine = lastMember.loc.start.line,
  302. candidateStartLine = candidate.loc.start.line;
  303. if (candidateStartLine - groupEndLine <= 1) {
  304. return true;
  305. }
  306. /*
  307. * Check that the first comment is adjacent to the end of the group, the
  308. * last comment is adjacent to the candidate property, and that successive
  309. * comments are adjacent to each other.
  310. */
  311. const leadingComments = sourceCode.getCommentsBefore(candidate);
  312. if (
  313. leadingComments.length &&
  314. leadingComments[0].loc.start.line - groupEndLine <= 1 &&
  315. candidateStartLine - last(leadingComments).loc.end.line <= 1
  316. ) {
  317. for (let i = 1; i < leadingComments.length; i++) {
  318. if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
  319. return false;
  320. }
  321. }
  322. return true;
  323. }
  324. return false;
  325. }
  326. /**
  327. * Determines if the given property is key-value property.
  328. * @param {ASTNode} property Property node to check.
  329. * @returns {boolean} Whether the property is a key-value property.
  330. */
  331. function isKeyValueProperty(property) {
  332. return !(
  333. (property.method ||
  334. property.shorthand ||
  335. property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement"
  336. );
  337. }
  338. /**
  339. * Starting from the given a node (a property.key node here) looks forward
  340. * until it finds the last token before a colon punctuator and returns it.
  341. * @param {ASTNode} node The node to start looking from.
  342. * @returns {ASTNode} The last token before a colon punctuator.
  343. */
  344. function getLastTokenBeforeColon(node) {
  345. const colonToken = sourceCode.getTokenAfter(node, astUtils.isColonToken);
  346. return sourceCode.getTokenBefore(colonToken);
  347. }
  348. /**
  349. * Starting from the given a node (a property.key node here) looks forward
  350. * until it finds the colon punctuator and returns it.
  351. * @param {ASTNode} node The node to start looking from.
  352. * @returns {ASTNode} The colon punctuator.
  353. */
  354. function getNextColon(node) {
  355. return sourceCode.getTokenAfter(node, astUtils.isColonToken);
  356. }
  357. /**
  358. * Gets an object literal property's key as the identifier name or string value.
  359. * @param {ASTNode} property Property node whose key to retrieve.
  360. * @returns {string} The property's key.
  361. */
  362. function getKey(property) {
  363. const key = property.key;
  364. if (property.computed) {
  365. return sourceCode.getText().slice(key.range[0], key.range[1]);
  366. }
  367. return property.key.name || property.key.value;
  368. }
  369. /**
  370. * Reports an appropriately-formatted error if spacing is incorrect on one
  371. * side of the colon.
  372. * @param {ASTNode} property Key-value pair in an object literal.
  373. * @param {string} side Side being verified - either "key" or "value".
  374. * @param {string} whitespace Actual whitespace string.
  375. * @param {int} expected Expected whitespace length.
  376. * @param {string} mode Value of the mode as "strict" or "minimum"
  377. * @returns {void}
  378. */
  379. function report(property, side, whitespace, expected, mode) {
  380. const diff = whitespace.length - expected,
  381. nextColon = getNextColon(property.key),
  382. tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
  383. tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
  384. isKeySide = side === "key",
  385. locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
  386. isExtra = diff > 0,
  387. diffAbs = Math.abs(diff),
  388. spaces = Array(diffAbs + 1).join(" ");
  389. if ((
  390. diff && mode === "strict" ||
  391. diff < 0 && mode === "minimum" ||
  392. diff > 0 && !expected && mode === "minimum") &&
  393. !(expected && containsLineTerminator(whitespace))
  394. ) {
  395. let fix;
  396. if (isExtra) {
  397. let range;
  398. // Remove whitespace
  399. if (isKeySide) {
  400. range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs];
  401. } else {
  402. range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]];
  403. }
  404. fix = function(fixer) {
  405. return fixer.removeRange(range);
  406. };
  407. } else {
  408. // Add whitespace
  409. if (isKeySide) {
  410. fix = function(fixer) {
  411. return fixer.insertTextAfter(tokenBeforeColon, spaces);
  412. };
  413. } else {
  414. fix = function(fixer) {
  415. return fixer.insertTextBefore(tokenAfterColon, spaces);
  416. };
  417. }
  418. }
  419. context.report({
  420. node: property[side],
  421. loc: locStart,
  422. message: messages[side],
  423. data: {
  424. error: isExtra ? "Extra" : "Missing",
  425. computed: property.computed ? "computed " : "",
  426. key: getKey(property)
  427. },
  428. fix
  429. });
  430. }
  431. }
  432. /**
  433. * Gets the number of characters in a key, including quotes around string
  434. * keys and braces around computed property keys.
  435. * @param {ASTNode} property Property of on object literal.
  436. * @returns {int} Width of the key.
  437. */
  438. function getKeyWidth(property) {
  439. const startToken = sourceCode.getFirstToken(property);
  440. const endToken = getLastTokenBeforeColon(property.key);
  441. return endToken.range[1] - startToken.range[0];
  442. }
  443. /**
  444. * Gets the whitespace around the colon in an object literal property.
  445. * @param {ASTNode} property Property node from an object literal.
  446. * @returns {Object} Whitespace before and after the property's colon.
  447. */
  448. function getPropertyWhitespace(property) {
  449. const whitespace = /(\s*):(\s*)/.exec(sourceCode.getText().slice(
  450. property.key.range[1], property.value.range[0]
  451. ));
  452. if (whitespace) {
  453. return {
  454. beforeColon: whitespace[1],
  455. afterColon: whitespace[2]
  456. };
  457. }
  458. return null;
  459. }
  460. /**
  461. * Creates groups of properties.
  462. * @param {ASTNode} node ObjectExpression node being evaluated.
  463. * @returns {Array.<ASTNode[]>} Groups of property AST node lists.
  464. */
  465. function createGroups(node) {
  466. if (node.properties.length === 1) {
  467. return [node.properties];
  468. }
  469. return node.properties.reduce((groups, property) => {
  470. const currentGroup = last(groups),
  471. prev = last(currentGroup);
  472. if (!prev || continuesPropertyGroup(prev, property)) {
  473. currentGroup.push(property);
  474. } else {
  475. groups.push([property]);
  476. }
  477. return groups;
  478. }, [
  479. []
  480. ]);
  481. }
  482. /**
  483. * Verifies correct vertical alignment of a group of properties.
  484. * @param {ASTNode[]} properties List of Property AST nodes.
  485. * @returns {void}
  486. */
  487. function verifyGroupAlignment(properties) {
  488. const length = properties.length,
  489. widths = properties.map(getKeyWidth), // Width of keys, including quotes
  490. align = alignmentOptions.on; // "value" or "colon"
  491. let targetWidth = Math.max(...widths),
  492. beforeColon, afterColon, mode;
  493. if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration.
  494. beforeColon = alignmentOptions.beforeColon;
  495. afterColon = alignmentOptions.afterColon;
  496. mode = alignmentOptions.mode;
  497. } else {
  498. beforeColon = multiLineOptions.beforeColon;
  499. afterColon = multiLineOptions.afterColon;
  500. mode = alignmentOptions.mode;
  501. }
  502. // Conditionally include one space before or after colon
  503. targetWidth += (align === "colon" ? beforeColon : afterColon);
  504. for (let i = 0; i < length; i++) {
  505. const property = properties[i];
  506. const whitespace = getPropertyWhitespace(property);
  507. if (whitespace) { // Object literal getters/setters lack a colon
  508. const width = widths[i];
  509. if (align === "value") {
  510. report(property, "key", whitespace.beforeColon, beforeColon, mode);
  511. report(property, "value", whitespace.afterColon, targetWidth - width, mode);
  512. } else { // align = "colon"
  513. report(property, "key", whitespace.beforeColon, targetWidth - width, mode);
  514. report(property, "value", whitespace.afterColon, afterColon, mode);
  515. }
  516. }
  517. }
  518. }
  519. /**
  520. * Verifies vertical alignment, taking into account groups of properties.
  521. * @param {ASTNode} node ObjectExpression node being evaluated.
  522. * @returns {void}
  523. */
  524. function verifyAlignment(node) {
  525. createGroups(node).forEach(group => {
  526. verifyGroupAlignment(group.filter(isKeyValueProperty));
  527. });
  528. }
  529. /**
  530. * Verifies spacing of property conforms to specified options.
  531. * @param {ASTNode} node Property node being evaluated.
  532. * @param {Object} lineOptions Configured singleLine or multiLine options
  533. * @returns {void}
  534. */
  535. function verifySpacing(node, lineOptions) {
  536. const actual = getPropertyWhitespace(node);
  537. if (actual) { // Object literal getters/setters lack colons
  538. report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);
  539. report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);
  540. }
  541. }
  542. /**
  543. * Verifies spacing of each property in a list.
  544. * @param {ASTNode[]} properties List of Property AST nodes.
  545. * @returns {void}
  546. */
  547. function verifyListSpacing(properties) {
  548. const length = properties.length;
  549. for (let i = 0; i < length; i++) {
  550. verifySpacing(properties[i], singleLineOptions);
  551. }
  552. }
  553. //--------------------------------------------------------------------------
  554. // Public API
  555. //--------------------------------------------------------------------------
  556. if (alignmentOptions) { // Verify vertical alignment
  557. return {
  558. ObjectExpression(node) {
  559. if (isSingleLine(node)) {
  560. verifyListSpacing(node.properties.filter(isKeyValueProperty));
  561. } else {
  562. verifyAlignment(node);
  563. }
  564. }
  565. };
  566. }
  567. // Obey beforeColon and afterColon in each property as configured
  568. return {
  569. Property(node) {
  570. verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);
  571. }
  572. };
  573. }
  574. };