mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-07 04:26:32 +08:00
This change makes it easier to run the script from directly `roll_py.deps` without having to fetch the dependencies first. This also makes it easier to track which dependencies our tooling have. Change-Id: I1ce55074ee6a1fffa6f2a2f05a56d0d74b4aca90 Bug: 1255619 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3431346 Reviewed-by: Mathias Bynens <mathias@chromium.org> Commit-Queue: Johan Bay <jobay@chromium.org>
304 lines
8.0 KiB
JavaScript
304 lines
8.0 KiB
JavaScript
import { Type } from "./type.js";
|
|
import { Argument } from "./argument.js";
|
|
import {
|
|
ExtendedAttributes,
|
|
SimpleExtendedAttribute,
|
|
} from "./extended-attributes.js";
|
|
import { Operation } from "./operation.js";
|
|
import { Attribute } from "./attribute.js";
|
|
import { Tokeniser } from "../tokeniser.js";
|
|
|
|
/**
|
|
* @param {string} identifier
|
|
*/
|
|
export function unescape(identifier) {
|
|
return identifier.startsWith("_") ? identifier.slice(1) : identifier;
|
|
}
|
|
|
|
/**
|
|
* Parses comma-separated list
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
* @param {object} args
|
|
* @param {Function} args.parser parser function for each item
|
|
* @param {boolean} [args.allowDangler] whether to allow dangling comma
|
|
* @param {string} [args.listName] the name to be shown on error messages
|
|
*/
|
|
export function list(tokeniser, { parser, allowDangler, listName = "list" }) {
|
|
const first = parser(tokeniser);
|
|
if (!first) {
|
|
return [];
|
|
}
|
|
first.tokens.separator = tokeniser.consume(",");
|
|
const items = [first];
|
|
while (first.tokens.separator) {
|
|
const item = parser(tokeniser);
|
|
if (!item) {
|
|
if (!allowDangler) {
|
|
tokeniser.error(`Trailing comma in ${listName}`);
|
|
}
|
|
break;
|
|
}
|
|
item.tokens.separator = tokeniser.consume(",");
|
|
items.push(item);
|
|
if (!item.tokens.separator) break;
|
|
}
|
|
return items;
|
|
}
|
|
|
|
/**
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
*/
|
|
export function const_value(tokeniser) {
|
|
return (
|
|
tokeniser.consumeKind("decimal", "integer") ||
|
|
tokeniser.consume("true", "false", "Infinity", "-Infinity", "NaN")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {object} token
|
|
* @param {string} token.type
|
|
* @param {string} token.value
|
|
*/
|
|
export function const_data({ type, value }) {
|
|
switch (type) {
|
|
case "decimal":
|
|
case "integer":
|
|
return { type: "number", value };
|
|
case "string":
|
|
return { type: "string", value: value.slice(1, -1) };
|
|
}
|
|
|
|
switch (value) {
|
|
case "true":
|
|
case "false":
|
|
return { type: "boolean", value: value === "true" };
|
|
case "Infinity":
|
|
case "-Infinity":
|
|
return { type: "Infinity", negative: value.startsWith("-") };
|
|
case "[":
|
|
return { type: "sequence", value: [] };
|
|
case "{":
|
|
return { type: "dictionary" };
|
|
default:
|
|
return { type: value };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
*/
|
|
export function primitive_type(tokeniser) {
|
|
function integer_type() {
|
|
const prefix = tokeniser.consume("unsigned");
|
|
const base = tokeniser.consume("short", "long");
|
|
if (base) {
|
|
const postfix = tokeniser.consume("long");
|
|
return new Type({ source, tokens: { prefix, base, postfix } });
|
|
}
|
|
if (prefix) tokeniser.error("Failed to parse integer type");
|
|
}
|
|
|
|
function decimal_type() {
|
|
const prefix = tokeniser.consume("unrestricted");
|
|
const base = tokeniser.consume("float", "double");
|
|
if (base) {
|
|
return new Type({ source, tokens: { prefix, base } });
|
|
}
|
|
if (prefix) tokeniser.error("Failed to parse float type");
|
|
}
|
|
|
|
const { source } = tokeniser;
|
|
const num_type = integer_type(tokeniser) || decimal_type(tokeniser);
|
|
if (num_type) return num_type;
|
|
const base = tokeniser.consume(
|
|
"bigint",
|
|
"boolean",
|
|
"byte",
|
|
"octet",
|
|
"undefined"
|
|
);
|
|
if (base) {
|
|
return new Type({ source, tokens: { base } });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
*/
|
|
export function argument_list(tokeniser) {
|
|
return list(tokeniser, {
|
|
parser: Argument.parse,
|
|
listName: "arguments list",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
* @param {string} typeName
|
|
*/
|
|
export function type_with_extended_attributes(tokeniser, typeName) {
|
|
const extAttrs = ExtendedAttributes.parse(tokeniser);
|
|
const ret = Type.parse(tokeniser, typeName);
|
|
if (ret) autoParenter(ret).extAttrs = extAttrs;
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
* @param {string} typeName
|
|
*/
|
|
export function return_type(tokeniser, typeName) {
|
|
const typ = Type.parse(tokeniser, typeName || "return-type");
|
|
if (typ) {
|
|
return typ;
|
|
}
|
|
const voidToken = tokeniser.consume("void");
|
|
if (voidToken) {
|
|
const ret = new Type({
|
|
source: tokeniser.source,
|
|
tokens: { base: voidToken },
|
|
});
|
|
ret.type = "return-type";
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import("../tokeniser").Tokeniser} tokeniser
|
|
*/
|
|
export function stringifier(tokeniser) {
|
|
const special = tokeniser.consume("stringifier");
|
|
if (!special) return;
|
|
const member =
|
|
Attribute.parse(tokeniser, { special }) ||
|
|
Operation.parse(tokeniser, { special }) ||
|
|
tokeniser.error("Unterminated stringifier");
|
|
return member;
|
|
}
|
|
|
|
/**
|
|
* @param {string} str
|
|
*/
|
|
export function getLastIndentation(str) {
|
|
const lines = str.split("\n");
|
|
// the first line visually binds to the preceding token
|
|
if (lines.length) {
|
|
const match = lines[lines.length - 1].match(/^\s+/);
|
|
if (match) {
|
|
return match[0];
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* @param {string} parentTrivia
|
|
*/
|
|
export function getMemberIndentation(parentTrivia) {
|
|
const indentation = getLastIndentation(parentTrivia);
|
|
const indentCh = indentation.includes("\t") ? "\t" : " ";
|
|
return indentation + indentCh;
|
|
}
|
|
|
|
/**
|
|
* @param {object} def
|
|
* @param {import("./extended-attributes.js").ExtendedAttributes} def.extAttrs
|
|
*/
|
|
export function autofixAddExposedWindow(def) {
|
|
return () => {
|
|
if (def.extAttrs.length) {
|
|
const tokeniser = new Tokeniser("Exposed=Window,");
|
|
const exposed = SimpleExtendedAttribute.parse(tokeniser);
|
|
exposed.tokens.separator = tokeniser.consume(",");
|
|
const existing = def.extAttrs[0];
|
|
if (!/^\s/.test(existing.tokens.name.trivia)) {
|
|
existing.tokens.name.trivia = ` ${existing.tokens.name.trivia}`;
|
|
}
|
|
def.extAttrs.unshift(exposed);
|
|
} else {
|
|
autoParenter(def).extAttrs = ExtendedAttributes.parse(
|
|
new Tokeniser("[Exposed=Window]")
|
|
);
|
|
const trivia = def.tokens.base.trivia;
|
|
def.extAttrs.tokens.open.trivia = trivia;
|
|
def.tokens.base.trivia = `\n${getLastIndentation(trivia)}`;
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the first syntax token for the given IDL object.
|
|
* @param {*} data
|
|
*/
|
|
export function getFirstToken(data) {
|
|
if (data.extAttrs.length) {
|
|
return data.extAttrs.tokens.open;
|
|
}
|
|
if (data.type === "operation" && !data.special) {
|
|
return getFirstToken(data.idlType);
|
|
}
|
|
const tokens = Object.values(data.tokens).sort((x, y) => x.index - y.index);
|
|
return tokens[0];
|
|
}
|
|
|
|
/**
|
|
* @template T
|
|
* @param {T[]} array
|
|
* @param {(item: T) => boolean} predicate
|
|
*/
|
|
export function findLastIndex(array, predicate) {
|
|
const index = array.slice().reverse().findIndex(predicate);
|
|
if (index === -1) {
|
|
return index;
|
|
}
|
|
return array.length - index - 1;
|
|
}
|
|
|
|
/**
|
|
* Returns a proxy that auto-assign `parent` field.
|
|
* @template T
|
|
* @param {T} data
|
|
* @param {*} [parent] The object that will be assigned to `parent`.
|
|
* If absent, it will be `data` by default.
|
|
* @return {T}
|
|
*/
|
|
export function autoParenter(data, parent) {
|
|
if (!parent) {
|
|
// Defaults to `data` unless specified otherwise.
|
|
parent = data;
|
|
}
|
|
if (!data) {
|
|
// This allows `autoParenter(undefined)` which again allows
|
|
// `autoParenter(parse())` where the function may return nothing.
|
|
return data;
|
|
}
|
|
return new Proxy(data, {
|
|
get(target, p) {
|
|
const value = target[p];
|
|
if (Array.isArray(value)) {
|
|
// Wraps the array so that any added items will also automatically
|
|
// get their `parent` values.
|
|
return autoParenter(value, target);
|
|
}
|
|
return value;
|
|
},
|
|
set(target, p, value) {
|
|
target[p] = value;
|
|
if (!value) {
|
|
return true;
|
|
} else if (Array.isArray(value)) {
|
|
// Assigning an array will add `parent` to its items.
|
|
for (const item of value) {
|
|
if (typeof item.parent !== "undefined") {
|
|
item.parent = parent;
|
|
}
|
|
}
|
|
} else if (typeof value.parent !== "undefined") {
|
|
value.parent = parent;
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
}
|