Enable 'eslint-plugin-regexp' and fix existing findings

Enable the recommended preset and fix or per-line-disable the 78
findings it surfaces. Most are equivalent rewrites, intentional
patterns (control chars, the whatwg email regex, autolinker URL regex)
keep their behavior via targeted disables.
This commit is contained in:
Calixte Denizet 2026-05-25 14:31:52 +02:00
parent 5f2691e77d
commit 7bda0fc97c
41 changed files with 152 additions and 63 deletions

View File

@ -7,6 +7,7 @@ import noUnsanitized from "eslint-plugin-no-unsanitized";
import perfectionist from "eslint-plugin-perfectionist";
import preferMathClamp from "./external/eslint_plugins/prefer-math-clamp.mjs";
import prettierRecommended from "eslint-plugin-prettier/recommended";
import regexpPlugin from "eslint-plugin-regexp";
import unicorn from "eslint-plugin-unicorn";
const jsFiles = folder => {
@ -55,6 +56,14 @@ export default [
\* ======================================================================== */
prettierRecommended,
{
files: jsFiles("."),
plugins: regexpPlugin.configs["flat/recommended"].plugins,
rules: {
...regexpPlugin.configs["flat/recommended"].rules,
"regexp/no-legacy-features": "off",
},
},
{
files: ["**/*.json", "**/.*.json"],
language: "json/json",

View File

@ -45,7 +45,7 @@ function watchObjectOrEmbed(elem) {
// <embed src> <object data>
var srcAttribute = "src" in elem ? "src" : "data";
var path = elem[srcAttribute];
if (!mimeType && !/\.pdf($|[?#])/i.test(path)) {
if (!mimeType && !/\.pdf(?:$|[?#])/i.test(path)) {
return;
}

View File

@ -132,7 +132,7 @@ function preprocess(inFilename, outFilename, defines) {
}
}
function expand(line) {
line = line.replaceAll(/__[\w]+__/g, function (variable) {
line = line.replaceAll(/__\w+__/g, function (variable) {
variable = variable.substring(2, variable.length - 2);
if (variable in defines) {
return defines[variable];
@ -158,6 +158,7 @@ function preprocess(inFilename, outFilename, defines) {
let state = STATE_NONE;
const stack = [];
const control =
// eslint-disable-next-line regexp/no-super-linear-backtracking
/^(?:\/\/|\s*\/\*|\s*<!--)\s*#(if|elif|else|endif|expand|include|error)\b(?:\s+(.*?)(?:\*\/|-->)?$)?/;
while ((line = readLine()) !== null) {

View File

@ -43,7 +43,7 @@ function extractFtlIds(ftlPath) {
const lines = readFileSync(ftlPath, "utf8").split("\n");
const ids = [];
for (const line of lines) {
const match = line.match(/^([a-zA-Z][a-zA-Z0-9-]*)\s*=/);
const match = line.match(/^([a-z][a-z0-9-]*)\s*=/i);
if (match) {
ids.push(match[1]);
}

View File

@ -14,7 +14,7 @@
*/
function parseAdobeCMap(content) {
let m = /(\bbegincmap\b[\s\S]*?)\bendcmap\b/.exec(content);
let m = /(\bbegincmap\b[\s\S]+?)\bendcmap\b/.exec(content);
if (!m) {
throw new Error("cmap was not found");
}
@ -37,7 +37,7 @@ function parseAdobeCMap(content) {
result.usecmap = m[1];
}
const re =
/(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g;
/(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(?:endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g;
while ((m = re.exec(body))) {
const lines = m[3].toLowerCase().split("\n");

View File

@ -1135,7 +1135,7 @@ gulp.task("locale", function () {
if (!checkDir(dirPath)) {
continue;
}
if (!/^[a-z][a-z]([a-z])?(-[A-Z][A-Z])?$/.test(locale)) {
if (!/^[a-z]{2,3}(?:-[A-Z]{2})?$/.test(locale)) {
console.log("Skipping invalid locale: " + locale);
continue;
}

75
package-lock.json generated
View File

@ -31,6 +31,7 @@
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-perfectionist": "^5.9.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-regexp": "^3.1.0",
"eslint-plugin-unicorn": "^64.0.0",
"globals": "^17.6.0",
"gulp": "^5.0.1",
@ -5462,6 +5463,28 @@
}
}
},
"node_modules/eslint-plugin-regexp": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-3.1.0.tgz",
"integrity": "sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0",
"comment-parser": "^1.4.0",
"jsdoc-type-pratt-parser": "^7.0.0",
"refa": "^0.12.1",
"regexp-ast-analysis": "^0.7.1",
"scslre": "^0.3.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"peerDependencies": {
"eslint": ">=9.38.0"
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "64.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-64.0.0.tgz",
@ -7378,6 +7401,16 @@
"node": ">=12.0.0"
}
},
"node_modules/jsdoc-type-pratt-parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.2.0.tgz",
"integrity": "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/jsdoc/node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
@ -9092,6 +9125,19 @@
"node": ">= 10.13.0"
}
},
"node_modules/refa": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz",
"integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.8.0"
},
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -9112,6 +9158,20 @@
"node": ">=4"
}
},
"node_modules/regexp-ast-analysis": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz",
"integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.8.0",
"refa": "^0.12.1"
},
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/regexp-tree": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
@ -9446,6 +9506,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/scslre": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz",
"integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.8.0",
"refa": "^0.12.0",
"regexp-ast-analysis": "^0.7.0"
},
"engines": {
"node": "^14.0.0 || >=16.0.0"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",

View File

@ -26,6 +26,7 @@
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-perfectionist": "^5.9.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-regexp": "^3.1.0",
"eslint-plugin-unicorn": "^64.0.0",
"globals": "^17.6.0",
"gulp": "^5.0.1",

View File

@ -564,7 +564,7 @@ function validateFontName(fontFamily, mustWarn = false) {
} else {
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
for (const ident of fontFamily.split(/[ \t]+/)) {
if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
if (/^(?:\d|-[\d-])/.test(ident) || !/^[\w\\-]+$/.test(ident)) {
if (mustWarn) {
warn(`FontFamily contains invalid <custom-ident>: ${fontFamily}.`);
}

View File

@ -1392,7 +1392,7 @@ class PDFDocument {
}
let fontFamily = descriptor.get("FontFamily");
// For example, "Wingdings 3" is not a valid font name in the css specs.
fontFamily = fontFamily.replaceAll(/[ ]+(\d)/g, "$1");
fontFamily = fontFamily.replaceAll(/ +(\d)/g, "$1");
const fontWeight = descriptor.get("FontWeight");
// Angle is expressed in degrees counterclockwise in PDF

View File

@ -4203,9 +4203,7 @@ class PartialEvaluator {
isSerifFont(baseFontName) {
// Simulating descriptor flags attribute
const fontNameWoStyle = baseFontName.split("-", 1)[0];
return (
fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle)
);
return fontNameWoStyle in getSerifFonts() || /serif/i.test(fontNameWoStyle);
}
getBaseFontMetrics(name) {

View File

@ -579,8 +579,8 @@ function getFontSubstitution(
return null;
}
// Maybe we'll be lucky and the OS will have the font.
const bold = /bold/gi.test(baseFontName);
const italic = /oblique|italic/gi.test(baseFontName);
const bold = /bold/i.test(baseFontName);
const italic = /oblique|italic/i.test(baseFontName);
const style =
(bold && italic && BOLDITALIC) ||
(bold && BOLD) ||

View File

@ -1235,16 +1235,16 @@ class Font {
}
}
this.bold = /bold/gi.test(fontName);
this.italic = /oblique|italic/gi.test(fontName);
this.bold = /bold/i.test(fontName);
this.italic = /oblique|italic/i.test(fontName);
// Use 'name' instead of 'fontName' here because the original
// name ArialBlack for example will be replaced by Helvetica.
this.black = /Black/g.test(name);
this.black = /Black/.test(name);
// Use 'name' instead of 'fontName' here because the original
// name ArialNarrow for example will be replaced by Helvetica.
const isNarrow = /Narrow/g.test(name);
const isNarrow = /Narrow/.test(name);
// if at least one width is present, remeasure all chars when exists
this.remeasure =

View File

@ -132,7 +132,7 @@ class Lexer {
this.pos = 0;
this.len = data.length;
// Sticky regexes: set lastIndex before exec() to match at an exact offset.
this._numberPattern = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
this._numberPattern = /[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/iy;
this._identifierPattern = /[a-z]+/y;
}

View File

@ -16,7 +16,11 @@
import { stringToBytes, Util, warn } from "../shared/util.js";
function isAscii(str) {
return typeof str === "string" && (!str || /^[\x00-\x7F]*$/.test(str));
return (
typeof str === "string" &&
// eslint-disable-next-line no-control-regex
(!str || /^[\x00-\x7F]*$/.test(str))
);
}
// If the string is null or undefined then it is returned as is.
@ -91,6 +95,7 @@ function stringToPDFString(str, keepEscapeSequence = false) {
if (keepEscapeSequence || !decoded.includes("\x1b")) {
return decoded;
}
// eslint-disable-next-line no-control-regex
return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
} catch (ex) {
warn(`stringToPDFString: "${ex}".`);

View File

@ -242,7 +242,7 @@ function getUnicodeRangeFor(value, lastPosition = -1) {
return -1;
}
const SpecialCharRegExp = new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$", "u");
const SpecialCharRegExp = /^(\s)|(\p{Mn})|(\p{Cf})$/u;
const CategoryCache = new Map();
function getCharUnicodeCategory(char) {

View File

@ -1003,7 +1003,7 @@ class Rename extends ContentObject {
// is no colon.
if (
this[$content].toLowerCase().startsWith("xml") ||
new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*", "u").test(this[$content])
/[\p{L}_][\p{L}\d._\p{M}-]*/u.test(this[$content])
) {
warn("XFA - Rename: invalid XFA name");
}

View File

@ -90,7 +90,7 @@ class FontFinder {
return font;
}
const pattern = /,|-|_| |bolditalic|bold|italic|regular|it/gi;
const pattern = /[,\-_ ]|bolditalic|bold|italic|regular|it/gi;
let name = fontName.replaceAll(pattern, "");
font = this.fonts.get(name);
if (font) {

View File

@ -116,11 +116,11 @@ const TOKEN = {
upto: 54,
};
const hexPattern = /^[uU]([0-9a-fA-F]{4,8})/;
const numberPattern = /^\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?/;
const dotNumberPattern = /^\d*(?:[Ee][+-]?\d+)?/;
const hexPattern = /^u([0-9a-f]{4,8})/i;
const numberPattern = /^\d*(?:\.\d*)?(?:E[+-]?\d+)?/i;
const dotNumberPattern = /^\d*(?:E[+-]?\d+)?/i;
const eolPattern = /[\r\n]+/;
const identifierPattern = new RegExp("^[\\p{L}_$!][\\p{L}\\p{N}_$]*", "u");
const identifierPattern = /^[\p{L}_$!][\p{L}\p{N}_$]*/u;
class Token {
constructor(id, value = null) {

View File

@ -134,8 +134,7 @@ function mapStyle(styleStr, node, richText) {
? `${style[key]} ${newValue}`
: newValue;
} else {
style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] =
newValue;
style[key.replaceAll(/-([a-z])/gi, (_, x) => x.toUpperCase())] = newValue;
}
}

View File

@ -1793,16 +1793,14 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
case "deleteWordBackward": {
const match = value
.substring(0, selectionStart)
.match(/\w*[^\w]*$/);
.match(/\w*\W*$/);
if (match) {
selStart -= match[0].length;
}
break;
}
case "deleteWordForward": {
const match = value
.substring(selectionStart)
.match(/^[^\w]*\w*/);
const match = value.substring(selectionStart).match(/^\W*\w*/);
if (match) {
selEnd += match[0].length;
}

View File

@ -81,6 +81,7 @@ function getFilenameFromContentDispositionHeader(contentDisposition) {
}
function textdecode(encoding, value) {
if (encoding) {
// eslint-disable-next-line no-control-regex
if (!/^[\x00-\xFF]+$/.test(value)) {
return value;
}
@ -184,6 +185,7 @@ function getFilenameFromContentDispositionHeader(contentDisposition) {
// Firefox also decodes words even where RFC 2047 section 5 states:
// "An 'encoded-word' MUST NOT appear within a 'quoted-string'."
// eslint-disable-next-line no-control-regex
if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) {
return value;
}
@ -195,12 +197,12 @@ function getFilenameFromContentDispositionHeader(contentDisposition) {
// encoded-text = any printable ASCII character other than ? or space.
// ... but Firefox permits ? and space.
return value.replaceAll(
/=\?([\w-]*)\?([QqBb])\?((?:[^?]|\?(?!=))*)\?=/g,
/=\?([\w-]*)\?([QB])\?((?:[^?]|\?(?!=))*)\?=/gi,
function (matches, charset, encoding, text) {
if (encoding === "q" || encoding === "Q") {
// RFC 2047 section 4.2.
text = text.replaceAll("_", " ");
text = text.replaceAll(/=([0-9a-fA-F]{2})/g, function (match, hex) {
text = text.replaceAll(/=([0-9a-f]{2})/gi, function (match, hex) {
return String.fromCharCode(parseInt(hex, 16));
});
return textdecode(charset, text);

View File

@ -296,7 +296,7 @@ class PDFDateString {
"(\\d{2})?" + // Hour (optional)
"(\\d{2})?" + // Minute (optional)
"(\\d{2})?" + // Second (optional)
"([Z|+|-])?" + // Universal time relation (optional)
"([Z|+\\-])?" + // Universal time relation (optional)
"(\\d{2})?" + // Offset hour (optional)
"'?" + // Splitting apostrophe (optional)
"(\\d{2})?" + // Offset minute (optional)
@ -756,7 +756,7 @@ function renderRichText({ html, dir, className }, container) {
if (typeof html === "string") {
const p = document.createElement("p");
p.dir = dir || "auto";
const lines = html.split(/(?:\r\n?|\n)/);
const lines = html.split(/\r\n?|\n/);
for (let i = 0, ii = lines.length; i < ii; ++i) {
const line = lines[i];
p.append(document.createTextNode(line));

View File

@ -140,7 +140,7 @@ class PDFNetworkStream extends BasePDFStream {
const chunk = getArrayBuffer(xhr.response);
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
const rangeHeader = xhr.getResponseHeader("Content-Range");
if (/bytes (\d+)-(\d+)\/(\d+)/.test(rangeHeader)) {
if (/bytes \d+-\d+\/\d+/.test(rangeHeader)) {
pendingRequest.onDone(chunk);
} else {
warn(`Missing or invalid "Content-Range" header.`);

View File

@ -26,8 +26,9 @@ class AForm {
// The e-mail address regex below originates from:
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
// eslint-disable-next-line regexp/use-ignore-case
this._emailRegex = new RegExp(
"^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+" +
"^[\\w.!#$%&'*+/=?^`{|}~-]+" +
"@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?" +
"(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
);
@ -184,12 +185,12 @@ class AForm {
// comma sep
pattern = event.willCommit
? /^[+-]?(\d+(,\d*)?|,\d+)$/
: /^[+-]?\d*,?\d*$/;
: /^[+-]?\d*(?:,\d*)?$/;
} else {
// dot sep
pattern = event.willCommit
? /^[+-]?(\d+(\.\d*)?|\.\d+)$/
: /^[+-]?\d*\.?\d*$/;
: /^[+-]?\d*(?:\.\d*)?$/;
}
if (!pattern.test(value)) {
@ -571,7 +572,7 @@ class AForm {
event.rc = true;
}
const re = /([-()]|\s)+/g;
const re = /[-()\s]+/g;
value = value.replaceAll(re, "");
for (const format of formats) {
this.#AFSpecial_KeystrokeEx_helper(

View File

@ -62,6 +62,7 @@ class Util extends PDFObject {
throw new TypeError("First argument of printf must be a string");
}
// eslint-disable-next-line regexp/no-misleading-capturing-group
const pattern = /%(,[0-4])?([+ 0#]+)?(\d+)?(\.\d+)?(.)/g;
const PLUS = 1;
const SPACE = 2;

View File

@ -1039,7 +1039,7 @@ function normalizeUnicode(str) {
// required.
// It appears that most the chars here contain some ligatures.
NormalizeRegex =
/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
NormalizationMap = new Map([["ſt", "ſt"]]);
}
return str.replaceAll(NormalizeRegex, (_, p1, p2) =>

View File

@ -21,7 +21,7 @@ function rewriteWebArchiveUrl(url) {
// Without this, an HTML page containing an iframe with the PDF file
// will be served instead (issue 8920).
const webArchiveRegex =
/(^https?:\/\/web\.archive\.org\/web\/)(\d+)(\/https?:\/\/.+)/g;
/(^https?:\/\/web\.archive\.org\/web\/)(\d+)(\/https?:\/\/.+)/;
const urlParts = webArchiveRegex.exec(url);
if (urlParts) {
return `${urlParts[1]}${urlParts[2]}if_${urlParts[3]}`;

View File

@ -36,7 +36,7 @@ describe("font_fpgm", function () {
verifyTtxOutput(output);
expect(
/(ENDF\[ \]|SVTCA\[0\])\s*\/\*.*\*\/\s*<\/assembly>\s*<\/fpgm>/.test(
/(?:ENDF\[ \]|SVTCA\[0\])\s*\/\*.*\*\/\s*<\/assembly>\s*<\/fpgm>/.test(
output
)
).toEqual(true);

View File

@ -109,7 +109,7 @@ describe("font_glyf", function () {
);
expect(notdef).not.toBeNull();
expect(notdef[1] || "").not.toMatch(
/<component\b[^>]*glyphName="\.notdef"/
/<component\b[^>]+glyphName="\.notdef"/
);
});
});
@ -132,7 +132,7 @@ describe("font_glyf", function () {
const output = await ttx(font.data);
verifyTtxOutput(output);
expect(
/<OS_2>\s*(<!--[\s\S]*?-->\s*)?<version value="3"\/>/.test(output)
/<OS_2>\s*(?:<!--[\s\S]*?-->\s*)?<version value="3"\/>/.test(output)
).toEqual(true);
expect(/<sCapHeight\b/.test(output)).toEqual(true);
expect(/<usMaxContext\b/.test(output)).toEqual(true);

View File

@ -530,7 +530,7 @@ describe("Text layer", () => {
// Selection starts mid-word in Heading 1, so assert the stable
// trailing content rather than exact full-line boundaries.
.toHaveRoughlySelected(
/ing 1\s+This paragraph 1\.\s+Heading 2\s+This paragraph 2/s
/ing 1\s+This paragraph 1\.\s+Heading 2\s+This paragraph 2/
);
})
);

View File

@ -205,7 +205,7 @@ window.onload = function () {
}
line = match[1];
match = line.match(
/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL)(\(EXPECTED RANDOM\)|) \| ([^|]+) \|(.*)/
/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL)(\(EXPECTED RANDOM\))? \| ([^|]+) \|(.*)/
);
if (match) {
const state = match[1];
@ -225,7 +225,7 @@ window.onload = function () {
continue;
}
match = line.match(
/^ {2}IMAGE[^:]*\((\d+\.?\d*)x(\d+\.?\d*)x(\d+\.?\d*)\): (.*)$/
/^ {2}IMAGE[^:]*\((\d+(?:\.\d*)?)x(\d+(?:\.\d*)?)x(\d+(?:\.\d*)?)\): (.*)$/
);
if (match) {
const item = gTestItems.at(-1);

View File

@ -78,7 +78,7 @@ function parseOptions() {
// Expand `-X=value` short-option forms into `["-X", "value"]` since
// parseArgs only strips the `=` separator for long options (--foo=bar).
const args = process.argv.slice(2).flatMap(arg => {
const m = arg.match(/^(-[a-zA-Z])=(.*)/s);
const m = arg.match(/^(-[a-z])=(.*)/is);
return m ? [m[1], m[2]] : [arg];
});
const { values } = parseArgs({

View File

@ -91,7 +91,7 @@ describe("custom ownerDocument", function () {
const checkFont = font => /g_d\d+_f1/.test(font.family);
const checkFontFaceRule = rule =>
/^@font-face {font-family:"g_d\d+_f1";src:/.test(rule);
/^@font-face \{font-family:"g_d\d+_f1";src:/.test(rule);
beforeEach(() => {
globalThis.FontFace = function MockFontFace(name) {

View File

@ -1115,7 +1115,7 @@ const PDFViewerApplication = {
// - The title may contain incorrectly encoded characters, which thus
// looks broken, hence we ignore the Metadata entry when it contains
// characters from the Specials Unicode block (fixes bug 1605526).
if (title !== "Untitled" && !/[\uFFF0-\uFFFF]/g.test(title)) {
if (title !== "Untitled" && !/[\uFFF0-\uFFFF]/.test(title)) {
return title;
}
}

View File

@ -19,7 +19,7 @@ if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
const isAndroid = /Android/.test(userAgent);
const isIOS =
/\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) ||
/\b(?:iPad|iPhone|iPod)(?=;)/.test(userAgent) ||
(platform === "MacIntel" && maxTouchPoints > 1);
// Limit canvas size to 5 mega-pixels on mobile.

View File

@ -138,7 +138,8 @@ class Autolinker {
static findLinks(text) {
// Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
this.#regex ??=
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gmv;
// eslint-disable-next-line regexp/no-super-linear-backtracking
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gv;
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
const matches = normalizedText.matchAll(this.#regex);

View File

@ -36,8 +36,8 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
// Run this code outside DOMContentLoaded to make sure that the URL
// is rewritten as soon as possible.
const queryString = document.location.search.slice(1);
const m = /(^|&)file=([^&]*)/.exec(queryString);
let defaultUrl = m ? decodeURIComponent(m[2]) : "";
const m = /(?:^|&)file=([^&]*)/.exec(queryString);
let defaultUrl = m ? decodeURIComponent(m[1]) : "";
if (!defaultUrl && queryString.startsWith("DNR:")) {
// Redirected via DNR, see registerPdfRedirectRule in pdfHandler.js.
defaultUrl = queryString.slice(4);

View File

@ -115,10 +115,7 @@ class FontView {
return;
}
const ext = MIMETYPE_TO_EXTENSION.get(font.mimetype) ?? "font";
const name = (font.name || font.loadedName).replaceAll(
/[^a-z0-9_-]/gi,
"_"
);
const name = (font.name || font.loadedName).replaceAll(/[^\w-]/g, "_");
const blob = new Blob([font.data], { type: font.mimetype });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");

View File

@ -75,8 +75,8 @@ let DIACRITICS_EXCEPTION_STR; // Lazily initialized, see below.
const DIACRITICS_REG_EXP = /\p{M}+/gu;
const SPECIAL_CHARS_REG_EXP = /([+^$|])|(\p{P}+)|(\s+)|(\p{M})|(\p{L})/gu;
const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
const NOT_DIACRITIC_FROM_END_REG_EXP = /(\P{M})\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*(\P{M})/u;
// The range [AC00-D7AF] corresponds to the Hangul syllables.
// The few other chars are some CJK Compatibility Ideographs.
@ -149,7 +149,7 @@ function normalize(text, options = {}) {
];
normalizationRegex = new RegExp(
regexps.map(r => `(${r})`).join("|"),
"gum"
"gmu"
);
if (hasSyllables) {

View File

@ -175,6 +175,7 @@ function parseQueryString(query) {
return params;
}
// eslint-disable-next-line no-control-regex
const InvisibleCharsRegExp = /[\x00-\x1F]/g;
/**