Bug 1622889 - Preview does not show up on expression with optional chaining. r=nchevobbe
authorJason Laster <jlaster@mozilla.com>
Wed, 25 Mar 2020 21:32:41 +0000
changeset 520444 082f545dff2b5c38578e68f27c2863a7238122da
parent 520443 fc67294a32f21330e53c169ec5a9bec8db960185
child 520445 202ac04e9c8d7b1be18197b20dfdabb6f1e83ceb
push id37250
push userdvarga@mozilla.com
push dateThu, 26 Mar 2020 04:04:15 +0000
treeherdermozilla-central@85bae8580dde [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1622889
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1622889 - Preview does not show up on expression with optional chaining. r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D67926
devtools/client/debugger/bin/module-manifest.json
devtools/client/debugger/dist/parser-worker.js
devtools/client/debugger/src/workers/parser/getSymbols.js
devtools/client/debugger/src/workers/parser/tests/__snapshots__/getSymbols.spec.js.snap
devtools/client/debugger/src/workers/parser/tests/fixtures/optional-chaining.js
devtools/client/debugger/src/workers/parser/tests/getSymbols.spec.js
devtools/client/debugger/src/workers/parser/utils/ast.js
devtools/client/debugger/test/mochitest/browser_dbg-preview.js
devtools/client/debugger/test/mochitest/examples/preview.js
devtools/client/shared/source-map/worker.js
--- a/devtools/client/debugger/bin/module-manifest.json
+++ b/devtools/client/debugger/bin/module-manifest.json
@@ -10,17 +10,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "extract-text-webpack-plugin ../../extract-text-webpack-plugin/dist ../../css-loader/index.js??ref--3-1!../../postcss-loader/lib/index.js!../../react-aria-components/src/tabs/tab.css": [
     {
       "modules": {
         "byIdentifier": {
@@ -31,17 +31,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "extract-text-webpack-plugin ../../extract-text-webpack-plugin/dist ../../css-loader/index.js??ref--3-1!../../postcss-loader/lib/index.js!../../react-aria-components/src/tabs/tab-list.css": [
     {
       "modules": {
         "byIdentifier": {
@@ -52,17 +52,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "extract-text-webpack-plugin ../../extract-text-webpack-plugin/dist ../../css-loader/index.js??ref--3-1!../../postcss-loader/lib/index.js!../../devtools-contextmenu/menu.css": [
     {
       "modules": {
         "byIdentifier": {
@@ -73,17 +73,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "extract-text-webpack-plugin ../../extract-text-webpack-plugin/dist ../../css-loader/index.js??ref--3-1!../../postcss-loader/lib/index.js!../../../packages/devtools-components/src/tree.css": [
     {
       "modules": {
         "byIdentifier": {
@@ -94,17 +94,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "extract-text-webpack-plugin ../../extract-text-webpack-plugin/dist ../../css-loader/index.js??ref--3-1!../../postcss-loader/lib/index.js!../../../packages/devtools-reps/src/object-inspector/components/ObjectInspector.css": [
     {
       "modules": {
         "byIdentifier": {
@@ -115,17 +115,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "extract-text-webpack-plugin ../../extract-text-webpack-plugin/dist ../../css-loader/index.js??ref--3-1!../../postcss-loader/lib/index.js!../../../packages/devtools-reps/src/reps/reps.css": [
     {
       "modules": {
         "byIdentifier": {
@@ -136,17 +136,17 @@
           "0": 0,
           "1": 1
         }
       },
       "chunks": {
         "byName": {},
         "byBlocks": {},
         "usedIds": {
-          "1": 1
+          "0": 0
         }
       }
     }
   ],
   "modules": {
     "byIdentifier": {
       "external \"devtools/client/shared/vendor/react-prop-types\"": 0,
       "external \"devtools/client/shared/vendor/react-dom-factories\"": 1,
@@ -665,17 +665,18 @@
       "../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-wasm-dwarf/src/wasmDwarfExpressions.js": 514,
       "../../babel-loader/lib/index.js??ref--1!../../../packages/devtools-source-map/src/utils/assetRootBrowser.js": 515,
       "../../babel-loader/lib/index.js??ref--1!../../devtools-modules/src/async-store-helper.js": 516,
       "../../@babel/types/lib/validators/isPlaceholderType.js": 517,
       "../../@babel/types/lib/definitions/placeholders.js": 518,
       "../../webpack/buildin/harmony-module.js": 519,
       "../../babel-loader/lib/index.js??ref--1!../../devtools-modules/src/saveAs.js": 520,
       "../../punycode/punycode.js": 521,
-      "../../babel-loader/lib/index.js??ref--1!../../../../../shared/dom-node-constants.js": 522
+      "../../babel-loader/lib/index.js??ref--1!../../../../../shared/dom-node-constants.js": 522,
+      "external \"\\n(() => {\\n  importScripts(\\\"resource://devtools/client/shared/vendor/whatwg-url.js\\\");\\n  return { URL }\\n})()\\n\"": 523
     },
     "usedIds": {
       "0": 0,
       "1": 1,
       "2": 2,
       "3": 3,
       "4": 4,
       "5": 5,
@@ -1190,17 +1191,18 @@
       "514": 514,
       "515": 515,
       "516": 516,
       "517": 517,
       "518": 518,
       "519": 519,
       "520": 520,
       "521": 521,
-      "522": 522
+      "522": 522,
+      "523": 523
     }
   },
   "chunks": {
     "byName": {
       "parser-worker": 0,
       "vendors": 1,
       "reps": 2,
       "source-map-worker": 3,
--- a/devtools/client/debugger/dist/parser-worker.js
+++ b/devtools/client/debugger/dist/parser-worker.js
@@ -7075,22 +7075,22 @@ function _parse(code, opts) {
     tokens: true
   });
 }
 
 const sourceOptions = {
   generated: {
     sourceType: "unambiguous",
     tokens: true,
-    plugins: ["objectRestSpread"]
+    plugins: ["objectRestSpread", "optionalChaining", "nullishCoalescingOperator"]
   },
   original: {
     sourceType: "unambiguous",
     tokens: true,
-    plugins: ["jsx", "flow", "doExpressions", "decorators-legacy", "objectRestSpread", "classProperties", "exportDefaultFrom", "exportNamespaceFrom", "asyncGenerators", "functionBind", "functionSent", "dynamicImport", "react-jsx"]
+    plugins: ["jsx", "flow", "doExpressions", "optionalChaining", "nullishCoalescingOperator", "decorators-legacy", "objectRestSpread", "classProperties", "exportDefaultFrom", "exportNamespaceFrom", "asyncGenerators", "functionBind", "functionSent", "dynamicImport", "react-jsx"]
   }
 };
 
 function parse(text, opts) {
   let ast;
 
   if (!text) {
     return;
@@ -15646,17 +15646,17 @@ function extractSymbol(path, symbols, st
       location: {
         start,
         end
       },
       expression: getSnippet(path)
     });
   }
 
-  if (t.isMemberExpression(path)) {
+  if (t.isMemberExpression(path) || t.isOptionalMemberExpression(path)) {
     const {
       start,
       end
     } = path.node.property.loc;
     symbols.memberExpressions.push({
       name: path.node.property.name,
       location: {
         start,
@@ -15829,16 +15829,17 @@ function extractSymbols(sourceId) {
   symbols.framework = (0, _frameworks.getFramework)(symbols);
   return symbols;
 }
 
 function extendSnippet(name, expression, path, prevPath) {
   var _path$node$property, _path$node$property$e;
 
   const computed = path === null || path === void 0 ? void 0 : path.node.computed;
+  const optional = path === null || path === void 0 ? void 0 : path.node.optional;
   const prevComputed = prevPath === null || prevPath === void 0 ? void 0 : prevPath.node.computed;
   const prevArray = t.isArrayExpression(prevPath);
   const array = t.isArrayExpression(path);
   const value = (path === null || path === void 0 ? void 0 : (_path$node$property = path.node.property) === null || _path$node$property === void 0 ? void 0 : (_path$node$property$e = _path$node$property.extra) === null || _path$node$property$e === void 0 ? void 0 : _path$node$property$e.raw) || "";
 
   if (expression === "") {
     if (computed) {
       return name === undefined ? `[${value}]` : `[${name}]`;
@@ -15858,72 +15859,76 @@ function extendSnippet(name, expression,
   if (prevComputed || prevArray) {
     return `${name}${expression}`;
   }
 
   if ((0, _helpers.isComputedExpression)(expression) && name !== undefined) {
     return `${name}${expression}`;
   }
 
+  if (optional) {
+    return `${name}?.${expression}`;
+  }
+
   return `${name}.${expression}`;
 }
 
-function getMemberSnippet(node, expression = "") {
-  if (t.isMemberExpression(node)) {
+function getMemberSnippet(node, expression = "", optional = false) {
+  if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
     const name = node.property.name;
     const snippet = getMemberSnippet(node.object, extendSnippet(name, expression, {
       node
-    }));
+    }), node.optional);
     return snippet;
   }
 
   if (t.isCallExpression(node)) {
     return "";
   }
 
   if (t.isThisExpression(node)) {
     return `this.${expression}`;
   }
 
   if (t.isIdentifier(node)) {
     if ((0, _helpers.isComputedExpression)(expression)) {
       return `${node.name}${expression}`;
     }
 
+    if (optional) {
+      return `${node.name}?.${expression}`;
+    }
+
     return `${node.name}.${expression}`;
   }
 
   return expression;
 }
 
 function getObjectSnippet(path, prevPath, expression = "") {
-  var _path$parentPath;
-
   if (!path) {
     return expression;
   }
 
   const name = path.node.key.name;
   const extendedExpression = extendSnippet(name, expression, path, prevPath);
   const nextPrevPath = path;
-  const nextPath = (_path$parentPath = path.parentPath) === null || _path$parentPath === void 0 ? void 0 : _path$parentPath.parentPath;
+  const nextPath = path.parentPath && path.parentPath.parentPath;
   return getSnippet(nextPath, nextPrevPath, extendedExpression);
 }
 
 function getArraySnippet(path, prevPath, expression) {
-  var _path$parentPath2;
-
   if (!prevPath.parentPath) {
     throw new Error("Assertion failure - path should exist");
   }
 
   const index = `${prevPath.parentPath.containerIndex}`;
   const extendedExpression = extendSnippet(index, expression, path, prevPath);
   const nextPrevPath = path;
-  const nextPath = (_path$parentPath2 = path.parentPath) === null || _path$parentPath2 === void 0 ? void 0 : _path$parentPath2.parentPath;
+  const nextPath = path.parentPath && path.parentPath.parentPath;
   return getSnippet(nextPath, nextPrevPath, extendedExpression);
 }
 
 function getSnippet(path, prevPath, expression = "") {
   if (!path) {
     return expression;
   }
 
@@ -15963,17 +15968,17 @@ function getSnippet(path, prevPath, expr
     return getObjectSnippet(path, prevPath, expression);
   }
 
   if (t.isObjectExpression(path)) {
     const parentPath = prevPath === null || prevPath === void 0 ? void 0 : prevPath.parentPath;
     return getObjectSnippet(parentPath, prevPath, expression);
   }
 
-  if (t.isMemberExpression(path)) {
+  if (t.isMemberExpression(path) || t.isOptionalMemberExpression(path)) {
     return getMemberSnippet(path.node, expression);
   }
 
   if (t.isArrayExpression(path)) {
     if (!prevPath) {
       throw new Error("Assertion failure - path should exist");
     }
 
--- a/devtools/client/debugger/src/workers/parser/getSymbols.js
+++ b/devtools/client/debugger/src/workers/parser/getSymbols.js
@@ -160,17 +160,17 @@ function extractSymbol(path: SimplePath,
     const { start, end, identifierName } = path.node.key.loc;
     symbols.objectProperties.push({
       name: identifierName,
       location: { start, end },
       expression: getSnippet(path),
     });
   }
 
-  if (t.isMemberExpression(path)) {
+  if (t.isMemberExpression(path) || t.isOptionalMemberExpression(path)) {
     const { start, end } = path.node.property.loc;
     symbols.memberExpressions.push({
       name: path.node.property.name,
       location: { start, end },
       expression: getSnippet(path),
       computed: path.node.computed,
     });
   }
@@ -307,16 +307,17 @@ function extractSymbols(sourceId): Symbo
 
 function extendSnippet(
   name: string,
   expression: string,
   path?: { node: Node },
   prevPath?: SimplePath
 ) {
   const computed = path?.node.computed;
+  const optional = path?.node.optional;
   const prevComputed = prevPath?.node.computed;
   const prevArray = t.isArrayExpression(prevPath);
   const array = t.isArrayExpression(path);
   const value = path?.node.property?.extra?.raw || "";
 
   if (expression === "") {
     if (computed) {
       return name === undefined ? `[${value}]` : `[${name}]`;
@@ -334,41 +335,53 @@ function extendSnippet(
   if (prevComputed || prevArray) {
     return `${name}${expression}`;
   }
 
   if (isComputedExpression(expression) && name !== undefined) {
     return `${name}${expression}`;
   }
 
+  if (optional) {
+    return `${name}?.${expression}`;
+  }
+
   return `${name}.${expression}`;
 }
 
-function getMemberSnippet(node: Node, expression: string = "") {
-  if (t.isMemberExpression(node)) {
-    const { name } = node.property;
+function getMemberSnippet(
+  node: Node,
+  expression: string = "",
+  optional = false
+) {
+  if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
+    const name = node.property.name;
     const snippet = getMemberSnippet(
       node.object,
-      extendSnippet(name, expression, { node })
+      extendSnippet(name, expression, { node }),
+      node.optional
     );
     return snippet;
   }
 
   if (t.isCallExpression(node)) {
     return "";
   }
 
   if (t.isThisExpression(node)) {
     return `this.${expression}`;
   }
 
   if (t.isIdentifier(node)) {
     if (isComputedExpression(expression)) {
       return `${node.name}${expression}`;
     }
+    if (optional) {
+      return `${node.name}?.${expression}`;
+    }
     return `${node.name}.${expression}`;
   }
 
   return expression;
 }
 
 function getObjectSnippet(
   path: ?SimplePath,
@@ -454,17 +467,17 @@ function getSnippet(
     return getObjectSnippet(path, prevPath, expression);
   }
 
   if (t.isObjectExpression(path)) {
     const parentPath = prevPath?.parentPath;
     return getObjectSnippet(parentPath, prevPath, expression);
   }
 
-  if (t.isMemberExpression(path)) {
+  if (t.isMemberExpression(path) || t.isOptionalMemberExpression(path)) {
     return getMemberSnippet(path.node, expression);
   }
 
   if (t.isArrayExpression(path)) {
     if (!prevPath) {
       throw new Error("Assertion failure - path should exist");
     }
 
--- a/devtools/client/debugger/src/workers/parser/tests/__snapshots__/getSymbols.spec.js.snap
+++ b/devtools/client/debugger/src/workers/parser/tests/__snapshots__/getSymbols.spec.js.snap
@@ -1248,16 +1248,60 @@ hasJsx: false
 
 hasTypes: false
 
 loading: false
 
 framework: undefined"
 `;
 
+exports[`Parser.getSymbols optional chaining 1`] = `
+"functions:
+
+
+callExpressions:
+
+
+memberExpressions:
+[(2, 5), (2, 6)] obj?.a a
+[(3, 8), (3, 9)] obj?.a?.b b
+[(3, 5), (3, 6)] obj?.a a
+
+objectProperties:
+
+
+comments:
+
+
+identifiers:
+[(1, 6), (1, 9)] obj obj
+[(2, 0), (2, 3)] obj obj
+[(2, 5), (2, 6)] a a
+[(3, 0), (3, 3)] obj obj
+[(3, 5), (3, 6)] a a
+[(3, 8), (3, 9)] b b
+
+classes:
+
+
+imports:
+
+
+literals:
+
+
+hasJsx: false
+
+hasTypes: false
+
+loading: false
+
+framework: undefined"
+`;
+
 exports[`Parser.getSymbols proto 1`] = `
 "functions:
 [(1, 12), (1, 25)]  foo()
 [(3, 12), (3, 20)]  bar()
 [(7, 14), (7, 27)]  initialize() TodoView
 [(8, 2), (10, 3)]  doThing(b) TodoView
 [(11, 10), (13, 3)]  render() TodoView
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/src/workers/parser/tests/fixtures/optional-chaining.js
@@ -0,0 +1,3 @@
+const obj = {};
+obj?.a;
+obj?.a?.b ?? [];
--- a/devtools/client/debugger/src/workers/parser/tests/getSymbols.spec.js
+++ b/devtools/client/debugger/src/workers/parser/tests/getSymbols.spec.js
@@ -26,16 +26,17 @@ cases(
     { name: "proto", file: "proto" },
     { name: "class", file: "class", original: true },
     { name: "var", file: "var" },
     { name: "expression", file: "expression" },
     { name: "allSymbols", file: "allSymbols" },
     { name: "call sites", file: "call-sites" },
     { name: "call expression", file: "callExpressions" },
     { name: "object expressions", file: "object-expressions" },
+    { name: "optional chaining", file: "optional-chaining" },
     {
       name: "finds symbols in an html file",
       file: "parseScriptTags",
       type: "html",
     },
     { name: "component", file: "component", original: true },
     {
       name: "react component",
--- a/devtools/client/debugger/src/workers/parser/utils/ast.js
+++ b/devtools/client/debugger/src/workers/parser/utils/ast.js
@@ -18,25 +18,31 @@ function _parse(code, opts) {
     tokens: true,
   });
 }
 
 const sourceOptions = {
   generated: {
     sourceType: "unambiguous",
     tokens: true,
-    plugins: ["objectRestSpread"],
+    plugins: [
+      "objectRestSpread",
+      "optionalChaining",
+      "nullishCoalescingOperator",
+    ],
   },
   original: {
     sourceType: "unambiguous",
     tokens: true,
     plugins: [
       "jsx",
       "flow",
       "doExpressions",
+      "optionalChaining",
+      "nullishCoalescingOperator",
       "decorators-legacy",
       "objectRestSpread",
       "classProperties",
       "exportDefaultFrom",
       "exportNamespaceFrom",
       "asyncGenerators",
       "functionBind",
       "functionSent",
--- a/devtools/client/debugger/test/mochitest/browser_dbg-preview.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-preview.js
@@ -10,17 +10,17 @@ async function previews(dbg, fnName, pre
   await resume(dbg);
 
   info(`Ran tests for ${fnName}`);
 }
 
 async function testBucketedArray(dbg) {
   const invokeResult = invokeInTab("largeArray");
   await waitForPaused(dbg);
-  const preview = await hoverOnToken(dbg, 33, 10, "popup");
+  const preview = await hoverOnToken(dbg, 34, 10, "popup");
 
   is(
     preview.properties.map(p => p.name).join(" "),
     "[0…99] [100…100] length <prototype>",
     "Popup properties are bucketed"
   );
 
   is(preview.properties[0].meta.endIndex, 99, "first bucket ends at 99");
@@ -40,16 +40,17 @@ add_task(async function() {
     { line: 6, column: 9, expression: "a", result: '""' },
     { line: 7, column: 9, expression: "b", result: "false" },
     { line: 8, column: 9, expression: "c", result: "undefined" },
     { line: 9, column: 9, expression: "d", result: "null" },
   ]);
 
   await previews(dbg, "objects", [
     { line: 27, column: 10, expression: "empty", result: "No properties" },
+    { line: 28, column: 22, expression: "obj?.foo", result: 1 },
   ]);
 
   await previews(dbg, "smalls", [
     { line: 14, column: 9, expression: "a", result: '"..."' },
     { line: 15, column: 9, expression: "b", result: "true" },
     { line: 16, column: 9, expression: "c", result: "1" },
     {
       line: 17,
--- a/devtools/client/debugger/test/mochitest/examples/preview.js
+++ b/devtools/client/debugger/test/mochitest/examples/preview.js
@@ -20,16 +20,17 @@ function smalls() {
 }
 
 function objects() {
   const obj = {
     foo: 1,
   };
 
   const empty = Object.create(null);
+  const foo = obj?.foo;
 
   debugger;
 }
 
 function largeArray() {
   const bs = [];
   for (let i = 0; i <= 100; i++) {
     bs.push({ a: 2, b: { c: 3 } });
--- a/devtools/client/shared/source-map/worker.js
+++ b/devtools/client/shared/source-map/worker.js
@@ -2039,17 +2039,17 @@ exports.encode = function(number) {
  *   webpack:///src/folder/file.js
  *
  * are very common in source maps. For the time being we use a JS
  * implementation in these contexts instead. See
  *
  * * https://bugzilla.mozilla.org/show_bug.cgi?id=1374505
  * * https://bugs.chromium.org/p/chromium/issues/detail?id=734880
  */
-module.exports = __webpack_require__(507).URL;
+module.exports = __webpack_require__(523).URL;
 
 
 /***/ }),
 
 /***/ 402:
 /***/ (function(module, exports, __webpack_require__) {
 
 /* -*- Mode: js; js-indent-level: 2; -*- */
@@ -4114,28 +4114,16 @@ async function getOriginalStackFrames(ge
 }
 
 module.exports = {
   getOriginalStackFrames
 };
 
 /***/ }),
 
-/***/ 507:
-/***/ (function(module, exports) {
-
-module.exports = 
-(() => { 
-  importScripts("resource://devtools/client/shared/vendor/whatwg-url.js");
-  return { URL }
-})()
-;
-
-/***/ }),
-
 /***/ 510:
 /***/ (function(module, exports, __webpack_require__) {
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 const {
   convertToJSON
@@ -4860,16 +4848,28 @@ function setAssetRootURL(assetRoot) {
 }
 
 module.exports = {
   setAssetRootURL
 };
 
 /***/ }),
 
+/***/ 523:
+/***/ (function(module, exports) {
+
+module.exports = 
+(() => {
+  importScripts("resource://devtools/client/shared/vendor/whatwg-url.js");
+  return { URL }
+})()
+;
+
+/***/ }),
+
 /***/ 60:
 /***/ (function(module, exports, __webpack_require__) {
 
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */