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(); |
+ } |
}; |