diff --git a/.eslintrc b/.eslintrc index 0b16cf9f6..ac3506b6d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -49,6 +49,7 @@ "unicorn/no-new-buffer": "error", "unicorn/no-instanceof-array": "error", "unicorn/no-useless-spread": "error", + "unicorn/prefer-at": "error", "unicorn/prefer-date-now": "error", "unicorn/prefer-dom-node-remove": "error", "unicorn/prefer-string-starts-ends-with": "error", diff --git a/external/.eslintrc b/external/.eslintrc index e3523dce5..0eb7db317 100644 --- a/external/.eslintrc +++ b/external/.eslintrc @@ -6,4 +6,8 @@ "env": { "node": true, }, + + "rules": { + "unicorn/prefer-at": "off", + }, } diff --git a/src/core/catalog.js b/src/core/catalog.js index c319b98fd..fa3efa081 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -1212,7 +1212,7 @@ class Catalog { } while (queue.length > 0) { - const queueItem = queue[queue.length - 1]; + const queueItem = queue.at(-1); const { currentNode, posInKids } = queueItem; let kids = currentNode.getRaw("Kids"); diff --git a/src/core/crypto.js b/src/core/crypto.js index 4aeddae95..f08aa6501 100644 --- a/src/core/crypto.js +++ b/src/core/crypto.js @@ -1014,7 +1014,7 @@ class AESBaseCipher { let outputLength = 16 * result.length; if (finalize) { // undo a padding that is described in RFC 2898 - const lastBlock = result[result.length - 1]; + const lastBlock = result.at(-1); let psLen = lastBlock[15]; if (psLen <= 16) { for (let i = 15, ii = 16 - psLen; i >= ii; --i) { @@ -1284,7 +1284,7 @@ const PDF20 = (function PDF20Closure() { let k = calculateSHA256(input, 0, input.length).subarray(0, 32); let e = [0]; let i = 0; - while (i < 64 || e[e.length - 1] > i - 32) { + while (i < 64 || e.at(-1) > i - 32) { const combinedLength = password.length + k.length + userBytes.length, combinedArray = new Uint8Array(combinedLength); let writeOffset = 0; diff --git a/src/core/document.js b/src/core/document.js index 44f64782e..bee5b2674 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -1045,7 +1045,7 @@ class PDFDocument { const pdfFonts = []; const initialState = { get font() { - return pdfFonts[pdfFonts.length - 1]; + return pdfFonts.at(-1); }, set font(font) { pdfFonts.push(font); diff --git a/src/core/evaluator.js b/src/core/evaluator.js index e92e243f8..4250d9f6f 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -3066,7 +3066,7 @@ class PartialEvaluator { } } - const item = elements[elements.length - 1]; + const item = elements.at(-1); if (typeof item === "string") { showSpacedTextBuffer.push(item); } diff --git a/src/core/font_renderer.js b/src/core/font_renderer.js index 6d738c332..ee32c5216 100644 --- a/src/core/font_renderer.js +++ b/src/core/font_renderer.js @@ -278,7 +278,7 @@ function compileGlyf(code, cmds, font) { } const instructionLength = getUint16(code, i); i += 2 + instructionLength; // skipping the instructions - const numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1; + const numberOfPoints = endPtsOfContours.at(-1) + 1; const points = []; while (points.length < numberOfPoints) { flags = code[i++]; @@ -329,15 +329,15 @@ function compileGlyf(code, cmds, font) { const contour = points.slice(startPoint, endPoint + 1); if (contour[0].flags & 1) { contour.push(contour[0]); // using start point at the contour end - } else if (contour[contour.length - 1].flags & 1) { + } else if (contour.at(-1).flags & 1) { // first is off-curve point, trying to use one from the end - contour.unshift(contour[contour.length - 1]); + contour.unshift(contour.at(-1)); } else { // start and end are off-curve points, creating implicit one const p = { flags: 1, - x: (contour[0].x + contour[contour.length - 1].x) / 2, - y: (contour[0].y + contour[contour.length - 1].y) / 2, + x: (contour[0].x + contour.at(-1).x) / 2, + y: (contour[0].y + contour.at(-1).y) / 2, }; contour.unshift(p); contour.push(p); diff --git a/src/core/fonts.js b/src/core/fonts.js index 22ebf3686..759a7acad 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -515,7 +515,7 @@ function getRanges(glyphs, numGlyphs) { function createCmapTable(glyphs, numGlyphs) { const ranges = getRanges(glyphs, numGlyphs); - const numTables = ranges[ranges.length - 1][1] > 0xffff ? 2 : 1; + const numTables = ranges.at(-1)[1] > 0xffff ? 2 : 1; let cmap = "\x00\x00" + // version string16(numTables) + // numTables @@ -2291,7 +2291,7 @@ class Font { // CALL if (!inFDEF && !inELSE) { // collecting information about which functions are used - funcId = stack[stack.length - 1]; + funcId = stack.at(-1); if (isNaN(funcId)) { info("TT: CALL empty stack (or invalid entry)."); } else { @@ -2374,7 +2374,7 @@ class Font { } else if (op === 0x1c) { // JMPR if (!inFDEF && !inELSE) { - const offset = stack[stack.length - 1]; + const offset = stack.at(-1); // only jumping forward to prevent infinite loop if (offset > 0) { i += offset - 1; diff --git a/src/core/function.js b/src/core/function.js index 3d1285f41..909ef3b20 100644 --- a/src/core/function.js +++ b/src/core/function.js @@ -1166,7 +1166,7 @@ const PostScriptCompiler = (function PostScriptCompilerClosure() { i += 6; break; } - ast1 = stack[stack.length - 1]; + ast1 = stack.at(-1); if (ast1.type === "literal" || ast1.type === "var") { // we don't have to save into intermediate variable a literal or // variable. diff --git a/src/core/operator_list.js b/src/core/operator_list.js index 2f395dfec..21ef90cf3 100644 --- a/src/core/operator_list.js +++ b/src/core/operator_list.js @@ -27,7 +27,7 @@ function addState(parentState, pattern, checkFn, iterateFn, processFn) { const item = pattern[i]; state = state[item] || (state[item] = []); } - state[pattern[pattern.length - 1]] = { + state[pattern.at(-1)] = { checkFn, iterateFn, processFn, diff --git a/src/core/pattern.js b/src/core/pattern.js index f5dfa4f9b..050e4e6a4 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -206,7 +206,7 @@ class RadialAxialShading extends BaseShading { } if (!extendEnd) { // Same idea as above in extendStart but for the end. - colorStops[colorStops.length - 1][0] -= BaseShading.SMALL_NUMBER; + colorStops.at(-1)[0] -= BaseShading.SMALL_NUMBER; colorStops.push([1, background]); } @@ -501,11 +501,11 @@ class MeshShading extends BaseShading { verticesLeft = 3; break; case 1: - ps.push(ps[ps.length - 2], ps[ps.length - 1]); + ps.push(ps.at(-2), ps.at(-1)); verticesLeft = 1; break; case 2: - ps.push(ps[ps.length - 3], ps[ps.length - 1]); + ps.push(ps.at(-3), ps.at(-1)); verticesLeft = 1; break; } diff --git a/src/core/type1_parser.js b/src/core/type1_parser.js index 13459ea70..2efac2d9b 100644 --- a/src/core/type1_parser.js +++ b/src/core/type1_parser.js @@ -228,7 +228,7 @@ const Type1CharString = (function Type1CharStringClosure() { // seac is like type 2's special endchar but it doesn't use the // first argument asb, so remove it. if (seacAnalysisEnabled) { - const asb = this.stack[this.stack.length - 5]; + const asb = this.stack.at(-5); this.seac = this.stack.splice(-4, 4); this.seac[0] += this.lsb - asb; error = this.executeCommand(0, COMMAND_MAP.endchar); diff --git a/src/core/writer.js b/src/core/writer.js index 4160ab721..9485537b2 100644 --- a/src/core/writer.js +++ b/src/core/writer.js @@ -249,7 +249,7 @@ function incrementalUpdate({ const refForXrefTable = xrefInfo.newRef; let buffer, baseOffset; - const lastByte = originalData[originalData.length - 1]; + const lastByte = originalData.at(-1); if (lastByte === /* \n */ 0x0a || lastByte === /* \r */ 0x0d) { buffer = []; baseOffset = originalData.length; diff --git a/src/core/xfa/builder.js b/src/core/xfa/builder.js index fd88684aa..7d64d328b 100644 --- a/src/core/xfa/builder.js +++ b/src/core/xfa/builder.js @@ -182,7 +182,7 @@ class Builder { } const prefixStack = this._namespacePrefixes.get(prefix); if (prefixStack && prefixStack.length > 0) { - return prefixStack[prefixStack.length - 1]; + return prefixStack.at(-1); } warn(`Unknown namespace prefix: ${prefix}.`); diff --git a/src/core/xfa/data.js b/src/core/xfa/data.js index 34a5a00fd..1a6c54372 100644 --- a/src/core/xfa/data.js +++ b/src/core/xfa/data.js @@ -32,7 +32,7 @@ class DataHandler { const stack = [[-1, this.data[$getChildren]()]]; while (stack.length > 0) { - const last = stack[stack.length - 1]; + const last = stack.at(-1); const [i, children] = last; if (i + 1 === children.length) { stack.pop(); diff --git a/src/core/xfa/formcalc_parser.js b/src/core/xfa/formcalc_parser.js index bf9de0947..72de8e0c6 100644 --- a/src/core/xfa/formcalc_parser.js +++ b/src/core/xfa/formcalc_parser.js @@ -354,7 +354,7 @@ class SimpleExprParser { return [tok, this.getNode()]; case TOKEN.leftParen: if (this.last === OPERAND) { - const lastOperand = this.operands[this.operands.length - 1]; + const lastOperand = this.operands.at(-1); if (!(lastOperand instanceof AstIdentifier)) { return [tok, this.getNode()]; } @@ -525,7 +525,7 @@ class SimpleExprParser { flushWithOperator(op) { while (true) { - const top = this.operators[this.operators.length - 1]; + const top = this.operators.at(-1); if (top) { if (top.id >= 0 && SimpleExprParser.checkPrecedence(top, op)) { this.operators.pop(); diff --git a/src/core/xfa/html_utils.js b/src/core/xfa/html_utils.js index 92897baac..eefd65109 100644 --- a/src/core/xfa/html_utils.js +++ b/src/core/xfa/html_utils.js @@ -562,7 +562,7 @@ function isPrintOnly(node) { function getCurrentPara(node) { const stack = node[$getTemplateRoot]()[$extra].paraStack; - return stack.length ? stack[stack.length - 1] : null; + return stack.length ? stack.at(-1) : null; } function setPara(node, nodeStyle, value) { diff --git a/src/core/xfa/som.js b/src/core/xfa/som.js index 49c7f88ac..9015a81c4 100644 --- a/src/core/xfa/som.js +++ b/src/core/xfa/som.js @@ -100,7 +100,7 @@ function parseExpression(expr, dotDotAllowed, noExpr = true) { warn("XFA - Invalid index in SOM expression"); return null; } - parsed[parsed.length - 1].index = parseIndex(match[0]); + parsed.at(-1).index = parseIndex(match[0]); pos += match[0].length + 1; continue; } diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index 8bc4659de..bd4e1b343 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -910,7 +910,7 @@ class Border extends XFAObject { if (!this[$extra]) { const edges = this.edge.children.slice(); if (edges.length < 4) { - const defaultEdge = edges[edges.length - 1] || new Edge({}); + const defaultEdge = edges.at(-1) || new Edge({}); for (let i = edges.length; i < 4; i++) { edges.push(defaultEdge); } @@ -950,7 +950,7 @@ class Border extends XFAObject { if (this.corner.children.some(node => node.radius !== 0)) { const cornerStyles = this.corner.children.map(node => node[$toStyle]()); if (cornerStyles.length === 2 || cornerStyles.length === 3) { - const last = cornerStyles[cornerStyles.length - 1]; + const last = cornerStyles.at(-1); for (let i = cornerStyles.length; i < 4; i++) { cornerStyles.push(last); } diff --git a/src/core/xfa/text.js b/src/core/xfa/text.js index a954d116d..3865e81cd 100644 --- a/src/core/xfa/text.js +++ b/src/core/xfa/text.js @@ -102,7 +102,7 @@ class FontSelector { } pushData(xfaFont, margin, lineHeight) { - const lastFont = this.stack[this.stack.length - 1]; + const lastFont = this.stack.at(-1); for (const name of [ "typeface", "posture", @@ -139,7 +139,7 @@ class FontSelector { } topFont() { - return this.stack[this.stack.length - 1]; + return this.stack.at(-1); } } diff --git a/src/core/xfa/xhtml.js b/src/core/xfa/xhtml.js index 273cc93bc..bf5aceaef 100644 --- a/src/core/xfa/xhtml.js +++ b/src/core/xfa/xhtml.js @@ -500,7 +500,7 @@ class P extends XhtmlObject { [$text]() { const siblings = this[$getParent]()[$getChildren](); - if (siblings[siblings.length - 1] === this) { + if (siblings.at(-1) === this) { return super[$text](); } return super[$text]() + "\n"; diff --git a/src/core/xfa_fonts.js b/src/core/xfa_fonts.js index 881a61baf..fc32b4231 100644 --- a/src/core/xfa_fonts.js +++ b/src/core/xfa_fonts.js @@ -284,10 +284,7 @@ function getXfaFontDict(name) { dict.set("CIDToGIDMap", Name.get("Identity")); dict.set("W", widths); dict.set("FirstChar", widths[0]); - dict.set( - "LastChar", - widths[widths.length - 2] + widths[widths.length - 1].length - 1 - ); + dict.set("LastChar", widths.at(-2) + widths.at(-1).length - 1); const descriptor = new Dict(null); dict.set("FontDescriptor", descriptor); const systemInfo = new Dict(null); diff --git a/src/core/xml_parser.js b/src/core/xml_parser.js index e322a364c..2054b7535 100644 --- a/src/core/xml_parser.js +++ b/src/core/xml_parser.js @@ -494,7 +494,7 @@ class SimpleXMLParser extends XMLParserBase { onEndElement(name) { this._currentFragment = this._stack.pop() || []; - const lastElement = this._currentFragment[this._currentFragment.length - 1]; + const lastElement = this._currentFragment.at(-1); if (!lastElement) { return null; } diff --git a/src/display/editor/fit_curve/fit_curve.js b/src/display/editor/fit_curve/fit_curve.js index d9562bda4..fa72f2f8a 100644 --- a/src/display/editor/fit_curve/fit_curve.js +++ b/src/display/editor/fit_curve/fit_curve.js @@ -257,7 +257,7 @@ function generateBezier(points, parameters, leftTangent, rightTangent) { ux; const firstPoint = points[0]; - const lastPoint = points[points.length - 1]; + const lastPoint = points.at(-1); // Bezier curve ctl pts const bezCurve = [firstPoint, null, null, lastPoint]; diff --git a/src/display/svg.js b/src/display/svg.js index c2f9a794e..5e542953c 100644 --- a/src/display/svg.js +++ b/src/display/svg.js @@ -389,7 +389,7 @@ if ( if (opListElement.fn === "save") { opTree.push({ fnId: 92, fn: "group", items: [] }); tmp.push(opTree); - opTree = opTree[opTree.length - 1].items; + opTree = opTree.at(-1).items; continue; } diff --git a/src/display/text_layer.js b/src/display/text_layer.js index 8c1480fee..8b953a4c4 100644 --- a/src/display/text_layer.js +++ b/src/display/text_layer.js @@ -488,7 +488,7 @@ function expandBoundsLTR(width, bounds) { affectedBoundary.x2 > boundary.x2 ? affectedBoundary : boundary; if (lastBoundary === useBoundary) { // Merging with previous. - changedHorizon[changedHorizon.length - 1].end = horizonPart.end; + changedHorizon.at(-1).end = horizonPart.end; } else { changedHorizon.push({ start: horizonPart.start, @@ -507,7 +507,7 @@ function expandBoundsLTR(width, bounds) { }); } if (boundary.y2 < horizon[j].end) { - changedHorizon[changedHorizon.length - 1].end = boundary.y2; + changedHorizon.at(-1).end = boundary.y2; changedHorizon.push({ start: boundary.y2, end: horizon[j].end, diff --git a/src/display/xfa_layer.js b/src/display/xfa_layer.js index 5abbb8a9b..6f8933e1b 100644 --- a/src/display/xfa_layer.js +++ b/src/display/xfa_layer.js @@ -185,13 +185,13 @@ class XfaLayer { const textDivs = []; while (stack.length > 0) { - const [parent, i, html] = stack[stack.length - 1]; + const [parent, i, html] = stack.at(-1); if (i + 1 === parent.children.length) { stack.pop(); continue; } - const child = parent.children[++stack[stack.length - 1][1]]; + const child = parent.children[++stack.at(-1)[1]]; if (child === null) { continue; } diff --git a/src/shared/compatibility.js b/src/shared/compatibility.js index 4bd1f4885..12040434f 100644 --- a/src/shared/compatibility.js +++ b/src/shared/compatibility.js @@ -88,6 +88,14 @@ if ( require("core-js/es/array/at.js"); })(); + // Support: Firefox<90, Chrome<92, Safari<15.4, Node.js<16.6.0 + (function checkTypedArrayAt() { + if (Uint8Array.prototype.at) { + return; + } + require("core-js/es/typed-array/at.js"); + })(); + // Support: Firefox<94, Chrome<98, Safari<15.4, Node.js<17.0.0 (function checkStructuredClone() { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("IMAGE_DECODERS")) { diff --git a/test/resources/reftest-analyzer.js b/test/resources/reftest-analyzer.js index 8a07f1fa9..1c4312d39 100644 --- a/test/resources/reftest-analyzer.js +++ b/test/resources/reftest-analyzer.js @@ -229,7 +229,7 @@ window.onload = function () { /^ {2}IMAGE[^:]*\((\d+\.?\d*)x(\d+\.?\d*)x(\d+\.?\d*)\): (.*)$/ ); if (match) { - const item = gTestItems[gTestItems.length - 1]; + const item = gTestItems.at(-1); item.images.push({ width: parseFloat(match[1]), height: parseFloat(match[2]), diff --git a/test/test.js b/test/test.js index b22f8c27b..4fe79fd4e 100644 --- a/test/test.js +++ b/test/test.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ +/* eslint-disable no-var, unicorn/prefer-at */ "use strict"; diff --git a/test/unit/ui_utils_spec.js b/test/unit/ui_utils_spec.js index c98c6d7ae..704008175 100644 --- a/test/unit/ui_utils_spec.js +++ b/test/unit/ui_utils_spec.js @@ -301,7 +301,7 @@ describe("ui_utils", function () { ids.add(view.id); } } - return { first: views[0], last: views[views.length - 1], views, ids }; + return { first: views[0], last: views.at(-1), views, ids }; } // This function takes a fixed layout of pages and compares the system under diff --git a/web/ui_utils.js b/web/ui_utils.js index c7876e5bf..e31c4e617 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -588,7 +588,7 @@ function getVisibleElements({ } const first = visible[0], - last = visible[visible.length - 1]; + last = visible.at(-1); if (sortByVisibility) { visible.sort(function (a, b) {