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:
Aman Verma 2020-03-18 10:43:34 +00:00
Родитель f45a693f18
Коммит f61414d70a
3 изменённых файлов: 248 добавлений и 62 удалений

Просмотреть файл

@ -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]