From 152974a8fe4ea4a3bcbf1acc9f0eee7e1101216d Mon Sep 17 00:00:00 2001 From: FranklinDM Date: Wed, 4 May 2022 01:20:37 +0800 Subject: Issue #1658 - Part 7: Implement support for optional chaining in console autocomplete This works by stripping the optional chaining characters from the completion part variable, allowing the developer tools' parser to proceed as if it were a regular, non-optional expression. Tests were partially based on: https://bugzilla.mozilla.org/show_bug.cgi?id=1594009 Tests for features that do not apply to our version of developer tools (e.g. autocomplete for integer literals, ignoring spaces between property accessors, etc) were excluded. --- devtools/shared/webconsole/js-property-provider.js | 16 +++++++++++ .../test/unit/test_js_property_provider.js | 32 ++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/devtools/shared/webconsole/js-property-provider.js b/devtools/shared/webconsole/js-property-provider.js index dff2af50a6..64b78673e0 100644 --- a/devtools/shared/webconsole/js-property-provider.js +++ b/devtools/shared/webconsole/js-property-provider.js @@ -178,6 +178,22 @@ function JSPropertyProvider(dbgObject, anEnvironment, inputValue, cursor) { } let completionPart = inputValue.substring(beginning.startPos); + + // Strip optional chaining characters from completion part, which we check + // by looking if it has ?. and if there are no digits (marker for ternary). + let optionalChainRegex = /\?\./g; + let optionalElemAccessRegex = /\?\.\[/g; + let digitRegex = /\?\.\d/; + // Handle optional element access + if (optionalElemAccessRegex.test(completionPart)) { + completionPart = completionPart.replace(optionalElemAccessRegex, "["); + } + // Handle optional chaining characters + if (optionalChainRegex.test(completionPart) && + !digitRegex.test(completionPart)) { + completionPart = completionPart.replace(optionalChainRegex, "."); + } + let lastDot = completionPart.lastIndexOf("."); // Don't complete on just an empty string. diff --git a/devtools/shared/webconsole/test/unit/test_js_property_provider.js b/devtools/shared/webconsole/test/unit/test_js_property_provider.js index c360cf96d9..53fca33081 100644 --- a/devtools/shared/webconsole/test/unit/test_js_property_provider.js +++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js @@ -145,6 +145,38 @@ function runChecks(dbgObject, dbgEnv) { do_print("Test that suggestions are not given if there is an hyphen in the chain."); results = JSPropertyProvider(dbgObject, dbgEnv, "testHyphenated['prop-A']."); do_check_null(results); + + do_print("Test that expression with optional chaining operator are completed"); + results = JSPropertyProvider(dbgObject, dbgEnv, "testObject?.prop"); + test_has_result(results, "propA"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "testObject?.propA[0]?.propB?.to"); + test_has_result(results, "toString"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "testObject?.propA?.[0]?.propB?.to"); + test_has_result(results, "toString"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3]?."); + test_has_result(results, "indexOf"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'?."); + test_has_result(results, "charAt"); + + // Check this doesn't throw since `propC` is not defined. + results = JSPropertyProvider(dbgObject, dbgEnv, "testObject?.propC?.this?.does?.not?.exist?.d"); + + // Test more ternary + results = JSPropertyProvider(dbgObject, dbgEnv, "true ? t"); + test_has_result(results, "testObject"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "true ?? t"); + test_has_result(results, "testObject"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "true ? /* comment */ t"); + test_has_result(results, "testObject"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "true?