strip-json-comments.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Natsu @xiaoxiaojx
  4. This file contains code ported from strip-json-comments:
  5. https://github.com/sindresorhus/strip-json-comments
  6. Original license: MIT
  7. Original author: Sindre Sorhus
  8. */
  9. "use strict";
  10. /**
  11. * @typedef {object} StripJsonCommentsOptions
  12. * @property {boolean=} whitespace Replace comments with whitespace
  13. * @property {boolean=} trailingCommas Strip trailing commas
  14. */
  15. const singleComment = Symbol("singleComment");
  16. const multiComment = Symbol("multiComment");
  17. /**
  18. * Strip without whitespace (returns empty string)
  19. * @param {string} _string Unused
  20. * @param {number} _start Unused
  21. * @param {number} _end Unused
  22. * @returns {string} Empty string for all input
  23. */
  24. const stripWithoutWhitespace = (_string, _start, _end) => "";
  25. /**
  26. * Replace all characters except ASCII spaces, tabs and line endings with regular spaces to ensure valid JSON output.
  27. * @param {string} string String to process
  28. * @param {number} start Start index
  29. * @param {number} end End index
  30. * @returns {string} Processed string with comments replaced by whitespace
  31. */
  32. const stripWithWhitespace = (string, start, end) =>
  33. string.slice(start, end).replace(/[^ \t\r\n]/g, " ");
  34. /**
  35. * Check if a quote is escaped
  36. * @param {string} jsonString JSON string
  37. * @param {number} quotePosition Position of the quote
  38. * @returns {boolean} True if the quote at the given position is escaped
  39. */
  40. const isEscaped = (jsonString, quotePosition) => {
  41. let index = quotePosition - 1;
  42. let backslashCount = 0;
  43. while (jsonString[index] === "\\") {
  44. index -= 1;
  45. backslashCount += 1;
  46. }
  47. return Boolean(backslashCount % 2);
  48. };
  49. /**
  50. * Strip comments from JSON string
  51. * @param {string} jsonString JSON string with potential comments
  52. * @param {StripJsonCommentsOptions} options Options
  53. * @returns {string} JSON string without comments
  54. */
  55. function stripJsonComments(
  56. jsonString,
  57. { whitespace = true, trailingCommas = false } = {},
  58. ) {
  59. if (typeof jsonString !== "string") {
  60. throw new TypeError(
  61. `Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``,
  62. );
  63. }
  64. const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
  65. let isInsideString = false;
  66. /** @type {false | typeof singleComment | typeof multiComment} */
  67. let isInsideComment = false;
  68. let offset = 0;
  69. let buffer = "";
  70. let result = "";
  71. let commaIndex = -1;
  72. for (let index = 0; index < jsonString.length; index++) {
  73. const currentCharacter = jsonString[index];
  74. const nextCharacter = jsonString[index + 1];
  75. if (!isInsideComment && currentCharacter === '"') {
  76. // Enter or exit string
  77. const escaped = isEscaped(jsonString, index);
  78. if (!escaped) {
  79. isInsideString = !isInsideString;
  80. }
  81. }
  82. if (isInsideString) {
  83. continue;
  84. }
  85. if (!isInsideComment && currentCharacter + nextCharacter === "//") {
  86. // Enter single-line comment
  87. buffer += jsonString.slice(offset, index);
  88. offset = index;
  89. isInsideComment = singleComment;
  90. index++;
  91. } else if (
  92. isInsideComment === singleComment &&
  93. currentCharacter + nextCharacter === "\r\n"
  94. ) {
  95. // Exit single-line comment via \r\n
  96. index++;
  97. isInsideComment = false;
  98. buffer += strip(jsonString, offset, index);
  99. offset = index;
  100. continue;
  101. } else if (isInsideComment === singleComment && currentCharacter === "\n") {
  102. // Exit single-line comment via \n
  103. isInsideComment = false;
  104. buffer += strip(jsonString, offset, index);
  105. offset = index;
  106. } else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
  107. // Enter multiline comment
  108. buffer += jsonString.slice(offset, index);
  109. offset = index;
  110. isInsideComment = multiComment;
  111. index++;
  112. continue;
  113. } else if (
  114. isInsideComment === multiComment &&
  115. currentCharacter + nextCharacter === "*/"
  116. ) {
  117. // Exit multiline comment
  118. index++;
  119. isInsideComment = false;
  120. buffer += strip(jsonString, offset, index + 1);
  121. offset = index + 1;
  122. continue;
  123. } else if (trailingCommas && !isInsideComment) {
  124. if (commaIndex !== -1) {
  125. if (currentCharacter === "}" || currentCharacter === "]") {
  126. // Strip trailing comma
  127. buffer += jsonString.slice(offset, index);
  128. result += strip(buffer, 0, 1) + buffer.slice(1);
  129. buffer = "";
  130. offset = index;
  131. commaIndex = -1;
  132. } else if (
  133. currentCharacter !== " " &&
  134. currentCharacter !== "\t" &&
  135. currentCharacter !== "\r" &&
  136. currentCharacter !== "\n"
  137. ) {
  138. // Hit non-whitespace following a comma; comma is not trailing
  139. buffer += jsonString.slice(offset, index);
  140. offset = index;
  141. commaIndex = -1;
  142. }
  143. } else if (currentCharacter === ",") {
  144. // Flush buffer prior to this point, and save new comma index
  145. result += buffer + jsonString.slice(offset, index);
  146. buffer = "";
  147. offset = index;
  148. commaIndex = index;
  149. }
  150. }
  151. }
  152. const remaining =
  153. isInsideComment === singleComment
  154. ? strip(jsonString, offset, jsonString.length)
  155. : jsonString.slice(offset);
  156. return result + buffer + remaining;
  157. }
  158. module.exports = stripJsonComments;