ScriptFormatterWorker.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. * Copyright (C) 2011 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. importScripts("utilities.js");
  31. importScripts("cm/headlesscodemirror.js");
  32. importScripts("cm/css.js");
  33. importScripts("cm/javascript.js");
  34. importScripts("cm/xml.js");
  35. importScripts("cm/htmlmixed.js");
  36. WebInspector = {};
  37. importScripts("CodeMirrorUtils.js");
  38. onmessage = function(event) {
  39. if (!event.data.method)
  40. return;
  41. self[event.data.method](event.data.params);
  42. };
  43. function format(params)
  44. {
  45. // Default to a 4-space indent.
  46. var indentString = params.indentString || " ";
  47. var result = {};
  48. if (params.mimeType === "text/html") {
  49. var formatter = new HTMLScriptFormatter(indentString);
  50. result = formatter.format(params.content);
  51. } else {
  52. result.mapping = { original: [0], formatted: [0] };
  53. result.content = formatScript(params.content, result.mapping, 0, 0, indentString);
  54. }
  55. postMessage(result);
  56. }
  57. function getChunkCount(totalLength, chunkSize)
  58. {
  59. if (totalLength <= chunkSize)
  60. return 1;
  61. var remainder = totalLength % chunkSize;
  62. var partialLength = totalLength - remainder;
  63. return (partialLength / chunkSize) + (remainder ? 1 : 0);
  64. }
  65. function outline(params)
  66. {
  67. const chunkSize = 100000; // characters per data chunk
  68. const totalLength = params.content.length;
  69. const lines = params.content.split("\n");
  70. const chunkCount = getChunkCount(totalLength, chunkSize);
  71. var outlineChunk = [];
  72. var previousIdentifier = null;
  73. var previousToken = null;
  74. var previousTokenType = null;
  75. var currentChunk = 1;
  76. var processedChunkCharacters = 0;
  77. var addedFunction = false;
  78. var isReadingArguments = false;
  79. var argumentsText = "";
  80. var currentFunction = null;
  81. var tokenizer = WebInspector.CodeMirrorUtils.createTokenizer("text/javascript");
  82. for (var i = 0; i < lines.length; ++i) {
  83. var line = lines[i];
  84. function processToken(tokenValue, tokenType, column, newColumn)
  85. {
  86. tokenType = tokenType ? WebInspector.CodeMirrorUtils.convertTokenType(tokenType) : null;
  87. if (tokenType === "javascript-ident") {
  88. previousIdentifier = tokenValue;
  89. if (tokenValue && previousToken === "function") {
  90. // A named function: "function f...".
  91. currentFunction = { line: i, column: column, name: tokenValue };
  92. addedFunction = true;
  93. previousIdentifier = null;
  94. }
  95. } else if (tokenType === "javascript-keyword") {
  96. if (tokenValue === "function") {
  97. if (previousIdentifier && (previousToken === "=" || previousToken === ":")) {
  98. // Anonymous function assigned to an identifier: "...f = function..."
  99. // or "funcName: function...".
  100. currentFunction = { line: i, column: column, name: previousIdentifier };
  101. addedFunction = true;
  102. previousIdentifier = null;
  103. }
  104. }
  105. } else if (tokenValue === "." && previousTokenType === "javascript-ident")
  106. previousIdentifier += ".";
  107. else if (tokenValue === "(" && addedFunction)
  108. isReadingArguments = true;
  109. if (isReadingArguments && tokenValue)
  110. argumentsText += tokenValue;
  111. if (tokenValue === ")" && isReadingArguments) {
  112. addedFunction = false;
  113. isReadingArguments = false;
  114. currentFunction.arguments = argumentsText.replace(/,[\r\n\s]*/g, ", ").replace(/([^,])[\r\n\s]+/g, "$1");
  115. argumentsText = "";
  116. outlineChunk.push(currentFunction);
  117. }
  118. if (tokenValue.trim().length) {
  119. // Skip whitespace tokens.
  120. previousToken = tokenValue;
  121. previousTokenType = tokenType;
  122. }
  123. processedChunkCharacters += newColumn - column;
  124. if (processedChunkCharacters >= chunkSize) {
  125. postMessage({ chunk: outlineChunk, total: chunkCount, index: currentChunk++ });
  126. outlineChunk = [];
  127. processedChunkCharacters = 0;
  128. }
  129. }
  130. tokenizer(line, processToken);
  131. }
  132. postMessage({ chunk: outlineChunk, total: chunkCount, index: chunkCount });
  133. }
  134. function formatScript(content, mapping, offset, formattedOffset, indentString)
  135. {
  136. var formattedContent;
  137. try {
  138. var tokenizer = new Tokenizer(content);
  139. var builder = new FormattedContentBuilder(tokenizer.content(), mapping, offset, formattedOffset, indentString);
  140. var formatter = new JavaScriptFormatter(tokenizer, builder);
  141. formatter.format();
  142. formattedContent = builder.content();
  143. } catch (e) {
  144. formattedContent = content;
  145. }
  146. return formattedContent;
  147. }
  148. Array.prototype.keySet = function()
  149. {
  150. var keys = {};
  151. for (var i = 0; i < this.length; ++i)
  152. keys[this[i]] = true;
  153. return keys;
  154. };
  155. HTMLScriptFormatter = function(indentString)
  156. {
  157. this._indentString = indentString;
  158. }
  159. HTMLScriptFormatter.prototype = {
  160. format: function(content)
  161. {
  162. this.line = content;
  163. this._content = content;
  164. this._formattedContent = "";
  165. this._mapping = { original: [0], formatted: [0] };
  166. this._position = 0;
  167. var scriptOpened = false;
  168. var tokenizer = WebInspector.CodeMirrorUtils.createTokenizer("text/html");
  169. function processToken(tokenValue, tokenType, tokenStart, tokenEnd) {
  170. if (tokenValue.toLowerCase() === "<script" && tokenType === "xml-tag") {
  171. scriptOpened = true;
  172. } else if (scriptOpened && tokenValue === ">" && tokenType === "xml-tag") {
  173. scriptOpened = false;
  174. this._scriptStarted(tokenEnd);
  175. } else if (tokenValue.toLowerCase() === "</script" && tokenType === "xml-tag") {
  176. this._scriptEnded(tokenStart);
  177. }
  178. }
  179. tokenizer(content, processToken.bind(this));
  180. this._formattedContent += this._content.substring(this._position);
  181. return { content: this._formattedContent, mapping: this._mapping };
  182. },
  183. _scriptStarted: function(cursor)
  184. {
  185. this._formattedContent += this._content.substring(this._position, cursor);
  186. this._formattedContent += "\n";
  187. this._position = cursor;
  188. },
  189. _scriptEnded: function(cursor)
  190. {
  191. if (cursor === this._position)
  192. return;
  193. var scriptContent = this._content.substring(this._position, cursor);
  194. this._mapping.original.push(this._position);
  195. this._mapping.formatted.push(this._formattedContent.length);
  196. var formattedScriptContent = formatScript(scriptContent, this._mapping, this._position, this._formattedContent.length, this._indentString);
  197. this._formattedContent += formattedScriptContent;
  198. this._position = cursor;
  199. },
  200. }
  201. function require()
  202. {
  203. return parse;
  204. }
  205. var exports = {};
  206. importScripts("UglifyJS/parse-js.js");
  207. var parse = exports;
  208. importScripts("JavaScriptFormatter.js");