Chromium Code Reviews| Index: third_party/WebKit/Source/devtools/front_end/formatter_worker/JavaScriptOutline.js |
| diff --git a/third_party/WebKit/Source/devtools/front_end/formatter_worker/JavaScriptOutline.js b/third_party/WebKit/Source/devtools/front_end/formatter_worker/JavaScriptOutline.js |
| index f3a6c1bcccbd127d383f222171dc5da340e293e3..30713c4dd49a51d9594485cd213f2b2b17ee104c 100644 |
| --- a/third_party/WebKit/Source/devtools/front_end/formatter_worker/JavaScriptOutline.js |
| +++ b/third_party/WebKit/Source/devtools/front_end/formatter_worker/JavaScriptOutline.js |
| @@ -5,78 +5,156 @@ |
| * @param {string} content |
| */ |
| FormatterWorker.javaScriptOutline = function(content) { |
| - var chunkSize = 100000; // characters per data chunk |
| + var chunkSize = 100000; |
| var outlineChunk = []; |
| - var previousIdentifier = null; |
| - var previousToken = null; |
| - var processedChunkCharacters = 0; |
| - var addedFunction = false; |
| - var isReadingArguments = false; |
| - var argumentsText = ''; |
| - var currentFunction = null; |
| - var tokenizer = new FormatterWorker.AcornTokenizer(content); |
| - var AT = FormatterWorker.AcornTokenizer; |
| + var lastReportedOffset = 0; |
| - while (tokenizer.peekToken()) { |
| - var token = /** @type {!Acorn.TokenOrComment} */ (tokenizer.nextToken()); |
| - if (AT.lineComment(token) || AT.blockComment(token)) |
| - continue; |
| + var ast; |
| + try { |
| + ast = acorn.parse(content, {ranges: false, ecmaVersion: 8}); |
| + } catch (e) { |
| + ast = acorn.parse_dammit(content, {ranges: false, ecmaVersion: 8}); |
| + } |
| - var tokenValue = content.substring(token.start, token.end); |
| + var textCursor = new TextUtils.TextCursor(content.computeLineEndings()); |
| + var walker = new FormatterWorker.ESTreeWalker(beforeVisit); |
| + walker.walk(ast); |
| + postMessage({chunk: outlineChunk, isLastChunk: true}); |
| - if (AT.identifier(token) && previousToken && |
| - (AT.identifier(previousToken, 'get') || AT.identifier(previousToken, 'set'))) { |
| - currentFunction = { |
| - line: tokenizer.tokenLineStart(), |
| - column: tokenizer.tokenColumnStart(), |
| - name: previousToken.value + ' ' + tokenValue |
| - }; |
| - addedFunction = true; |
| - previousIdentifier = null; |
| - } else if (AT.identifier(token)) { |
| - previousIdentifier = tokenValue; |
| - if (tokenValue && previousToken && AT.keyword(previousToken, 'function')) { |
| - // A named function: "function f...". |
| - currentFunction = {line: tokenizer.tokenLineStart(), column: tokenizer.tokenColumnStart(), name: tokenValue}; |
| - addedFunction = true; |
| - previousIdentifier = null; |
| - } |
| + /** |
| + * @param {!ESTree.Node} node |
| + */ |
| + function beforeVisit(node) { |
| + if (node.type === 'ClassDeclaration') { |
| + reportClass(/** @type {!ESTree.Node} */ (node.id)); |
| + } else if (node.type === 'VariableDeclarator' && isClassNode(node.init)) { |
| + reportClass(/** @type {!ESTree.Node} */ (node.id)); |
| + } else if (node.type === 'AssignmentExpression' && isIdentifier(node.left) && isClassNode(node.right)) { |
| + reportClass(/** @type {!ESTree.Node} */ (node.left)); |
| + } else if (node.type === 'FunctionDeclaration') { |
| + reportFunction(/** @type {!ESTree.Node} */ (node.id), node); |
| + } else if (node.type === 'VariableDeclarator' && isFunctionNode(node.init)) { |
| + reportFunction(/** @type {!ESTree.Node} */ (node.id), /** @type {!ESTree.Node} */ (node.init)); |
| + } else if (node.type === 'AssignmentExpression' && isIdentifier(node.left) && isFunctionNode(node.right)) { |
| + reportFunction(/** @type {!ESTree.Node} */ (node.left), /** @type {!ESTree.Node} */ (node.right)); |
| } else if ( |
| - AT.keyword(token, 'function') && previousIdentifier && previousToken && AT.punctuator(previousToken, ':=')) { |
| - // Anonymous function assigned to an identifier: "...f = function..." |
| - // or "funcName: function...". |
| - currentFunction = { |
| - line: tokenizer.tokenLineStart(), |
| - column: tokenizer.tokenColumnStart(), |
| - name: previousIdentifier |
| - }; |
| - addedFunction = true; |
| - previousIdentifier = null; |
| - } else if (AT.punctuator(token, '.') && previousToken && AT.identifier(previousToken)) { |
| - previousIdentifier += '.'; |
| - } else if (AT.punctuator(token, '(') && addedFunction) { |
| - isReadingArguments = true; |
| + (node.type === 'MethodDefinition' || node.type === 'Property') && isIdentifier(node.key) && |
|
dgozman
2017/03/24 01:00:00
String literal as a computed property key.
lushnikov
2017/03/24 19:19:36
Done.
|
| + isFunctionNode(node.value)) { |
|
dgozman
2017/03/24 01:00:00
Property could be a class.
lushnikov
2017/03/24 19:19:37
Done.
|
| + var namePrefix = []; |
| + if (node.kind === 'get' || node.kind === 'set') |
| + namePrefix.push(node.kind); |
| + if (node.static) |
| + namePrefix.push('static'); |
| + reportFunction(node.key, node.value, namePrefix.join(' ')); |
| } |
| - if (isReadingArguments && tokenValue) |
| - argumentsText += tokenValue; |
| + } |
| - if (AT.punctuator(token, ')') && isReadingArguments) { |
| - addedFunction = false; |
| - isReadingArguments = false; |
| - currentFunction.arguments = argumentsText.replace(/,[\r\n\s]*/g, ', ').replace(/([^,])[\r\n\s]+/g, '$1'); |
| - argumentsText = ''; |
| - outlineChunk.push(currentFunction); |
| - } |
| + /** |
| + * @param {!ESTree.Node} nameNode |
| + */ |
| + function reportClass(nameNode) { |
| + var name = 'class ' + stringifyIdentifier(nameNode); |
| + textCursor.advance(nameNode.start); |
| + addOutlineItem({ |
| + name: name, |
| + line: textCursor.lineNumber(), |
| + column: textCursor.columnNumber(), |
| + }); |
| + } |
| + |
| + /** |
| + * @param {!ESTree.Node} nameNode |
| + * @param {!ESTree.Node} functionNode |
| + * @param {string=} namePrefix |
| + */ |
| + function reportFunction(nameNode, functionNode, namePrefix) { |
| + var name = stringifyIdentifier(nameNode); |
| + if (functionNode.generator) |
| + name = '*' + name; |
| + if (namePrefix) |
| + name = namePrefix + ' ' + name; |
| + if (functionNode.async) |
| + name = 'async ' + name; |
| + |
| + textCursor.advance(nameNode.start); |
| + addOutlineItem({ |
| + name: name, |
| + line: textCursor.lineNumber(), |
| + column: textCursor.columnNumber(), |
| + arguments: stringifyArguments(/** @type {!Array<!ESTree.Node>} */ (functionNode.params)) |
| + }); |
| + } |
| + |
| + /** |
| + * @param {(!ESTree.Node|undefined)} node |
| + * @return {boolean} |
| + */ |
| + function isIdentifier(node) { |
|
dgozman
2017/03/24 01:00:00
isName
lushnikov
2017/03/24 19:19:36
Done.
|
| + if (!node) |
| + return false; |
| + return node.type === 'Identifier' || node.type === 'MemberExpression'; |
|
dgozman
2017/03/24 01:00:00
&& !node.computed
lushnikov
2017/03/24 19:19:36
Done.
|
| + } |
| - previousToken = token; |
| - processedChunkCharacters += token.end - token.start; |
| + /** |
| + * @param {(!ESTree.Node|undefined)} node |
| + * @return {boolean} |
| + */ |
| + function isFunctionNode(node) { |
| + if (!node) |
| + return false; |
| + return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression'; |
| + } |
| - if (processedChunkCharacters >= chunkSize) { |
| - postMessage({chunk: outlineChunk, isLastChunk: false}); |
| - outlineChunk = []; |
| - processedChunkCharacters = 0; |
| + /** |
| + * @param {(!ESTree.Node|undefined)} node |
| + * @return {boolean} |
| + */ |
| + function isClassNode(node) { |
| + return !!node && node.type === 'ClassExpression'; |
| + } |
| + |
| + /** |
| + * @param {!ESTree.Node} node |
| + * @return {string} |
| + */ |
| + function stringifyIdentifier(node) { |
| + var path = []; |
| + while (node.type === 'MemberExpression') { |
| + path.push(node.property.name); |
|
dgozman
2017/03/24 01:00:00
Cannot get property "name" of undefined
lushnikov
2017/03/24 19:19:37
Done.
|
| + node = /** @type {!ESTree.Node} */ (node.object); |
| } |
| + console.assert(node.type === 'Identifier', 'Cannot extract identifier from unknown type: ' + node.type); |
|
dgozman
2017/03/24 01:00:00
(a || b).c = 2;
lushnikov
2017/03/24 19:19:37
Done.
|
| + path.push(node.name); |
| + path.reverse(); |
| + return path.join('.'); |
| } |
| - postMessage({chunk: outlineChunk, isLastChunk: true}); |
| + /** |
| + * @param {!Array<!ESTree.Node>} params |
| + * @return {string} |
| + */ |
| + function stringifyArguments(params) { |
| + var result = []; |
| + for (var param of params) { |
| + if (param.type === 'Identifier') |
| + result.push(param.name); |
| + else if (param.type === 'RestElement' && param.argument.type === 'Identifier') |
| + result.push('...' + param.argument.name); |
| + else |
| + console.error('Error: unexpected function parameter type: ' + param.type); |
| + } |
| + return '(' + result.join(', ') + ')'; |
| + } |
| + |
| + /** |
| + * @param {{name: string, line: number, column: number, arguments: (string|undefined)}} item |
| + */ |
| + function addOutlineItem(item) { |
| + outlineChunk.push(item); |
| + if (textCursor.offset() - lastReportedOffset < chunkSize) |
| + return; |
| + postMessage({chunk: outlineChunk, isLastChunk: false}); |
| + outlineChunk = []; |
| + lastReportedOffset = textCursor.offset(); |
| + } |
| }; |