debug-server-next
Version:
Dev server for hippy-core.
499 lines (498 loc) • 18.4 kB
JavaScript
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export const escapeCharacters = (inputString, charsToEscape) => {
let foundChar = false;
for (let i = 0; i < charsToEscape.length; ++i) {
if (inputString.indexOf(charsToEscape.charAt(i)) !== -1) {
foundChar = true;
break;
}
}
if (!foundChar) {
return String(inputString);
}
let result = '';
for (let i = 0; i < inputString.length; ++i) {
if (charsToEscape.indexOf(inputString.charAt(i)) !== -1) {
result += '\\';
}
result += inputString.charAt(i);
}
return result;
};
export const tokenizeFormatString = function (formatString, formatters) {
const tokens = [];
function addStringToken(str) {
if (!str) {
return;
}
if (tokens.length && tokens[tokens.length - 1].type === "string" /* STRING */) {
tokens[tokens.length - 1].value += str;
}
else {
tokens.push({
type: "string" /* STRING */,
value: str,
});
}
}
function addSpecifierToken(specifier, precision, substitutionIndex) {
tokens.push({ type: "specifier" /* SPECIFIER */, specifier, precision, substitutionIndex, value: undefined });
}
function addAnsiColor(code) {
const types = { 3: 'color', 9: 'colorLight', 4: 'bgColor', 10: 'bgColorLight' };
const colorCodes = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightGray', '', 'default'];
const colorCodesLight = ['darkGray', 'lightRed', 'lightGreen', 'lightYellow', 'lightBlue', 'lightMagenta', 'lightCyan', 'white', ''];
const colors = { color: colorCodes, colorLight: colorCodesLight, bgColor: colorCodes, bgColorLight: colorCodesLight };
const type = types[Math.floor(code / 10)];
if (!type) {
return;
}
const color = colors[type][code % 10];
if (!color) {
return;
}
tokens.push({
type: "specifier" /* SPECIFIER */,
specifier: 'c',
value: { description: (type.startsWith('bg') ? 'background : ' : 'color: ') + color },
precision: undefined,
substitutionIndex: undefined,
});
}
let textStart = 0;
let substitutionIndex = 0;
const re = new RegExp(`%%|%(?:(\\d+)\\$)?(?:\\.(\\d*))?([${Object.keys(formatters).join('')}])|\\u001b\\[(\\d+)m`, 'g');
for (let match = re.exec(formatString); match !== null; match = re.exec(formatString)) {
const matchStart = match.index;
if (matchStart > textStart) {
addStringToken(formatString.substring(textStart, matchStart));
}
if (match[0] === '%%') {
addStringToken('%');
}
else if (match[0].startsWith('%')) {
const [, substitionString, precisionString, specifierString] = match;
if (substitionString && Number(substitionString) > 0) {
substitutionIndex = Number(substitionString) - 1;
}
const precision = precisionString ? Number(precisionString) : -1;
addSpecifierToken(specifierString, precision, substitutionIndex);
++substitutionIndex;
}
else {
const code = Number(match[4]);
addAnsiColor(code);
}
textStart = matchStart + match[0].length;
}
addStringToken(formatString.substring(textStart));
return tokens;
};
export const format = function (formatString, substitutions, formatters, initialValue, append, tokenizedFormat) {
if (!formatString || ((!substitutions || !substitutions.length) && formatString.search(/\u001b\[(\d+)m/) === -1)) {
return { formattedResult: append(initialValue, formatString), unusedSubstitutions: substitutions };
}
function prettyFunctionName() {
return 'String.format("' + formatString + '", "' + Array.prototype.join.call(substitutions, '", "') + '")';
}
function warn(msg) {
console.warn(prettyFunctionName() + ': ' + msg);
}
function error(msg) {
console.error(prettyFunctionName() + ': ' + msg);
}
let result = initialValue;
const tokens = tokenizedFormat || tokenizeFormatString(formatString, formatters);
const usedSubstitutionIndexes = {};
const actualSubstitutions = substitutions || [];
for (const token of tokens) {
if (token.type === "string" /* STRING */) {
result = append(result, token.value);
continue;
}
if (token.type !== "specifier" /* SPECIFIER */) {
error('Unknown token type "' + token.type + '" found.');
continue;
}
if (!token.value && token.substitutionIndex !== undefined &&
token.substitutionIndex >= actualSubstitutions.length) {
// If there are not enough substitutions for the current substitutionIndex
// just output the format specifier literally and move on.
error('not enough substitution arguments. Had ' + actualSubstitutions.length + ' but needed ' +
(token.substitutionIndex + 1) + ', so substitution was skipped.');
result = append(result, '%' + ((token.precision !== undefined && token.precision > -1) ? token.precision : '') + token.specifier);
continue;
}
if (!token.value && token.substitutionIndex !== undefined) {
usedSubstitutionIndexes[token.substitutionIndex] = true;
}
if (token.specifier === undefined || !(token.specifier in formatters)) {
// Encountered an unsupported format character, treat as a string.
warn('unsupported format character \u201C' + token.specifier + '\u201D. Treating as a string.');
const stringToAppend = (token.value || token.substitutionIndex === undefined) ?
'' :
String(actualSubstitutions[token.substitutionIndex]);
result = append(result, stringToAppend);
continue;
}
const formatter = formatters[token.specifier];
const valueToFormat = token.value ||
(token.substitutionIndex !== undefined ? actualSubstitutions[token.substitutionIndex] : undefined);
const stringToAppend = formatter(valueToFormat, token);
result = append(result, stringToAppend);
}
const unusedSubstitutions = [];
for (let i = 0; i < actualSubstitutions.length; ++i) {
if (i in usedSubstitutionIndexes) {
continue;
}
unusedSubstitutions.push(actualSubstitutions[i]);
}
return { formattedResult: result, unusedSubstitutions: unusedSubstitutions };
};
export const standardFormatters = {
d: function (substitution) {
return (!isNaN(substitution) ? substitution : 0);
},
f: function (substitution, token) {
if (substitution && typeof substitution === 'number' && token.precision !== undefined && token.precision > -1) {
substitution = substitution.toFixed(token.precision);
}
const precision = (token.precision !== undefined && token.precision > -1) ? Number(0).toFixed(token.precision) : '0';
return !isNaN(substitution) ? substitution : precision;
},
s: function (substitution) {
return substitution;
},
};
const toHexadecimal = (charCode, padToLength) => {
return charCode.toString(16).toUpperCase().padStart(padToLength, '0');
};
// Remember to update the third group in the regexps patternsToEscape and
// patternsToEscapePlusSingleQuote when adding new entries in this map.
const escapedReplacements = new Map([
['\b', '\\b'],
['\f', '\\f'],
['\n', '\\n'],
['\r', '\\r'],
['\t', '\\t'],
['\v', '\\v'],
['\'', '\\\''],
['<!--', '<\\!--'],
['<script', '<\\script'],
['</script', '<\\/script'],
]);
export const formatAsJSLiteral = (content) => {
const patternsToEscape = /(\p{Control})|(\p{Surrogate})|(<(?:!--|\/?script))/gu;
const patternsToEscapePlusSingleQuote = /(\p{Control})|(\p{Surrogate})|(<(?:!--|\/?script)|')/gu;
const escapePattern = (match, controlChar, loneSurrogate, pattern) => {
if (controlChar) {
if (escapedReplacements.has(controlChar)) {
// @ts-ignore https://github.com/microsoft/TypeScript/issues/13086
return escapedReplacements.get(controlChar);
}
const twoDigitHex = toHexadecimal(controlChar.charCodeAt(0), 2);
return '\\x' + twoDigitHex;
}
if (loneSurrogate) {
const fourDigitHex = toHexadecimal(loneSurrogate.charCodeAt(0), 4);
return '\\u' + fourDigitHex;
}
if (pattern) {
return escapedReplacements.get(pattern) || '';
}
return match;
};
let escapedContent = '';
let quote = '';
if (!content.includes('\'')) {
quote = '\'';
escapedContent = content.replaceAll(patternsToEscape, escapePattern);
}
else if (!content.includes('"')) {
quote = '"';
escapedContent = content.replaceAll(patternsToEscape, escapePattern);
}
else if (!content.includes('`') && !content.includes('${')) {
quote = '`';
escapedContent = content.replaceAll(patternsToEscape, escapePattern);
}
else {
quote = '\'';
escapedContent = content.replaceAll(patternsToEscapePlusSingleQuote, escapePattern);
}
return `${quote}${escapedContent}${quote}`;
};
export const vsprintf = function (formatString, substitutions) {
return format(formatString, substitutions, standardFormatters, '', (a, b) => a + b).formattedResult;
};
export const sprintf = function (format, ...varArg) {
return vsprintf(format, varArg);
};
export const toBase64 = (inputString) => {
/* note to the reader: we can't use btoa here because we need to
* support Unicode correctly. See the test cases for this function and
* also
* https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
*/
function encodeBits(b) {
return b < 26 ? b + 65 : b < 52 ? b + 71 : b < 62 ? b - 4 : b === 62 ? 43 : b === 63 ? 47 : 65;
}
const encoder = new TextEncoder();
const data = encoder.encode(inputString.toString());
const n = data.length;
let encoded = '';
if (n === 0) {
return encoded;
}
let shift;
let v = 0;
for (let i = 0; i < n; i++) {
shift = i % 3;
v |= data[i] << (16 >>> shift & 24);
if (shift === 2) {
encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), encodeBits(v & 63));
v = 0;
}
}
if (shift === 0) {
encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), 61, 61);
}
else if (shift === 1) {
encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>> 6 & 63), 61);
}
return encoded;
};
export const findIndexesOfSubString = (inputString, searchString) => {
const matches = [];
let i = inputString.indexOf(searchString);
while (i !== -1) {
matches.push(i);
i = inputString.indexOf(searchString, i + searchString.length);
}
return matches;
};
export const findLineEndingIndexes = (inputString) => {
const endings = findIndexesOfSubString(inputString, '\n');
endings.push(inputString.length);
return endings;
};
export const isWhitespace = (inputString) => {
return /^\s*$/.test(inputString);
};
export const trimURL = (url, baseURLDomain) => {
let result = url.replace(/^(https|http|file):\/\//i, '');
if (baseURLDomain) {
if (result.toLowerCase().startsWith(baseURLDomain.toLowerCase())) {
result = result.substr(baseURLDomain.length);
}
}
return result;
};
export const collapseWhitespace = (inputString) => {
return inputString.replace(/[\s\xA0]+/g, ' ');
};
export const reverse = (inputString) => {
return inputString.split('').reverse().join('');
};
export const replaceControlCharacters = (inputString) => {
// Replace C0 and C1 control character sets with replacement character.
// Do not replace '\t', \n' and '\r'.
return inputString.replace(/[\0-\x08\x0B\f\x0E-\x1F\x80-\x9F]/g, '\uFFFD');
};
export const countWtf8Bytes = (inputString) => {
let count = 0;
for (let i = 0; i < inputString.length; i++) {
const c = inputString.charCodeAt(i);
if (c <= 0x7F) {
count++;
}
else if (c <= 0x07FF) {
count += 2;
}
else if (c < 0xD800 || 0xDFFF < c) {
count += 3;
}
else {
if (c <= 0xDBFF && i + 1 < inputString.length) {
// The current character is a leading surrogate, and there is a
// next character.
const next = inputString.charCodeAt(i + 1);
if (0xDC00 <= next && next <= 0xDFFF) {
// The next character is a trailing surrogate, meaning this
// is a surrogate pair.
count += 4;
i++;
continue;
}
}
count += 3;
}
}
return count;
};
export const stripLineBreaks = (inputStr) => {
return inputStr.replace(/(\r)?\n/g, '');
};
export const toTitleCase = (inputStr) => {
return inputStr.substring(0, 1).toUpperCase() + inputStr.substring(1);
};
export const removeURLFragment = (inputStr) => {
const url = new URL(inputStr);
url.hash = '';
return url.toString();
};
const SPECIAL_REGEX_CHARACTERS = '^[]{}()\\.^$*+?|-,';
export const regexSpecialCharacters = function () {
return SPECIAL_REGEX_CHARACTERS;
};
export const filterRegex = function (query) {
let regexString = '';
for (let i = 0; i < query.length; ++i) {
let c = query.charAt(i);
if (SPECIAL_REGEX_CHARACTERS.indexOf(c) !== -1) {
c = '\\' + c;
}
if (i) {
regexString += '[^\\0' + c + ']*';
}
regexString += c;
}
return new RegExp(regexString, 'i');
};
export const createSearchRegex = function (query, caseSensitive, isRegex) {
const regexFlags = caseSensitive ? 'g' : 'gi';
let regexObject;
if (isRegex) {
try {
regexObject = new RegExp(query, regexFlags);
}
catch (e) {
// Silent catch.
}
}
if (!regexObject) {
regexObject = self.createPlainTextSearchRegex(query, regexFlags);
}
return regexObject;
};
export const caseInsensetiveComparator = function (a, b) {
a = a.toUpperCase();
b = b.toUpperCase();
if (a === b) {
return 0;
}
return a > b ? 1 : -1;
};
export const hashCode = function (string) {
if (!string) {
return 0;
}
// Hash algorithm for substrings is described in "Über die Komplexität der Multiplikation in
// eingeschränkten Branchingprogrammmodellen" by Woelfe.
// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
const p = ((1 << 30) * 4 - 5); // prime: 2^32 - 5
const z = 0x5033d967; // 32 bits from random.org
const z2 = 0x59d2f15d; // random odd 32 bit number
let s = 0;
let zi = 1;
for (let i = 0; i < string.length; i++) {
const xi = string.charCodeAt(i) * z2;
s = (s + zi * xi) % p;
zi = (zi * z) % p;
}
s = (s + zi * (p - 1)) % p;
return Math.abs(s | 0);
};
export const compare = (a, b) => {
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
};
export const trimMiddle = (str, maxLength) => {
if (str.length <= maxLength) {
return String(str);
}
let leftHalf = maxLength >> 1;
let rightHalf = maxLength - leftHalf - 1;
if (str.codePointAt(str.length - rightHalf - 1) >= 0x10000) {
--rightHalf;
++leftHalf;
}
if (leftHalf > 0 && str.codePointAt(leftHalf - 1) >= 0x10000) {
--leftHalf;
}
return str.substr(0, leftHalf) + '…' + str.substr(str.length - rightHalf, rightHalf);
};
export const trimEndWithMaxLength = (str, maxLength) => {
if (str.length <= maxLength) {
return String(str);
}
return str.substr(0, maxLength - 1) + '…';
};
export const escapeForRegExp = (str) => {
return escapeCharacters(str, SPECIAL_REGEX_CHARACTERS);
};
export const naturalOrderComparator = (a, b) => {
const chunk = /^\d+|^\D+/;
let chunka, chunkb, anum, bnum;
while (true) {
if (a) {
if (!b) {
return 1;
}
}
else {
if (b) {
return -1;
}
return 0;
}
chunka = a.match(chunk)[0];
chunkb = b.match(chunk)[0];
anum = !Number.isNaN(Number(chunka));
bnum = !Number.isNaN(Number(chunkb));
if (anum && !bnum) {
return -1;
}
if (bnum && !anum) {
return 1;
}
if (anum && bnum) {
const diff = Number(chunka) - Number(chunkb);
if (diff) {
return diff;
}
if (chunka.length !== chunkb.length) {
if (!Number(chunka) && !Number(chunkb)) { // chunks are strings of all 0s (special case)
return chunka.length - chunkb.length;
}
return chunkb.length - chunka.length;
}
}
else if (chunka !== chunkb) {
return (chunka < chunkb) ? -1 : 1;
}
a = a.substring(chunka.length);
b = b.substring(chunkb.length);
}
};
export const base64ToSize = function (content) {
if (!content) {
return 0;
}
let size = content.length * 3 / 4;
if (content[content.length - 1] === '=') {
size--;
}
if (content.length > 1 && content[content.length - 2] === '=') {
size--;
}
return size;
};