зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1616306 - Merge splitInputAtLastPropertyAccess into analyzeInputString, r=nchevobbe
Differential Revision: https://phabricator.services.mozilla.com/D63917 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f45a693f18
Коммит
f61414d70a
|
@ -112,11 +112,12 @@ function JSPropertyProvider({
|
|||
}
|
||||
|
||||
let {
|
||||
lastStatement,
|
||||
isElementAccess,
|
||||
mainExpression,
|
||||
matchProp,
|
||||
isPropertyAccess,
|
||||
} = splitInputAtLastPropertyAccess(inputAnalysis);
|
||||
const { lastStatement, isElementAccess } = inputAnalysis;
|
||||
} = inputAnalysis;
|
||||
|
||||
// Eagerly evaluate the main expression and return the results properties.
|
||||
// e.g. `obj.func().a` will evaluate `obj.func()` and return properties matching `a`.
|
||||
|
@ -393,62 +394,6 @@ function shouldInputBeAutocompleted(inputAnalysisState) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that will process the result of analyzeInputString and return useful information
|
||||
* about the expression.
|
||||
*
|
||||
* @param {Object} inputAnalysisState: Result of analyzeInputString.
|
||||
* @returns {Object} An object of the following shape:
|
||||
* - mainExpression: The part of the expression before any property access,
|
||||
* (e.g. `a.b` if expression is `a.b.`)
|
||||
* - matchProp: The part of the expression that should match the properties
|
||||
* on the mainExpression (e.g. `que` when expression is `document.body.que`)
|
||||
* - isPropertyAccess: true of the expression indicate that the
|
||||
* expression has a property access (e.g. `a.x` or `a["x"`).
|
||||
*/
|
||||
function splitInputAtLastPropertyAccess(inputAnalysisState) {
|
||||
const { lastStatement, isElementAccess } = inputAnalysisState;
|
||||
const lastDotIndex = lastStatement.lastIndexOf(".");
|
||||
const lastOpeningBracketIndex = isElementAccess
|
||||
? lastStatement.lastIndexOf("[")
|
||||
: -1;
|
||||
const lastCompletionCharIndex = Math.max(
|
||||
lastDotIndex,
|
||||
lastOpeningBracketIndex
|
||||
);
|
||||
|
||||
const matchProp = lastStatement.slice(lastCompletionCharIndex + 1).trimLeft();
|
||||
const matchPropPrefix = lastStatement.slice(0, lastCompletionCharIndex + 1);
|
||||
|
||||
const optionalChainingElementAccessRegex = /\s*\?\.\s*\[\s*$/;
|
||||
const isPropertyAccess = lastCompletionCharIndex > 0;
|
||||
|
||||
let mainExpression;
|
||||
if (!isPropertyAccess) {
|
||||
mainExpression = matchProp;
|
||||
} else if (
|
||||
isElementAccess &&
|
||||
optionalChainingElementAccessRegex.test(matchPropPrefix)
|
||||
) {
|
||||
// Strip the optional chaining operator at the end;
|
||||
mainExpression = matchPropPrefix.replace(
|
||||
optionalChainingElementAccessRegex,
|
||||
""
|
||||
);
|
||||
} else if (!isElementAccess && matchPropPrefix.endsWith("?.")) {
|
||||
mainExpression = matchPropPrefix.slice(0, matchPropPrefix.length - 2);
|
||||
} else {
|
||||
mainExpression = matchPropPrefix.slice(0, matchPropPrefix.length - 1);
|
||||
}
|
||||
mainExpression = mainExpression.trim();
|
||||
|
||||
return {
|
||||
mainExpression,
|
||||
matchProp,
|
||||
isPropertyAccess,
|
||||
};
|
||||
}
|
||||
|
||||
function hasArrayIndex(str) {
|
||||
return /\[\d+\]$/.test(str);
|
||||
}
|
||||
|
@ -494,6 +439,14 @@ const OPERATOR_CHARS_SET = new Set(";,:=<>+-*%|&^~!".split(""));
|
|||
* lastStatement: the last statement in the string,
|
||||
* isElementAccess: boolean that indicates if the lastStatement has an open
|
||||
* element access (e.g. `x["match`).
|
||||
* isPropertyAccess: boolean indicating if we are accessing property
|
||||
* (e.g `true` in `var a = {b: 1};a.b`)
|
||||
* matchProp: The part of the expression that should match the properties
|
||||
* on the mainExpression (e.g. `que` when expression is `document.body.que`)
|
||||
* mainExpression: The part of the expression before any property access,
|
||||
* (e.g. `a.b` if expression is `a.b.`)
|
||||
* expressionBeforePropertyAccess: The part of the expression before property access
|
||||
* (e.g `var a = {b: 1};a` if expression is `var a = {b: 1};a.b`)
|
||||
* }
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
|
@ -503,8 +456,9 @@ function analyzeInputString(str) {
|
|||
let state = STATE_NORMAL;
|
||||
let previousNonWhitespaceChar;
|
||||
let lastStatement = "";
|
||||
let currentIndex = -1;
|
||||
let dotIndex;
|
||||
let pendingWhitespaceChars = "";
|
||||
|
||||
const TIMEOUT = 2500;
|
||||
const startingTime = Date.now();
|
||||
|
||||
|
@ -520,9 +474,9 @@ function analyzeInputString(str) {
|
|||
};
|
||||
}
|
||||
|
||||
currentIndex += 1;
|
||||
let resetLastStatement = false;
|
||||
const isWhitespaceChar = c.trim() === "";
|
||||
|
||||
switch (state) {
|
||||
// Normal JS state.
|
||||
case STATE_NORMAL:
|
||||
|
@ -532,6 +486,11 @@ function analyzeInputString(str) {
|
|||
lastStatement = "";
|
||||
}
|
||||
|
||||
// Storing the index of dot of the input string
|
||||
if (c === ".") {
|
||||
dotIndex = currentIndex;
|
||||
}
|
||||
|
||||
// If the last characters were spaces, and the current one is not.
|
||||
if (pendingWhitespaceChars && !isWhitespaceChar) {
|
||||
// If we have a legitimate property/element access, or potential optional
|
||||
|
@ -576,6 +535,7 @@ function analyzeInputString(str) {
|
|||
bodyStack.push({
|
||||
token: c,
|
||||
lastStatement,
|
||||
index: currentIndex,
|
||||
});
|
||||
// And we compute a new statement.
|
||||
resetLastStatement = true;
|
||||
|
@ -675,6 +635,8 @@ function analyzeInputString(str) {
|
|||
// If we're not dealing with optional chaining (?.), it means we have a ternary,
|
||||
// so we are starting a new statement that includes the current character.
|
||||
lastStatement = "";
|
||||
} else {
|
||||
dotIndex = currentIndex;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -682,7 +644,6 @@ function analyzeInputString(str) {
|
|||
if (!isWhitespaceChar) {
|
||||
previousNonWhitespaceChar = c;
|
||||
}
|
||||
|
||||
if (resetLastStatement) {
|
||||
lastStatement = "";
|
||||
} else {
|
||||
|
@ -698,9 +659,12 @@ function analyzeInputString(str) {
|
|||
}
|
||||
|
||||
let isElementAccess = false;
|
||||
let lastOpeningBracketIndex = -1;
|
||||
if (bodyStack.length === 1 && bodyStack[0].token === "[") {
|
||||
lastStatement = bodyStack[0].lastStatement;
|
||||
lastOpeningBracketIndex = bodyStack[0].index;
|
||||
isElementAccess = true;
|
||||
|
||||
if (
|
||||
state === STATE_DQUOTE ||
|
||||
state === STATE_QUOTE ||
|
||||
|
@ -713,10 +677,64 @@ function analyzeInputString(str) {
|
|||
lastStatement = "";
|
||||
}
|
||||
|
||||
const lastCompletionCharIndex = isElementAccess
|
||||
? lastOpeningBracketIndex
|
||||
: dotIndex;
|
||||
|
||||
const stringBeforeLastCompletionChar = str.slice(0, lastCompletionCharIndex);
|
||||
|
||||
const isPropertyAccess =
|
||||
lastCompletionCharIndex && lastCompletionCharIndex > 0;
|
||||
|
||||
// Compute `isOptionalAccess`, so that we can use it
|
||||
// later for computing `expressionBeforePropertyAccess`.
|
||||
//Check `?.` before `[` for element access ( e.g `a?.["b` or `a ?. ["b` )
|
||||
// and `?` before `.` for regular property access ( e.g `a?.b` or `a ?. b` )
|
||||
const optionalElementAccessRegex = /\?\.\s*$/;
|
||||
const isOptionalAccess = isElementAccess
|
||||
? optionalElementAccessRegex.test(stringBeforeLastCompletionChar)
|
||||
: isPropertyAccess &&
|
||||
str.slice(lastCompletionCharIndex - 1, lastCompletionCharIndex + 1) ===
|
||||
"?.";
|
||||
|
||||
// Get the filtered string for the properties (e.g if `document.qu` then `qu`)
|
||||
const matchProp = isPropertyAccess
|
||||
? str.slice(lastCompletionCharIndex + 1).trimLeft()
|
||||
: null;
|
||||
|
||||
const expressionBeforePropertyAccess = isPropertyAccess
|
||||
? str.slice(
|
||||
0,
|
||||
// For optional access, we can take all the chars before the last "?" char.
|
||||
isOptionalAccess
|
||||
? stringBeforeLastCompletionChar.lastIndexOf("?")
|
||||
: lastCompletionCharIndex
|
||||
)
|
||||
: str;
|
||||
|
||||
let mainExpression = lastStatement;
|
||||
if (isPropertyAccess) {
|
||||
if (isOptionalAccess) {
|
||||
// Strip anything before the last `?`.
|
||||
mainExpression = mainExpression.slice(0, mainExpression.lastIndexOf("?"));
|
||||
} else {
|
||||
mainExpression = mainExpression.slice(
|
||||
0,
|
||||
-1 * (str.length - lastCompletionCharIndex)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mainExpression = mainExpression.trim();
|
||||
|
||||
return {
|
||||
state,
|
||||
lastStatement,
|
||||
isElementAccess,
|
||||
isPropertyAccess,
|
||||
expressionBeforePropertyAccess,
|
||||
lastStatement,
|
||||
mainExpression,
|
||||
matchProp,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1138,3 +1156,6 @@ exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
|
|||
|
||||
// Export a version that will throw (for tests)
|
||||
exports.FallibleJSPropertyProvider = JSPropertyProvider;
|
||||
|
||||
// Export analyzeInputString (for tests)
|
||||
exports.analyzeInputString = analyzeInputString;
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
"use strict";
|
||||
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
||||
const {
|
||||
analyzeInputString,
|
||||
} = require("devtools/shared/webconsole/js-property-provider");
|
||||
|
||||
add_task(() => {
|
||||
const tests = [
|
||||
{
|
||||
desc: "simple property access",
|
||||
input: `var a = {b: 1};a.b`,
|
||||
expected: {
|
||||
isElementAccess: false,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `var a = {b: 1};a`,
|
||||
lastStatement: "a.b",
|
||||
mainExpression: `a`,
|
||||
matchProp: `b`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "deep property access",
|
||||
input: `a.b.c`,
|
||||
expected: {
|
||||
isElementAccess: false,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a.b`,
|
||||
lastStatement: "a.b.c",
|
||||
mainExpression: `a.b`,
|
||||
matchProp: `c`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "element access",
|
||||
input: `a["b`,
|
||||
expected: {
|
||||
isElementAccess: true,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a`,
|
||||
lastStatement: `a["b`,
|
||||
mainExpression: `a`,
|
||||
matchProp: `"b`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "element access without quotes",
|
||||
input: `a[b`,
|
||||
expected: {
|
||||
isElementAccess: true,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a`,
|
||||
lastStatement: `a[b`,
|
||||
mainExpression: `a`,
|
||||
matchProp: `b`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "simple optional chaining access",
|
||||
input: `a?.b`,
|
||||
expected: {
|
||||
isElementAccess: false,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a`,
|
||||
lastStatement: `a?.b`,
|
||||
mainExpression: `a`,
|
||||
matchProp: `b`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "deep optional chaining access",
|
||||
input: `a?.b?.c`,
|
||||
expected: {
|
||||
isElementAccess: false,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a?.b`,
|
||||
lastStatement: `a?.b?.c`,
|
||||
mainExpression: `a?.b`,
|
||||
matchProp: `c`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "optional chaining element access",
|
||||
input: `a?.["b`,
|
||||
expected: {
|
||||
isElementAccess: true,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a`,
|
||||
lastStatement: `a?.["b`,
|
||||
mainExpression: `a`,
|
||||
matchProp: `"b`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "optional chaining element access without quotes",
|
||||
input: `a?.[b`,
|
||||
expected: {
|
||||
isElementAccess: true,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `a`,
|
||||
lastStatement: `a?.[b`,
|
||||
mainExpression: `a`,
|
||||
matchProp: `b`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "deep optional chaining element access with quotes",
|
||||
input: `var a = {b: 1, c: ["."]}; a?.["b"]?.c?.["d[.`,
|
||||
expected: {
|
||||
isElementAccess: true,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `var a = {b: 1, c: ["."]}; a?.["b"]?.c`,
|
||||
lastStatement: `a?.["b"]?.c?.["d[.`,
|
||||
mainExpression: `a?.["b"]?.c`,
|
||||
matchProp: `"d[.`,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "literal arrays with newline",
|
||||
input: `[1,2,3,\n4\n].`,
|
||||
expected: {
|
||||
isElementAccess: false,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `[1,2,3,\n4\n]`,
|
||||
lastStatement: `[1,2,3,4].`,
|
||||
mainExpression: `[1,2,3,4]`,
|
||||
matchProp: ``,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "number literal with newline",
|
||||
input: `1\n.`,
|
||||
expected: {
|
||||
isElementAccess: false,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `1\n`,
|
||||
lastStatement: `1\n.`,
|
||||
mainExpression: `1`,
|
||||
matchProp: ``,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "optional chaining operator with spaces",
|
||||
input: `test ?. ["propA"] ?. [0] ?. ["propB"] ?. ['to`,
|
||||
expected: {
|
||||
isElementAccess: true,
|
||||
isPropertyAccess: true,
|
||||
expressionBeforePropertyAccess: `test ?. ["propA"] ?. [0] ?. ["propB"] `,
|
||||
lastStatement: `test ?. ["propA"] ?. [0] ?. ["propB"] ?. ['to`,
|
||||
mainExpression: `test ?. ["propA"] ?. [0] ?. ["propB"]`,
|
||||
matchProp: `'to`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { input, desc, expected } of tests) {
|
||||
const result = analyzeInputString(input);
|
||||
for (const [key, value] of Object.entries(expected)) {
|
||||
Assert.equal(value, result[key], `${desc} | ${key} has expected value`);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -5,6 +5,7 @@ firefox-appdir = browser
|
|||
skip-if = toolkit == 'android'
|
||||
support-files =
|
||||
|
||||
[test_analyze_input_string.js]
|
||||
[test_js_property_provider.js]
|
||||
[test_network_helper.js]
|
||||
[test_security-info-certificate.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче