mediawiki/extensions/CentralNotice: main (log #2468217)

sourcepatches

This run took 129 seconds.

From 56a439c678376eb4c6fe0ad2283188ab26cea4f7 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Thu, 7 May 2026 02:55:59 +0000
Subject: [PATCH] build: Updating dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

composer:
* mediawiki/mediawiki-codesniffer: 50.0.0 → 51.0.0

npm:
* eslint-config-wikimedia: 0.32.3 → 0.32.4
* basic-ftp: 5.3.0 → 5.3.1
  * https://github.com/advisories/GHSA-rpmf-866q-6p89
* ip-address: 10.0.1 → 10.2.0
  * https://github.com/advisories/GHSA-v2v4-37r5-5v8g

Change-Id: Ibf16f23dd723f5187d7a9abf4475559d9cb26501
---
 composer.json                                 |   2 +-
 package-lock.json                             | 805 ++++++++++--------
 package.json                                  |   2 +-
 resources/infrastructure/campaignManager.js   |   6 +-
 .../ext.centralNotice.impressionDiet.js       |   2 +
 5 files changed, 461 insertions(+), 356 deletions(-)

diff --git a/composer.json b/composer.json
index baecf45..7d0269c 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
 {
 	"require-dev": {
-		"mediawiki/mediawiki-codesniffer": "50.0.0",
+		"mediawiki/mediawiki-codesniffer": "51.0.0",
 		"mediawiki/mediawiki-phan-config": "0.20.0",
 		"mediawiki/minus-x": "2.0.1",
 		"php-parallel-lint/php-console-highlighter": "1.0.0",
diff --git a/package-lock.json b/package-lock.json
index ffd21c5..152eac7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
 				"@wdio/local-runner": "9.19.2",
 				"@wdio/mocha-framework": "9.19.2",
 				"@wdio/spec-reporter": "9.19.2",
-				"eslint-config-wikimedia": "0.32.3",
+				"eslint-config-wikimedia": "0.32.4",
 				"grunt": "1.6.2",
 				"grunt-banana-checker": "0.13.0",
 				"grunt-eslint": "24.3.0",
@@ -128,19 +128,32 @@
 			}
 		},
 		"node_modules/@es-joy/jsdoccomment": {
-			"version": "0.76.0",
-			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz",
-			"integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==",
+			"version": "0.86.0",
+			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz",
+			"integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==",
 			"dev": true,
 			"dependencies": {
 				"@types/estree": "^1.0.8",
-				"@typescript-eslint/types": "^8.46.0",
-				"comment-parser": "1.4.1",
-				"esquery": "^1.6.0",
-				"jsdoc-type-pratt-parser": "~6.10.0"
+				"@typescript-eslint/types": "^8.58.0",
+				"comment-parser": "1.4.6",
+				"esquery": "^1.7.0",
+				"jsdoc-type-pratt-parser": "~7.2.0"
 			},
 			"engines": {
-				"node": ">=20.11.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": {
+			"version": "8.59.2",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
+			"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
 			}
 		},
 		"node_modules/@es-joy/resolve.exports": {
@@ -569,9 +582,9 @@
 			}
 		},
 		"node_modules/@eslint-community/eslint-utils": {
-			"version": "4.7.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
-			"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+			"version": "4.9.1",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+			"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
 			"dev": true,
 			"dependencies": {
 				"eslint-visitor-keys": "^3.4.3"
@@ -1186,9 +1199,9 @@
 			"dev": true
 		},
 		"node_modules/@mdn/browser-compat-data": {
-			"version": "5.7.6",
-			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
-			"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+			"version": "6.1.5",
+			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-6.1.5.tgz",
+			"integrity": "sha512-PzdZZzRhcXvKB0begee28n5lvwAcinGKYuLZOVxHAZm+n7y01ddEGfdS1ZXRuVcV+ndG6mSEAE8vgudom5UjYg==",
 			"dev": true
 		},
 		"node_modules/@nodable/entities": {
@@ -1437,9 +1450,9 @@
 			"dev": true
 		},
 		"node_modules/@types/estree": {
-			"version": "1.0.8",
-			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
-			"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+			"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
 			"dev": true
 		},
 		"node_modules/@types/istanbul-lib-coverage": {
@@ -1546,20 +1559,19 @@
 			}
 		},
 		"node_modules/@typescript-eslint/eslint-plugin": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
-			"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
+			"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/regexpp": "^4.10.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/type-utils": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"graphemer": "^1.4.0",
-				"ignore": "^7.0.0",
+				"@eslint-community/regexpp": "^4.12.2",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/type-utils": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"ignore": "^7.0.5",
 				"natural-compare": "^1.4.0",
-				"ts-api-utils": "^2.1.0"
+				"ts-api-utils": "^2.4.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1569,7 +1581,7 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"@typescript-eslint/parser": "^8.46.0",
+				"@typescript-eslint/parser": "^8.54.0",
 				"eslint": "^8.57.0 || ^9.0.0",
 				"typescript": ">=4.8.4 <6.0.0"
 			}
@@ -1584,16 +1596,16 @@
 			}
 		},
 		"node_modules/@typescript-eslint/parser": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
-			"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
+			"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1608,14 +1620,14 @@
 			}
 		},
 		"node_modules/@typescript-eslint/project-service": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
-			"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
+			"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/tsconfig-utils": "^8.46.0",
-				"@typescript-eslint/types": "^8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/tsconfig-utils": "^8.54.0",
+				"@typescript-eslint/types": "^8.54.0",
+				"debug": "^4.4.3"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1629,13 +1641,13 @@
 			}
 		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
-			"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
+			"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1646,9 +1658,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/tsconfig-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
-			"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
+			"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1662,16 +1674,16 @@
 			}
 		},
 		"node_modules/@typescript-eslint/type-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
-			"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
+			"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"debug": "^4.3.4",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"debug": "^4.4.3",
+				"ts-api-utils": "^2.4.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1686,9 +1698,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
-			"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
+			"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1699,21 +1711,20 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
-			"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
+			"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/project-service": "8.46.0",
-				"@typescript-eslint/tsconfig-utils": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4",
-				"fast-glob": "^3.3.2",
-				"is-glob": "^4.0.3",
-				"minimatch": "^9.0.4",
-				"semver": "^7.6.0",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/project-service": "8.54.0",
+				"@typescript-eslint/tsconfig-utils": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3",
+				"minimatch": "^9.0.5",
+				"semver": "^7.7.3",
+				"tinyglobby": "^0.2.15",
+				"ts-api-utils": "^2.4.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1727,9 +1738,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
-			"version": "2.0.3",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
-			"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+			"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0"
@@ -1751,15 +1762,15 @@
 			}
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
-			"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
+			"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/eslint-utils": "^4.7.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0"
+				"@eslint-community/eslint-utils": "^4.9.1",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1774,12 +1785,12 @@
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
-			"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
+			"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.46.0",
+				"@typescript-eslint/types": "8.54.0",
 				"eslint-visitor-keys": "^4.2.1"
 			},
 			"engines": {
@@ -2529,9 +2540,9 @@
 			}
 		},
 		"node_modules/acorn": {
-			"version": "8.15.0",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
-			"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+			"version": "8.16.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+			"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -2840,6 +2851,12 @@
 				"@mdn/browser-compat-data": "^5.6.19"
 			}
 		},
+		"node_modules/ast-metadata-inferer/node_modules/@mdn/browser-compat-data": {
+			"version": "5.7.6",
+			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
+			"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+			"dev": true
+		},
 		"node_modules/ast-types": {
 			"version": "0.13.4",
 			"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
@@ -3033,9 +3050,9 @@
 			}
 		},
 		"node_modules/basic-ftp": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz",
-			"integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==",
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+			"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
 			"dev": true,
 			"engines": {
 				"node": ">=10.0.0"
@@ -3494,9 +3511,9 @@
 			}
 		},
 		"node_modules/comment-parser": {
-			"version": "1.4.1",
-			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
-			"integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+			"version": "1.4.6",
+			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz",
+			"integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==",
 			"dev": true,
 			"engines": {
 				"node": ">= 12.0.0"
@@ -4514,13 +4531,13 @@
 			}
 		},
 		"node_modules/enhanced-resolve": {
-			"version": "5.18.3",
-			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
-			"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+			"version": "5.21.0",
+			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
+			"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
 			"dev": true,
 			"dependencies": {
 				"graceful-fs": "^4.2.4",
-				"tapable": "^2.2.0"
+				"tapable": "^2.3.3"
 			},
 			"engines": {
 				"node": ">=10.13.0"
@@ -4721,46 +4738,47 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.32.3",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.3.tgz",
-			"integrity": "sha512-Ekz2/ozpCCjQl3VbC6dW7ChqoW7FRilLDxmJ+FJOZhIxxzZSZR5QqQOAGWSZAlG1ONkZbYV/TPwGLWZcrNxyaA==",
+			"version": "0.32.4",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.4.tgz",
+			"integrity": "sha512-zcHJYss2vo8HK5PzkFuaV9mzaSGRuhA+jFGoQ4rNIwWz0usZsuQ2LYpkKxrbCVX1CbV0PzG+jJ6p0cLI+G37JQ==",
 			"dev": true,
 			"dependencies": {
 				"@stylistic/eslint-plugin": "^3.1.0",
-				"@typescript-eslint/eslint-plugin": "8.46.0",
-				"@typescript-eslint/parser": "8.46.0",
+				"@typescript-eslint/eslint-plugin": "8.54.0",
+				"@typescript-eslint/parser": "8.54.0",
 				"browserslist-config-wikimedia": "^0.7.0",
-				"eslint": "^8.57.0",
-				"eslint-plugin-compat": "^6.0.2",
+				"eslint-plugin-compat": "^6.1.0",
 				"eslint-plugin-es-x": "^8.7.0",
-				"eslint-plugin-jest": "^29.0.1",
-				"eslint-plugin-jsdoc": "61.3.0",
+				"eslint-plugin-jest": "^29.12.2",
+				"eslint-plugin-jsdoc": "^62.9.0",
 				"eslint-plugin-json-es": "^1.6.0",
-				"eslint-plugin-mediawiki": "^0.8.2",
+				"eslint-plugin-mediawiki": "^0.8.3",
 				"eslint-plugin-mocha": "^10.5.0",
-				"eslint-plugin-n": "^17.23.1",
-				"eslint-plugin-no-jquery": "^3.1.1",
-				"eslint-plugin-qunit": "^8.2.5",
-				"eslint-plugin-security": "^3.0.1",
+				"eslint-plugin-n": "^17.24.0",
+				"eslint-plugin-no-jquery": "^4.0.0",
+				"eslint-plugin-qunit": "^8.2.6",
+				"eslint-plugin-security": "^4.0.0",
 				"eslint-plugin-unicorn": "^56.0.1",
 				"eslint-plugin-vue": "^9.33.0",
-				"eslint-plugin-wdio": "^9.16.2",
+				"eslint-plugin-wdio": "9.23.0",
 				"eslint-plugin-yml": "^1.19.0"
 			},
 			"engines": {
 				"node": ">=20 <25"
+			},
+			"peerDependencies": {
+				"eslint": "^8.57.0"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
-			"version": "6.0.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.0.2.tgz",
-			"integrity": "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA==",
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.2.1.tgz",
+			"integrity": "sha512-gLKqUH+lQcCL+HzsROUjBDvakc5Zaga51Y4ZAkPCXc41pzKBfyluqTr2j8zOx8QQQb7zyglu1LVoL5aSNWf2SQ==",
 			"dev": true,
 			"dependencies": {
-				"@mdn/browser-compat-data": "^5.5.35",
+				"@mdn/browser-compat-data": "^6.1.1",
 				"ast-metadata-inferer": "^0.8.1",
-				"browserslist": "^4.24.2",
-				"caniuse-lite": "^1.0.30001687",
+				"browserslist": "^4.25.2",
 				"find-up": "^5.0.0",
 				"globals": "^15.7.0",
 				"lodash.memoize": "^4.1.2",
@@ -4770,7 +4788,7 @@
 				"node": ">=18.x"
 			},
 			"peerDependencies": {
-				"eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
+				"eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-compat/node_modules/globals": {
@@ -4836,57 +4854,57 @@
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc": {
-			"version": "61.3.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.3.0.tgz",
-			"integrity": "sha512-E4m/5J5lrasd63Z74q4CCZ4PFnywnnrcvA7zZ98802NPhrZKKTp5NH+XAT+afcjXp2ps2/OQF5gPSWCT2XFCJg==",
+			"version": "62.9.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz",
+			"integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==",
 			"dev": true,
 			"dependencies": {
-				"@es-joy/jsdoccomment": "~0.76.0",
+				"@es-joy/jsdoccomment": "~0.86.0",
 				"@es-joy/resolve.exports": "1.2.0",
 				"are-docs-informative": "^0.0.2",
-				"comment-parser": "1.4.1",
+				"comment-parser": "1.4.6",
 				"debug": "^4.4.3",
 				"escape-string-regexp": "^4.0.0",
-				"espree": "^10.4.0",
-				"esquery": "^1.6.0",
+				"espree": "^11.2.0",
+				"esquery": "^1.7.0",
 				"html-entities": "^2.6.0",
 				"object-deep-merge": "^2.0.0",
 				"parse-imports-exports": "^0.2.4",
-				"semver": "^7.7.3",
+				"semver": "^7.7.4",
 				"spdx-expression-parse": "^4.0.0",
 				"to-valid-identifier": "^1.0.0"
 			},
 			"engines": {
-				"node": ">=20.11.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
 			},
 			"peerDependencies": {
-				"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+				"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": {
-			"version": "4.2.1",
-			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
-			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+			"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
 			"dev": true,
 			"engines": {
-				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
 			},
 			"funding": {
 				"url": "https://opencollective.com/eslint"
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc/node_modules/espree": {
-			"version": "10.4.0",
-			"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
-			"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+			"version": "11.2.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+			"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
 			"dev": true,
 			"dependencies": {
-				"acorn": "^8.15.0",
+				"acorn": "^8.16.0",
 				"acorn-jsx": "^5.3.2",
-				"eslint-visitor-keys": "^4.2.1"
+				"eslint-visitor-keys": "^5.0.1"
 			},
 			"engines": {
-				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
 			},
 			"funding": {
 				"url": "https://opencollective.com/eslint"
@@ -4916,9 +4934,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-mediawiki": {
-			"version": "0.8.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.2.tgz",
-			"integrity": "sha512-ydYrpkzm8IVVDQA96QPF3HnFd2xjkIEh7gixD2gvOqUbUZF0p36LtpWXOFAlPWAvHLePWbNNTD5ovd3d4hEtog==",
+			"version": "0.8.3",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.3.tgz",
+			"integrity": "sha512-RQKZd40C1taMDk5N9+aFLEBGBB95RNG7Gc54EsJ8pHsJu8//nIdpxNFWPtQz6RNxz6pZUXBnMCxzkMOLM3Mm1w==",
 			"dev": true,
 			"dependencies": {
 				"upath": "^2.0.1"
@@ -4945,9 +4963,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-n": {
-			"version": "17.23.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz",
-			"integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==",
+			"version": "17.24.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz",
+			"integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.5.0",
@@ -5019,31 +5037,34 @@
 			}
 		},
 		"node_modules/eslint-plugin-no-jquery": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.1.1.tgz",
-			"integrity": "sha512-LTLO3jH/Tjr1pmxCEqtV6qmt+OChv8La4fwgG470JRpgxyFF4NOzoC9CRy92GIWD3Yjl0qLEgPmD2FLQWcNEjg==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-4.0.0.tgz",
+			"integrity": "sha512-ZR631D3qIQfgjKOAcgvYa5cB8xdTvFXAD5MbK5x5WltLSwFxmGnoaTXNtnptFU7py07ALrIe5dZRYncu4RD/Ug==",
 			"dev": true,
 			"peerDependencies": {
-				"eslint": ">=8.0.0"
+				"eslint": ">=8.0.0 <9.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-qunit": {
-			"version": "8.2.5",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.5.tgz",
-			"integrity": "sha512-qr7RJCYImKQjB+39q4q46i1l7p1V3joHzBE5CAYfxn5tfVFjrnjn/tw7q/kDyweU9kAIcLul0Dx/KWVUCb3BgA==",
+			"version": "8.2.6",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.6.tgz",
+			"integrity": "sha512-S1jC/DIW9J8VtNX4uG1vlf5FZVrfQFlcuiYmvTHR2IICUhubHqpWA5o+qS1tujh+81Gs39omKV2D4OXfbSJE5g==",
 			"dev": true,
 			"dependencies": {
-				"eslint-utils": "^3.0.0",
+				"@eslint-community/eslint-utils": "^4.4.0",
 				"requireindex": "^1.2.0"
 			},
 			"engines": {
 				"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
+			},
+			"peerDependencies": {
+				"eslint": ">=8.38.0"
 			}
 		},
 		"node_modules/eslint-plugin-security": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz",
-			"integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz",
+			"integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==",
 			"dev": true,
 			"dependencies": {
 				"safe-regex": "^2.1.1"
@@ -5123,9 +5144,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-wdio": {
-			"version": "9.16.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.16.2.tgz",
-			"integrity": "sha512-qkqsPgxN70OnUPWMjmzJbSbvm2+Q087JIGss53/OFI4Y46xKlV5VLhLiYealaAibAiXmnfWKd0tERjZAzVL87A==",
+			"version": "9.23.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.23.0.tgz",
+			"integrity": "sha512-8tcpupzp2Qmv+uSfhzeHi42LVA9PyjkpMBPclSIkPxBfXpj4fMrejwAHu1PROh1OmJN1VQcGQUTWvSzyRcV2vA==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.20.0"
@@ -5252,9 +5273,9 @@
 			}
 		},
 		"node_modules/esquery": {
-			"version": "1.6.0",
-			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
-			"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+			"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
 			"dev": true,
 			"dependencies": {
 				"estraverse": "^5.1.0"
@@ -6815,9 +6836,9 @@
 			"dev": true
 		},
 		"node_modules/ip-address": {
-			"version": "10.0.1",
-			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
-			"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+			"version": "10.2.0",
+			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+			"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
 			"dev": true,
 			"engines": {
 				"node": ">= 12"
@@ -7199,9 +7220,9 @@
 			"dev": true
 		},
 		"node_modules/jsdoc-type-pratt-parser": {
-			"version": "6.10.0",
-			"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz",
-			"integrity": "sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==",
+			"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,
 			"engines": {
 				"node": ">=20.0.0"
@@ -10295,9 +10316,9 @@
 			"dev": true
 		},
 		"node_modules/tapable": {
-			"version": "2.3.0",
-			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
-			"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+			"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
 			"dev": true,
 			"engines": {
 				"node": ">=6"
@@ -10347,6 +10368,51 @@
 			"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
 			"dev": true
 		},
+		"node_modules/tinyglobby": {
+			"version": "0.2.16",
+			"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+			"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+			"dev": true,
+			"dependencies": {
+				"fdir": "^6.5.0",
+				"picomatch": "^4.0.4"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/SuperchupuDev"
+			}
+		},
+		"node_modules/tinyglobby/node_modules/fdir": {
+			"version": "6.5.0",
+			"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+			"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+			"dev": true,
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"peerDependencies": {
+				"picomatch": "^3 || ^4"
+			},
+			"peerDependenciesMeta": {
+				"picomatch": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/tinyglobby/node_modules/picomatch": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+			"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
 		"node_modules/tinyrainbow": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
@@ -10398,9 +10464,9 @@
 			}
 		},
 		"node_modules/ts-api-utils": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
-			"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+			"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.12"
@@ -11234,16 +11300,24 @@
 			"dev": true
 		},
 		"@es-joy/jsdoccomment": {
-			"version": "0.76.0",
-			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz",
-			"integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==",
+			"version": "0.86.0",
+			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz",
+			"integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==",
 			"dev": true,
 			"requires": {
 				"@types/estree": "^1.0.8",
-				"@typescript-eslint/types": "^8.46.0",
-				"comment-parser": "1.4.1",
-				"esquery": "^1.6.0",
-				"jsdoc-type-pratt-parser": "~6.10.0"
+				"@typescript-eslint/types": "^8.58.0",
+				"comment-parser": "1.4.6",
+				"esquery": "^1.7.0",
+				"jsdoc-type-pratt-parser": "~7.2.0"
+			},
+			"dependencies": {
+				"@typescript-eslint/types": {
+					"version": "8.59.2",
+					"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
+					"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
+					"dev": true
+				}
 			}
 		},
 		"@es-joy/resolve.exports": {
@@ -11435,9 +11509,9 @@
 			"optional": true
 		},
 		"@eslint-community/eslint-utils": {
-			"version": "4.7.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
-			"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+			"version": "4.9.1",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+			"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
 			"dev": true,
 			"requires": {
 				"eslint-visitor-keys": "^3.4.3"
@@ -11810,9 +11884,9 @@
 			"dev": true
 		},
 		"@mdn/browser-compat-data": {
-			"version": "5.7.6",
-			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
-			"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+			"version": "6.1.5",
+			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-6.1.5.tgz",
+			"integrity": "sha512-PzdZZzRhcXvKB0begee28n5lvwAcinGKYuLZOVxHAZm+n7y01ddEGfdS1ZXRuVcV+ndG6mSEAE8vgudom5UjYg==",
 			"dev": true
 		},
 		"@nodable/entities": {
@@ -11980,9 +12054,9 @@
 			"dev": true
 		},
 		"@types/estree": {
-			"version": "1.0.8",
-			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
-			"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+			"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
 			"dev": true
 		},
 		"@types/istanbul-lib-coverage": {
@@ -12089,20 +12163,19 @@
 			}
 		},
 		"@typescript-eslint/eslint-plugin": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
-			"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
+			"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
 			"dev": true,
 			"requires": {
-				"@eslint-community/regexpp": "^4.10.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/type-utils": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"graphemer": "^1.4.0",
-				"ignore": "^7.0.0",
+				"@eslint-community/regexpp": "^4.12.2",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/type-utils": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"ignore": "^7.0.5",
 				"natural-compare": "^1.4.0",
-				"ts-api-utils": "^2.1.0"
+				"ts-api-utils": "^2.4.0"
 			},
 			"dependencies": {
 				"ignore": {
@@ -12114,87 +12187,86 @@
 			}
 		},
 		"@typescript-eslint/parser": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
-			"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
+			"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3"
 			}
 		},
 		"@typescript-eslint/project-service": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
-			"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
+			"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/tsconfig-utils": "^8.46.0",
-				"@typescript-eslint/types": "^8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/tsconfig-utils": "^8.54.0",
+				"@typescript-eslint/types": "^8.54.0",
+				"debug": "^4.4.3"
 			}
 		},
 		"@typescript-eslint/scope-manager": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
-			"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
+			"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0"
 			}
 		},
 		"@typescript-eslint/tsconfig-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
-			"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
+			"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
 			"dev": true,
 			"requires": {}
 		},
 		"@typescript-eslint/type-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
-			"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
+			"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"debug": "^4.3.4",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"debug": "^4.4.3",
+				"ts-api-utils": "^2.4.0"
 			}
 		},
 		"@typescript-eslint/types": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
-			"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
+			"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
 			"dev": true
 		},
 		"@typescript-eslint/typescript-estree": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
-			"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
+			"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/project-service": "8.46.0",
-				"@typescript-eslint/tsconfig-utils": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4",
-				"fast-glob": "^3.3.2",
-				"is-glob": "^4.0.3",
-				"minimatch": "^9.0.4",
-				"semver": "^7.6.0",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/project-service": "8.54.0",
+				"@typescript-eslint/tsconfig-utils": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3",
+				"minimatch": "^9.0.5",
+				"semver": "^7.7.3",
+				"tinyglobby": "^0.2.15",
+				"ts-api-utils": "^2.4.0"
 			},
 			"dependencies": {
 				"brace-expansion": {
-					"version": "2.0.3",
-					"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
-					"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+					"version": "2.1.0",
+					"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+					"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
 					"dev": true,
 					"requires": {
 						"balanced-match": "^1.0.0"
@@ -12212,24 +12284,24 @@
 			}
 		},
 		"@typescript-eslint/utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
-			"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
+			"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
 			"dev": true,
 			"requires": {
-				"@eslint-community/eslint-utils": "^4.7.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0"
+				"@eslint-community/eslint-utils": "^4.9.1",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0"
 			}
 		},
 		"@typescript-eslint/visitor-keys": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
-			"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
+			"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/types": "8.46.0",
+				"@typescript-eslint/types": "8.54.0",
 				"eslint-visitor-keys": "^4.2.1"
 			},
 			"dependencies": {
@@ -12754,9 +12826,9 @@
 			}
 		},
 		"acorn": {
-			"version": "8.15.0",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
-			"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+			"version": "8.16.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+			"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
 			"dev": true
 		},
 		"acorn-jsx": {
@@ -12983,6 +13055,14 @@
 			"dev": true,
 			"requires": {
 				"@mdn/browser-compat-data": "^5.6.19"
+			},
+			"dependencies": {
+				"@mdn/browser-compat-data": {
+					"version": "5.7.6",
+					"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
+					"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+					"dev": true
+				}
 			}
 		},
 		"ast-types": {
@@ -13113,9 +13193,9 @@
 			"dev": true
 		},
 		"basic-ftp": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz",
-			"integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==",
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+			"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
 			"dev": true
 		},
 		"bcrypt-pbkdf": {
@@ -13427,9 +13507,9 @@
 			"dev": true
 		},
 		"comment-parser": {
-			"version": "1.4.1",
-			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
-			"integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+			"version": "1.4.6",
+			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz",
+			"integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==",
 			"dev": true
 		},
 		"compress-commons": {
@@ -14167,13 +14247,13 @@
 			}
 		},
 		"enhanced-resolve": {
-			"version": "5.18.3",
-			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
-			"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+			"version": "5.21.0",
+			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
+			"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
 			"dev": true,
 			"requires": {
 				"graceful-fs": "^4.2.4",
-				"tapable": "^2.2.0"
+				"tapable": "^2.3.3"
 			}
 		},
 		"entities": {
@@ -14331,43 +14411,41 @@
 			}
 		},
 		"eslint-config-wikimedia": {
-			"version": "0.32.3",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.3.tgz",
-			"integrity": "sha512-Ekz2/ozpCCjQl3VbC6dW7ChqoW7FRilLDxmJ+FJOZhIxxzZSZR5QqQOAGWSZAlG1ONkZbYV/TPwGLWZcrNxyaA==",
+			"version": "0.32.4",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.4.tgz",
+			"integrity": "sha512-zcHJYss2vo8HK5PzkFuaV9mzaSGRuhA+jFGoQ4rNIwWz0usZsuQ2LYpkKxrbCVX1CbV0PzG+jJ6p0cLI+G37JQ==",
 			"dev": true,
 			"requires": {
 				"@stylistic/eslint-plugin": "^3.1.0",
-				"@typescript-eslint/eslint-plugin": "8.46.0",
-				"@typescript-eslint/parser": "8.46.0",
+				"@typescript-eslint/eslint-plugin": "8.54.0",
+				"@typescript-eslint/parser": "8.54.0",
 				"browserslist-config-wikimedia": "^0.7.0",
-				"eslint": "^8.57.0",
-				"eslint-plugin-compat": "^6.0.2",
+				"eslint-plugin-compat": "^6.1.0",
 				"eslint-plugin-es-x": "^8.7.0",
-				"eslint-plugin-jest": "^29.0.1",
-				"eslint-plugin-jsdoc": "61.3.0",
+				"eslint-plugin-jest": "^29.12.2",
+				"eslint-plugin-jsdoc": "^62.9.0",
 				"eslint-plugin-json-es": "^1.6.0",
-				"eslint-plugin-mediawiki": "^0.8.2",
+				"eslint-plugin-mediawiki": "^0.8.3",
 				"eslint-plugin-mocha": "^10.5.0",
-				"eslint-plugin-n": "^17.23.1",
-				"eslint-plugin-no-jquery": "^3.1.1",
-				"eslint-plugin-qunit": "^8.2.5",
-				"eslint-plugin-security": "^3.0.1",
+				"eslint-plugin-n": "^17.24.0",
+				"eslint-plugin-no-jquery": "^4.0.0",
+				"eslint-plugin-qunit": "^8.2.6",
+				"eslint-plugin-security": "^4.0.0",
 				"eslint-plugin-unicorn": "^56.0.1",
 				"eslint-plugin-vue": "^9.33.0",
-				"eslint-plugin-wdio": "^9.16.2",
+				"eslint-plugin-wdio": "9.23.0",
 				"eslint-plugin-yml": "^1.19.0"
 			}
 		},
 		"eslint-plugin-compat": {
-			"version": "6.0.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.0.2.tgz",
-			"integrity": "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA==",
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.2.1.tgz",
+			"integrity": "sha512-gLKqUH+lQcCL+HzsROUjBDvakc5Zaga51Y4ZAkPCXc41pzKBfyluqTr2j8zOx8QQQb7zyglu1LVoL5aSNWf2SQ==",
 			"dev": true,
 			"requires": {
-				"@mdn/browser-compat-data": "^5.5.35",
+				"@mdn/browser-compat-data": "^6.1.1",
 				"ast-metadata-inferer": "^0.8.1",
-				"browserslist": "^4.24.2",
-				"caniuse-lite": "^1.0.30001687",
+				"browserslist": "^4.25.2",
 				"find-up": "^5.0.0",
 				"globals": "^15.7.0",
 				"lodash.memoize": "^4.1.2",
@@ -14403,42 +14481,42 @@
 			}
 		},
 		"eslint-plugin-jsdoc": {
-			"version": "61.3.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.3.0.tgz",
-			"integrity": "sha512-E4m/5J5lrasd63Z74q4CCZ4PFnywnnrcvA7zZ98802NPhrZKKTp5NH+XAT+afcjXp2ps2/OQF5gPSWCT2XFCJg==",
+			"version": "62.9.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz",
+			"integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==",
 			"dev": true,
 			"requires": {
-				"@es-joy/jsdoccomment": "~0.76.0",
+				"@es-joy/jsdoccomment": "~0.86.0",
 				"@es-joy/resolve.exports": "1.2.0",
 				"are-docs-informative": "^0.0.2",
-				"comment-parser": "1.4.1",
+				"comment-parser": "1.4.6",
 				"debug": "^4.4.3",
 				"escape-string-regexp": "^4.0.0",
-				"espree": "^10.4.0",
-				"esquery": "^1.6.0",
+				"espree": "^11.2.0",
+				"esquery": "^1.7.0",
 				"html-entities": "^2.6.0",
 				"object-deep-merge": "^2.0.0",
 				"parse-imports-exports": "^0.2.4",
-				"semver": "^7.7.3",
+				"semver": "^7.7.4",
 				"spdx-expression-parse": "^4.0.0",
 				"to-valid-identifier": "^1.0.0"
 			},
 			"dependencies": {
 				"eslint-visitor-keys": {
-					"version": "4.2.1",
-					"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
-					"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+					"version": "5.0.1",
+					"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+					"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
 					"dev": true
 				},
 				"espree": {
-					"version": "10.4.0",
-					"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
-					"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+					"version": "11.2.0",
+					"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+					"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
 					"dev": true,
 					"requires": {
-						"acorn": "^8.15.0",
+						"acorn": "^8.16.0",
 						"acorn-jsx": "^5.3.2",
-						"eslint-visitor-keys": "^4.2.1"
+						"eslint-visitor-keys": "^5.0.1"
 					}
 				},
 				"spdx-expression-parse": {
@@ -14464,9 +14542,9 @@
 			}
 		},
 		"eslint-plugin-mediawiki": {
-			"version": "0.8.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.2.tgz",
-			"integrity": "sha512-ydYrpkzm8IVVDQA96QPF3HnFd2xjkIEh7gixD2gvOqUbUZF0p36LtpWXOFAlPWAvHLePWbNNTD5ovd3d4hEtog==",
+			"version": "0.8.3",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.3.tgz",
+			"integrity": "sha512-RQKZd40C1taMDk5N9+aFLEBGBB95RNG7Gc54EsJ8pHsJu8//nIdpxNFWPtQz6RNxz6pZUXBnMCxzkMOLM3Mm1w==",
 			"dev": true,
 			"requires": {
 				"upath": "^2.0.1"
@@ -14484,9 +14562,9 @@
 			}
 		},
 		"eslint-plugin-n": {
-			"version": "17.23.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz",
-			"integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==",
+			"version": "17.24.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz",
+			"integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==",
 			"dev": true,
 			"requires": {
 				"@eslint-community/eslint-utils": "^4.5.0",
@@ -14529,26 +14607,26 @@
 			}
 		},
 		"eslint-plugin-no-jquery": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.1.1.tgz",
-			"integrity": "sha512-LTLO3jH/Tjr1pmxCEqtV6qmt+OChv8La4fwgG470JRpgxyFF4NOzoC9CRy92GIWD3Yjl0qLEgPmD2FLQWcNEjg==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-4.0.0.tgz",
+			"integrity": "sha512-ZR631D3qIQfgjKOAcgvYa5cB8xdTvFXAD5MbK5x5WltLSwFxmGnoaTXNtnptFU7py07ALrIe5dZRYncu4RD/Ug==",
 			"dev": true,
 			"requires": {}
 		},
 		"eslint-plugin-qunit": {
-			"version": "8.2.5",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.5.tgz",
-			"integrity": "sha512-qr7RJCYImKQjB+39q4q46i1l7p1V3joHzBE5CAYfxn5tfVFjrnjn/tw7q/kDyweU9kAIcLul0Dx/KWVUCb3BgA==",
+			"version": "8.2.6",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.6.tgz",
+			"integrity": "sha512-S1jC/DIW9J8VtNX4uG1vlf5FZVrfQFlcuiYmvTHR2IICUhubHqpWA5o+qS1tujh+81Gs39omKV2D4OXfbSJE5g==",
 			"dev": true,
 			"requires": {
-				"eslint-utils": "^3.0.0",
+				"@eslint-community/eslint-utils": "^4.4.0",
 				"requireindex": "^1.2.0"
 			}
 		},
 		"eslint-plugin-security": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz",
-			"integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz",
+			"integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==",
 			"dev": true,
 			"requires": {
 				"safe-regex": "^2.1.1"
@@ -14603,9 +14681,9 @@
 			}
 		},
 		"eslint-plugin-wdio": {
-			"version": "9.16.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.16.2.tgz",
-			"integrity": "sha512-qkqsPgxN70OnUPWMjmzJbSbvm2+Q087JIGss53/OFI4Y46xKlV5VLhLiYealaAibAiXmnfWKd0tERjZAzVL87A==",
+			"version": "9.23.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.23.0.tgz",
+			"integrity": "sha512-8tcpupzp2Qmv+uSfhzeHi42LVA9PyjkpMBPclSIkPxBfXpj4fMrejwAHu1PROh1OmJN1VQcGQUTWvSzyRcV2vA==",
 			"dev": true
 		},
 		"eslint-plugin-yml": {
@@ -14673,9 +14751,9 @@
 			"dev": true
 		},
 		"esquery": {
-			"version": "1.6.0",
-			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
-			"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+			"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
 			"dev": true,
 			"requires": {
 				"estraverse": "^5.1.0"
@@ -15793,9 +15871,9 @@
 			"dev": true
 		},
 		"ip-address": {
-			"version": "10.0.1",
-			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
-			"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+			"version": "10.2.0",
+			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+			"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
 			"dev": true
 		},
 		"is-absolute": {
@@ -16078,9 +16156,9 @@
 			"dev": true
 		},
 		"jsdoc-type-pratt-parser": {
-			"version": "6.10.0",
-			"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz",
-			"integrity": "sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==",
+			"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
 		},
 		"jsesc": {
@@ -18354,9 +18432,9 @@
 			}
 		},
 		"tapable": {
-			"version": "2.3.0",
-			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
-			"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+			"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
 			"dev": true
 		},
 		"tar-fs": {
@@ -18397,6 +18475,31 @@
 			"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
 			"dev": true
 		},
+		"tinyglobby": {
+			"version": "0.2.16",
+			"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+			"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+			"dev": true,
+			"requires": {
+				"fdir": "^6.5.0",
+				"picomatch": "^4.0.4"
+			},
+			"dependencies": {
+				"fdir": {
+					"version": "6.5.0",
+					"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+					"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+					"dev": true,
+					"requires": {}
+				},
+				"picomatch": {
+					"version": "4.0.4",
+					"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+					"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+					"dev": true
+				}
+			}
+		},
 		"tinyrainbow": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
@@ -18433,9 +18536,9 @@
 			}
 		},
 		"ts-api-utils": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
-			"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+			"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
 			"dev": true,
 			"requires": {}
 		},
diff --git a/package.json b/package.json
index a4b2ed1..252d3aa 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
 		"@wdio/local-runner": "9.19.2",
 		"@wdio/mocha-framework": "9.19.2",
 		"@wdio/spec-reporter": "9.19.2",
-		"eslint-config-wikimedia": "0.32.3",
+		"eslint-config-wikimedia": "0.32.4",
 		"grunt": "1.6.2",
 		"grunt-banana-checker": "0.13.0",
 		"grunt-eslint": "24.3.0",
diff --git a/resources/infrastructure/campaignManager.js b/resources/infrastructure/campaignManager.js
index fd18870..301ab4f 100644
--- a/resources/infrastructure/campaignManager.js
+++ b/resources/infrastructure/campaignManager.js
@@ -428,7 +428,7 @@
 		$.each( paramDefs, ( paramName, paramDef ) => {
 
 			const paramTemplateVars = {
-				// eslint-disable-next-line mediawiki/msg-doc
+
 				labelMsg: mw.message( paramDef.labelMsg ).text(),
 				inputName: makeNoticeMixinControlName( mixinName, paramName ),
 				dataType: paramDef.type,
@@ -512,7 +512,7 @@
 			}
 
 			if ( paramDef.helpMsg ) {
-				// eslint-disable-next-line mediawiki/msg-doc
+
 				paramTemplateVars.help = mw.message( paramDef.helpMsg ).text();
 			}
 
@@ -567,7 +567,7 @@
 
 		if ( error ) {
 			if ( !messageBox ) {
-				// eslint-disable-next-line mediawiki/msg-doc
+
 				messageBox = mw.util.messageBox( mw.message( msgKey ).text(), 'alert' );
 				$input.closest( 'p' ).before( messageBox );
 			}
diff --git a/resources/subscribing/ext.centralNotice.impressionDiet.js b/resources/subscribing/ext.centralNotice.impressionDiet.js
index 93cae0d..2406670 100644
--- a/resources/subscribing/ext.centralNotice.impressionDiet.js
+++ b/resources/subscribing/ext.centralNotice.impressionDiet.js
@@ -176,6 +176,8 @@
 
 	/**
 	 * Store updated counts
+	 *
+	 * @param c
 	 */
 	function storeCounts( c ) {
 		if ( identifier ) {
-- 
2.47.3

$ date
--- stdout ---
Thu May  7 02:53:59 UTC 2026

--- end ---
$ git clone file:///srv/git/mediawiki-extensions-CentralNotice.git /src/repo --depth=1 -b master
--- stderr ---
Cloning into '/src/repo'...
--- stdout ---

--- end ---
$ git config user.name libraryupgrader
--- stdout ---

--- end ---
$ git config user.email tools.libraryupgrader@tools.wmflabs.org
--- stdout ---

--- end ---
$ git submodule update --init
--- stdout ---

--- end ---
$ grr init
--- stdout ---
Installed commit-msg hook.

--- end ---
$ git show-ref refs/heads/master
--- stdout ---
15527a316ffc662232645dc29dd74ed59983b3f0 refs/heads/master

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "@wdio/mocha-framework": {
      "name": "@wdio/mocha-framework",
      "severity": "high",
      "isDirect": true,
      "via": [
        "mocha"
      ],
      "effects": [],
      "range": ">=6.1.19",
      "nodes": [
        "node_modules/@wdio/mocha-framework"
      ],
      "fixAvailable": {
        "name": "@wdio/mocha-framework",
        "version": "6.1.17",
        "isSemVerMajor": true
      }
    },
    "basic-ftp": {
      "name": "basic-ftp",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1117726,
          "name": "basic-ftp",
          "dependency": "basic-ftp",
          "title": "basic-ftp allows a malicious FTP server to cause client-side denial of service via unbounded multiline control response buffering",
          "url": "https://github.com/advisories/GHSA-rpmf-866q-6p89",
          "severity": "high",
          "cwe": [
            "CWE-400",
            "CWE-770"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<=5.3.0"
        }
      ],
      "effects": [],
      "range": "<=5.3.0",
      "nodes": [
        "node_modules/basic-ftp"
      ],
      "fixAvailable": true
    },
    "form-data": {
      "name": "form-data",
      "severity": "critical",
      "isDirect": false,
      "via": [
        {
          "source": 1109540,
          "name": "form-data",
          "dependency": "form-data",
          "title": "form-data uses unsafe random function in form-data for choosing boundary",
          "url": "https://github.com/advisories/GHSA-fjxv-7rqg-78g4",
          "severity": "critical",
          "cwe": [
            "CWE-330"
          ],
          "cvss": {
            "score": 0,
            "vectorString": null
          },
          "range": "<2.5.4"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<2.5.4",
      "nodes": [
        "node_modules/form-data"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "ip-address": {
      "name": "ip-address",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1117683,
          "name": "ip-address",
          "dependency": "ip-address",
          "title": "ip-address has XSS in Address6 HTML-emitting methods",
          "url": "https://github.com/advisories/GHSA-v2v4-37r5-5v8g",
          "severity": "moderate",
          "cwe": [
            "CWE-79"
          ],
          "cvss": {
            "score": 0,
            "vectorString": null
          },
          "range": "<=10.1.0"
        }
      ],
      "effects": [],
      "range": "<=10.1.0",
      "nodes": [
        "node_modules/ip-address"
      ],
      "fixAvailable": true
    },
    "mocha": {
      "name": "mocha",
      "severity": "high",
      "isDirect": false,
      "via": [
        "serialize-javascript"
      ],
      "effects": [
        "@wdio/mocha-framework"
      ],
      "range": "8.0.0 - 12.0.0-beta-2",
      "nodes": [
        "node_modules/mocha"
      ],
      "fixAvailable": {
        "name": "@wdio/mocha-framework",
        "version": "6.1.17",
        "isSemVerMajor": true
      }
    },
    "mwbot": {
      "name": "mwbot",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "request"
      ],
      "effects": [
        "wdio-mediawiki"
      ],
      "range": ">=0.1.6",
      "nodes": [
        "node_modules/mwbot"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "qs": {
      "name": "qs",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1113719,
          "name": "qs",
          "dependency": "qs",
          "title": "qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion",
          "url": "https://github.com/advisories/GHSA-6rw7-vpxm-498p",
          "severity": "moderate",
          "cwe": [
            "CWE-20"
          ],
          "cvss": {
            "score": 3.7,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": "<6.14.1"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<6.14.1",
      "nodes": [
        "node_modules/qs"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "request": {
      "name": "request",
      "severity": "critical",
      "isDirect": false,
      "via": [
        {
          "source": 1096727,
          "name": "request",
          "dependency": "request",
          "title": "Server-Side Request Forgery in Request",
          "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 6.1,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
          },
          "range": "<=2.88.2"
        },
        "form-data",
        "qs",
        "tough-cookie"
      ],
      "effects": [
        "mwbot"
      ],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "serialize-javascript": {
      "name": "serialize-javascript",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1113686,
          "name": "serialize-javascript",
          "dependency": "serialize-javascript",
          "title": "Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()",
          "url": "https://github.com/advisories/GHSA-5c6j-r48x-rmvq",
          "severity": "high",
          "cwe": [
            "CWE-96"
          ],
          "cvss": {
            "score": 8.1,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
          },
          "range": "<=7.0.2"
        },
        {
          "source": 1115723,
          "name": "serialize-javascript",
          "dependency": "serialize-javascript",
          "title": "Serialize JavaScript has CPU Exhaustion Denial of Service via crafted array-like objects",
          "url": "https://github.com/advisories/GHSA-qj8w-gfj5-8c6v",
          "severity": "moderate",
          "cwe": [
            "CWE-400",
            "CWE-834"
          ],
          "cvss": {
            "score": 5.9,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<7.0.5"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "<=7.0.4",
      "nodes": [
        "node_modules/serialize-javascript"
      ],
      "fixAvailable": {
        "name": "@wdio/mocha-framework",
        "version": "6.1.17",
        "isSemVerMajor": true
      }
    },
    "tough-cookie": {
      "name": "tough-cookie",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097682,
          "name": "tough-cookie",
          "dependency": "tough-cookie",
          "title": "tough-cookie Prototype Pollution vulnerability",
          "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
          "severity": "moderate",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<4.1.3"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<4.1.3",
      "nodes": [
        "node_modules/tough-cookie"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "wdio-mediawiki": {
      "name": "wdio-mediawiki",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "mwbot"
      ],
      "effects": [],
      "range": "<=5.1.0",
      "nodes": [
        "node_modules/wdio-mediawiki"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 5,
      "high": 4,
      "critical": 2,
      "total": 11
    },
    "dependencies": {
      "prod": 1,
      "dev": 884,
      "optional": 38,
      "peer": 1,
      "peerOptional": 0,
      "total": 884
    }
  }
}

--- end ---
$ /usr/bin/composer install
--- stderr ---
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 37 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.3.2)
  - Locking composer/semver (3.4.4)
  - Locking composer/spdx-licenses (1.5.10)
  - Locking composer/xdebug-handler (3.0.5)
  - Locking danog/advanced-json-rpc (v3.2.3)
  - Locking dealerdirect/phpcodesniffer-composer-installer (v1.2.1)
  - Locking doctrine/deprecations (1.1.6)
  - Locking mediawiki/mediawiki-codesniffer (v50.0.0)
  - Locking mediawiki/mediawiki-phan-config (0.20.0)
  - Locking mediawiki/minus-x (2.0.1)
  - Locking mediawiki/phan-taint-check-plugin (9.1.0)
  - Locking netresearch/jsonmapper (v5.0.1)
  - Locking phan/phan (6.0.2)
  - Locking phan/tolerant-php-parser (v0.2.0)
  - Locking phan/var_representation_polyfill (0.1.4)
  - Locking php-parallel-lint/php-console-color (v1.0.1)
  - Locking php-parallel-lint/php-console-highlighter (v1.0.0)
  - Locking php-parallel-lint/php-parallel-lint (v1.4.0)
  - Locking phpcsstandards/phpcsextra (1.4.0)
  - Locking phpcsstandards/phpcsutils (1.2.2)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (6.0.3)
  - Locking phpdocumentor/type-resolver (2.0.0)
  - Locking phpstan/phpdoc-parser (2.3.2)
  - Locking psr/container (2.0.2)
  - Locking psr/log (3.0.2)
  - Locking sabre/event (6.1.0)
  - Locking squizlabs/php_codesniffer (3.13.5)
  - Locking symfony/console (v8.0.9)
  - Locking symfony/deprecation-contracts (v3.7.0)
  - Locking symfony/polyfill-ctype (v1.37.0)
  - Locking symfony/polyfill-intl-grapheme (v1.37.0)
  - Locking symfony/polyfill-intl-normalizer (v1.37.0)
  - Locking symfony/polyfill-mbstring (v1.37.0)
  - Locking symfony/service-contracts (v3.7.0)
  - Locking symfony/string (v8.0.8)
  - Locking webmozart/assert (2.3.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 37 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Installing squizlabs/php_codesniffer (3.13.5): Extracting archive
  - Installing dealerdirect/phpcodesniffer-composer-installer (v1.2.1): Extracting archive
  - Installing composer/pcre (3.3.2): Extracting archive
  - Installing phpcsstandards/phpcsutils (1.2.2): Extracting archive
  - Installing phpcsstandards/phpcsextra (1.4.0): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.37.0): Extracting archive
  - Installing composer/spdx-licenses (1.5.10): Extracting archive
  - Installing composer/semver (3.4.4): Extracting archive
  - Installing mediawiki/mediawiki-codesniffer (v50.0.0): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.37.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.37.0): Extracting archive
  - Installing symfony/polyfill-ctype (v1.37.0): Extracting archive
  - Installing symfony/string (v8.0.8): Extracting archive
  - Installing symfony/deprecation-contracts (v3.7.0): Extracting archive
  - Installing psr/container (2.0.2): Extracting archive
  - Installing symfony/service-contracts (v3.7.0): Extracting archive
  - Installing symfony/console (v8.0.9): Extracting archive
  - Installing sabre/event (6.1.0): Extracting archive
  - Installing phan/var_representation_polyfill (0.1.4): Extracting archive
  - Installing phan/tolerant-php-parser (v0.2.0): Extracting archive
  - Installing netresearch/jsonmapper (v5.0.1): Extracting archive
  - Installing webmozart/assert (2.3.0): Extracting archive
  - Installing phpstan/phpdoc-parser (2.3.2): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing doctrine/deprecations (1.1.6): Extracting archive
  - Installing phpdocumentor/type-resolver (2.0.0): Extracting archive
  - Installing phpdocumentor/reflection-docblock (6.0.3): Extracting archive
  - Installing danog/advanced-json-rpc (v3.2.3): Extracting archive
  - Installing psr/log (3.0.2): Extracting archive
  - Installing composer/xdebug-handler (3.0.5): Extracting archive
  - Installing phan/phan (6.0.2): Extracting archive
  - Installing mediawiki/phan-taint-check-plugin (9.1.0): Extracting archive
  - Installing mediawiki/mediawiki-phan-config (0.20.0): Extracting archive
  - Installing mediawiki/minus-x (2.0.1): Extracting archive
  - Installing php-parallel-lint/php-console-color (v1.0.1): Extracting archive
  - Installing php-parallel-lint/php-console-highlighter (v1.0.0): Extracting archive
  - Installing php-parallel-lint/php-parallel-lint (v1.4.0): Extracting archive
  0/35 [>---------------------------]   0%
 28/35 [======================>-----]  80%
 35/35 [============================] 100%
1 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
16 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---
PHP CodeSniffer Config installed_paths set to ../../mediawiki/mediawiki-codesniffer,../../phpcsstandards/phpcsextra,../../phpcsstandards/phpcsutils

--- end ---
Upgrading n:eslint-config-wikimedia from 0.32.3 -> 0.32.4
$ /usr/bin/npm install
--- stderr ---
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated node-domexception@1.0.0: Use your platform's native DOMException instead
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
--- stdout ---

added 863 packages, and audited 864 packages in 16s

200 packages are looking for funding
  run `npm fund` for details

11 vulnerabilities (5 moderate, 4 high, 2 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

--- end ---
$ package-lock-lint /src/repo/package-lock.json
--- stdout ---
Checking /src/repo/package-lock.json

--- end ---
$ /usr/bin/npm install grunt-eslint@24.3.0 --save-exact
--- stdout ---

up to date, audited 864 packages in 2s

200 packages are looking for funding
  run `npm fund` for details

11 vulnerabilities (5 moderate, 4 high, 2 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

--- end ---
$ package-lock-lint /src/repo/package-lock.json
--- stdout ---
Checking /src/repo/package-lock.json

--- end ---
$ ./node_modules/.bin/eslint . --fix
--- stdout ---

/src/repo/resources/ext.centralNotice.display/index.js
  265:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  297:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  842:4  warning  Prefer .then to .fail  no-jquery/no-done-fail
  842:4  warning  Prefer .then to .done  no-jquery/no-done-fail
  852:4  warning  Prefer .then to .fail  no-jquery/no-done-fail
  852:4  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/ext.centralNotice.kvStore/kvStore.js
   83:18  warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   87:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   89:9   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   91:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  198:4   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  202:9   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  230:15  warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  259:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  298:4   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage

/src/repo/resources/ext.centralNotice.startUp/index.js
  44:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
   33:17  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   38:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   61:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   65:9   warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   69:7   warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   84:28  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie
   90:14  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie
  109:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
  109:35  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
  117:18  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie

/src/repo/resources/infrastructure/bannereditor.js
   50:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
   50:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  117:3  warning  Prefer .then to .fail  no-jquery/no-done-fail

/src/repo/resources/infrastructure/campaignManager.js
  809:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
   808:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
   808:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail
  1251:20  warning  OOUI button has no label. Even icon-only buttons should set a label with invisibleLabel set to true  mediawiki/no-unlabeled-buttonwidget
  1322:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
  1322:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail
  1358:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
  1358:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
  296:4  warning  Prefer .then to .done  no-jquery/no-done-fail
  367:4  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.geoIP.js
  160:2  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.impressionDiet.js
  180:1  warning  Missing JSDoc @param "c" type  jsdoc/require-param-type

/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js
  439:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  479:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/tests/qunit/subscribing/ext.centralNotice.geoIP.tests.js
  49:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  49:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  63:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  63:3  warning  Prefer .then to .done  no-jquery/no-done-fail

✖ 47 problems (0 errors, 47 warnings)


--- end ---
$ ./node_modules/.bin/eslint . -f json
--- stdout ---
[{"filePath":"/src/repo/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/.stylelintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/.svgo.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/Gruntfile.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bundlesize.config.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/composer.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/extension.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/api/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/api/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package-lock.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.display/bucketer.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-map-util","severity":2,"message":"Prefer Array#map to $.map","line":136,"column":22,"nodeType":"CallExpression","endLine":149,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.display/chooser.js","messages":[],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'campaignName' is assigned a value but never used.","line":150,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":150,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'fallbackLoopIndex' is defined but never used.","line":322,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":322,"endColumn":37,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.display/hide.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.display/index.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":265,"column":3,"nodeType":"CallExpression","endLine":271,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":297,"column":3,"nodeType":"CallExpression","endLine":297,"endColumn":64},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":842,"column":4,"nodeType":"CallExpression","endLine":843,"endColumn":49},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":842,"column":4,"nodeType":"CallExpression","endLine":844,"endColumn":42},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":852,"column":4,"nodeType":"CallExpression","endLine":853,"endColumn":49},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":852,"column":4,"nodeType":"CallExpression","endLine":854,"endColumn":42}],"suppressedMessages":[{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":109,"column":3,"nodeType":"CallExpression","endLine":130,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * CentralNotice display module.\n *\n * Handles campaign and banner selection, execution of campaign mixin code, and\n * banner display. Provides an API to be used from campaign mixins and in-banner\n * JS.\n *\n * The layout of this module is:\n *\n *     ext.centralNotice.display.js (this file): General logic, banner injection\n *         and external access points, provided on mw.centralNotice. Code here\n *         may know about and manipulate other objects in this module, but not\n *         vice versa.\n *\n *     ext.centralNotice.display.state.js: Stores data for campaign/banner\n *         processing and data related to the state of that processing. Provides\n *         cn.internal.state and cn.data.\n *\n *     ext.centralNotice.display.chooser.js: Logic for selecting a campaign and\n *         a banner (or not). Provides cn.internal.chooser.\n *\n *     ext.centralNotice.display.bucketer.js: Storage, retrieval and other\n *         processing of buckets. Provides cn.internal.bucketer.\n *\n *     ext.centralNotice.display.hide.js: Retrieves, processes and stores 'hide'\n *         cookies, which prevent banners from showing in certain circumstances.\n *         Provides cn.internal.hide.\n *\n * For an overview of how this all fits together, see\n * mw.centralNotice.reallyChooseAndMaybeDisplay() (below).\n */\n( function () {\n\tlet cn,\n\n\t\t// For providing a jQuery.Promise to signal when a banner has loaded\n\t\tbannerLoadedDeferredObj,\n\n\t\t// Name of a requested banner; see cn.requestBanner(), below.\n\t\trequestedBannerName = null;\n\n\t// Registry of campaign-associated mixins\n\tconst campaignMixins = {},\n\n\t\t// Maximum time to delay the record impression call, in milliseconds\n\t\tMAX_RECORD_IMPRESSION_DELAY = 250,\n\n\t\t// EventLogging schema name for logging impressions\n\t\tIMPRESSION_EVENT_LOGGING_SCHEMA = 'CentralNoticeImpression',\n\n\t\t// Prefix for key used to store banner preview content for external preview.\n\t\t// Coordinate with PREVIEW_STORAGE_KEY_PREFIX in bannereditor.js\n\t\tPREVIEW_STORAGE_KEY_PREFIX = 'cn-banner-preview-';\n\n\t// TODO: make data.result options explicit via constants\n\n\t/**\n\t * Class for campaign-associated mixins. Access via mw.centralNotice.Mixin.\n\t *\n\t * @param {string} name\n\t */\n\tconst Mixin = function ( name ) {\n\t\tthis.name = name;\n\t};\n\n\t// TODO Refactor hooks (rename, divide hooks that run at multiple points into\n\t// separate hooks, and adapt handlers accordingly)\n\n\t/**\n\t * Run after we've chosen a campaign to attempt, but before we try to choose a\n\t * banner from that campaign.\n\t *\n\t * @param {Function} handlerFunc\n\t */\n\tMixin.prototype.setPreBannerHandler = function ( handlerFunc ) {\n\t\tthis.preBannerHandler = handlerFunc;\n\t};\n\n\t/**\n\t * Run after we chose and loaded a banner, or after the campaign failed, or after\n\t * we unsuccessfully attempted to chose a banner.\n\t *\n\t * @param {Function} handlerFunc\n\t */\n\tMixin.prototype.setPostBannerOrFailHandler = function ( handlerFunc ) {\n\t\tthis.postBannerOrFailHandler = handlerFunc;\n\t};\n\n\t/**\n\t * Run at the end of the selection and display process. If a banner was chosen\n\t * to be displayed, the hook runs after it's loaded. If all attempted campaigns failed\n\t * and no banner will show, the hook runs then.\n\t *\n\t * @param {Function} handlerFunc\n\t */\n\tMixin.prototype.setFinalizeChooseAndMaybeDisplayHandler = function ( handlerFunc ) {\n\t\tthis.finalizeChooseAndMaybeDisplayHandler = handlerFunc;\n\t};\n\n\t/**\n\t * Run handlers stored in the mixin property indicated by hookPropertyName,\n\t * for all campaign mixins.\n\t *\n\t * @param {string} hookPropertyName The name of a Mixin property containing\n\t * hook handlers\n\t * @param {Object} campaign\n\t */\n\tfunction runMixinHooks( hookPropertyName, campaign ) {\n\t\t// eslint-disable-next-line no-jquery/no-each-util\n\t\t$.each( campaign.mixins, ( mixinName, mixinParams ) => {\n\t\t\t// Sanity check\n\t\t\tif ( !( mixinName in campaignMixins ) ) {\n\t\t\t\tmw.log.warn( 'Mixin ' + mixinName + ' not registered.' );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Mixins need not handle all hooks\n\t\t\tif ( !( hookPropertyName in campaignMixins[ mixinName ] ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handler = campaignMixins[ mixinName ][ hookPropertyName ];\n\n\t\t\t// Another sanity check\n\t\t\tif ( typeof handler !== 'function' ) {\n\t\t\t\tmw.log.warn( hookPropertyName + ' for ' + mixinName + ' not a function.' );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\thandler( mixinParams, campaign );\n\t\t} );\n\t}\n\n\tfunction runPreBannerHooks() {\n\t\trunMixinHooks( 'preBannerHandler', cn.internal.state.getAttemptingCampaign() );\n\t}\n\n\tfunction runPostBannerOrFailHooks() {\n\t\trunMixinHooks( 'postBannerOrFailHandler', cn.internal.state.getAttemptingCampaign() );\n\t}\n\n\tfunction runFinalizeChooseAndMaybeDisplayHooks() {\n\t\tcn.internal.state.getAttemptedCampaigns().forEach( ( campaign ) => {\n\t\t\trunMixinHooks( 'finalizeChooseAndMaybeDisplayHandler', campaign );\n\t\t} );\n\t}\n\n\t/**\n\t * Set up the legacy cn.data property using a getter, or a normal property\n\t * (for browsers that don't support getters).\n\t */\n\tfunction setUpDataProperty() {\n\n\t\t// try/catch since some browsers don't support Object.defineProperty\n\t\t// or don't support it fully\n\t\ttry {\n\t\t\tObject.defineProperty( cn, 'data', {\n\t\t\t\tget: function () {\n\t\t\t\t\treturn cn.internal.state.getData();\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\treturn;\n\n\t\t} catch ( e ) {}\n\n\t\t// FIXME For browsers that don't support defineProperty, we don't\n\t\t// fully respect our internal contract with the state object to\n\t\t// manage data, since we assume the object reference won't change.\n\t\tcn.data = cn.internal.state.getData();\n\t}\n\n\t/**\n\t * Expose a promise object to be resolved when the banner is loaded.\n\t */\n\tfunction setUpBannerLoadedPromise() {\n\t\tbannerLoadedDeferredObj = $.Deferred();\n\t\tcn.bannerLoadedPromise = bannerLoadedDeferredObj.promise();\n\n\t\t// Legacy location of the above\n\t\t// TODO Deprecate and remove\n\t\tcn.events = {};\n\t\tcn.events.bannerLoaded = cn.bannerLoadedPromise;\n\t}\n\n\t/***\n\t * Based on implementation of now deprecated mw.Uri.encode\n\t *\n\t * @param {string} s\n\t * @return {string}\n\t */\n\tfunction encode( s ) {\n\t\treturn encodeURIComponent( s )\n\t\t\t.replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\\(/g, '%28' )\n\t\t\t.replace( /\\)/g, '%29' ).replace( /\\*/g, '%2A' )\n\t\t\t.replace( /%20/g, '+' );\n\t}\n\n\tfunction fetchOrRetrieveBanner() {\n\t\tconst data = cn.internal.state.getData();\n\n\t\t// If this is a preview of an unsaved version, retrieve and inject the banner as\n\t\t// soon as the DOM's ready.\n\t\tif ( data.preview ) {\n\t\t\t$( () => {\n\t\t\t\tconst previewBannerContent = cn.kvStore.getItem(\n\t\t\t\t\tPREVIEW_STORAGE_KEY_PREFIX + data.banner,\n\t\t\t\t\tcn.kvStore.contexts.GLOBAL\n\t\t\t\t);\n\n\t\t\t\tif ( previewBannerContent === null ) {\n\t\t\t\t\tmw.log.warn( 'Could not retrieve preview banner ' + data.banner );\n\t\t\t\t} else {\n\t\t\t\t\tinjectBannerHTML( previewBannerContent );\n\t\t\t\t}\n\t\t\t} );\n\t\t} else {\n\t\t\tfetchBanner();\n\t\t}\n\t}\n\n\t/**\n\t * @param {string} dispatcherUrl\n\t * @param {Object} data Banner data\n\t * @return {URL}\n\t */\n\tfunction getBannerUrl( dispatcherUrl, data ) {\n\t\tconst urlBase = new URL( dispatcherUrl, location );\n\t\t// Prior to using URL we were using mw.Uri\n\t\t// For Varnish purges of banner content, we ensure query param order\n\t\t// Param order must coordinate with CdnCacheUpdateBannerLoader in php.\n\t\t// Now we are using URL it should be possible to rewrite this to use\n\t\t// SearchURLParams in a follow up to retain this ordering.\n\t\tconst urlQuery = [\n\t\t\t'banner=' + encode( data.banner ),\n\t\t\t'uselang=' + encode( data.uselang ),\n\t\t\t'debug=' + ( !!data.debug ).toString()\n\t\t];\n\n\t\t// If this is a test display, there might not be a campaign\n\t\tif ( data.campaign ) {\n\t\t\turlQuery.unshift( 'campaign=' + encode( data.campaign ) );\n\t\t}\n\n\t\t// Only a title param (for ugly URL format) is allowed as a param on the\n\t\t// configured banner dispatchers\n\t\tconst searchTitle = urlBase.searchParams.get( 'title' );\n\t\tif ( searchTitle ) {\n\t\t\turlQuery.unshift( 'title=' + mw.util.wikiUrlencode( searchTitle ) );\n\t\t}\n\n\t\t// Remove any other query or fragment info parsed from the configured URL\n\t\turlBase.search = urlQuery.join( '&' );\n\t\turlBase.hash = '';\n\t\treturn urlBase;\n\t}\n\tfunction fetchBanner() {\n\t\tconst url = getBannerUrl(\n\t\t\tmw.config.get( 'wgCentralNoticeActiveBannerDispatcher' ),\n\t\t\tcn.internal.state.getData()\n\t\t);\n\n\t\t// The returned javascript will call mw.centralNotice.insertBanner()\n\t\t// or mw.centralNotice.handleBannerLoaderError() (if an error was\n\t\t// handled on the server).\n\t\t$.ajax( {\n\t\t\turl: url.toString(),\n\t\t\tdataType: 'script',\n\t\t\tcache: true\n\t\t} ).fail( ( jqXHR, status, error ) => {\n\t\t\tcn.handleBannerLoaderError( status + ': ' + error );\n\t\t} );\n\t}\n\n\tfunction injectBannerHTML( bannerHtml ) {\n\n\t\t// The centralNotice div should already have been added by\n\t\t// ext.centralNotice.startUp.\n\n\t\t// Inject the HTML\n\t\t$( 'div#centralNotice' )\n\t\t\t.attr(\n\t\t\t\t'class',\n\t\t\t\tmw.html.escape( 'cn-' + cn.internal.state.getData().bannerCategory )\n\t\t\t)\n\t\t\t.prepend( bannerHtml );\n\n\t\tif ( window.performance && performance.mark ) {\n\t\t\tperformance.mark( 'mwCentralNoticeBanner' );\n\t\t}\n\t}\n\n\t/**\n\t * Adds reallyRecordImpression() as the last handler for cn.recordImpressionDeferredObj,\n\t * then resolves.\n\t */\n\tfunction resolveRecordImpressionDeferred() {\n\t\tcn.recordImpressionDeferredObj.done( reallyRecordImpression );\n\t\tcn.recordImpressionDeferredObj.resolve();\n\t}\n\n\tfunction recordImpression() {\n\t\tlet timeoutHasRun = false;\n\n\t\tif ( cn.recordImpressionDelayPromises.length === 0 ) {\n\t\t\treallyRecordImpression();\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are promises in cn.recordImpressionDelayPromises, then\n\t\t// cn.recordImpressionDeferredObj (used in resolveRecordImpressionDeferred())\n\t\t// should already have been set.\n\n\t\tconst timeout = setTimeout( () => {\n\t\t\ttimeoutHasRun = true;\n\t\t\tresolveRecordImpressionDeferred();\n\t\t}, MAX_RECORD_IMPRESSION_DELAY );\n\n\t\t// This function can only run once, so checking that the timeout hasn't run yet\n\t\t// should be sufficient to prevent extra record impression calls.\n\t\t$.when.apply( $, cn.recordImpressionDelayPromises ).always( () => {\n\t\t\tif ( !timeoutHasRun ) {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t\tresolveRecordImpressionDeferred();\n\t\t\t}\n\t\t} );\n\t}\n\n\tfunction reallyRecordImpression() {\n\t\tconst state = cn.internal.state,\n\t\t\trandom = Math.random();\n\n\t\tlet dataCopy;\n\t\t// Legacy record impression\n\t\tif ( random <= state.getData().recordImpressionSampleRate ) {\n\t\t\tconst url = new URL( mw.config.get( 'wgCentralBannerRecorder' ), location );\n\t\t\tdataCopy = state.getDataCopy( true );\n\t\t\tObject.keys( dataCopy ).forEach(\n\t\t\t\t( key ) => url.searchParams.append( key, dataCopy[ key ] )\n\t\t\t);\n\t\t\tsendBeacon( url.toString() );\n\t\t}\n\n\t\t// Impression event\n\t\tif ( random <= state.getData().impressionEventSampleRate ) {\n\t\t\tdataCopy = dataCopy || state.getDataCopy( true );\n\t\t\tmw.eventLog.logEvent( IMPRESSION_EVENT_LOGGING_SCHEMA, dataCopy );\n\t\t}\n\t}\n\n\tfunction sendBeacon( urlStr ) {\n\t\tif ( navigator.sendBeacon ) {\n\t\t\ttry {\n\t\t\t\tnavigator.sendBeacon( urlStr );\n\t\t\t} catch ( e ) {}\n\t\t} else {\n\t\t\tsetTimeout( () => {\n\t\t\t\tdocument.createElement( 'img' ).src = urlStr;\n\t\t\t}, 0 );\n\t\t}\n\t}\n\n\tfunction reallyChooseAndMaybeDisplay() {\n\n\t\tconst chooser = cn.internal.chooser,\n\t\t\tbucketer = cn.internal.bucketer,\n\t\t\tstate = cn.internal.state,\n\t\t\thide = cn.internal.hide;\n\n\t\t// This will gather initial data needed for selection and display.\n\t\tstate.setUp();\n\n\t\t// Because of browser limitations, and to maintain our contract among\n\t\t// components of this module, we have to do this here.\n\t\tsetUpDataProperty();\n\n\t\t// Bow out and show no banners if choice data seems stale\n\t\tif ( !chooser.choiceDataSeemsFresh( cn.choiceData ) ) {\n\t\t\tstate.setChoiceDataStale();\n\t\t\treturn;\n\t\t}\n\n\t\t// Below, we explicitly pass information from state to other\n\t\t// internal objects, which are not allowed to have dependencies.\n\t\t// While this could be made more compact by allowing internal\n\t\t// objects to access state for themselves, disallowing it ensures\n\t\t// their scope is limited and keeps the information flow visible.\n\n\t\t// First, get a list of campiangs targeting this pageview, and pass it\n\t\t// to state. (choiceData typically has more campaigns than actually\n\t\t// could be shown on a given pageview, since many targeting criteria\n\t\t// are not available to the server process that generates choiceData.)\n\t\t// These are the campaigns that may be selected in the fallback loop,\n\t\t// below.\n\t\tstate.setAvailableCampaigns( chooser.makeAvailableCampaigns(\n\t\t\tcn.choiceData,\n\t\t\tstate.getData().country,\n\t\t\tstate.getData().region,\n\t\t\tstate.getData().anonymous,\n\t\t\tstate.getData().device\n\t\t) );\n\n\t\t// Set maximum iterations for the loop below. Pick the lowest between the\n\t\t// configured limit and the total available campaigns.\n\t\tconst maxCampaignFallbackConfig = mw.config.get( 'wgCentralNoticeMaxCampaignFallback' );\n\t\tconst maxCampaignFallback = Math.min(\n\t\t\tstate.getData().availableCampaigns.length,\n\t\t\tmaxCampaignFallbackConfig\n\t\t);\n\n\t\tlet campaign;\n\t\t// Fallback loop. Try to display something until we're out of choices.\n\t\tfor ( let i = 0; i < maxCampaignFallback; i++ ) {\n\n\t\t\t// Try to choose a campaign. Just because we choose one doesn't necessarily\n\t\t\t// mean it'll display a banner, though. (That's why we set it as\n\t\t\t// 'attempting' below.)\n\t\t\tcampaign = chooser.chooseCampaign(\n\t\t\t\tstate.getData().availableCampaigns,\n\t\t\t\tstate.getData().randomcampaign\n\t\t\t);\n\n\t\t\t// Nothing is selected. That can happen following a roll of the dice if\n\t\t\t// all the campaigns currently available are throttled.\n\t\t\tif ( campaign === null ) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Now that we have a campaign, send some info to other objects\n\t\t\tstate.setAttemptingCampaign( campaign );\n\t\t\tbucketer.setCampaign( campaign );\n\t\t\thide.setCategory( state.getData().campaignCategory );\n\n\t\t\tif ( cn.kvStore ) {\n\t\t\t\tcn.kvStore.setCampaignName( state.getData().campaign );\n\t\t\t\tcn.kvStore.setCategory( state.getData().campaignCategory );\n\t\t\t}\n\n\t\t\t// Get a bucket\n\t\t\tbucketer.process();\n\t\t\tstate.setBucket( bucketer.getBucket() );\n\t\t\tstate.setReducedBucket( bucketer.getReducedBucket() );\n\n\t\t\t// Check user preferences for campaign type displaying\n\t\t\tif ( !state.getData().anonymous ) {\n\t\t\t\t// Do not check user preferences on anon users\n\t\t\t\tif (\n\t\t\t\t\tcampaign.type === 0 ||\n\t\t\t\t\tstate.getData().optedOutCampaigns.includes( campaign.type )\n\t\t\t\t) {\n\t\t\t\t\t// User opted out of viewing this type of campaigns\n\t\t\t\t\t// or campaign does not have a type set\n\t\t\t\t\t// TODO Consolidate code below and code in shouldHide() conditional\n\t\t\t\t\t// in a function.\n\t\t\t\t\tstate.failCampaign( 'userOptOut' );\n\t\t\t\t\trunPreBannerHooks();\n\t\t\t\t\trunPostBannerOrFailHooks();\n\n\t\t\t\t\t// Update available campaigns\n\t\t\t\t\tfallbackLoopUpdateAvailableCampaigns( i );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check the hide cookie and possibly fail the campaign.\n\t\t\t// We do this before running pre-banner hooks so that these can count\n\t\t\t// stuff differently if there was a hide cookie.\n\t\t\thide.processCookie();\n\t\t\tif ( hide.shouldHide() ) {\n\t\t\t\tstate.failCampaign( hide.getReason() );\n\t\t\t\trunPreBannerHooks();\n\t\t\t\trunPostBannerOrFailHooks();\n\n\t\t\t\t// Update available campaigns\n\t\t\t\tfallbackLoopUpdateAvailableCampaigns( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trunPreBannerHooks();\n\n\t\t\t// If a pre-banner hook cancelled the campaign, then wrap up this iteration\n\t\t\t// of the fallback loop.\n\t\t\tif ( state.isCampaignFailed() ) {\n\t\t\t\trunPostBannerOrFailHooks();\n\n\t\t\t\t// Update available campaigns\n\t\t\t\tfallbackLoopUpdateAvailableCampaigns( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Reaching this point means that a campaign was chosen and was not failed.\n\t\t\tbreak;\n\n\t\t}\n\n\t\t// Bow out if no campaign was ever even attempted. This can happen if no campaigns\n\t\t// were available, or if we rolled the dice once, but no campaign was chosen\n\t\t// due to throttling of all available campaigns.\n\t\tif ( state.getAttemptingCampaign() === null ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If the last campaign attempted in the loop (and any previous ones that may have\n\t\t// been attempted) failed, run post-display-process hooks for all attempted\n\t\t// campaigns, then record impression and bow out.\n\t\tif ( state.isCampaignFailed() ) {\n\t\t\trunFinalizeChooseAndMaybeDisplayHooks();\n\t\t\trecordImpression();\n\t\t\treturn;\n\t\t}\n\n\t\t// Choose a banner. Because of how campaign and banner settings are organized, we\n\t\t// need to check logged-in status and device again. (This seems to indicate a\n\t\t// problem with our domain model.)\n\n\t\t// If a specific banner has been requested from a pre-banner hook, try to choose\n\t\t// it.\n\t\tlet banner;\n\t\tif ( requestedBannerName ) {\n\n\t\t\tbanner = chooser.requestBanner(\n\t\t\t\tcampaign,\n\t\t\t\tstate.getData().reducedBucket,\n\t\t\t\tstate.getData().anonymous,\n\t\t\t\tstate.getData().device,\n\t\t\t\trequestedBannerName\n\t\t\t);\n\n\t\t\tif ( !banner ) {\n\t\t\t\tstate.setRequestedBannerNotAvailable( requestedBannerName );\n\t\t\t}\n\n\t\t} else {\n\t\t\t// Otherwise, use a random number and banner weights to choose from among\n\t\t\t// banners available to the user in this campaign, in this bucket. (Most\n\t\t\t// of the time, there's only one.)\n\t\t\tbanner = chooser.chooseBanner(\n\t\t\t\tcampaign,\n\t\t\t\tstate.getData().reducedBucket,\n\t\t\t\tstate.getData().anonymous,\n\t\t\t\tstate.getData().device,\n\t\t\t\tstate.getData().randombanner\n\t\t\t);\n\n\t\t\tif ( !banner ) {\n\t\t\t\tstate.setNoBannerAvailable();\n\t\t\t}\n\t\t}\n\n\t\t// In either of the above cases, if no banner was selected, bow out.\n\t\tif ( !banner ) {\n\t\t\trunPostBannerOrFailHooks();\n\t\t\trunFinalizeChooseAndMaybeDisplayHooks();\n\t\t\trecordImpression();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass more info following banner selection\n\t\tstate.setBanner( banner );\n\n\t\tif ( cn.kvStore ) {\n\t\t\tcn.kvStore.setBannerName( banner.name );\n\t\t}\n\n\t\t// TODO From legacy; not sure it's useful\n\t\tcn.bannerData.bannerName = banner.name;\n\n\t\tsetUpBannerLoadedPromise();\n\n\t\t// Get the banner\n\t\t// The ajax response will call mw.centralNotice.insertBanner()\n\t\tfetchBanner();\n\t}\n\n\t/**\n\t * Convenience method used only by reallyChooseAndMaybeDisplay() to update available\n\t * campaigns within the fallback loop.\n\t *\n\t * @param {number} iteration\n\t */\n\tfunction fallbackLoopUpdateAvailableCampaigns( iteration ) {\n\t\tconst state = cn.internal.state;\n\n\t\tstate.setAvailableCampaigns( cn.internal.chooser.updateAvailableCampaigns(\n\t\t\tstate.getData().availableCampaigns,\n\t\t\tstate.getAttemptingCampaign(),\n\t\t\titeration\n\t\t) );\n\t}\n\n\t/**\n\t * Stuff we have to do following the call to fetch a banner (successful\n\t * or not)\n\t */\n\tfunction processAfterBannerFetch() {\n\n\t\t// If we're testing a banner, don't call Special:RecordImpression or\n\t\t// run mixin hooks.\n\t\tif ( !cn.internal.state.getData().testingBanner ) {\n\t\t\trunPostBannerOrFailHooks();\n\t\t\trunFinalizeChooseAndMaybeDisplayHooks();\n\t\t\trecordImpression();\n\t\t}\n\t}\n\n\t/**\n\t * CentralNotice base public object, exposed as mw.centralNotice. Note:\n\t * other CN modules may add properties to this object, and we add some\n\t * dynamically. These additional properties are:\n\t *\n\t *     choiceData: An array of campaigns possibly available to this user,\n\t *         along with data needed to chose one (or none). This contains\n\t *         everything the server can determine ahead-of-time about campaigns\n\t *         for this user. Added by ext.centralNotice.choiceData (see\n\t *         the PHP class CNChoiceDataResourceLoaderModule).\n\t *\n\t *     data: An object with more data for campaign/banner selection and\n\t *         display, and for recording what happened. Properties of this\n\t *         object should be easily serializable to URL parameters (i.e.,\n\t *         not objects or arrays). Note: this should be seen as read-only\n\t *         for any code outside this module. No properties that code in this\n\t *         module reacts to are available on mw.centralNotice.data. Also,\n\t *         it will be deprecated soon. Use getDataProperty( prop ) instead.\n\t *\n\t *     bannerLoadedPromise: A promise that resolves when a banner is loaded.\n\t *         This property is only set after a banner has been chosen.\n\t *         Campaign mixins can use a postBannerOrFailMixinHook instead. Following\n\t *         legacy code, we call the promise with an object containing\n\t *         (almost all) the same data that is sent to\n\t *         Special:RecordImpression (though this data is also now available\n\t *         via mw.centralNotice.data).\n\t *\n\t *     events.bannerLoaded: Legacy location of bannerLoadedPromise.\n\t *\n\t *     kvStore: Key-value store object, added by ext.centralNotice.kvStore,\n\t *         if that module has been loaded.\n\t *\n\t *     bannerHistoryLogger: Banner history logging feature, added by\n\t *         ext.centralNotice.bannerHistoryLogger, if that module has been\n\t *         loaded.\n\t */\n\tcn = {\n\t\t/**\n\t\t * Really insert the banner (without waiting for the DOM to be ready).\n\t\t * Only exposed for use in tests.\n\t\t *\n\t\t * @param {Object} bannerJson\n\t\t * @private\n\t\t */\n\t\treallyInsertBanner: function ( bannerJson ) {\n\n\t\t\tconst state = cn.internal.state;\n\t\t\tlet shownAfterLoadingBanner = true;\n\n\t\t\t// Inject the banner HTML into the DOM\n\t\t\tinjectBannerHTML( bannerJson.bannerHtml );\n\n\t\t\tbannerLoadedDeferredObj.resolve( cn.internal.state.getData() );\n\n\t\t\t// Process legacy hook for in-banner JS that hides banners after\n\t\t\t// they're loaded and/or adds data to send to\n\t\t\t// Special:RecordImpression. Only do this if\n\t\t\t// bannersNotGuaranteedToDisplay is set.\n\t\t\tif ( state.getData().bannersNotGuaranteedToDisplay ) {\n\t\t\t\tif ( typeof cn.bannerData.alterImpressionData === 'function' ) {\n\n\t\t\t\t\t// Data from state is considered read-only. This legacy hook\n\t\t\t\t\t// may add a 'reason' property to the object it receives.\n\t\t\t\t\t// So we send only a copy of the data and check the added\n\t\t\t\t\t// 'reason' property.\n\t\t\t\t\tconst tmpData = state.getDataCopy();\n\n\t\t\t\t\tshownAfterLoadingBanner =\n\t\t\t\t\t\tcn.bannerData.alterImpressionData( tmpData );\n\n\t\t\t\t\tif ( !shownAfterLoadingBanner ) {\n\t\t\t\t\t\tconst bannerLoadedButHiddenReason = tmpData.reason || '';\n\t\t\t\t\t\tstate.setBannerLoadedButHidden(\n\t\t\t\t\t\t\tbannerLoadedButHiddenReason\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( tmpData.banner_count ) {\n\t\t\t\t\t\tstate.setBannerCount( tmpData.banner_count );\n\t\t\t\t\t}\n\n\t\t\t\t} else {\n\t\t\t\t\tstate.setAlterFunctionMissing();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Banner shown following load (normal scenario)\n\t\t\tif ( shownAfterLoadingBanner ) {\n\t\t\t\tstate.setBannerShown();\n\t\t\t}\n\n\t\t\tprocessAfterBannerFetch();\n\t\t},\n\n\t\t/**\n\t\t * Promises to delay the record impression call, if possible; see\n\t\t * cn.requestRecordImpressionDelay(), below. Only exposed for use in tests.\n\t\t *\n\t\t * @private\n\t\t */\n\t\trecordImpressionDelayPromises: [],\n\n\t\t/**\n\t\t * For providing a jQuery.Promise to signal when the record impression call is\n\t\t * about to be sent. (Value will be set to a new deferred object only as needed.)\n\t\t *\n\t\t * @private\n\t\t */\n\t\trecordImpressionDeferredObj: null,\n\n\t\t/**\n\t\t * Attachment point for other objects in this module that are not meant\n\t\t * for outside use.\n\t\t */\n\t\tinternal: {},\n\n\t\t/**\n\t\t * Call this to indicate that banners in a campaign may not always\n\t\t * display to a user even if they're loaded, that is, that they may\n\t\t * contain logic that prevents them from showing after they're loaded.\n\t\t */\n\t\tsetBannersNotGuaranteedToDisplay: function () {\n\t\t\tcn.internal.state.setBannersNotGuaranteedToDisplay();\n\t\t},\n\n\t\t/**\n\t\t * Call this from the preBannerMixinHook to prevent a banner for the\n\t\t * currently attempting campaign from being chosen and loaded.\n\t\t *\n\t\t * @param {string} reason An explanation of why the banner was canceled.\n\t\t */\n\t\tfailCampaign: function ( reason ) {\n\t\t\tcn.internal.state.failCampaign( reason );\n\t\t},\n\n\t\t/**\n\t\t * Legacy method, deprecated. Use failCampaign().\n\t\t *\n\t\t * @param {string} reason\n\t\t */\n\t\tcancelBanner: function ( reason ) {\n\t\t\tcn.failCampaign( reason );\n\t\t},\n\n\t\tisCampaignFailed: function () {\n\t\t\treturn cn.internal.state.isCampaignFailed();\n\t\t},\n\n\t\t/**\n\t\t * Legacy metod, deprecated. Use isCampaignFailed().\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBannerCanceled: function () {\n\t\t\treturn cn.isCampaignFailed();\n\t\t},\n\n\t\tisBannerShown: function () {\n\t\t\treturn cn.internal.state.isBannerShown();\n\t\t},\n\n\t\t/**\n\t\t * Indicate that a banner was hidden after being loaded, and provide\n\t\t * a reason.\n\t\t *\n\t\t * @param {string} reason\n\t\t */\n\t\tsetBannerLoadedButHidden: function ( reason ) {\n\t\t\tcn.internal.state.setBannerLoadedButHidden( reason );\n\t\t},\n\n\t\t/**\n\t\t * Set the minimal sample rate for calling Special:RecordImpression. Default is\n\t\t * wgCentralNoticeSampleRate. Note that Special:RecordImpression will\n\t\t * not be called at all if a campaign was not chosen for this user. Also note\n\t\t * that the highest rate set will be used.\n\t\t *\n\t\t * @param {number} rate\n\t\t */\n\t\tsetMinRecordImpressionSampleRate: function ( rate ) {\n\t\t\tcn.internal.state.setMinRecordImpressionSampleRate( rate );\n\t\t},\n\n\t\t/**\n\t\t * Set the minimal sample rate for the logging of impression events (unless it was\n\t\t * overridden by a URL parameter, in which that takes precedence). Default is\n\t\t * wgCentralNoticeImpressionEventSampleRate. Also note that the highest rate set\n\t\t * will be used.\n\t\t *\n\t\t * @param {number} rate\n\t\t */\n\t\tsetMinImpressionEventSampleRate: function ( rate ) {\n\t\t\tcn.internal.state.setMinImpressionEventSampleRate( rate );\n\t\t},\n\n\t\t/**\n\t\t * Legacy object used by in-banner scripts to store and pass data about.\n\t\t * Also, if a function is set on the alterImpressionData property, that\n\t\t * function will be called after the banner HTML has been injected.\n\t\t * Returning false from that function indicates that the banner was\n\t\t * not actually shown. Note: this may be deprecated. If possible, use\n\t\t * setBannerLoadedButHidden() instead.\n\t\t */\n\t\tbannerData: {},\n\n\t\t/**\n\t\t * Base class for campaign-associated mixins (defined above).\n\t\t */\n\t\tMixin: Mixin,\n\n\t\t/**\n\t\t * Register a campaign-associated mixin to make it available for campaigns\n\t\t * to use it. Should be called for every campaign-associated mixin.\n\t\t *\n\t\t * @param {mw.centralNotice.Mixin} mixin\n\t\t */\n\t\tregisterCampaignMixin: function ( mixin ) {\n\t\t\tcampaignMixins[ mixin.name ] = mixin;\n\t\t},\n\n\t\t/**\n\t\t * Select a campaign and a banner, run hooks, and maybe display a\n\t\t * banner.\n\t\t * Note: cn.choiceData must be set before this is called\n\t\t */\n\t\tchooseAndMaybeDisplay: function () {\n\n\t\t\t// Make sure GeoIP info is available before processing\n\n\t\t\t// geoIP usually doesn't make background requests; however, it may\n\t\t\t// make one if location data wasn't retrievable from a cookie. We\n\t\t\t// use a callback just in case, even though most of the time, the\n\t\t\t// callback executes without delay.\n\n\t\t\t// TODO Take GeoIP out of CentralNotice.\n\t\t\t// See https://phabricator.wikimedia.org/T102848.\n\n\t\t\tmw.geoIP.getPromise()\n\t\t\t\t.fail( cn.internal.state.setInvalidGeoData )\n\t\t\t\t.done( cn.internal.state.setGeoData )\n\t\t\t\t.always( reallyChooseAndMaybeDisplay );\n\t\t},\n\n\t\tdisplayTestingBanner: function () {\n\n\t\t\t// We gather the same data as for normal banner display, plus\n\t\t\t// campaign and banner.\n\t\t\tmw.geoIP.getPromise()\n\t\t\t\t.fail( cn.internal.state.setInvalidGeoData )\n\t\t\t\t.done( cn.internal.state.setGeoData )\n\t\t\t\t.always( () => {\n\t\t\t\t\tcn.internal.state.setUpForTestingBanner();\n\t\t\t\t\tsetUpDataProperty();\n\t\t\t\t\tsetUpBannerLoadedPromise();\n\t\t\t\t\tfetchOrRetrieveBanner();\n\t\t\t\t} );\n\t\t},\n\n\t\tinsertBanner: function ( bannerJson ) {\n\n\t\t\t// Insert the banner only after the DOM is ready\n\t\t\t$( () => {\n\t\t\t\tcn.reallyInsertBanner( bannerJson );\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Handle a banner loader error, with an optional message\n\t\t *\n\t\t * @param {string} [msg]\n\t\t */\n\t\thandleBannerLoaderError: function ( msg ) {\n\t\t\tcn.internal.state.setBannerLoaderError( msg );\n\t\t\tbannerLoadedDeferredObj.reject( cn.internal.state.getData() );\n\t\t\tprocessAfterBannerFetch();\n\t\t},\n\n\t\thideBannerWithCloseButton: function () {\n\t\t\t// Hide the banner element\n\t\t\t$( '#centralNotice' ).hide();\n\t\t\tcn.internal.hide.setHideWithCloseButtonCookies();\n\t\t},\n\n\t\tcustomHideBanner: function ( reason, duration ) {\n\t\t\t// Hide the banner element\n\t\t\t$( '#centralNotice' ).hide();\n\t\t\tcn.internal.hide.setHideCookies( reason, duration );\n\t\t},\n\n\t\thideBanner: function () {\n\t\t\tcn.hideBannerWithCloseButton();\n\t\t},\n\n\t\t/**\n\t\t * Set and store this user's bucket for the current campaign. The\n\t\t * bucketer must be initialized first. However, code in campaign mixin\n\t\t * hook handlers and banners can safely assume that's the case.\n\t\t *\n\t\t * The current bucket can be read using\n\t\t * mw.centralNotice.getDataProperty( 'bucket' )\n\t\t *\n\t\t * @param {number} bucket\n\t\t */\n\t\tsetBucket: function ( bucket ) {\n\t\t\tcn.internal.bucketer.setBucket( bucket );\n\t\t\tcn.internal.state.setBucket( bucket );\n\t\t\tcn.internal.state.setReducedBucket( cn.internal.bucketer.getReducedBucket() );\n\t\t},\n\n\t\t/**\n\t\t * Request a specific banner be displayed. This may be called before a banner\n\t\t * has been selected (for example, from a pre-banner hook). To be shown, the\n\t\t * banner must be among the banners assigned to the user's bucket for the\n\t\t * selected campaign, and must be available for the user's logged in status\n\t\t * and device. If the requested banner can't be displayed, no banner will be\n\t\t * shown.\n\t\t *\n\t\t * @param {string} banner The name of the banner to request\n\t\t */\n\t\trequestBanner: function ( banner ) {\n\t\t\trequestedBannerName = banner;\n\t\t},\n\n\t\t/**\n\t\t * Register that the current page view is included in a test.\n\t\t *\n\t\t * @param {string} identifier A string to identify the test. Should not contain\n\t\t *   commas.\n\t\t */\n\t\tregisterTest: function ( identifier ) {\n\t\t\tcn.internal.state.registerTest( identifier );\n\t\t},\n\n\t\t/**\n\t\t * Set a string with information for debugging. (All strings set here will be\n\t\t * sent to the server via the debugInfo parameter on the record impression call).\n\t\t *\n\t\t * @param {string} str A string with the debugging information; should not\n\t\t *   contain pipe characters ('|').\n\t\t */\n\t\tsetDebugInfo: function ( str ) {\n\t\t\tcn.internal.state.setDebugInfo( str );\n\t\t},\n\n\t\t/**\n\t\t * Request that, if possible, the record impression call be delayed until a\n\t\t * promise is resolved. If the promise does not resolve before\n\t\t * MAX_RECORD_IMPRESSION_DELAY milliseconds after the banner is injected,\n\t\t * the call will be made in any case.\n\t\t *\n\t\t * Returns another promise that will resolve immediately before the record\n\t\t * impression call is made.\n\t\t *\n\t\t * @param {jQuery.Promise} promise\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\trequestRecordImpressionDelay: function ( promise ) {\n\t\t\tcn.recordImpressionDelayPromises.push( promise );\n\t\t\tcn.recordImpressionDeferredObj = cn.recordImpressionDeferredObj || $.Deferred();\n\t\t\treturn cn.recordImpressionDeferredObj.promise();\n\t\t},\n\n\t\t/**\n\t\t * Get the value of a property used in campaign/banner selection and\n\t\t * display, and for recording the results of that process.\n\t\t *\n\t\t * @param {string} prop\n\t\t * @return {any}\n\t\t */\n\t\tgetDataProperty: function ( prop ) {\n\t\t\treturn cn.internal.state.getData()[ prop ];\n\t\t}\n\t};\n\n\t// For testing purposes only.\n\tif ( window.QUnit ) {\n\t\tcn.test = {\n\t\t\tgetBannerUrl\n\t\t};\n\t}\n\n\t// Expose cn. Note that there are situations in which a base\n\t// mw.centralNotice object may already have been created by another\n\t// CentralNotice module. (Other CN modules sometimes have to load before\n\t// this one.)\n\tif ( mw.centralNotice === undefined ) {\n\t\tmw.centralNotice = cn;\n\t} else {\n\t\tObject.assign( mw.centralNotice, cn );\n\t\tcn = mw.centralNotice; // Update the closured-in local variable\n\t}\n\n\t// Set up deprecated access points and warnings\n\tmw.log.deprecate(\n\t\twindow, 'insertBanner', cn.insertBanner,\n\t\t'Use mw.centralNotice method instead'\n\t);\n\n\tmw.log.deprecate(\n\t\twindow, 'hideBanner', cn.hideBanner,\n\t\t'Use mw.centralNotice method instead'\n\t);\n\n\tmw.log.deprecate(\n\t\twindow, 'cancelBanner', cn.cancelBanner,\n\t\t'Use mw.centralNotice.failCampaign() instead'\n\t);\n\n\tmw.log.deprecate(\n\t\twindow, 'isBannerCanceled', cn.isBannerCanceled,\n\t\t'Use mw.centralNotice.isCampaignFailed() instead'\n\t);\n\n\tmw.log.deprecate(\n\t\twindow, 'toggleNotice', cn.hideBanner,\n\t\t'Use mw.centralNotice method instead'\n\t);\n\n\t// Execute the other files\n\t// TODO: Convert to using module.exports\n\trequire( './state.js' );\n\trequire( './chooser.js' );\n\trequire( './bucketer.js' );\n\trequire( './hide.js' );\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.display/state.js","messages":[],"suppressedMessages":[{"ruleId":"camelcase","severity":2,"message":"Identifier 'banner_count' is not in camel case.","line":565,"column":15,"nodeType":"Identifier","messageId":"notCamelCase","endLine":565,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.kvStore/index.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.kvStore/kvStore.js","messages":[{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":83,"column":18,"nodeType":"Identifier","messageId":"noStorage","endLine":83,"endColumn":30},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":87,"column":6,"nodeType":"Identifier","messageId":"noStorage","endLine":87,"endColumn":18},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":89,"column":9,"nodeType":"Identifier","messageId":"noStorage","endLine":89,"endColumn":21},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":91,"column":6,"nodeType":"Identifier","messageId":"noStorage","endLine":91,"endColumn":18},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":198,"column":4,"nodeType":"Identifier","messageId":"noStorage","endLine":198,"endColumn":16},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":202,"column":9,"nodeType":"Identifier","messageId":"noStorage","endLine":202,"endColumn":21},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":230,"column":15,"nodeType":"Identifier","messageId":"noStorage","endLine":230,"endColumn":27},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":259,"column":6,"nodeType":"Identifier","messageId":"noStorage","endLine":259,"endColumn":18},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":298,"column":4,"nodeType":"Identifier","messageId":"noStorage","endLine":298,"endColumn":16}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Module for key-value storage in localStorage, used by CentralNotice campaign\n * mixins and in-banner JS.\n *\n * This class is made available at mw.centralNotice.kvStore\n */\n( function () {\n\n\tlet error = null,\n\t\tcampaignName = null,\n\t\tbannerName = null,\n\t\tcategory = null,\n\t\tcookiesEnabled = null,\n\t\tlocalStorageAvailable = null,\n\t\tkvStore = null;\n\tconst now = Math.round( Date.now() / 1000 ),\n\n\t\tSEPARATOR = '|',\n\n\t\t// | gets encoded in cookies, but is already in use in localStorage\n\t\tSEPARATOR_IN_COOKIES = '!',\n\n\t\tFIND_KEY_REGEX = /\\|([^|]*)$/,\n\n\t\t// Prefix for all localStorage keys.\n\t\t// Must correspond with PREFIX_REGEX from kvStoreMaintenance.\n\t\tPREFIX = 'CentralNoticeKV',\n\n\t\t// In cookies, keep it short\n\t\tPREFIX_IN_COOKIES = 'CN',\n\n\t\t// Default TTL of KV store items is 1/2 year, in seconds\n\t\tDEFAULT_ITEM_TTL = ( 365 / 2 ) * 60 * 60 * 24;\n\n\t/**\n\t * A context for key-value storage.\n\t *\n\t * @class KVStorageContext\n\t * @param {string} key A unique string to identify this context, when using\n\t *   LocalStorage. Must not contain SEPARATOR.\n\t * @param {string} keyInCookies A unique string to identify this context,\n\t *   when using cookies. Must not contain SEPARATOR_IN_COOKIES. (Distinct\n\t *   keys for cookies help keep cookies small, improving performance.)\n\t */\n\tconst KVStorageContext = function ( key, keyInCookies ) {\n\t\tthis.key = key;\n\t\tthis.keyInCookies = keyInCookies;\n\t};\n\n\t/**\n\t * Are cookies enabled on this client?\n\t * TODO Should this go in core?\n\t *\n\t * @return {boolean}\n\t */\n\tfunction areCookiesEnabled() {\n\t\t// On the first call, set a cookie and try to read it back\n\t\tif ( cookiesEnabled === null ) {\n\n\t\t\tmw.cookie.set( 'cookieTest', 'testVal' );\n\t\t\tcookiesEnabled = ( mw.cookie.get( 'cookieTest' ) === 'testVal' );\n\t\t\t// Clear it out\n\t\t\tmw.cookie.set( 'cookieTest', null );\n\t\t}\n\n\t\treturn cookiesEnabled;\n\t}\n\n\t/**\n\t * Is LocalStorage available as a storage option? (Browser\n\t * compatibility and certain user privacy options are required.)\n\t *\n\t * @return {boolean}\n\t */\n\tfunction isLocalStorageAvailable() {\n\t\tif ( localStorageAvailable === null ) {\n\n\t\t\t// For the KV store to work, the browser has to support\n\t\t\t// localStorage, and not throw an error if we try to access or use\n\t\t\t// it. (An error can be thrown if the user completely disables\n\t\t\t// offline website data/cookies, and in a few other circumstances)\n\t\t\ttry {\n\t\t\t\tif ( !window.localStorage ) {\n\t\t\t\t\tlocalStorageAvailable = false;\n\n\t\t\t\t} else {\n\t\t\t\t\tlocalStorage.setItem( 'localStorageTest', 'testVal' );\n\t\t\t\t\tlocalStorageAvailable =\n\t\t\t\t\t\t( localStorage.getItem( 'localStorageTest' ) === 'testVal' );\n\n\t\t\t\t\tlocalStorage.removeItem( 'localStorageTest' );\n\t\t\t\t}\n\t\t\t} catch ( e ) {\n\t\t\t\tlocalStorageAvailable = false;\n\t\t\t}\n\t\t}\n\n\t\treturn localStorageAvailable;\n\t}\n\n\t/**\n\t * Flag that a problem with key-value storage occurred, and log via mw.log.\n\t *\n\t * @param {string} message A message about the error\n\t * @param {string} key\n\t * @param {*} value\n\t * @param {KVStorageContext} context\n\t */\n\tfunction setError( message, key, value, context ) {\n\t\terror = {\n\t\t\tmessage: message,\n\t\t\tkey: key,\n\t\t\tvalue: value,\n\t\t\tcontext: context ? context.key : null,\n\t\t\ttime: new Date()\n\t\t};\n\n\t\t// If a campaign and/or a banner name have been set, include their names\n\t\t// in the error\n\t\terror.campaign = campaignName;\n\t\terror.banner = bannerName;\n\n\t\tmw.log( 'CentralNotice KV storage error: ' + JSON.stringify( error ) );\n\t}\n\n\t/**\n\t * Return the actual key to be used in localStorage, for the given key and\n\t * context.\n\t *\n\t * The key returned should be unique among all localStorage keys used by\n\t * this site. It includes unique strings for centralNotice and context, and\n\t * may also include the campaign name or category.\n\t *\n\t * @param {string} key\n\t * @param {KVStorageContext} context\n\t * @return {string}\n\t */\n\tfunction makeKeyForLocalStorage( key, context ) {\n\t\tconst base = PREFIX + SEPARATOR + context.key + SEPARATOR;\n\n\t\tswitch ( context.key ) {\n\t\t\tcase kvStore.contexts.CAMPAIGN.key:\n\t\t\t\treturn base + campaignName + SEPARATOR + key;\n\n\t\t\tcase kvStore.contexts.CATEGORY.key:\n\t\t\t\treturn base + category + SEPARATOR + key;\n\n\t\t\tcase kvStore.contexts.GLOBAL.key:\n\t\t\t\treturn base + key;\n\n\t\t\tdefault:\n\t\t\t\tsetError( 'Invalid KV storage context', key, null, context );\n\t\t\t\treturn base + 'invalidContext' + SEPARATOR + key;\n\t\t}\n\t}\n\n\t/**\n\t * Return the actual key to be used for a cookie (i.e., the cookie name)\n\t * for the given key and context.\n\t *\n\t * Note: the key used in cookies contains the same information as the\n\t * key used for localStorage, though the cookie key will be shorter.\n\t *\n\t * @param {string} key\n\t * @param {KVStorageContext} context\n\t * @return {string}\n\t */\n\tfunction makeKeyForCookie( key, context ) {\n\t\tconst base = PREFIX_IN_COOKIES + SEPARATOR_IN_COOKIES +\n\t\t\tcontext.keyInCookies + SEPARATOR_IN_COOKIES;\n\n\t\tswitch ( context.key ) {\n\t\t\tcase kvStore.contexts.CAMPAIGN.key:\n\t\t\t\treturn base + campaignName + SEPARATOR_IN_COOKIES + key;\n\n\t\t\tcase kvStore.contexts.CATEGORY.key:\n\t\t\t\treturn base + category + SEPARATOR_IN_COOKIES + key;\n\n\t\t\tcase kvStore.contexts.GLOBAL.key:\n\t\t\t\treturn base + key;\n\n\t\t\tdefault:\n\t\t\t\tsetError( 'Invalid KV storage context', key, null, context );\n\t\t\t\treturn base + 'invalidContext' + SEPARATOR_IN_COOKIES + key;\n\t\t}\n\t}\n\n\tfunction setLocalStorageItem( key, value, context, ttl ) {\n\t\tconst lsKey = makeKeyForLocalStorage( key, context );\n\t\tconst encodedWrappedValue = JSON.stringify( {\n\t\t\texpiry: ttl ? ( ttl * 86400 ) + now : DEFAULT_ITEM_TTL + now,\n\t\t\tval: value\n\t\t} );\n\n\t\t// Write the value\n\t\ttry {\n\n\t\t\tlocalStorage.setItem( lsKey, encodedWrappedValue );\n\n\t\t\t// Check that it was written (it might not have been, if we're over\n\t\t\t// the localStorage quota for this site, for example)\n\t\t\tif ( localStorage.getItem( lsKey ) !== encodedWrappedValue ) {\n\t\t\t\tsetError( 'Couldn\\'t write value', key, value, context );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\n\t\t} catch ( e ) {\n\t\t\tsetError( 'Couldn\\'t write value due to LocalStorage exception ' +\n\t\t\t\te.toString(), key, value, context );\n\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfunction setCookieItem( key, value, context, ttl ) {\n\t\treturn Boolean( $.cookie(\n\t\t\tmakeKeyForCookie( key, context ),\n\t\t\tencodeURIComponent( JSON.stringify( value ) ),\n\t\t\t{ expires: ttl, path: '/' }\n\t\t) );\n\t}\n\n\tfunction getLocalStorageItem( key, context ) {\n\t\tconst lsKey = makeKeyForLocalStorage( key, context );\n\t\tlet rawValue, wrappedValue;\n\n\t\ttry {\n\t\t\trawValue = localStorage.getItem( lsKey );\n\n\t\t} catch ( e ) {\n\t\t\tsetError( 'Couldn\\'t read value due to LocalStorage exception ' +\n\t\t\t\te.toString(), key, null, context );\n\n\t\t\treturn null;\n\t\t}\n\n\t\tif ( rawValue === null ) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\twrappedValue = JSON.parse( rawValue );\n\n\t\t} catch ( e ) {\n\t\t\t// FIXME: Consider detecting out-of-space errors and perform\n\t\t\t// garbage-collection immediately, starting by removing the oldest\n\t\t\t// expired (and unexpired) keys older than a certain threshold.\n\n\t\t\t// If the JSON couldn't be parsed, log and return null (which is\n\t\t\t// the same value we'd get if the key were not set).\n\t\t\tif ( e instanceof SyntaxError ) {\n\n\t\t\t\tsetError( 'Couldn\\'t parse value, removing. ' + e.message,\n\t\t\t\t\tkey, rawValue, context );\n\n\t\t\t\ttry {\n\t\t\t\t\tlocalStorage.removeItem( lsKey );\n\t\t\t\t} catch ( ex ) {\n\t\t\t\t\tsetError( 'Couldn\\'t remove value due to LocalStorage exception ' +\n\t\t\t\t\t\tex.toString(), key, rawValue, context );\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\n\t\t\t// For any other errors, set and re-throw\n\t\t\t} else {\n\t\t\t\tsetError( 'Couldn\\'t read value ' + e.message,\n\t\t\t\t\tkey, rawValue, context );\n\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t}\n\n\t\tif ( !wrappedValue.expiry || wrappedValue.expiry < now ) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn wrappedValue.val;\n\t}\n\n\tfunction getCookieItem( key, context ) {\n\t\tconst storageKey = makeKeyForCookie( key, context ),\n\t\t\trawCookie = $.cookie( storageKey );\n\n\t\ttry {\n\t\t\treturn JSON.parse( decodeURIComponent( rawCookie ) );\n\t\t} catch ( e ) {\n\t\t\t// The cookie is probably corrupt. Remove.\n\t\t\t$.removeCookie( storageKey, { path: '/' } );\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tfunction removeLocalStorageItem( key, context ) {\n\t\ttry {\n\t\t\tlocalStorage.removeItem( makeKeyForLocalStorage( key, context ) );\n\n\t\t} catch ( e ) {\n\t\t\tsetError( 'Couldn\\'t remove value due to LocalStorage exception ' +\n\t\t\t\te.toString(), key, null, context );\n\t\t}\n\t}\n\n\tfunction removeCookieItem( key, context ) {\n\t\t$.removeCookie( makeKeyForCookie( key, context ), { path: '/' } );\n\t}\n\n\t/**\n\t * Public API\n\t */\n\tkvStore = {\n\n\t\t/**\n\t\t * Available key-value storage contexts\n\t\t *\n\t\t * @enum\n\t\t * @readonly\n\t\t */\n\t\tcontexts: {\n\t\t\tCAMPAIGN: new KVStorageContext( 'campaign', 'c' ),\n\t\t\tCATEGORY: new KVStorageContext( 'category', 't' ),\n\t\t\tGLOBAL: new KVStorageContext( 'global', 'g' )\n\t\t},\n\n\t\t/**\n\t\t * Options for storing data with a cookie or with the kvStore\n\t\t * (LocalStorage).\n\t\t *\n\t\t * @enum\n\t\t * @readonly\n\t\t */\n\t\tmultiStorageOptions: {\n\t\t\tLOCAL_STORAGE: 'kv_store',\n\t\t\tCOOKIE: 'cookie',\n\t\t\tNO_STORAGE: 'no_storage'\n\t\t},\n\n\t\t/**\n\t\t * Set the given value for the given key in the given context, using\n\t\t * LocalStorage or a cookie. If the key already exists, its value will\n\t\t * be overwritten.\n\t\t *\n\t\t * Value can be any type; will be json-encoded.\n\t\t *\n\t\t * Only when using LocalStorage: if the value was set, return true; if\n\t\t * the value could not be set, we log the error via mw.log and return\n\t\t * false. The error will be available via getError().\n\t\t *\n\t\t * Note: check isAvailable() before calling, or provide a\n\t\t * multiStorageOption.\n\t\t *\n\t\t * Note: when using CAMPAIGN and CATEGORY contexts, ensure that you have\n\t\t * set campaign and category, respectively. We don't check them here.\n\t\t *\n\t\t * @param {string} key\n\t\t * @param {*} value\n\t\t * @param {KVStorageContext} context\n\t\t *\n\t\t * @param {number} [ttl] Time to live for this item, in days; defaults\n\t\t *   to 1/2 a year. Null will trigger the default.\n\t\t *\n\t\t * @param {string} [multiStorageOption] A key from among\n\t\t *   kvStore.multiStorageOptions, to indicate how to store the item.\n\t\t *   Defaults to kvStore.multiStorageOptions.LOCAL_STORAGE.\n\t\t *\n\t\t * @return {boolean} true if the value could be set, false otherwise\n\t\t */\n\t\tsetItem: function ( key, value, context, ttl, multiStorageOption ) {\n\t\t\t// Check validity of key\n\t\t\tif ( ( key.includes( SEPARATOR ) ) ||\n\t\t\t\t( key.includes( SEPARATOR_IN_COOKIES ) ) ) {\n\n\t\t\t\tsetError( 'Invalid key', key, value, context );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tmultiStorageOption =\n\t\t\t\tmultiStorageOption || kvStore.multiStorageOptions.LOCAL_STORAGE;\n\n\t\t\tswitch ( multiStorageOption ) {\n\n\t\t\t\tcase kvStore.multiStorageOptions.LOCAL_STORAGE:\n\t\t\t\t\treturn setLocalStorageItem( key, value, context, ttl );\n\n\t\t\t\tcase kvStore.multiStorageOptions.COOKIE:\n\t\t\t\t\treturn setCookieItem( key, value, context, ttl );\n\n\t\t\t\tcase kvStore.multiStorageOptions.NO_STORAGE:\n\t\t\t\t\treturn false;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new Error( 'Unexpected multi-storage option' );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Get the stored value for the given key in the given context.\n\t\t *\n\t\t * Note: check isAvailable() before calling.\n\t\t *\n\t\t * @param {string} key\n\t\t * @param {KVStorageContext} context\n\t\t * @param {string} [multiStorageOption] A key from among\n\t\t *   kvStore.multiStorageOptions, to indicate how to store the item.\n\t\t *   Defaults to kvStore.multiStorageOptions.LOCAL_STORAGE.\n\t\t *   @return {any}\n\t\t */\n\t\tgetItem: function ( key, context, multiStorageOption ) {\n\t\t\tmultiStorageOption =\n\t\t\t\tmultiStorageOption || kvStore.multiStorageOptions.LOCAL_STORAGE;\n\n\t\t\tswitch ( multiStorageOption ) {\n\n\t\t\t\tcase kvStore.multiStorageOptions.LOCAL_STORAGE:\n\t\t\t\t\treturn getLocalStorageItem( key, context );\n\n\t\t\t\tcase kvStore.multiStorageOptions.COOKIE:\n\t\t\t\t\treturn getCookieItem( key, context );\n\n\t\t\t\tcase kvStore.multiStorageOptions.NO_STORAGE:\n\t\t\t\t\treturn null;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new Error( 'Unexpected multi-storage option' );\n\t\t\t}\n\n\t\t},\n\n\t\t/**\n\t\t * Remove the stored value for the given key in the given context\n\t\t *\n\t\t * Note: check isAvailable() before calling.\n\t\t *\n\t\t * @param {string} key\n\t\t * @param {KVStorageContext} context\n\t\t * @param {string} [multiStorageOption] A key from among\n\t\t *   kvStore.multiStorageOptions, to indicate how to store the item.\n\t\t *   Defaults to kvStore.multiStorageOptions.LOCAL_STORAGE.\n\t\t */\n\t\tremoveItem: function ( key, context, multiStorageOption ) {\n\t\t\tmultiStorageOption =\n\t\t\t\tmultiStorageOption || kvStore.multiStorageOptions.LOCAL_STORAGE;\n\n\t\t\tswitch ( multiStorageOption ) {\n\n\t\t\t\tcase kvStore.multiStorageOptions.LOCAL_STORAGE:\n\t\t\t\t\tremoveLocalStorageItem( key, context );\n\t\t\t\t\treturn;\n\n\t\t\t\tcase kvStore.multiStorageOptions.COOKIE:\n\t\t\t\t\tremoveCookieItem( key, context );\n\t\t\t\t\treturn;\n\n\t\t\t\tcase kvStore.multiStorageOptions.NO_STORAGE:\n\t\t\t\t\treturn;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new Error( 'Unexpected multi-storage option' );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Convenience method to check for availability of storage without\n\t\t * falling back to cookies.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisAvailable: function () {\n\t\t\treturn ( kvStore.getMultiStorageOption( false ) !==\n\t\t\t\tkvStore.multiStorageOptions.NO_STORAGE );\n\t\t},\n\n\t\t/**\n\t\t * Determine the appropriate multi-storage option\n\t\t *\n\t\t * @param {boolean} cookieAllowed\n\t\t * @return {string} A string key\n\t\t */\n\t\tgetMultiStorageOption: function ( cookieAllowed ) {\n\t\t\tif ( isLocalStorageAvailable() ) {\n\t\t\t\treturn kvStore.multiStorageOptions.LOCAL_STORAGE;\n\t\t\t}\n\n\t\t\tif ( cookieAllowed && areCookiesEnabled() ) {\n\t\t\t\treturn kvStore.multiStorageOptions.COOKIE;\n\t\t\t}\n\n\t\t\treturn kvStore.multiStorageOptions.NO_STORAGE;\n\t\t},\n\n\t\t/**\n\t\t * If a KVStore error has occurred (during this page view), return an\n\t\t * object with information about it. If no KVStore errors have occurred,\n\t\t * return null.\n\t\t *\n\t\t * @return {?Object}\n\t\t */\n\t\tgetError: function () {\n\t\t\treturn error;\n\t\t},\n\n\t\tsetNotAvailableError: function () {\n\t\t\tsetError( 'LocalStorage not available.', null, null );\n\t\t},\n\n\t\tsetMaintenanceError: function ( lsKey ) {\n\t\t\tconst m = lsKey.match( FIND_KEY_REGEX ),\n\t\t\t\tkey = m ? m[ 1 ] : null;\n\n\t\t\tsetError( 'Error during KVStore maintenance.', key, null );\n\t\t},\n\n\t\tsetCampaignName: function ( cName ) {\n\t\t\tcampaignName = cName;\n\t\t},\n\n\t\tsetBannerName: function ( bName ) {\n\t\t\tbannerName = bName;\n\t\t},\n\n\t\tsetCategory: function ( c ) {\n\t\t\tcategory = c;\n\t\t}\n\t};\n\n\tmodule.exports = kvStore;\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.startUp/index.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":44,"column":3,"nodeType":"CallExpression","endLine":46,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Start-up script for CentralNotice.\n *\n * Here's what it does:\n * - Check if we're on a Special page; quick bow-out if so.\n * - For legacy support, ensure that the centralNotice div is available, as it\n *   was in legacy code.\n * - If a banner was requested for testing, load that.\n * - Otherwise, if there are campaigns in choiceData, filter and process that,\n *   and possibly display a banner.\n *\n * This module depends on ext.centralNotice.geoIP and\n * ext.centralNotice.choiceData. If there are campaigns in choiceData,\n * that module will depend on any other modules needed for further processing.\n */\n( function () {\n\n\tconst cn = mw.centralNotice,\n\t\ttestingBannerName = mw.util.getParamValue( 'banner' ),\n\t\tkvStoreMaintenance = require( './kvStoreMaintenance.js' ),\n\t\tNULL_BANNER_NAME = 'null';\n\n\t// For back-compat and debugging, export globally.\n\tcn.kvStoreMaintenance = kvStoreMaintenance;\n\n\t// Note: In legacy code, CentralNotice initialization was done after the DOM\n\t// finished loading (via $( function() {...} )). Now, we only delay logic\n\t// that accesses DOM elements in that way, and run other code sooner.\n\n\t// Legacy support:\n\t// Legacy code inserted the CN div everywhere (except on Special pages),\n\t// even when there were no campaigns. Let's do the same thing for now, in\n\t// case other code has grown up around it.\n\t// TODO Add this only if there's a banner one day?\n\t$( () => {\n\t\t$( '#siteNotice' ).prepend( '<div id=\"centralNotice\" data-nosnippet=\"\"></div>' );\n\t} );\n\n\t// Testing banner or forced no banner\n\tif ( testingBannerName ) {\n\t\tif ( testingBannerName === NULL_BANNER_NAME ) {\n\t\t\treturn;\n\t\t}\n\t\tmw.loader.using( 'ext.centralNotice.display' ).done( () => {\n\t\t\tcn.displayTestingBanner();\n\t\t} );\n\t\treturn;\n\t}\n\n\t// Sanity check\n\tif ( cn.choiceData === undefined ) {\n\t\tmw.log.warn( 'No choice data set for CentralNotice campaign ' +\n\t\t\t'and banner selection.' );\n\t\treturn;\n\t}\n\n\t// Maintenance: This schedules the removal of old KV keys.\n\t// The schedule action itself is deferred, too, as it accesses localStorage.\n\t// - FIXME: Consider doing this behind a random sample instead of every page view\n\t//   (e.g. 50% or 20%). Or, instead of sampling:\n\t// - FIXME: Use sessionStorage (mw.storage.session) to not schedule multiple\n\t//   maintenance windows simultaneously and to not schedule maintenance too\n\t//   often (e.g. no more once every 6 hours). It may be attractive to do\n\t//   \"No more than once per session\" but a time-bound is still needed as\n\t//   sessions often never end due to save-on-quit and restore-on-reopen features\n\t//   in many products.\n\tmw.requestIdleCallback( kvStoreMaintenance.doMaintenance );\n\n\t// Nothing more to do if there are no possible campaigns for this user\n\tif ( cn.choiceData.length === 0 ) {\n\t\treturn;\n\t}\n\n\t// If there's some issue with RL causing ext.centralNotice.display not\n\t// to load, don't fail hard\n\tif ( !cn.chooseAndMaybeDisplay ) {\n\t\tmw.log.warn( 'Possible campaign(s) received in choiceData, but ' +\n\t\t\t'mw.centralNotice.chooseAndMaybeDisplay() is not available' );\n\n\t\treturn;\n\t}\n\n\tcn.chooseAndMaybeDisplay();\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ext.centralNotice.startUp/kvStoreMaintenance.js","messages":[{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":33,"column":17,"nodeType":"Identifier","messageId":"noStorage","endLine":33,"endColumn":29},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":38,"column":18,"nodeType":"Identifier","messageId":"noStorage","endLine":38,"endColumn":30},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":61,"column":18,"nodeType":"Identifier","messageId":"noStorage","endLine":61,"endColumn":30},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":65,"column":9,"nodeType":"Identifier","messageId":"noStorage","endLine":65,"endColumn":21},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":69,"column":7,"nodeType":"Identifier","messageId":"noStorage","endLine":69,"endColumn":19},{"ruleId":"mediawiki/no-cookie","severity":1,"message":"Avoid direct access to document.cookie. Use mw.cookie instead.","line":84,"column":28,"nodeType":"Identifier","messageId":"noCookie","endLine":84,"endColumn":34},{"ruleId":"mediawiki/no-cookie","severity":1,"message":"Avoid direct access to document.cookie. Use mw.cookie instead.","line":90,"column":14,"nodeType":"Identifier","messageId":"noCookie","endLine":90,"endColumn":20},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":109,"column":18,"nodeType":"Identifier","messageId":"noStorage","endLine":109,"endColumn":30},{"ruleId":"mediawiki/no-storage","severity":1,"message":"Avoid direct access to localStorage. Use mw.storage instead.","line":109,"column":35,"nodeType":"Identifier","messageId":"noStorage","endLine":109,"endColumn":47},{"ruleId":"mediawiki/no-cookie","severity":1,"message":"Avoid direct access to document.cookie. Use mw.cookie instead.","line":117,"column":18,"nodeType":"Identifier","messageId":"noCookie","endLine":117,"endColumn":24}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":10,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Module for maintenance of items in kvStore. During idle time, it checks the\n * expiry times of items and removes those that expired a specified \"leeway\"\n * time ago.\n *\n * This module provides an API at mw.centralNotice.kvStoreMaintenance.\n */\n( function () {\n\tconst now = Date.now() / 1000,\n\n\t\t// Regex to find kvStore localStorage keys. Must correspond with PREFIX\n\t\t// in ext.centralNotice.kvStore.\n\t\tPREFIX_REGEX = /^CentralNoticeKV/,\n\n\t\t// Must coordinate with PREFIX_IN_COOKIES and SEPARATOR_IN_COOKIES in\n\t\t// ext.centralNotice.kvStore.\n\t\tPREFIX_AND_SEPARATOR_IN_COOKIES = 'CN!',\n\n\t\t// Time past expiry before actually removing items: 1 day (in seconds).\n\t\t// (This should prevent race conditions among browser tabs.)\n\t\tLEEWAY_FOR_REMOVAL = 86400,\n\n\t\t// Minimum amount of time (in milliseconds) for an iteration involving localStorage access.\n\t\tMIN_WORK_TIME = 3;\n\n\t/**\n\t * @return {jQuery.Promise<string[]>} List of key strings\n\t */\n\tfunction getKeys() {\n\t\treturn $.Deferred( ( d ) => {\n\t\t\tmw.requestIdleCallback( ( deadline ) => {\n\t\t\t\tconst keys = [];\n\t\t\t\tlet index = localStorage.length;\n\n\t\t\t\t// We don't expect to have more keys than we can handle in a single iteration.\n\t\t\t\t// But just in case, ensure we don't stall for too long.\n\t\t\t\twhile ( index-- > 0 && deadline.timeRemaining() > MIN_WORK_TIME ) {\n\t\t\t\t\tconst key = localStorage.key( index );\n\t\t\t\t\t// Operate only on our own localStorage items.\n\t\t\t\t\t// Also recheck key existence as it may race with other tabs.\n\t\t\t\t\tif ( key !== null && PREFIX_REGEX.test( key ) ) {\n\t\t\t\t\t\tkeys.push( key );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\td.resolve( keys );\n\t\t\t} );\n\t\t} ).promise();\n\t}\n\n\t/**\n\t * @param {string[]} queue\n\t * @return {jQuery.Promise}\n\t */\n\tfunction processKeys( queue ) {\n\t\treturn $.Deferred( ( d ) => {\n\t\t\tmw.requestIdleCallback( function iterate( deadline ) {\n\t\t\t\tlet key, rawValue, value;\n\t\t\t\twhile ( queue[ 0 ] !== undefined && deadline.timeRemaining() > MIN_WORK_TIME ) {\n\t\t\t\t\tkey = queue.shift();\n\t\t\t\t\ttry {\n\t\t\t\t\t\trawValue = localStorage.getItem( key );\n\t\t\t\t\t\tif ( rawValue ) {\n\t\t\t\t\t\t\tvalue = JSON.parse( rawValue );\n\t\t\t\t\t\t\tif ( !value.expiry || ( value.expiry + LEEWAY_FOR_REMOVAL ) < now ) {\n\t\t\t\t\t\t\t\tlocalStorage.removeItem( key );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\tlocalStorage.removeItem( key );\n\t\t\t\t\t\tmw.log.warn( 'CentralNotice kvStoreMaintenance error for key ' + key, e );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( queue[ 0 ] !== undefined ) {\n\t\t\t\t\t// Time's up, continue later\n\t\t\t\t\tmw.requestIdleCallback( iterate );\n\t\t\t\t} else {\n\t\t\t\t\td.resolve();\n\t\t\t\t}\n\t\t\t} );\n\t\t} ).promise();\n\t}\n\n\tfunction purgeFallbackCookies() {\n\t\tconst cookies = document.cookie.split( ';' ),\n\t\t\tr = new RegExp( '^' + PREFIX_AND_SEPARATOR_IN_COOKIES + '[^=]*(?==)' );\n\n\t\tfor ( let i = 0; i < cookies.length; i++ ) {\n\t\t\tconst matches = cookies[ i ].trim().match( r );\n\t\t\tif ( matches ) {\n\t\t\t\tdocument.cookie = matches[ 0 ] +\n\t\t\t\t\t'=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Public API\n\t */\n\tconst kvStoreMaintenance = {\n\n\t\t/**\n\t\t * Start the removal of expired KVStore items. Also check for fallback\n\t\t * cookies and remove them if LocalStorage is available.\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tdoMaintenance: function () {\n\t\t\ttry {\n\t\t\t\tif ( !window.localStorage || !localStorage.length ) {\n\t\t\t\t\treturn $.Deferred().resolve();\n\t\t\t\t}\n\t\t\t} catch ( e ) {\n\t\t\t\treturn $.Deferred().resolve();\n\t\t\t}\n\n\t\t\t// Fallback cookies? LocalStorage seems to work, so purge them.\n\t\t\tif ( document.cookie.includes( PREFIX_AND_SEPARATOR_IN_COOKIES ) ) {\n\t\t\t\tpurgeFallbackCookies();\n\t\t\t}\n\n\t\t\treturn getKeys().then( processKeys );\n\t\t}\n\t};\n\n\tmodule.exports = kvStoreMaintenance;\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/infrastructure/bannereditor.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":50,"column":3,"nodeType":"CallExpression","endLine":71,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":50,"column":3,"nodeType":"CallExpression","endLine":76,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":117,"column":3,"nodeType":"CallExpression","endLine":126,"endColumn":6}],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":190,"column":5,"nodeType":"CallExpression","messageId":"unexpected","endLine":190,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Backing JS for Special:CentralNoticeBanners/edit, the form that allows\n * editing of banner content and changing of banner settings.\n *\n * This file is part of the CentralNotice Extension to MediaWiki\n * https://www.mediawiki.org/wiki/Extension:CentralNotice\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n * http://www.gnu.org/copyleft/gpl.html\n */\n( function () {\n\tlet bannerEditor, bannerName, $previewFieldSet, $previewContent, $bannerMessages,\n\t\tfileScopedOpenExternalPreview;\n\n\t// Prefix for key used to store banner preview content for external preview.\n\t// Coordinate with PREVIEW_STORAGE_KEY_PREFIX in ext.centralNotice.display.js\n\tconst PREVIEW_STORAGE_KEY_PREFIX = 'cn-banner-preview-';\n\n\tconst isEditable = mw.config.get( 'CentralNoticeEditable' );\n\n\tfunction doPurgeCache() {\n\t\tconst language = $( '#cn-cdn-cache-language' ).val(),\n\t\t\tmessageId = 'centralnotice-purge-cache-' + language;\n\n\t\t// Do nothing if the button was disabled (from lack of CN admin rights)\n\t\tif ( $( '#cn-cdn-cache-purge' ).prop( 'disabled' ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Show notification with info if the background call takes a while to return\n\t\tconst waiting = setTimeout( () => {\n\t\t\tmw.notify( mw.message( 'centralnotice-banner-cdn-dialog-waiting-text' ).text(), {\n\t\t\t\tautoHide: false,\n\t\t\t\ttag: messageId\n\t\t\t} );\n\t\t}, 300 );\n\n\t\tnew mw.Api().postWithToken( 'csrf', {\n\t\t\taction: 'centralnoticecdncacheupdatebanner',\n\t\t\tbanner: bannerName,\n\t\t\tlanguage: language\n\t\t}, {\n\t\t\ttimeout: 2000\n\t\t} ).always( () => {\n\t\t\tclearTimeout( waiting );\n\t\t} ).fail( ( code, result ) => {\n\t\t\tlet text = mw.message( 'centralnotice-banner-cdn-dialog-error' ).text();\n\n\t\t\tif ( result && result.error && result.error.info ) {\n\t\t\t\ttext += ' (' + result.error.info + ')';\n\t\t\t} else if ( result && result.exception ) {\n\t\t\t\ttext += ' (' + result.exception + ')';\n\t\t\t}\n\n\t\t\tmw.notify( text, {\n\t\t\t\ttype: 'error',\n\t\t\t\ttag: messageId\n\t\t\t} );\n\t\t} ).done( () => {\n\t\t\tmw.notify( mw.message( 'centralnotice-banner-cdn-dialog-success' ).text(), {\n\t\t\t\ttype: 'success',\n\t\t\t\ttag: messageId\n\t\t\t} );\n\t\t} );\n\t}\n\n\t/**\n\t * Collects unsaved messages values from banner editing form (if any)\n\t *\n\t * @return {Object}\n\t */\n\tfunction getUnsavedMessagesValues() {\n\t\tconst bannerMessagesCache = {};\n\n\t\tif ( $bannerMessages.length ) {\n\t\t\t$bannerMessages.each( ( i, message ) => {\n\t\t\t\tconst label = $( message ).find( 'label' ).text(),\n\t\t\t\t\tvalue = $( message ).find( 'textarea' ).val();\n\t\t\t\tbannerMessagesCache[ label ] = value;\n\t\t\t} );\n\t\t}\n\n\t\treturn bannerMessagesCache;\n\t}\n\n\t/**\n\t * Renders banner content preview in live preview section.\n\t *\n\t * @param {boolean} openExternalPreview\n\t */\n\tfunction fetchAndUpdateBannerPreview( openExternalPreview ) {\n\t\tconst $bannerContentTextArea = $( '#mw-input-wpbanner-body' );\n\t\tconst bannerMessagesCache = getUnsavedMessagesValues();\n\t\tconst url = new URL( mw.config.get( 'wgCentralNoticeActiveBannerDispatcher' ), location );\n\n\t\t// Set this file-scoped variable so the callback knows whether to open the\n\t\t// external preview.\n\t\tfileScopedOpenExternalPreview = openExternalPreview;\n\n\t\t// Activate the barbershop \"loading\" animation\n\t\t$previewFieldSet.attr( 'disabled', true );\n\n\t\t// Send the raw unsaved banner content and messages for server-side rendering.\n\t\t// This will call bannerEditor.updateBannerPreview().\n\t\t$.post( url.toString(),\n\t\t\t{\n\t\t\t\tbanner: bannerName,\n\t\t\t\tpreviewcontent: $bannerContentTextArea.val(),\n\t\t\t\tpreviewmessages: bannerMessagesCache,\n\t\t\t\ttoken: mw.user.tokens.get( 'csrfToken' )\n\t\t\t}\n\t\t).fail( ( jqXHR, status, error ) => {\n\t\t\tbannerEditor.handleBannerLoaderError( status + ': ' + error );\n\t\t} ).always( () => {\n\t\t\t// De-activate the barbershop \"loading\" animation\n\t\t\t$previewFieldSet.attr( 'disabled', false );\n\t\t} );\n\t}\n\n\t// TODO Several functions exposed aren't used elsewhere, so they should be private\n\tmw.centralNotice.adminUi.bannerEditor = bannerEditor = {\n\n\t\t/**\n\t\t * Display the 'Create Banner' dialog\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tdoCloneBannerDialog: function () {\n\t\t\tconst buttons = {},\n\t\t\t\tokButtonText = mw.message( 'centralnotice-clone' ).text(),\n\t\t\t\tcancelButtonText = mw.message( 'centralnotice-clone-cancel' ).text(),\n\t\t\t\t$dialogObj = $( '<form>' );\n\n\t\t\t// Implement the functionality\n\t\t\tbuttons[ cancelButtonText ] = function () {\n\t\t\t\t$( this ).dialog( 'close' );\n\t\t\t};\n\t\t\tbuttons[ okButtonText ] = function () {\n\n\t\t\t\t// We'll submit the real form (not the one in the dialog).\n\t\t\t\t// Copy in values to that form before submitting.\n\t\t\t\tconst formobj = $( '#cn-banner-editor' )[ 0 ];\n\t\t\t\tformobj.wpaction.value = 'clone';\n\t\t\t\tformobj.wpcloneName.value = $( this )[ 0 ].wpcloneName.value;\n\n\t\t\t\tformobj.wpcloneEditSummary.value =\n\t\t\t\t\t$( this )[ 0 ].wpcloneEditSummary.value;\n\n\t\t\t\tformobj.submit();\n\t\t\t};\n\n\t\t\t// Copy value of summary from main form into clone summary field\n\t\t\t$( '#mw-input-wpcloneEditSummary' )\n\t\t\t\t.val( $( '#mw-input-wpsummary' ).val() );\n\n\t\t\t// Create the dialog by copying the text fields into a new form\n\t\t\t$dialogObj[ 0 ].name = 'addBannerDialog';\n\t\t\t$dialogObj.append( $( '#cn-formsection-clone-banner' ).children( 'div' ).clone().show() )\n\t\t\t\t.dialog( {\n\t\t\t\t\ttitle: mw.message( 'centralnotice-clone-notice' ).escaped(),\n\t\t\t\t\tmodal: true,\n\t\t\t\t\tbuttons: buttons,\n\t\t\t\t\twidth: 'auto'\n\t\t\t\t} );\n\n\t\t\t// Do not submit the form... that's up to the ok button\n\t\t\treturn false;\n\t\t},\n\n\t\t/**\n\t\t * Validates the contents of the banner body before submission.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tdoSaveBanner: function () {\n\t\t\tif ( $( '#mw-input-wpbanner-body' ).prop( 'value' ).includes( 'document.write' ) ) {\n\t\t\t\t// eslint-disable-next-line no-alert\n\t\t\t\talert( mw.msg( 'centralnotice-documentwrite-error' ) );\n\t\t\t} else {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\n\t\t/**\n\t\t * Asks the user if they actually wish to delete the selected banners and if yes will submit\n\t\t * the form with the 'remove' action.\n\t\t */\n\t\tdoDeleteBanner: function () {\n\t\t\tconst $dialogObj = $( '<form>' ),\n\t\t\t\t$dialogMessage = $( '<div>' ).addClass( 'cn-dialog-message' ),\n\t\t\t\tbuttons = {},\n\t\t\t\tdeleteText = mw.message( 'centralnotice-delete-banner' ).text(),\n\t\t\t\tcancelButtonText = mw.message( 'centralnotice-delete-banner-cancel' ).text();\n\n\t\t\t// We'll submit the real form (outside the dialog).\n\t\t\t// Copy in values to that form before submitting.\n\t\t\tbuttons[ deleteText ] = function () {\n\t\t\t\tconst formobj = $( '#cn-banner-editor' )[ 0 ];\n\t\t\t\tformobj.wpaction.value = 'delete';\n\n\t\t\t\tformobj.wpdeleteEditSummary.value =\n\t\t\t\t\t$( this )[ 0 ].wpdeleteEditSummary.value;\n\n\t\t\t\tformobj.submit();\n\t\t\t};\n\t\t\tbuttons[ cancelButtonText ] = function () {\n\t\t\t\t$( this ).dialog( 'close' );\n\t\t\t};\n\n\t\t\t// Copy value of summary from main form into delete summary field\n\t\t\t$( '#mw-input-wpdeleteEditSummary' )\n\t\t\t\t.val( $( '#mw-input-wpsummary' ).val() );\n\n\t\t\t$dialogObj.append( $dialogMessage );\n\t\t\t$dialogMessage.text( mw.message( 'centralnotice-delete-banner-confirm' ).text() );\n\n\t\t\t$dialogObj.append( $( '#cn-formsection-delete-banner' ).children( 'div' ).clone().show() )\n\t\t\t\t.dialog( {\n\t\t\t\t\ttitle: mw.message( 'centralnotice-delete-banner-title', 1 ).escaped(),\n\t\t\t\t\tmodal: true,\n\t\t\t\t\tbuttons: buttons,\n\t\t\t\t\twidth: '35em'\n\t\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Submits the form with the archive action.\n\t\t */\n\t\tdoArchiveBanner: function () {\n\t\t\tconst $dialogObj = $( '<div>' ),\n\t\t\t\tbuttons = {},\n\t\t\t\tarchiveText = mw.message( 'centralnotice-archive-banner' ).text(),\n\t\t\t\tcancelButtonText = mw.message( 'centralnotice-archive-banner-cancel' ).text();\n\n\t\t\tbuttons[ archiveText ] = function () {\n\t\t\t\tconst formobj = $( '#cn-banner-editor' )[ 0 ];\n\t\t\t\tformobj.wpaction.value = 'archive';\n\t\t\t\tformobj.submit();\n\t\t\t};\n\t\t\tbuttons[ cancelButtonText ] = function () {\n\t\t\t\t$( this ).dialog( 'close' );\n\t\t\t};\n\n\t\t\t$dialogObj.text( mw.message( 'centralnotice-archive-banner-confirm' ).text() );\n\t\t\t$dialogObj.dialog( {\n\t\t\t\ttitle: mw.message( 'centralnotice-archive-banner-title', 1 ).escaped(),\n\t\t\t\tresizable: false,\n\t\t\t\tmodal: true,\n\t\t\t\tbuttons: buttons\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Updates banner preview render\n\t\t *\n\t\t * @param {Object} data\n\t\t */\n\t\tupdateBannerPreview: function ( data ) {\n\t\t\tconst bannerHtml = data.bannerHtml;\n\t\t\t$previewContent.html( bannerHtml );\n\n\t\t\tif ( fileScopedOpenExternalPreview ) {\n\t\t\t\t// Put the rendered banner content in LocalStorage, for use\n\t\t\t\t// by the external preview.\n\t\t\t\tmw.centralNotice.kvStore.setItem(\n\t\t\t\t\tPREVIEW_STORAGE_KEY_PREFIX + bannerName,\n\t\t\t\t\tbannerHtml,\n\t\t\t\t\tmw.centralNotice.kvStore.contexts.GLOBAL,\n\t\t\t\t\t1\n\t\t\t\t);\n\n\t\t\t\twindow.open( mw.Title.makeTitle( -1, 'Random' ).getUrl( {\n\t\t\t\t\tbanner: bannerName,\n\t\t\t\t\tforce: 1,\n\t\t\t\t\tpreview: 1\n\t\t\t\t} ) );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Display notification when the banner failed to load.\n\t\t *\n\t\t * @param {string} [msg] The body of the error message\n\t\t */\n\t\thandleBannerLoaderError: function ( msg ) {\n\t\t\tmw.notify( msg, {\n\t\t\t\ttitle: mw.msg( 'centralnotice-preview-loader-error-dialog-title' ),\n\t\t\t\ttype: 'error'\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Hook function from onclick of the translate language drop down -- will submit the\n\t\t * form in order to update the language of the preview and the displayed translations.\n\t\t */\n\t\tupdateLanguage: function () {\n\t\t\tconst formobj = $( '#cn-banner-editor' )[ 0 ];\n\t\t\tformobj.wpaction.value = 'update-lang';\n\t\t\tformobj.submit();\n\t\t},\n\n\t\t/**\n\t\t * Legacy insert close button code. Happens on link click above the edit area\n\t\t * TODO: Make this jQuery friendly...\n\t\t *\n\t\t * @param {string} buttonType\n\t\t */\n\t\tinsertButton: function ( buttonType ) {\n\t\t\tlet buttonValue;\n\t\t\tif ( !isEditable ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst bannerField = document.getElementById( 'mw-input-wpbanner-body' );\n\t\t\tif ( buttonType === 'close' ) {\n\t\t\t\tbuttonValue = '<a href=\"#\" title=\"' +\n\t\t\t\t\tmw.message( 'centralnotice-close-title' ).escaped() +\n\t\t\t\t\t'\" onclick=\"mw.centralNotice.hideBanner();return false;\">' +\n\t\t\t\t\t'<div class=\"cn-closeButton\">' + mw.message( 'centralnotice-close-title' ).escaped() +\n\t\t\t\t\t'</div></a>';\n\t\t\t}\n\t\t\tif ( document.selection ) {\n\t\t\t\t// IE support\n\t\t\t\tbannerField.focus();\n\t\t\t\tconst sel = document.selection.createRange();\n\t\t\t\tsel.text = buttonValue;\n\t\t\t} else if ( bannerField.selectionStart || bannerField.selectionStart === 0 ) {\n\t\t\t\t// Mozilla support\n\t\t\t\tconst startPos = bannerField.selectionStart;\n\t\t\t\tconst endPos = bannerField.selectionEnd;\n\t\t\t\tbannerField.value = bannerField.value.slice( 0, startPos ) +\n\t\t\t\t\tbuttonValue +\n\t\t\t\t\tbannerField.value.slice( endPos, bannerField.value.length );\n\t\t\t} else {\n\t\t\t\tbannerField.value += buttonValue;\n\t\t\t}\n\t\t\tbannerField.focus();\n\n\t\t\t// Trigger preview on close button insertion\n\t\t\tfetchAndUpdateBannerPreview( false );\n\t\t}\n\t};\n\n\t// Attach handlers and initialize stuff after document ready\n\t$( () => {\n\t\t$( '#cn-js-error-warn' ).hide();\n\n\t\tconst $editSection = $( '#cn-formsection-edit-template' ),\n\t\t\t$previewLink = $( '<a>' ),\n\t\t\t$previewLegend = $( '<legend>' ),\n\t\t\t$previewUpdateButton = $( '<button>' );\n\n\t\t// Retrieve banner name sent via data attribute\n\t\tbannerName = $( '#centralnotice-data-container' ).data( 'banner-name' );\n\n\t\t$previewLink.text( mw.msg( 'centralnotice-preview-page' ) );\n\n\t\tif ( !isEditable ) {\n\t\t\t$previewLink.insertBefore( $editSection );\n\t\t\t$previewLink.attr( 'href', mw.Title.makeTitle( -1, 'Random' ).getUrl( {\n\t\t\t\tbanner: bannerName,\n\t\t\t\tforce: 1\n\t\t\t} ) ).attr( 'target', '_blank' );\n\t\t\treturn;\n\t\t}\n\n\t\t// Create and attach banner preview elements\n\t\t$previewFieldSet = $( '<fieldset>' );\n\t\t$previewFieldSet.addClass( 'cn-banner-preview-fieldset' );\n\t\t$previewLegend.append( $( '<span>' ).text( mw.msg( 'centralnotice-fieldset-preview' ) ) );\n\n\t\t$previewLegend.append( $previewLink );\n\t\t$previewFieldSet.append( $previewLegend );\n\n\t\t$previewContent = $( '<div>' ).addClass( 'cn-banner-preview-content' );\n\t\t$previewFieldSet.append( $previewContent );\n\n\t\t// Preview button: use same css classes as are generated by PHP form\n\t\t$previewUpdateButton.addClass( 'cn-formbutton' )\n\t\t\t.addClass( 'webfonts-changed' )\n\t\t\t.attr( 'type', 'button' )\n\t\t\t.text( mw.msg( 'centralnotice-update-preview' ) );\n\n\t\t$previewFieldSet.append( $previewUpdateButton );\n\n\t\t$previewFieldSet.insertBefore( $editSection );\n\n\t\t// Find banner message form elements (if any)\n\t\t$bannerMessages = $( '#mw-htmlform-banner-messages' ).find(\n\t\t\t'.mw-htmlform-field-HTMLCentralNoticeBannerMessage' );\n\n\t\t// Attach handlers\n\t\t$previewLink.on( 'click', () => {\n\t\t\tfetchAndUpdateBannerPreview( true );\n\t\t} );\n\n\t\t$previewUpdateButton.on( 'click', () => {\n\t\t\tfetchAndUpdateBannerPreview( false );\n\t\t} );\n\n\t\t$( '#mw-input-wpdelete-button' ).on( 'click', bannerEditor.doDeleteBanner );\n\t\t$( '#mw-input-wparchive-button' ).on( 'click', bannerEditor.doArchiveBanner );\n\t\t$( '#mw-input-wpclone-button' ).on( 'click', bannerEditor.doCloneBannerDialog );\n\t\t$( '#mw-input-wpsave-button' ).on( 'click', bannerEditor.doSaveBanner );\n\t\t$( '#mw-input-wptranslate-language' ).on( 'change', bannerEditor.updateLanguage );\n\t\t$( '#cn-cdn-cache-purge' ).on( 'click', doPurgeCache );\n\n\t\t// Trigger preview right away\n\t\tfetchAndUpdateBannerPreview( false );\n\t} );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/infrastructure/bannermanager.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-ready-shorthand","severity":1,"message":"Prefer $() to .ready","line":77,"column":4,"nodeType":"CallExpression","endLine":82,"endColumn":7,"fix":{"range":[2696,2795],"text":"$"},"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/infrastructure/campaignManager.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":809,"column":3,"nodeType":"CallExpression","endLine":809,"endColumn":62}],"suppressedMessages":[{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":428,"column":3,"nodeType":"CallExpression","endLine":520,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-map-util","severity":2,"message":"Prefer Array#map to $.map","line":799,"column":33,"nodeType":"CallExpression","endLine":801,"endColumn":4,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * JS for campaign editor (handled by Special:CentralNotice)\n *\n * This file is part of the CentralNotice Extension to MediaWiki\n * https://www.mediawiki.org/wiki/Extension:CentralNotice\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License along\n * with this program; if not, write to the Free Software Foundation, Inc.,\n * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n * http://www.gnu.org/copyleft/gpl.html\n *\n */\n( function () {\n\tconst stepSize = 1,\n\t\tmixinDefs = mw.config.get( 'wgCentralNoticeCampaignMixins' ),\n\t\tmixinParamsTemplate = mw.template.get(\n\t\t\t'ext.centralNotice.adminUi.campaignManager',\n\t\t\t'campaignMixinParamControls.mustache'\n\t\t),\n\t\tmixinCustomUiControllerFactory = new OO.Factory(),\n\t\tBUCKET_LABELS = [ 'A', 'B', 'C', 'D' ]; // TODO Fix for configs with more buckets\n\tlet $form, $submitBtn,\n\t\tassignedBanners;\n\n\t/* Event bus */\n\n\t/**\n\t * eventBus: A simple object for subscribing to and emitting events.\n\t */\n\n\t/**\n\t * @event bucket-change\n\t *\n\t * The control for the number of buckets in the campaign has changed.\n\t * @param {number} numBuckets\n\t */\n\n\t/**\n\t * @event assigned-banners-change\n\t *\n\t * The controls for banner assignment (bucket assignments and banner removal\n\t * checkboxes) have changed. Note: This event does not fire when a checkbox for\n\t * adding a banner is checked.\n\t */\n\t/**\n\t * @event error-state\n\t *\n\t * An error state is set or removed\n\t * @param {string} errorKey\n\t * @param {boolean} state true to set error, false to clear one.\n\t */\n\tconst eventBus = new OO.EventEmitter();\n\n\t/* MixinCustomUiController */\n\n\t// Note: the following code uses two completely distinct meanings of\n\t// \"mixin\". One is \"campaign mixin\", bits of JS code that can run in the\n\t// browsers of users in specific campaigns. The other is the OOjs concept\n\t// of mixin, that is, a bit of functionality that can be added to a\n\t// javascript class. For example, the MixinCustomWidget class provides a\n\t// bit of UI for a campaign mixin, and it mixes in, in the OOjs sense,\n\t// functionality from OO.ui.mixin.GroupElement.\n\n\t/**\n\t * Base class for custom campaign mixin UI controllers.\n\t *\n\t * Provides facilities for setting hidden form input elements for mixin parameter\n\t * values. This lets custom mixins provide interactive interfaces that are not input\n\t * elements, and send data to the server via these hidden inputs.\n\t *\n\t * Note: Subclasses are expected to be singletons.\n\t *\n\t * @abstract\n\t * @class MixinCustomUiController\n\t * @constructor\n\t */\n\tconst MixinCustomUiController = function () {\n\n\t\t// Declare the abstract property here, but don't force it to null, in case the\n\t\t// subclass decides to set it before calling the constructor.\n\t\t/**\n\t\t * The element of the corresponding MixinCustomWidget.\n\t\t *\n\t\t * @abstract\n\t\t * @property {jQuery}\n\t\t */\n\t\tthis.$widgetElement = this.$widgetElement || null;\n\t};\n\n\tOO.initClass( MixinCustomUiController );\n\n\t/**\n\t * The name of the campaign mixin that this control group sets the parameters for.\n\t *\n\t * @abstract\n\t * @inheritable\n\t * @static\n\t * @property {string}\n\t */\n\tMixinCustomUiController.static.name = null;\n\n\t/**\n\t * Initialize the controller with the data provided.\n\t *\n\t * @method\n\t * @abstract\n\t * @param {Object} data Object in which properties and their values are mixin\n\t *   parameter names and values. Format should coordinate with the format sent\n\t *   to the client via the 'mixin-param-values' data value on the checkbox that\n\t *   enables the mixin.\n\t */\n\tMixinCustomUiController.prototype.init = null;\n\n\t/**\n\t * Set the (string-encoded) value of a mixin parameter via a hidden input element.\n\t *\n\t * @param {string} name The name of the parameter.\n\t * @param {string} value The value (formatted as appropriate for form submission).\n\t */\n\tMixinCustomUiController.prototype.setParam = function ( name, value ) {\n\t\tconst $input = this.getParamInputEl( name, true );\n\n\t\t$input.val( value );\n\t};\n\n\t/**\n\t * Remove a mixin parameter's hidden input element, if it exists.\n\t *\n\t * @param {string} name The name of the parameter\n\t */\n\tMixinCustomUiController.prototype.removeParam = function ( name ) {\n\t\tconst $input = this.getParamInputEl( name, false );\n\n\t\tif ( $input.length ) {\n\t\t\t$input.remove();\n\t\t}\n\t};\n\n\t/**\n\t * Get the hidden input element for a mixin parameter, if it exists. If requested,\n\t * create it if it doesn't exist.\n\t *\n\t * @private\n\t * @param {string} name The name of the parameter\n\t * @param {boolean} create Create the element if it doesn't exist\n\t * @return {jQuery|null}\n\t */\n\tMixinCustomUiController.prototype.getParamInputEl = function ( name, create ) {\n\t\tconst inputName = makeNoticeMixinControlName( this.constructor.static.name, name );\n\t\tlet $input = $form.find( 'input[name=\"' + inputName + '\"]' );\n\n\t\tif ( create && !( $input.length ) ) {\n\n\t\t\t$input = $( '<input>' ).attr( {\n\t\t\t\tname: inputName,\n\t\t\t\ttype: 'hidden'\n\t\t\t} );\n\n\t\t\t$form.append( $input );\n\t\t}\n\n\t\treturn $input;\n\t};\n\n\t/* MixinCustomWidget */\n\n\t/**\n\t * Base class for custom campaign mixin widgets.\n\t *\n\t * @abstract\n\t * @class MixinCustomWidget\n\t * @extends OO.ui.Widget\n\t * @mixes OO.ui.mixin.GroupWidget\n\t * @constructor\n\t *\n\t * @param {MixinCustomUiController} controller\n\t * @param {Object} [config] Configuration options\n\t */\n\tconst MixinCustomWidget = function ( controller, config ) {\n\n\t\tconst $element = $( '<fieldset>' ),\n\t\t\t$group = $( '<div>' );\n\n\t\t// Set up config with elements, CSS class and id. This should coordinate with\n\t\t// makeMixinParamControlSet() (below) and\n\t\t// templates/campaignMixinParamControls.mustache (used for the automatic creation\n\t\t// of mixin param controls).\n\t\tconfig = Object.assign( {\n\t\t\t$element: $element,\n\n\t\t\t// This works because controller classes are singletons.\n\t\t\tid: mixinParamControlsId( controller.constructor.static.name )\n\t\t}, config );\n\n\t\t$group.addClass( 'campaignMixinControls' );\n\t\t$element.append( $group );\n\n\t\t// Call parent constructor\n\t\tMixinCustomWidget.super.call( this, config );\n\n\t\t// Call mixin constructor\n\t\tOO.ui.mixin.GroupElement.call(\n\t\t\tthis,\n\t\t\tObject.assign( {}, config, { $group: $group } )\n\t\t);\n\t};\n\n\tOO.inheritClass( MixinCustomWidget, OO.ui.Widget );\n\tOO.mixinClass( MixinCustomWidget, OO.ui.mixin.GroupElement );\n\n\t/* Event handlers (non-OOUI) and related logic */\n\n\t/**\n\t * Simple object for keeping track of validation errors.\n\t *\n\t * @class ErrorStateTracker\n\t * @constructor\n\t */\n\tconst ErrorStateTracker = function () {\n\t\tthis.errors = {};\n\t\tthis.messageBoxes = {};\n\t};\n\n\t/**\n\t * Set or clear an error state.\n\t *\n\t * @param {string} errorKey A unique key identifying this error\n\t * @param {boolean} state true sets an error for this key, and false clear it\n\t * @param {Element} messageBox\n\t */\n\tErrorStateTracker.prototype.setErrorState = function ( errorKey, state, messageBox ) {\n\t\tif ( state ) {\n\t\t\tthis.errors[ errorKey ] = true;\n\t\t\tthis.messageBoxes[ errorKey ] = messageBox;\n\t\t} else {\n\t\t\tdelete this.errors[ errorKey ];\n\t\t\tdelete this.messageBoxes[ errorKey ];\n\t\t}\n\t};\n\n\t/**\n\t * Gets the message box relating to an error\n\t *\n\t * @param {string} errorKey A unique key identifying this error\n\t * @return {Element}\n\t */\n\tErrorStateTracker.prototype.getMessageBox = function ( errorKey ) {\n\t\treturn this.messageBoxes[ errorKey ];\n\t};\n\n\t/**\n\t * Is one or more error currently set?\n\t *\n\t * @return {boolean}\n\t */\n\tErrorStateTracker.prototype.hasErrorState = function () {\n\t\treturn Object.keys( this.errors ).length > 0;\n\t};\n\n\t// General error state tracker for the page\n\tconst errorStateTracker = new ErrorStateTracker();\n\n\t// Connect handler for error-state events\n\teventBus.on( 'error-state', ( errorKey, state, messageBox ) => {\n\n\t\t// Pass state on to errorStateTracker\n\t\terrorStateTracker.setErrorState( errorKey, state, messageBox );\n\n\t\t// Update the submit button\n\t\tif ( errorStateTracker.hasErrorState() ) {\n\t\t\t$submitBtn.prop( 'disabled', true );\n\t\t} else {\n\t\t\t$submitBtn.prop( 'disabled', false );\n\t\t}\n\t} );\n\n\tfunction updateThrottle() {\n\t\tif ( $( '#throttle-enabled' ).prop( 'checked' ) ) {\n\t\t\t$( '.cn-throttle-amount' ).show();\n\t\t} else {\n\t\t\t$( '.cn-throttle-amount' ).hide();\n\t\t}\n\t}\n\n\tfunction updateWeightColumn() {\n\t\tif ( $( '#balanced' ).prop( 'checked' ) ) {\n\t\t\t$( '.cn-weight' ).hide();\n\t\t} else {\n\t\t\t$( '.cn-weight' ).show();\n\t\t}\n\t}\n\n\tfunction updateBuckets() {\n\t\tconst numBuckets = getNumBuckets(),\n\t\t\tmaxNumBuckets = mw.config.get( 'wgNoticeNumberOfBuckets' ),\n\t\t\t$bucketSelectors = $( 'select.bucketSelector' ),\n\n\t\t\t$bucketSelectorUnassigned =\n\t\t\t\t$bucketSelectors.not( '.bucketSelectorForAssignedBanners' );\n\n\t\t// Change selected value of bucket selectors to only available buckets\n\t\t$bucketSelectors.each( function () {\n\t\t\tconst $selector = $( this ),\n\t\t\t\tselectedVal = $selector.val();\n\n\t\t\t$selector.val( selectedVal % numBuckets );\n\t\t} );\n\n\t\t// If only one bucket is available, disable the selectors for unassigned banners,\n\t\t// and enable them if more than one bucket is available.\n\n\t\t// (If we disable selectors for assigned banners, then they won't send their\n\t\t// values when the form is submitted. In that case, if the number of buckets were\n\t\t// changed from a value greater than 1 to 1, the selectors would show all banners\n\t\t// on bucket 0, but for any banners that were moved to bucket 0, the change would\n\t\t// not be submitted to the server.)\n\n\t\tif ( numBuckets === 1 ) {\n\t\t\t$bucketSelectorUnassigned.prop( 'disabled', true );\n\t\t} else {\n\t\t\t$bucketSelectors.prop( 'disabled', false );\n\t\t}\n\n\t\t// Enable or disable bucket options in drop-downs, as appropriate\n\t\tfor ( let i = 0; i < maxNumBuckets; i++ ) {\n\t\t\tconst isBucketDisabled = ( i >= numBuckets );\n\n\t\t\t$bucketSelectors.find( 'option[value=' + i + ']' )\n\t\t\t\t.prop( 'disabled', isBucketDisabled );\n\t\t}\n\n\t\t// Broadcast bucket change event\n\t\teventBus.emit( 'bucket-change', numBuckets );\n\n\t\t// It's important to update assigned banners *after* emitting bucket-change so\n\t\t// widgets first can adjust to the new bucket number.\n\t\tupdateAssignedBanners();\n\t}\n\n\tfunction getNumBuckets() {\n\t\treturn +$( 'select#buckets' ).val();\n\t}\n\n\t/**\n\t * Hide or display campaign mixin parameter controls based on checkbox state.\n\t * The mixin name and any existing parameter values are received as data\n\t * properties on the checkbox.\n\t */\n\tfunction showOrHideCampaignMixinControls() {\n\n\t\tconst $checkBox = $( this ),\n\t\t\tmixinName = $checkBox.data( 'mixin-name' );\n\t\tlet $paramControlSet = $( '#' + mixinParamControlsId( mixinName ) );\n\n\t\tif ( $checkBox.prop( 'checked' ) ) {\n\n\t\t\t// If the controls don't exist yet, create them\n\t\t\tif ( $paramControlSet.length === 0 ) {\n\n\t\t\t\tconst paramValues = $checkBox.data( 'mixin-param-values' );\n\n\t\t\t\t// If the mixin uses a custom UI to set params, instantiate that\n\t\t\t\tif ( mixinDefs[ mixinName ].customAdminUIControlsModule ) {\n\n\t\t\t\t\tconst mixinCustomUiController = mixinCustomUiControllerFactory\n\t\t\t\t\t\t.create( mixinName );\n\n\t\t\t\t\tmixinCustomUiController.init( paramValues );\n\t\t\t\t\t$paramControlSet = mixinCustomUiController.$widgetElement;\n\n\t\t\t\t} else {\n\n\t\t\t\t\t// Otherwise, create generic controls using a template\n\t\t\t\t\t$paramControlSet = makeMixinParamControlSet(\n\t\t\t\t\t\tmixinName,\n\t\t\t\t\t\tparamValues\n\t\t\t\t\t);\n\n\t\t\t\t\t// Hook up handler for verification\n\t\t\t\t\tconst $paramControls = $paramControlSet.find( 'input' );\n\t\t\t\t\t$paramControls.on(\n\t\t\t\t\t\t'keyup keydown change mouseup cut paste focus blur',\n\t\t\t\t\t\tmw.util.debounce( verifyParamControl, 100 )\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Attach the controls\n\t\t\t\t$checkBox.parent( 'div' ).append( $paramControlSet );\n\n\t\t\t} else {\n\t\t\t\t$paramControlSet.show();\n\t\t\t}\n\t\t} else if ( $paramControlSet.length !== 0 ) {\n\t\t\t$paramControlSet.hide();\n\t\t}\n\t}\n\n\tfunction mixinParamControlsId( mixinName ) {\n\t\treturn 'notice-mixin-' + mixinName + '-paramControls';\n\t}\n\n\tfunction makeNoticeMixinControlName( mixinName, paramName ) {\n\t\treturn 'notice-mixin-' + mixinName + '-' + paramName;\n\t}\n\n\tfunction makeMixinParamControlSet( mixinName, paramValues ) {\n\n\t\tconst paramDefs = mixinDefs[ mixinName ].parameters,\n\t\t\teditable = mw.config.get( 'CentralNoticeEditable' ),\n\t\t\ttemplateVars = {\n\t\t\t\tdivId: mixinParamControlsId( mixinName ),\n\t\t\t\tparams: []\n\t\t\t};\n\n\t\tparamValues = paramValues || {};\n\n\t\t// eslint-disable-next-line no-jquery/no-each-util\n\t\t$.each( paramDefs, ( paramName, paramDef ) => {\n\n\t\t\tconst paramTemplateVars = {\n\n\t\t\t\tlabelMsg: mw.message( paramDef.labelMsg ).text(),\n\t\t\t\tinputName: makeNoticeMixinControlName( mixinName, paramName ),\n\t\t\t\tdataType: paramDef.type,\n\t\t\t\tminVal: paramDef.minVal,\n\t\t\t\tmaxVal: paramDef.maxVal,\n\t\t\t\tstep: paramDef.step,\n\t\t\t\tdisabled: !editable\n\t\t\t};\n\n\t\t\tswitch ( paramDef.type ) {\n\t\t\t\tcase 'string':\n\t\t\t\t\tparamTemplateVars.inputType = 'text';\n\t\t\t\t\tparamTemplateVars.inputSizeFlagAndVar = {\n\t\t\t\t\t\tinputSize: 30\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'integer':\n\t\t\t\tcase 'float':\n\t\t\t\t\tparamTemplateVars.inputType = 'text';\n\t\t\t\t\tparamTemplateVars.inputSizeFlagAndVar = {\n\t\t\t\t\t\tinputSize: 5\n\t\t\t\t\t};\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'boolean':\n\t\t\t\t\tparamTemplateVars.inputType = 'checkbox';\n\t\t\t\t\tparamTemplateVars.inputValue = paramName;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'json':\n\t\t\t\t\tthrow new Error( 'json parameter type requires custom admin UI module.' );\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new Error( 'Invalid parameter definition type: ' + paramDef.type );\n\t\t\t}\n\n\t\t\t// If parameter value was not provided, set a default\n\t\t\tif ( !( paramName in paramValues ) ) {\n\t\t\t\tif ( typeof paramDef.defaultValue !== 'undefined' ) {\n\t\t\t\t\tparamValues[ paramName ] = paramDef.defaultValue;\n\t\t\t\t} else {\n\t\t\t\t\tswitch ( paramDef.type ) {\n\t\t\t\t\t\tcase 'string':\n\t\t\t\t\t\t\tparamValues[ paramName ] = '';\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase 'integer':\n\t\t\t\t\t\tcase 'float':\n\t\t\t\t\t\t\tparamValues[ paramName ] = '0';\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tcase 'boolean':\n\t\t\t\t\t\t\tparamValues[ paramName ] = false;\n\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tthrow new Error( 'Invalid parameter definition type: ' + paramDef.type );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set form control values\n\t\t\tswitch ( paramDef.type ) {\n\t\t\t\tcase 'string':\n\t\t\t\tcase 'integer':\n\t\t\t\tcase 'float':\n\t\t\t\t\tparamTemplateVars.inputValue = paramValues[ paramName ];\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase 'boolean':\n\t\t\t\t\tif ( paramValues[ paramName ] ) {\n\t\t\t\t\t\tparamTemplateVars.checkedFlagAndVar = {\n\t\t\t\t\t\t\tchecked: 'checked'\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new Error( 'Invalid parameter definition type: ' + paramDef.type );\n\t\t\t}\n\n\t\t\tif ( paramDef.helpMsg ) {\n\n\t\t\t\tparamTemplateVars.help = mw.message( paramDef.helpMsg ).text();\n\t\t\t}\n\n\t\t\ttemplateVars.params.push( paramTemplateVars );\n\t\t} );\n\n\t\treturn $( mixinParamsTemplate.render( templateVars ) );\n\t}\n\n\tfunction verifyParamControl() {\n\t\tconst $input = $( this ),\n\t\t\tval = $input.val().trim();\n\n\t\tswitch ( $input.data( 'data-type' ) ) {\n\t\t\tcase 'integer':\n\t\t\t\tif ( /^-?\\d+$/.test( val ) ) {\n\t\t\t\t\tsetValidationError( false, $input );\n\t\t\t\t} else {\n\t\t\t\t\tsetValidationError(\n\t\t\t\t\t\ttrue, $input, 'centralnotice-notice-mixins-int-required'\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase 'float':\n\t\t\t\tif ( /^-?\\d+\\.?\\d*$|^-?\\d*\\.?\\d+$/.test( val ) ) {\n\t\t\t\t\tsetValidationError( false, $input );\n\t\t\t\t} else {\n\t\t\t\t\tsetValidationError(\n\t\t\t\t\t\ttrue, $input, 'centralnotice-notice-mixins-float-required'\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif ( !isNaN( $input.data( 'min-val' ) ) && Number( val ) < Number( $input.data( 'min-val' ) ) ) {\n\t\t\tsetValidationError(\n\t\t\t\ttrue, $input, 'centralnotice-notice-mixins-out-of-bound'\n\t\t\t);\n\t\t}\n\n\t\tif ( !isNaN( $input.data( 'max-val' ) ) && Number( val ) > Number( $input.data( 'max-val' ) ) ) {\n\t\t\tsetValidationError(\n\t\t\t\ttrue, $input, 'centralnotice-notice-mixins-out-of-bound'\n\t\t\t);\n\t\t}\n\t}\n\n\tfunction setValidationError( error, $input, msgKey ) {\n\t\tconst errorKey = $input.attr( 'name' );\n\t\tlet messageBox = errorStateTracker.getMessageBox( errorKey );\n\n\t\tif ( error ) {\n\t\t\tif ( !messageBox ) {\n\n\t\t\t\tmessageBox = mw.util.messageBox( mw.message( msgKey ).text(), 'alert' );\n\t\t\t\t$input.closest( 'p' ).before( messageBox );\n\t\t\t}\n\n\t\t\teventBus.emit( 'error-state', errorKey, true, messageBox );\n\t\t} else {\n\t\t\tif ( messageBox ) {\n\t\t\t\tmessageBox.remove();\n\t\t\t}\n\t\t\teventBus.emit( 'error-state', errorKey, false );\n\t\t}\n\t}\n\n\t/**\n\t * Create a by-bucket index of assigned banners using data received from the server.\n\t */\n\tfunction setUpAssignedBanners() {\n\t\t// Create outer array and inner arrays for all possible buckets\n\t\tassignedBanners = [];\n\n\t\tfor ( let i = 0; i < mw.config.get( 'wgNoticeNumberOfBuckets' ); i++ ) {\n\t\t\tassignedBanners[ i ] = [];\n\t\t}\n\n\t\t// Get the data sent from the server\n\t\t// If there are no assigned banners, the assigned banner fieldset isn't included\n\t\t// in the page. In that case, jQuery will return undefined from data()\n\t\tconst assignedBannersFlat =\n\t\t\t$( '#centralnotice-assigned-banners' ).data( 'assigned-banners' ) || [];\n\n\t\t// Fill up the index\n\t\tfor ( let i = 0; i < assignedBannersFlat.length; i++ ) {\n\t\t\tassignedBanners[ assignedBannersFlat[ i ].bucket ]\n\t\t\t\t.push( assignedBannersFlat[ i ].bannerName );\n\t\t}\n\t}\n\n\t/**\n\t * Update the by-bucket index of assigned banners when a remove banner checkbox or a\n\t * bucket selector for an assigned banner changes. Then, broadcast the\n\t * assigned-banner-change event.\n\t */\n\tfunction updateAssignedBanners() {\n\t\tconst $removeCheckboxes = $( '.bannerRemoveCheckbox' ),\n\t\t\t$selectors = $( '.bucketSelectorForAssignedBanners' ),\n\t\t\tremovedBanners = [];\n\n\t\t// Create an array with the names of banners whose remove checkbox is checked\n\t\t$removeCheckboxes.each( function () {\n\t\t\tconst $this = $( this );\n\t\t\tif ( $this.prop( 'checked' ) ) {\n\t\t\t\tremovedBanners.push( $this.val() );\n\t\t\t}\n\t\t} );\n\n\t\t// Iterate over the bucket selectors for assigned banners\n\t\t$selectors.each( function () {\n\t\t\tconst $this = $( this ),\n\t\t\t\tassignedBucket = +$this.val(),\n\t\t\t\tbannerName = $this.data( 'banner-name' ),\n\t\t\t\tremoved = ( removedBanners.includes( bannerName ) );\n\n\t\t\t// Iterate over all buckets, adding banners to the index or removeing them,\n\t\t\t// as needed. (assignedBanners has elements for all possible buckets.)\n\t\t\t// TODO Make the order of banners the same as the order displayed in the UI\n\t\t\tfor ( let i = 0; i < assignedBanners.length; i++ ) {\n\n\t\t\t\tconst bannerIdx = assignedBanners[ i ].indexOf( bannerName );\n\n\t\t\t\t// If the box is checked to remove, just ensure the banner is not there\n\t\t\t\tif ( removed ) {\n\t\t\t\t\tif ( bannerIdx !== -1 ) {\n\t\t\t\t\t\tassignedBanners[ i ].splice( bannerIdx, 1 );\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// If the banner is assigned to this bucket but not in the array, add it.\n\t\t\t\tif ( i === assignedBucket && bannerIdx === -1 ) {\n\t\t\t\t\tassignedBanners[ i ].push( bannerName );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// If the banner isn't assigned to this bucket but it is in the array,\n\t\t\t\t// remove it.\n\t\t\t\tif ( i !== assignedBucket && bannerIdx !== -1 ) {\n\t\t\t\t\tassignedBanners[ i ].splice( bannerIdx, 1 );\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\n\t\t// Broadcast the event\n\t\teventBus.emit( 'assigned-banners-change' );\n\t}\n\n\t/**\n\t * Get an array of banners assigned to a specific bucket.\n\t *\n\t * @param {number} bucket\n\t * @return {Array}\n\t */\n\tfunction getAssignedBanners( bucket ) {\n\t\treturn assignedBanners[ bucket ];\n\t}\n\n\t/**\n\t * Get the human-friendly alphabetic label for a bucket number\n\t *\n\t * @param {number} bucket\n\t * @return {string}\n\t */\n\tfunction getBucketLabel( bucket ) {\n\t\treturn BUCKET_LABELS[ bucket ];\n\t}\n\n\t/* Exports */\n\n\tmodule.exports = {\n\n\t\t/**\n\t\t * Base class for custom campaign mixin UI controllers.\n\t\t *\n\t\t * @see MixinCustomUiController\n\t\t * @type {Function}\n\t\t */\n\t\tMixinCustomUiController: MixinCustomUiController,\n\n\t\t/**\n\t\t * Base class for custom campaign mixin widgets.\n\t\t *\n\t\t * @see MixinCustomWidget\n\t\t * @type {Function}\n\t\t */\n\t\tMixinCustomWidget: MixinCustomWidget,\n\n\t\t/**\n\t\t * Simple object for keeping track of validation errors.\n\t\t *\n\t\t * @see ErrorStateTracker\n\t\t * @type {Function}\n\t\t */\n\t\tErrorStateTracker: ErrorStateTracker,\n\n\t\t/**\n\t\t * Factory for custom mixin UI controllers.\n\t\t *\n\t\t * @type {OO.Factory}\n\t\t */\n\t\tmixinCustomUiControllerFactory: mixinCustomUiControllerFactory,\n\n\t\t/**\n\t\t * Centralized object for emitting and subscribing to events.\n\t\t *\n\t\t * @type {OO.EventEmitter}\n\t\t */\n\t\teventBus: eventBus,\n\n\t\t/**\n\t\t * Get the number of buckets currently set in the bucket input.\n\t\t *\n\t\t * @method\n\t\t * @return {number}\n\t\t */\n\t\tgetNumBuckets: getNumBuckets,\n\n\t\t/**\n\t\t * Get the human-friendly alphabetic label for a bucket number.\n\t\t *\n\t\t * @method\n\t\t * @param {number} bucket\n\t\t * @return {string}\n\t\t */\n\t\tgetBucketLabel: getBucketLabel,\n\n\t\t/**\n\t\t * Get an array of banners assigned to a specific bucket.\n\t\t *\n\t\t * @method\n\t\t * @param {number} bucket\n\t\t * @return {Array}\n\t\t */\n\t\tgetAssignedBanners: getAssignedBanners\n\t};\n\n\t/* General setup */\n\n\t/**\n\t * Finalize setup: initialize slider, set handlers, set variables for jQuery elements\n\t */\n\tfunction initialize() {\n\t\tconst $mixinCheckboxes = $( 'input.noticeMixinCheck' );\n\n\t\t$( '#centralnotice-throttle-amount' ).slider( {\n\t\t\trange: 'min',\n\t\t\tmin: 0,\n\t\t\tmax: 100,\n\t\t\tvalue: $( '#centralnotice-throttle-cur' ).val(),\n\t\t\tstep: stepSize,\n\t\t\tslide: function ( event, element ) {\n\t\t\t\tconst val = Number( element.value ),\n\t\t\t\t\trounded = Math.round( val * 10 ) / 10;\n\t\t\t\t$( '#centralnotice-throttle-echo' ).text( String( rounded ) + '%' );\n\t\t\t\t$( '#centralnotice-throttle-cur' ).val( val );\n\t\t\t}\n\t\t} );\n\n\t\t$submitBtn = $( '#noticeDetailSubmit' );\n\t\t$form = $( '#centralnotice-notice-detail' );\n\n\t\tupdateThrottle();\n\t\tupdateWeightColumn();\n\t\tsetUpAssignedBanners();\n\t\tupdateBuckets();\n\n\t\t$( '#throttle-enabled' ).on( 'click', updateThrottle );\n\t\t$( '#balanced' ).on( 'click', updateWeightColumn );\n\t\t$( 'select#buckets' ).on( 'change', updateBuckets );\n\t\t$( '.bucketSelectorForAssignedBanners, .bannerRemoveCheckbox' )\n\t\t\t.on( 'change', updateAssignedBanners );\n\n\t\t$mixinCheckboxes.each( showOrHideCampaignMixinControls );\n\t\t$mixinCheckboxes.on( 'change', showOrHideCampaignMixinControls );\n\t}\n\n\t// We have to wait for document ready and for custom controls modules to be loaded\n\t// before initializing everything\n\t$( () => {\n\t\t// eslint-disable-next-line no-jquery/no-map-util\n\t\tconst customControlsModules = $.map(\n\t\t\tmixinDefs, ( mixinDef ) => mixinDef.customAdminUIControlsModule\n\t\t);\n\n\t\t// Custom mixin control modules depend on this module so they can access base\n\t\t// classes here when they declare subclasses. So, this module can't depend on\n\t\t// them. However, we need those modules to be loaded when we first call\n\t\t// showOrHideCampaignMixinControls() (from initialize(), above). Since the\n\t\t// custom control modules are added server-side, the following call to\n\t\t// mw.loader.using() should be quick.\n\t\tmw.loader.using( customControlsModules ).done( initialize );\n\t} );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/infrastructure/centralnotice.js","messages":[],"suppressedMessages":[{"ruleId":"camelcase","severity":2,"message":"Identifier 'show_only_matches' is not in camel case.","line":104,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":104,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'show_only_matches_children' is not in camel case.","line":106,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":106,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":171,"column":4,"nodeType":"CallExpression","endLine":171,"endColumn":68,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeIn","line":177,"column":5,"nodeType":"CallExpression","endLine":177,"endColumn":68,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":181,"column":5,"nodeType":"CallExpression","endLine":181,"endColumn":69,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":808,"column":5,"nodeType":"CallExpression","endLine":820,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":808,"column":5,"nodeType":"CallExpression","endLine":830,"endColumn":8},{"ruleId":"mediawiki/no-unlabeled-buttonwidget","severity":1,"message":"OOUI button has no label. Even icon-only buttons should set a label with invisibleLabel set to true.","line":1251,"column":20,"nodeType":"NewExpression","messageId":"noLabel","endLine":1254,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":1322,"column":5,"nodeType":"CallExpression","endLine":1339,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":1322,"column":5,"nodeType":"CallExpression","endLine":1349,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":1358,"column":5,"nodeType":"CallExpression","endLine":1375,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":1358,"column":5,"nodeType":"CallExpression","endLine":1385,"endColumn":8}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Custom UI for banner sequence campaign mixin administration.\n *\n * Code layout and separation of concerns\n * --------------------------------------\n *\n * The code for this part of the UI roughly follows an MVC pattern. Here are the\n * components and rules for separation of concerns:\n *\n * - Controller (BannerSequenceUiController):\n *   - Controls most interactions among components and with other parts of the UI (managed\n *     by ext.centralNotice.adminUi.campaignManager). The main exceptions to this rule\n *     are: widgets notify the controller of updates, may request certain information from\n *     the controller, and manage their contained widgets.\n *   - The controller instantiates the main container widget.\n *   - It provides methods to handle changes to the model.\n *   - To act on smaller widgets contained within the main container widget, the\n *     controller either goes through the main container, or receives a reference to the\n *     contained widget.\n *\n * - Model (BannerSequenceUiModel):\n *   - The model is passive, and acts only on itself.\n *   - It is responsible for validation, providing default values, and updating the data.\n *\n * - View widgets (BannerSequenceWidget, BucketSeqContainerWidget, BucketSeqWidget,\n *   StepWidget):\n *   - Widgets receive a model on instantiation (either the full model, in the case of\n *     the main container widget, or submodels that correspond to the data they set).\n *   - Widgets provide updateFromModel() methods, in which they update their values, add\n *     or remove contained widgets as needed, and tell contained widgets to update their\n *     values.\n *   - Widgets can read from their models, but cannot alter on the models directly; to\n *     update the data, widgets go through the controller.\n *   - Widgets receive user interactions directly, manage their own state, display\n *     validation error messages, keep track of contained widgets, and handle events from\n *     contained widgets.\n *\n * Data structure for sequences\n * ----------------------------\n *\n * The same sequences data structure is used internally by BannerSequenceUiModel and\n * externally for the mixin parameters sent to the server and received by the\n * mixin's subscribing module, ext.centralNotice.bannerSequence. Here is the structure:\n *\n *   // Outer array; each element is a sequence for a bucket. The element's index\n *   // corresponds to the bucket number.\n *   [\n *\n *     // Inner arrays are sequences; each element corresponds to a step in the sequence.\n *     [\n *\n *       // The elements of the inner arrays are objects, whose properties control\n *       // the functioning of the step they represent.\n *       {\n *\n *         // {string} The name of the banner to display, or null to display no\n *         // banner during this step.\n *         'banner': 'TheNameOfABanner',\n *\n *         // {number} The number of page views to display this step.\n *         'numPageViews': 4,\n *\n *         // {string} An identifier to use for a flag in the browser. If a flag with the\n *         // identifier is present, the step will be skipped. If not, the step will be\n *         // shown, and when it completes, a flag with this identifier will be set. If\n *         // this property is null, the step will always show.\n *         'skipWithIdentifier': null\n *       }\n *     ]\n *   ]\n */\n\n// TODO: Maybe on submit, we should verify that the data shown in the widgets is the same\n// as what we're submitting? Given the complexity of this code, it's not inconceivable\n// that a bug could cause different data to display than what be submitted.\n\n( function () {\n\n\tlet BannerSequenceUiController = null, BannerSequenceUiModel = null,\n\t\tBannerSequenceWidget = null, BucketSeqContainerWidget = null, BucketSeqWidget = null,\n\t\tStepWidget = null;\n\tconst campaignManager = require( 'ext.centralNotice.adminUi.campaignManager' );\n\n\t/* BannerSequenceUiController */\n\n\t/**\n\t * Singleton controller for the banner sequence administration UI.\n\t *\n\t * @class BannerSequenceUiController\n\t * @constructor\n\t */\n\tBannerSequenceUiController = function () {\n\t\tBannerSequenceUiController.super.call( this );\n\t};\n\n\tOO.inheritClass(\n\t\tBannerSequenceUiController,\n\t\tcampaignManager.MixinCustomUiController\n\t);\n\n\t// This allows JS for the rest of the UI to find this class following registration\n\t// with campaignManager.mixinCustomUiControllerFactory\n\tBannerSequenceUiController.static.name = 'bannerSequence';\n\n\t/**\n\t * Setup to be called immediately after instantiation.\n\t *\n\t * @param {Object} [data] Current banner sequence settings received from the server.\n\t */\n\tBannerSequenceUiController.prototype.init = function ( data ) {\n\n\t\t// Don't assume that data has been provided\n\t\tdata = data || {};\n\n\t\t// Facility for widgets to get a unique (within this page view) key, used for\n\t\t// tracking error states\n\t\tthis.errorStateKeyAutoIncrement = 0;\n\n\t\t/**\n\t\t * The data model\n\t\t *\n\t\t * @property {BannerSequenceUiModel}\n\t\t */\n\t\tthis.model = new BannerSequenceUiModel(\n\t\t\tdata,\n\t\t\tcampaignManager.getNumBuckets()\n\t\t);\n\n\t\t// On instantiation, the widget will create subwidgets in accordance with the model\n\t\t/**\n\t\t * The enclosing view widget\n\t\t *\n\t\t * @property {BannerSequenceWidget}\n\t\t */\n\t\tthis.widget = new BannerSequenceWidget( this, this.model );\n\n\t\t/**\n\t\t * Access point for the view widget's element\n\t\t *\n\t\t * @property {jQuery}\n\t\t */\n\t\tthis.$widgetElement = this.widget.$element;\n\n\t\t// Banners might no longer be valid if the mixin was enabled, disabled and\n\t\t// re-enabled, and banner assignments were modified while it was disabled (since\n\t\t// re-enabled mixins remember their previous settings).\n\t\tthis.verifyAndFixBanners();\n\n\t\t// Set input fields for form submission (uses data from the model)\n\t\tthis.setSequencesInputParam();\n\t\tthis.setDaysInputParam();\n\n\t\t// Subscribe to external events\n\t\tcampaignManager.eventBus.connect( this, {\n\t\t\t'bucket-change': this.onBucketChange\n\t\t} );\n\n\t\tcampaignManager.eventBus.connect( this, {\n\t\t\t'assigned-banners-change': this.onAssignedBannersChange\n\t\t} );\n\t};\n\n\t/**\n\t * Get the list of banners currently available for a bucket.\n\t *\n\t * @param {number} bucket\n\t * @return {string[]}\n\t */\n\tBannerSequenceUiController.prototype.getBannersForBucket = function ( bucket ) {\n\t\treturn campaignManager.getAssignedBanners( bucket );\n\t};\n\n\t/**\n\t * Get the human-readable alphabetic label for a bucket.\n\t *\n\t * @param {number} bucket\n\t * @return {string}\n\t */\n\tBannerSequenceUiController.prototype.getBucketLabel = function ( bucket ) {\n\t\treturn campaignManager.getBucketLabel( bucket );\n\t};\n\n\tBannerSequenceUiController.prototype.onBucketChange = function ( numBuckets ) {\n\t\tthis.model.updateNumBuckets( numBuckets );\n\t\tthis.widget.updateFromModel();\n\n\t\t// The banners available for different buckets may have changed\n\t\tthis.verifyAndFixBanners();\n\t\tthis.setSequencesInputParam();\n\t};\n\n\tBannerSequenceUiController.prototype.onAssignedBannersChange = function () {\n\t\tthis.verifyAndFixBanners();\n\t\tthis.setSequencesInputParam();\n\t};\n\n\t/**\n\t * Check that all banners currently set in sequence steps are available for the\n\t * sequence's bucket. If any are not available, update the model and widgets and set\n\t * errors accordingly.\n\t */\n\tBannerSequenceUiController.prototype.verifyAndFixBanners = function () {\n\n\t\tlet bucket, bannersForBucket, stepsWithMissingBanners;\n\n\t\t// Iterate over active buckets\n\t\tfor ( bucket = 0; bucket < campaignManager.getNumBuckets(); bucket++ ) {\n\n\t\t\tbannersForBucket = this.getBannersForBucket( bucket );\n\n\t\t\t// Ask the model to check itself, and get an array of steps with issues\n\t\t\tstepsWithMissingBanners = this.model.verifyAndFixBannersForBucket(\n\t\t\t\tbucket,\n\t\t\t\tbannersForBucket\n\t\t\t);\n\n\t\t\t// If there were any problem steps in this bucket, tell the widget to update\n\t\t\t// and set error messages as needed\n\t\t\tif ( stepsWithMissingBanners.length > 0 ) {\n\t\t\t\tthis.widget.updateFromModelForBucket( bucket );\n\t\t\t\tthis.widget.setMissingBannerErrorsForBucket(\n\t\t\t\t\tbucket, stepsWithMissingBanners );\n\t\t\t}\n\n\t\t\t// Tell the widget to update the options in the banner drop-downs\n\t\t\tthis.widget.updateBannersForDropdownsForBucket( bucket, bannersForBucket );\n\t\t}\n\t};\n\n\t/**\n\t * Get a unique (within this page view) key, for tracking error states\n\t *\n\t * @return {string}\n\t */\n\tBannerSequenceUiController.prototype.getErrorStateKey = function () {\n\t\treturn String( this.errorStateKeyAutoIncrement++ );\n\t};\n\n\t/**\n\t * Add a new step with default values at the end of the sequence for this bucket,\n\t * and update the widget.\n\t *\n\t * @param {number} bucket\n\t */\n\tBannerSequenceUiController.prototype.addStep = function ( bucket ) {\n\t\tthis.model.addStep( bucket );\n\t\tthis.widget.updateFromModelForBucket( bucket );\n\t\tthis.setSequencesInputParam();\n\t};\n\n\t/**\n\t * Remove the indicated step in the sequence for the indicated bucket, and update the\n\t * widget.\n\t *\n\t * @param {number} bucket\n\t * @param {number} stepNum\n\t */\n\tBannerSequenceUiController.prototype.removeStep = function ( bucket, stepNum ) {\n\t\tthis.model.removeStep( bucket, stepNum );\n\t\tthis.setSequencesInputParam();\n\t\tthis.widget.removeStepForBucket( bucket, stepNum );\n\n\t\t// Updating from model ensures widget state is all good (for example, add step\n\t\t// button state)\n\t\tthis.widget.updateFromModelForBucket( bucket );\n\t};\n\n\t/**\n\t * Set the global error state of the banner sequence controls. This is called when\n\t * a widget error state changes. The ID provided is used in the ID for the event\n\t * emitted (which will be used by a global error state tracker).\n\t *\n\t * @param {string} errorStateKey Unique key for this error state\n\t * @param {boolean} state true sets an error for this key, and false clear it\n\t */\n\tBannerSequenceUiController.prototype.setErrorState =\n\t\tfunction ( errorStateKey, state ) {\n\t\t\t// Broadcast an event to the rest of the UI\n\t\t\tcampaignManager.eventBus.emit(\n\t\t\t\t'error-state',\n\t\t\t\t'banner-sequence-' + errorStateKey,\n\t\t\t\tstate\n\t\t\t);\n\t\t};\n\n\t/**\n\t * Move a step to a new location, within the sequence of the bucket indicated.\n\t * Note: Does not update widgets from model, as it's not necessary.\n\t *\n\t * @param {number} bucket Bucket number\n\t * @param {number} newStepNum Index at which to place the step, according to\n\t *   how steps would be indexed before the step is removed from its current\n\t *   location.\n\t * @param {number} oldStepNum Current step index.\n\t */\n\tBannerSequenceUiController.prototype.moveStepNoWidgetUpdate =\n\t\tfunction ( bucket, newStepNum, oldStepNum ) {\n\t\t\tthis.model.moveStep( bucket, newStepNum, oldStepNum );\n\t\t\tthis.setSequencesInputParam();\n\t\t};\n\n\tBannerSequenceUiController.prototype.setBanner =\n\t\tfunction ( bucket, stepNum, banner, stepWidget ) {\n\t\t\tthis.model.setBanner( bucket, stepNum, banner );\n\t\t\tthis.setSequencesInputParam();\n\t\t\tstepWidget.model = this.model.getBktSequences()[ bucket ][ stepNum ];\n\t\t\tstepWidget.updateFromModel();\n\t\t};\n\n\tBannerSequenceUiController.prototype.setNumPageViews =\n\t\tfunction ( bucket, stepNum, numPageViews, stepWidget ) {\n\t\t\tthis.model.setNumPageViews( bucket, stepNum, numPageViews );\n\t\t\tthis.setSequencesInputParam();\n\t\t\tstepWidget.model = this.model.getBktSequences()[ bucket ][ stepNum ];\n\t\t\tstepWidget.updateFromModel();\n\t\t\tthis.widget.updateTotalPageViewsForBucket( bucket );\n\t\t};\n\n\tBannerSequenceUiController.prototype.setSkipWithIdentifier =\n\t\tfunction ( bucket, stepNum, skipWithIdentifier, stepWidget ) {\n\t\t\tthis.model.setSkipWithIdentifier( bucket, stepNum, skipWithIdentifier );\n\t\t\tthis.setSequencesInputParam();\n\t\t\tstepWidget.model = this.model.getBktSequences()[ bucket ][ stepNum ];\n\t\t\tstepWidget.updateFromModel();\n\t\t};\n\n\tBannerSequenceUiController.prototype.setDays = function ( days ) {\n\t\tthis.model.setDays( days );\n\t\tthis.setDaysInputParam();\n\t};\n\n\tBannerSequenceUiController.prototype.validateSkipWithIdentifier =\n\t\tfunction ( identifier ) {\n\t\t\treturn this.model.validateSkipWithIdentifier( identifier );\n\t\t};\n\n\tBannerSequenceUiController.prototype.canAddAStep = function ( bucket ) {\n\t\treturn this.model.canAddAStep( bucket );\n\t};\n\n\tBannerSequenceUiController.prototype.canRemoveAStep = function ( bucket ) {\n\t\treturn this.model.canRemoveAStep( bucket );\n\t};\n\n\tBannerSequenceUiController.prototype.canMoveSteps = function ( bucket ) {\n\t\treturn this.model.canMoveSteps( bucket );\n\t};\n\n\t/**\n\t * Set the value of the hidden form input for the sequences mixin parameter\n\t *\n\t * @private\n\t */\n\tBannerSequenceUiController.prototype.setSequencesInputParam = function () {\n\t\tthis.setParam( 'sequences', this.model.sequencesAsJSON() );\n\t};\n\n\t/**\n\t * Set the value of the hidden form input for the days mixin parameter\n\t *\n\t * @private\n\t */\n\tBannerSequenceUiController.prototype.setDaysInputParam = function () {\n\t\tthis.setParam( 'days', this.model.getDays() );\n\t};\n\n\t/* BannerSequenceUiModel */\n\n\t/**\n\t * Singleton model for the banner sequence administration UI.\n\t *\n\t * @class BannerSequenceUiModel\n\t * @constructor\n\t * @param {Array} data Array of banner sequences, by bucket\n\t * @param {number} numBuckets The number of buckets currently active (may be different\n\t *   from the number of sequences in data).\n\t */\n\tBannerSequenceUiModel = function ( data, numBuckets ) {\n\n\t\t// Initialize bucket sequences as a deep copy of data, or empty if not provided\n\t\tif ( data && data.sequences ) {\n\n\t\t\t// Even though validateAndFix() also checks for an array, we need to do so\n\t\t\t// now, too, before extending\n\t\t\tif ( Array.isArray( data.sequences ) ) {\n\n\t\t\t\t/**\n\t\t\t\t * Sequences by bucket (index corresponds to bucket number)\n\t\t\t\t *\n\t\t\t\t * @property {Array}\n\t\t\t\t */\n\t\t\t\tthis.bucketSequences = $.extend( true, [], data.sequences );\n\n\t\t\t} else {\n\t\t\t\tmw.log.warn( 'Received banner sequence data is not an array.' );\n\t\t\t\tthis.bucketSequences = [];\n\t\t\t}\n\n\t\t} else {\n\t\t\tthis.bucketSequences = [];\n\t\t}\n\n\t\t// Number of days that identifiers to skip steps should last, or default\n\t\tif ( data && data.days ) {\n\t\t\tthis.days = data.days;\n\t\t} else {\n\t\t\tthis.days = this.constructor.static.DEFAULT_DAYS_PARAM;\n\t\t}\n\n\t\t// Validate and, if necessary, repair the data received\n\t\tthis.validateAndFix();\n\n\t\t// Set the number of bucket and adjust data, if necessary.\n\t\t// (If no initial data was provided, this will create the correct number of\n\t\t// default sequences.)\n\t\tthis.updateNumBuckets( numBuckets );\n\t};\n\n\tOO.initClass( BannerSequenceUiModel );\n\n\t// TODO Check this is the right number\n\t/**\n\t * Maximum number of steps allowed in a sequence\n\t *\n\t * @private\n\t * @static\n\t */\n\tBannerSequenceUiModel.static.MAX_SEQ_STEPS = 20;\n\n\t/**\n\t * Default duration of identifiers to skip steps (in days)\n\t *\n\t * @private\n\t * @static\n\t */\n\tBannerSequenceUiModel.static.DEFAULT_DAYS_PARAM = 250;\n\n\t/**\n\t * Export the contents of the model as a JSON string.\n\t *\n\t * @return {string}\n\t */\n\tBannerSequenceUiModel.prototype.sequencesAsJSON = function () {\n\t\t// Only export valid data\n\t\tthis.validateAndFix();\n\t\treturn JSON.stringify( this.getBktSequences() );\n\t};\n\n\tBannerSequenceUiModel.prototype.getDays = function () {\n\t\treturn this.days;\n\t};\n\n\tBannerSequenceUiModel.prototype.setDays = function ( days ) {\n\t\tthis.days = days;\n\t};\n\n\tBannerSequenceUiModel.prototype.updateNumBuckets = function ( numBuckets ) {\n\n\t\t/**\n\t\t * Number of buckets in the model\n\t\t *\n\t\t * @property {number}\n\t\t */\n\t\tthis.numBuckets = numBuckets;\n\n\t\t// It's OK if there are more sequences in bucketSequences than there are active\n\t\t// buckets, since getBktSequences() outputs only sequences for active buckets.\n\n\t\t// Create default sequences as necessary\n\t\tfor ( let i = 0; i < this.numBuckets; i++ ) {\n\t\t\tif ( !this.bucketSequences[ i ] ) {\n\t\t\t\tthis.bucketSequences[ i ] = this.defaultBktSeq();\n\t\t\t}\n\t\t}\n\t};\n\n\tBannerSequenceUiModel.prototype.addStep = function ( bucket ) {\n\t\tconst bucketSequence = this.bucketSequences[ bucket ];\n\n\t\tbucketSequence.push( this.defaultStep() );\n\t};\n\n\tBannerSequenceUiModel.prototype.setBanner = function ( bucket, stepNum, banner ) {\n\t\tthis.bucketSequences[ bucket ][ stepNum ].banner = banner;\n\t};\n\n\tBannerSequenceUiModel.prototype.setNumPageViews =\n\t\tfunction ( bucket, stepNum, numPageViews ) {\n\t\t\tthis.bucketSequences[ bucket ][ stepNum ].numPageViews = numPageViews;\n\t\t};\n\n\tBannerSequenceUiModel.prototype.setSkipWithIdentifier =\n\t\tfunction ( bucket, stepNum, skipWithIdentifier ) {\n\t\t\tthis.bucketSequences[ bucket ][ stepNum ].skipWithIdentifier = skipWithIdentifier;\n\t\t};\n\n\tBannerSequenceUiModel.prototype.removeStep = function ( bucket, stepNum ) {\n\t\tthis.bucketSequences[ bucket ].splice( stepNum, 1 );\n\t};\n\n\t/**\n\t * Move a step to a new location, within the sequence of the bucket indicated.\n\t *\n\t * @param {number} bucket Bucket number\n\t * @param {number} newStepNum Index at which to place the step, according to\n\t *   how steps would be indexed before the step is removed from its current\n\t *   location.\n\t * @param {number} oldStepNum Current step index.\n\t */\n\tBannerSequenceUiModel.prototype.moveStep =\n\t\tfunction ( bucket, newStepNum, oldStepNum ) {\n\t\t\tconst step = this.bucketSequences[ bucket ][ oldStepNum ];\n\n\t\t\tnewStepNum = ( newStepNum > oldStepNum ) ? newStepNum - 1 : newStepNum;\n\n\t\t\tthis.bucketSequences[ bucket ].splice( oldStepNum, 1 );\n\t\t\tthis.bucketSequences[ bucket ].splice( newStepNum, 0, step );\n\t\t};\n\n\t/**\n\t * Get an array of sequences for all currently active buckets.\n\t *\n\t * @return {Object[]}\n\t */\n\tBannerSequenceUiModel.prototype.getBktSequences = function () {\n\t\t// Sliced in case it has data on buckets that have been de-activated\n\t\treturn this.bucketSequences.slice( 0, this.numBuckets );\n\t};\n\n\tBannerSequenceUiModel.prototype.canAddAStep = function ( bucket ) {\n\t\treturn this.bucketSequences[ bucket ].length <\n\t\t\tthis.constructor.static.MAX_SEQ_STEPS;\n\t};\n\n\tBannerSequenceUiModel.prototype.canRemoveAStep = function ( bucket ) {\n\t\treturn ( this.bucketSequences[ bucket ].length > 1 );\n\t};\n\n\tBannerSequenceUiModel.prototype.canMoveSteps = function ( bucket ) {\n\t\treturn ( this.bucketSequences[ bucket ].length > 1 );\n\t};\n\n\t/**\n\t * @private\n\t * @return {Object[]}\n\t */\n\tBannerSequenceUiModel.prototype.defaultBktSeq = function () {\n\t\treturn [ this.defaultStep() ];\n\t};\n\n\t/**\n\t * @private\n\t * @return {Object}\n\t */\n\tBannerSequenceUiModel.prototype.defaultStep = function () {\n\t\treturn {\n\t\t\tbanner: this.defaultBanner(),\n\t\t\tnumPageViews: this.defaultNumPageViews(),\n\t\t\tskipWithIdentifier: this.defaultSkipWithIdentifier()\n\t\t};\n\t};\n\n\t/**\n\t * @private\n\t * @return {Object|null}\n\t */\n\tBannerSequenceUiModel.prototype.defaultBanner = function () {\n\t\treturn null;\n\t};\n\n\t/**\n\t * @private\n\t * @return {number}\n\t */\n\tBannerSequenceUiModel.prototype.defaultNumPageViews = function () {\n\t\treturn 1;\n\t};\n\n\t/**\n\t * @private\n\t * @return {string|null}\n\t */\n\tBannerSequenceUiModel.prototype.defaultSkipWithIdentifier = function () {\n\t\treturn null;\n\t};\n\n\t/**\n\t * Validate this.bucketSequences and replace any data that's invalid.\n\t *\n\t * @private\n\t */\n\tBannerSequenceUiModel.prototype.validateAndFix = function () {\n\t\t// First, check for an array\n\t\tif ( !Array.isArray( this.bucketSequences ) ) {\n\t\t\tmw.log.warn( 'Bucket sequences should be an array.' );\n\t\t\tthis.bucketSequences = [];\n\t\t\treturn;\n\t\t}\n\n\t\t// Check the sequences in the array\n\t\tfor ( let i = 0; i < this.bucketSequences.length; i++ ) {\n\n\t\t\tif ( !this.validateBktSeq( this.bucketSequences[ i ] ) ) {\n\t\t\t\tmw.log.warn( 'Invalid data in sequence for bucket ' + i );\n\t\t\t\tthis.bucketSequences[ i ] = this.defaultBktSeq();\n\t\t\t}\n\t\t}\n\n\t\t// Check the days parameter\n\t\tif ( !this.validateDays( this.days ) ) {\n\t\t\tthis.days = this.constructor.static.DEFAULT_DAYS_PARAM;\n\t\t}\n\t};\n\n\t/**\n\t * @param {Array} seq\n\t * @private\n\t * @return {boolean}\n\t */\n\tBannerSequenceUiModel.prototype.validateBktSeq = function ( seq ) {\n\t\t// Check size limits\n\t\tif ( !Array.isArray( seq ) ||\n\t\t\t( seq.length > this.constructor.static.MAX_SEQ_STEPS ) ||\n\t\t\t( seq.length < 1 ) ) {\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check the steps in the sequence\n\t\tfor ( let i = 0; i < seq.length; i++ ) {\n\t\t\tif ( !this.validateStep( seq[ i ] ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t};\n\n\t/**\n\t * @param {Object} step\n\t * @private\n\t * @return {boolean}\n\t */\n\tBannerSequenceUiModel.prototype.validateStep = function ( step ) {\n\t\tconst hasOwn = Object.prototype.hasOwnProperty;\n\n\t\t// Check the step object\n\t\tif ( ( step === null ) || ( typeof step !== 'object' ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check that the properties exist and validate their values\n\n\t\tif ( !hasOwn.call( step, 'banner' ) || !this.validateBanner( step.banner ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( !hasOwn.call( step, 'numPageViews' ) ||\n\t\t\t\t!this.validateNumPageViews( step.numPageViews ) ) {\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( !hasOwn.call( step, 'skipWithIdentifier' ) ||\n\t\t\t!this.validateSkipWithIdentifier( step.skipWithIdentifier ) ) {\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t};\n\n\t// Validation methods for individual fields are not marked private, since they might\n\t// be called by the controller for the benefit of widgets (though, in practice, this\n\t// only happens with validateSkipWithIdentifier()).\n\n\tBannerSequenceUiModel.prototype.validateBanner = function ( banner ) {\n\t\t// Note: regex should coordinate with Banner::isValidBannerName() in Banner.php\n\t\treturn ( typeof banner === 'string' && /^[A-Za-z0-9_]{1,230}$/.test( banner ) ) ||\n\t\t\tbanner === null;\n\t};\n\n\tBannerSequenceUiModel.prototype.validateNumPageViews = function ( numPageViews ) {\n\t\treturn this.validateIntOneOrGreater( numPageViews );\n\t};\n\n\tBannerSequenceUiModel.prototype.validateSkipWithIdentifier = function ( id ) {\n\t\treturn ( typeof id === 'string' && !id.includes( '|' ) ) || id === null;\n\t};\n\n\tBannerSequenceUiModel.prototype.validateDays = function ( days ) {\n\t\treturn this.validateIntOneOrGreater( days );\n\t};\n\n\t/**\n\t * @param {any} n\n\t * @return {boolean}\n\t * @private\n\t */\n\tBannerSequenceUiModel.prototype.validateIntOneOrGreater = function ( n ) {\n\t\treturn typeof n === 'number' &&\n\t\t\tisFinite( n ) &&\n\t\t\tMath.floor( n ) === n &&\n\t\t\tn > 0;\n\t};\n\n\t/**\n\t * For all the steps in a sequence, check that any selected banners are included in\n\t * the provided list of assigned banners. If a step's selected banner is not in the\n\t * list, reset it to default and include the step's index in the returned array.\n\t *\n\t * @param {number} bucket The bucket whose sequence to check\n\t * @param {string[]} assignedBanners An array of the names of banners assigned to\n\t *   this bucket\n\t * @return {number[]} An array with the indexes of steps whose selected banners were\n\t *   not found in assignedBanners\n\t */\n\tBannerSequenceUiModel.prototype.verifyAndFixBannersForBucket = function (\n\t\tbucket,\n\t\tassignedBanners\n\t) {\n\t\tconst sequence = this.bucketSequences[ bucket ],\n\t\t\tstepsWithMissingBanners = [];\n\n\t\tfor ( let i = 0; i < sequence.length; i++ ) {\n\t\t\tconst banner = sequence[ i ].banner;\n\n\t\t\tif ( banner !== null && !assignedBanners.includes( banner ) ) {\n\t\t\t\tstepsWithMissingBanners.push( i );\n\t\t\t\tsequence[ i ].banner = this.defaultBanner();\n\t\t\t}\n\t\t}\n\n\t\treturn stepsWithMissingBanners;\n\t};\n\n\t/**\n\t * Global container widget for the banner sequence administration UI.\n\t *\n\t * @param {BannerSequenceUiController} controller\n\t * @param {BannerSequenceUiModel} model\n\t * @class BannerSequenceWidget\n\t * @constructor\n\t */\n\tBannerSequenceWidget = function ( controller, model ) {\n\n\t\t/**\n\t\t * @property {BannerSequenceUiController}\n\t\t */\n\t\tthis.controller = controller;\n\n\t\t/**\n\t\t * @property {BannerSequenceUiModel}\n\t\t */\n\t\tthis.model = model;\n\n\t\t// Call parent constructor\n\t\tBannerSequenceWidget.super.call( this, controller );\n\n\t\t// Set up days widget and field layout\n\n\t\tthis.daysInput = new OO.ui.NumberInputWidget( {\n\t\t\tmin: 1,\n\t\t\tisInteger: true,\n\t\t\tclasses: [ 'centralNoticeBannerSeqDays' ]\n\t\t} );\n\n\t\tthis.daysLayout = new OO.ui.FieldLayout( this.daysInput, {\n\t\t\tlabel: mw.message( 'centralnotice-banner-sequence-days' ).text(),\n\t\t\talign: 'left',\n\t\t\tclasses: [ 'centralNoticeBannerSeqDaysLayout' ]\n\t\t} );\n\n\t\t// Prepend help text and days input so they come before $group, in reverse order\n\t\tthis.$element.prepend(\n\t\t\t$( '<div>' )\n\t\t\t\t.addClass( 'htmlform-help' )\n\t\t\t\t.text( mw.message( 'centralnotice-banner-sequence-days-help' ).text() )\n\t\t);\n\n\t\tthis.$element.prepend( this.daysLayout.$element );\n\n\t\t// Append help text for sequences, so it comes after $group\n\t\tthis.$element.append(\n\t\t\t$( '<div>' )\n\t\t\t\t.addClass( 'centralNoticeBannerSeqHelpContainer' )\n\t\t\t\t.append(\n\t\t\t\t\t$( '<div>' )\n\t\t\t\t\t\t.addClass( 'htmlform-help' )\n\t\t\t\t\t\t.text( mw.message( 'centralnotice-banner-sequence-detailed-help' ).text() )\n\t\t\t\t)\n\t\t);\n\n\t\t// Create widgets and set values based on data from the model\n\t\tthis.updateFromModel();\n\n\t\t// Flag for suppressing some actions in change handlers when changes are not due to\n\t\t// user intervention\n\t\tthis.updating = false;\n\n\t\t// Change handler for days\n\t\tthis.daysInput.connect(\n\t\t\tthis,\n\t\t\t{ change: function () {\n\n\t\t\t\t// TODO Create a bug requesting public getValidity() on NumberInputWidget\n\t\t\t\t// (same note below for numPageViewsInput in StepWidget)\n\t\t\t\tthis.daysInput.input.getValidity().done( () => {\n\n\t\t\t\t\t// If the value passes validation, send it to the controller and\n\t\t\t\t\t// clear any errors\n\n\t\t\t\t\tif ( !this.updating ) {\n\t\t\t\t\t\tthis.controller.setDays( parseInt( this.daysInput.getValue(), 10 ) );\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.daysLayout.setErrors( [] );\n\t\t\t\t\tthis.controller.setErrorState( 'days', false );\n\n\t\t\t\t} ).fail( () => {\n\n\t\t\t\t\t// If the value fails validation, set errors\n\n\t\t\t\t\tthis.daysLayout.setErrors( [\n\t\t\t\t\t\tmw.message( 'centralnotice-banner-sequence-days-error' ).text()\n\t\t\t\t\t] );\n\n\t\t\t\t\tthis.controller.setErrorState( 'days', true );\n\n\t\t\t\t} );\n\t\t\t} }\n\t\t);\n\t};\n\n\tOO.inheritClass( BannerSequenceWidget, campaignManager.MixinCustomWidget );\n\n\t/**\n\t * Add or remove contained widgets, and update their values, based on the model.\n\t */\n\tBannerSequenceWidget.prototype.updateFromModel = function () {\n\t\tconst numSequences = this.model.getBktSequences().length;\n\n\t\tthis.updating = true;\n\n\t\t// Ensure BucketSeqContainerWidget widgets with correct values\n\t\tfor ( let b = 0; b < numSequences; b++ ) {\n\t\t\tthis.updateFromModelForBucket( b );\n\t\t}\n\n\t\t// Remove inactive bucket widgets\n\t\tif ( this.getItemCount() > numSequences ) {\n\t\t\tthis.removeItems( this.getItems().slice( numSequences ) );\n\t\t}\n\n\t\t// Update days input\n\t\tthis.daysInput.setValue( String( this.model.getDays() ) );\n\n\t\tthis.updating = false;\n\t};\n\n\t/**\n\t * Possibly create a widget, and update its values, based on the model, for the\n\t * sequence for a specific bucket.\n\t *\n\t * @param {number} bucket\n\t */\n\tBannerSequenceWidget.prototype.updateFromModelForBucket = function ( bucket ) {\n\n\t\tconst sequence = this.model.getBktSequences()[ bucket ],\n\t\t\tseqContainer = this.findItemFromData( bucket );\n\n\t\t// If we don't have a sequence container for this bucket it, create it\n\t\tif ( !seqContainer ) {\n\t\t\tthis.addItems(\n\t\t\t\t[ new BucketSeqContainerWidget(\n\t\t\t\t\tthis.controller,\n\n\t\t\t\t\t// The model for the sequence container is just the array of step, as\n\t\t\t\t\t// provided by the general model\n\t\t\t\t\tsequence,\n\t\t\t\t\t{ data: bucket }\n\t\t\t\t) ],\n\t\t\t\tbucket\n\t\t\t);\n\n\t\t} else {\n\n\t\t\t// If there already was a sequence container, reset its model and tell it to\n\t\t\t// update\n\t\t\tseqContainer.model = sequence;\n\t\t\tseqContainer.updateFromModel();\n\t\t}\n\t};\n\n\t/**\n\t * Tell the sequence container for a bucket to remove a step\n\t *\n\t * @param {Object} bucket\n\t * @param {number} stepNum\n\t */\n\tBannerSequenceWidget.prototype.removeStepForBucket = function ( bucket, stepNum ) {\n\t\tconst seqContainerWidget = this.findItemFromData( bucket );\n\n\t\tseqContainerWidget.removeStep( stepNum );\n\t};\n\n\t/**\n\t * Tell the sequence container for a bucket to set missing banner errors for one or\n\t * more steps.\n\t *\n\t * @param {number} bucket\n\t * @param {number[]} stepsWithMissingBanners\n\t */\n\tBannerSequenceWidget.prototype.setMissingBannerErrorsForBucket = function (\n\t\tbucket,\n\t\tstepsWithMissingBanners\n\t) {\n\t\tthis.findItemFromData( bucket ).setMissingBannerErrors( stepsWithMissingBanners );\n\t};\n\n\t/**\n\t * Tell the sequence container for a bucket to update banners in drop-down inputs.\n\t *\n\t * @param {Object} bucket\n\t * @param {string[]} banners\n\t */\n\tBannerSequenceWidget.prototype.updateBannersForDropdownsForBucket = function (\n\t\tbucket,\n\t\tbanners\n\t) {\n\t\tthis.findItemFromData( bucket ).updateBannersForDropdowns( banners );\n\t};\n\n\t/**\n\t * Tell the sequence container for a bucket to re-calculate total page views in the\n\t * sequence.\n\t *\n\t * @param {Object} bucket\n\t */\n\tBannerSequenceWidget.prototype.updateTotalPageViewsForBucket = function ( bucket ) {\n\t\tthis.findItemFromData( bucket ).updateTotalPageViews();\n\t};\n\n\t/* BucketSeqContainerWidget */\n\n\t/**\n\t * Container widget for a sequence for a bucket and related controls (heading and add\n\t * step button).\n\t *\n\t * @param {BannerSequenceUiController} controller\n\t * @param {BannerSequenceUiModel} model\n\t * @param {Object} config\n\t * @class BucketSeqContainerWidget\n\t * @constructor\n\t */\n\tBucketSeqContainerWidget = function ( controller, model, config ) {\n\n\t\t// Properties\n\t\tthis.controller = controller;\n\t\tthis.model = model;\n\t\tthis.bucket = config.data;\n\t\tthis.totalPageViews = null;\n\n\t\t// This is a contained widget that has only the sequence. It is lightweight; its\n\t\t// state and model are managed from BucketSeqContainerWidget.\n\t\tthis.bucketSeqWidget = new BucketSeqWidget();\n\n\t\t// Add step button\n\t\tthis.addStepButton = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'centralnotice-banner-sequence-bucket-add-step' ).text(),\n\t\t\ticon: 'add'\n\t\t} );\n\n\t\t// Heading text\n\t\tthis.$heading = $( '<div>' )\n\t\t\t.addClass( 'centralNoticeBannerSeqBucketSeqTitle' );\n\n\t\t// Add stuff to config before calling parent constructor\n\t\tconfig = Object.assign( {}, config, {\n\t\t\t$content: this.$heading,\n\t\t\tclasses: [ 'centralNoticeBannerSeqBucketSeqContainer' ]\n\t\t} );\n\n\t\t// Call parent constructor\n\t\tBucketSeqContainerWidget.super.call( this, config );\n\n\t\t// Call mixin constructor\n\t\tOO.ui.mixin.GroupElement.call(\n\t\t\tthis, Object.assign( {}, config, { $group: this.$element } ) );\n\n\t\t// Add widgets\n\t\tthis.addItems( [ this.bucketSeqWidget, this.addStepButton ] );\n\n\t\t// Add or remove steps, and set their values, based on the model\n\t\tthis.updateFromModel();\n\n\t\t// Handle reordering of steps (via drag-and-drop)\n\t\tthis.bucketSeqWidget.connect(\n\t\t\tthis,\n\t\t\t{ reorder: function ( item, newStepNum ) {\n\n\t\t\t\tthis.controller.moveStepNoWidgetUpdate(\n\t\t\t\t\tthis.bucket, newStepNum, item.getData() );\n\n\t\t\t\t// Reset step widget indexes\n\t\t\t\tthis.updateStepNumbers();\n\t\t\t} }\n\t\t);\n\n\t\t// Handle clicks on add step button\n\t\tthis.addStepButton.connect(\n\t\t\tthis,\n\t\t\t{ click: function () {\n\t\t\t\tthis.controller.addStep( this.bucket );\n\t\t\t} }\n\t\t);\n\t};\n\n\tOO.inheritClass( BucketSeqContainerWidget, OO.ui.Widget );\n\tOO.mixinClass( BucketSeqContainerWidget, OO.ui.mixin.GroupElement );\n\n\t/**\n\t * Add or remove steps, and update their values, based on the model.\n\t */\n\tBucketSeqContainerWidget.prototype.updateFromModel = function () {\n\t\t// Go through steps in model, adding or updating widgets as needed\n\t\tfor ( let i = 0; i < this.model.length; i++ ) {\n\t\t\tconst stepModel = this.model[ i ];\n\t\t\tconst stepWidget = this.bucketSeqWidget.findItemFromData( i );\n\n\t\t\tif ( !stepWidget ) {\n\t\t\t\tthis.addStepWidget( stepModel, i );\n\t\t\t} else {\n\t\t\t\tstepWidget.model = stepModel;\n\t\t\t\tstepWidget.setData( i );\n\t\t\t\tstepWidget.updateFromModel();\n\t\t\t}\n\t\t}\n\n\t\t// Remove unneeded step widgets\n\t\tif ( this.bucketSeqWidget.getItemCount() > this.model.length ) {\n\t\t\tthis.bucketSeqWidget.removeItems(\n\t\t\t\tthis.bucketSeqWidget.getItems().slice( this.model.length ) );\n\t\t}\n\n\t\t// Enable or disable add step button as needed\n\t\tthis.updateAddStepButtonState();\n\n\t\t// Update total pageViews data and text in UI\n\t\tthis.updateTotalPageViews();\n\t};\n\n\t/**\n\t * Add a new step widget with the specified step model and index\n\t *\n\t * @param {Object} stepModel\n\t * @param {number} index\n\t */\n\tBucketSeqContainerWidget.prototype.addStepWidget = function ( stepModel, index ) {\n\n\t\tconst stepWidget = new StepWidget(\n\t\t\tthis.controller,\n\t\t\tstepModel,\n\t\t\tthis.bucket,\n\t\t\t{ data: index }\n\t\t);\n\n\t\tthis.bucketSeqWidget.addItems( [ stepWidget ],\n\t\t\tindex\n\t\t);\n\t};\n\n\t/**\n\t * Reset the index numbers of the step widgets based on current widget order. (This is\n\t * called following drag-and-drop or the removal of a step.)\n\t */\n\tBucketSeqContainerWidget.prototype.updateStepNumbers = function () {\n\t\t// widgets are expected to be provided here in the correct order\n\t\tconst stepWidgets = this.bucketSeqWidget.getItems();\n\n\t\tfor ( let i = 0; i < stepWidgets.length; i++ ) {\n\t\t\tstepWidgets[ i ].setData( i );\n\t\t}\n\t};\n\n\tBucketSeqContainerWidget.prototype.updateAddStepButtonState = function () {\n\t\tthis.addStepButton.setDisabled( !this.controller.canAddAStep( this.bucket ) );\n\t};\n\n\t/**\n\t * Update heading text with current total page views in the sequence\n\t */\n\tBucketSeqContainerWidget.prototype.updateHeadingText = function () {\n\n\t\tthis.$heading.text( mw.message(\n\t\t\t'centralnotice-banner-sequence-bucket-seq',\n\t\t\tthis.controller.getBucketLabel( this.bucket ),\n\t\t\tthis.totalPageViews\n\t\t).text() );\n\t};\n\n\tBucketSeqContainerWidget.prototype.removeStep = function ( stepNum ) {\n\n\t\tconst stepWidget = this.bucketSeqWidget.findItemFromData( stepNum );\n\n\t\tstepWidget.clearFromGeneralErrorState();\n\t\tthis.bucketSeqWidget.removeItems( [ stepWidget ] );\n\n\t\t// Reset the step numbers\n\t\tthis.updateStepNumbers();\n\t};\n\n\t/**\n\t * Set missing banner errors for one or more steps in the sequence\n\t *\n\t * @param {number[]} stepsWithMissingBanners\n\t */\n\tBucketSeqContainerWidget.prototype.setMissingBannerErrors = function (\n\t\tstepsWithMissingBanners\n\t) {\n\t\tfor ( let i = 0; i < stepsWithMissingBanners.length; i++ ) {\n\n\t\t\tthis.bucketSeqWidget\n\t\t\t\t.findItemFromData( stepsWithMissingBanners[ i ] )\n\t\t\t\t.setMissingBannerError( true );\n\t\t}\n\t};\n\n\t/**\n\t * Update banners shown in drop-down input menus.\n\t *\n\t * @param {string[]} banners\n\t */\n\tBucketSeqContainerWidget.prototype.updateBannersForDropdowns = function ( banners ) {\n\t\tconst stepWidgets = this.bucketSeqWidget.getItems();\n\n\t\tfor ( let i = 0; i < stepWidgets.length; i++ ) {\n\t\t\tstepWidgets[ i ].updatebannersDropdown( banners );\n\t\t}\n\t};\n\n\t/**\n\t * Re-calculate total page views in the sequence.\n\t */\n\tBucketSeqContainerWidget.prototype.updateTotalPageViews = function () {\n\t\tthis.totalPageViews = 0;\n\n\t\tfor ( let i = 0; i < this.model.length; i++ ) {\n\t\t\tthis.totalPageViews += this.model[ i ].numPageViews;\n\t\t}\n\n\t\tthis.updateHeadingText();\n\t};\n\n\t/* BucketSeqWidget */\n\n\t/**\n\t * Widget for a sequence (only the steps, not the add step button or heading)\n\t * Note: this widget is manipulated by BucketSeqContainerWidget, which has most of the\n\t * related logic.\n\t *\n\t * @class BucketSeqWidget\n\t * @constructor\n\t */\n\tBucketSeqWidget = function () {\n\n\t\tconst config = { classes: [ 'centralNoticeBannerSeqSequenceContainer' ] };\n\n\t\t// Call parent constructor\n\t\tBucketSeqWidget.super.call( this, config );\n\n\t\t// Call mixin constructor\n\t\tOO.ui.mixin.DraggableGroupElement.call(\n\t\t\tthis, Object.assign( {}, config, { $group: this.$element } ) );\n\t};\n\n\tOO.inheritClass( BucketSeqWidget, OO.ui.Widget );\n\tOO.mixinClass( BucketSeqWidget, OO.ui.mixin.DraggableGroupElement );\n\n\t/* StepWidget */\n\n\t// TODO: Fix vertical alignment of error messages below inputs in StepWidget\n\n\t/**\n\t * Widget for a step in a sequence.\n\t *\n\t * @param {BannerSequenceUiController} controller\n\t * @param {Object} model\n\t * @param {Object} bucket\n\t * @param {Object} config\n\t * @class StepWidget\n\t * @constructor\n\t */\n\tStepWidget = function ( controller, model, bucket, config ) {\n\t\tthis.controller = controller;\n\t\tthis.model = model;\n\t\tthis.bucket = bucket;\n\n\t\t// Flag for suppressing some actions in event handlers when changes to widgets\n\t\t// are not due to user intervention.\n\t\tthis.updating = false;\n\n\t\t// Tracks validation errors for the contained inputs\n\t\tthis.errorStateTracker = new campaignManager.ErrorStateTracker();\n\n\t\t// A unique key for tracking error states (outside this widget)\n\t\tthis.errorStateKey = 'step-widget-' + this.controller.getErrorStateKey();\n\n\t\tconfig = Object.assign( {}, config, { classes: [ 'centralNoticeBannerSeqStep' ] } );\n\n\t\t// Call parent constructor\n\t\tStepWidget.super.call( this, config );\n\n\t\t// Set drag handle\n\t\t// Note: Adding the oo-uiwidget class is a hack, apparently needed due to an issue\n\t\t// in OOUI styles.\n\t\t// TODO Check this out; possible mistaken change to DraggableElement.less in\n\t\t// f6be5b2f1f0ef67ab0efeaa25568976587435d95 ?\n\t\tthis.$handle = $( '<div>' ).addClass(\n\t\t\t'centralNoticeBannerSeqStepHandle oo-ui-widget' );\n\n\t\tthis.$group.append( this.$handle );\n\n\t\t// Draggable mixin constructor\n\t\tOO.ui.mixin.DraggableElement.call(\n\t\t\tthis, Object.assign( {}, config, { $handle: this.$handle } ) );\n\n\t\t// Create the widgets\n\n\t\tconst dropMenuItems = this.makeDropMenuItems(\n\t\t\tcontroller.getBannersForBucket( this.bucket ) );\n\n\t\tthis.bannersDropdown = new OO.ui.DropdownWidget( {\n\t\t\tmenu: { items: dropMenuItems },\n\t\t\tclasses: [ 'centralNoticeBannerSeqBanner' ]\n\t\t} );\n\n\t\tthis.numPageViewsInput = new OO.ui.NumberInputWidget( {\n\t\t\tmin: 1,\n\t\t\tisInteger: true,\n\t\t\tclasses: [ 'centralNoticeBannerSeqPageViews' ]\n\t\t} );\n\n\t\tthis.skipWithIdInput = new OO.ui.TextInputWidget( {\n\t\t\tclasses: [ 'centralNoticeBannerSeqIdentifier' ],\n\t\t\tvalidate: function ( val ) {\n\t\t\t\treturn this.controller.validateSkipWithIdentifier( val );\n\t\t\t}.bind( this )\n\t\t} );\n\n\t\tthis.removeBtn = new OO.ui.ButtonWidget( {\n\t\t\ticon: 'trash',\n\t\t\tflags: 'destructive'\n\t\t} );\n\n\t\t// Field layouts\n\n\t\tthis.bannerFieldLayout = new OO.ui.FieldLayout( this.bannersDropdown, {\n\t\t\tlabel: mw.message( 'centralnotice-banner-sequence-banner' ).text(),\n\t\t\talign: 'top',\n\t\t\tclasses: [ 'centralNoticeBannerSeqBannerLayout' ]\n\t\t} );\n\n\t\tthis.numPageViewsFieldLayout = new OO.ui.FieldLayout( this.numPageViewsInput, {\n\t\t\tlabel: mw.message( 'centralnotice-banner-sequence-page-views' ).text(),\n\t\t\talign: 'top',\n\t\t\tclasses: [ 'centralNoticeBannerSeqPageViewsLayout' ]\n\t\t} );\n\n\t\tthis.skipWithIdFieldLayout = new OO.ui.FieldLayout( this.skipWithIdInput, {\n\t\t\tlabel: mw.message( 'centralnotice-banner-sequence-skip-with-id' ).text(),\n\t\t\talign: 'top',\n\t\t\tclasses: [ 'centralNoticeBannerSeqIdentifierLayout' ]\n\t\t} );\n\n\t\tthis.hLayout = new OO.ui.HorizontalLayout( {\n\t\t\titems: [\n\t\t\t\tthis.bannerFieldLayout,\n\t\t\t\tthis.numPageViewsFieldLayout,\n\t\t\t\tthis.skipWithIdFieldLayout,\n\t\t\t\tnew OO.ui.FieldLayout( this.removeBtn, {\n\t\t\t\t\tlabel: ' ', // Blank label for consistent vertical alignment\n\t\t\t\t\talign: 'top'\n\t\t\t\t} )\n\t\t\t],\n\t\t\tclasses: [ 'centralNoticeBannerSeqStepLayout' ]\n\t\t} );\n\n\t\tthis.addItems( [ this.hLayout ] );\n\n\t\t// Set input values with data from the model\n\t\tthis.updateFromModel();\n\n\t\t// Change handler for banner dropdown\n\t\tthis.bannersDropdown.getMenu().connect(\n\t\t\tthis,\n\t\t\t{ choose: function ( menuItem ) {\n\t\t\t\tlet val;\n\n\t\t\t\tif ( !this.updating ) {\n\t\t\t\t\tval = menuItem.getData();\n\n\t\t\t\t\tthis.controller.setBanner(\n\t\t\t\t\t\tthis.bucket,\n\t\t\t\t\t\tthis.getData(), // step number\n\t\t\t\t\t\tval !== this.constructor.static.NO_BANNER_FLAG ? val : null,\n\t\t\t\t\t\tthis\n\t\t\t\t\t);\n\n\t\t\t\t\tthis.setMissingBannerError( false );\n\t\t\t\t}\n\t\t\t} }\n\t\t);\n\n\t\t// Change handler for input for number of pageViews\n\t\tthis.numPageViewsInput.connect(\n\t\t\tthis,\n\t\t\t{ change: function () {\n\n\t\t\t\t// TODO Bug for public getValidity() on NumberInputWidget\n\t\t\t\t// (same issue above for daysInput in BannerSequenceWidget)\n\t\t\t\tthis.numPageViewsInput.input.getValidity().done( () => {\n\n\t\t\t\t\t// If the value passes validation, send it to the controller and\n\t\t\t\t\t// clear any errors\n\n\t\t\t\t\tif ( !this.updating ) {\n\t\t\t\t\t\tthis.controller.setNumPageViews(\n\t\t\t\t\t\t\tthis.bucket,\n\t\t\t\t\t\t\tthis.getData(), // step number\n\t\t\t\t\t\t\tparseInt( this.numPageViewsInput.getValue(), 10 ),\n\t\t\t\t\t\t\tthis\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.numPageViewsFieldLayout.setErrors( [] );\n\t\t\t\t\tthis.setErrorState( 'numPageViews', false );\n\n\t\t\t\t} ).fail( () => {\n\n\t\t\t\t\t// If the value fails validation, set errors\n\n\t\t\t\t\tthis.numPageViewsFieldLayout.setErrors( [\n\t\t\t\t\t\tmw.message( 'centralnotice-banner-sequence-page-views-error' ).text()\n\t\t\t\t\t] );\n\n\t\t\t\t\tthis.setErrorState( 'numPageViews', true );\n\n\t\t\t\t} );\n\t\t\t} }\n\t\t);\n\n\t\t// Change handler for skip with id input\n\t\tthis.skipWithIdInput.connect(\n\t\t\tthis,\n\t\t\t{ change: function () {\n\n\t\t\t\tthis.skipWithIdInput.getValidity().done( () => {\n\n\t\t\t\t\t// If the value passes validation, send it to the controller and\n\t\t\t\t\t// clear any errors\n\n\t\t\t\t\tif ( !this.updating ) {\n\t\t\t\t\t\tthis.controller.setSkipWithIdentifier(\n\t\t\t\t\t\t\tthis.bucket,\n\t\t\t\t\t\t\tthis.getData(), // step number\n\t\t\t\t\t\t\tthis.skipWithIdInput.getValue() || null,\n\t\t\t\t\t\t\tthis\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.skipWithIdFieldLayout.setErrors( [] );\n\t\t\t\t\tthis.setErrorState( 'skipWithId', false );\n\n\t\t\t\t} ).fail( () => {\n\n\t\t\t\t\t// If the value fails validation, set errors\n\n\t\t\t\t\tthis.skipWithIdFieldLayout.setErrors( [\n\t\t\t\t\t\tmw.message( 'centralnotice-banner-sequence-skip-with-id-error' ).text()\n\t\t\t\t\t] );\n\n\t\t\t\t\tthis.setErrorState( 'skipWithId', true );\n\n\t\t\t\t} );\n\t\t\t} }\n\t\t);\n\n\t\t// Blur handler for skip with id input, to trim value on blur\n\n\t\t// TextInputWidget doesn't natively expose a blur event; this is a hack.\n\t\t// TODO Make a task requesting that feature.\n\n\t\tthis.skipWithIdInput.$input.on( 'blur', () => {\n\t\t\tthis.skipWithIdInput.setValue( this.skipWithIdInput.getValue().trim() );\n\t\t} );\n\n\t\t// Click handler for remove step button\n\t\tthis.removeBtn.connect(\n\t\t\tthis,\n\t\t\t{ click: function () {\n\t\t\t\tthis.controller.removeStep(\n\t\t\t\t\tthis.bucket,\n\t\t\t\t\tthis.getData() // step number\n\t\t\t\t);\n\t\t\t} }\n\t\t);\n\t};\n\n\tOO.inheritClass( StepWidget, OO.ui.FieldsetLayout );\n\tOO.mixinClass( StepWidget, OO.ui.mixin.DraggableElement );\n\n\t/**\n\t * Flag to indicate no banner should be shown on this step. Note: This flag is only\n\t * used in widgets. To signal a step with no banner in the data model, null is used.\n\t */\n\tStepWidget.static.NO_BANNER_FLAG = -1;\n\n\t/**\n\t * Update input values with data from the model.\n\t */\n\tStepWidget.prototype.updateFromModel = function () {\n\n\t\tthis.updating = true;\n\n\t\t// Set input values (if the step doesn't have an error state)\n\t\tif ( !this.hasErrorState() ) {\n\t\t\tthis.bannersDropdown.getMenu().selectItemByData(\n\t\t\t\tthis.model.banner || this.constructor.static.NO_BANNER_FLAG\n\t\t\t);\n\n\t\t\tthis.numPageViewsInput.setValue( String( this.model.numPageViews ) );\n\t\t\tthis.skipWithIdInput.setValue( String( this.model.skipWithIdentifier || '' ) );\n\t\t}\n\n\t\t// Enable/disable step removal and dragging\n\t\tthis.removeBtn.setDisabled( !this.controller.canRemoveAStep( this.bucket ) );\n\n\t\tif ( this.controller.canMoveSteps( this.bucket ) ) {\n\t\t\tthis.toggleDraggable( true );\n\t\t\tthis.$handle.addClass( 'centralNoticeBannerSeqStepHandleEnabled' );\n\t\t\tthis.$handle.removeClass( 'centralNoticeBannerSeqStepHandleDisabled' );\n\n\t\t} else {\n\t\t\tthis.toggleDraggable( false );\n\t\t\tthis.$handle.addClass( 'centralNoticeBannerSeqStepHandleDisabled' );\n\t\t\tthis.$handle.removeClass( 'centralNoticeBannerSeqStepHandleEnabled' );\n\t\t}\n\n\t\tthis.updating = false;\n\t};\n\n\t/**\n\t * Set a banner missing error state for the banner drop-down\n\t *\n\t * @param {boolean} state true to set an error, false to clear one\n\t */\n\tStepWidget.prototype.setMissingBannerError = function ( state ) {\n\n\t\tif ( state ) {\n\t\t\tthis.bannerFieldLayout.setErrors( [\n\t\t\t\tmw.message( 'centralnotice-banner-sequence-banner-removed-error' ).text()\n\t\t\t] );\n\n\t\t} else {\n\t\t\tthis.bannerFieldLayout.setErrors( [] );\n\t\t}\n\n\t\tthis.setErrorState( 'missingBanner', state );\n\t};\n\n\t/**\n\t * Refresh the list of banners shown in the banner drop-down menu\n\t *\n\t * @param {string[]} banners Names of banners available for this bucket\n\t */\n\tStepWidget.prototype.updatebannersDropdown = function ( banners ) {\n\n\t\tconst dropdownMenu = this.bannersDropdown.getMenu(),\n\t\t\tselectedItem = dropdownMenu.findSelectedItem(),\n\t\t\tselectedBanner = selectedItem ? selectedItem.getData() : null;\n\n\t\tthis.updating = true;\n\n\t\t// Clear and re-create the list of banners, and restore the previous selection,\n\t\t// if there was one\n\t\tdropdownMenu.clearItems();\n\t\tdropdownMenu.addItems( this.makeDropMenuItems( banners ) );\n\t\tif ( selectedItem ) {\n\t\t\tdropdownMenu.selectItemByData( selectedBanner );\n\t\t}\n\n\t\tthis.updating = false;\n\t};\n\n\t/**\n\t * Determine whether any inputs have validation errors\n\t *\n\t * @return {boolean} true if one or more input has a validation error, false\n\t *   otherwise\n\t */\n\tStepWidget.prototype.hasErrorState = function () {\n\t\treturn this.errorStateTracker.hasErrorState();\n\t};\n\n\t/**\n\t * Tell the controller to clear any error states set for this widget (called when\n\t * the step is removed).\n\t */\n\tStepWidget.prototype.clearFromGeneralErrorState = function () {\n\t\tthis.controller.setErrorState( this.errorStateKey, false );\n\t};\n\n\t/**\n\t * @param {string[]} banners\n\t * @return {OO.ui.MenuOptionWidget[]}\n\t * @private\n\t */\n\tStepWidget.prototype.makeDropMenuItems = function ( banners ) {\n\t\tconst dropdownMenuItems = [];\n\n\t\t// First option is always no banner\n\t\tdropdownMenuItems.push( new OO.ui.MenuOptionWidget( {\n\t\t\tdata: this.constructor.static.NO_BANNER_FLAG,\n\t\t\tlabel: mw.message( 'centralnotice-banner-sequence-no-banner' ).text()\n\t\t} ) );\n\n\t\tfor ( let i = 0; i < banners.length; i++ ) {\n\t\t\tdropdownMenuItems.push( new OO.ui.MenuOptionWidget( {\n\t\t\t\tdata: banners[ i ],\n\t\t\t\tlabel: banners[ i ]\n\t\t\t} ) );\n\t\t}\n\n\t\treturn dropdownMenuItems;\n\t};\n\n\t/**\n\t * @private\n\t * @param {string} errorStateKey A local key (unique only within this StepWidget\n\t * instance, to distinguish controls with an error state)\n\t * @param {boolean} state True when an error exists, false otherwise\n\t */\n\tStepWidget.prototype.setErrorState = function ( errorStateKey, state ) {\n\t\tthis.errorStateTracker.setErrorState( errorStateKey, state );\n\n\t\t// Tell the controller about this step widget's overall error state\n\t\tthis.controller.setErrorState( this.errorStateKey, this.hasErrorState() );\n\n\t\t// Update widget styles\n\t\tthis.updateErrorStateStyle();\n\t};\n\n\t/**\n\t * @private\n\t */\n\tStepWidget.prototype.updateErrorStateStyle = function () {\n\n\t\tthis.$element.toggleClass(\n\t\t\t'centralNoticeBannerSeqStepError',\n\t\t\tthis.hasErrorState()\n\t\t);\n\t};\n\n\t/* General setup */\n\n\t// Register controller class with factory\n\tcampaignManager.mixinCustomUiControllerFactory.register( BannerSequenceUiController );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/infrastructure/ext.centralNotice.adminUi.campaignPager.js","messages":[],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'k' is assigned a value but never used.","line":33,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":33,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":296,"column":4,"nodeType":"CallExpression","endLine":329,"endColumn":7},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":367,"column":4,"nodeType":"CallExpression","endLine":381,"endColumn":7}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'inSample' is assigned a value but never used.","line":37,"column":6,"nodeType":"Identifier","messageId":"unusedVar","endLine":37,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* eslint-disable no-unused-vars */\n// TODO Remove above directive once inSample var is used.\n/*\n * Banner history logger mixin. Records an event every time this campaign is\n * selected for the user (even if the banner is hidden). The log is kept in\n * LocalStorage (via CentralNotice's kvStore). A sample of logs are sent to the\n * server via EventLogging. Also allows forcing the log to be sent via\n * cn.bannerHistoryLogger.ensureLogSent().\n */\n( function () {\n\t// Guaranteed to exist; we depend on display RL module\n\tconst cn = mw.centralNotice;\n\tconst mixin = new cn.Mixin( 'bannerHistoryLogger' );\n\tconst doNotTrackEnabled =\n\t\t// Support: Firefox < 32 (yes/no)\n\t\t/1|yes/.test( navigator.doNotTrack ) ||\n\t\t// Support: IE 11, Safari 7.1.3+ (window.doNotTrack)\n\t\twindow.doNotTrack === '1';\n\tconst now = Math.round( Date.now() / 1000 );\n\tconst readyToLogDeferredObj = $.Deferred();\n\n\tconst BANNER_HISTORY_KV_STORE_KEY = 'banner_history';\n\t// Maximum time (in days) that the banner history store KV store item\n\t// will persist if no entries are added to it.\n\tconst BANNER_HISTORY_KV_STORE_TTL = 365;\n\t// Update this version when the log format changes\n\tconst BANNER_HISTORY_LOG_ENTRY_VERSION = 1;\n\tconst EVENT_LOGGING_SCHEMA = 'CentralNoticeBannerHistory';\n\n\t// The maximum random shift applied to timestamps, for user privacy\n\tconst TIMESTAMP_RANDOM_SHIFT_MAX = 60;\n\n\tlet bhLogger;\n\tlet log;\n\tlet logSent = false;\n\tlet alreadyRun = false;\n\tlet inSample;\n\n\t/**\n\t * Load the banner history log from KV storage\n\t */\n\tfunction loadLog() {\n\t\tlog = cn.kvStore.getItem(\n\t\t\tBANNER_HISTORY_KV_STORE_KEY,\n\t\t\tcn.kvStore.contexts.GLOBAL\n\t\t);\n\n\t\tif ( !log ) {\n\t\t\tlog = [];\n\t\t}\n\t}\n\n\t/**\n\t * Return a log entry about the current campaign selection event.\n\t *\n\t * @return {Object}\n\t */\n\tfunction makeLogEntry() {\n\t\tconst data = cn.data;\n\n\t\t// Randomly shift timestamp +/- 0 to 10 seconds, so logs can't be\n\t\t// linked to specific Web requests. This is to strengthen user\n\t\t// privacy.\n\t\tconst randomTimeShift =\n\t\t\tMath.round( Math.random() * TIMESTAMP_RANDOM_SHIFT_MAX ) -\n\t\t\t( TIMESTAMP_RANDOM_SHIFT_MAX / 2 );\n\n\t\tconst time = now + randomTimeShift;\n\n\t\tconst logEntry = {\n\t\t\tversion: BANNER_HISTORY_LOG_ENTRY_VERSION,\n\t\t\tlanguage: data.uselang,\n\t\t\tcountry: data.country,\n\t\t\tisAnon: data.anonymous,\n\t\t\tcampaign: data.campaign,\n\t\t\tcampaignCategory: data.campaignCategory,\n\t\t\tbucket: data.bucket,\n\t\t\ttime: time,\n\t\t\tstatus: data.status,\n\t\t\tstatusCode: data.statusCode,\n\t\t\tbannersNotGuaranteedToDisplay: !!data.bannersNotGuaranteedToDisplay\n\t\t};\n\n\t\tif ( data.banner ) {\n\t\t\tlogEntry.banner = data.banner;\n\t\t}\n\n\t\tif ( data.bannerCanceledReason ) {\n\t\t\tlogEntry.bannerCanceledReason = data.bannerCanceledReason;\n\t\t}\n\n\t\tif ( data.bannerLoadedButHiddenReason ) {\n\t\t\tlogEntry.bannerLoadedButHiddenReason = data.bannerLoadedButHiddenReason;\n\t\t}\n\n\t\treturn logEntry;\n\t}\n\n\t/**\n\t * Remove log entries older than maxEntryAge (in days) and, if necessary,\n\t * remove entries to keep the total within maxEntries.\n\t *\n\t * @param {number} maxEntryAge\n\t * @param {number} maxEntries\n\t */\n\tfunction purgeOldLogEntries( maxEntryAge, maxEntries ) {\n\t\tconst cutoff = now - maxEntryAge * 86400;\n\n\t\t// If we're above the max number of entries, pare it down, starting\n\t\t// with older entries\n\t\tif ( log.length > maxEntries ) {\n\t\t\tlog = log.slice( 0 - maxEntries );\n\t\t}\n\n\t\tlet i = 0;\n\t\t// Remove any remaining entries that are older than maxEntryAge\n\t\twhile ( i < log.length && log[ i ].time < cutoff ) {\n\t\t\ti++;\n\t\t}\n\t\tlog = log.slice( i );\n\t}\n\n\t/**\n\t * Store the contents of the log variable in kvStorage\n\t */\n\tfunction storeLog() {\n\t\tcn.kvStore.setItem(\n\t\t\tBANNER_HISTORY_KV_STORE_KEY,\n\t\t\tlog,\n\t\t\tcn.kvStore.contexts.GLOBAL,\n\t\t\tBANNER_HISTORY_KV_STORE_TTL\n\t\t);\n\t}\n\n\t/**\n\t * Return an object with data for EventLogging.\n\t *\n\t * We scrunch the data as small as possible due to the WMF infrastructure's\n\t * EventLogging payload limit.\n\t *\n\t * @param {number} rate The sampling rate used\n\t * @return {Object}\n\t */\n\tfunction makeEventLoggingData( rate ) {\n\t\tconst elData = {};\n\t\tconst kvError = cn.kvStore.getError();\n\n\t\t// Log ID: should be generated before this is called, and should not be\n\t\t// persisted anywhere on the client (see below).\n\t\telData.i = bhLogger.id;\n\n\t\t// sample rate\n\t\tif ( rate ) {\n\t\t\telData.r = rate;\n\t\t}\n\n\t\t// if applicable, the message from any kv store error\n\t\tif ( kvError ) {\n\t\t\telData.e = kvError.message;\n\t\t\treturn elData;\n\t\t}\n\n\t\t// total log length\n\t\telData.n = log.length;\n\t\telData.l = [];\n\n\t\t// Add log entries, starting with the most recent ones, until the EL\n\t\t// URL is too big, or we reach the end of the log.\n\t\tlet i = log.length - 1;\n\n\t\twhile ( i >= 0 ) {\n\t\t\tconst logEntry = log[ i ];\n\n\t\t\tconst elLogEntry = [\n\t\t\t\tlogEntry.banner || '',\n\t\t\t\tlogEntry.campaign,\n\t\t\t\tlogEntry.time,\n\t\t\t\tlogEntry.statusCode\n\t\t\t];\n\n\t\t\telData.l.unshift( elLogEntry.join( '|' ) );\n\n\t\t\tif ( !checkEventLoggingURLSize( elData ) ) {\n\t\t\t\telData.l.shift();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\ti--;\n\t\t}\n\n\t\treturn elData;\n\t}\n\n\t/**\n\t * Send the log via EventLogging.\n\t *\n\t * Returns a promise that resolves as soon as the log is submitted for users.\n\t *\n\t * @param {Object} elData Event log data\n\t * @return {jQuery.Promise}\n\t */\n\tfunction sendLog( elData ) {\n\t\tconst deferred = $.Deferred();\n\n\t\tconst elPromise = mw.eventLog.logEvent( EVENT_LOGGING_SCHEMA, elData );\n\n\t\telPromise.then( () => {\n\t\t\tdeferred.resolve();\n\t\t}, () => {\n\t\t\tdeferred.reject();\n\t\t} );\n\n\t\treturn deferred.promise();\n\t}\n\n\t/**\n\t * Check the EventLogging URL we'd get from this data isn't too big. Here\n\t * we copy some of the same processes done by ext.eventLogging.\n\t *\n\t * FIXME This is a temporary measure!\n\t *\n\t * @param {Object} elData\n\t * @return {boolean} true if the EL payload size is OK\n\t */\n\tfunction checkEventLoggingURLSize( elData ) {\n\t\treturn ( makeEventLoggingURL( elData ).length <= mw.eventLog.maxUrlSize );\n\t}\n\n\t/**\n\t * Make an EventLogging URL ourselves.\n\t * FIXME This is a temporary measure!\n\t *\n\t * @param {Object} elData\n\t * @return {string}\n\t */\n\tfunction makeEventLoggingURL( elData ) {\n\t\treturn mw.eventLog.makeBeaconUrl( {\n\t\t\tevent: elData,\n\t\t\trevision: 19079897, // Coordinate with extension.json\n\t\t\tschema: EVENT_LOGGING_SCHEMA,\n\t\t\twebHost: location.hostname,\n\t\t\twiki: mw.config.get( 'wgDBname' )\n\t\t} );\n\t}\n\n\t// Set a function to run after the entire display process\n\tmixin.setFinalizeChooseAndMaybeDisplayHandler( ( mixinParams ) => {\n\n\t\t// Only run any processes once per pageview. This prevents multiple log\n\t\t// entries per pageview if more than one attempted campaign has enabled\n\t\t// this mixin. Note that there is currently no reconciliation for different\n\t\t// banner history configuration settings in that scenario. See T261718.\n\t\tif ( alreadyRun ) {\n\t\t\treturn;\n\t\t}\n\t\talreadyRun = true;\n\n\t\t// Do this idly to avoid browser lock-ups\n\t\tmw.requestIdleCallback( () => {\n\n\t\t\tif ( !cn.kvStore.isAvailable() ) {\n\t\t\t\tcn.kvStore.setNotAvailableError();\n\n\t\t\t} else {\n\t\t\t\t// If KV storage works here, do our stuff\n\t\t\t\tloadLog();\n\n\t\t\t\t// Only don't accumulate log entries if DNT is enabled... But do\n\t\t\t\t// purge old entries.\n\t\t\t\tif ( !doNotTrackEnabled ) {\n\t\t\t\t\tlog.push( makeLogEntry() );\n\t\t\t\t}\n\n\t\t\t\tpurgeOldLogEntries(\n\t\t\t\t\tmixinParams.maxEntryAge,\n\t\t\t\t\tmixinParams.maxEntries\n\t\t\t\t);\n\n\t\t\t\tstoreLog();\n\t\t\t}\n\n\t\t\t// Bow out now if DNT\n\t\t\tif ( doNotTrackEnabled ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Load needed resources\n\n\t\t\t// Note: we don't set the following up as RL dependencies because a\n\t\t\t// lot of campaign filtering happens on the client, so many users\n\t\t\t// see campaigns in choiceData that don't target them. If any of\n\t\t\t// those campaigns were to use this mixin, all those users would\n\t\t\t// needlessly get these dependencies. Also, they're not needed right\n\t\t\t// away.\n\n\t\t\tmw.loader.using( [\n\t\t\t\t'mediawiki.util',\n\t\t\t\t'mediawiki.user'\n\t\t\t] ).done( () => {\n\t\t\t\t// URL param bannerHistoryLogRate can override rate, for debugging\n\t\t\t\tconst rateParam = mw.util.getParamValue( 'bannerHistoryLogRate' ),\n\t\t\t\t\trate = rateParam !== null ?\n\t\t\t\t\t\tparseFloat( rateParam ) : mixinParams.rate;\n\n\t\t\t\t// We send back the temporary ID for all logs.\n\t\t\t\tbhLogger.id = mw.user.generateRandomSessionId();\n\n\t\t\t\t// Send a sample to the server\n\t\t\t\tif ( Math.random() < rate ) {\n\n\t\t\t\t\tsendLog( makeEventLoggingData( rate ) ).always( () => {\n\n\t\t\t\t\t\tinSample = true;\n\t\t\t\t\t\tlogSent = true;\n\n\t\t\t\t\t\t// By resolving only after sampling and possibly\n\t\t\t\t\t\t// sending the log, we ensure that a sampled log\n\t\t\t\t\t\t// would be sent first. That simplifies the logic\n\t\t\t\t\t\t// for whether to send in other circumstances.\n\t\t\t\t\t\treadyToLogDeferredObj.resolve();\n\t\t\t\t\t} );\n\n\t\t\t\t} else {\n\n\t\t\t\t\t// If not in the sample, ready right away\n\t\t\t\t\treadyToLogDeferredObj.resolve();\n\t\t\t\t}\n\n\t\t\t} );\n\t\t} );\n\t} );\n\n\t// Register the mixin\n\tcn.registerCampaignMixin( mixin );\n\n\t// Object for public access\n\tcn.bannerHistoryLogger = bhLogger = {\n\n\t\t/**\n\t\t * A client-generated unique ID for the log (on this pageview), not\n\t\t * persisted in the log or anywhere else between pageviews.\n\t\t *\n\t\t * Note: this unique ID should not be stored anywhere on the client. It\n\t\t * should be used only within the current browsing session to flag when\n\t\t * a banner history is associated with a donation. If a user clicks on a\n\t\t * banner to donate, it may be passed on to the WMF's donation sites via\n\t\t * a URL parameter. Those sites should never store it on the client.\n\t\t */\n\t\tid: null,\n\n\t\t/**\n\t\t * Send the banner history log to the server, if it wasn't sent already.\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tensureLogSent: function () {\n\n\t\t\tconst deferred = $.Deferred();\n\n\t\t\t// Bow out if DNT\n\t\t\tif ( doNotTrackEnabled ) {\n\t\t\t\tdeferred.resolve();\n\t\t\t\treturn deferred.promise();\n\t\t\t}\n\n\t\t\t// It's likely that this will be resolved by the time we get here\n\t\t\treadyToLogDeferredObj.done( () => {\n\n\t\t\t\t// This is included in the done() function to ensure a sampled\n\t\t\t\t// log would be sent first (see above).\n\t\t\t\tif ( logSent ) {\n\t\t\t\t\tdeferred.resolve();\n\t\t\t\t} else {\n\t\t\t\t\tsendLog( makeEventLoggingData() ).then( () => {\n\t\t\t\t\t\tdeferred.resolve();\n\t\t\t\t\t}, () => {\n\t\t\t\t\t\tdeferred.reject();\n\t\t\t\t\t} );\n\t\t\t\t}\n\n\t\t\t} );\n\n\t\t\treturn deferred.promise();\n\t\t}\n\t};\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.bannerSequence.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.cspViolationAlert.js","messages":[],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":11,"column":3,"nodeType":"CallExpression","messageId":"unexpected","endLine":11,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.freegeoipLookup.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.geoIP.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":160,"column":2,"nodeType":"CallExpression","endLine":162,"endColumn":5}],"suppressedMessages":[{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":125,"column":30,"nodeType":"CallExpression","endLine":125,"endColumn":53,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Processes location data and sets up a promise that resolves with that data.\n * Sets the global window.Geo and provides the public mw.geoIP object.\n * TODO Deprecate global window.Geo\n * TODO Move this out of CentralNotice. See https://phabricator.wikimedia.org/T102848\n */\n( function () {\n\n\tconst COOKIE_NAME = 'GeoIP';\n\tlet geoPromise;\n\n\t/**\n\t * Parse geo data in cookieValue and return an object with properties from\n\t * the fields therein. Returns null if the value couldn't be parsed or\n\t * doesn't contain location data.\n\t *\n\t * The cookie will look like one of the following:\n\t * - \"US:CO:Denver:39.6762:-104.887:v4\"\n\t * - \":::::v4\"\n\t *\n\t * @param {string} cookieValue\n\t * @return {?Object}\n\t */\n\tfunction parseCookieValue( cookieValue ) {\n\n\t\t// TODO Verify that these Regexes are optimal. (Why no anchors? Why the\n\t\t// semicolon in the last group?)\n\n\t\tlet matches =\n\t\t\t// Parse cookie format currently set by WMF servers\n\t\t\tcookieValue.match( /([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^;]*)/ ) ||\n\n\t\t\t// If that didn't match, try the old cookie format (no region data).\n\t\t\t// Even though these are session cookies, some might still be around.\n\t\t\tcookieValue.match( /([^:]*):([^:]*):([^:]*):([^:]*):([^;]*)/ );\n\n\t\t// No matches...? Boo, no data from geo cookie.\n\t\tif ( !matches ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// If the matches were from the old cookie format, add an empty region\n\t\t// element.\n\t\tif ( matches.length === 6 ) {\n\t\t\tmatches = matches.slice( 0, 2 ).concat( [ '' ] )\n\t\t\t\t.concat( matches.slice( 2 ) );\n\t\t}\n\n\t\t// There was no info found if there's no country field, or if it's\n\t\t// empty\n\t\tif ( ( typeof matches[ 1 ] !== 'string' ) || ( matches[ 1 ].length === 0 ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Return a juicy Geo object\n\t\treturn {\n\t\t\tcountry: matches[ 1 ],\n\t\t\tregion: matches[ 2 ],\n\t\t\tcity: matches[ 3 ],\n\t\t\tlat: matches[ 4 ] && parseFloat( matches[ 4 ] ),\n\t\t\tlon: matches[ 5 ] && parseFloat( matches[ 5 ] ),\n\t\t\taf: matches[ 6 ]\n\t\t};\n\t}\n\n\t/**\n\t * Serialize a geo object and store it in the cookie\n\t *\n\t * @param {Object} geo\n\t */\n\tfunction storeGeoInCookie( geo ) {\n\t\tconst parts = [\n\t\t\t\tgeo.country,\n\t\t\t\tgeo.region || '',\n\t\t\t\t( geo.city && geo.city.replace( /[^a-z]/i, '_' ) ) || '',\n\t\t\t\tgeo.lat || '',\n\t\t\t\tgeo.lon || '',\n\t\t\t\tgeo.af || ''\n\t\t\t],\n\t\t\tcookieValue = parts.join( ':' );\n\n\t\t$.cookie( COOKIE_NAME, cookieValue, { path: '/' } );\n\t}\n\n\t/**\n\t * Public geoIP object\n\t */\n\tmw.geoIP = {\n\n\t\t/**\n\t\t * Don't call this function! It is only exposed for tests.\n\t\t *\n\t\t * Set a promise that resolves with geo. First try to get data from the\n\t\t * GeoIP cookie. If that fails, and if a background lookup callback\n\t\t * module is configured, try the background lookup.\n\t\t *\n\t\t * @private\n\t\t */\n\t\tmakeGeoWithPromise: function () {\n\n\t\t\tconst cookieValue = $.cookie( COOKIE_NAME );\n\n\t\t\t// Were we able to read the cookie?\n\t\t\tif ( cookieValue ) {\n\t\t\t\tconst geo = parseCookieValue( cookieValue );\n\n\t\t\t\t// All good? Resolve with geo and get outta here.\n\t\t\t\tif ( geo ) {\n\t\t\t\t\tgeoPromise = $.Deferred().resolve( geo ).promise();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle no geo data from the cookie.\n\n\t\t\t// If there's a background lookup to fall back to, do that\n\t\t\tconst lookupModule =\n\t\t\t\tmw.config.get( 'wgCentralNoticeGeoIPBackgroundLookupModule' );\n\n\t\t\tif ( lookupModule ) {\n\n\t\t\t\tgeoPromise = mw.loader.using( lookupModule )\n\t\t\t\t\t.then( () => {\n\t\t\t\t\t\t// eslint-disable-next-line security/detect-non-literal-require\n\t\t\t\t\t\tconst lookupCallback = require( lookupModule );\n\n\t\t\t\t\t\t// Chaining lookup: here we return the promise provided by\n\t\t\t\t\t\t// lookupCallback(). The result of that promise (geo object)\n\t\t\t\t\t\t// will be what then() resolves to, and what future then()\n\t\t\t\t\t\t// handlers get.\n\t\t\t\t\t\treturn lookupCallback();\n\t\t\t\t\t} );\n\n\t\t\t\t// If the lookup was successful, store geo in a cookie\n\t\t\t\tgeoPromise.then( ( g ) => {\n\t\t\t\t\tstoreGeoInCookie( g );\n\t\t\t\t} );\n\n\t\t\t// If no background lookup is available, we don't have geo data\n\t\t\t} else {\n\t\t\t\tgeoPromise = $.Deferred().reject().promise();\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Returns a promise that resolves with geo when it's available. While\n\t\t * it's usually available right away, it may not be if a background\n\t\t * call is performed.\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tgetPromise: function () {\n\t\t\treturn geoPromise;\n\t\t}\n\t};\n\n\tmw.geoIP.makeGeoWithPromise();\n\n\t// For legacy code, set global window.Geo TODO: deprecate\n\tgeoPromise.done( ( geo ) => {\n\t\twindow.Geo = geo;\n\t} );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.impressionDiet.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"c\" type.","line":180,"column":1,"nodeType":"Block","endLine":180,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Impression diet mixin. Provides knobs to cap the number of impressions\n * each user will see.\n *\n * This mixin stores count data in the kvStore (LocalStorage).\n *\n * In general, showing a banner entails that a certain number of impressions\n * have already occurred in a time period. Parameter documentation is provided\n * in the CentralNotice campaign management UI.\n *\n * - Banner may be forced via URL parameter force=1.\n * - Cycle counters may be reset via URL parameter reset=1.\n *\n * Flow chart: https://commons.wikimedia.org/wiki/File:CentralNotice_-_wait_cookie_code_flow.png\n * (FIXME: update ^^ with new parameter names)\n */\n( function () {\n\t'use strict';\n\n\tconst cn = mw.centralNotice;\n\tconst mixin = new cn.Mixin( 'impressionDiet' );\n\tconst STORAGE_KEY = 'impression_diet';\n\t// Time to store impression-counting data, in days\n\tconst COUNTS_STORAGE_TTL = 365;\n\n\tlet identifier;\n\tlet multiStorageOption;\n\n\tmixin.setPreBannerHandler( impressionDietHandler );\n\tfunction impressionDietHandler( mixinParams ) {\n\t\t// URL forced a banner\n\t\tif ( mw.util.getParamValue( 'force' ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tidentifier = mixinParams.cookieName; // TODO change param name\n\n\t\t// Check if and how we can store counts\n\t\tmultiStorageOption = cn.kvStore.getMultiStorageOption(\n\t\t\tcn.getDataProperty( 'campaignCategoryUsesLegacy' )\n\t\t);\n\n\t\t// Banner was hidden already\n\t\tif ( cn.isCampaignFailed() ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// No options for storing stuff, so hide banner and bow out\n\t\tif ( multiStorageOption === cn.kvStore.multiStorageOptions.NO_STORAGE ) {\n\t\t\tcn.failCampaign( 'waitnostorage' );\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = Date.now();\n\n\t\t/**\n\t\t * Object with data used to determine whether to hide the banner\n\t\t * Properties:\n\t\t *   seenCount:        Total number of impressions seen by this user\n\t\t *   skippedThisCycle: Number of initial impressions we've skipped this cycle\n\t\t *   nextCycleStart:   Unix timestamp after which we can show more banners\n\t\t *   seenThisCycle:    Number of impressions seen this cycle\n\t\t */\n\t\tlet counts;\n\n\t\t// Reset counts if requested (for testing)\n\t\tif ( mw.util.getParamValue( 'reset' ) === '1' ) {\n\t\t\tcounts = getZeroedCounts();\n\t\t} else {\n\t\t\t// Otherwise get counts from storage\n\t\t\tcounts = getCounts();\n\t\t}\n\n\t\tif ( now > counts.nextCycleStart &&\n\t\t\tcounts.seenThisCycle >= mixinParams.maximumSeen\n\t\t) {\n\t\t\t// We're beyond the wait period, and have nothing to do except\n\t\t\t// maybe start a new cycle.\n\t\t\tif ( mixinParams.restartCycleDelay !== 0 ) {\n\t\t\t\t// Begin a new cycle by clearing counters.\n\t\t\t\tcounts.skippedThisCycle = 0;\n\t\t\t\tcounts.seenThisCycle = 0;\n\t\t\t}\n\t\t}\n\n\t\t// Compare counts against campaign settings and decide whether to\n\t\t// show a banner\n\n\t\tlet hide;\n\t\tif ( counts.seenThisCycle < mixinParams.maximumSeen ) {\n\t\t\t// You haven't seen the maximum count of banners per cycle!\n\t\t\tif ( counts.skippedThisCycle < mixinParams.skipInitial ) {\n\t\t\t\t// Skip initial impressions.\n\t\t\t\thide = 'waitimps';\n\t\t\t\tcounts.skippedThisCycle += 1;\n\t\t\t} else {\n\t\t\t\t// Show a banner--you win!\n\t\t\t\thide = false;\n\t\t\t}\n\t\t} else {\n\t\t\t// Wait for the next cycle to begin.\n\t\t\thide = 'waitdate';\n\t\t}\n\n\t\tif ( hide ) {\n\t\t\t// Hide based on the results.\n\t\t\tcn.failCampaign( hide );\n\t\t} else {\n\t\t\t// Count shown impression.\n\t\t\tcounts.seenThisCycle += 1;\n\t\t\tcounts.seenCount += 1;\n\n\t\t\t// Reset the wait timer on every impression.  The configured delay\n\t\t\t// is the minimum amount of time allowed between the final impression\n\t\t\t// and the start of the next cycle.\n\n\t\t\tcounts.nextCycleStart = now +\n\t\t\t\t( mixinParams.restartCycleDelay * 1000 );\n\t\t}\n\n\t\t// Bookkeeping.\n\t\tstoreCounts( counts );\n\t}\n\n\tfunction getZeroedCounts() {\n\t\treturn {\n\t\t\tseenCount: 0,\n\t\t\tskippedThisCycle: 0,\n\t\t\tnextCycleStart: 0,\n\t\t\tseenThisCycle: 0\n\t\t};\n\t}\n\n\t/**\n\t * Migrate or discard old or invalid data\n\t *\n\t * @param {Object|undefined} kvStoreCounts Possibly using previous schemas\n\t * @return {Object|undefined} Counts object using current names or undefined\n\t *  if data was invalid or too old.\n\t */\n\tfunction fixCountNames( kvStoreCounts ) {\n\t\tif ( !kvStoreCounts || kvStoreCounts.skippedThisCycle === undefined ) {\n\t\t\t// * undefined\n\t\t\t// * T121178: March 2018 schema change (rename waitCount to skippedThisCycle)\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn kvStoreCounts;\n\t}\n\n\t/**\n\t * Get running impression counts from kvStore, if available, or return\n\t * zeroed counts.\n\t *\n\t * @return {Object} An object containing count data.\n\t */\n\tfunction getCounts() {\n\t\tlet c;\n\n\t\tif ( identifier ) {\n\t\t\tc = cn.kvStore.getItem(\n\t\t\t\tSTORAGE_KEY + '_' + identifier,\n\t\t\t\tcn.kvStore.contexts.GLOBAL,\n\t\t\t\tmultiStorageOption\n\t\t\t);\n\t\t} else {\n\t\t\tc = cn.kvStore.getItem(\n\t\t\t\tSTORAGE_KEY,\n\t\t\t\tcn.kvStore.contexts.CATEGORY,\n\t\t\t\tmultiStorageOption\n\t\t\t);\n\t\t}\n\n\t\treturn fixCountNames( c ) || getZeroedCounts();\n\t}\n\n\t/**\n\t * Store updated counts\n\t *\n\t * @param c\n\t */\n\tfunction storeCounts( c ) {\n\t\tif ( identifier ) {\n\t\t\tcn.kvStore.setItem(\n\t\t\t\tSTORAGE_KEY + '_' + identifier,\n\t\t\t\tc,\n\t\t\t\tcn.kvStore.contexts.GLOBAL,\n\t\t\t\tCOUNTS_STORAGE_TTL,\n\t\t\t\tmultiStorageOption\n\t\t\t);\n\t\t} else {\n\t\t\tcn.kvStore.setItem(\n\t\t\t\tSTORAGE_KEY,\n\t\t\t\tc,\n\t\t\t\tcn.kvStore.contexts.CATEGORY,\n\t\t\t\tCOUNTS_STORAGE_TTL,\n\t\t\t\tmultiStorageOption\n\t\t\t);\n\t\t}\n\t}\n\n\t// Register the mixin\n\tcn.registerCampaignMixin( mixin );\n\n\t// For use in QUnit tests\n\tmodule.exports.private = {\n\t\timpressionDietHandler\n\t};\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.impressionEventsSampleRate.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.largeBannerLimit.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/subscribing/ext.centralNotice.legacySupport.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"max-len","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/abstractSchemaChanges/patch-cn_notice_countries-unique-to-pk.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/abstractSchemaChanges/patch-cn_notice_languages-unique-to-pk.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/abstractSchemaChanges/patch-cn_notice_projects-unique-to-pk.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/abstractSchemaChanges/patch-cn_notice_regions-unique-to-pk.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/tables.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/phpunit/data/AllocationsFixtures.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/ext.centralNotice.display/chooser.tests.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":16,"column":2,"nodeType":"CallExpression","endLine":35,"endColumn":5,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":18,"column":3,"nodeType":"CallExpression","endLine":34,"endColumn":4,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":59,"column":4,"nodeType":"CallExpression","endLine":68,"endColumn":7,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/ext.centralNotice.display/hide.tests.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":439,"column":3,"nodeType":"CallExpression","endLine":441,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":479,"column":3,"nodeType":"CallExpression","endLine":481,"endColumn":6}],"suppressedMessages":[{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":23,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":23,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":34,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":34,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":35,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":35,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":44,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":44,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":55,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":55,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":56,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":56,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":67,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":67,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":78,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":78,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":79,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":79,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":87,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":87,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":88,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":88,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":99,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":99,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":110,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":110,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":111,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":111,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":119,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":119,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":120,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":120,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":131,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":131,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":142,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":142,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":143,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":143,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":154,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":154,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":165,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":165,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":166,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":166,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":177,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":177,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":188,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":188,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":189,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":189,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":200,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":200,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":211,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":211,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":212,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":212,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":221,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":221,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":232,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":232,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":233,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":233,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":244,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":244,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":255,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":255,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":256,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":256,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":265,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":265,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":276,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":276,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":277,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":277,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":288,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":288,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":299,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":299,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":300,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":300,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'bucket_count' is not in camel case.","line":309,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":309,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_anon' is not in camel case.","line":320,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":320,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'display_account' is not in camel case.","line":321,"column":7,"nodeType":"Identifier","messageId":"notCamelCase","endLine":321,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* eslint-disable camelcase */\n( function () {\n\t'use strict';\n\n\tconst realAjax = $.ajax,\n\t\trealGeoIP = mw.geoIP,\n\t\trealBucketCookie = $.cookie( 'CN' ),\n\t\trealHideCookie = $.cookie( 'centralnotice_hide_fundraising' ),\n\t\trealSendBeacon = navigator.sendBeacon,\n\t\trealshouldHide = mw.centralNotice.internal.hide.shouldHide,\n\t\tbannerData = {\n\t\t\tbannerName: 'test_banner',\n\t\t\tcampaign: 'test_campaign',\n\t\t\tcategory: 'test',\n\t\t\tbannerHtml: '<div id=\"test_banner\"></div>'\n\t\t},\n\t\tnowSec = Date.now() / 1000,\n\t\tchoiceData2Campaigns = [\n\t\t\t{\n\t\t\t\tname: 'campaign1',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {}\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'campaign2',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {}\n\t\t\t}\n\t\t],\n\t\tchoiceData1Campaign2Banners = [\n\t\t\t{\n\t\t\t\tname: 'campaign1',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: { testMixin: [ 'arg1', 'arg2' ] }\n\t\t\t}\n\t\t],\n\t\tchoiceData1Campaign2Banners2Buckets = [\n\t\t\t{\n\t\t\t\tname: 'campaign1',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 2,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'mobile' ],\n\t\t\t\t\t\tdisplay_anon: false,\n\t\t\t\t\t\tdisplay_account: false\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 1,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: { testMixin: [ 'arg1', 'arg2' ] }\n\t\t\t}\n\t\t],\n\t\tchoiceDataCampaignsStaleness = [\n\t\t\t{\n\t\t\t\tname: 'campaign1_stale',\n\t\t\t\tpreferred: 2,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - ( 60000 * ( 15 * 60000 ) ), // with leeway\n\t\t\t\tend: nowSec - ( 600 * ( 15 * 60000 ) ), // with leeway\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {}\n\t\t\t}\n\t\t],\n\t\tchoiceDataCampaignsFallbackMixin = [\n\t\t\t{\n\t\t\t\tname: 'campaign1_mixin',\n\t\t\t\tpreferred: 2,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {\n\t\t\t\t\ttestMixin: [ 'arg1', 'arg2' ]\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'campaign2',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {}\n\t\t\t}\n\t\t],\n\t\tchoiceDataCampaignsFallbackHidden = [\n\t\t\t{\n\t\t\t\tname: 'campaign1',\n\t\t\t\tpreferred: 2,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {}\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'campaign2',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'something',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: {}\n\t\t\t}\n\t\t],\n\t\tchoiceDataAllCampaignsFail = [\n\t\t\t{\n\t\t\t\tname: 'campaign1',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: { testMixin: [] }\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'campaign2',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: { testMixin: [] }\n\t\t\t}\n\t\t],\n\t\tchoiceDataAllCampaignsImpressionRates = [\n\t\t\t{\n\t\t\t\tname: 'campaign1',\n\t\t\t\tpreferred: 2,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner1',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: { testMixinRates: [ 0.75 ] }\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'campaign2',\n\t\t\t\tpreferred: 1,\n\t\t\t\tthrottle: 100,\n\t\t\t\tbucket_count: 1,\n\t\t\t\tgeotargeted: false,\n\t\t\t\tstart: nowSec - 1,\n\t\t\t\tend: nowSec + 600,\n\t\t\t\tbanners: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'banner2',\n\t\t\t\t\t\tweight: 25,\n\t\t\t\t\t\tbucket: 0,\n\t\t\t\t\t\tcategory: 'fundraising',\n\t\t\t\t\t\tdevices: [ 'desktop' ],\n\t\t\t\t\t\tdisplay_anon: true,\n\t\t\t\t\t\tdisplay_account: true\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\tmixins: { testMixinRates: [ 0.5 ] }\n\t\t\t}\n\t\t];\n\n\tQUnit.module( 'ext.centralNotice.display', QUnit.newMwEnvironment( {\n\t\tbeforeEach: function () {\n\n\t\t\t$( '#siteNotice' ).remove();\n\n\t\t\t$.removeCookie( 'centralnotice_hide_fundraising', { path: '/' } );\n\t\t\t$.removeCookie( 'CN', { path: '/' } );\n\n\t\t\t// Suppress background calls\n\t\t\t$.ajax = function () {\n\t\t\t\treturn $.Deferred();\n\t\t\t};\n\n\t\t\t// Create normalized siteNotice.\n\t\t\t$( '#qunit-fixture' ).append(\n\t\t\t\t'<div id=siteNotice><div id=centralNotice></div></div>'\n\t\t\t);\n\n\t\t\t// Mock mw.geoIP\n\t\t\tmw.geoIP = {\n\t\t\t\tgetPromise: function () {\n\t\t\t\t\tconst deferred = $.Deferred();\n\t\t\t\t\t// Resolve with minimal valid geo object\n\t\t\t\t\tdeferred.resolve( { country: 'AQ' } );\n\t\t\t\t\treturn deferred.promise();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Reset record impression deferred object and array of promises for delaying\n\t\t\t// record impression call\n\t\t\tmw.centralNotice.recordImpressionDeferredObj = null;\n\t\t\tmw.centralNotice.recordImpressionDelayPromises = [];\n\t\t},\n\t\tafterEach: function () {\n\t\t\t$.ajax = realAjax;\n\t\t\tmw.geoIP = realGeoIP;\n\t\t\tnavigator.sendBeacon = realSendBeacon;\n\t\t\t$.cookie( 'centralnotice_hide_fundraising', realHideCookie, { path: '/' } );\n\t\t\t$.cookie( 'CN', realBucketCookie, { path: '/' } );\n\t\t\tmw.centralNotice.internal.state.data = {};\n\t\t\tmw.centralNotice.internal.state.campaign = null;\n\t\t\tmw.centralNotice.internal.state.banner = null;\n\t\t\tmw.centralNotice.internal.state.urlParams.recordImpressionSampleRate = null;\n\t\t\tmw.centralNotice.internal.state.urlParams.impressionEventSampleRate = null;\n\t\t\tmw.centralNotice.internal.state.attemptedCampaignsByName = {};\n\t\t\tmw.centralNotice.internal.hide.shouldHide = realshouldHide;\n\t\t}\n\t} ) );\n\n\tQUnit.test( 'canInsertBanner', ( assert ) => {\n\t\tmw.centralNotice.choiceData = choiceData2Campaigns;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\t// We call reallyInsertBanner() instead of insertBanner() to avoid\n\t\t// the async DOM-waiting of the latter.\n\t\tmw.centralNotice.reallyInsertBanner( bannerData );\n\n\t\tassert.strictEqual( $( 'div#test_banner' ).length, 1 );\n\t} );\n\n\t/**\n\t * Create the required state in CN for the record impression call to occur. The first\n\t * campaign in choiceData2Campaigns will be chosen.\n\t *\n\t * @param {Object[]} campaignsData\n\t */\n\tfunction mockChoiceDataForRecordImpressionCall( campaignsData ) {\n\n\t\t// Request 100% sample rate for record impression\n\t\tmw.centralNotice.internal.state.urlParams.recordImpressionSampleRate = 1;\n\n\t\tmw.centralNotice.choiceData = campaignsData;\n\t\t// Set `randomcampaign` to force the same choice each time, for tests where more\n\t\t// than one campaign may be available\n\t\tmw.centralNotice.internal.state.urlParams.randomcampaign = 0.25;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\t}\n\n\tQUnit.test( 'call record impression', ( assert ) => {\n\n\t\t// Mock navigator.sendBeacon to capture calls and check data points sent\n\t\tnavigator.sendBeacon = function ( urlString ) {\n\t\t\tconst url = new URL( urlString );\n\t\t\tassert.strictEqual( url.searchParams.get( 'campaign' ), 'campaign1', 'record impression campaign' );\n\t\t\tassert.strictEqual( url.searchParams.get( 'banner' ), 'banner1', 'record impression banner' );\n\t\t};\n\n\t\tmockChoiceDataForRecordImpressionCall( choiceData2Campaigns );\n\n\t\t// Call reallyInsertBanner() instead of insertBanner() to avoid the async\n\t\t// DOM-waiting of the latter. This triggers the call to record impression.\n\t\tmw.centralNotice.reallyInsertBanner( bannerData );\n\t} );\n\n\tQUnit.test( 'delay record impression call and register tests', ( assert ) => {\n\t\tconst deferred = $.Deferred(),\n\t\t\tsignalTestDone = assert.async();\n\n\t\t// Mock navigator.sendBeacon to check record impression doesn't fire early\n\t\tnavigator.sendBeacon = function () {\n\t\t\t// If we get here, it's a failure\n\t\t\tassert.true( false, 'record impression waits, as requested' );\n\t\t};\n\n\t\tmockChoiceDataForRecordImpressionCall( choiceData2Campaigns );\n\n\t\t// Request a delay and capture the promise that should run right before the\n\t\t// record impression call\n\t\tconst recordImpresionPromise =\n\t\t\tmw.centralNotice.requestRecordImpressionDelay( deferred.promise() );\n\n\t\trecordImpresionPromise.done( () => {\n\t\t\tmw.centralNotice.registerTest( 'test_test' );\n\t\t} );\n\n\t\t// Call reallyInsertBanner() instead of insertBanner() to avoid the async\n\t\t// DOM-waiting of the latter. This doesn't trigger the call to record impression\n\t\t// yet because we requested a delay.\n\t\tmw.centralNotice.reallyInsertBanner( bannerData );\n\n\t\t// Mock navigator.sendBeacon to capture calls and check data points sent\n\t\tnavigator.sendBeacon = function ( urlString ) {\n\t\t\tconst url = new URL( urlString );\n\t\t\tassert.strictEqual( url.searchParams.get( 'campaign' ), 'campaign1', 'record impression campaign' );\n\t\t\tassert.strictEqual( url.searchParams.get( 'banner' ), 'banner1', 'record impression banner' );\n\n\t\t\tassert.strictEqual(\n\t\t\t\turl.searchParams.get( 'testIdentifiers' ),\n\t\t\t\t'test_test',\n\t\t\t\t'record impression test identifier'\n\t\t\t);\n\n\t\t\tsignalTestDone();\n\t\t};\n\n\t\t// Resolve the promise to let the record impression call go ahead\n\t\tdeferred.resolve();\n\t} );\n\n\tQUnit.skip( 'record impression timeout and register tests', ( assert ) => {\n\t\tconst start = Date.now(),\n\t\t\tMAX_RECORD_IMPRESSION_DELAY = 250, // Coordinate with ext.centralnotice.display.js\n\t\t\tsignalTestDone = assert.async();\n\n\t\tmockChoiceDataForRecordImpressionCall( choiceData2Campaigns );\n\n\t\t// Request a delay and capture the promise that should run right before the\n\t\t// record impression call\n\t\tconst recordImpresionPromise =\n\t\t\tmw.centralNotice.requestRecordImpressionDelay( $.Deferred().promise() );\n\n\t\trecordImpresionPromise.done( () => {\n\t\t\tmw.centralNotice.registerTest( 'test_timeouted_test' );\n\t\t} );\n\n\t\t// Mock navigator.sendBeacon to capture calls and check time and data points sent\n\t\tnavigator.sendBeacon = function ( urlString ) {\n\t\t\tconst url = new URL( urlString ),\n\t\t\t\tdelay = Date.now() - start;\n\n\t\t\tassert.strictEqual( url.searchParams.get( 'campaign' ), 'campaign1', 'record impression campaign' );\n\t\t\tassert.strictEqual( url.searchParams.get( 'banner' ), 'banner1', 'record impression banner' );\n\n\t\t\t// 50 ms leewway is bit arbitrary\n\t\t\tassert.true(\n\t\t\t\t( delay > MAX_RECORD_IMPRESSION_DELAY - 50 ) &&\n\t\t\t\t( delay < MAX_RECORD_IMPRESSION_DELAY + 50 ),\n\t\t\t\t'record impression called by timeout'\n\t\t\t);\n\n\t\t\tassert.strictEqual(\n\t\t\t\turl.query.testIdentifiers,\n\t\t\t\t'test_timeouted_test',\n\t\t\t\t'record impression test identifier'\n\t\t\t);\n\n\t\t\tsignalTestDone();\n\t\t};\n\n\t\t// Call reallyInsertBanner() instead of insertBanner() to avoid the async\n\t\t// DOM-waiting of the latter. This starts the record impression timeout.\n\t\tmw.centralNotice.reallyInsertBanner( bannerData );\n\n\t\t// We don't resolve the promise to delay record impression. The call should\n\t\t// be made by MAX_RECORD_IMPRESSION_DELAY milliseconds (give or take a bit).\n\t} );\n\n\tQUnit.skip( 'record impression called only once', ( assert ) => {\n\t\tconst deferred = $.Deferred(),\n\t\t\tMAX_RECORD_IMPRESSION_DELAY = 250, // Coordinate with ext.centralnotice.display.js\n\t\t\tsignalTestDone = assert.async();\n\n\t\tmockChoiceDataForRecordImpressionCall( choiceData2Campaigns );\n\n\t\t// Request a delay and capture the promise that should run right before the\n\t\t// record impression call\n\t\tconst recordImpresionPromise =\n\t\t\tmw.centralNotice.requestRecordImpressionDelay( deferred.promise() );\n\n\t\t// Mock navigator.sendBeacon to capture calls\n\t\tnavigator.sendBeacon = function () {\n\t\t\tassert.true( true, 'record impression called once' );\n\n\t\t\t// Re-mock to fail test if called again\n\t\t\tnavigator.sendBeacon = function () {\n\t\t\t\tassert.true( false, 'record impression not called twice' );\n\t\t\t};\n\t\t};\n\n\t\t// Call reallyInsertBanner() instead of insertBanner() to avoid the async\n\t\t// DOM-waiting of the latter. This starts the record impression timeout.\n\t\tmw.centralNotice.reallyInsertBanner( bannerData );\n\n\t\t// Set another timeout to resolve the promise requesting a delay after the timeout\n\t\t// to call record impression has run. By starting this timeout after the record\n\t\t// impression timeout (above) and making it a bit longer, we ensure it runs second.\n\t\tsetTimeout( () => {\n\t\t\tassert.strictEqual(\n\t\t\t\trecordImpresionPromise.state(),\n\t\t\t\t'resolved',\n\t\t\t\t'record impression call was from timeout'\n\t\t\t);\n\n\t\t\t// Resolve the promise that delays the call only up until the timeout, to\n\t\t\t// test that record impression isn't called a second time.\n\t\t\tdeferred.resolve();\n\t\t}, MAX_RECORD_IMPRESSION_DELAY + 1 );\n\n\t\tsetTimeout( () => {\n\t\t\tsignalTestDone();\n\t\t}, MAX_RECORD_IMPRESSION_DELAY + 2 );\n\t} );\n\n\tQUnit.test( 'banner= override param', ( assert ) => {\n\t\tmw.centralNotice.internal.state.urlParams.banner = 'test_banner';\n\t\t$.ajax = function ( params ) {\n\t\t\tassert.true( /Special(?:[:]|%3A)BannerLoader.*[?&]banner=test_banner/.test( params.url ) );\n\t\t\treturn $.Deferred();\n\t\t};\n\t\tmw.centralNotice.displayTestingBanner();\n\n\t\tassert.true( mw.centralNotice.data.testingBanner );\n\t} );\n\n\tQUnit.test( 'randomcampaign= override param', ( assert ) => {\n\t\tmw.centralNotice.choiceData = choiceData2Campaigns;\n\n\t\t// Get the first campaign\n\t\tmw.centralNotice.internal.state.urlParams.randomcampaign = 0.25;\n\n\t\t$.ajax = function ( params ) {\n\t\t\tassert.true( /Special(?:[:]|%3A)BannerLoader.*[?&]banner=banner1/.test( params.url ) );\n\t\t\treturn $.Deferred();\n\t\t};\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\t// Get the second campaign\n\t\tmw.centralNotice.internal.state.urlParams.randomcampaign = 0.75;\n\n\t\t$.ajax = function ( params ) {\n\t\t\tassert.true( /Special(?:[:]|%3A)BannerLoader.*[?&]banner=banner2/.test( params.url ) );\n\t\t\treturn $.Deferred();\n\t\t};\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\t} );\n\n\tQUnit.test( 'randombanner= override param', ( assert ) => {\n\t\tmw.centralNotice.choiceData = choiceData1Campaign2Banners;\n\n\t\t// Get the first banner\n\t\tmw.centralNotice.internal.state.urlParams.randombanner = 0.25;\n\n\t\t$.ajax = function ( params ) {\n\t\t\tassert.true( /Special(?:[:]|%3A)BannerLoader.*[?&]banner=banner1/.test( params.url ) );\n\t\t\treturn $.Deferred();\n\t\t};\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\t// Get the second banner\n\t\tmw.centralNotice.internal.state.urlParams.randombanner = 0.75;\n\n\t\t$.ajax = function ( params ) {\n\t\t\tassert.true( /Special(?:[:]|%3A)BannerLoader.*[?&]banner=banner2/.test( params.url ) );\n\t\t\treturn $.Deferred();\n\t\t};\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\t} );\n\n\tQUnit.test( 'runs hooks on banner shown', ( assert ) => {\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixin' );\n\n\t\t$.ajax = function () {\n\t\t\tmw.centralNotice.reallyInsertBanner( bannerData );\n\t\t\treturn $.Deferred();\n\t\t};\n\n\t\tmixin.setPreBannerHandler(\n\t\t\t( params ) => {\n\t\t\t\tassert.deepEqual( params, choiceData1Campaign2Banners[ 0 ].mixins.testMixin );\n\t\t\t}\n\t\t);\n\t\tmixin.setPostBannerOrFailHandler(\n\t\t\t( params ) => {\n\t\t\t\tassert.deepEqual( params, choiceData1Campaign2Banners[ 0 ].mixins.testMixin );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\t\tmw.centralNotice.choiceData = choiceData1Campaign2Banners;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.data.status,\n\t\t\tmw.centralNotice.internal.state.STATUSES.BANNER_SHOWN.key\n\t\t);\n\t} );\n\n\tQUnit.test( 'runs hooks on banner canceled', ( assert ) => {\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixin' );\n\n\t\tmixin.setPreBannerHandler(\n\t\t\t( params ) => {\n\t\t\t\tassert.deepEqual( params, choiceData1Campaign2Banners[ 0 ].mixins.testMixin );\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\t\tmixin.setPostBannerOrFailHandler(\n\t\t\t( params ) => {\n\t\t\t\tassert.deepEqual( params, choiceData1Campaign2Banners[ 0 ].mixins.testMixin );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\t\tmw.centralNotice.choiceData = choiceData1Campaign2Banners;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.data.status,\n\t\t\tmw.centralNotice.internal.state.STATUSES.BANNER_CANCELED.key\n\t\t);\n\t\tassert.strictEqual( mw.centralNotice.data.bannerCanceledReason, 'testReason' );\n\t\tassert.true( mw.centralNotice.internal.state.isCampaignFailed() );\n\t} );\n\n\tQUnit.test( 'runs hooks on no banner available', ( assert ) => {\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixin' );\n\n\t\tmixin.setPreBannerHandler(\n\t\t\t( params ) => {\n\t\t\t\tassert.deepEqual(\n\t\t\t\t\tparams,\n\t\t\t\t\tchoiceData1Campaign2Banners2Buckets[ 0 ].mixins.testMixin\n\t\t\t\t);\n\t\t\t}\n\t\t);\n\t\tmixin.setPostBannerOrFailHandler(\n\t\t\t( params ) => {\n\t\t\t\tassert.deepEqual(\n\t\t\t\t\tparams,\n\t\t\t\t\tchoiceData1Campaign2Banners2Buckets[ 0 ].mixins.testMixin\n\t\t\t\t);\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\t\tmw.centralNotice.choiceData = choiceData1Campaign2Banners2Buckets;\n\t\tmw.centralNotice.internal.bucketer.setCampaign( choiceData1Campaign2Banners2Buckets[ 0 ] );\n\t\tmw.centralNotice.internal.bucketer.process();\n\t\tmw.centralNotice.internal.bucketer.setBucket( 0 );\n\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.data.status,\n\t\t\tmw.centralNotice.internal.state.STATUSES.NO_BANNER_AVAILABLE.key\n\t\t);\n\t} );\n\n\tQUnit.test( 'short-circuit when there is a stale campaign in choices', ( assert ) => {\n\t\tmw.centralNotice.choiceData = choiceDataCampaignsStaleness;\n\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.data.status,\n\t\t\tmw.centralNotice.internal.state.STATUSES.CHOICE_DATA_STALE.key,\n\t\t\t'choice data is stale'\n\t\t);\n\t} );\n\n\tQUnit.test( 'fallback when preferred campaign is filtered out by a mixin', ( assert ) => {\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixin' );\n\n\t\tmixin.setPreBannerHandler(\n\t\t\t() => {\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\t\tmw.centralNotice.choiceData = choiceDataCampaignsFallbackMixin;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.data.status,\n\t\t\tmw.centralNotice.internal.state.STATUSES.BANNER_CHOSEN.key,\n\t\t\t'banner is chosen'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.data.banner,\n\t\t\t'banner2'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.campaign.name,\n\t\t\t'campaign2',\n\t\t\t'campaign is chosen correctly'\n\t\t);\n\t} );\n\n\tQUnit.test( 'fallback when preferred campaign banner is hidden', ( assert ) => {\n\t\tlet i = 0;\n\n\t\tmw.centralNotice.internal.hide.shouldHide = function () {\n\t\t\ti++;\n\t\t\t// Ensure only the first campaign from the list will hit hide.shouldHide()\n\t\t\treturn i === 1;\n\t\t};\n\n\t\tmw.centralNotice.choiceData = choiceDataCampaignsFallbackHidden;\n\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.data.status,\n\t\t\tmw.centralNotice.internal.state.STATUSES.BANNER_CHOSEN.key,\n\t\t\t'banner is chosen'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.data.banner,\n\t\t\t'banner2'\n\t\t);\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.campaign.name,\n\t\t\t'campaign2',\n\t\t\t'campaign is chosen correctly'\n\t\t);\n\t} );\n\n\tQUnit.test( 'record impression is being called when all campaigns fail', function ( assert ) {\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixin' );\n\n\t\tthis.sandbox.stub( navigator, 'sendBeacon', () => {\n\t\t\tassert.step( 'beacon' );\n\t\t} );\n\n\t\t// Make every campaign fail by cancelling banners\n\t\tmixin.setPreBannerHandler(\n\t\t\t() => {\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\n\t\tmockChoiceDataForRecordImpressionCall( choiceDataAllCampaignsFail );\n\n\t\t// Expect sendBeacon to be called once\n\t\tassert.verifySteps( [ 'beacon' ] );\n\n\t} );\n\n\tQUnit.test( 'impression sample rate is kept as highest value', ( assert ) => {\n\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixinRates' );\n\n\t\t// Set new sample rates via mixin\n\t\tmixin.setPreBannerHandler(\n\t\t\t( mixinParams ) => {\n\t\t\t\tmw.centralNotice.setMinRecordImpressionSampleRate( mixinParams[ 0 ] );\n\t\t\t\t// Fail campaign by cancelling a banner\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\n\t\tmw.centralNotice.choiceData = choiceDataAllCampaignsImpressionRates;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.getData().recordImpressionSampleRate,\n\t\t\t0.75\n\t\t);\n\n\t} );\n\n\tQUnit.test( 'event sample rate is kept as highest value', ( assert ) => {\n\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixinRates' );\n\n\t\t// Set new sample rates via mixin\n\t\tmixin.setPreBannerHandler(\n\t\t\t( mixinParams ) => {\n\t\t\t\tmw.centralNotice.setMinImpressionEventSampleRate( mixinParams[ 0 ] );\n\t\t\t\t// Fail campaign by cancelling a banner\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\n\t\tmw.centralNotice.choiceData = choiceDataAllCampaignsImpressionRates;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.getData().impressionEventSampleRate,\n\t\t\t0.75\n\t\t);\n\n\t} );\n\n\tQUnit.test( 'event sample rate is being correctly overridden from url', ( assert ) => {\n\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixinRates' );\n\n\t\t// Override rate via URL param\n\t\tmw.centralNotice.internal.state.urlParams.impressionEventSampleRate = 1;\n\n\t\t// Set new sample rates via mixin\n\t\tmixin.setPreBannerHandler(\n\t\t\t( mixinParams ) => {\n\t\t\t\tmw.centralNotice.setMinImpressionEventSampleRate( mixinParams[ 0 ] );\n\t\t\t\t// Fail campaign by cancelling a banner\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\n\t\tmw.centralNotice.choiceData = choiceDataAllCampaignsImpressionRates;\n\t\tmw.centralNotice.chooseAndMaybeDisplay();\n\n\t\tassert.strictEqual(\n\t\t\tmw.centralNotice.internal.state.getData().impressionEventSampleRate,\n\t\t\t1\n\t\t);\n\n\t} );\n\n\tQUnit.test( 'campaign statuses are tracked as serialized JSON string', ( assert ) => {\n\n\t\tconst mixin = new mw.centralNotice.Mixin( 'testMixin' );\n\n\t\t// Make every campaign fail by cancelling banners\n\t\tmixin.setPreBannerHandler(\n\t\t\t() => {\n\t\t\t\tmw.centralNotice.failCampaign( 'testReason' );\n\t\t\t}\n\t\t);\n\n\t\t// Mock navigator.sendBeacon\n\t\tnavigator.sendBeacon = function ( urlString ) {\n\t\t\tconst url = new URL( urlString );\n\t\t\tconst statuses = JSON.parse( url.searchParams.get( 'campaignStatuses' ) );\n\t\t\tassert.strictEqual( statuses.length, 2, 'correct amount of records' );\n\t\t};\n\n\t\tmw.centralNotice.registerCampaignMixin( mixin );\n\n\t\tmockChoiceDataForRecordImpressionCall( choiceDataAllCampaignsFail );\n\n\t} );\n\n\tQUnit.test( 'getBannerUrl', ( assert ) => {\n\t\tconst host = location.host;\n\t\tconst protocol = location.protocol;\n\t\t[\n\t\t\t[\n\t\t\t\t'https://meta.wikimedia.org/wiki/Special:BannerLoader',\n\t\t\t\t{\n\t\t\t\t\tbanner: 'b1',\n\t\t\t\t\tuselang: 'fr',\n\t\t\t\t\tdebug: false\n\t\t\t\t},\n\t\t\t\t'https://meta.wikimedia.org/wiki/Special:BannerLoader?banner=b1&uselang=fr&debug=false'\n\t\t\t],\n\t\t\t[\n\t\t\t\t'//meta.wikimedia.org/wiki/Special:BannerLoader',\n\t\t\t\t{\n\t\t\t\t\tbanner: 'b1',\n\t\t\t\t\tuselang: 'fr',\n\t\t\t\t\tdebug: false\n\t\t\t\t},\n\t\t\t\t`${ protocol }//meta.wikimedia.org/wiki/Special:BannerLoader?banner=b1&uselang=fr&debug=false`\n\t\t\t],\n\t\t\t[\n\t\t\t\t'https://mediawiki.org/w/index.php?title=Special:BannerLoader',\n\t\t\t\t{\n\t\t\t\t\tbanner: 'b2',\n\t\t\t\t\tuselang: 'es',\n\t\t\t\t\tdebug: false\n\t\t\t\t},\n\t\t\t\t'https://mediawiki.org/w/index.php?title=Special:BannerLoader&banner=b2&uselang=es&debug=false'\n\t\t\t],\n\t\t\t[\n\t\t\t\t'/wiki/Special:BannerLoader',\n\t\t\t\t{\n\t\t\t\t\tbanner: 'b1',\n\t\t\t\t\tuselang: 'fr',\n\t\t\t\t\tdebug: false\n\t\t\t\t},\n\t\t\t\t`${ protocol }//${ host }/wiki/Special:BannerLoader?banner=b1&uselang=fr&debug=false`\n\t\t\t],\n\t\t\t[\n\t\t\t\t'https://meta.wikimedia.org/wiki/Special:BannerLoader',\n\t\t\t\t{\n\t\t\t\t\tbanner: 'z + a!\\'(*)=b,קי',\n\t\t\t\t\tuselang: 'qqq',\n\t\t\t\t\tdebug: false\n\t\t\t\t},\n\t\t\t\t'https://meta.wikimedia.org/wiki/Special:BannerLoader?banner=z+%2B+a%21%27%28%2A%29%3Db%2C%D7%A7%D7%99&uselang=qqq&debug=false'\n\t\t\t]\n\t\t].forEach( ( [ path, data, expected ] ) => {\n\t\t\tconst url = mw.centralNotice.test.getBannerUrl( path, data );\n\t\t\tassert.strictEqual( url.toString(), expected );\n\t\t} );\n\t} );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/ext.centralNotice.kvStore/kvStore.tests.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/ext.centralNotice.startUp/kvStoreMaintenance.tests.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/subscribing/ext.centralNotice.bannerSequence.tests.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/subscribing/ext.centralNotice.geoIP.tests.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":49,"column":3,"nodeType":"CallExpression","endLine":51,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":49,"column":3,"nodeType":"CallExpression","endLine":53,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":63,"column":3,"nodeType":"CallExpression","endLine":65,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":63,"column":3,"nodeType":"CallExpression","endLine":67,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\t'use strict';\n\n\tconst\n\t\tCOOKIE_NAME = 'GeoIP',\n\t\tBAD_COOKIE = 'asdfasdf',\n\t\tUNKNOWN_COOKIE = ':::::v4',\n\t\tGOOD_COOKIE = 'US:CO:Denver:39.6762:-104.887:v4',\n\t\tGOOD_GEO = {\n\t\t\taf: 'v4',\n\t\t\tcity: 'Denver',\n\t\t\tcountry: 'US',\n\t\t\tlat: 39.6762,\n\t\t\tlon: -104.887,\n\t\t\tregion: 'CO'\n\t\t},\n\t\trealCookie = $.cookie( COOKIE_NAME );\n\n\tQUnit.module( 'ext.centralNotice.geoIP', QUnit.newMwEnvironment( {\n\t\tafterEach: function () {\n\t\t\t// This cookie is always set to '/' in prod and should be here too.\n\t\t\t// If a cookie of the same name is set without a path it may be\n\t\t\t// found first by the jquery getter and will screw some behaviors\n\t\t\t// up until it is removed.\n\t\t\t$.cookie( COOKIE_NAME, realCookie, { path: '/' } );\n\t\t}\n\t} ) );\n\n\tQUnit.test( 'parse geo from valid cookie', ( assert ) => {\n\t\t$.cookie( COOKIE_NAME, GOOD_COOKIE, { path: '/' } );\n\n\t\tmw.geoIP.makeGeoWithPromise();\n\t\treturn mw.geoIP.getPromise().then(\n\t\t\t( geo ) => {\n\t\t\t\tassert.deepEqual( geo, GOOD_GEO, 'parsed geo' );\n\t\t\t},\n\t\t\t// Message to show when promise fails\n\t\t\t() => 'geo not retrieved'\n\t\t);\n\t} );\n\n\tQUnit.test( 'cookie invalid', ( assert ) => {\n\t\t$.cookie( COOKIE_NAME, BAD_COOKIE, { path: '/' } );\n\n\t\t// Make sure that we don't fall back\n\t\tmw.config.set( 'wgCentralNoticeGeoIPBackgroundLookupModule', false );\n\n\t\tmw.geoIP.makeGeoWithPromise();\n\t\tmw.geoIP.getPromise().fail( () => {\n\t\t\tassert.true( true, 'geoIP promise fails, as expected' );\n\t\t} ).done( () => {\n\t\t\tassert.true( false, 'geoIP promise succeeded, but should not have' );\n\t\t} );\n\t} );\n\n\tQUnit.test( 'cookie valid but unknown location', ( assert ) => {\n\t\t$.cookie( COOKIE_NAME, UNKNOWN_COOKIE, { path: '/' } );\n\n\t\t// Make sure that we don't fall back\n\t\tmw.config.set( 'wgCentralNoticeGeoIPBackgroundLookupModule', false );\n\n\t\tmw.geoIP.makeGeoWithPromise();\n\t\tmw.geoIP.getPromise().fail( () => {\n\t\t\tassert.true( true, 'geoIP promise fails, as expected' );\n\t\t} ).done( () => {\n\t\t\tassert.true( false, 'geoIP promise succeeded, but should not have' );\n\t\t} );\n\t} );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/subscribing/ext.centralNotice.impressionDiet.tests.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/selenium/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/selenium/pageobjects/main.page.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/selenium/specs/centralnotice.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/selenium/wdio.conf.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]}]

--- end ---
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated node-domexception@1.0.0: Use your platform's native DOMException instead
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
--- stdout ---

added 863 packages, and audited 864 packages in 14s

200 packages are looking for funding
  run `npm fund` for details

11 vulnerabilities (5 moderate, 4 high, 2 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> test
> grunt test

Running "eslint:all" (eslint) task

/src/repo/resources/ext.centralNotice.display/index.js
  265:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  297:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  842:4  warning  Prefer .then to .fail  no-jquery/no-done-fail
  842:4  warning  Prefer .then to .done  no-jquery/no-done-fail
  852:4  warning  Prefer .then to .fail  no-jquery/no-done-fail
  852:4  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/ext.centralNotice.kvStore/kvStore.js
   83:18  warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   87:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   89:9   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   91:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  198:4   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  202:9   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  230:15  warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  259:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  298:4   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage

/src/repo/resources/ext.centralNotice.startUp/index.js
  44:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
   33:17  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   38:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   61:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   65:9   warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   69:7   warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   84:28  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie
   90:14  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie
  109:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
  109:35  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
  117:18  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie

/src/repo/resources/infrastructure/bannereditor.js
   50:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
   50:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  117:3  warning  Prefer .then to .fail  no-jquery/no-done-fail

/src/repo/resources/infrastructure/campaignManager.js
  809:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
   808:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
   808:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail
  1251:20  warning  OOUI button has no label. Even icon-only buttons should set a label with invisibleLabel set to true  mediawiki/no-unlabeled-buttonwidget
  1322:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
  1322:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail
  1358:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
  1358:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
  296:4  warning  Prefer .then to .done  no-jquery/no-done-fail
  367:4  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.geoIP.js
  160:2  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.impressionDiet.js
  180:1  warning  Missing JSDoc @param "c" type  jsdoc/require-param-type

/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js
  439:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  479:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/tests/qunit/subscribing/ext.centralNotice.geoIP.tests.js
  49:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  49:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  63:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  63:3  warning  Prefer .then to .done  no-jquery/no-done-fail

✖ 47 problems (0 errors, 47 warnings)


Running "banana:CentralNotice" (banana) task
>> 2 message directories checked.

Running "stylelint:all" (stylelint) task
>> Linted 7 files without errors

Done.

--- end ---
Upgrading c:mediawiki/mediawiki-codesniffer from 50.0.0 -> 51.0.0
$ /usr/bin/composer update
--- stderr ---
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 3 updates, 0 removals
  - Upgrading composer/spdx-licenses (1.5.10 => 1.6.0)
  - Upgrading mediawiki/mediawiki-codesniffer (v50.0.0 => v51.0.0)
  - Upgrading phpcsstandards/phpcsextra (1.4.0 => 1.5.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 3 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Upgrading phpcsstandards/phpcsextra (1.4.0 => 1.5.0): Extracting archive
  - Upgrading composer/spdx-licenses (1.5.10 => 1.6.0): Extracting archive
  - Upgrading mediawiki/mediawiki-codesniffer (v50.0.0 => v51.0.0): Extracting archive
 0/3 [>---------------------------]   0%
 1/3 [=========>------------------]  33%
 3/3 [============================] 100%
Generating autoload files
16 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found.
--- stdout ---

--- end ---
$ vendor/bin/phpcs --report=json
--- stdout ---
{"totals":{"errors":0,"warnings":0,"fixable":0},"files":{"\/src\/repo\/includes\/CNDatabase.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/api\/ApiCentralNoticeQueryCampaign.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/HTMLBannerPagerNavigation.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/EmptyBannerException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/api\/ApiCentralNoticeChoiceData.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/StaleCampaignException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/BannerPreviewPermissionsException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CampaignExistenceException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/api\/ApiCentralNoticeQueryActiveCampaigns.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialBannerRandom.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/MissingRequiredParamsException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialRecordImpression.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/HTMLLargeMultiSelectField.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/UserMergeHookHandlers.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CNDeviceTarget.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CdnCacheUpdateBannerLoader.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/LanguageSelectHeaderElement.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/BannerLoaderException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CampaignLog.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/api\/ApiCentralNoticeLogs.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/IBannerMixin.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CentralNoticeCampaignChangeHook.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/BannerExistenceException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/maintenance\/CleanCNTranslateMetadata.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/MixinController.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/MixinNotFoundException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/BannerDataException.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/HtmlFormElements\/HTMLCentralNoticeBannerMessage.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/api\/ApiCentralNoticeCdnCacheUpdateBanner.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CNChoiceDataResourceLoaderModule.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CNCountry.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CampaignType.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/AllocationCalculator.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/BannerMessage.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CentralNoticeHookRunner.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/CentralNoticePager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/CNTestFixturesResourceLoaderModule.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/unit\/CampaignTypeTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/ApiCentralNoticeChoiceDataTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/CampaignTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/structure\/BundleSizeTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/unit\/CentralNoticeHookRunnerTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/CentralNoticeTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/CNChoiceDataResourceLoaderModuleTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CNDatabasePatcher.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialBannerLoader.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/CNBannerPager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/CentralNotice.alias.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/.phan\/config.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/BannerTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/ChoiceDataProvider.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/AllocationCalculatorTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/HistoryTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/BannerMessageGroup.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/CNCampaignPager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/BannerRenderer.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialBannerAllocation.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/CentralNoticePageLogPager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/CentralNoticeBannerLogPager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/TemplatePager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/CentralNoticeHooks.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/ChoiceDataProviderTest.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/tests\/phpunit\/CentralNoticeTestFixtures.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/pagers\/CentralNoticeCampaignLogPager.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialNoticeTemplate.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialCentralNoticeLogs.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/CentralNotice.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialCentralNoticeBanners.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/CentralNoticeHtmlForm.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/specials\/SpecialHideBanners.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/Banner.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/Campaign.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/GeoTarget.php":{"errors":0,"warnings":0,"messages":[]},"\/src\/repo\/includes\/BannerContentException.php":{"errors":0,"warnings":0,"messages":[]}}}

--- end ---
$ /usr/bin/composer install
--- stderr ---
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating autoload files
16 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---

--- end ---
$ /usr/bin/composer test
--- stderr ---
> parallel-lint . --exclude vendor --exclude node_modules
> phpcs -sp --cache
> minus-x check .
--- stdout ---
PHP 8.4.18 | 10 parallel jobs
............................................................ 60/74 ( 81%)
..............                                               74/74 (100%)


Checked 74 files in 0.3 seconds
No syntax error found
..................................... 37 / 37 (100%)


Time: 570ms; Memory: 14MB

MinusX
======
Processing /src/repo...
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.........................
All good!

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "@wdio/mocha-framework": {
      "name": "@wdio/mocha-framework",
      "severity": "high",
      "isDirect": true,
      "via": [
        "mocha"
      ],
      "effects": [],
      "range": ">=6.1.19",
      "nodes": [
        "node_modules/@wdio/mocha-framework"
      ],
      "fixAvailable": {
        "name": "@wdio/mocha-framework",
        "version": "6.1.17",
        "isSemVerMajor": true
      }
    },
    "basic-ftp": {
      "name": "basic-ftp",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1117726,
          "name": "basic-ftp",
          "dependency": "basic-ftp",
          "title": "basic-ftp allows a malicious FTP server to cause client-side denial of service via unbounded multiline control response buffering",
          "url": "https://github.com/advisories/GHSA-rpmf-866q-6p89",
          "severity": "high",
          "cwe": [
            "CWE-400",
            "CWE-770"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<=5.3.0"
        }
      ],
      "effects": [],
      "range": "<=5.3.0",
      "nodes": [
        "node_modules/basic-ftp"
      ],
      "fixAvailable": true
    },
    "form-data": {
      "name": "form-data",
      "severity": "critical",
      "isDirect": false,
      "via": [
        {
          "source": 1109540,
          "name": "form-data",
          "dependency": "form-data",
          "title": "form-data uses unsafe random function in form-data for choosing boundary",
          "url": "https://github.com/advisories/GHSA-fjxv-7rqg-78g4",
          "severity": "critical",
          "cwe": [
            "CWE-330"
          ],
          "cvss": {
            "score": 0,
            "vectorString": null
          },
          "range": "<2.5.4"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<2.5.4",
      "nodes": [
        "node_modules/form-data"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "ip-address": {
      "name": "ip-address",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1117683,
          "name": "ip-address",
          "dependency": "ip-address",
          "title": "ip-address has XSS in Address6 HTML-emitting methods",
          "url": "https://github.com/advisories/GHSA-v2v4-37r5-5v8g",
          "severity": "moderate",
          "cwe": [
            "CWE-79"
          ],
          "cvss": {
            "score": 0,
            "vectorString": null
          },
          "range": "<=10.1.0"
        }
      ],
      "effects": [],
      "range": "<=10.1.0",
      "nodes": [
        "node_modules/ip-address"
      ],
      "fixAvailable": true
    },
    "mocha": {
      "name": "mocha",
      "severity": "high",
      "isDirect": false,
      "via": [
        "serialize-javascript"
      ],
      "effects": [
        "@wdio/mocha-framework"
      ],
      "range": "8.0.0 - 12.0.0-beta-2",
      "nodes": [
        "node_modules/mocha"
      ],
      "fixAvailable": {
        "name": "@wdio/mocha-framework",
        "version": "6.1.17",
        "isSemVerMajor": true
      }
    },
    "mwbot": {
      "name": "mwbot",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "request"
      ],
      "effects": [
        "wdio-mediawiki"
      ],
      "range": ">=0.1.6",
      "nodes": [
        "node_modules/mwbot"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "qs": {
      "name": "qs",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1113719,
          "name": "qs",
          "dependency": "qs",
          "title": "qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion",
          "url": "https://github.com/advisories/GHSA-6rw7-vpxm-498p",
          "severity": "moderate",
          "cwe": [
            "CWE-20"
          ],
          "cvss": {
            "score": 3.7,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": "<6.14.1"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<6.14.1",
      "nodes": [
        "node_modules/qs"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "request": {
      "name": "request",
      "severity": "critical",
      "isDirect": false,
      "via": [
        {
          "source": 1096727,
          "name": "request",
          "dependency": "request",
          "title": "Server-Side Request Forgery in Request",
          "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 6.1,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
          },
          "range": "<=2.88.2"
        },
        "form-data",
        "qs",
        "tough-cookie"
      ],
      "effects": [
        "mwbot"
      ],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "serialize-javascript": {
      "name": "serialize-javascript",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1113686,
          "name": "serialize-javascript",
          "dependency": "serialize-javascript",
          "title": "Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()",
          "url": "https://github.com/advisories/GHSA-5c6j-r48x-rmvq",
          "severity": "high",
          "cwe": [
            "CWE-96"
          ],
          "cvss": {
            "score": 8.1,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
          },
          "range": "<=7.0.2"
        },
        {
          "source": 1115723,
          "name": "serialize-javascript",
          "dependency": "serialize-javascript",
          "title": "Serialize JavaScript has CPU Exhaustion Denial of Service via crafted array-like objects",
          "url": "https://github.com/advisories/GHSA-qj8w-gfj5-8c6v",
          "severity": "moderate",
          "cwe": [
            "CWE-400",
            "CWE-834"
          ],
          "cvss": {
            "score": 5.9,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<7.0.5"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "<=7.0.4",
      "nodes": [
        "node_modules/serialize-javascript"
      ],
      "fixAvailable": {
        "name": "@wdio/mocha-framework",
        "version": "6.1.17",
        "isSemVerMajor": true
      }
    },
    "tough-cookie": {
      "name": "tough-cookie",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097682,
          "name": "tough-cookie",
          "dependency": "tough-cookie",
          "title": "tough-cookie Prototype Pollution vulnerability",
          "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
          "severity": "moderate",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<4.1.3"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<4.1.3",
      "nodes": [
        "node_modules/tough-cookie"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    },
    "wdio-mediawiki": {
      "name": "wdio-mediawiki",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "mwbot"
      ],
      "effects": [],
      "range": "<=5.1.0",
      "nodes": [
        "node_modules/wdio-mediawiki"
      ],
      "fixAvailable": {
        "name": "wdio-mediawiki",
        "version": "6.5.1",
        "isSemVerMajor": true
      }
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 5,
      "high": 4,
      "critical": 2,
      "total": 11
    },
    "dependencies": {
      "prod": 1,
      "dev": 889,
      "optional": 38,
      "peer": 1,
      "peerOptional": 0,
      "total": 889
    }
  }
}

--- end ---
Attempting to npm audit fix
$ /usr/bin/npm audit fix --dry-run --only=dev --json
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---
{
  "added": 26,
  "removed": 0,
  "changed": 2,
  "audited": 890,
  "funding": 200,
  "audit": {
    "auditReportVersion": 2,
    "vulnerabilities": {
      "@wdio/mocha-framework": {
        "name": "@wdio/mocha-framework",
        "severity": "high",
        "isDirect": true,
        "via": [
          "mocha"
        ],
        "effects": [],
        "range": ">=6.1.19",
        "nodes": [
          "node_modules/@wdio/mocha-framework"
        ],
        "fixAvailable": {
          "name": "@wdio/mocha-framework",
          "version": "6.1.17",
          "isSemVerMajor": true
        }
      },
      "basic-ftp": {
        "name": "basic-ftp",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1117726,
            "name": "basic-ftp",
            "dependency": "basic-ftp",
            "title": "basic-ftp allows a malicious FTP server to cause client-side denial of service via unbounded multiline control response buffering",
            "url": "https://github.com/advisories/GHSA-rpmf-866q-6p89",
            "severity": "high",
            "cwe": [
              "CWE-400",
              "CWE-770"
            ],
            "cvss": {
              "score": 7.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
            },
            "range": "<=5.3.0"
          }
        ],
        "effects": [],
        "range": "<=5.3.0",
        "nodes": [
          ""
        ],
        "fixAvailable": true
      },
      "form-data": {
        "name": "form-data",
        "severity": "critical",
        "isDirect": false,
        "via": [
          {
            "source": 1109540,
            "name": "form-data",
            "dependency": "form-data",
            "title": "form-data uses unsafe random function in form-data for choosing boundary",
            "url": "https://github.com/advisories/GHSA-fjxv-7rqg-78g4",
            "severity": "critical",
            "cwe": [
              "CWE-330"
            ],
            "cvss": {
              "score": 0,
              "vectorString": null
            },
            "range": "<2.5.4"
          }
        ],
        "effects": [
          "request"
        ],
        "range": "<2.5.4",
        "nodes": [
          "node_modules/form-data"
        ],
        "fixAvailable": {
          "name": "wdio-mediawiki",
          "version": "6.5.1",
          "isSemVerMajor": true
        }
      },
      "ip-address": {
        "name": "ip-address",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1117683,
            "name": "ip-address",
            "dependency": "ip-address",
            "title": "ip-address has XSS in Address6 HTML-emitting methods",
            "url": "https://github.com/advisories/GHSA-v2v4-37r5-5v8g",
            "severity": "moderate",
            "cwe": [
              "CWE-79"
            ],
            "cvss": {
              "score": 0,
              "vectorString": null
            },
            "range": "<=10.1.0"
          }
        ],
        "effects": [],
        "range": "<=10.1.0",
        "nodes": [
          ""
        ],
        "fixAvailable": true
      },
      "mocha": {
        "name": "mocha",
        "severity": "high",
        "isDirect": false,
        "via": [
          "serialize-javascript"
        ],
        "effects": [
          "@wdio/mocha-framework"
        ],
        "range": "8.0.0 - 12.0.0-beta-2",
        "nodes": [
          "node_modules/mocha"
        ],
        "fixAvailable": {
          "name": "@wdio/mocha-framework",
          "version": "6.1.17",
          "isSemVerMajor": true
        }
      },
      "mwbot": {
        "name": "mwbot",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "request"
        ],
        "effects": [
          "wdio-mediawiki"
        ],
        "range": ">=0.1.6",
        "nodes": [
          "node_modules/mwbot"
        ],
        "fixAvailable": {
          "name": "wdio-mediawiki",
          "version": "6.5.1",
          "isSemVerMajor": true
        }
      },
      "qs": {
        "name": "qs",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1113719,
            "name": "qs",
            "dependency": "qs",
            "title": "qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion",
            "url": "https://github.com/advisories/GHSA-6rw7-vpxm-498p",
            "severity": "moderate",
            "cwe": [
              "CWE-20"
            ],
            "cvss": {
              "score": 3.7,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
            },
            "range": "<6.14.1"
          }
        ],
        "effects": [
          "request"
        ],
        "range": "<6.14.1",
        "nodes": [
          "node_modules/qs"
        ],
        "fixAvailable": {
          "name": "wdio-mediawiki",
          "version": "6.5.1",
          "isSemVerMajor": true
        }
      },
      "request": {
        "name": "request",
        "severity": "critical",
        "isDirect": false,
        "via": [
          {
            "source": 1096727,
            "name": "request",
            "dependency": "request",
            "title": "Server-Side Request Forgery in Request",
            "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
            "severity": "moderate",
            "cwe": [
              "CWE-918"
            ],
            "cvss": {
              "score": 6.1,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
            },
            "range": "<=2.88.2"
          },
          "form-data",
          "qs",
          "tough-cookie"
        ],
        "effects": [
          "mwbot"
        ],
        "range": "*",
        "nodes": [
          "node_modules/request"
        ],
        "fixAvailable": {
          "name": "wdio-mediawiki",
          "version": "6.5.1",
          "isSemVerMajor": true
        }
      },
      "serialize-javascript": {
        "name": "serialize-javascript",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1113686,
            "name": "serialize-javascript",
            "dependency": "serialize-javascript",
            "title": "Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()",
            "url": "https://github.com/advisories/GHSA-5c6j-r48x-rmvq",
            "severity": "high",
            "cwe": [
              "CWE-96"
            ],
            "cvss": {
              "score": 8.1,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
            },
            "range": "<=7.0.2"
          },
          {
            "source": 1115723,
            "name": "serialize-javascript",
            "dependency": "serialize-javascript",
            "title": "Serialize JavaScript has CPU Exhaustion Denial of Service via crafted array-like objects",
            "url": "https://github.com/advisories/GHSA-qj8w-gfj5-8c6v",
            "severity": "moderate",
            "cwe": [
              "CWE-400",
              "CWE-834"
            ],
            "cvss": {
              "score": 5.9,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
            },
            "range": "<7.0.5"
          }
        ],
        "effects": [
          "mocha"
        ],
        "range": "<=7.0.4",
        "nodes": [
          "node_modules/serialize-javascript"
        ],
        "fixAvailable": {
          "name": "@wdio/mocha-framework",
          "version": "6.1.17",
          "isSemVerMajor": true
        }
      },
      "tough-cookie": {
        "name": "tough-cookie",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1097682,
            "name": "tough-cookie",
            "dependency": "tough-cookie",
            "title": "tough-cookie Prototype Pollution vulnerability",
            "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
            "severity": "moderate",
            "cwe": [
              "CWE-1321"
            ],
            "cvss": {
              "score": 6.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
            },
            "range": "<4.1.3"
          }
        ],
        "effects": [
          "request"
        ],
        "range": "<4.1.3",
        "nodes": [
          "node_modules/tough-cookie"
        ],
        "fixAvailable": {
          "name": "wdio-mediawiki",
          "version": "6.5.1",
          "isSemVerMajor": true
        }
      },
      "wdio-mediawiki": {
        "name": "wdio-mediawiki",
        "severity": "moderate",
        "isDirect": true,
        "via": [
          "mwbot"
        ],
        "effects": [],
        "range": "<=5.1.0",
        "nodes": [
          "node_modules/wdio-mediawiki"
        ],
        "fixAvailable": {
          "name": "wdio-mediawiki",
          "version": "6.5.1",
          "isSemVerMajor": true
        }
      }
    },
    "metadata": {
      "vulnerabilities": {
        "info": 0,
        "low": 0,
        "moderate": 5,
        "high": 4,
        "critical": 2,
        "total": 11
      },
      "dependencies": {
        "prod": 1,
        "dev": 889,
        "optional": 38,
        "peer": 1,
        "peerOptional": 0,
        "total": 889
      }
    }
  }
}

--- end ---
{"added": 26, "removed": 0, "changed": 2, "audited": 890, "funding": 200, "audit": {"auditReportVersion": 2, "vulnerabilities": {"@wdio/mocha-framework": {"name": "@wdio/mocha-framework", "severity": "high", "isDirect": true, "via": ["mocha"], "effects": [], "range": ">=6.1.19", "nodes": ["node_modules/@wdio/mocha-framework"], "fixAvailable": {"name": "@wdio/mocha-framework", "version": "6.1.17", "isSemVerMajor": true}}, "basic-ftp": {"name": "basic-ftp", "severity": "high", "isDirect": false, "via": [{"source": 1117726, "name": "basic-ftp", "dependency": "basic-ftp", "title": "basic-ftp allows a malicious FTP server to cause client-side denial of service via unbounded multiline control response buffering", "url": "https://github.com/advisories/GHSA-rpmf-866q-6p89", "severity": "high", "cwe": ["CWE-400", "CWE-770"], "cvss": {"score": 7.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}, "range": "<=5.3.0"}], "effects": [], "range": "<=5.3.0", "nodes": [""], "fixAvailable": true}, "form-data": {"name": "form-data", "severity": "critical", "isDirect": false, "via": [{"source": 1109540, "name": "form-data", "dependency": "form-data", "title": "form-data uses unsafe random function in form-data for choosing boundary", "url": "https://github.com/advisories/GHSA-fjxv-7rqg-78g4", "severity": "critical", "cwe": ["CWE-330"], "cvss": {"score": 0, "vectorString": null}, "range": "<2.5.4"}], "effects": ["request"], "range": "<2.5.4", "nodes": ["node_modules/form-data"], "fixAvailable": {"name": "wdio-mediawiki", "version": "6.5.1", "isSemVerMajor": true}}, "ip-address": {"name": "ip-address", "severity": "moderate", "isDirect": false, "via": [{"source": 1117683, "name": "ip-address", "dependency": "ip-address", "title": "ip-address has XSS in Address6 HTML-emitting methods", "url": "https://github.com/advisories/GHSA-v2v4-37r5-5v8g", "severity": "moderate", "cwe": ["CWE-79"], "cvss": {"score": 0, "vectorString": null}, "range": "<=10.1.0"}], "effects": [], "range": "<=10.1.0", "nodes": [""], "fixAvailable": true}, "mocha": {"name": "mocha", "severity": "high", "isDirect": false, "via": ["serialize-javascript"], "effects": ["@wdio/mocha-framework"], "range": "8.0.0 - 12.0.0-beta-2", "nodes": ["node_modules/mocha"], "fixAvailable": {"name": "@wdio/mocha-framework", "version": "6.1.17", "isSemVerMajor": true}}, "mwbot": {"name": "mwbot", "severity": "moderate", "isDirect": false, "via": ["request"], "effects": ["wdio-mediawiki"], "range": ">=0.1.6", "nodes": ["node_modules/mwbot"], "fixAvailable": {"name": "wdio-mediawiki", "version": "6.5.1", "isSemVerMajor": true}}, "qs": {"name": "qs", "severity": "moderate", "isDirect": false, "via": [{"source": 1113719, "name": "qs", "dependency": "qs", "title": "qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion", "url": "https://github.com/advisories/GHSA-6rw7-vpxm-498p", "severity": "moderate", "cwe": ["CWE-20"], "cvss": {"score": 3.7, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"}, "range": "<6.14.1"}], "effects": ["request"], "range": "<6.14.1", "nodes": ["node_modules/qs"], "fixAvailable": {"name": "wdio-mediawiki", "version": "6.5.1", "isSemVerMajor": true}}, "request": {"name": "request", "severity": "critical", "isDirect": false, "via": [{"source": 1096727, "name": "request", "dependency": "request", "title": "Server-Side Request Forgery in Request", "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6", "severity": "moderate", "cwe": ["CWE-918"], "cvss": {"score": 6.1, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"}, "range": "<=2.88.2"}, "form-data", "qs", "tough-cookie"], "effects": ["mwbot"], "range": "*", "nodes": ["node_modules/request"], "fixAvailable": {"name": "wdio-mediawiki", "version": "6.5.1", "isSemVerMajor": true}}, "serialize-javascript": {"name": "serialize-javascript", "severity": "high", "isDirect": false, "via": [{"source": 1113686, "name": "serialize-javascript", "dependency": "serialize-javascript", "title": "Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()", "url": "https://github.com/advisories/GHSA-5c6j-r48x-rmvq", "severity": "high", "cwe": ["CWE-96"], "cvss": {"score": 8.1, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"}, "range": "<=7.0.2"}, {"source": 1115723, "name": "serialize-javascript", "dependency": "serialize-javascript", "title": "Serialize JavaScript has CPU Exhaustion Denial of Service via crafted array-like objects", "url": "https://github.com/advisories/GHSA-qj8w-gfj5-8c6v", "severity": "moderate", "cwe": ["CWE-400", "CWE-834"], "cvss": {"score": 5.9, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"}, "range": "<7.0.5"}], "effects": ["mocha"], "range": "<=7.0.4", "nodes": ["node_modules/serialize-javascript"], "fixAvailable": {"name": "@wdio/mocha-framework", "version": "6.1.17", "isSemVerMajor": true}}, "tough-cookie": {"name": "tough-cookie", "severity": "moderate", "isDirect": false, "via": [{"source": 1097682, "name": "tough-cookie", "dependency": "tough-cookie", "title": "tough-cookie Prototype Pollution vulnerability", "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3", "severity": "moderate", "cwe": ["CWE-1321"], "cvss": {"score": 6.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"}, "range": "<4.1.3"}], "effects": ["request"], "range": "<4.1.3", "nodes": ["node_modules/tough-cookie"], "fixAvailable": {"name": "wdio-mediawiki", "version": "6.5.1", "isSemVerMajor": true}}, "wdio-mediawiki": {"name": "wdio-mediawiki", "severity": "moderate", "isDirect": true, "via": ["mwbot"], "effects": [], "range": "<=5.1.0", "nodes": ["node_modules/wdio-mediawiki"], "fixAvailable": {"name": "wdio-mediawiki", "version": "6.5.1", "isSemVerMajor": true}}}, "metadata": {"vulnerabilities": {"info": 0, "low": 0, "moderate": 5, "high": 4, "critical": 2, "total": 11}, "dependencies": {"prod": 1, "dev": 889, "optional": 38, "peer": 1, "peerOptional": 0, "total": 889}}}}
$ /usr/bin/npm audit fix --only=dev
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---

changed 2 packages, and audited 864 packages in 3s

200 packages are looking for funding
  run `npm fund` for details

# npm audit report

form-data  <2.5.4
Severity: critical
form-data uses unsafe random function in form-data for choosing boundary - https://github.com/advisories/GHSA-fjxv-7rqg-78g4
fix available via `npm audit fix --force`
Will install wdio-mediawiki@6.5.1, which is a breaking change
node_modules/form-data
  request  *
  Depends on vulnerable versions of form-data
  Depends on vulnerable versions of qs
  Depends on vulnerable versions of tough-cookie
  node_modules/request
    mwbot  >=0.1.6
    Depends on vulnerable versions of request
    node_modules/mwbot
      wdio-mediawiki  <=5.1.0
      Depends on vulnerable versions of mwbot
      node_modules/wdio-mediawiki

qs  <6.14.1
Severity: moderate
qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion - https://github.com/advisories/GHSA-6rw7-vpxm-498p
fix available via `npm audit fix --force`
Will install wdio-mediawiki@6.5.1, which is a breaking change
node_modules/qs


serialize-javascript  <=7.0.4
Severity: high
Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString() - https://github.com/advisories/GHSA-5c6j-r48x-rmvq
Serialize JavaScript has CPU Exhaustion Denial of Service via crafted array-like objects - https://github.com/advisories/GHSA-qj8w-gfj5-8c6v
fix available via `npm audit fix --force`
Will install @wdio/mocha-framework@6.1.17, which is a breaking change
node_modules/serialize-javascript
  mocha  8.0.0 - 12.0.0-beta-2
  Depends on vulnerable versions of serialize-javascript
  node_modules/mocha
    @wdio/mocha-framework  >=6.1.19
    Depends on vulnerable versions of mocha
    node_modules/@wdio/mocha-framework

tough-cookie  <4.1.3
Severity: moderate
tough-cookie Prototype Pollution vulnerability - https://github.com/advisories/GHSA-72xf-g2v4-qvf3
fix available via `npm audit fix --force`
Will install wdio-mediawiki@6.5.1, which is a breaking change
node_modules/tough-cookie

9 vulnerabilities (4 moderate, 3 high, 2 critical)

To address all issues (including breaking changes), run:
  npm audit fix --force

--- end ---
Verifying that tests still pass
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated node-domexception@1.0.0: Use your platform's native DOMException instead
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
--- stdout ---

added 863 packages, and audited 864 packages in 15s

200 packages are looking for funding
  run `npm fund` for details

9 vulnerabilities (4 moderate, 3 high, 2 critical)

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> test
> grunt test

Running "eslint:all" (eslint) task

/src/repo/resources/ext.centralNotice.display/index.js
  265:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  297:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  842:4  warning  Prefer .then to .fail  no-jquery/no-done-fail
  842:4  warning  Prefer .then to .done  no-jquery/no-done-fail
  852:4  warning  Prefer .then to .fail  no-jquery/no-done-fail
  852:4  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/ext.centralNotice.kvStore/kvStore.js
   83:18  warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   87:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   89:9   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
   91:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  198:4   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  202:9   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  230:15  warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  259:6   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage
  298:4   warning  Avoid direct access to localStorage. Use mw.storage instead  mediawiki/no-storage

/src/repo/resources/ext.centralNotice.startUp/index.js
  44:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
   33:17  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   38:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   61:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   65:9   warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   69:7   warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
   84:28  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie
   90:14  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie
  109:18  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
  109:35  warning  Avoid direct access to localStorage. Use mw.storage instead    mediawiki/no-storage
  117:18  warning  Avoid direct access to document.cookie. Use mw.cookie instead  mediawiki/no-cookie

/src/repo/resources/infrastructure/bannereditor.js
   50:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
   50:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  117:3  warning  Prefer .then to .fail  no-jquery/no-done-fail

/src/repo/resources/infrastructure/campaignManager.js
  809:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
   808:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
   808:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail
  1251:20  warning  OOUI button has no label. Even icon-only buttons should set a label with invisibleLabel set to true  mediawiki/no-unlabeled-buttonwidget
  1322:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
  1322:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail
  1358:5   warning  Prefer .then to .done                                                                                no-jquery/no-done-fail
  1358:5   warning  Prefer .then to .fail                                                                                no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
  296:4  warning  Prefer .then to .done  no-jquery/no-done-fail
  367:4  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.geoIP.js
  160:2  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.impressionDiet.js
  180:1  warning  Missing JSDoc @param "c" type  jsdoc/require-param-type

/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js
  439:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  479:3  warning  Prefer .then to .done  no-jquery/no-done-fail

/src/repo/tests/qunit/subscribing/ext.centralNotice.geoIP.tests.js
  49:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  49:3  warning  Prefer .then to .done  no-jquery/no-done-fail
  63:3  warning  Prefer .then to .fail  no-jquery/no-done-fail
  63:3  warning  Prefer .then to .done  no-jquery/no-done-fail

✖ 47 problems (0 errors, 47 warnings)


Running "banana:CentralNotice" (banana) task
>> 2 message directories checked.

Running "stylelint:all" (stylelint) task
>> Linted 7 files without errors

Done.

--- end ---
{"1117726": {"source": 1117726, "name": "basic-ftp", "dependency": "basic-ftp", "title": "basic-ftp allows a malicious FTP server to cause client-side denial of service via unbounded multiline control response buffering", "url": "https://github.com/advisories/GHSA-rpmf-866q-6p89", "severity": "high", "cwe": ["CWE-400", "CWE-770"], "cvss": {"score": 7.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}, "range": "<=5.3.0"}}
Upgrading n:basic-ftp from 5.3.0 -> 5.3.1
{"1117683": {"source": 1117683, "name": "ip-address", "dependency": "ip-address", "title": "ip-address has XSS in Address6 HTML-emitting methods", "url": "https://github.com/advisories/GHSA-v2v4-37r5-5v8g", "severity": "moderate", "cwe": ["CWE-79"], "cvss": {"score": 0, "vectorString": null}, "range": "<=10.1.0"}}
Upgrading n:ip-address from 10.0.1 -> 10.2.0
$ package-lock-lint /src/repo/package-lock.json
--- stdout ---
Checking /src/repo/package-lock.json

--- end ---
build: Updating dependencies

composer:
* mediawiki/mediawiki-codesniffer: 50.0.0 → 51.0.0

npm:
* eslint-config-wikimedia: 0.32.3 → 0.32.4
* basic-ftp: 5.3.0 → 5.3.1
  * https://github.com/advisories/GHSA-rpmf-866q-6p89
* ip-address: 10.0.1 → 10.2.0
  * https://github.com/advisories/GHSA-v2v4-37r5-5v8g


$ git add .
--- stdout ---

--- end ---
$ git commit -F /tmp/tmpsjt6bhj_
--- stdout ---
[master 56a439c] build: Updating dependencies
 5 files changed, 461 insertions(+), 356 deletions(-)

--- end ---
$ git format-patch HEAD~1 --stdout
--- stdout ---
From 56a439c678376eb4c6fe0ad2283188ab26cea4f7 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Thu, 7 May 2026 02:55:59 +0000
Subject: [PATCH] build: Updating dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

composer:
* mediawiki/mediawiki-codesniffer: 50.0.0 → 51.0.0

npm:
* eslint-config-wikimedia: 0.32.3 → 0.32.4
* basic-ftp: 5.3.0 → 5.3.1
  * https://github.com/advisories/GHSA-rpmf-866q-6p89
* ip-address: 10.0.1 → 10.2.0
  * https://github.com/advisories/GHSA-v2v4-37r5-5v8g

Change-Id: Ibf16f23dd723f5187d7a9abf4475559d9cb26501
---
 composer.json                                 |   2 +-
 package-lock.json                             | 805 ++++++++++--------
 package.json                                  |   2 +-
 resources/infrastructure/campaignManager.js   |   6 +-
 .../ext.centralNotice.impressionDiet.js       |   2 +
 5 files changed, 461 insertions(+), 356 deletions(-)

diff --git a/composer.json b/composer.json
index baecf45..7d0269c 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
 {
 	"require-dev": {
-		"mediawiki/mediawiki-codesniffer": "50.0.0",
+		"mediawiki/mediawiki-codesniffer": "51.0.0",
 		"mediawiki/mediawiki-phan-config": "0.20.0",
 		"mediawiki/minus-x": "2.0.1",
 		"php-parallel-lint/php-console-highlighter": "1.0.0",
diff --git a/package-lock.json b/package-lock.json
index ffd21c5..152eac7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
 				"@wdio/local-runner": "9.19.2",
 				"@wdio/mocha-framework": "9.19.2",
 				"@wdio/spec-reporter": "9.19.2",
-				"eslint-config-wikimedia": "0.32.3",
+				"eslint-config-wikimedia": "0.32.4",
 				"grunt": "1.6.2",
 				"grunt-banana-checker": "0.13.0",
 				"grunt-eslint": "24.3.0",
@@ -128,19 +128,32 @@
 			}
 		},
 		"node_modules/@es-joy/jsdoccomment": {
-			"version": "0.76.0",
-			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz",
-			"integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==",
+			"version": "0.86.0",
+			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz",
+			"integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==",
 			"dev": true,
 			"dependencies": {
 				"@types/estree": "^1.0.8",
-				"@typescript-eslint/types": "^8.46.0",
-				"comment-parser": "1.4.1",
-				"esquery": "^1.6.0",
-				"jsdoc-type-pratt-parser": "~6.10.0"
+				"@typescript-eslint/types": "^8.58.0",
+				"comment-parser": "1.4.6",
+				"esquery": "^1.7.0",
+				"jsdoc-type-pratt-parser": "~7.2.0"
 			},
 			"engines": {
-				"node": ">=20.11.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
+			}
+		},
+		"node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": {
+			"version": "8.59.2",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
+			"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/typescript-eslint"
 			}
 		},
 		"node_modules/@es-joy/resolve.exports": {
@@ -569,9 +582,9 @@
 			}
 		},
 		"node_modules/@eslint-community/eslint-utils": {
-			"version": "4.7.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
-			"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+			"version": "4.9.1",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+			"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
 			"dev": true,
 			"dependencies": {
 				"eslint-visitor-keys": "^3.4.3"
@@ -1186,9 +1199,9 @@
 			"dev": true
 		},
 		"node_modules/@mdn/browser-compat-data": {
-			"version": "5.7.6",
-			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
-			"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+			"version": "6.1.5",
+			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-6.1.5.tgz",
+			"integrity": "sha512-PzdZZzRhcXvKB0begee28n5lvwAcinGKYuLZOVxHAZm+n7y01ddEGfdS1ZXRuVcV+ndG6mSEAE8vgudom5UjYg==",
 			"dev": true
 		},
 		"node_modules/@nodable/entities": {
@@ -1437,9 +1450,9 @@
 			"dev": true
 		},
 		"node_modules/@types/estree": {
-			"version": "1.0.8",
-			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
-			"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+			"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
 			"dev": true
 		},
 		"node_modules/@types/istanbul-lib-coverage": {
@@ -1546,20 +1559,19 @@
 			}
 		},
 		"node_modules/@typescript-eslint/eslint-plugin": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
-			"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
+			"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/regexpp": "^4.10.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/type-utils": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"graphemer": "^1.4.0",
-				"ignore": "^7.0.0",
+				"@eslint-community/regexpp": "^4.12.2",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/type-utils": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"ignore": "^7.0.5",
 				"natural-compare": "^1.4.0",
-				"ts-api-utils": "^2.1.0"
+				"ts-api-utils": "^2.4.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1569,7 +1581,7 @@
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"@typescript-eslint/parser": "^8.46.0",
+				"@typescript-eslint/parser": "^8.54.0",
 				"eslint": "^8.57.0 || ^9.0.0",
 				"typescript": ">=4.8.4 <6.0.0"
 			}
@@ -1584,16 +1596,16 @@
 			}
 		},
 		"node_modules/@typescript-eslint/parser": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
-			"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
+			"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1608,14 +1620,14 @@
 			}
 		},
 		"node_modules/@typescript-eslint/project-service": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
-			"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
+			"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/tsconfig-utils": "^8.46.0",
-				"@typescript-eslint/types": "^8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/tsconfig-utils": "^8.54.0",
+				"@typescript-eslint/types": "^8.54.0",
+				"debug": "^4.4.3"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1629,13 +1641,13 @@
 			}
 		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
-			"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
+			"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1646,9 +1658,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/tsconfig-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
-			"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
+			"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1662,16 +1674,16 @@
 			}
 		},
 		"node_modules/@typescript-eslint/type-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
-			"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
+			"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"debug": "^4.3.4",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"debug": "^4.4.3",
+				"ts-api-utils": "^2.4.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1686,9 +1698,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
-			"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
+			"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
 			"dev": true,
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1699,21 +1711,20 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
-			"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
+			"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/project-service": "8.46.0",
-				"@typescript-eslint/tsconfig-utils": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4",
-				"fast-glob": "^3.3.2",
-				"is-glob": "^4.0.3",
-				"minimatch": "^9.0.4",
-				"semver": "^7.6.0",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/project-service": "8.54.0",
+				"@typescript-eslint/tsconfig-utils": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3",
+				"minimatch": "^9.0.5",
+				"semver": "^7.7.3",
+				"tinyglobby": "^0.2.15",
+				"ts-api-utils": "^2.4.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1727,9 +1738,9 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
-			"version": "2.0.3",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
-			"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+			"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
 			"dev": true,
 			"dependencies": {
 				"balanced-match": "^1.0.0"
@@ -1751,15 +1762,15 @@
 			}
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
-			"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
+			"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/eslint-utils": "^4.7.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0"
+				"@eslint-community/eslint-utils": "^4.9.1",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0"
 			},
 			"engines": {
 				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1774,12 +1785,12 @@
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
-			"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
+			"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "8.46.0",
+				"@typescript-eslint/types": "8.54.0",
 				"eslint-visitor-keys": "^4.2.1"
 			},
 			"engines": {
@@ -2529,9 +2540,9 @@
 			}
 		},
 		"node_modules/acorn": {
-			"version": "8.15.0",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
-			"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+			"version": "8.16.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+			"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -2840,6 +2851,12 @@
 				"@mdn/browser-compat-data": "^5.6.19"
 			}
 		},
+		"node_modules/ast-metadata-inferer/node_modules/@mdn/browser-compat-data": {
+			"version": "5.7.6",
+			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
+			"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+			"dev": true
+		},
 		"node_modules/ast-types": {
 			"version": "0.13.4",
 			"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
@@ -3033,9 +3050,9 @@
 			}
 		},
 		"node_modules/basic-ftp": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz",
-			"integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==",
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+			"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
 			"dev": true,
 			"engines": {
 				"node": ">=10.0.0"
@@ -3494,9 +3511,9 @@
 			}
 		},
 		"node_modules/comment-parser": {
-			"version": "1.4.1",
-			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
-			"integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+			"version": "1.4.6",
+			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz",
+			"integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==",
 			"dev": true,
 			"engines": {
 				"node": ">= 12.0.0"
@@ -4514,13 +4531,13 @@
 			}
 		},
 		"node_modules/enhanced-resolve": {
-			"version": "5.18.3",
-			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
-			"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+			"version": "5.21.0",
+			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
+			"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
 			"dev": true,
 			"dependencies": {
 				"graceful-fs": "^4.2.4",
-				"tapable": "^2.2.0"
+				"tapable": "^2.3.3"
 			},
 			"engines": {
 				"node": ">=10.13.0"
@@ -4721,46 +4738,47 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.32.3",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.3.tgz",
-			"integrity": "sha512-Ekz2/ozpCCjQl3VbC6dW7ChqoW7FRilLDxmJ+FJOZhIxxzZSZR5QqQOAGWSZAlG1ONkZbYV/TPwGLWZcrNxyaA==",
+			"version": "0.32.4",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.4.tgz",
+			"integrity": "sha512-zcHJYss2vo8HK5PzkFuaV9mzaSGRuhA+jFGoQ4rNIwWz0usZsuQ2LYpkKxrbCVX1CbV0PzG+jJ6p0cLI+G37JQ==",
 			"dev": true,
 			"dependencies": {
 				"@stylistic/eslint-plugin": "^3.1.0",
-				"@typescript-eslint/eslint-plugin": "8.46.0",
-				"@typescript-eslint/parser": "8.46.0",
+				"@typescript-eslint/eslint-plugin": "8.54.0",
+				"@typescript-eslint/parser": "8.54.0",
 				"browserslist-config-wikimedia": "^0.7.0",
-				"eslint": "^8.57.0",
-				"eslint-plugin-compat": "^6.0.2",
+				"eslint-plugin-compat": "^6.1.0",
 				"eslint-plugin-es-x": "^8.7.0",
-				"eslint-plugin-jest": "^29.0.1",
-				"eslint-plugin-jsdoc": "61.3.0",
+				"eslint-plugin-jest": "^29.12.2",
+				"eslint-plugin-jsdoc": "^62.9.0",
 				"eslint-plugin-json-es": "^1.6.0",
-				"eslint-plugin-mediawiki": "^0.8.2",
+				"eslint-plugin-mediawiki": "^0.8.3",
 				"eslint-plugin-mocha": "^10.5.0",
-				"eslint-plugin-n": "^17.23.1",
-				"eslint-plugin-no-jquery": "^3.1.1",
-				"eslint-plugin-qunit": "^8.2.5",
-				"eslint-plugin-security": "^3.0.1",
+				"eslint-plugin-n": "^17.24.0",
+				"eslint-plugin-no-jquery": "^4.0.0",
+				"eslint-plugin-qunit": "^8.2.6",
+				"eslint-plugin-security": "^4.0.0",
 				"eslint-plugin-unicorn": "^56.0.1",
 				"eslint-plugin-vue": "^9.33.0",
-				"eslint-plugin-wdio": "^9.16.2",
+				"eslint-plugin-wdio": "9.23.0",
 				"eslint-plugin-yml": "^1.19.0"
 			},
 			"engines": {
 				"node": ">=20 <25"
+			},
+			"peerDependencies": {
+				"eslint": "^8.57.0"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
-			"version": "6.0.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.0.2.tgz",
-			"integrity": "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA==",
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.2.1.tgz",
+			"integrity": "sha512-gLKqUH+lQcCL+HzsROUjBDvakc5Zaga51Y4ZAkPCXc41pzKBfyluqTr2j8zOx8QQQb7zyglu1LVoL5aSNWf2SQ==",
 			"dev": true,
 			"dependencies": {
-				"@mdn/browser-compat-data": "^5.5.35",
+				"@mdn/browser-compat-data": "^6.1.1",
 				"ast-metadata-inferer": "^0.8.1",
-				"browserslist": "^4.24.2",
-				"caniuse-lite": "^1.0.30001687",
+				"browserslist": "^4.25.2",
 				"find-up": "^5.0.0",
 				"globals": "^15.7.0",
 				"lodash.memoize": "^4.1.2",
@@ -4770,7 +4788,7 @@
 				"node": ">=18.x"
 			},
 			"peerDependencies": {
-				"eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
+				"eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-compat/node_modules/globals": {
@@ -4836,57 +4854,57 @@
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc": {
-			"version": "61.3.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.3.0.tgz",
-			"integrity": "sha512-E4m/5J5lrasd63Z74q4CCZ4PFnywnnrcvA7zZ98802NPhrZKKTp5NH+XAT+afcjXp2ps2/OQF5gPSWCT2XFCJg==",
+			"version": "62.9.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz",
+			"integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==",
 			"dev": true,
 			"dependencies": {
-				"@es-joy/jsdoccomment": "~0.76.0",
+				"@es-joy/jsdoccomment": "~0.86.0",
 				"@es-joy/resolve.exports": "1.2.0",
 				"are-docs-informative": "^0.0.2",
-				"comment-parser": "1.4.1",
+				"comment-parser": "1.4.6",
 				"debug": "^4.4.3",
 				"escape-string-regexp": "^4.0.0",
-				"espree": "^10.4.0",
-				"esquery": "^1.6.0",
+				"espree": "^11.2.0",
+				"esquery": "^1.7.0",
 				"html-entities": "^2.6.0",
 				"object-deep-merge": "^2.0.0",
 				"parse-imports-exports": "^0.2.4",
-				"semver": "^7.7.3",
+				"semver": "^7.7.4",
 				"spdx-expression-parse": "^4.0.0",
 				"to-valid-identifier": "^1.0.0"
 			},
 			"engines": {
-				"node": ">=20.11.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
 			},
 			"peerDependencies": {
-				"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+				"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": {
-			"version": "4.2.1",
-			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
-			"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+			"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
 			"dev": true,
 			"engines": {
-				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
 			},
 			"funding": {
 				"url": "https://opencollective.com/eslint"
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc/node_modules/espree": {
-			"version": "10.4.0",
-			"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
-			"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+			"version": "11.2.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+			"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
 			"dev": true,
 			"dependencies": {
-				"acorn": "^8.15.0",
+				"acorn": "^8.16.0",
 				"acorn-jsx": "^5.3.2",
-				"eslint-visitor-keys": "^4.2.1"
+				"eslint-visitor-keys": "^5.0.1"
 			},
 			"engines": {
-				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+				"node": "^20.19.0 || ^22.13.0 || >=24"
 			},
 			"funding": {
 				"url": "https://opencollective.com/eslint"
@@ -4916,9 +4934,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-mediawiki": {
-			"version": "0.8.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.2.tgz",
-			"integrity": "sha512-ydYrpkzm8IVVDQA96QPF3HnFd2xjkIEh7gixD2gvOqUbUZF0p36LtpWXOFAlPWAvHLePWbNNTD5ovd3d4hEtog==",
+			"version": "0.8.3",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.3.tgz",
+			"integrity": "sha512-RQKZd40C1taMDk5N9+aFLEBGBB95RNG7Gc54EsJ8pHsJu8//nIdpxNFWPtQz6RNxz6pZUXBnMCxzkMOLM3Mm1w==",
 			"dev": true,
 			"dependencies": {
 				"upath": "^2.0.1"
@@ -4945,9 +4963,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-n": {
-			"version": "17.23.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz",
-			"integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==",
+			"version": "17.24.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz",
+			"integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.5.0",
@@ -5019,31 +5037,34 @@
 			}
 		},
 		"node_modules/eslint-plugin-no-jquery": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.1.1.tgz",
-			"integrity": "sha512-LTLO3jH/Tjr1pmxCEqtV6qmt+OChv8La4fwgG470JRpgxyFF4NOzoC9CRy92GIWD3Yjl0qLEgPmD2FLQWcNEjg==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-4.0.0.tgz",
+			"integrity": "sha512-ZR631D3qIQfgjKOAcgvYa5cB8xdTvFXAD5MbK5x5WltLSwFxmGnoaTXNtnptFU7py07ALrIe5dZRYncu4RD/Ug==",
 			"dev": true,
 			"peerDependencies": {
-				"eslint": ">=8.0.0"
+				"eslint": ">=8.0.0 <9.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-qunit": {
-			"version": "8.2.5",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.5.tgz",
-			"integrity": "sha512-qr7RJCYImKQjB+39q4q46i1l7p1V3joHzBE5CAYfxn5tfVFjrnjn/tw7q/kDyweU9kAIcLul0Dx/KWVUCb3BgA==",
+			"version": "8.2.6",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.6.tgz",
+			"integrity": "sha512-S1jC/DIW9J8VtNX4uG1vlf5FZVrfQFlcuiYmvTHR2IICUhubHqpWA5o+qS1tujh+81Gs39omKV2D4OXfbSJE5g==",
 			"dev": true,
 			"dependencies": {
-				"eslint-utils": "^3.0.0",
+				"@eslint-community/eslint-utils": "^4.4.0",
 				"requireindex": "^1.2.0"
 			},
 			"engines": {
 				"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
+			},
+			"peerDependencies": {
+				"eslint": ">=8.38.0"
 			}
 		},
 		"node_modules/eslint-plugin-security": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz",
-			"integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz",
+			"integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==",
 			"dev": true,
 			"dependencies": {
 				"safe-regex": "^2.1.1"
@@ -5123,9 +5144,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-wdio": {
-			"version": "9.16.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.16.2.tgz",
-			"integrity": "sha512-qkqsPgxN70OnUPWMjmzJbSbvm2+Q087JIGss53/OFI4Y46xKlV5VLhLiYealaAibAiXmnfWKd0tERjZAzVL87A==",
+			"version": "9.23.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.23.0.tgz",
+			"integrity": "sha512-8tcpupzp2Qmv+uSfhzeHi42LVA9PyjkpMBPclSIkPxBfXpj4fMrejwAHu1PROh1OmJN1VQcGQUTWvSzyRcV2vA==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.20.0"
@@ -5252,9 +5273,9 @@
 			}
 		},
 		"node_modules/esquery": {
-			"version": "1.6.0",
-			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
-			"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+			"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
 			"dev": true,
 			"dependencies": {
 				"estraverse": "^5.1.0"
@@ -6815,9 +6836,9 @@
 			"dev": true
 		},
 		"node_modules/ip-address": {
-			"version": "10.0.1",
-			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
-			"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+			"version": "10.2.0",
+			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+			"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
 			"dev": true,
 			"engines": {
 				"node": ">= 12"
@@ -7199,9 +7220,9 @@
 			"dev": true
 		},
 		"node_modules/jsdoc-type-pratt-parser": {
-			"version": "6.10.0",
-			"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz",
-			"integrity": "sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==",
+			"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,
 			"engines": {
 				"node": ">=20.0.0"
@@ -10295,9 +10316,9 @@
 			"dev": true
 		},
 		"node_modules/tapable": {
-			"version": "2.3.0",
-			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
-			"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+			"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
 			"dev": true,
 			"engines": {
 				"node": ">=6"
@@ -10347,6 +10368,51 @@
 			"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
 			"dev": true
 		},
+		"node_modules/tinyglobby": {
+			"version": "0.2.16",
+			"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+			"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+			"dev": true,
+			"dependencies": {
+				"fdir": "^6.5.0",
+				"picomatch": "^4.0.4"
+			},
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/SuperchupuDev"
+			}
+		},
+		"node_modules/tinyglobby/node_modules/fdir": {
+			"version": "6.5.0",
+			"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+			"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+			"dev": true,
+			"engines": {
+				"node": ">=12.0.0"
+			},
+			"peerDependencies": {
+				"picomatch": "^3 || ^4"
+			},
+			"peerDependenciesMeta": {
+				"picomatch": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/tinyglobby/node_modules/picomatch": {
+			"version": "4.0.4",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+			"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+			"dev": true,
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/jonschlinkert"
+			}
+		},
 		"node_modules/tinyrainbow": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
@@ -10398,9 +10464,9 @@
 			}
 		},
 		"node_modules/ts-api-utils": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
-			"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+			"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
 			"dev": true,
 			"engines": {
 				"node": ">=18.12"
@@ -11234,16 +11300,24 @@
 			"dev": true
 		},
 		"@es-joy/jsdoccomment": {
-			"version": "0.76.0",
-			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz",
-			"integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==",
+			"version": "0.86.0",
+			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz",
+			"integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==",
 			"dev": true,
 			"requires": {
 				"@types/estree": "^1.0.8",
-				"@typescript-eslint/types": "^8.46.0",
-				"comment-parser": "1.4.1",
-				"esquery": "^1.6.0",
-				"jsdoc-type-pratt-parser": "~6.10.0"
+				"@typescript-eslint/types": "^8.58.0",
+				"comment-parser": "1.4.6",
+				"esquery": "^1.7.0",
+				"jsdoc-type-pratt-parser": "~7.2.0"
+			},
+			"dependencies": {
+				"@typescript-eslint/types": {
+					"version": "8.59.2",
+					"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz",
+					"integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==",
+					"dev": true
+				}
 			}
 		},
 		"@es-joy/resolve.exports": {
@@ -11435,9 +11509,9 @@
 			"optional": true
 		},
 		"@eslint-community/eslint-utils": {
-			"version": "4.7.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
-			"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+			"version": "4.9.1",
+			"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+			"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
 			"dev": true,
 			"requires": {
 				"eslint-visitor-keys": "^3.4.3"
@@ -11810,9 +11884,9 @@
 			"dev": true
 		},
 		"@mdn/browser-compat-data": {
-			"version": "5.7.6",
-			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
-			"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+			"version": "6.1.5",
+			"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-6.1.5.tgz",
+			"integrity": "sha512-PzdZZzRhcXvKB0begee28n5lvwAcinGKYuLZOVxHAZm+n7y01ddEGfdS1ZXRuVcV+ndG6mSEAE8vgudom5UjYg==",
 			"dev": true
 		},
 		"@nodable/entities": {
@@ -11980,9 +12054,9 @@
 			"dev": true
 		},
 		"@types/estree": {
-			"version": "1.0.8",
-			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
-			"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+			"version": "1.0.9",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+			"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
 			"dev": true
 		},
 		"@types/istanbul-lib-coverage": {
@@ -12089,20 +12163,19 @@
 			}
 		},
 		"@typescript-eslint/eslint-plugin": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
-			"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
+			"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
 			"dev": true,
 			"requires": {
-				"@eslint-community/regexpp": "^4.10.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/type-utils": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"graphemer": "^1.4.0",
-				"ignore": "^7.0.0",
+				"@eslint-community/regexpp": "^4.12.2",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/type-utils": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"ignore": "^7.0.5",
 				"natural-compare": "^1.4.0",
-				"ts-api-utils": "^2.1.0"
+				"ts-api-utils": "^2.4.0"
 			},
 			"dependencies": {
 				"ignore": {
@@ -12114,87 +12187,86 @@
 			}
 		},
 		"@typescript-eslint/parser": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
-			"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
+			"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3"
 			}
 		},
 		"@typescript-eslint/project-service": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
-			"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
+			"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/tsconfig-utils": "^8.46.0",
-				"@typescript-eslint/types": "^8.46.0",
-				"debug": "^4.3.4"
+				"@typescript-eslint/tsconfig-utils": "^8.54.0",
+				"@typescript-eslint/types": "^8.54.0",
+				"debug": "^4.4.3"
 			}
 		},
 		"@typescript-eslint/scope-manager": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
-			"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
+			"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0"
 			}
 		},
 		"@typescript-eslint/tsconfig-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
-			"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
+			"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
 			"dev": true,
 			"requires": {}
 		},
 		"@typescript-eslint/type-utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
-			"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
+			"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0",
-				"@typescript-eslint/utils": "8.46.0",
-				"debug": "^4.3.4",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0",
+				"@typescript-eslint/utils": "8.54.0",
+				"debug": "^4.4.3",
+				"ts-api-utils": "^2.4.0"
 			}
 		},
 		"@typescript-eslint/types": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
-			"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
+			"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
 			"dev": true
 		},
 		"@typescript-eslint/typescript-estree": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
-			"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
+			"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/project-service": "8.46.0",
-				"@typescript-eslint/tsconfig-utils": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/visitor-keys": "8.46.0",
-				"debug": "^4.3.4",
-				"fast-glob": "^3.3.2",
-				"is-glob": "^4.0.3",
-				"minimatch": "^9.0.4",
-				"semver": "^7.6.0",
-				"ts-api-utils": "^2.1.0"
+				"@typescript-eslint/project-service": "8.54.0",
+				"@typescript-eslint/tsconfig-utils": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/visitor-keys": "8.54.0",
+				"debug": "^4.4.3",
+				"minimatch": "^9.0.5",
+				"semver": "^7.7.3",
+				"tinyglobby": "^0.2.15",
+				"ts-api-utils": "^2.4.0"
 			},
 			"dependencies": {
 				"brace-expansion": {
-					"version": "2.0.3",
-					"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
-					"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
+					"version": "2.1.0",
+					"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+					"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
 					"dev": true,
 					"requires": {
 						"balanced-match": "^1.0.0"
@@ -12212,24 +12284,24 @@
 			}
 		},
 		"@typescript-eslint/utils": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
-			"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
+			"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
 			"dev": true,
 			"requires": {
-				"@eslint-community/eslint-utils": "^4.7.0",
-				"@typescript-eslint/scope-manager": "8.46.0",
-				"@typescript-eslint/types": "8.46.0",
-				"@typescript-eslint/typescript-estree": "8.46.0"
+				"@eslint-community/eslint-utils": "^4.9.1",
+				"@typescript-eslint/scope-manager": "8.54.0",
+				"@typescript-eslint/types": "8.54.0",
+				"@typescript-eslint/typescript-estree": "8.54.0"
 			}
 		},
 		"@typescript-eslint/visitor-keys": {
-			"version": "8.46.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
-			"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
+			"version": "8.54.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
+			"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
 			"dev": true,
 			"requires": {
-				"@typescript-eslint/types": "8.46.0",
+				"@typescript-eslint/types": "8.54.0",
 				"eslint-visitor-keys": "^4.2.1"
 			},
 			"dependencies": {
@@ -12754,9 +12826,9 @@
 			}
 		},
 		"acorn": {
-			"version": "8.15.0",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
-			"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+			"version": "8.16.0",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+			"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
 			"dev": true
 		},
 		"acorn-jsx": {
@@ -12983,6 +13055,14 @@
 			"dev": true,
 			"requires": {
 				"@mdn/browser-compat-data": "^5.6.19"
+			},
+			"dependencies": {
+				"@mdn/browser-compat-data": {
+					"version": "5.7.6",
+					"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz",
+					"integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==",
+					"dev": true
+				}
 			}
 		},
 		"ast-types": {
@@ -13113,9 +13193,9 @@
 			"dev": true
 		},
 		"basic-ftp": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz",
-			"integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==",
+			"version": "5.3.1",
+			"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+			"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
 			"dev": true
 		},
 		"bcrypt-pbkdf": {
@@ -13427,9 +13507,9 @@
 			"dev": true
 		},
 		"comment-parser": {
-			"version": "1.4.1",
-			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
-			"integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+			"version": "1.4.6",
+			"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz",
+			"integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==",
 			"dev": true
 		},
 		"compress-commons": {
@@ -14167,13 +14247,13 @@
 			}
 		},
 		"enhanced-resolve": {
-			"version": "5.18.3",
-			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
-			"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+			"version": "5.21.0",
+			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
+			"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
 			"dev": true,
 			"requires": {
 				"graceful-fs": "^4.2.4",
-				"tapable": "^2.2.0"
+				"tapable": "^2.3.3"
 			}
 		},
 		"entities": {
@@ -14331,43 +14411,41 @@
 			}
 		},
 		"eslint-config-wikimedia": {
-			"version": "0.32.3",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.3.tgz",
-			"integrity": "sha512-Ekz2/ozpCCjQl3VbC6dW7ChqoW7FRilLDxmJ+FJOZhIxxzZSZR5QqQOAGWSZAlG1ONkZbYV/TPwGLWZcrNxyaA==",
+			"version": "0.32.4",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.4.tgz",
+			"integrity": "sha512-zcHJYss2vo8HK5PzkFuaV9mzaSGRuhA+jFGoQ4rNIwWz0usZsuQ2LYpkKxrbCVX1CbV0PzG+jJ6p0cLI+G37JQ==",
 			"dev": true,
 			"requires": {
 				"@stylistic/eslint-plugin": "^3.1.0",
-				"@typescript-eslint/eslint-plugin": "8.46.0",
-				"@typescript-eslint/parser": "8.46.0",
+				"@typescript-eslint/eslint-plugin": "8.54.0",
+				"@typescript-eslint/parser": "8.54.0",
 				"browserslist-config-wikimedia": "^0.7.0",
-				"eslint": "^8.57.0",
-				"eslint-plugin-compat": "^6.0.2",
+				"eslint-plugin-compat": "^6.1.0",
 				"eslint-plugin-es-x": "^8.7.0",
-				"eslint-plugin-jest": "^29.0.1",
-				"eslint-plugin-jsdoc": "61.3.0",
+				"eslint-plugin-jest": "^29.12.2",
+				"eslint-plugin-jsdoc": "^62.9.0",
 				"eslint-plugin-json-es": "^1.6.0",
-				"eslint-plugin-mediawiki": "^0.8.2",
+				"eslint-plugin-mediawiki": "^0.8.3",
 				"eslint-plugin-mocha": "^10.5.0",
-				"eslint-plugin-n": "^17.23.1",
-				"eslint-plugin-no-jquery": "^3.1.1",
-				"eslint-plugin-qunit": "^8.2.5",
-				"eslint-plugin-security": "^3.0.1",
+				"eslint-plugin-n": "^17.24.0",
+				"eslint-plugin-no-jquery": "^4.0.0",
+				"eslint-plugin-qunit": "^8.2.6",
+				"eslint-plugin-security": "^4.0.0",
 				"eslint-plugin-unicorn": "^56.0.1",
 				"eslint-plugin-vue": "^9.33.0",
-				"eslint-plugin-wdio": "^9.16.2",
+				"eslint-plugin-wdio": "9.23.0",
 				"eslint-plugin-yml": "^1.19.0"
 			}
 		},
 		"eslint-plugin-compat": {
-			"version": "6.0.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.0.2.tgz",
-			"integrity": "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA==",
+			"version": "6.2.1",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.2.1.tgz",
+			"integrity": "sha512-gLKqUH+lQcCL+HzsROUjBDvakc5Zaga51Y4ZAkPCXc41pzKBfyluqTr2j8zOx8QQQb7zyglu1LVoL5aSNWf2SQ==",
 			"dev": true,
 			"requires": {
-				"@mdn/browser-compat-data": "^5.5.35",
+				"@mdn/browser-compat-data": "^6.1.1",
 				"ast-metadata-inferer": "^0.8.1",
-				"browserslist": "^4.24.2",
-				"caniuse-lite": "^1.0.30001687",
+				"browserslist": "^4.25.2",
 				"find-up": "^5.0.0",
 				"globals": "^15.7.0",
 				"lodash.memoize": "^4.1.2",
@@ -14403,42 +14481,42 @@
 			}
 		},
 		"eslint-plugin-jsdoc": {
-			"version": "61.3.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.3.0.tgz",
-			"integrity": "sha512-E4m/5J5lrasd63Z74q4CCZ4PFnywnnrcvA7zZ98802NPhrZKKTp5NH+XAT+afcjXp2ps2/OQF5gPSWCT2XFCJg==",
+			"version": "62.9.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz",
+			"integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==",
 			"dev": true,
 			"requires": {
-				"@es-joy/jsdoccomment": "~0.76.0",
+				"@es-joy/jsdoccomment": "~0.86.0",
 				"@es-joy/resolve.exports": "1.2.0",
 				"are-docs-informative": "^0.0.2",
-				"comment-parser": "1.4.1",
+				"comment-parser": "1.4.6",
 				"debug": "^4.4.3",
 				"escape-string-regexp": "^4.0.0",
-				"espree": "^10.4.0",
-				"esquery": "^1.6.0",
+				"espree": "^11.2.0",
+				"esquery": "^1.7.0",
 				"html-entities": "^2.6.0",
 				"object-deep-merge": "^2.0.0",
 				"parse-imports-exports": "^0.2.4",
-				"semver": "^7.7.3",
+				"semver": "^7.7.4",
 				"spdx-expression-parse": "^4.0.0",
 				"to-valid-identifier": "^1.0.0"
 			},
 			"dependencies": {
 				"eslint-visitor-keys": {
-					"version": "4.2.1",
-					"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
-					"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+					"version": "5.0.1",
+					"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+					"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
 					"dev": true
 				},
 				"espree": {
-					"version": "10.4.0",
-					"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
-					"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+					"version": "11.2.0",
+					"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+					"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
 					"dev": true,
 					"requires": {
-						"acorn": "^8.15.0",
+						"acorn": "^8.16.0",
 						"acorn-jsx": "^5.3.2",
-						"eslint-visitor-keys": "^4.2.1"
+						"eslint-visitor-keys": "^5.0.1"
 					}
 				},
 				"spdx-expression-parse": {
@@ -14464,9 +14542,9 @@
 			}
 		},
 		"eslint-plugin-mediawiki": {
-			"version": "0.8.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.2.tgz",
-			"integrity": "sha512-ydYrpkzm8IVVDQA96QPF3HnFd2xjkIEh7gixD2gvOqUbUZF0p36LtpWXOFAlPWAvHLePWbNNTD5ovd3d4hEtog==",
+			"version": "0.8.3",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.8.3.tgz",
+			"integrity": "sha512-RQKZd40C1taMDk5N9+aFLEBGBB95RNG7Gc54EsJ8pHsJu8//nIdpxNFWPtQz6RNxz6pZUXBnMCxzkMOLM3Mm1w==",
 			"dev": true,
 			"requires": {
 				"upath": "^2.0.1"
@@ -14484,9 +14562,9 @@
 			}
 		},
 		"eslint-plugin-n": {
-			"version": "17.23.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz",
-			"integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==",
+			"version": "17.24.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz",
+			"integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==",
 			"dev": true,
 			"requires": {
 				"@eslint-community/eslint-utils": "^4.5.0",
@@ -14529,26 +14607,26 @@
 			}
 		},
 		"eslint-plugin-no-jquery": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.1.1.tgz",
-			"integrity": "sha512-LTLO3jH/Tjr1pmxCEqtV6qmt+OChv8La4fwgG470JRpgxyFF4NOzoC9CRy92GIWD3Yjl0qLEgPmD2FLQWcNEjg==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-4.0.0.tgz",
+			"integrity": "sha512-ZR631D3qIQfgjKOAcgvYa5cB8xdTvFXAD5MbK5x5WltLSwFxmGnoaTXNtnptFU7py07ALrIe5dZRYncu4RD/Ug==",
 			"dev": true,
 			"requires": {}
 		},
 		"eslint-plugin-qunit": {
-			"version": "8.2.5",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.5.tgz",
-			"integrity": "sha512-qr7RJCYImKQjB+39q4q46i1l7p1V3joHzBE5CAYfxn5tfVFjrnjn/tw7q/kDyweU9kAIcLul0Dx/KWVUCb3BgA==",
+			"version": "8.2.6",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-qunit/-/eslint-plugin-qunit-8.2.6.tgz",
+			"integrity": "sha512-S1jC/DIW9J8VtNX4uG1vlf5FZVrfQFlcuiYmvTHR2IICUhubHqpWA5o+qS1tujh+81Gs39omKV2D4OXfbSJE5g==",
 			"dev": true,
 			"requires": {
-				"eslint-utils": "^3.0.0",
+				"@eslint-community/eslint-utils": "^4.4.0",
 				"requireindex": "^1.2.0"
 			}
 		},
 		"eslint-plugin-security": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz",
-			"integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==",
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-4.0.0.tgz",
+			"integrity": "sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==",
 			"dev": true,
 			"requires": {
 				"safe-regex": "^2.1.1"
@@ -14603,9 +14681,9 @@
 			}
 		},
 		"eslint-plugin-wdio": {
-			"version": "9.16.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.16.2.tgz",
-			"integrity": "sha512-qkqsPgxN70OnUPWMjmzJbSbvm2+Q087JIGss53/OFI4Y46xKlV5VLhLiYealaAibAiXmnfWKd0tERjZAzVL87A==",
+			"version": "9.23.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-wdio/-/eslint-plugin-wdio-9.23.0.tgz",
+			"integrity": "sha512-8tcpupzp2Qmv+uSfhzeHi42LVA9PyjkpMBPclSIkPxBfXpj4fMrejwAHu1PROh1OmJN1VQcGQUTWvSzyRcV2vA==",
 			"dev": true
 		},
 		"eslint-plugin-yml": {
@@ -14673,9 +14751,9 @@
 			"dev": true
 		},
 		"esquery": {
-			"version": "1.6.0",
-			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
-			"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+			"version": "1.7.0",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+			"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
 			"dev": true,
 			"requires": {
 				"estraverse": "^5.1.0"
@@ -15793,9 +15871,9 @@
 			"dev": true
 		},
 		"ip-address": {
-			"version": "10.0.1",
-			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
-			"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
+			"version": "10.2.0",
+			"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+			"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
 			"dev": true
 		},
 		"is-absolute": {
@@ -16078,9 +16156,9 @@
 			"dev": true
 		},
 		"jsdoc-type-pratt-parser": {
-			"version": "6.10.0",
-			"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz",
-			"integrity": "sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==",
+			"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
 		},
 		"jsesc": {
@@ -18354,9 +18432,9 @@
 			}
 		},
 		"tapable": {
-			"version": "2.3.0",
-			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
-			"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+			"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
 			"dev": true
 		},
 		"tar-fs": {
@@ -18397,6 +18475,31 @@
 			"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
 			"dev": true
 		},
+		"tinyglobby": {
+			"version": "0.2.16",
+			"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+			"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+			"dev": true,
+			"requires": {
+				"fdir": "^6.5.0",
+				"picomatch": "^4.0.4"
+			},
+			"dependencies": {
+				"fdir": {
+					"version": "6.5.0",
+					"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+					"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+					"dev": true,
+					"requires": {}
+				},
+				"picomatch": {
+					"version": "4.0.4",
+					"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+					"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+					"dev": true
+				}
+			}
+		},
 		"tinyrainbow": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
@@ -18433,9 +18536,9 @@
 			}
 		},
 		"ts-api-utils": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
-			"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+			"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
 			"dev": true,
 			"requires": {}
 		},
diff --git a/package.json b/package.json
index a4b2ed1..252d3aa 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
 		"@wdio/local-runner": "9.19.2",
 		"@wdio/mocha-framework": "9.19.2",
 		"@wdio/spec-reporter": "9.19.2",
-		"eslint-config-wikimedia": "0.32.3",
+		"eslint-config-wikimedia": "0.32.4",
 		"grunt": "1.6.2",
 		"grunt-banana-checker": "0.13.0",
 		"grunt-eslint": "24.3.0",
diff --git a/resources/infrastructure/campaignManager.js b/resources/infrastructure/campaignManager.js
index fd18870..301ab4f 100644
--- a/resources/infrastructure/campaignManager.js
+++ b/resources/infrastructure/campaignManager.js
@@ -428,7 +428,7 @@
 		$.each( paramDefs, ( paramName, paramDef ) => {
 
 			const paramTemplateVars = {
-				// eslint-disable-next-line mediawiki/msg-doc
+
 				labelMsg: mw.message( paramDef.labelMsg ).text(),
 				inputName: makeNoticeMixinControlName( mixinName, paramName ),
 				dataType: paramDef.type,
@@ -512,7 +512,7 @@
 			}
 
 			if ( paramDef.helpMsg ) {
-				// eslint-disable-next-line mediawiki/msg-doc
+
 				paramTemplateVars.help = mw.message( paramDef.helpMsg ).text();
 			}
 
@@ -567,7 +567,7 @@
 
 		if ( error ) {
 			if ( !messageBox ) {
-				// eslint-disable-next-line mediawiki/msg-doc
+
 				messageBox = mw.util.messageBox( mw.message( msgKey ).text(), 'alert' );
 				$input.closest( 'p' ).before( messageBox );
 			}
diff --git a/resources/subscribing/ext.centralNotice.impressionDiet.js b/resources/subscribing/ext.centralNotice.impressionDiet.js
index 93cae0d..2406670 100644
--- a/resources/subscribing/ext.centralNotice.impressionDiet.js
+++ b/resources/subscribing/ext.centralNotice.impressionDiet.js
@@ -176,6 +176,8 @@
 
 	/**
 	 * Store updated counts
+	 *
+	 * @param c
 	 */
 	function storeCounts( c ) {
 		if ( identifier ) {
-- 
2.47.3


--- end ---
Source code is licensed under the AGPL.