json-utils.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. function appendPropertyInAstObject(recorder, node, propertyName, value, indent) {
  4. const indentStr = _buildIndent(indent);
  5. let index = node.start.offset + 1;
  6. if (node.properties.length > 0) {
  7. // Insert comma.
  8. const last = node.properties[node.properties.length - 1];
  9. const { text, end } = last;
  10. const commaIndex = text.endsWith('\n') ? end.offset - 1 : end.offset;
  11. recorder.insertRight(commaIndex, ',');
  12. index = end.offset;
  13. }
  14. const content = _stringifyContent(value, indentStr);
  15. recorder.insertRight(index, (node.properties.length === 0 && indent ? '\n' : '')
  16. + ' '.repeat(indent)
  17. + `"${propertyName}":${indent ? ' ' : ''}${content}`
  18. + indentStr.slice(0, -indent));
  19. }
  20. exports.appendPropertyInAstObject = appendPropertyInAstObject;
  21. function insertPropertyInAstObjectInOrder(recorder, node, propertyName, value, indent) {
  22. if (node.properties.length === 0) {
  23. appendPropertyInAstObject(recorder, node, propertyName, value, indent);
  24. return;
  25. }
  26. // Find insertion info.
  27. let insertAfterProp = null;
  28. let prev = null;
  29. let isLastProp = false;
  30. const last = node.properties[node.properties.length - 1];
  31. for (const prop of node.properties) {
  32. if (prop.key.value > propertyName) {
  33. if (prev) {
  34. insertAfterProp = prev;
  35. }
  36. break;
  37. }
  38. if (prop === last) {
  39. isLastProp = true;
  40. insertAfterProp = last;
  41. }
  42. prev = prop;
  43. }
  44. if (isLastProp) {
  45. appendPropertyInAstObject(recorder, node, propertyName, value, indent);
  46. return;
  47. }
  48. const indentStr = _buildIndent(indent);
  49. const insertIndex = insertAfterProp === null
  50. ? node.start.offset + 1
  51. : insertAfterProp.end.offset + 1;
  52. const content = _stringifyContent(value, indentStr);
  53. recorder.insertRight(insertIndex, indentStr
  54. + `"${propertyName}":${indent ? ' ' : ''}${content}`
  55. + ',');
  56. }
  57. exports.insertPropertyInAstObjectInOrder = insertPropertyInAstObjectInOrder;
  58. function removePropertyInAstObject(recorder, node, propertyName) {
  59. // Find the property inside the object.
  60. const propIdx = node.properties.findIndex(prop => prop.key.value === propertyName);
  61. if (propIdx === -1) {
  62. // There's nothing to remove.
  63. return;
  64. }
  65. if (node.properties.length === 1) {
  66. // This is a special case. Everything should be removed, including indentation.
  67. recorder.remove(node.start.offset, node.end.offset - node.start.offset);
  68. recorder.insertRight(node.start.offset, '{}');
  69. return;
  70. }
  71. // The AST considers commas and indentation to be part of the preceding property.
  72. // To get around messy comma and identation management, we can work over the range between
  73. // two properties instead.
  74. const previousProp = node.properties[propIdx - 1];
  75. const targetProp = node.properties[propIdx];
  76. const nextProp = node.properties[propIdx + 1];
  77. let start, end;
  78. if (previousProp) {
  79. // Given the object below, and intending to remove the `m` property:
  80. // "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}"
  81. // ^---------------^
  82. // Removing the range above results in:
  83. // "{\n \"a\": \"a\",\n \"z\": \"z\"\n}"
  84. start = previousProp.end;
  85. end = targetProp.end;
  86. }
  87. else {
  88. // If there's no previousProp there is a nextProp, since we've specialcased the 1 length case.
  89. // Given the object below, and intending to remove the `a` property:
  90. // "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}"
  91. // ^---------------^
  92. // Removing the range above results in:
  93. // "{\n \"m\": \"m\",\n \"z\": \"z\"\n}"
  94. start = targetProp.start;
  95. end = nextProp.start;
  96. }
  97. recorder.remove(start.offset, end.offset - start.offset);
  98. if (!nextProp) {
  99. recorder.insertRight(start.offset, '\n');
  100. }
  101. }
  102. exports.removePropertyInAstObject = removePropertyInAstObject;
  103. function appendValueInAstArray(recorder, node, value, indent = 4) {
  104. let indentStr = _buildIndent(indent);
  105. let index = node.start.offset + 1;
  106. // tslint:disable-next-line: no-any
  107. let newNodes;
  108. if (node.elements.length > 0) {
  109. // Insert comma.
  110. const { end } = node.elements[node.elements.length - 1];
  111. const isClosingOnSameLine = node.end.offset - end.offset === 1;
  112. if (isClosingOnSameLine && indent) {
  113. // Reformat the entire array
  114. recorder.remove(node.start.offset, node.end.offset - node.start.offset);
  115. newNodes = [
  116. ...node.elements.map(({ value }) => value),
  117. value,
  118. ];
  119. index = node.start.offset;
  120. // In case we are generating the entire node we need to reduce the spacing as
  121. // otherwise we'd end up having incorrect double spacing
  122. indent = indent - 2;
  123. indentStr = _buildIndent(indent);
  124. }
  125. else {
  126. recorder.insertRight(end.offset, ',');
  127. index = end.offset;
  128. }
  129. }
  130. recorder.insertRight(index, (newNodes ? '' : indentStr)
  131. + _stringifyContent(newNodes || value, indentStr)
  132. + (node.elements.length === 0 && indent ? indentStr.substr(0, -indent) + '\n' : ''));
  133. }
  134. exports.appendValueInAstArray = appendValueInAstArray;
  135. function findPropertyInAstObject(node, propertyName) {
  136. let maybeNode = null;
  137. for (const property of node.properties) {
  138. if (property.key.value == propertyName) {
  139. maybeNode = property.value;
  140. }
  141. }
  142. return maybeNode;
  143. }
  144. exports.findPropertyInAstObject = findPropertyInAstObject;
  145. function _buildIndent(count) {
  146. return count ? '\n' + ' '.repeat(count) : '';
  147. }
  148. function _stringifyContent(value, indentStr) {
  149. // TODO: Add snapshot tests
  150. // The 'space' value is 2, because we want to add 2 additional
  151. // indents from the 'key' node.
  152. // If we use the indent provided we will have double indents:
  153. // "budgets": [
  154. // {
  155. // "type": "initial",
  156. // "maximumWarning": "2mb",
  157. // "maximumError": "5mb"
  158. // },
  159. // {
  160. // "type": "anyComponentStyle",
  161. // 'maximumWarning": "5kb"
  162. // }
  163. // ]
  164. return JSON.stringify(value, null, 2).replace(/\n/g, indentStr);
  165. }