key-spacing.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. /**
  2. * @fileoverview Rule to specify spacing of object literal keys and values
  3. * @author Brandon Mills
  4. * @deprecated in ESLint v8.53.0
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const astUtils = require("./utils/ast-utils");
  11. const { getGraphemeCount } = require("../shared/string-utils");
  12. /**
  13. * Checks whether a string contains a line terminator as defined in
  14. * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
  15. * @param {string} str String to test.
  16. * @returns {boolean} True if str contains a line terminator.
  17. */
  18. function containsLineTerminator(str) {
  19. return astUtils.LINEBREAK_MATCHER.test(str);
  20. }
  21. /**
  22. * Gets the last element of an array.
  23. * @param {Array} arr An array.
  24. * @returns {any} Last element of arr.
  25. */
  26. function last(arr) {
  27. return arr.at(-1);
  28. }
  29. /**
  30. * Checks whether a node is contained on a single line.
  31. * @param {ASTNode} node AST Node being evaluated.
  32. * @returns {boolean} True if the node is a single line.
  33. */
  34. function isSingleLine(node) {
  35. return node.loc.end.line === node.loc.start.line;
  36. }
  37. /**
  38. * Checks whether the properties on a single line.
  39. * @param {ASTNode[]} properties List of Property AST nodes.
  40. * @returns {boolean} True if all properties is on a single line.
  41. */
  42. function isSingleLineProperties(properties) {
  43. const [firstProp] = properties,
  44. lastProp = last(properties);
  45. return firstProp.loc.start.line === lastProp.loc.end.line;
  46. }
  47. /**
  48. * Initializes a single option property from the configuration with defaults for undefined values
  49. * @param {Object} toOptions Object to be initialized
  50. * @param {Object} fromOptions Object to be initialized from
  51. * @returns {Object} The object with correctly initialized options and values
  52. */
  53. function initOptionProperty(toOptions, fromOptions) {
  54. toOptions.mode = fromOptions.mode || "strict";
  55. // Set value of beforeColon
  56. if (typeof fromOptions.beforeColon !== "undefined") {
  57. toOptions.beforeColon = +fromOptions.beforeColon;
  58. } else {
  59. toOptions.beforeColon = 0;
  60. }
  61. // Set value of afterColon
  62. if (typeof fromOptions.afterColon !== "undefined") {
  63. toOptions.afterColon = +fromOptions.afterColon;
  64. } else {
  65. toOptions.afterColon = 1;
  66. }
  67. // Set align if exists
  68. if (typeof fromOptions.align !== "undefined") {
  69. if (typeof fromOptions.align === "object") {
  70. toOptions.align = fromOptions.align;
  71. } else {
  72. // "string"
  73. toOptions.align = {
  74. on: fromOptions.align,
  75. mode: toOptions.mode,
  76. beforeColon: toOptions.beforeColon,
  77. afterColon: toOptions.afterColon,
  78. };
  79. }
  80. }
  81. return toOptions;
  82. }
  83. /**
  84. * Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values
  85. * @param {Object} toOptions Object to be initialized
  86. * @param {Object} fromOptions Object to be initialized from
  87. * @returns {Object} The object with correctly initialized options and values
  88. */
  89. function initOptions(toOptions, fromOptions) {
  90. if (typeof fromOptions.align === "object") {
  91. // Initialize the alignment configuration
  92. toOptions.align = initOptionProperty({}, fromOptions.align);
  93. toOptions.align.on = fromOptions.align.on || "colon";
  94. toOptions.align.mode = fromOptions.align.mode || "strict";
  95. toOptions.multiLine = initOptionProperty(
  96. {},
  97. fromOptions.multiLine || fromOptions,
  98. );
  99. toOptions.singleLine = initOptionProperty(
  100. {},
  101. fromOptions.singleLine || fromOptions,
  102. );
  103. } else {
  104. // string or undefined
  105. toOptions.multiLine = initOptionProperty(
  106. {},
  107. fromOptions.multiLine || fromOptions,
  108. );
  109. toOptions.singleLine = initOptionProperty(
  110. {},
  111. fromOptions.singleLine || fromOptions,
  112. );
  113. // If alignment options are defined in multiLine, pull them out into the general align configuration
  114. if (toOptions.multiLine.align) {
  115. toOptions.align = {
  116. on: toOptions.multiLine.align.on,
  117. mode:
  118. toOptions.multiLine.align.mode || toOptions.multiLine.mode,
  119. beforeColon: toOptions.multiLine.align.beforeColon,
  120. afterColon: toOptions.multiLine.align.afterColon,
  121. };
  122. }
  123. }
  124. return toOptions;
  125. }
  126. //------------------------------------------------------------------------------
  127. // Rule Definition
  128. //------------------------------------------------------------------------------
  129. /** @type {import('../types').Rule.RuleModule} */
  130. module.exports = {
  131. meta: {
  132. deprecated: {
  133. message: "Formatting rules are being moved out of ESLint core.",
  134. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  135. deprecatedSince: "8.53.0",
  136. availableUntil: "11.0.0",
  137. replacedBy: [
  138. {
  139. message:
  140. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  141. url: "https://eslint.style/guide/migration",
  142. plugin: {
  143. name: "@stylistic/eslint-plugin",
  144. url: "https://eslint.style",
  145. },
  146. rule: {
  147. name: "key-spacing",
  148. url: "https://eslint.style/rules/key-spacing",
  149. },
  150. },
  151. ],
  152. },
  153. type: "layout",
  154. docs: {
  155. description:
  156. "Enforce consistent spacing between keys and values in object literal properties",
  157. recommended: false,
  158. url: "https://eslint.org/docs/latest/rules/key-spacing",
  159. },
  160. fixable: "whitespace",
  161. schema: [
  162. {
  163. anyOf: [
  164. {
  165. type: "object",
  166. properties: {
  167. align: {
  168. anyOf: [
  169. {
  170. enum: ["colon", "value"],
  171. },
  172. {
  173. type: "object",
  174. properties: {
  175. mode: {
  176. enum: ["strict", "minimum"],
  177. },
  178. on: {
  179. enum: ["colon", "value"],
  180. },
  181. beforeColon: {
  182. type: "boolean",
  183. },
  184. afterColon: {
  185. type: "boolean",
  186. },
  187. },
  188. additionalProperties: false,
  189. },
  190. ],
  191. },
  192. mode: {
  193. enum: ["strict", "minimum"],
  194. },
  195. beforeColon: {
  196. type: "boolean",
  197. },
  198. afterColon: {
  199. type: "boolean",
  200. },
  201. },
  202. additionalProperties: false,
  203. },
  204. {
  205. type: "object",
  206. properties: {
  207. singleLine: {
  208. type: "object",
  209. properties: {
  210. mode: {
  211. enum: ["strict", "minimum"],
  212. },
  213. beforeColon: {
  214. type: "boolean",
  215. },
  216. afterColon: {
  217. type: "boolean",
  218. },
  219. },
  220. additionalProperties: false,
  221. },
  222. multiLine: {
  223. type: "object",
  224. properties: {
  225. align: {
  226. anyOf: [
  227. {
  228. enum: ["colon", "value"],
  229. },
  230. {
  231. type: "object",
  232. properties: {
  233. mode: {
  234. enum: [
  235. "strict",
  236. "minimum",
  237. ],
  238. },
  239. on: {
  240. enum: [
  241. "colon",
  242. "value",
  243. ],
  244. },
  245. beforeColon: {
  246. type: "boolean",
  247. },
  248. afterColon: {
  249. type: "boolean",
  250. },
  251. },
  252. additionalProperties: false,
  253. },
  254. ],
  255. },
  256. mode: {
  257. enum: ["strict", "minimum"],
  258. },
  259. beforeColon: {
  260. type: "boolean",
  261. },
  262. afterColon: {
  263. type: "boolean",
  264. },
  265. },
  266. additionalProperties: false,
  267. },
  268. },
  269. additionalProperties: false,
  270. },
  271. {
  272. type: "object",
  273. properties: {
  274. singleLine: {
  275. type: "object",
  276. properties: {
  277. mode: {
  278. enum: ["strict", "minimum"],
  279. },
  280. beforeColon: {
  281. type: "boolean",
  282. },
  283. afterColon: {
  284. type: "boolean",
  285. },
  286. },
  287. additionalProperties: false,
  288. },
  289. multiLine: {
  290. type: "object",
  291. properties: {
  292. mode: {
  293. enum: ["strict", "minimum"],
  294. },
  295. beforeColon: {
  296. type: "boolean",
  297. },
  298. afterColon: {
  299. type: "boolean",
  300. },
  301. },
  302. additionalProperties: false,
  303. },
  304. align: {
  305. type: "object",
  306. properties: {
  307. mode: {
  308. enum: ["strict", "minimum"],
  309. },
  310. on: {
  311. enum: ["colon", "value"],
  312. },
  313. beforeColon: {
  314. type: "boolean",
  315. },
  316. afterColon: {
  317. type: "boolean",
  318. },
  319. },
  320. additionalProperties: false,
  321. },
  322. },
  323. additionalProperties: false,
  324. },
  325. ],
  326. },
  327. ],
  328. messages: {
  329. extraKey: "Extra space after {{computed}}key '{{key}}'.",
  330. extraValue:
  331. "Extra space before value for {{computed}}key '{{key}}'.",
  332. missingKey: "Missing space after {{computed}}key '{{key}}'.",
  333. missingValue:
  334. "Missing space before value for {{computed}}key '{{key}}'.",
  335. },
  336. },
  337. create(context) {
  338. /**
  339. * OPTIONS
  340. * "key-spacing": [2, {
  341. * beforeColon: false,
  342. * afterColon: true,
  343. * align: "colon" // Optional, or "value"
  344. * }
  345. */
  346. const options = context.options[0] || {},
  347. ruleOptions = initOptions({}, options),
  348. multiLineOptions = ruleOptions.multiLine,
  349. singleLineOptions = ruleOptions.singleLine,
  350. alignmentOptions = ruleOptions.align || null;
  351. const sourceCode = context.sourceCode;
  352. /**
  353. * Determines if the given property is key-value property.
  354. * @param {ASTNode} property Property node to check.
  355. * @returns {boolean} Whether the property is a key-value property.
  356. */
  357. function isKeyValueProperty(property) {
  358. return !(
  359. (
  360. property.method ||
  361. property.shorthand ||
  362. property.kind !== "init" ||
  363. property.type !== "Property"
  364. ) // Could be "ExperimentalSpreadProperty" or "SpreadElement"
  365. );
  366. }
  367. /**
  368. * Starting from the given node (a property.key node here) looks forward
  369. * until it finds the colon punctuator and returns it.
  370. * @param {ASTNode} node The node to start looking from.
  371. * @returns {ASTNode} The colon punctuator.
  372. */
  373. function getNextColon(node) {
  374. return sourceCode.getTokenAfter(node, astUtils.isColonToken);
  375. }
  376. /**
  377. * Starting from the given node (a property.key node here) looks forward
  378. * until it finds the last token before a colon punctuator and returns it.
  379. * @param {ASTNode} node The node to start looking from.
  380. * @returns {ASTNode} The last token before a colon punctuator.
  381. */
  382. function getLastTokenBeforeColon(node) {
  383. const colonToken = getNextColon(node);
  384. return sourceCode.getTokenBefore(colonToken);
  385. }
  386. /**
  387. * Starting from the given node (a property.key node here) looks forward
  388. * until it finds the first token after a colon punctuator and returns it.
  389. * @param {ASTNode} node The node to start looking from.
  390. * @returns {ASTNode} The first token after a colon punctuator.
  391. */
  392. function getFirstTokenAfterColon(node) {
  393. const colonToken = getNextColon(node);
  394. return sourceCode.getTokenAfter(colonToken);
  395. }
  396. /**
  397. * Checks whether a property is a member of the property group it follows.
  398. * @param {ASTNode} lastMember The last Property known to be in the group.
  399. * @param {ASTNode} candidate The next Property that might be in the group.
  400. * @returns {boolean} True if the candidate property is part of the group.
  401. */
  402. function continuesPropertyGroup(lastMember, candidate) {
  403. const groupEndLine = lastMember.loc.start.line,
  404. candidateValueStartLine = (
  405. isKeyValueProperty(candidate)
  406. ? getFirstTokenAfterColon(candidate.key)
  407. : candidate
  408. ).loc.start.line;
  409. if (candidateValueStartLine - groupEndLine <= 1) {
  410. return true;
  411. }
  412. /*
  413. * Check that the first comment is adjacent to the end of the group, the
  414. * last comment is adjacent to the candidate property, and that successive
  415. * comments are adjacent to each other.
  416. */
  417. const leadingComments = sourceCode.getCommentsBefore(candidate);
  418. if (
  419. leadingComments.length &&
  420. leadingComments[0].loc.start.line - groupEndLine <= 1 &&
  421. candidateValueStartLine - last(leadingComments).loc.end.line <=
  422. 1
  423. ) {
  424. for (let i = 1; i < leadingComments.length; i++) {
  425. if (
  426. leadingComments[i].loc.start.line -
  427. leadingComments[i - 1].loc.end.line >
  428. 1
  429. ) {
  430. return false;
  431. }
  432. }
  433. return true;
  434. }
  435. return false;
  436. }
  437. /**
  438. * Gets an object literal property's key as the identifier name or string value.
  439. * @param {ASTNode} property Property node whose key to retrieve.
  440. * @returns {string} The property's key.
  441. */
  442. function getKey(property) {
  443. const key = property.key;
  444. if (property.computed) {
  445. return sourceCode.getText().slice(key.range[0], key.range[1]);
  446. }
  447. return astUtils.getStaticPropertyName(property);
  448. }
  449. /**
  450. * Reports an appropriately-formatted error if spacing is incorrect on one
  451. * side of the colon.
  452. * @param {ASTNode} property Key-value pair in an object literal.
  453. * @param {string} side Side being verified - either "key" or "value".
  454. * @param {string} whitespace Actual whitespace string.
  455. * @param {number} expected Expected whitespace length.
  456. * @param {string} mode Value of the mode as "strict" or "minimum"
  457. * @returns {void}
  458. */
  459. function report(property, side, whitespace, expected, mode) {
  460. const diff = whitespace.length - expected;
  461. if (
  462. ((diff && mode === "strict") ||
  463. (diff < 0 && mode === "minimum") ||
  464. (diff > 0 && !expected && mode === "minimum")) &&
  465. !(expected && containsLineTerminator(whitespace))
  466. ) {
  467. const nextColon = getNextColon(property.key),
  468. tokenBeforeColon = sourceCode.getTokenBefore(nextColon, {
  469. includeComments: true,
  470. }),
  471. tokenAfterColon = sourceCode.getTokenAfter(nextColon, {
  472. includeComments: true,
  473. }),
  474. isKeySide = side === "key",
  475. isExtra = diff > 0,
  476. diffAbs = Math.abs(diff),
  477. spaces = Array(diffAbs + 1).join(" ");
  478. const locStart = isKeySide
  479. ? tokenBeforeColon.loc.end
  480. : nextColon.loc.start;
  481. const locEnd = isKeySide
  482. ? nextColon.loc.start
  483. : tokenAfterColon.loc.start;
  484. const missingLoc = isKeySide
  485. ? tokenBeforeColon.loc
  486. : tokenAfterColon.loc;
  487. const loc = isExtra
  488. ? { start: locStart, end: locEnd }
  489. : missingLoc;
  490. let fix;
  491. if (isExtra) {
  492. let range;
  493. // Remove whitespace
  494. if (isKeySide) {
  495. range = [
  496. tokenBeforeColon.range[1],
  497. tokenBeforeColon.range[1] + diffAbs,
  498. ];
  499. } else {
  500. range = [
  501. tokenAfterColon.range[0] - diffAbs,
  502. tokenAfterColon.range[0],
  503. ];
  504. }
  505. fix = function (fixer) {
  506. return fixer.removeRange(range);
  507. };
  508. } else {
  509. // Add whitespace
  510. if (isKeySide) {
  511. fix = function (fixer) {
  512. return fixer.insertTextAfter(
  513. tokenBeforeColon,
  514. spaces,
  515. );
  516. };
  517. } else {
  518. fix = function (fixer) {
  519. return fixer.insertTextBefore(
  520. tokenAfterColon,
  521. spaces,
  522. );
  523. };
  524. }
  525. }
  526. let messageId;
  527. if (isExtra) {
  528. messageId = side === "key" ? "extraKey" : "extraValue";
  529. } else {
  530. messageId = side === "key" ? "missingKey" : "missingValue";
  531. }
  532. context.report({
  533. node: property[side],
  534. loc,
  535. messageId,
  536. data: {
  537. computed: property.computed ? "computed " : "",
  538. key: getKey(property),
  539. },
  540. fix,
  541. });
  542. }
  543. }
  544. /**
  545. * Gets the number of characters in a key, including quotes around string
  546. * keys and braces around computed property keys.
  547. * @param {ASTNode} property Property of on object literal.
  548. * @returns {number} Width of the key.
  549. */
  550. function getKeyWidth(property) {
  551. const startToken = sourceCode.getFirstToken(property);
  552. const endToken = getLastTokenBeforeColon(property.key);
  553. return getGraphemeCount(
  554. sourceCode
  555. .getText()
  556. .slice(startToken.range[0], endToken.range[1]),
  557. );
  558. }
  559. /**
  560. * Gets the whitespace around the colon in an object literal property.
  561. * @param {ASTNode} property Property node from an object literal.
  562. * @returns {Object} Whitespace before and after the property's colon.
  563. */
  564. function getPropertyWhitespace(property) {
  565. const whitespace = /(\s*):(\s*)/u.exec(
  566. sourceCode
  567. .getText()
  568. .slice(property.key.range[1], property.value.range[0]),
  569. );
  570. if (whitespace) {
  571. return {
  572. beforeColon: whitespace[1],
  573. afterColon: whitespace[2],
  574. };
  575. }
  576. return null;
  577. }
  578. /**
  579. * Creates groups of properties.
  580. * @param {ASTNode} node ObjectExpression node being evaluated.
  581. * @returns {Array<ASTNode[]>} Groups of property AST node lists.
  582. */
  583. function createGroups(node) {
  584. if (node.properties.length === 1) {
  585. return [node.properties];
  586. }
  587. return node.properties.reduce(
  588. (groups, property) => {
  589. const currentGroup = last(groups),
  590. prev = last(currentGroup);
  591. if (!prev || continuesPropertyGroup(prev, property)) {
  592. currentGroup.push(property);
  593. } else {
  594. groups.push([property]);
  595. }
  596. return groups;
  597. },
  598. [[]],
  599. );
  600. }
  601. /**
  602. * Verifies correct vertical alignment of a group of properties.
  603. * @param {ASTNode[]} properties List of Property AST nodes.
  604. * @returns {void}
  605. */
  606. function verifyGroupAlignment(properties) {
  607. const length = properties.length,
  608. widths = properties.map(getKeyWidth), // Width of keys, including quotes
  609. align = alignmentOptions.on; // "value" or "colon"
  610. let targetWidth = Math.max(...widths),
  611. beforeColon,
  612. afterColon,
  613. mode;
  614. if (alignmentOptions && length > 1) {
  615. // When aligning values within a group, use the alignment configuration.
  616. beforeColon = alignmentOptions.beforeColon;
  617. afterColon = alignmentOptions.afterColon;
  618. mode = alignmentOptions.mode;
  619. } else {
  620. beforeColon = multiLineOptions.beforeColon;
  621. afterColon = multiLineOptions.afterColon;
  622. mode = alignmentOptions.mode;
  623. }
  624. // Conditionally include one space before or after colon
  625. targetWidth += align === "colon" ? beforeColon : afterColon;
  626. for (let i = 0; i < length; i++) {
  627. const property = properties[i];
  628. const whitespace = getPropertyWhitespace(property);
  629. if (whitespace) {
  630. // Object literal getters/setters lack a colon
  631. const width = widths[i];
  632. if (align === "value") {
  633. report(
  634. property,
  635. "key",
  636. whitespace.beforeColon,
  637. beforeColon,
  638. mode,
  639. );
  640. report(
  641. property,
  642. "value",
  643. whitespace.afterColon,
  644. targetWidth - width,
  645. mode,
  646. );
  647. } else {
  648. // align = "colon"
  649. report(
  650. property,
  651. "key",
  652. whitespace.beforeColon,
  653. targetWidth - width,
  654. mode,
  655. );
  656. report(
  657. property,
  658. "value",
  659. whitespace.afterColon,
  660. afterColon,
  661. mode,
  662. );
  663. }
  664. }
  665. }
  666. }
  667. /**
  668. * Verifies spacing of property conforms to specified options.
  669. * @param {ASTNode} node Property node being evaluated.
  670. * @param {Object} lineOptions Configured singleLine or multiLine options
  671. * @returns {void}
  672. */
  673. function verifySpacing(node, lineOptions) {
  674. const actual = getPropertyWhitespace(node);
  675. if (actual) {
  676. // Object literal getters/setters lack colons
  677. report(
  678. node,
  679. "key",
  680. actual.beforeColon,
  681. lineOptions.beforeColon,
  682. lineOptions.mode,
  683. );
  684. report(
  685. node,
  686. "value",
  687. actual.afterColon,
  688. lineOptions.afterColon,
  689. lineOptions.mode,
  690. );
  691. }
  692. }
  693. /**
  694. * Verifies spacing of each property in a list.
  695. * @param {ASTNode[]} properties List of Property AST nodes.
  696. * @param {Object} lineOptions Configured singleLine or multiLine options
  697. * @returns {void}
  698. */
  699. function verifyListSpacing(properties, lineOptions) {
  700. const length = properties.length;
  701. for (let i = 0; i < length; i++) {
  702. verifySpacing(properties[i], lineOptions);
  703. }
  704. }
  705. /**
  706. * Verifies vertical alignment, taking into account groups of properties.
  707. * @param {ASTNode} node ObjectExpression node being evaluated.
  708. * @returns {void}
  709. */
  710. function verifyAlignment(node) {
  711. createGroups(node).forEach(group => {
  712. const properties = group.filter(isKeyValueProperty);
  713. if (
  714. properties.length > 0 &&
  715. isSingleLineProperties(properties)
  716. ) {
  717. verifyListSpacing(properties, multiLineOptions);
  718. } else {
  719. verifyGroupAlignment(properties);
  720. }
  721. });
  722. }
  723. //--------------------------------------------------------------------------
  724. // Public API
  725. //--------------------------------------------------------------------------
  726. if (alignmentOptions) {
  727. // Verify vertical alignment
  728. return {
  729. ObjectExpression(node) {
  730. if (isSingleLine(node)) {
  731. verifyListSpacing(
  732. node.properties.filter(isKeyValueProperty),
  733. singleLineOptions,
  734. );
  735. } else {
  736. verifyAlignment(node);
  737. }
  738. },
  739. };
  740. }
  741. // Obey beforeColon and afterColon in each property as configured
  742. return {
  743. Property(node) {
  744. verifySpacing(
  745. node,
  746. isSingleLine(node.parent)
  747. ? singleLineOptions
  748. : multiLineOptions,
  749. );
  750. },
  751. };
  752. },
  753. };