Bug 1216668 - Implement console autocomplete for inline arrays and multiline strings;r=fitzgen
authorBrian Grinstead <bgrinstead@mozilla.com>
Wed, 21 Oct 2015 07:10:38 -0700
changeset 268703 9c494af00cfb40168b415f40c2341071189122ba
parent 268702 b491da27641b5c0d7357b1e5584bf156ead8a107
child 268704 113e19e7e902821fad7ac7a692db5b3132a90f7a
push id15791
push userbgrinstead@mozilla.com
push dateWed, 21 Oct 2015 14:11:23 +0000
treeherderfx-team@9c494af00cfb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen
bugs1216668
milestone44.0a1
Bug 1216668 - Implement console autocomplete for inline arrays and multiline strings;r=fitzgen
devtools/shared/Parser.jsm
devtools/shared/webconsole/test/unit/test_js_property_provider.js
devtools/shared/webconsole/utils.js
--- a/devtools/shared/Parser.jsm
+++ b/devtools/shared/Parser.jsm
@@ -149,16 +149,24 @@ SyntaxTreesPool.prototype = {
   /**
    * @see SyntaxTree.prototype.getNamedFunctionDefinitions
    */
   getNamedFunctionDefinitions: function(aSubstring) {
     return this._call("getNamedFunctionDefinitions", -1, aSubstring);
   },
 
   /**
+   * @return SyntaxTree
+   *         The last tree in this._trees
+   */
+  getLastSyntaxTree: function() {
+    return this._trees[this._trees.length - 1];
+  },
+
+  /**
    * Gets the total number of scripts in the parent source.
    * @return number
    */
   get scriptCount() {
     return this._trees.length;
   },
 
   /**
--- a/devtools/shared/webconsole/test/unit/test_js_property_provider.js
+++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js
@@ -36,16 +36,56 @@ function run_test() {
   let results = JSPropertyProvider(dbgObject, null, "testArray[0].");
   do_print("Test that suggestions are given for 'foo[n]' where n is an integer.");
   test_has_result(results, "propA");
 
   do_print("Test that suggestions are given for multidimensional arrays.");
   results = JSPropertyProvider(dbgObject, null, "testArray[2][0].");
   test_has_result(results, "propE");
 
+  do_print("Test that suggestions are given for literal arrays.");
+  results = JSPropertyProvider(dbgObject, null, "[1,2,3].");
+  test_has_result(results, "indexOf");
+
+  do_print("Test that suggestions are given for literal arrays with newlines.");
+  results = JSPropertyProvider(dbgObject, null, "[1,2,3,\n4\n].");
+  test_has_result(results, "indexOf");
+
+  do_print("Test that suggestions are given for literal strings.");
+  results = JSPropertyProvider(dbgObject, null, "'foo'.");
+  test_has_result(results, "charAt");
+  results = JSPropertyProvider(dbgObject, null, '"foo".');
+  test_has_result(results, "charAt");
+  results = JSPropertyProvider(dbgObject, null, "`foo`.");
+  test_has_result(results, "charAt");
+  results = JSPropertyProvider(dbgObject, null, "'[1,2,3]'.");
+  test_has_result(results, "charAt");
+
+  do_print("Test that suggestions are not given for syntax errors.");
+  results = JSPropertyProvider(dbgObject, null, "'foo\"");
+  do_check_null(results);
+  results = JSPropertyProvider(dbgObject, null, "[1,',2]");
+  do_check_null(results);
+  results = JSPropertyProvider(dbgObject, null, "'[1,2].");
+  do_check_null(results);
+  results = JSPropertyProvider(dbgObject, null, "'foo'..");
+  do_check_null(results);
+
+  do_print("Test that suggestions are not given without a dot.");
+  results = JSPropertyProvider(dbgObject, null, "'foo'");
+  test_has_no_results(results);
+  results = JSPropertyProvider(dbgObject, null, "[1,2,3]");
+  test_has_no_results(results);
+  results = JSPropertyProvider(dbgObject, null, "[1,2,3].\n'foo'");
+  test_has_no_results(results);
+
+  do_print("Test that suggestions are not given for numeric literals.");
+  results = JSPropertyProvider(dbgObject, null, "1.");
+  do_check_null(results);
+
   do_print("Test that suggestions are not given for index that's out of bounds.");
   results = JSPropertyProvider(dbgObject, null, "testArray[10].");
   do_check_null(results);
 
   do_print("Test that no suggestions are given if an index is not numerical somewhere in the chain.");
   results = JSPropertyProvider(dbgObject, null, "testArray[0]['propC'][0].");
   do_check_null(results);
 
@@ -59,16 +99,25 @@ function run_test() {
   do_check_null(results);
 
   do_print("Test that suggestions are not given if there is an hyphen in the chain.");
   results = JSPropertyProvider(dbgObject, null, "testHyphenated['prop-A'].");
   do_check_null(results);
 }
 
 /**
+ * A helper that ensures an empty array of results were found.
+ * @param Object aResults
+ *        The results returned by JSPropertyProvider.
+ */
+function test_has_no_results(aResults) {
+  do_check_neq(aResults, null);
+  do_check_eq(aResults.matches.length, 0);
+}
+/**
  * A helper that ensures (required) results were found.
  * @param Object aResults
  *        The results returned by JSPropertyProvider.
  * @param String aRequiredSuggestion
  *        A suggestion that must be found from the results.
  */
 function test_has_result(aResults, aRequiredSuggestion) {
   do_check_neq(aResults, null);
--- a/devtools/shared/webconsole/utils.js
+++ b/devtools/shared/webconsole/utils.js
@@ -7,16 +7,17 @@
 "use strict";
 
 const {Cc, Ci, Cu, components} = require("chrome");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
+loader.lazyImporter(this, "Parser", "resource://gre/modules/devtools/shared/Parser.jsm");
 
 // TODO: Bug 842672 - browser/ imports modules from toolkit/.
 // Note that these are only used in WebConsoleCommands, see $0 and pprint().
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/client/shared/widgets/VariablesView.jsm");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 // Match the function name from the result of toString() or toSource().
 //
@@ -856,29 +857,44 @@ function JSPropertyProvider(aDbgObject, 
 
   // If the current state is not STATE_NORMAL, then we are inside of an string
   // which means that no completion is possible.
   if (beginning.state != STATE_NORMAL) {
     return null;
   }
 
   let completionPart = inputValue.substring(beginning.startPos);
+  let lastDot = completionPart.lastIndexOf(".");
 
   // Don't complete on just an empty string.
   if (completionPart.trim() == "") {
     return null;
   }
 
-  let lastDot = completionPart.lastIndexOf(".");
-  if (lastDot > 0 &&
-      (completionPart[0] == "'" || completionPart[0] == '"') &&
-      completionPart[lastDot - 1] == completionPart[0]) {
-    // We are completing a string literal.
-    let matchProp = completionPart.slice(lastDot + 1);
-    return getMatchedProps(String.prototype, matchProp);
+  // Catch literals like [1,2,3] or "foo" and return the matches from
+  // their prototypes.
+  if (lastDot > 0) {
+    let parser = new Parser();
+    parser.logExceptions = false;
+    let syntaxTree = parser.get(completionPart.slice(0, lastDot));
+    let lastTree = syntaxTree.getLastSyntaxTree();
+    let lastBody = lastTree && lastTree.AST.body[lastTree.AST.body.length - 1];
+
+    // Finding the last expression since we've sliced up until the dot.
+    // If there were parse errors this won't exist.
+    if (lastBody) {
+      let expression = lastBody.expression;
+      let matchProp = completionPart.slice(lastDot + 1);
+      if (expression.type === "ArrayExpression") {
+        return getMatchedProps(Array.prototype, matchProp);
+      } else if (expression.type === "Literal" &&
+                 (typeof expression.value === "string")) {
+        return getMatchedProps(String.prototype, matchProp);
+      }
+    }
   }
 
   // We are completing a variable / a property lookup.
   let properties = completionPart.split(".");
   let matchProp = properties.pop().trimLeft();
   let obj = aDbgObject;
 
   // The first property must be found in the environment if the debugger is