| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| /** |
| * @param {string} mimeType |
| * @return {function(string, function(string, ?string, number, number):(!Object|undefined))} |
| */ |
| FormatterWorker.createTokenizer = function(mimeType) { |
| var mode = CodeMirror.getMode({indentUnit: 2}, mimeType); |
| var state = CodeMirror.startState(mode); |
| /** |
| * @param {string} line |
| * @param {function(string, ?string, number, number):?} callback |
| */ |
| function tokenize(line, callback) { |
| var stream = new CodeMirror.StringStream(line); |
| while (!stream.eol()) { |
| var style = mode.token(stream, state); |
| var value = stream.current(); |
| if (callback(value, style, stream.start, stream.start + value.length) === FormatterWorker.AbortTokenization) |
| return; |
| stream.start = stream.pos; |
| } |
| } |
| return tokenize; |
| }; |
| |
| FormatterWorker.AbortTokenization = {}; |
| |
| self.onmessage = function(event) { |
| var method = /** @type {string} */ (event.data.method); |
| var params = /** @type !{indentString: string, content: string, mimeType: string} */ (event.data.params); |
| if (!method) |
| return; |
| |
| switch (method) { |
| case 'format': |
| FormatterWorker.format(params.mimeType, params.content, params.indentString); |
| break; |
| case 'parseCSS': |
| FormatterWorker.parseCSS(params.content); |
| break; |
| case 'parseSCSS': |
| FormatterWorker.FormatterWorkerContentParser.parse(params.content, 'text/x-scss'); |
| break; |
| case 'javaScriptOutline': |
| FormatterWorker.javaScriptOutline(params.content); |
| break; |
| case 'javaScriptIdentifiers': |
| FormatterWorker.javaScriptIdentifiers(params.content); |
| break; |
| case 'evaluatableJavaScriptSubstring': |
| FormatterWorker.evaluatableJavaScriptSubstring(params.content); |
| break; |
| case 'parseJSONRelaxed': |
| FormatterWorker.parseJSONRelaxed(params.content); |
| break; |
| case 'preprocessTopLevelAwaitExpressions': |
| FormatterWorker.preprocessTopLevelAwaitExpressions(params.content); |
| break; |
| default: |
| console.error('Unsupport method name: ' + method); |
| } |
| }; |
| |
| /** |
| * @param {string} content |
| */ |
| FormatterWorker.parseJSONRelaxed = function(content) { |
| postMessage(FormatterWorker.RelaxedJSONParser.parse(content)); |
| }; |
| |
| /** |
| * @param {string} content |
| */ |
| FormatterWorker.evaluatableJavaScriptSubstring = function(content) { |
| var tokenizer = acorn.tokenizer(content, {ecmaVersion: 8}); |
| var result = ''; |
| try { |
| var token = tokenizer.getToken(); |
| while (token.type !== acorn.tokTypes.eof && FormatterWorker.AcornTokenizer.punctuator(token)) |
| token = tokenizer.getToken(); |
| |
| var startIndex = token.start; |
| var endIndex = token.end; |
| var openBracketsCounter = 0; |
| while (token.type !== acorn.tokTypes.eof) { |
| var isIdentifier = FormatterWorker.AcornTokenizer.identifier(token); |
| var isThis = FormatterWorker.AcornTokenizer.keyword(token, 'this'); |
| var isString = token.type === acorn.tokTypes.string; |
| if (!isThis && !isIdentifier && !isString) |
| break; |
| |
| endIndex = token.end; |
| token = tokenizer.getToken(); |
| while (FormatterWorker.AcornTokenizer.punctuator(token, '.[]')) { |
| if (FormatterWorker.AcornTokenizer.punctuator(token, '[')) |
| openBracketsCounter++; |
| |
| if (FormatterWorker.AcornTokenizer.punctuator(token, ']')) { |
| endIndex = openBracketsCounter > 0 ? token.end : endIndex; |
| openBracketsCounter--; |
| } |
| |
| token = tokenizer.getToken(); |
| } |
| } |
| result = content.substring(startIndex, endIndex); |
| } catch (e) { |
| console.error(e); |
| } |
| postMessage(result); |
| }; |
| |
| /** |
| * @param {string} content |
| */ |
| FormatterWorker.preprocessTopLevelAwaitExpressions = function(content) { |
| var wrapped = '(async () => {' + content + '})()'; |
| var root = acorn.parse(wrapped, {ecmaVersion: 8}); |
| var body = root.body[0].expression.callee.body; |
| var changes = []; |
| var containsAwait = false; |
| var containsReturn = false; |
| class Visitor { |
| ClassDeclaration(node) { |
| if (node.parent === body) |
| changes.push({text: node.id.name + '=', start: node.start, end: node.start}); |
| } |
| FunctionDeclaration(node) { |
| changes.push({text: node.id.name + '=', start: node.start, end: node.start}); |
| return FormatterWorker.ESTreeWalker.SkipSubtree; |
| } |
| FunctionExpression(node) { |
| return FormatterWorker.ESTreeWalker.SkipSubtree; |
| } |
| ArrowFunctionExpression(node) { |
| return FormatterWorker.ESTreeWalker.SkipSubtree; |
| } |
| MethodDefinition(node) { |
| return FormatterWorker.ESTreeWalker.SkipSubtree; |
| } |
| AwaitExpression(node) { |
| containsAwait = true; |
| } |
| ReturnStatement(node) { |
| containsReturn = true; |
| } |
| VariableDeclaration(node) { |
| if (node.kind !== 'var' && node.parent !== body) |
| return; |
| var onlyOneDeclaration = node.declarations.length === 1; |
| changes.push( |
| {text: onlyOneDeclaration ? 'void' : 'void (', start: node.start, end: node.start + node.kind.length}); |
| for (var declaration of node.declarations) { |
| if (!declaration.init) { |
| changes.push({text: '(', start: declaration.start, end: declaration.start}); |
| changes.push({text: '=undefined)', start: declaration.end, end: declaration.end}); |
| continue; |
| } |
| changes.push({text: '(', start: declaration.start, end: declaration.start}); |
| changes.push({text: ')', start: declaration.end, end: declaration.end}); |
| } |
| if (!onlyOneDeclaration) { |
| var last = node.declarations.peekLast(); |
| changes.push({text: ')', start: last.end, end: last.end}); |
| } |
| } |
| } |
| var walker = new FormatterWorker.ESTreeWalker(visit.bind(new Visitor())); |
| walker.walk(body); |
| /** |
| * @param {!ESTree.Node} node |
| * @this {Object} |
| */ |
| function visit(node) { |
| if (node.type in this) |
| return this[node.type](node); |
| } |
| // Top-level return is not allowed. |
| if (!containsAwait || containsReturn) { |
| postMessage(''); |
| return; |
| } |
| var last = body.body[body.body.length - 1]; |
| if (last.type === 'ExpressionStatement') { |
| changes.push({text: 'return (', start: last.start, end: last.start}); |
| if (wrapped[last.end - 1] !== ';') |
| changes.push({text: ')', start: last.end, end: last.end}); |
| else |
| changes.push({text: ')', start: last.end - 1, end: last.end - 1}); |
| } |
| while (changes.length) { |
| var change = changes.pop(); |
| wrapped = wrapped.substr(0, change.start) + change.text + wrapped.substr(change.end); |
| } |
| postMessage(wrapped); |
| }; |
| |
| /** |
| * @param {string} content |
| */ |
| FormatterWorker.javaScriptIdentifiers = function(content) { |
| var root = acorn.parse(content, {ranges: false, ecmaVersion: 8}); |
| |
| /** @type {!Array<!ESTree.Node>} */ |
| var identifiers = []; |
| var walker = new FormatterWorker.ESTreeWalker(beforeVisit); |
| |
| /** |
| * @param {!ESTree.Node} node |
| * @return {boolean} |
| */ |
| function isFunction(node) { |
| return node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || |
| node.type === 'ArrowFunctionExpression'; |
| } |
| |
| /** |
| * @param {!ESTree.Node} node |
| */ |
| function beforeVisit(node) { |
| if (isFunction(node)) { |
| if (node.id) |
| identifiers.push(node.id); |
| return FormatterWorker.ESTreeWalker.SkipSubtree; |
| } |
| |
| if (node.type !== 'Identifier') |
| return; |
| |
| if (node.parent && node.parent.type === 'MemberExpression' && node.parent.property === node && |
| !node.parent.computed) |
| return; |
| identifiers.push(node); |
| } |
| |
| if (!root || root.type !== 'Program' || root.body.length !== 1 || !isFunction(root.body[0])) { |
| postMessage([]); |
| return; |
| } |
| |
| var functionNode = root.body[0]; |
| for (var param of functionNode.params) |
| walker.walk(param); |
| walker.walk(functionNode.body); |
| var reduced = identifiers.map(id => ({name: id.name, offset: id.start})); |
| postMessage(reduced); |
| }; |
| |
| /** |
| * @param {string} mimeType |
| * @param {string} text |
| * @param {string=} indentString |
| */ |
| FormatterWorker.format = function(mimeType, text, indentString) { |
| // Default to a 4-space indent. |
| indentString = indentString || ' '; |
| var result = {}; |
| var builder = new FormatterWorker.FormattedContentBuilder(indentString); |
| var lineEndings = text.computeLineEndings(); |
| try { |
| switch (mimeType) { |
| case 'text/html': |
| var formatter = new FormatterWorker.HTMLFormatter(builder); |
| formatter.format(text, lineEndings); |
| break; |
| case 'text/css': |
| var formatter = new FormatterWorker.CSSFormatter(builder); |
| formatter.format(text, lineEndings, 0, text.length); |
| break; |
| case 'text/javascript': |
| var formatter = new FormatterWorker.JavaScriptFormatter(builder); |
| formatter.format(text, lineEndings, 0, text.length); |
| break; |
| default: |
| var formatter = new FormatterWorker.IdentityFormatter(builder); |
| formatter.format(text, lineEndings, 0, text.length); |
| } |
| result.mapping = builder.mapping(); |
| result.content = builder.content(); |
| } catch (e) { |
| console.error(e); |
| result.mapping = {original: [0], formatted: [0]}; |
| result.content = text; |
| } |
| postMessage(result); |
| }; |
| |
| /** |
| * @interface |
| */ |
| FormatterWorker.FormatterWorkerContentParser = function() {}; |
| |
| FormatterWorker.FormatterWorkerContentParser.prototype = { |
| /** |
| * @param {string} content |
| * @return {!Object} |
| */ |
| parse(content) {} |
| }; |
| |
| /** |
| * @param {string} content |
| * @param {string} mimeType |
| */ |
| FormatterWorker.FormatterWorkerContentParser.parse = function(content, mimeType) { |
| var extension = self.runtime.extensions(FormatterWorker.FormatterWorkerContentParser).find(findExtension); |
| console.assert(extension); |
| extension.instance().then(instance => instance.parse(content)).catchException(null).then(postMessage); |
| |
| /** |
| * @param {!Runtime.Extension} extension |
| * @return {boolean} |
| */ |
| function findExtension(extension) { |
| return extension.descriptor()['mimeType'] === mimeType; |
| } |
| }; |