mediawiki/extensions/CentralNotice (main)

sourcepatches
From b34a0d3532f6843f56292ae81caae4b2a4a0ed66 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Mon, 31 Mar 2025 01:03:42 +0000
Subject: [PATCH] build: Updating npm dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* @wdio/cli: 7.33.0 → 7.40.0
* @wdio/junit-reporter: 7.33.0 → 7.40.0
* @wdio/local-runner: 7.33.0 → 7.40.0
* @wdio/mocha-framework: 7.33.0 → 7.40.0
* eslint-config-wikimedia: 0.28.2 → 0.29.1
  The following rules are failing and were disabled:
  * resources:
    * no-jquery/no-done-fail* tests/qunit:
    * no-jquery/no-done-fail
* @wdio/spec-reporter: 7.33.0 → 7.40.0

Change-Id: Ia96888a13eaa3f5e4adb7865298b9444e2ef01cd
---
 package-lock.json                             | 469 +++++++++---------
 package.json                                  |  12 +-
 resources/.eslintrc.json                      |   3 +-
 .../ext.centralNotice.display/chooser.js      |   8 +-
 resources/ext.centralNotice.display/index.js  |   2 +-
 resources/ext.centralNotice.display/state.js  |   4 +-
 .../ext.centralNotice.kvStore/kvStore.js      |   4 +-
 .../kvStoreMaintenance.js                     |   2 +-
 resources/infrastructure/bannereditor.js      |   2 +-
 resources/infrastructure/campaignManager.js   |   2 +-
 ...xt.centralNotice.adminUi.bannerSequence.js |   4 +-
 tests/qunit/.eslintrc.json                    |   3 +-
 12 files changed, 254 insertions(+), 261 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 40681e2..47a15bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,12 +6,12 @@
 		"": {
 			"name": "CentralNotice",
 			"devDependencies": {
-				"@wdio/cli": "7.33.0",
-				"@wdio/junit-reporter": "7.33.0",
-				"@wdio/local-runner": "7.33.0",
-				"@wdio/mocha-framework": "7.33.0",
-				"@wdio/spec-reporter": "7.33.0",
-				"eslint-config-wikimedia": "0.28.2",
+				"@wdio/cli": "7.40.0",
+				"@wdio/junit-reporter": "7.40.0",
+				"@wdio/local-runner": "7.40.0",
+				"@wdio/mocha-framework": "7.40.0",
+				"@wdio/spec-reporter": "7.40.0",
+				"eslint-config-wikimedia": "0.29.1",
 				"grunt": "1.6.1",
 				"grunt-banana-checker": "0.13.0",
 				"grunt-eslint": "24.3.0",
@@ -496,9 +496,9 @@
 			}
 		},
 		"node_modules/@types/diff": {
-			"version": "5.2.1",
-			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz",
-			"integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==",
+			"version": "5.2.3",
+			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz",
+			"integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
 			"dev": true
 		},
 		"node_modules/@types/easy-table": {
@@ -937,9 +937,9 @@
 			"dev": true
 		},
 		"node_modules/@wdio/cli": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.33.0.tgz",
-			"integrity": "sha512-S5Iy4AVcbcJDMhAP4k/Yf18mKma9NGFM8A5bafcGRpFlIj97rpnb0/cpmJVVEr4v/wr3XCu0k38ooJw0B/D3nw==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.40.0.tgz",
+			"integrity": "sha512-M0txYEqqamBvJe4FEuqwWq1jd879sElF047BXSv2GRu4R1/iEBPYJHjn9KuL60Fkkpp/L1NMHTl7gW9i445edQ==",
 			"dev": true,
 			"dependencies": {
 				"@types/ejs": "^3.0.5",
@@ -950,11 +950,11 @@
 				"@types/lodash.union": "^4.6.6",
 				"@types/node": "^18.0.0",
 				"@types/recursive-readdir": "^2.2.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"chalk": "^4.0.0",
 				"chokidar": "^3.0.0",
@@ -967,7 +967,7 @@
 				"lodash.union": "^4.6.0",
 				"mkdirp": "^3.0.0",
 				"recursive-readdir": "^2.2.2",
-				"webdriverio": "7.33.0",
+				"webdriverio": "7.40.0",
 				"yargs": "^17.0.0",
 				"yarn-install": "^1.0.0"
 			},
@@ -979,15 +979,15 @@
 			}
 		},
 		"node_modules/@wdio/config": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz",
-			"integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.40.0.tgz",
+			"integrity": "sha512-ayQELXyxa+k9/2a509F5a1oTsCa/w8D1nDrd+hzm+1mYb4Te2lceWCCzm+atGKkMpvjLH4GvhrEBYLh3rIWk2A==",
 			"dev": true,
 			"dependencies": {
 				"@types/glob": "^8.1.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"glob": "^8.0.3"
 			},
@@ -1037,15 +1037,15 @@
 			}
 		},
 		"node_modules/@wdio/junit-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.33.0.tgz",
-			"integrity": "sha512-0Gj+lvUmscTjXbC+ziiG/1W64h2Z1Lgy04rHn4vU3xNp771+KJ13Ry1nxY5bUbOsfD1Ix6R1gKSz98nCoZCZpg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.40.0.tgz",
+			"integrity": "sha512-nGxzvdCBHUQOtKbCrihO+MjLdfyeYPVeoCAWBNbPHP06nnjsoVDT7k1Ic7BwAbrDZn1SUOVhwdGOxqDdc1E8Fg==",
 			"dev": true,
 			"dependencies": {
 				"@types/json-stringify-safe": "^5.0.0",
 				"@types/validator": "^13.1.3",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"json-stringify-safe": "^5.0.1",
 				"junit-report-builder": "^3.0.0",
 				"validator": "^13.0.0"
@@ -1058,16 +1058,16 @@
 			}
 		},
 		"node_modules/@wdio/local-runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.33.0.tgz",
-			"integrity": "sha512-oZLLyOizlX2mV3FIxRLWgN0J2sDL+6LhC71CwFxcV8iVjXvp16my9jbKrgtkIgdo1BsaWIqq+tZlCr9e9NUUjA==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.40.0.tgz",
+			"integrity": "sha512-OBuN7TlFhbPUH7Wbh2S8OKZOjeW4rHXOfuGzJfaKkzjHje2Dqide/uC3Gd25MwmzgZcVkOo9DUYiGFCHXc44ug==",
 			"dev": true,
 			"dependencies": {
 				"@types/stream-buffers": "^3.0.3",
 				"@wdio/logger": "7.26.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/runner": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/runner": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"split2": "^4.0.0",
 				"stream-buffers": "^3.0.2"
@@ -1095,15 +1095,15 @@
 			}
 		},
 		"node_modules/@wdio/mocha-framework": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.33.0.tgz",
-			"integrity": "sha512-y6+iBF+QrqeiXC+mNwW/o0vRsB+qaRznxoh+ds6Xz9V0tui55cn4kl2gYkBu3oHX8h+9R52ykLyaY9wv+r2aeg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.40.0.tgz",
+			"integrity": "sha512-Pc+c4M07qhz3CdhitETWq8htMPb3xwmmQF5CKUpcy+F6nBTy4Q3wDOSLRQnFD7iP+JqnpJ2o3k1NPeuNYc7+CQ==",
 			"dev": true,
 			"dependencies": {
 				"@types/mocha": "^10.0.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"expect-webdriverio": "^3.0.0",
 				"mocha": "^10.0.0"
 			},
@@ -1121,21 +1121,21 @@
 			}
 		},
 		"node_modules/@wdio/repl": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz",
-			"integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.40.0.tgz",
+			"integrity": "sha512-6tzT7lOMxBwdqMVdW4QxlzrQadGPta4HedFcJo4LyRz9PkXPTF68qeIGs0GyZvy/5AqspNWaAJvIR7f3T3tCyw==",
 			"dev": true,
 			"dependencies": {
-				"@wdio/utils": "7.33.0"
+				"@wdio/utils": "7.40.0"
 			},
 			"engines": {
 				"node": ">=12.0.0"
 			}
 		},
 		"node_modules/@wdio/reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.33.0.tgz",
-			"integrity": "sha512-iL3SwP+hVmu1qj54YPwRCK+ZpVN75xpltYihjpuZCWZKJ0qpQuE2oBlNauFQWgrrd74ta20EDV4mSIhXm9lX6g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.40.0.tgz",
+			"integrity": "sha512-nWVh20JONsN4xf2PRWAS+81r1a6t6M5OtlVOti7G8/pODCul1kxmi9l07s0JaU9g64C1nDc4bOxvAPOWR3/wIw==",
 			"dev": true,
 			"dependencies": {
 				"@types/diff": "^5.0.0",
@@ -1143,7 +1143,7 @@
 				"@types/object-inspect": "^1.8.0",
 				"@types/supports-color": "^8.1.0",
 				"@types/tmp": "^0.2.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"diff": "^5.0.0",
 				"fs-extra": "^11.1.1",
 				"object-inspect": "^1.10.3",
@@ -1154,33 +1154,33 @@
 			}
 		},
 		"node_modules/@wdio/runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.33.0.tgz",
-			"integrity": "sha512-3B+29EanAdRFh4vT3E4XnHQga/apdLIDZq5pGEbqnDA5LarbIvsNWbJjeJzWM6XaZmEwrPfjOunjOevJt5yvdg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.40.0.tgz",
+			"integrity": "sha512-3dGn8sU9Oc0kTq+hcxNSqkF1acqiTAzamyNWsWXAX7V0FOfZxp0wmD9aMqY+sVT6g8mUE5aePT1ydONE5o+6QA==",
 			"dev": true,
 			"dependencies": {
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"gaze": "^1.1.2",
-				"webdriver": "7.33.0",
-				"webdriverio": "7.33.0"
+				"webdriver": "7.40.0",
+				"webdriverio": "7.40.0"
 			},
 			"engines": {
 				"node": ">=12.0.0"
 			}
 		},
 		"node_modules/@wdio/spec-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.33.0.tgz",
-			"integrity": "sha512-+BTJE6p82EaQMK+2t3lmXlpxF0Q72EJwUSEqY6RPyPUZL7fB+AZdHKQcxcmCR8bYyOUp68H45Yj4PuCKRS6hAg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.40.0.tgz",
+			"integrity": "sha512-DhkfnWrN/X0DKpj/maIsk76yr5iG0t/ZbbajtBXLv9lMn8j+ALY34dfj0mvvTKX77wlzDtgeuC+8BzxPKBWU6g==",
 			"dev": true,
 			"dependencies": {
 				"@types/easy-table": "^1.2.0",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"chalk": "^4.0.0",
 				"easy-table": "^1.1.1",
 				"pretty-ms": "^7.0.0"
@@ -1193,9 +1193,9 @@
 			}
 		},
 		"node_modules/@wdio/types": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz",
-			"integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.40.0.tgz",
+			"integrity": "sha512-MWMbU+8uk+JrF7ygP/TJDsaSvFozKauiW6EnG7rxx9+GvU1Q1B3l4UjAc7GDbgLKjwt8T2y5GDRiDoD3UOjVyw==",
 			"dev": true,
 			"dependencies": {
 				"@types/node": "^18.0.0",
@@ -1214,13 +1214,13 @@
 			}
 		},
 		"node_modules/@wdio/utils": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz",
-			"integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.40.0.tgz",
+			"integrity": "sha512-jLF57xHmz5nnGuM6ZRWjVYa/LQb22CS7yG50dUFa9wJ509mC1HlUzaA01Gjk9TV5jf9vnwE/yZfUMCoecTgG9w==",
 			"dev": true,
 			"dependencies": {
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"p-iteration": "^1.1.8"
 			},
 			"engines": {
@@ -1440,12 +1440,12 @@
 			"dev": true
 		},
 		"node_modules/aria-query": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
-			"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+			"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
 			"dev": true,
-			"dependencies": {
-				"dequal": "^2.0.3"
+			"engines": {
+				"node": ">= 0.4"
 			}
 		},
 		"node_modules/array-differ": {
@@ -2403,9 +2403,9 @@
 			}
 		},
 		"node_modules/css-shorthand-properties": {
-			"version": "1.1.1",
-			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz",
-			"integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==",
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz",
+			"integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==",
 			"dev": true
 		},
 		"node_modules/css-tokenize": {
@@ -2643,15 +2643,6 @@
 				"node": ">=0.4.0"
 			}
 		},
-		"node_modules/dequal": {
-			"version": "2.0.3",
-			"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
-			"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
-			"dev": true,
-			"engines": {
-				"node": ">=6"
-			}
-		},
 		"node_modules/detect-file": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -2662,18 +2653,18 @@
 			}
 		},
 		"node_modules/devtools": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.33.0.tgz",
-			"integrity": "sha512-9sxWcdZLOUtgvw4kotL8HqvIFkO/yuHUecgqCYXnqIzwdWSoxWCeKAyZhOJNMeFtzjEnHGvIrUIquEuifk2STg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.40.0.tgz",
+			"integrity": "sha512-hiDPCNG/mpD+bSgegxoe5nwyxWav+QpIvT+7H9D0dUwjB0q04OF473qGflSQ1QpGig6l4qG92tA7dVnLsdP75A==",
 			"dev": true,
 			"dependencies": {
 				"@types/node": "^18.0.0",
 				"@types/ua-parser-js": "^0.7.33",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"chrome-launcher": "^0.15.0",
 				"edge-paths": "^2.1.0",
 				"puppeteer-core": "13.1.3",
@@ -2686,9 +2677,9 @@
 			}
 		},
 		"node_modules/devtools-protocol": {
-			"version": "0.0.1203626",
-			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz",
-			"integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==",
+			"version": "0.0.1260888",
+			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz",
+			"integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==",
 			"dev": true
 		},
 		"node_modules/devtools/node_modules/debug": {
@@ -3118,9 +3109,9 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.28.2",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
-			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
+			"version": "0.29.1",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.29.1.tgz",
+			"integrity": "sha512-4dbL5o3hKGSvreyrGZWLPoTDLFubZ575IQOPhUaTcpbTsi0u05TBEMsOyYkthTaK21vsFQqhSYtxp/xU93BSdA==",
 			"dev": true,
 			"dependencies": {
 				"browserslist-config-wikimedia": "^0.7.0",
@@ -3133,13 +3124,16 @@
 				"eslint-plugin-mediawiki": "^0.7.0",
 				"eslint-plugin-mocha": "^10.4.3",
 				"eslint-plugin-n": "^17.7.0",
-				"eslint-plugin-no-jquery": "^3.0.1",
+				"eslint-plugin-no-jquery": "^3.1.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
 				"eslint-plugin-unicorn": "^53.0.0",
 				"eslint-plugin-vue": "^9.26.0",
 				"eslint-plugin-wdio": "^8.24.12",
 				"eslint-plugin-yml": "^1.14.0"
+			},
+			"engines": {
+				"node": ">=18 <23"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
@@ -3363,9 +3357,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-no-jquery": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.0.1.tgz",
-			"integrity": "sha512-GrzdjIxox/3x8hpSwpxiMuEQFipiJHTGiVsp0T1TI6GH+KVSbXa4z/56xTV1WiIe66u3iRgvCIipu9CRthecpQ==",
+			"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==",
 			"dev": true,
 			"peerDependencies": {
 				"eslint": ">=8.0.0"
@@ -6166,10 +6160,13 @@
 			}
 		},
 		"node_modules/object-inspect": {
-			"version": "1.13.1",
-			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-			"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+			"version": "1.13.4",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+			"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
 			"dev": true,
+			"engines": {
+				"node": ">= 0.4"
+			},
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -6829,9 +6826,9 @@
 			"dev": true
 		},
 		"node_modules/pump": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-			"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+			"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
 			"dev": true,
 			"dependencies": {
 				"end-of-stream": "^1.1.0",
@@ -8427,9 +8424,9 @@
 			}
 		},
 		"node_modules/ua-parser-js": {
-			"version": "1.0.38",
-			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz",
-			"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==",
+			"version": "1.0.40",
+			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
+			"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
 			"dev": true,
 			"funding": [
 				{
@@ -8445,6 +8442,9 @@
 					"url": "https://github.com/sponsors/faisalman"
 				}
 			],
+			"bin": {
+				"ua-parser-js": "script/cli.js"
+			},
 			"engines": {
 				"node": "*"
 			}
@@ -8666,17 +8666,17 @@
 			}
 		},
 		"node_modules/webdriver": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz",
-			"integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.40.0.tgz",
+			"integrity": "sha512-CKi3cDWgNVE/ibcsBfdtA+pQVeZ4oYlecLlwemulVxJdgr4l5bv+nXuoIhnYeVb6aAI4naK772vmWQ0XuRYhDQ==",
 			"dev": true,
 			"dependencies": {
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"got": "^11.0.2",
 				"ky": "0.30.0",
 				"lodash.merge": "^4.6.1"
@@ -8686,25 +8686,25 @@
 			}
 		},
 		"node_modules/webdriverio": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.33.0.tgz",
-			"integrity": "sha512-9MRPYkOEdsvsBpDJRSMAR+dLID6I65vKjpzNTTFJSjRLSHF6MByOH3mV2trlpIyV+TIp87GysYUVf3Cmufg9eg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.40.0.tgz",
+			"integrity": "sha512-UswBOjpWwk7ziGi9beZGX/XFrp4m1Ws0ni5HI9mzAkOlpKKKWhnX6i95pWQV6sPF4Urv4RJf8WXayHhTbzXzdA==",
 			"dev": true,
 			"dependencies": {
 				"@types/aria-query": "^5.0.0",
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"archiver": "^5.0.0",
 				"aria-query": "^5.2.1",
 				"css-shorthand-properties": "^1.1.1",
 				"css-value": "^0.0.1",
-				"devtools": "7.33.0",
-				"devtools-protocol": "^0.0.1203626",
+				"devtools": "7.40.0",
+				"devtools-protocol": "^0.0.1260888",
 				"fs-extra": "^11.1.1",
 				"grapheme-splitter": "^1.0.2",
 				"lodash.clonedeep": "^4.5.0",
@@ -8717,7 +8717,7 @@
 				"resq": "^1.9.1",
 				"rgb2hex": "0.2.5",
 				"serialize-error": "^8.0.0",
-				"webdriver": "7.33.0"
+				"webdriver": "7.40.0"
 			},
 			"engines": {
 				"node": ">=12.0.0"
@@ -9483,9 +9483,9 @@
 			}
 		},
 		"@types/diff": {
-			"version": "5.2.1",
-			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz",
-			"integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==",
+			"version": "5.2.3",
+			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz",
+			"integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
 			"dev": true
 		},
 		"@types/easy-table": {
@@ -9870,9 +9870,9 @@
 			"dev": true
 		},
 		"@wdio/cli": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.33.0.tgz",
-			"integrity": "sha512-S5Iy4AVcbcJDMhAP4k/Yf18mKma9NGFM8A5bafcGRpFlIj97rpnb0/cpmJVVEr4v/wr3XCu0k38ooJw0B/D3nw==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.40.0.tgz",
+			"integrity": "sha512-M0txYEqqamBvJe4FEuqwWq1jd879sElF047BXSv2GRu4R1/iEBPYJHjn9KuL60Fkkpp/L1NMHTl7gW9i445edQ==",
 			"dev": true,
 			"requires": {
 				"@types/ejs": "^3.0.5",
@@ -9883,11 +9883,11 @@
 				"@types/lodash.union": "^4.6.6",
 				"@types/node": "^18.0.0",
 				"@types/recursive-readdir": "^2.2.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"chalk": "^4.0.0",
 				"chokidar": "^3.0.0",
@@ -9900,21 +9900,21 @@
 				"lodash.union": "^4.6.0",
 				"mkdirp": "^3.0.0",
 				"recursive-readdir": "^2.2.2",
-				"webdriverio": "7.33.0",
+				"webdriverio": "7.40.0",
 				"yargs": "^17.0.0",
 				"yarn-install": "^1.0.0"
 			}
 		},
 		"@wdio/config": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz",
-			"integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.40.0.tgz",
+			"integrity": "sha512-ayQELXyxa+k9/2a509F5a1oTsCa/w8D1nDrd+hzm+1mYb4Te2lceWCCzm+atGKkMpvjLH4GvhrEBYLh3rIWk2A==",
 			"dev": true,
 			"requires": {
 				"@types/glob": "^8.1.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"glob": "^8.0.3"
 			},
@@ -9953,31 +9953,31 @@
 			}
 		},
 		"@wdio/junit-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.33.0.tgz",
-			"integrity": "sha512-0Gj+lvUmscTjXbC+ziiG/1W64h2Z1Lgy04rHn4vU3xNp771+KJ13Ry1nxY5bUbOsfD1Ix6R1gKSz98nCoZCZpg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.40.0.tgz",
+			"integrity": "sha512-nGxzvdCBHUQOtKbCrihO+MjLdfyeYPVeoCAWBNbPHP06nnjsoVDT7k1Ic7BwAbrDZn1SUOVhwdGOxqDdc1E8Fg==",
 			"dev": true,
 			"requires": {
 				"@types/json-stringify-safe": "^5.0.0",
 				"@types/validator": "^13.1.3",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"json-stringify-safe": "^5.0.1",
 				"junit-report-builder": "^3.0.0",
 				"validator": "^13.0.0"
 			}
 		},
 		"@wdio/local-runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.33.0.tgz",
-			"integrity": "sha512-oZLLyOizlX2mV3FIxRLWgN0J2sDL+6LhC71CwFxcV8iVjXvp16my9jbKrgtkIgdo1BsaWIqq+tZlCr9e9NUUjA==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.40.0.tgz",
+			"integrity": "sha512-OBuN7TlFhbPUH7Wbh2S8OKZOjeW4rHXOfuGzJfaKkzjHje2Dqide/uC3Gd25MwmzgZcVkOo9DUYiGFCHXc44ug==",
 			"dev": true,
 			"requires": {
 				"@types/stream-buffers": "^3.0.3",
 				"@wdio/logger": "7.26.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/runner": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/runner": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"split2": "^4.0.0",
 				"stream-buffers": "^3.0.2"
@@ -9996,15 +9996,15 @@
 			}
 		},
 		"@wdio/mocha-framework": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.33.0.tgz",
-			"integrity": "sha512-y6+iBF+QrqeiXC+mNwW/o0vRsB+qaRznxoh+ds6Xz9V0tui55cn4kl2gYkBu3oHX8h+9R52ykLyaY9wv+r2aeg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.40.0.tgz",
+			"integrity": "sha512-Pc+c4M07qhz3CdhitETWq8htMPb3xwmmQF5CKUpcy+F6nBTy4Q3wDOSLRQnFD7iP+JqnpJ2o3k1NPeuNYc7+CQ==",
 			"dev": true,
 			"requires": {
 				"@types/mocha": "^10.0.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"expect-webdriverio": "^3.0.0",
 				"mocha": "^10.0.0"
 			}
@@ -10016,18 +10016,18 @@
 			"dev": true
 		},
 		"@wdio/repl": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz",
-			"integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.40.0.tgz",
+			"integrity": "sha512-6tzT7lOMxBwdqMVdW4QxlzrQadGPta4HedFcJo4LyRz9PkXPTF68qeIGs0GyZvy/5AqspNWaAJvIR7f3T3tCyw==",
 			"dev": true,
 			"requires": {
-				"@wdio/utils": "7.33.0"
+				"@wdio/utils": "7.40.0"
 			}
 		},
 		"@wdio/reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.33.0.tgz",
-			"integrity": "sha512-iL3SwP+hVmu1qj54YPwRCK+ZpVN75xpltYihjpuZCWZKJ0qpQuE2oBlNauFQWgrrd74ta20EDV4mSIhXm9lX6g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.40.0.tgz",
+			"integrity": "sha512-nWVh20JONsN4xf2PRWAS+81r1a6t6M5OtlVOti7G8/pODCul1kxmi9l07s0JaU9g64C1nDc4bOxvAPOWR3/wIw==",
 			"dev": true,
 			"requires": {
 				"@types/diff": "^5.0.0",
@@ -10035,7 +10035,7 @@
 				"@types/object-inspect": "^1.8.0",
 				"@types/supports-color": "^8.1.0",
 				"@types/tmp": "^0.2.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"diff": "^5.0.0",
 				"fs-extra": "^11.1.1",
 				"object-inspect": "^1.10.3",
@@ -10043,39 +10043,39 @@
 			}
 		},
 		"@wdio/runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.33.0.tgz",
-			"integrity": "sha512-3B+29EanAdRFh4vT3E4XnHQga/apdLIDZq5pGEbqnDA5LarbIvsNWbJjeJzWM6XaZmEwrPfjOunjOevJt5yvdg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.40.0.tgz",
+			"integrity": "sha512-3dGn8sU9Oc0kTq+hcxNSqkF1acqiTAzamyNWsWXAX7V0FOfZxp0wmD9aMqY+sVT6g8mUE5aePT1ydONE5o+6QA==",
 			"dev": true,
 			"requires": {
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"gaze": "^1.1.2",
-				"webdriver": "7.33.0",
-				"webdriverio": "7.33.0"
+				"webdriver": "7.40.0",
+				"webdriverio": "7.40.0"
 			}
 		},
 		"@wdio/spec-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.33.0.tgz",
-			"integrity": "sha512-+BTJE6p82EaQMK+2t3lmXlpxF0Q72EJwUSEqY6RPyPUZL7fB+AZdHKQcxcmCR8bYyOUp68H45Yj4PuCKRS6hAg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.40.0.tgz",
+			"integrity": "sha512-DhkfnWrN/X0DKpj/maIsk76yr5iG0t/ZbbajtBXLv9lMn8j+ALY34dfj0mvvTKX77wlzDtgeuC+8BzxPKBWU6g==",
 			"dev": true,
 			"requires": {
 				"@types/easy-table": "^1.2.0",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"chalk": "^4.0.0",
 				"easy-table": "^1.1.1",
 				"pretty-ms": "^7.0.0"
 			}
 		},
 		"@wdio/types": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz",
-			"integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.40.0.tgz",
+			"integrity": "sha512-MWMbU+8uk+JrF7ygP/TJDsaSvFozKauiW6EnG7rxx9+GvU1Q1B3l4UjAc7GDbgLKjwt8T2y5GDRiDoD3UOjVyw==",
 			"dev": true,
 			"requires": {
 				"@types/node": "^18.0.0",
@@ -10083,13 +10083,13 @@
 			}
 		},
 		"@wdio/utils": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz",
-			"integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.40.0.tgz",
+			"integrity": "sha512-jLF57xHmz5nnGuM6ZRWjVYa/LQb22CS7yG50dUFa9wJ509mC1HlUzaA01Gjk9TV5jf9vnwE/yZfUMCoecTgG9w==",
 			"dev": true,
 			"requires": {
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"p-iteration": "^1.1.8"
 			}
 		},
@@ -10259,13 +10259,10 @@
 			"dev": true
 		},
 		"aria-query": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
-			"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
-			"dev": true,
-			"requires": {
-				"dequal": "^2.0.3"
-			}
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+			"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+			"dev": true
 		},
 		"array-differ": {
 			"version": "3.0.0",
@@ -10945,9 +10942,9 @@
 			}
 		},
 		"css-shorthand-properties": {
-			"version": "1.1.1",
-			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz",
-			"integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==",
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz",
+			"integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==",
 			"dev": true
 		},
 		"css-tokenize": {
@@ -11127,12 +11124,6 @@
 			"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
 			"dev": true
 		},
-		"dequal": {
-			"version": "2.0.3",
-			"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
-			"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
-			"dev": true
-		},
 		"detect-file": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -11140,18 +11131,18 @@
 			"dev": true
 		},
 		"devtools": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.33.0.tgz",
-			"integrity": "sha512-9sxWcdZLOUtgvw4kotL8HqvIFkO/yuHUecgqCYXnqIzwdWSoxWCeKAyZhOJNMeFtzjEnHGvIrUIquEuifk2STg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.40.0.tgz",
+			"integrity": "sha512-hiDPCNG/mpD+bSgegxoe5nwyxWav+QpIvT+7H9D0dUwjB0q04OF473qGflSQ1QpGig6l4qG92tA7dVnLsdP75A==",
 			"dev": true,
 			"requires": {
 				"@types/node": "^18.0.0",
 				"@types/ua-parser-js": "^0.7.33",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"chrome-launcher": "^0.15.0",
 				"edge-paths": "^2.1.0",
 				"puppeteer-core": "13.1.3",
@@ -11221,9 +11212,9 @@
 			}
 		},
 		"devtools-protocol": {
-			"version": "0.0.1203626",
-			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz",
-			"integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==",
+			"version": "0.0.1260888",
+			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz",
+			"integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==",
 			"dev": true
 		},
 		"diff": {
@@ -11500,9 +11491,9 @@
 			}
 		},
 		"eslint-config-wikimedia": {
-			"version": "0.28.2",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
-			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
+			"version": "0.29.1",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.29.1.tgz",
+			"integrity": "sha512-4dbL5o3hKGSvreyrGZWLPoTDLFubZ575IQOPhUaTcpbTsi0u05TBEMsOyYkthTaK21vsFQqhSYtxp/xU93BSdA==",
 			"dev": true,
 			"requires": {
 				"browserslist-config-wikimedia": "^0.7.0",
@@ -11515,7 +11506,7 @@
 				"eslint-plugin-mediawiki": "^0.7.0",
 				"eslint-plugin-mocha": "^10.4.3",
 				"eslint-plugin-n": "^17.7.0",
-				"eslint-plugin-no-jquery": "^3.0.1",
+				"eslint-plugin-no-jquery": "^3.1.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
 				"eslint-plugin-unicorn": "^53.0.0",
@@ -11671,9 +11662,9 @@
 			}
 		},
 		"eslint-plugin-no-jquery": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.0.1.tgz",
-			"integrity": "sha512-GrzdjIxox/3x8hpSwpxiMuEQFipiJHTGiVsp0T1TI6GH+KVSbXa4z/56xTV1WiIe66u3iRgvCIipu9CRthecpQ==",
+			"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==",
 			"dev": true,
 			"requires": {}
 		},
@@ -13766,9 +13757,9 @@
 			"dev": true
 		},
 		"object-inspect": {
-			"version": "1.13.1",
-			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-			"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+			"version": "1.13.4",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+			"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
 			"dev": true
 		},
 		"object.defaults": {
@@ -14247,9 +14238,9 @@
 			"dev": true
 		},
 		"pump": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-			"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+			"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
 			"dev": true,
 			"requires": {
 				"end-of-stream": "^1.1.0",
@@ -15414,9 +15405,9 @@
 			"peer": true
 		},
 		"ua-parser-js": {
-			"version": "1.0.38",
-			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz",
-			"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==",
+			"version": "1.0.40",
+			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
+			"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
 			"dev": true
 		},
 		"unbzip2-stream": {
@@ -15575,42 +15566,42 @@
 			}
 		},
 		"webdriver": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz",
-			"integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.40.0.tgz",
+			"integrity": "sha512-CKi3cDWgNVE/ibcsBfdtA+pQVeZ4oYlecLlwemulVxJdgr4l5bv+nXuoIhnYeVb6aAI4naK772vmWQ0XuRYhDQ==",
 			"dev": true,
 			"requires": {
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"got": "^11.0.2",
 				"ky": "0.30.0",
 				"lodash.merge": "^4.6.1"
 			}
 		},
 		"webdriverio": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.33.0.tgz",
-			"integrity": "sha512-9MRPYkOEdsvsBpDJRSMAR+dLID6I65vKjpzNTTFJSjRLSHF6MByOH3mV2trlpIyV+TIp87GysYUVf3Cmufg9eg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.40.0.tgz",
+			"integrity": "sha512-UswBOjpWwk7ziGi9beZGX/XFrp4m1Ws0ni5HI9mzAkOlpKKKWhnX6i95pWQV6sPF4Urv4RJf8WXayHhTbzXzdA==",
 			"dev": true,
 			"requires": {
 				"@types/aria-query": "^5.0.0",
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"archiver": "^5.0.0",
 				"aria-query": "^5.2.1",
 				"css-shorthand-properties": "^1.1.1",
 				"css-value": "^0.0.1",
-				"devtools": "7.33.0",
-				"devtools-protocol": "^0.0.1203626",
+				"devtools": "7.40.0",
+				"devtools-protocol": "^0.0.1260888",
 				"fs-extra": "^11.1.1",
 				"grapheme-splitter": "^1.0.2",
 				"lodash.clonedeep": "^4.5.0",
@@ -15623,7 +15614,7 @@
 				"resq": "^1.9.1",
 				"rgb2hex": "0.2.5",
 				"serialize-error": "^8.0.0",
-				"webdriver": "7.33.0"
+				"webdriver": "7.40.0"
 			},
 			"dependencies": {
 				"brace-expansion": {
diff --git a/package.json b/package.json
index 03daffa..d13cc88 100644
--- a/package.json
+++ b/package.json
@@ -8,12 +8,12 @@
 		"test": "grunt test"
 	},
 	"devDependencies": {
-		"@wdio/cli": "7.33.0",
-		"@wdio/junit-reporter": "7.33.0",
-		"@wdio/local-runner": "7.33.0",
-		"@wdio/mocha-framework": "7.33.0",
-		"@wdio/spec-reporter": "7.33.0",
-		"eslint-config-wikimedia": "0.28.2",
+		"@wdio/cli": "7.40.0",
+		"@wdio/junit-reporter": "7.40.0",
+		"@wdio/local-runner": "7.40.0",
+		"@wdio/mocha-framework": "7.40.0",
+		"@wdio/spec-reporter": "7.40.0",
+		"eslint-config-wikimedia": "0.29.1",
 		"grunt": "1.6.1",
 		"grunt-banana-checker": "0.13.0",
 		"grunt-eslint": "24.3.0",
diff --git a/resources/.eslintrc.json b/resources/.eslintrc.json
index 66584d8..4a067f4 100644
--- a/resources/.eslintrc.json
+++ b/resources/.eslintrc.json
@@ -10,6 +10,7 @@
 	},
 	"rules": {
 		"no-jquery/no-global-selector": "off",
-		"compat/compat": "warn"
+		"compat/compat": "warn",
+		"no-jquery/no-done-fail": "warn"
 	}
 }
diff --git a/resources/ext.centralNotice.display/chooser.js b/resources/ext.centralNotice.display/chooser.js
index ada4ba2..0961aa8 100644
--- a/resources/ext.centralNotice.display/chooser.js
+++ b/resources/ext.centralNotice.display/chooser.js
@@ -165,7 +165,7 @@
 			}
 
 			// Filter for device
-			if ( banner.devices.indexOf( device ) === -1 ) {
+			if ( !banner.devices.includes( device ) ) {
 				continue;
 			}
 
@@ -279,8 +279,8 @@
 
 				// Filter for country if geotargeted
 				if ( campaign.geotargeted && (
-					campaign.countries.indexOf( country ) === -1 && // No country wide match
-					campaign.regions.indexOf( uniqueRegionCode ) === -1 // And no region match
+					!campaign.countries.includes( country ) && // No country wide match
+					!campaign.regions.includes( uniqueRegionCode ) // And no region match
 				) ) {
 					continue;
 				}
@@ -298,7 +298,7 @@
 					}
 
 					// Device
-					if ( banner.devices.indexOf( device ) === -1 ) {
+					if ( !banner.devices.includes( device ) ) {
 						continue;
 					}
 
diff --git a/resources/ext.centralNotice.display/index.js b/resources/ext.centralNotice.display/index.js
index 4b6b30c..bb61fe2 100644
--- a/resources/ext.centralNotice.display/index.js
+++ b/resources/ext.centralNotice.display/index.js
@@ -424,7 +424,7 @@
 				// Do not check user preferences on anon users
 				if (
 					campaign.type === 0 ||
-					state.getData().optedOutCampaigns.indexOf( campaign.type ) !== -1
+					state.getData().optedOutCampaigns.includes( campaign.type )
 				) {
 					// User opted out of viewing this type of campaigns
 					// or campaign does not have a type set
diff --git a/resources/ext.centralNotice.display/state.js b/resources/ext.centralNotice.display/state.js
index e2c1f89..c270997 100644
--- a/resources/ext.centralNotice.display/state.js
+++ b/resources/ext.centralNotice.display/state.js
@@ -441,7 +441,7 @@
 			// Is the campaign category among the categories configured to use
 			// legacy mechanisms?
 			state.data.campaignCategoryUsesLegacy =
-				config.categoriesUsingLegacy.indexOf( campaignCategory ) !== -1;
+				config.categoriesUsingLegacy.includes( campaignCategory );
 		},
 
 		/**
@@ -609,7 +609,7 @@
 			const tests = state.data.tests = state.data.tests || [];
 
 			// Add if it isn't already registered.
-			if ( tests.indexOf( identifier ) === -1 ) {
+			if ( !tests.includes( identifier ) ) {
 				tests.push( identifier );
 
 				if ( tests.length === 1 ) {
diff --git a/resources/ext.centralNotice.kvStore/kvStore.js b/resources/ext.centralNotice.kvStore/kvStore.js
index c6d9564..b668dfb 100644
--- a/resources/ext.centralNotice.kvStore/kvStore.js
+++ b/resources/ext.centralNotice.kvStore/kvStore.js
@@ -365,8 +365,8 @@
 		 */
 		setItem: function ( key, value, context, ttl, multiStorageOption ) {
 			// Check validity of key
-			if ( ( key.indexOf( SEPARATOR ) !== -1 ) ||
-				( key.indexOf( SEPARATOR_IN_COOKIES ) !== -1 ) ) {
+			if ( ( key.includes( SEPARATOR ) ) ||
+				( key.includes( SEPARATOR_IN_COOKIES ) ) ) {
 
 				setError( 'Invalid key', key, value, context );
 				return false;
diff --git a/resources/ext.centralNotice.startUp/kvStoreMaintenance.js b/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
index b78c1bc..a9495b6 100644
--- a/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
+++ b/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
@@ -114,7 +114,7 @@
 			}
 
 			// Fallback cookies? LocalStorage seems to work, so purge them.
-			if ( document.cookie.indexOf( PREFIX_AND_SEPARATOR_IN_COOKIES ) !== -1 ) {
+			if ( document.cookie.includes( PREFIX_AND_SEPARATOR_IN_COOKIES ) ) {
 				purgeFallbackCookies();
 			}
 
diff --git a/resources/infrastructure/bannereditor.js b/resources/infrastructure/bannereditor.js
index db12a0f..263743a 100644
--- a/resources/infrastructure/bannereditor.js
+++ b/resources/infrastructure/bannereditor.js
@@ -184,7 +184,7 @@
 		 * @return {boolean}
 		 */
 		doSaveBanner: function () {
-			if ( $( '#mw-input-wpbanner-body' ).prop( 'value' ).indexOf( 'document.write' ) > -1 ) {
+			if ( $( '#mw-input-wpbanner-body' ).prop( 'value' ).includes( 'document.write' ) ) {
 				// eslint-disable-next-line no-alert
 				alert( mw.msg( 'centralnotice-documentwrite-error' ) );
 			} else {
diff --git a/resources/infrastructure/campaignManager.js b/resources/infrastructure/campaignManager.js
index 6077efd..9c4e029 100644
--- a/resources/infrastructure/campaignManager.js
+++ b/resources/infrastructure/campaignManager.js
@@ -626,7 +626,7 @@
 			const $this = $( this ),
 				assignedBucket = +$this.val(),
 				bannerName = $this.data( 'banner-name' ),
-				removed = ( removedBanners.indexOf( bannerName ) !== -1 );
+				removed = ( removedBanners.includes( bannerName ) );
 
 			// Iterate over all buckets, adding banners to the index or removeing them,
 			// as needed. (assignedBanners has elements for all possible buckets.)
diff --git a/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js b/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
index 00589dd..43b3bfb 100644
--- a/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
+++ b/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
@@ -680,7 +680,7 @@
 	};
 
 	BannerSequenceUiModel.prototype.validateSkipWithIdentifier = function ( id ) {
-		return ( typeof id === 'string' && id.indexOf( '|' ) === -1 ) || id === null;
+		return ( typeof id === 'string' && !id.includes( '|' ) ) || id === null;
 	};
 
 	BannerSequenceUiModel.prototype.validateDays = function ( days ) {
@@ -719,7 +719,7 @@
 		for ( let i = 0; i < sequence.length; i++ ) {
 			const banner = sequence[ i ].banner;
 
-			if ( banner !== null && assignedBanners.indexOf( banner ) === -1 ) {
+			if ( banner !== null && !assignedBanners.includes( banner ) ) {
 				stepsWithMissingBanners.push( i );
 				sequence[ i ].banner = this.defaultBanner();
 			}
diff --git a/tests/qunit/.eslintrc.json b/tests/qunit/.eslintrc.json
index 839f531..91964e4 100644
--- a/tests/qunit/.eslintrc.json
+++ b/tests/qunit/.eslintrc.json
@@ -5,6 +5,7 @@
 		"wikimedia/qunit"
 	],
 	"rules": {
-		"no-jquery/no-parse-html-literal": "off"
+		"no-jquery/no-parse-html-literal": "off",
+		"no-jquery/no-done-fail": "warn"
 	}
 }
-- 
2.39.2

$ date
--- stdout ---
Mon Mar 31 01:02:05 UTC 2025

--- end ---
$ git clone file:///srv/git/mediawiki-extensions-CentralNotice.git repo --depth=1 -b master
--- stderr ---
Cloning into '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 ---
0b32a350476da5a1cf94527ffbe3044292dccc8b refs/heads/master

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "@wdio/cli": {
      "name": "@wdio/cli",
      "severity": "high",
      "isDirect": true,
      "via": [
        "webdriverio",
        "yarn-install"
      ],
      "effects": [
        "@wdio/junit-reporter",
        "@wdio/local-runner",
        "@wdio/spec-reporter"
      ],
      "range": "5.4.10 - 8.43.0",
      "nodes": [
        "node_modules/@wdio/cli"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/junit-reporter": {
      "name": "@wdio/junit-reporter",
      "severity": "high",
      "isDirect": true,
      "via": [
        "@wdio/cli"
      ],
      "effects": [],
      "range": "6.0.4 - 8.0.0-alpha.631",
      "nodes": [
        "node_modules/@wdio/junit-reporter"
      ],
      "fixAvailable": {
        "name": "@wdio/junit-reporter",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/local-runner": {
      "name": "@wdio/local-runner",
      "severity": "high",
      "isDirect": true,
      "via": [
        "@wdio/cli",
        "@wdio/runner"
      ],
      "effects": [],
      "range": "6.0.4 - 8.43.0",
      "nodes": [
        "node_modules/@wdio/local-runner"
      ],
      "fixAvailable": {
        "name": "@wdio/local-runner",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/runner": {
      "name": "@wdio/runner",
      "severity": "high",
      "isDirect": false,
      "via": [
        "webdriverio"
      ],
      "effects": [
        "@wdio/local-runner"
      ],
      "range": "7.16.5 - 8.43.0",
      "nodes": [
        "node_modules/@wdio/runner"
      ],
      "fixAvailable": {
        "name": "@wdio/local-runner",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/spec-reporter": {
      "name": "@wdio/spec-reporter",
      "severity": "high",
      "isDirect": true,
      "via": [
        "@wdio/cli"
      ],
      "effects": [],
      "range": "6.0.4 - 8.0.0-alpha.631",
      "nodes": [
        "node_modules/@wdio/spec-reporter"
      ],
      "fixAvailable": {
        "name": "@wdio/spec-reporter",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "cross-spawn": {
      "name": "cross-spawn",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1100562,
          "name": "cross-spawn",
          "dependency": "cross-spawn",
          "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn",
          "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275",
          "severity": "high",
          "cwe": [
            "CWE-1333"
          ],
          "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": "<6.0.6"
        }
      ],
      "effects": [
        "yarn-install"
      ],
      "range": "<6.0.6",
      "nodes": [
        "node_modules/yarn-install/node_modules/cross-spawn"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "devtools": {
      "name": "devtools",
      "severity": "high",
      "isDirect": false,
      "via": [
        "puppeteer-core"
      ],
      "effects": [],
      "range": ">=7.16.5",
      "nodes": [
        "node_modules/devtools"
      ],
      "fixAvailable": true
    },
    "mwbot": {
      "name": "mwbot",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "request"
      ],
      "effects": [
        "wdio-mediawiki"
      ],
      "range": ">=0.1.6",
      "nodes": [
        "node_modules/mwbot"
      ],
      "fixAvailable": false
    },
    "puppeteer-core": {
      "name": "puppeteer-core",
      "severity": "high",
      "isDirect": false,
      "via": [
        "ws"
      ],
      "effects": [
        "devtools",
        "webdriverio"
      ],
      "range": "11.0.0 - 22.11.1",
      "nodes": [
        "node_modules/devtools/node_modules/puppeteer-core",
        "node_modules/puppeteer-core"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "request": {
      "name": "request",
      "severity": "moderate",
      "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"
        },
        "tough-cookie"
      ],
      "effects": [
        "mwbot"
      ],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": false
    },
    "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": false
    },
    "wdio-mediawiki": {
      "name": "wdio-mediawiki",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "mwbot"
      ],
      "effects": [],
      "range": "*",
      "nodes": [
        "node_modules/wdio-mediawiki"
      ],
      "fixAvailable": false
    },
    "webdriverio": {
      "name": "webdriverio",
      "severity": "high",
      "isDirect": false,
      "via": [
        "devtools",
        "puppeteer-core"
      ],
      "effects": [
        "@wdio/cli",
        "@wdio/runner"
      ],
      "range": "7.16.5 - 8.43.0",
      "nodes": [
        "node_modules/webdriverio"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "ws": {
      "name": "ws",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1098392,
          "name": "ws",
          "dependency": "ws",
          "title": "ws affected by a DoS when handling a request with many HTTP headers",
          "url": "https://github.com/advisories/GHSA-3h5v-q93c-6h6q",
          "severity": "high",
          "cwe": [
            "CWE-476"
          ],
          "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": ">=8.0.0 <8.17.1"
        }
      ],
      "effects": [
        "puppeteer-core"
      ],
      "range": "8.0.0 - 8.17.0",
      "nodes": [
        "node_modules/devtools/node_modules/ws",
        "node_modules/ws"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "yarn-install": {
      "name": "yarn-install",
      "severity": "high",
      "isDirect": false,
      "via": [
        "cross-spawn"
      ],
      "effects": [
        "@wdio/cli"
      ],
      "range": "*",
      "nodes": [
        "node_modules/yarn-install"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 4,
      "high": 11,
      "critical": 0,
      "total": 15
    },
    "dependencies": {
      "prod": 1,
      "dev": 775,
      "optional": 2,
      "peer": 1,
      "peerOptional": 0,
      "total": 775
    }
  }
}

--- 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: 38 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.3.2)
  - Locking composer/semver (3.4.3)
  - Locking composer/spdx-licenses (1.5.8)
  - Locking composer/xdebug-handler (3.0.5)
  - Locking dealerdirect/phpcodesniffer-composer-installer (v1.0.0)
  - Locking doctrine/deprecations (1.1.4)
  - Locking felixfbecker/advanced-json-rpc (v3.2.1)
  - Locking mediawiki/mediawiki-codesniffer (v46.0.0)
  - Locking mediawiki/mediawiki-phan-config (0.15.1)
  - Locking mediawiki/minus-x (1.1.3)
  - Locking mediawiki/phan-taint-check-plugin (6.1.0)
  - Locking microsoft/tolerant-php-parser (v0.1.2)
  - Locking netresearch/jsonmapper (v4.5.0)
  - Locking phan/phan (5.4.5)
  - 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.2.1)
  - Locking phpcsstandards/phpcsutils (1.0.12)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (5.6.1)
  - Locking phpdocumentor/type-resolver (1.10.0)
  - Locking phpstan/phpdoc-parser (2.1.0)
  - Locking psr/container (2.0.2)
  - Locking psr/log (3.0.2)
  - Locking sabre/event (5.1.7)
  - Locking squizlabs/php_codesniffer (3.11.3)
  - Locking symfony/console (v7.2.5)
  - Locking symfony/deprecation-contracts (v3.5.1)
  - Locking symfony/polyfill-ctype (v1.31.0)
  - Locking symfony/polyfill-intl-grapheme (v1.31.0)
  - Locking symfony/polyfill-intl-normalizer (v1.31.0)
  - Locking symfony/polyfill-mbstring (v1.31.0)
  - Locking symfony/polyfill-php80 (v1.31.0)
  - Locking symfony/service-contracts (v3.5.1)
  - Locking symfony/string (v7.2.0)
  - Locking tysonandre/var_representation_polyfill (0.1.3)
  - Locking webmozart/assert (1.11.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 38 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Installing squizlabs/php_codesniffer (3.11.3): Extracting archive
  - Installing dealerdirect/phpcodesniffer-composer-installer (v1.0.0): Extracting archive
  - Installing composer/pcre (3.3.2): Extracting archive
  - Installing symfony/polyfill-php80 (v1.31.0): Extracting archive
  - Installing phpcsstandards/phpcsutils (1.0.12): Extracting archive
  - Installing phpcsstandards/phpcsextra (1.2.1): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.31.0): Extracting archive
  - Installing composer/spdx-licenses (1.5.8): Extracting archive
  - Installing composer/semver (3.4.3): Extracting archive
  - Installing mediawiki/mediawiki-codesniffer (v46.0.0): Extracting archive
  - Installing tysonandre/var_representation_polyfill (0.1.3): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.31.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.31.0): Extracting archive
  - Installing symfony/polyfill-ctype (v1.31.0): Extracting archive
  - Installing symfony/string (v7.2.0): Extracting archive
  - Installing symfony/deprecation-contracts (v3.5.1): Extracting archive
  - Installing psr/container (2.0.2): Extracting archive
  - Installing symfony/service-contracts (v3.5.1): Extracting archive
  - Installing symfony/console (v7.2.5): Extracting archive
  - Installing sabre/event (5.1.7): Extracting archive
  - Installing netresearch/jsonmapper (v4.5.0): Extracting archive
  - Installing microsoft/tolerant-php-parser (v0.1.2): Extracting archive
  - Installing webmozart/assert (1.11.0): Extracting archive
  - Installing phpstan/phpdoc-parser (2.1.0): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing doctrine/deprecations (1.1.4): Extracting archive
  - Installing phpdocumentor/type-resolver (1.10.0): Extracting archive
  - Installing phpdocumentor/reflection-docblock (5.6.1): Extracting archive
  - Installing felixfbecker/advanced-json-rpc (v3.2.1): Extracting archive
  - Installing psr/log (3.0.2): Extracting archive
  - Installing composer/xdebug-handler (3.0.5): Extracting archive
  - Installing phan/phan (5.4.5): Extracting archive
  - Installing mediawiki/phan-taint-check-plugin (6.1.0): Extracting archive
  - Installing mediawiki/mediawiki-phan-config (0.15.1): Extracting archive
  - Installing mediawiki/minus-x (1.1.3): 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/36 [>---------------------------]   0%
 20/36 [===============>------------]  55%
 30/36 [=======================>----]  83%
 36/36 [============================] 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:@wdio/cli from 7.33.0 -> 7.40.0
Upgrading n:@wdio/junit-reporter from 7.33.0 -> 7.40.0
Upgrading n:@wdio/local-runner from 7.33.0 -> 7.40.0
Upgrading n:@wdio/mocha-framework from 7.33.0 -> 7.40.0
Upgrading n:eslint-config-wikimedia from 0.28.2 -> 0.29.1
Upgrading n:@wdio/spec-reporter from 7.33.0 -> 7.40.0
$ /usr/bin/npm install
--- stderr ---
npm WARN deprecated @types/easy-table@1.2.0: This is a stub types definition. easy-table provides its own type definitions, so you do not need this installed.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@7.1.7: 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
--- stdout ---

added 773 packages, and audited 774 packages in 10s

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

15 vulnerabilities (4 moderate, 11 high)

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

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

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

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

up to date, audited 774 packages in 3s

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

15 vulnerabilities (4 moderate, 11 high)

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

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

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

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

/src/repo/resources/ext.centralNotice.display/bucketer.js
  45:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  48:1  warning  Missing JSDoc @param "name" type   jsdoc/require-param-type
  54:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  57:1  warning  Missing JSDoc @param "name" type   jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/chooser.js
   23:1  warning  Missing JSDoc @param "availableCampaigns" type         jsdoc/require-param-type
  131:2  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  140:1  warning  Missing JSDoc @param "campaign" type                   jsdoc/require-param-type
  141:1  warning  Missing JSDoc @param "bucket" type                     jsdoc/require-param-type
  142:1  warning  Missing JSDoc @param "anon" type                       jsdoc/require-param-type
  143:1  warning  Missing JSDoc @param "device" type                     jsdoc/require-param-type
  184:1  warning  Missing JSDoc @param "possibleBanners" type            jsdoc/require-param-type
  261:1  warning  Missing JSDoc @param "choiceData" type                 jsdoc/require-param-type
  262:1  warning  Missing JSDoc @param "country" type                    jsdoc/require-param-type
  263:1  warning  Missing JSDoc @param "region" type                     jsdoc/require-param-type
  264:1  warning  Missing JSDoc @param "anon" type                       jsdoc/require-param-type
  265:1  warning  Missing JSDoc @param "device" type                     jsdoc/require-param-type
  331:1  warning  This line has a length of 101. Maximum allowed is 100  max-len
  345:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  350:1  warning  Missing JSDoc @param "choiceData" type                 jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/index.js
   60:1  warning  Missing JSDoc @param "name" type         jsdoc/require-param-type
   73:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
   83:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
   94:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
  106:1  warning  Missing JSDoc @param "campaign" type     jsdoc/require-param-type
  246:3  error    Prefer .then to .fail                    no-jquery/no-done-fail
  278:3  error    Prefer .then to .done                    no-jquery/no-done-fail
  557:1  warning  Missing JSDoc @param "iteration" type    jsdoc/require-param-type
  626:1  warning  Missing JSDoc @param "bannerJson" type   jsdoc/require-param-type
  723:1  warning  Missing JSDoc @param "reason" type       jsdoc/require-param-type
  733:3  warning  Missing JSDoc @return declaration        jsdoc/require-returns
  748:1  warning  Missing JSDoc @param "reason" type       jsdoc/require-param-type
  760:1  warning  Missing JSDoc @param "rate" type         jsdoc/require-param-type
  772:1  warning  Missing JSDoc @param "rate" type         jsdoc/require-param-type
  820:4  error    Prefer .then to .fail                    no-jquery/no-done-fail
  820:4  error    Prefer .then to .done                    no-jquery/no-done-fail
  830:4  error    Prefer .then to .fail                    no-jquery/no-done-fail
  830:4  error    Prefer .then to .done                    no-jquery/no-done-fail
  884:1  warning  Missing JSDoc @param "bucket" type       jsdoc/require-param-type
  945:3  warning  Missing JSDoc @return declaration        jsdoc/require-returns
  949:1  warning  Missing JSDoc @param "prop" type         jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/state.js
  107:1  warning  This line has a length of 128. Maximum allowed is 100  max-len
  117:2  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  301:1  warning  Missing JSDoc @param "geo" type                        jsdoc/require-param-type
  335:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  345:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  380:1  warning  Missing JSDoc @param "availableCampaigns" type         jsdoc/require-param-type
  410:1  warning  This line has a length of 101. Maximum allowed is 100  max-len
  447:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  486:1  warning  Missing JSDoc @param "reason" type                     jsdoc/require-param-type
  497:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  548:1  warning  Missing JSDoc @param "bannerCount" type                jsdoc/require-param-type
  558:1  warning  Missing JSDoc @param "rate" type                       jsdoc/require-param-type
  571:1  warning  Missing JSDoc @param "rate" type                       jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.kvStore/kvStore.js
   50:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
   67:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  394:3  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  459:3  warning  Missing JSDoc @return declaration  jsdoc/require-returns

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

/src/repo/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
  51:1  warning  Missing JSDoc @param "queue" type  jsdoc/require-param-type

/src/repo/resources/infrastructure/bannereditor.js
   49:3  error  Prefer .then to .fail  no-jquery/no-done-fail
   49:3  error  Prefer .then to .done  no-jquery/no-done-fail
  116:3  error  Prefer .then to .fail  no-jquery/no-done-fail

/src/repo/resources/infrastructure/bannermanager.js
  249:3  warning  Missing JSDoc @return declaration           jsdoc/require-returns
  253:1  warning  Missing JSDoc @param "e" type               jsdoc/require-param-type
  262:3  warning  Missing JSDoc @return declaration           jsdoc/require-returns
  267:1  warning  Missing JSDoc @param "$origFilterStr" type  jsdoc/require-param-type

/src/repo/resources/infrastructure/campaignManager.js
  797:1  warning  This line has a length of 111. Maximum allowed is 100  max-len
  805:3  error    Prefer .then to .done                                  no-jquery/no-done-fail

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
   543:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   550:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   561:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   568:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   575:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   611:1  warning  Missing JSDoc @param "seq" type         jsdoc/require-param-type
   635:1  warning  Missing JSDoc @param "step" type        jsdoc/require-param-type
   690:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   691:1  warning  Missing JSDoc @param "n" type           jsdoc/require-param-type
   734:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
   735:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
   802:5  error    Prefer .then to .done                   no-jquery/no-done-fail
   802:5  error    Prefer .then to .fail                   no-jquery/no-done-fail
   892:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   893:1  warning  Missing JSDoc @param "stepNum" type     jsdoc/require-param-type
   918:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   919:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type
   932:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   944:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
   945:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
   946:1  warning  Missing JSDoc @param "config" type      jsdoc/require-param-type
  1050:1  warning  Missing JSDoc @param "stepModel" type   jsdoc/require-param-type
  1051:1  warning  Missing JSDoc @param "index" type       jsdoc/require-param-type
  1126:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type
  1181:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
  1182:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
  1183:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
  1184:1  warning  Missing JSDoc @param "config" type      jsdoc/require-param-type
  1317:5  error    Prefer .then to .done                   no-jquery/no-done-fail
  1317:5  error    Prefer .then to .fail                   no-jquery/no-done-fail
  1353:5  error    Prefer .then to .done                   no-jquery/no-done-fail
  1353:5  error    Prefer .then to .fail                   no-jquery/no-done-fail
  1509:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
  1510:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.campaignPager.js
  13:1  warning  Missing JSDoc @param "campaignName" type  jsdoc/require-param-type
  14:1  warning  Missing JSDoc @param "property" type      jsdoc/require-param-type
  15:1  warning  Missing JSDoc @param "value" type         jsdoc/require-param-type
  16:1  warning  Missing JSDoc @param "initialValue" type  jsdoc/require-param-type

/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
  225:1  warning  Missing JSDoc @param "elData" type  jsdoc/require-param-type
  232:2  warning  Missing JSDoc @return declaration   jsdoc/require-returns
  236:1  warning  Missing JSDoc @param "elData" type  jsdoc/require-param-type
  300:4  error    Prefer .then to .done               no-jquery/no-done-fail
  371:4  error    Prefer .then to .done               no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.geoIP.js
  124:30  warning  Found non-literal argument in require  security/detect-non-literal-require
  159:2   error    Prefer .then to .done                  no-jquery/no-done-fail

/src/repo/tests/qunit/ext.centralNotice.display/chooser.tests.js
   37:2  warning  Missing JSDoc @return declaration             jsdoc/require-returns
   41:1  warning  Missing JSDoc @param "contextAndOutput" type  jsdoc/require-param-type
   42:1  warning  Missing JSDoc @param "bucket" type            jsdoc/require-param-type
  197:1  warning  Missing JSDoc @param "choices" type           jsdoc/require-param-type

/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js
  392:1  warning  Missing JSDoc @param "campaignsData" type  jsdoc/require-param-type
  441:3  error    Prefer .then to .done                      no-jquery/no-done-fail
  481:3  error    Prefer .then to .done                      no-jquery/no-done-fail

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

✖ 124 problems (26 errors, 98 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":[{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":45,"column":2,"nodeType":"Block","endLine":49,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"name\" type.","line":48,"column":1,"nodeType":"Block","endLine":48,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":54,"column":2,"nodeType":"Block","endLine":58,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"name\" type.","line":57,"column":1,"nodeType":"Block","endLine":57,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-jquery/no-map-util","severity":2,"message":"Prefer Array#map to $.map","line":134,"column":22,"nodeType":"CallExpression","endLine":147,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Storage, retrieval and other processing of buckets. Provides\n * cn.internal.bucketer.\n *\n * Bucket assignments are stored using the kvStore, in LocalStorage or a cookie.\n * To maximize concision for users that fall back to cookies, the value consists\n * of '*'-separated campaigns, each of which is made up of '!'-separated fields.\n * The format is:\n *\n *  NAME!START!END!VALUE[*NAME!START!END!VALUE..]\n *\n * - Start is stored as a second offset from UNIX timestamp 1400000000\n *   (March 2014).\n * - End is stored as second offset from start.\n *\n * For example:\n *\n * 'WikiConference_USA!39942400!3729600!2'\n *\n * ...would be deserialized to:\n *\n * { WikiConference_USA: { start: 1439942400, end: 1443672000, val: 2 } }\n *\n */\n( function () {\n\n\t// Bucket objects by campaign; properties are campaign names.\n\t// Retrieved from kvStore (which uses LocalStorage or a fallback cookie)\n\t// or from a legacy cookie.\n\tlet buckets = null,\n\n\t\t// The campaign we're working with.\n\t\tcampaign = null,\n\n\t\tmultiStorageOption;\n\tconst kvStore = mw.centralNotice.kvStore,\n\n\t\t// Name of the legacy cookie for CentralNotice buckets. Its value is\n\t\t// a compact serialization of buckets in the same format as is\n\t\t// currently used here.\n\t\tLEGACY_COOKIE = 'CN',\n\n\t\tSTORAGE_KEY = 'buckets';\n\n\t/**\n\t * Escape '*' and '!' in a campaign name to make it safe for serialization.\n\t *\n\t * @param name\n\t */\n\tfunction escapeCampaignName( name ) {\n\t\treturn name.replace( /[*!]/g, ( match ) => '&#' + match.charCodeAt( 0 ) );\n\t}\n\n\t/**\n\t * Decode any escaped '*' and '!' characters in a serialized campaign name.\n\t *\n\t * @param name\n\t */\n\tfunction decodeCampaignName( name ) {\n\t\treturn name.replace( /&#(33|42)/, ( match, $1 ) => String.fromCharCode( $1 ) );\n\t}\n\n\tfunction parseSerializedBuckets( serialized ) {\n\n\t\tconst parsedBuckets = {};\n\n\t\tserialized.split( '*' ).forEach( ( strBucket ) => {\n\t\t\tconst parts = strBucket.split( '!' ),\n\t\t\t\tkey = decodeCampaignName( parts[ 0 ] ),\n\t\t\t\tstart = parseInt( parts[ 1 ], 10 ) + 14e8,\n\t\t\t\tend = start + parseInt( parts[ 2 ], 10 ),\n\t\t\t\tval = parseInt( parts[ 3 ], 10 );\n\n\t\t\tif ( key && start && end && !isNaN( val ) ) {\n\t\t\t\tparsedBuckets[ key ] = {\n\t\t\t\t\tstart: start,\n\t\t\t\t\tend: end,\n\t\t\t\t\tval: val\n\t\t\t\t};\n\t\t\t}\n\t\t} );\n\n\t\treturn parsedBuckets;\n\t}\n\n\t/**\n\t * Check legacy bucket cookie, and try to migrate. If a legacy cookie is\n\t * found, load buckets from there.\n\t *\n\t * @return {boolean} true if a legacy cookie was migrated, false if not\n\t */\n\tfunction possiblyLoadAndMigrateLegacyBuckets() {\n\n\t\tconst cookieVal = $.cookie( LEGACY_COOKIE );\n\n\t\tif ( cookieVal ) {\n\n\t\t\t// We need to deserialize and store again to determine ttl\n\t\t\tbuckets = parseSerializedBuckets( cookieVal );\n\t\t\tstoreBuckets();\n\t\t\t$.removeCookie( LEGACY_COOKIE, { path: '/' } );\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Attempt to get buckets from the storage. If no stored buckets are\n\t * found, set buckets to an empty object.\n\t */\n\tfunction loadBuckets() {\n\n\t\tconst val = kvStore.getItem(\n\t\t\tSTORAGE_KEY,\n\t\t\tkvStore.contexts.GLOBAL,\n\t\t\tmultiStorageOption\n\t\t);\n\n\t\tbuckets = ( val ? parseSerializedBuckets( val ) : {} );\n\t}\n\n\t/**\n\t * Store buckets using the kvStore. The storage item will be set to\n\t * expire after the all the buckets it contains do.\n\t *\n\t * Though compact serialization is no longer needed for the majority\n\t * of users, who'll get LocalStorage, it's useful for those who fall\n\t * back to cookies, and it seems preferable to keep things consistent.\n\t */\n\tfunction storeBuckets() {\n\t\tlet expires = Math.ceil( Date.now() / 1000 );\n\t\t// eslint-disable-next-line no-jquery/no-map-util\n\t\tconst serialized = $.map( buckets, ( opts, key ) => {\n\t\t\tconst parts = [\n\t\t\t\tescapeCampaignName( key ),\n\t\t\t\tMath.floor( opts.start - 14e8 ),\n\t\t\t\tMath.ceil( opts.end - opts.start ),\n\t\t\t\topts.val\n\t\t\t];\n\n\t\t\tif ( opts.end > expires ) {\n\t\t\t\texpires = Math.ceil( opts.end );\n\t\t\t}\n\n\t\t\treturn parts.join( '!' );\n\t\t} ).join( '*' );\n\n\t\tkvStore.setItem(\n\t\t\tSTORAGE_KEY,\n\t\t\tserialized,\n\t\t\tkvStore.contexts.GLOBAL,\n\t\t\t// Convert expires to ttl in days\n\t\t\tMath.ceil( ( expires - ( Date.now() / 1000 ) ) / 86400 ),\n\t\t\tmultiStorageOption\n\t\t);\n\t}\n\n\t/**\n\t * Get a random bucket (integer greater or equal to 0 and less than\n\t * wgNoticeNumberOfControllerBuckets).\n\t *\n\t * @return {number}\n\t */\n\tfunction getRandomBucket() {\n\t\treturn Math.floor(\n\t\t\tMath.random() * mw.config.get( 'wgNoticeNumberOfControllerBuckets' )\n\t\t);\n\t}\n\n\t/**\n\t * Do all things bucket:\n\t * - Get buckets from the kvStore or legacy cookie, if available.\n\t * - If necessary, generate a random bucket for the campaign.\n\t * - Ensure the stored end date for this campaign is up-to-date.\n\t * - Go through all the buckets, purging expired buckets.\n\t * - Store the updated bucket data using the kvStore.\n\t *\n\t * This should be called before a bucket is requested but after\n\t * setCampaign() has been called.\n\t */\n\tfunction retrieveProcessAndGet() {\n\n\t\tlet campaignName = campaign.name,\n\t\t\tbucketsModified = false;\n\t\tconst extension = mw.config.get( 'wgCentralNoticePerCampaignBucketExtension' ),\n\t\t\tnow = new Date();\n\n\t\tconst campaignStartDate = new Date();\n\t\tcampaignStartDate.setTime( campaign.start * 1000 );\n\n\t\t// Buckets should end the time indicated by extension after\n\t\t// the campaign's end\n\t\tlet bucketEndDate = new Date();\n\t\tbucketEndDate.setTime( campaign.end * 1000 );\n\t\tbucketEndDate.setUTCDate( bucketEndDate.getUTCDate() + extension );\n\n\t\t// Check if and how we can store buckets. Allow cookie fallback in all\n\t\t// cases in which localStorage isn't available.\n\n\t\t// If we have no storage options (cookies and localStorage disabled),\n\t\t// loading and storing buckets will be no-ops, and a new bucket will be\n\t\t// chosen every time.\n\t\tmultiStorageOption = kvStore.getMultiStorageOption( true );\n\n\t\t// In all cases, check for a legacy cookie and try to migrate if one\n\t\t// was found. Otherwise, load normally.\n\t\tif ( !possiblyLoadAndMigrateLegacyBuckets() ) {\n\t\t\tloadBuckets();\n\t\t}\n\n\t\tlet bucket = buckets[ campaignName ];\n\n\t\t// If we have a valid bucket, just check and possibly update its\n\t\t// expiry.\n\n\t\t// Note that buckets that are expired but that were retrieved from\n\t\t// storage (because they didn't have the chance to get purged) are\n\t\t// not considered valid. In that case, for consistency, we choose a\n\t\t// new random bucket, just as if no bucket had been found.\n\n\t\tif ( bucket && bucketEndDate > now ) {\n\n\t\t\tconst retrievedBucketEndDate = new Date();\n\t\t\tretrievedBucketEndDate.setTime( bucket.end * 1000 );\n\n\t\t\tif ( retrievedBucketEndDate.getTime() !== bucketEndDate.getTime() ) {\n\t\t\t\tbucket.end = bucketEndDate.getTime() / 1000;\n\t\t\t\tbucketsModified = true;\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t// We always use wgNoticeNumberOfControllerBuckets, and\n\t\t\t// not the campaign's number of buckets, to determine\n\t\t\t// how many possible buckets to randomly choose from. If\n\t\t\t// the campaign actually has less buckets than that,\n\t\t\t// the value is mapped down as necessary. This lets\n\t\t\t// campaigns modify the number of buckets they use.\n\t\t\tconst val = getRandomBucket();\n\n\t\t\tbucket = {\n\t\t\t\tval: val,\n\t\t\t\tstart: campaignStartDate.getTime() / 1000,\n\t\t\t\tend: bucketEndDate.getTime() / 1000\n\t\t\t};\n\n\t\t\tbuckets[ campaignName ] = bucket;\n\t\t\tbucketsModified = true;\n\t\t}\n\n\t\t// Purge any expired buckets\n\t\tfor ( campaignName in buckets ) {\n\n\t\t\tbucketEndDate = new Date();\n\t\t\tbucketEndDate.setTime( buckets[ campaignName ].end * 1000 );\n\n\t\t\tif ( bucketEndDate < now ) {\n\t\t\t\tdelete buckets[ campaignName ];\n\t\t\t\tbucketsModified = true;\n\t\t\t}\n\t\t}\n\n\t\t// Store the buckets if there were changes\n\t\tif ( bucketsModified ) {\n\t\t\tstoreBuckets();\n\t\t}\n\t}\n\n\t/**\n\t * Bucketer object (intended for access from within this RL module).\n\t */\n\tconst bucketer = mw.centralNotice.internal.bucketer = {\n\n\t\t/**\n\t\t * @param {Object} c A campaign object. Note: we don't check that the\n\t\t * object is valid.\n\t\t */\n\t\tsetCampaign: function ( c ) {\n\t\t\tcampaign = c;\n\t\t},\n\n\t\t/**\n\t\t * This should only be called once. setCampaign() must have been called\n\t\t * first.\n\t\t */\n\t\tprocess: function () {\n\t\t\tretrieveProcessAndGet();\n\t\t},\n\n\t\t/**\n\t\t * Get the bucket for this user for the campaign sent in setCampaign().\n\t\t * setCampaign() and process() must have been called first. (Normally\n\t\t * they're called by mw.centralNotice.chooseAndMaybeDisplay(). Calls\n\t\t * to this method from mixin hooks don't have to worry about this.)\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tgetBucket: function () {\n\t\t\treturn buckets[ campaign.name ].val;\n\t\t},\n\n\t\t/**\n\t\t * Return the bucket pared down to fit within the number of buckets in the\n\t\t * campaign. Note: setCampaign() must have been called first.\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tgetReducedBucket: function () {\n\t\t\treturn bucketer.getBucket() % campaign.bucket_count;\n\t\t},\n\n\t\t/**\n\t\t * Store this bucket value for this user for the campaign sent in\n\t\t * setCampaign().\n\t\t * setCampaign() and process() must have been called first. (Normally\n\t\t * they're called by mw.centralNotice.chooseAndMaybeDisplay(). Calls\n\t\t * to this method from mixin hooks don't have to worry about this.)\n\t\t *\n\t\t * @param {number} val The numeric bucket value to store\n\t\t */\n\t\tsetBucket: function ( val ) {\n\t\t\tbuckets[ campaign.name ].val = val;\n\t\t\tstoreBuckets();\n\t\t}\n\t};\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/chooser.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"availableCampaigns\" type.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":131,"column":2,"nodeType":"Block","endLine":144,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"campaign\" type.","line":140,"column":1,"nodeType":"Block","endLine":140,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":141,"column":1,"nodeType":"Block","endLine":141,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"anon\" type.","line":142,"column":1,"nodeType":"Block","endLine":142,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"device\" type.","line":143,"column":1,"nodeType":"Block","endLine":143,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"possibleBanners\" type.","line":184,"column":1,"nodeType":"Block","endLine":184,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"choiceData\" type.","line":261,"column":1,"nodeType":"Block","endLine":261,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"country\" type.","line":262,"column":1,"nodeType":"Block","endLine":262,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"region\" type.","line":263,"column":1,"nodeType":"Block","endLine":263,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"anon\" type.","line":264,"column":1,"nodeType":"Block","endLine":264,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"device\" type.","line":265,"column":1,"nodeType":"Block","endLine":265,"endColumn":1},{"ruleId":"max-len","severity":1,"message":"This line has a length of 101. Maximum allowed is 100.","line":331,"column":1,"nodeType":"Program","messageId":"max","endLine":331,"endColumn":90},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":345,"column":3,"nodeType":"Block","endLine":351,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"choiceData\" type.","line":350,"column":1,"nodeType":"Block","endLine":350,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'campaignName' is assigned a value but never used.","line":149,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":149,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'fallbackLoopIndex' is defined but never used.","line":321,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":321,"endColumn":37,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":15,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* eslint-disable no-unused-vars */\n/**\n * Logic for selecting a campaign and a banner (or not). Provides\n * cn.internal.chooser.\n */\n( function () {\n\n\tconst cn = mw.centralNotice,\n\n\t\t// Minutes leeway for checking stale choice data. Should be the same\n\t\t// as SpecialBannerLoader::CAMPAIGN_STALENESS_LEEWAY.\n\t\t// TODO Make this a global config variable.\n\t\tCAMPAIGN_STALENESS_LEEWAY = 15;\n\n\t/**\n\t * For targeted users (users meeting the same logged-in status, country,\n\t * and language criteria as this user) calculate the probability that\n\t * of receiving each campaign in availableCampaigns, and set the probability\n\t * on the allocation property of each campaign. This takes into account\n\t * campaign priority and throttling. The equivalent server-side method\n\t * is AllocationCalculator::calculateCampaignAllocations().\n\t *\n\t * @param availableCampaigns\n\t */\n\tfunction setCampaignAllocations( availableCampaigns ) {\n\n\t\tconst campaignsByPriority = [],\n\t\t\tpriorities = [];\n\t\tlet remainingAllocation = 1;\n\n\t\t// Optimize for the common scenario of a single campaign\n\t\tif ( availableCampaigns.length === 1 ) {\n\t\t\tavailableCampaigns[ 0 ].allocation = availableCampaigns[ 0 ].throttle / 100;\n\t\t\treturn;\n\t\t}\n\n\t\t// Make an index of campaigns by priority level.\n\t\t// Note that the actual values of priority levels are integers,\n\t\t// and higher integers represent higher priority. These values are\n\t\t// defined by class constants in the CentralNotice PHP class.\n\n\t\tfor ( let i = 0; i < availableCampaigns.length; i++ ) {\n\n\t\t\tconst campaign = availableCampaigns[ i ];\n\t\t\tconst campaignPriority = campaign.preferred;\n\n\t\t\t// Initialize index the first time we hit this priority\n\t\t\tif ( !campaignsByPriority[ campaignPriority ] ) {\n\t\t\t\tcampaignsByPriority[ campaignPriority ] = [];\n\t\t\t}\n\n\t\t\tcampaignsByPriority[ campaignPriority ].push( campaign );\n\t\t}\n\n\t\t// Make an array of priority levels and sort in descending order.\n\t\tfor ( const priority in campaignsByPriority ) {\n\t\t\tpriorities.push( priority );\n\t\t}\n\t\tpriorities.sort();\n\t\tpriorities.reverse();\n\n\t\t// Now go through the priority levels from highest to lowest. If\n\t\t// campaigns are not throttled, then campaigns with a higher\n\t\t// priority level will eclipse all campaigns with lower priority.\n\t\t// Only if some campaigns are throttled will they allow some space\n\t\t// for campaigns at the next level down.\n\n\t\tfor ( let i = 0; i < priorities.length; i++ ) {\n\n\t\t\tconst campaignsAtThisPriority = campaignsByPriority[ priorities[ i ] ];\n\n\t\t\t// If we fully allocated at a previous level, set allocations\n\t\t\t// at this level to zero. (We check with 0.01 instead of 0 in\n\t\t\t// case of issues due to finite precision.)\n\t\t\tif ( remainingAllocation < 0.01 ) {\n\t\t\t\tfor ( let j = 0; j < campaignsAtThisPriority.length; j++ ) {\n\t\t\t\t\tcampaignsAtThisPriority[ j ].allocation = 0;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If we are here, there is some allocation remaining.\n\n\t\t\t// All campaigns at a given priority level are alloted the same\n\t\t\t// allocation, unless they are throttled, in which case the\n\t\t\t// throttling value (taken as a percentage of the whole\n\t\t\t// allocation pie) is their maximum possible allocation.\n\n\t\t\t// To calculate this, we'll loop through the campaigns at this\n\t\t\t// level in order from the most throttled (lowest throttling\n\t\t\t// value) to the least throttled (highest value) and on each\n\t\t\t// loop, we'll re-calculate the remaining total allocation and\n\t\t\t// the proportional (i.e. unthrottled) allocation available to\n\t\t\t// each campaign.\n\n\t\t\t// First, sort the campaigns by throttling value (ascending)\n\n\t\t\tcampaignsAtThisPriority.sort( ( a, b ) => {\n\t\t\t\tif ( a.throttle < b.throttle ) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif ( a.throttle > b.throttle ) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t} );\n\n\t\t\tconst campaignsAtThisPriorityCount = campaignsAtThisPriority.length;\n\t\t\tfor ( let j = 0; j < campaignsAtThisPriorityCount; j++ ) {\n\n\t\t\t\tconst campaign = campaignsAtThisPriority[ j ];\n\n\t\t\t\t// Calculate the proportional, unthrottled allocation now\n\t\t\t\t// available to a campaign at this level.\n\t\t\t\tconst currentFullAllocation =\n\t\t\t\t\tremainingAllocation / ( campaignsAtThisPriorityCount - j );\n\n\t\t\t\t// A campaign may get the above amount, or less, if\n\t\t\t\t// throttling indicates that'd be too much.\n\t\t\t\tconst actualAllocation =\n\t\t\t\t\tMath.min( currentFullAllocation, campaign.throttle / 100 );\n\n\t\t\t\tcampaign.allocation = actualAllocation;\n\n\t\t\t\t// Update remaining allocation\n\t\t\t\tremainingAllocation -= actualAllocation;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Filter banners for this campaign on the user's logged-in status,\n\t * device and bucket (some banners that are not for the user's status\n\t * or device may remain following previous filters) and return a list\n\t * of possible banners to chose from.\n\t *\n\t * The equivalent server-side method\n\t * AllocationCalculator::makePossibleBanners().\n\t *\n\t * @param campaign\n\t * @param bucket\n\t * @param anon\n\t * @param device\n\t */\n\tfunction makePossibleBanners( campaign, bucket, anon, device ) {\n\n\t\tconst possibleBanners = [];\n\n\t\tconst campaignName = campaign.name;\n\n\t\tfor ( let i = 0; i < campaign.banners.length; i++ ) {\n\t\t\tconst banner = campaign.banners[ i ];\n\n\t\t\t// Filter for bucket\n\t\t\tif ( bucket !== banner.bucket ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Filter for logged-in status\n\t\t\tif ( anon && !banner.display_anon ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ( !anon && !banner.display_account ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Filter for device\n\t\t\tif ( !banner.devices.includes( device ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpossibleBanners.push( banner );\n\t\t}\n\n\t\treturn possibleBanners;\n\t}\n\n\t/**\n\t * Calculate the allocation of banners (from a single campaign) based on\n\t * relative weights of banners in possibleBanners. The equivalent\n\t * server-side method is\n\t * AllocationCalculator::calculateBannerAllocations().\n\t *\n\t * @param possibleBanners\n\t */\n\tfunction setBannerAllocations( possibleBanners ) {\n\t\tlet totalWeights = 0;\n\n\t\t// Optimize for just one banner available for the user in this\n\t\t// campaign, by far our most common scenario.\n\t\tif ( possibleBanners.length === 1 ) {\n\t\t\tpossibleBanners[ 0 ].allocation = 1;\n\t\t\treturn;\n\t\t}\n\n\t\t// Find the sum of all banner weights\n\t\tfor ( let i = 0; i < possibleBanners.length; i++ ) {\n\t\t\ttotalWeights += possibleBanners[ i ].weight;\n\t\t}\n\n\t\t// Set allocation property to the normalized weight\n\t\tfor ( let i = 0; i < possibleBanners.length; i++ ) {\n\t\t\tconst banner = possibleBanners[ i ];\n\t\t\tbanner.allocation = banner.weight / totalWeights;\n\t\t}\n\t}\n\n\t/**\n\t * Method used for choosing a campaign or banner from an array of\n\t * allocated campaigns or banners.\n\t *\n\t * Given an array of objects with 'allocation' properties, the sum of which\n\t * is greater than or equal to 0 and less than or equal to 1, return the\n\t * object whose allocation block is indicated by a number greater than or\n\t * equal to 0 and less than 1.\n\t *\n\t * @param {number} random A random number, greater or equal to 0  and less\n\t *   than 1, to use in choosing an object.\n\t * @param {Array} allocatedArray\n\t * @return {?Object} The selected element in the array\n\t */\n\tfunction chooseObjInAllocatedArray( random, allocatedArray ) {\n\t\tlet blockStart = 0;\n\n\t\t// Cycle through objects, calculating which piece of\n\t\t// the allocation pie they should get. When random is in the piece,\n\t\t// choose the object.\n\n\t\tfor ( let i = 0; i < allocatedArray.length; i++ ) {\n\t\t\tconst obj = allocatedArray[ i ];\n\t\t\tconst blockEnd = blockStart + obj.allocation;\n\n\t\t\tif ( ( random >= blockStart ) && ( random < blockEnd ) ) {\n\t\t\t\treturn obj;\n\t\t\t}\n\n\t\t\tblockStart = blockEnd;\n\t\t}\n\n\t\t// We get here if there is less than full allocation (including no\n\t\t// allocation) and random points to the unallocated chunk.\n\t\treturn null;\n\t}\n\n\t/**\n\t * Chooser object (intended for access from within this RL module)\n\t */\n\tcn.internal.chooser = {\n\n\t\t/**\n\t\t * Filter choiceData on country, region, logged-in status and device.\n\t\t * Only campaigns that target the user's country and have at least\n\t\t * one banner for the user's logged-in status and device pass this filter.\n\t\t *\n\t\t * Campaigns that don't target the user's country or region, or have\n\t\t * no banners for their logged-in status and device will be removed.\n\t\t *\n\t\t * The server-side equivalent of this method is\n\t\t * AllocationCalculator::makeAvailableCampaigns().\n\t\t *\n\t\t * @param choiceData\n\t\t * @param country\n\t\t * @param region\n\t\t * @param anon\n\t\t * @param device\n\t\t * @return {Array}\n\t\t */\n\t\tmakeAvailableCampaigns: function ( choiceData, country, region, anon, device ) {\n\n\t\t\tconst availableCampaigns = [];\n\n\t\t\t// This needs to yield the same result as makeUniqueRegionCode in GeoTarget.php\n\t\t\tconst uniqueRegionCode = country + '_' + region;\n\n\t\t\tfor ( let i = 0; i < choiceData.length; i++ ) {\n\n\t\t\t\tconst campaign = choiceData[ i ];\n\t\t\t\tlet keepCampaign = false;\n\n\t\t\t\t// Filter for country if geotargeted\n\t\t\t\tif ( campaign.geotargeted && (\n\t\t\t\t\t!campaign.countries.includes( country ) && // No country wide match\n\t\t\t\t\t!campaign.regions.includes( uniqueRegionCode ) // And no region match\n\t\t\t\t) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Now filter by banner logged-in status and device.\n\t\t\t\tfor ( let j = 0; j < campaign.banners.length; j++ ) {\n\t\t\t\t\tconst banner = campaign.banners[ j ];\n\n\t\t\t\t\t// Logged-in status\n\t\t\t\t\tif ( anon && !banner.display_anon ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif ( !anon && !banner.display_account ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Device\n\t\t\t\t\tif ( !banner.devices.includes( device ) ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// We get here if the campaign targets the user's country,\n\t\t\t\t\t// and has at least one banner for the user's logged-in status\n\t\t\t\t\t// and device.\n\t\t\t\t\tkeepCampaign = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif ( keepCampaign ) {\n\t\t\t\t\tavailableCampaigns.push( campaign );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn availableCampaigns;\n\t\t},\n\n\t\tupdateAvailableCampaigns: function ( previousAvailableCampaigns,\n\t\t\tfailedCampaign, fallbackLoopIndex ) {\n\t\t\t// FIXME As well as removing the failed campaign, also update throttling\n\t\t\t// for any other campaigns that were throttled and that may have been up for\n\t\t\t// grabs in the previous fallback loop. (Calculating the correct the\n\t\t\t// probability in that case is what the fallbackLoopIndex parameter is for.)\n\n\t\t\tconst newAvailableCampaigns = previousAvailableCampaigns.slice(),\n\n\t\t\t\t// Remove campaign from available campaigns list\n\t\t\t\t// Find campaign object index by name\n\t\t\t\tcIndex = newAvailableCampaigns.map( ( c ) => c.name ).indexOf( failedCampaign.name );\n\n\t\t\t// Sanity check: Verify the failed campaign was in the list of available\n\t\t\t// campaigns. (That should always be the case, so this conditional should\n\t\t\t// never be true.)\n\t\t\tif ( cIndex === -1 ) {\n\t\t\t\tmw.log.warn( 'Failed campaign was not in list of available campaigns' );\n\t\t\t} else {\n\t\t\t\tnewAvailableCampaigns.splice( cIndex, 1 );\n\t\t\t}\n\n\t\t\treturn newAvailableCampaigns;\n\t\t},\n\n\t\t/**\n\t\t * Check for campaigns that are have already ended, which might happen due to\n\t\t * incorrect caching of choiceData between us and the user. This check can easily\n\t\t * result in false positives.\n\t\t *\n\t\t * @param choiceData\n\t\t */\n\t\tchoiceDataSeemsFresh: function ( choiceData ) {\n\n\t\t\tconst now = new Date();\n\n\t\t\tfor ( let i = 0; i < choiceData.length; i++ ) {\n\t\t\t\tconst campaign = choiceData[ i ];\n\t\t\t\tconst campaignEndDatePlusLeeway = new Date();\n\n\t\t\t\tcampaignEndDatePlusLeeway.setTime(\n\t\t\t\t\t( campaign.end * 1000 ) +\n\t\t\t\t\t( CAMPAIGN_STALENESS_LEEWAY * 60000 )\n\t\t\t\t);\n\n\t\t\t\tif ( campaignEndDatePlusLeeway < now ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\n\t\tchooseCampaign: function ( availableCampaigns, random ) {\n\n\t\t\tif ( availableCampaigns.length === 0 ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Calculate the user's probability of getting each campaign. This\n\t\t\t// will set allocation properties on the elements in\n\t\t\t// availableCampaigns.\n\t\t\tsetCampaignAllocations( availableCampaigns );\n\n\t\t\treturn chooseObjInAllocatedArray( random, availableCampaigns );\n\t\t},\n\n\t\tchooseBanner: function ( campaign, bucket, anon, device, random ) {\n\n\t\t\t// Make a list of possible banners. Because of our wonky data model,\n\t\t\t// this call must filter on logged-in status and device again.\n\t\t\tconst possibleBanners =\n\t\t\t\tmakePossibleBanners( campaign, bucket, anon, device );\n\n\t\t\tif ( possibleBanners.length === 0 ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Calculate the user's probability of getting each banner. This\n\t\t\t// will set allocation properties on the elements in\n\t\t\t// possibleBanners.\n\t\t\tsetBannerAllocations( possibleBanners );\n\n\t\t\treturn chooseObjInAllocatedArray( random, possibleBanners );\n\t\t},\n\n\t\t/**\n\t\t * Request a specific banner from among those available for this user\n\t\t *\n\t\t * @param {Object} campaign\n\t\t * @param {number} bucket\n\t\t * @param {boolean} anon\n\t\t * @param {string} device\n\t\t * @param {string} requestedBannerName\n\t\t * @return {Object}\n\t\t */\n\t\trequestBanner: function ( campaign, bucket, anon, device, requestedBannerName ) {\n\n\t\t\t// Make a list of possible banners.\n\t\t\tconst possibleBanners = makePossibleBanners( campaign, bucket, anon, device );\n\n\t\t\tfor ( let i = 0; i < possibleBanners.length; i++ ) {\n\n\t\t\t\tconst possibleBanner = possibleBanners[ i ];\n\n\t\t\t\tif ( possibleBanner.name === requestedBannerName ) {\n\t\t\t\t\treturn possibleBanner;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn null;\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/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":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"name\" type.","line":60,"column":1,"nodeType":"Block","endLine":60,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"handlerFunc\" type.","line":73,"column":1,"nodeType":"Block","endLine":73,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"handlerFunc\" type.","line":83,"column":1,"nodeType":"Block","endLine":83,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"handlerFunc\" type.","line":94,"column":1,"nodeType":"Block","endLine":94,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"campaign\" type.","line":106,"column":1,"nodeType":"Block","endLine":106,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":246,"column":3,"nodeType":"CallExpression","endLine":252,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":278,"column":3,"nodeType":"CallExpression","endLine":278,"endColumn":64},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"iteration\" type.","line":557,"column":1,"nodeType":"Block","endLine":557,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bannerJson\" type.","line":626,"column":1,"nodeType":"Block","endLine":626,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"reason\" type.","line":723,"column":1,"nodeType":"Block","endLine":723,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":733,"column":3,"nodeType":"Block","endLine":735,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"reason\" type.","line":748,"column":1,"nodeType":"Block","endLine":748,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"rate\" type.","line":760,"column":1,"nodeType":"Block","endLine":760,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"rate\" type.","line":772,"column":1,"nodeType":"Block","endLine":772,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":820,"column":4,"nodeType":"CallExpression","endLine":821,"endColumn":49},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":820,"column":4,"nodeType":"CallExpression","endLine":822,"endColumn":42},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":830,"column":4,"nodeType":"CallExpression","endLine":831,"endColumn":49},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":830,"column":4,"nodeType":"CallExpression","endLine":832,"endColumn":42},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":884,"column":1,"nodeType":"Block","endLine":884,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":945,"column":3,"nodeType":"Block","endLine":950,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"prop\" type.","line":949,"column":1,"nodeType":"Block","endLine":949,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":110,"column":3,"nodeType":"CallExpression","endLine":131,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":6,"fatalErrorCount":0,"warningCount":15,"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\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 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 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 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 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 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\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\tfunction fetchBanner() {\n\n\t\tconst data = cn.internal.state.getData(),\n\t\t\turlBase = new mw.Uri(\n\t\t\t\tmw.config.get( 'wgCentralNoticeActiveBannerDispatcher' )\n\t\t\t),\n\n\t\t\t// For Varnish purges of banner content, we ensure query param order (thus we\n\t\t\t// can't use the object-based facilities for params in mw.Uri).\n\t\t\t// Rather, we use mw.Uri only to parse the URL set in config and to\n\t\t\t// reconstruct the bits before the query.\n\t\t\t// Param order must coordinate with CdnCacheUpdateBannerLoader in php.\n\t\t\turlQuery = [\n\t\t\t\t'banner=' + mw.Uri.encode( data.banner ),\n\t\t\t\t'uselang=' + mw.Uri.encode( data.uselang ),\n\t\t\t\t'debug=' + ( !!data.debug ).toString()\n\t\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=' + mw.Uri.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\tif ( urlBase.query.title ) {\n\t\t\t// As per mediawiki.Uri.js\n\t\t\turlQuery.unshift( 'title=' + mw.util.wikiUrlencode( urlBase.query.title ) );\n\t\t}\n\n\t\t// Remove any other query or fragment info parsed from the configured URL\n\t\turlBase.query = {};\n\t\turlBase.fragment = '';\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: urlBase.toString() + '?' + urlQuery.join( '&' ),\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 mw.Uri( mw.config.get( 'wgCentralBannerRecorder' ) );\n\t\t\tdataCopy = state.getDataCopy( true );\n\t\t\turl.extend( dataCopy );\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 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\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 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 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\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 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 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 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 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 prop\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// 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":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 128. Maximum allowed is 100.","line":107,"column":1,"nodeType":"Program","messageId":"max","endLine":107,"endColumn":117},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":117,"column":2,"nodeType":"Block","endLine":119,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"geo\" type.","line":301,"column":1,"nodeType":"Block","endLine":301,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":335,"column":3,"nodeType":"Block","endLine":340,"endColumn":6},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":345,"column":3,"nodeType":"Block","endLine":356,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"availableCampaigns\" type.","line":380,"column":1,"nodeType":"Block","endLine":380,"endColumn":1},{"ruleId":"max-len","severity":1,"message":"This line has a length of 101. Maximum allowed is 100.","line":410,"column":1,"nodeType":"Program","messageId":"max","endLine":410,"endColumn":93},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":447,"column":3,"nodeType":"Block","endLine":450,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"reason\" type.","line":486,"column":1,"nodeType":"Block","endLine":486,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":497,"column":3,"nodeType":"Block","endLine":499,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bannerCount\" type.","line":548,"column":1,"nodeType":"Block","endLine":548,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"rate\" type.","line":558,"column":1,"nodeType":"Block","endLine":558,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"rate\" type.","line":571,"column":1,"nodeType":"Block","endLine":571,"endColumn":1}],"suppressedMessages":[{"ruleId":"camelcase","severity":2,"message":"Identifier 'banner_count' is not in camel case.","line":552,"column":15,"nodeType":"Identifier","messageId":"notCamelCase","endLine":552,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":13,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Stores data for campaign/banner processing and data related to the state of\n * that processing. Provides cn.internal.state and cn.data.\n *\n * Note: Coordinate with CentralNoticeImpression schema; all data properties must\n * either be in the schema and have the correct data type, or must be removed by\n * calling getDataCopy( true ).\n */\n( function () {\n\n\tlet state = null,\n\t\tstatus,\n\t\timpressionEventSampleRateOverridden = false;\n\tconst config = require( './config.json' ),\n\n\t\tUNKNOWN_GEO_CODE = 'XX',\n\n\t\t// Campaign category, a temporary hack due to our hiccupy data model, is\n\t\t// gleaned from the categories of all the banners in a campaign. In the\n\t\t// unlikely case that a campaign contains banners of different\n\t\t// categories, the campaign category is set to this value.\n\t\tCAMPAIGN_CATEGORY_FOR_MIXED_BANNER_CATEGORIES = 'mixed_banner_categories',\n\n\t\t// These must coordinate with device names in the CN database\n\t\tDEVICES = {\n\t\t\tDESKTOP: 'desktop',\n\t\t\tIPHONE: 'iphone',\n\t\t\tIPAD: 'ipad',\n\t\t\tANDROID: 'android',\n\t\t\tUNKNOWN: 'unknown'\n\t\t},\n\n\t\tSTATUSES = {\n\t\t\tCAMPAIGN_NOT_CHOSEN: new Status( 'campaign_not_chosen', 0 ),\n\t\t\t// TODO Rename this status to ATTEMPTING_CAMPAIGN (T232236)\n\t\t\tCAMPAIGN_CHOSEN: new Status( 'campaign_chosen', 1 ),\n\t\t\t// TODO Rename this status to CAMPAIGN_FAILED (T232236)\n\t\t\tBANNER_CANCELED: new Status( 'banner_canceled', 2 ),\n\t\t\tNO_BANNER_AVAILABLE: new Status( 'no_banner_available', 3 ),\n\t\t\tBANNER_CHOSEN: new Status( 'banner_chosen', 4 ),\n\t\t\tBANNER_LOADED_BUT_HIDDEN: new Status( 'banner_loaded_but_hidden', 5 ),\n\t\t\tBANNER_SHOWN: new Status( 'banner_shown', 6 ),\n\t\t\tBANNER_LOADER_ERROR: new Status( 'banner_loader_error', 7 ),\n\t\t\tCHOICE_DATA_STALE: new Status( 'choice_data_stale', 8 )\n\t\t},\n\n\t\t// Until T114078 is closed, we minify banner history logs. This lookup\n\t\t// table maps from hide reason string to a numeric code.\n\t\tREASONS = {\n\t\t\t// Any reason not listed here will be stored as \"other\".\n\t\t\tother: 0,\n\t\t\tclose: 1,\n\t\t\twaitdate: 2,\n\t\t\twaitimps: 3,\n\t\t\twaiterr: 4, // Deprecated\n\t\t\tbelowMinEdits: 5,\n\t\t\tviewLimit: 6,\n\t\t\t'seen-fullscreen': 7,\n\t\t\t'cookies-disabled': 8,\n\t\t\tdonate: 9,\n\t\t\tcookies: 10,\n\t\t\tseen: 11,\n\t\t\tempty: 12,\n\t\t\twaitnorestart: 13, // Deprecated\n\t\t\twaitnostorage: 14, // TODO Switch impression diet to use just noStorage?\n\t\t\tnamespace: 15,\n\t\t\tnoStorage: 16,\n\t\t\trequestedBannerNotAvailable: 17,\n\t\t\tjsonParamError: 18,\n\t\t\tbannerSequenceEmptyStep: 19,\n\t\t\tbannerSequenceAllStepsSkipped: 20,\n\t\t\tuserOptOut: 21\n\t\t};\n\n\tconst campaignAttemptsManager = ( function () {\n\t\tconst attemptedCampaignStatusesByName = {},\n\t\t\thasOwn = Object.prototype.hasOwnProperty;\n\n\t\treturn {\n\t\t\tsetCampaignStatus: function ( c, statusCode ) {\n\t\t\t\tlet statusObj;\n\n\t\t\t\tif ( !hasOwn.call( state.attemptedCampaignsByName, c.name ) ) {\n\t\t\t\t\t// If this is the first time we've seen this campaign, add it to the\n\t\t\t\t\t// indexes and to the list of campiagn statuses.\n\t\t\t\t\tstatusObj = {\n\t\t\t\t\t\tstatusCode: statusCode,\n\t\t\t\t\t\tcampaign: c.name,\n\t\t\t\t\t\tbannersCount: c.banners.length\n\t\t\t\t\t};\n\n\t\t\t\t\tstate.data.campaignStatuses.push( statusObj );\n\t\t\t\t\tattemptedCampaignStatusesByName[ c.name ] = statusObj;\n\t\t\t\t\tstate.attemptedCampaignsByName[ c.name ] = c;\n\n\t\t\t\t} else {\n\t\t\t\t\t// Otherwise, just update the status code in campaign status object.\n\t\t\t\t\t// The following will update the object in state.data.campaignStatuses,\n\t\t\t\t\t// since the objects in that array are the same as the values of\n\t\t\t\t\t// attemptedCampaignStatusesByName.\n\n\t\t\t\t\tattemptedCampaignStatusesByName[ c.name ].statusCode = statusCode;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tgetAttemptedCampaigns: function () {\n\t\t\t\treturn state.data.campaignStatuses.map( ( statusObj ) => state.attemptedCampaignsByName[ statusObj.campaign ] );\n\t\t\t}\n\t\t};\n\t}() );\n\n\tfunction Status( key, code ) {\n\t\tthis.key = key;\n\t\tthis.code = code;\n\t}\n\n\t/**\n\t * Get a code for the general category the user's device is in.\n\t */\n\tfunction getDeviceCode() {\n\t\t// If we're on the desktop site, all your device are belong to DESKTOP\n\t\t// TODO Fix this! Skin != device. Maybe screen width? P.S. Talk to users.\n\t\t// TODO Make a test for this; it could stop working without notice.\n\t\t// See also https://phabricator.wikimedia.org/T71366\n\t\tif ( mw.config.get( 'skin' ) !== 'minerva' ) {\n\t\t\treturn DEVICES.DESKTOP;\n\t\t}\n\n\t\tconst ua = navigator.userAgent;\n\n\t\tif ( /iphone/i.test( ua ) ) {\n\t\t\treturn DEVICES.IPHONE;\n\t\t}\n\t\tif ( /ipad/i.test( ua ) ) {\n\t\t\treturn DEVICES.IPAD;\n\t\t}\n\t\tif ( /android/i.test( ua ) ) {\n\t\t\treturn DEVICES.ANDROID;\n\t\t}\n\t\treturn DEVICES.UNKNOWN;\n\t}\n\n\t/**\n\t * Set the data we need for campaign/banner selection and display, and for\n\t * recording what happened. Here we load up all the data that's available\n\t * initially.\n\t */\n\tfunction setInitialData() {\n\n\t\t// Keep existing properties of state.urlParams, which may be set by tests\n\t\tconst urlParams = Object.assign( state.urlParams, ( new mw.Uri() ).query );\n\n\t\tstate.data.anonymous = ( !mw.user.isNamed() );\n\t\tstate.data.project = mw.config.get( 'wgNoticeProject' );\n\t\tstate.data.db = mw.config.get( 'wgDBname' );\n\t\tstate.data.optedOutCampaigns = getOptedOutCampaignsForUser();\n\n\t\t// All of the following may be overridden by URL parameters (including\n\t\t// language, which can be overridden by uselang).\n\t\tstate.data.uselang = mw.config.get( 'wgUserLanguage' );\n\t\tstate.data.device = urlParams.device || getDeviceCode();\n\n\t\t// data.country already have been set\n\t\tstate.data.country = urlParams.country || state.data.country || UNKNOWN_GEO_CODE;\n\n\t\t// data.region should also already have been set, though it might be empty\n\t\tstate.data.region = urlParams.region ||\n\t\t\t( state.data.region !== undefined ? state.data.region : false ) ||\n\t\t\tUNKNOWN_GEO_CODE;\n\n\t\t// debug should be set no matter what\n\t\tstate.data.debug = ( urlParams.debug !== undefined );\n\n\t\t// The following four parameters should be used if they're numbers\n\t\tstate.data.randomcampaign =\n\t\t\tnumericalUrlParamOrVal( urlParams.randomcampaign, Math.random() );\n\n\t\tstate.data.randombanner =\n\t\t\tnumericalUrlParamOrVal( urlParams.randombanner, Math.random() );\n\n\t\tstate.data.recordImpressionSampleRate = numericalUrlParamOrVal(\n\t\t\turlParams.recordImpressionSampleRate,\n\t\t\tmw.config.get( 'wgCentralNoticeSampleRate' )\n\t\t);\n\n\t\t// In the case of impressionEventSampleRate, also remember if it's overridden by\n\t\t// a URL param\n\t\tconst impressionEventSampleRateFromUrl =\n\t\t\tnumericalUrlParamOrVal( urlParams.impressionEventSampleRate, null );\n\n\t\tif ( impressionEventSampleRateFromUrl !== null ) {\n\t\t\tstate.data.impressionEventSampleRate = impressionEventSampleRateFromUrl;\n\t\t\timpressionEventSampleRateOverridden = true;\n\n\t\t} else {\n\t\t\tstate.data.impressionEventSampleRate =\n\t\t\t\tmw.config.get( 'wgCentralNoticeImpressionEventSampleRate' );\n\t\t}\n\n\t\t// Legacy code exposed urlParams at mw.centralNotice.data.getVars.\n\t\t// TODO Is this still needed? Maybe deprecate?\n\t\tstate.data.getVars = urlParams;\n\n\t\t// Contains list of campaigns statuses\n\t\tstate.data.campaignStatuses = [];\n\t}\n\n\tfunction getOptedOutCampaignsForUser() {\n\t\tconst blocked = [],\n\t\t\t// Note: coordinate with CampaignType::PREFERENCE_KEY_PREFIX\n\t\t\tregex = /^centralnotice-display-campaign-type-(.*)$/;\n\n\t\tif ( mw.config.get( 'wgUserName' ) === null ) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst allOptions = Object.assign( {}, mw.user.options.values );\n\n\t\tfor ( const key in allOptions ) {\n\t\t\tif ( !Object.prototype.hasOwnProperty.call( allOptions, key ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst matches = regex.exec( key );\n\n\t\t\tif ( Array.isArray( matches ) && matches.length === 2 && !allOptions[ key ] ) {\n\t\t\t\tblocked.push( matches[ 1 ] );\n\t\t\t}\n\t\t}\n\n\t\treturn blocked;\n\t}\n\n\tfunction numericalUrlParamOrVal( urlParam, val ) {\n\t\tconst urlParamAsFloat = parseFloat( urlParam );\n\t\treturn !isNaN( urlParamAsFloat ) ? urlParamAsFloat : val;\n\t}\n\n\tfunction setTestingBannerData() {\n\t\tstate.data.campaign = state.urlParams.campaign;\n\t\tstate.data.banner = state.urlParams.banner;\n\t\tstate.data.testingBanner = true;\n\t\tstate.data.preview = ( state.urlParams.preview !== undefined );\n\t}\n\n\tfunction setStatus( s, reason ) {\n\t\tconst reasonCodeStr = reason ? ( '.' + state.lookupReasonCode( reason ) ) : '';\n\t\tstatus = s;\n\t\tstate.data.status = s.key;\n\t\tstate.data.statusCode = s.code.toString() + reasonCodeStr;\n\n\t\t// Update campaign status in the campaign attempts manager if a campaign is\n\t\t// currently being attempted.\n\t\tif ( state.data.campaign ) {\n\t\t\tcampaignAttemptsManager.setCampaignStatus(\n\t\t\t\tstate.campaign,\n\t\t\t\tstate.data.statusCode\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * State object (intended for access from within this RL module)\n\t */\n\tstate = mw.centralNotice.internal.state = {\n\n\t\tSTATUSES: STATUSES,\n\n\t\t// The following four properties are only exposed so QUnit\n\t\t// tests can manipulate data\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\turlParams: {},\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\tdata: {},\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\tcampaign: null,\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\tbanner: null,\n\n\t\t/**\n\t\t * @private\n\t\t */\n\t\tattemptedCampaignsByName: {},\n\n\t\t/**\n\t\t * Call this with geo data before calling setUp() or\n\t\t * setUpForTestingBanner().\n\t\t *\n\t\t * @param geo\n\t\t */\n\t\tsetGeoData: function ( geo ) {\n\t\t\tif ( geo ) {\n\t\t\t\tstate.data.country = geo.country;\n\t\t\t\tstate.data.region = geo.region;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Call this before calling setUp() or setUpForTestingBanner()\n\t\t * if valid geo data is not available.\n\t\t */\n\t\tsetInvalidGeoData: function () {\n\t\t\tstate.data.country = UNKNOWN_GEO_CODE;\n\t\t\tstate.data.region = UNKNOWN_GEO_CODE;\n\t\t},\n\n\t\tsetUp: function () {\n\t\t\tsetInitialData();\n\t\t\tsetStatus( STATUSES.CAMPAIGN_NOT_CHOSEN );\n\t\t},\n\n\t\tsetUpForTestingBanner: function () {\n\t\t\tsetInitialData();\n\n\t\t\t// Load banner and campaign URL params into data\n\t\t\tsetTestingBannerData();\n\n\t\t\t// For testing, we'll set the status to what it normally is after\n\t\t\t// a banner is chosen\n\t\t\tsetStatus( STATUSES.BANNER_CHOSEN );\n\t\t},\n\n\t\t/**\n\t\t * Return the data object, with data needed for campaign and banner\n\t\t * selection, and data about the state of the selection process. The\n\t\t * returned object should be considered read-only; i.e., don't modify\n\t\t * it.\n\t\t */\n\t\tgetData: function () {\n\t\t\treturn state.data;\n\t\t},\n\n\t\t/**\n\t\t * Get a copy of the data object. If cleanForLogging is true, remove\n\t\t * properties or those that are not strings, numbers or booleans,\n\t\t * to provide an object with properties appropriate to send as URL\n\t\t * params (for legacy impression recordings) or as an impression event\n\t\t * (via EventLogging).\n\t\t *\n\t\t * Note: Coordinate with CentralNoticeImpression schema; remove any\n\t\t * data properties that do not conform to that schema.\n\t\t *\n\t\t * @param {boolean} prepareForLogging\n\t\t */\n\t\tgetDataCopy: function ( prepareForLogging ) {\n\n\t\t\tconst dataCopy = $.extend( true, {}, state.data );\n\n\t\t\tif ( prepareForLogging ) {\n\t\t\t\tdelete dataCopy.getVars;\n\t\t\t\tdelete dataCopy.mixins;\n\t\t\t\tdelete dataCopy.tests;\n\t\t\t\tdelete dataCopy.reducedBucket;\n\t\t\t\tdelete dataCopy.availableCampaigns;\n\t\t\t\tdelete dataCopy.optedOutCampaigns;\n\t\t\t\t// Serialized as JSON string for b/c, later, when we switch fully to EventLogging\n\t\t\t\t// instead of the custom beacon/impression, the serialization could be removed\n\t\t\t\tdataCopy.campaignStatuses = JSON.stringify( dataCopy.campaignStatuses );\n\t\t\t}\n\n\t\t\treturn dataCopy;\n\t\t},\n\n\t\t/**\n\t\t * Set a list of campaigns that may be selected for this pageview. This method\n\t\t * will be called to update the list on each iteration of the fallback loop.\n\t\t *\n\t\t * @param availableCampaigns\n\t\t */\n\t\tsetAvailableCampaigns: function ( availableCampaigns ) {\n\t\t\tstate.data.availableCampaigns = availableCampaigns;\n\t\t},\n\n\t\t/**\n\t\t * Sets the campaign that is currently being attempted. This campaign will be used\n\t\t * by state as current, and if no others are attempted, final.\n\t\t *\n\t\t * @param {Object} c the campaign object, from the list of available campaigns\n\t\t */\n\t\tsetAttemptingCampaign: function ( c ) {\n\t\t\tlet prop, i, category,\n\t\t\t\tcampaignCategory = null;\n\n\t\t\t// Resetting previously set flags (if any)\n\t\t\tdelete state.data.result;\n\t\t\tdelete state.data.reason;\n\t\t\tdelete state.data.bannerCanceledReason;\n\t\t\tdelete state.data.bannersNotGuaranteedToDisplay;\n\n\t\t\tstate.campaign = c;\n\t\t\tstate.data.campaign = c.name;\n\n\t\t\t// The following should ony be called _after_ state.campaign is set, otherwise\n\t\t\t// the status won't be included in the record of attempted campaign statuses.\n\t\t\tsetStatus( STATUSES.CAMPAIGN_CHOSEN );\n\n\t\t\t// Provide the names of mixins enabled in this campaign. (By re-setting each time a\n\t\t\t// campaign is attempted, we'll get here only mixins enabled for this specific campaign.)\n\t\t\t// This is used in in-banner js to sanity-check that specific mixins are available and\n\t\t\t// enabled.\n\t\t\tstate.data.mixins = {};\n\t\t\tfor ( prop in c.mixins ) {\n\t\t\t\tif ( Object.hasOwnProperty.call( c.mixins, prop ) ) {\n\t\t\t\t\tstate.data.mixins[ prop ] = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set the campaignCategory property if all the banners in this\n\t\t\t// campaign have the same category. This is necessary so we can\n\t\t\t// use category even if a banner has not been chosen. In all normal\n\t\t\t// cases, this won't be a problem.\n\t\t\t// TODO Eventually, category should be a property of campaigns,\n\t\t\t// not banners.\n\t\t\tfor ( i = 0; i < state.campaign.banners.length; i++ ) {\n\n\t\t\t\tcategory = state.campaign.banners[ i ].category;\n\n\t\t\t\tif ( campaignCategory === null ) {\n\t\t\t\t\tcampaignCategory = category;\n\t\t\t\t} else if ( campaignCategory !== category ) {\n\t\t\t\t\tcampaignCategory =\n\t\t\t\t\t\tCAMPAIGN_CATEGORY_FOR_MIXED_BANNER_CATEGORIES;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstate.data.campaignCategory = campaignCategory;\n\n\t\t\t// Is the campaign category among the categories configured to use\n\t\t\t// legacy mechanisms?\n\t\t\tstate.data.campaignCategoryUsesLegacy =\n\t\t\t\tconfig.categoriesUsingLegacy.includes( campaignCategory );\n\t\t},\n\n\t\t/**\n\t\t * Return the campaign currently being attempted, or null if no campaign has\n\t\t * been attempted yet.\n\t\t */\n\t\tgetAttemptingCampaign: function () {\n\t\t\treturn state.campaign === undefined ? null : state.campaign;\n\t\t},\n\n\t\tsetBanner: function ( b ) {\n\t\t\tstate.banner = b;\n\t\t\tstate.data.banner = state.banner.name;\n\t\t\tstate.data.bannerCategory = state.banner.category;\n\t\t\tsetStatus( STATUSES.BANNER_CHOSEN );\n\t\t},\n\n\t\tsetBucket: function ( bucket ) {\n\t\t\tstate.data.bucket = bucket;\n\t\t},\n\n\t\tsetReducedBucket: function ( reducedBucket ) {\n\t\t\tstate.data.reducedBucket = reducedBucket;\n\t\t},\n\n\t\tsetBannersNotGuaranteedToDisplay: function () {\n\t\t\tstate.data.bannersNotGuaranteedToDisplay = true;\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\tstate.failCampaign( reason );\n\t\t},\n\n\t\t/**\n\t\t * Marks a campaign as failed.\n\t\t *\n\t\t * @param reason\n\t\t */\n\t\tfailCampaign: function ( reason ) {\n\t\t\tstate.data.bannerCanceledReason = reason;\n\t\t\tsetStatus( STATUSES.BANNER_CANCELED, reason );\n\n\t\t\t// Legacy fields for Special:RecordImpression\n\t\t\tstate.data.result = 'hide';\n\t\t\tstate.data.reason = reason;\n\t\t},\n\n\t\t/**\n\t\t * Legacy metod, deprecated. Use isCampaignFailed().\n\t\t */\n\t\tisBannerCanceled: function () {\n\t\t\treturn state.isCampaignFailed();\n\t\t},\n\n\t\tisCampaignFailed: function () {\n\t\t\treturn status === STATUSES.BANNER_CANCELED;\n\t\t},\n\n\t\tisBannerShown: function () {\n\t\t\treturn status === STATUSES.BANNER_SHOWN;\n\t\t},\n\n\t\tsetNoBannerAvailable: function () {\n\t\t\tsetStatus( STATUSES.NO_BANNER_AVAILABLE );\n\n\t\t\t// Legacy fields for Special:RecordImpression\n\t\t\tstate.data.result = 'hide';\n\t\t\tstate.data.reason = 'empty';\n\t\t},\n\n\t\tsetRequestedBannerNotAvailable: function ( bannerName ) {\n\t\t\tstate.data.requestedBanner = bannerName;\n\t\t\tsetStatus( STATUSES.NO_BANNER_AVAILABLE, 'requestedBannerNotAvailable' );\n\t\t},\n\n\t\tsetBannerLoadedButHidden: function ( reason ) {\n\t\t\tstate.data.bannerLoadedButHiddenReason = reason;\n\t\t\tsetStatus( STATUSES.BANNER_LOADED_BUT_HIDDEN, reason );\n\n\t\t\t// Legacy fields for Special:RecordImpression\n\t\t\tstate.data.result = 'hide';\n\t\t\tstate.data.reason = reason;\n\t\t},\n\n\t\tsetAlterFunctionMissing: function () {\n\t\t\tstate.data.alterFunctionMissing = true;\n\t\t},\n\n\t\tsetBannerShown: function () {\n\t\t\tsetStatus( STATUSES.BANNER_SHOWN );\n\n\t\t\t// Legacy field for Special:RecordImpression\n\t\t\tstate.data.result = 'show';\n\t\t},\n\n\t\t/**\n\t\t * Sets banner_count, a legacy field for Special:RecordImpression\n\t\t *\n\t\t * @param bannerCount\n\t\t */\n\t\tsetBannerCount: function ( bannerCount ) {\n\t\t\t// eslint-disable-next-line camelcase\n\t\t\tstate.data.banner_count = bannerCount;\n\t\t},\n\n\t\t/**\n\t\t * Sets minimal impression sample rate, the highest rate set will be used\n\t\t *\n\t\t * @param rate\n\t\t */\n\t\tsetMinRecordImpressionSampleRate: function ( rate ) {\n\t\t\t// Update rate only if supplied rate is higher than current one\n\t\t\tif ( rate > state.data.recordImpressionSampleRate ) {\n\t\t\t\tstate.data.recordImpressionSampleRate = rate;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Sets minimal impression event sample rate, the highest rate set will be used\n\t\t * (unless it was overridden by a URL parameter, in which that takes precedence).\n\t\t *\n\t\t * @param rate\n\t\t */\n\t\tsetMinImpressionEventSampleRate: function ( rate ) {\n\t\t\tif (\n\t\t\t\t!impressionEventSampleRateOverridden &&\n\t\t\t\t// Update rate only if supplied rate is higher than current one\n\t\t\t\trate > state.data.impressionEventSampleRate\n\t\t\t) {\n\t\t\t\tstate.data.impressionEventSampleRate = rate;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set a banner loader error, with an optional message\n\t\t *\n\t\t * @param {string} [msg]\n\t\t */\n\t\tsetBannerLoaderError: function ( msg ) {\n\t\t\tif ( msg ) {\n\t\t\t\tstate.data.errorMsg = msg;\n\t\t\t}\n\t\t\tsetStatus( STATUSES.BANNER_LOADER_ERROR );\n\t\t},\n\n\t\t/**\n\t\t * Set a status indicating stale choice data was received.\n\t\t */\n\t\tsetChoiceDataStale: function () {\n\t\t\tsetStatus( STATUSES.CHOICE_DATA_STALE );\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\tconst tests = state.data.tests = state.data.tests || [];\n\n\t\t\t// Add if it isn't already registered.\n\t\t\tif ( !tests.includes( identifier ) ) {\n\t\t\t\ttests.push( identifier );\n\n\t\t\t\tif ( tests.length === 1 ) {\n\t\t\t\t\tstate.data.testIdentifiers = identifier;\n\t\t\t\t} else {\n\t\t\t\t\tstate.data.testIdentifiers += ',' + identifier;\n\t\t\t\t}\n\t\t\t}\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 * added to state data).\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\tif ( !state.data.debugInfo ) {\n\t\t\t\tstate.data.debugInfo = str;\n\t\t\t} else {\n\t\t\t\tstate.data.debugInfo += '|' + str;\n\t\t\t}\n\t\t},\n\n\t\tlookupReasonCode: function ( reasonName ) {\n\t\t\tif ( reasonName in REASONS ) {\n\t\t\t\treturn REASONS[ reasonName ];\n\t\t\t}\n\t\t\treturn REASONS.other;\n\t\t},\n\n\t\t/**\n\t\t * Returns number of campaigns were chosen\n\t\t *\n\t\t * @return {number}\n\t\t */\n\t\tcountCampaignsAttempted: function () {\n\t\t\treturn state.data.campaignStatuses.length;\n\t\t},\n\n\t\tgetAttemptedCampaigns: function () {\n\t\t\treturn campaignAttemptsManager.getAttemptedCampaigns();\n\t\t}\n\t};\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.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":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":50,"column":2,"nodeType":"Block","endLine":53,"endColumn":5},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":67,"column":2,"nodeType":"Block","endLine":70,"endColumn":5},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":394,"column":3,"nodeType":"Block","endLine":404,"endColumn":6},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":459,"column":3,"nodeType":"Block","endLine":462,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"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\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\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 */\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\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":2,"message":"Prefer .then to .done","line":44,"column":3,"nodeType":"CallExpression","endLine":46,"endColumn":6}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"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\"></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":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"queue\" type.","line":51,"column":1,"nodeType":"Block","endLine":51,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"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} 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 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":2,"message":"Prefer .then to .fail","line":49,"column":3,"nodeType":"CallExpression","endLine":70,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":49,"column":3,"nodeType":"CallExpression","endLine":75,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":116,"column":3,"nodeType":"CallExpression","endLine":125,"endColumn":6}],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":189,"column":5,"nodeType":"CallExpression","messageId":"unexpected","endLine":189,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":3,"fatalErrorCount":0,"warningCount":0,"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\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\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\t\tbannerMessagesCache = getUnsavedMessagesValues(),\n\t\t\turl = new mw.Uri( mw.config.get( 'wgCentralNoticeActiveBannerDispatcher' ) );\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\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\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// 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$previewLink.text( mw.msg( 'centralnotice-preview-page' ) );\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\t\t$( '#cn-js-error-warn' ).hide();\n\n\t\t// Retrieve banner name sent via data attribute\n\t\tbannerName = $( '#centralnotice-data-container' ).data( 'banner-name' );\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":[{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":249,"column":3,"nodeType":"Block","endLine":254,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"e\" type.","line":253,"column":1,"nodeType":"Block","endLine":253,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":262,"column":3,"nodeType":"Block","endLine":268,"endColumn":6},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"$origFilterStr\" type.","line":267,"column":1,"nodeType":"Block","endLine":267,"endColumn":1}],"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":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Backing JS for Special:CentralNoticeBanners, the banner list view form.\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\n\tconst bm = mw.centralNotice.adminUi.bannerManagement = {\n\t\t/**\n\t\t * State tracking variable for the number of items currently selected\n\t\t *\n\t\t * @protected\n\t\t */\n\t\tselectedItemCount: 0,\n\n\t\t/**\n\t\t * State tracking variable for the number of items available to be selected\n\t\t *\n\t\t * @protected\n\t\t */\n\t\ttotalSelectableItems: 0,\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\tdoAddBannerDialog: function () {\n\t\t\tconst buttons = {},\n\t\t\t\tokButtonText = mw.message( 'centralnotice-add-notice-button' ).text(),\n\t\t\t\tcancelButtonText = mw.message( 'centralnotice-add-notice-cancel-button' ).text(),\n\t\t\t\t$dialogObj = $( '<form>' ),\n\t\t\t\ttitle = mw.message( 'centralnotice-add-new-banner-title' );\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\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[ okButtonText ] = function () {\n\t\t\t\tconst formobj = $( '#cn-banner-manager' )[ 0 ];\n\t\t\t\tformobj.wpaction.value = 'create';\n\t\t\t\tformobj.wpnewBannerName.value = $( this )[ 0 ].wpnewBannerName.value;\n\n\t\t\t\tformobj.wpnewBannerTemplate.value = null;\n\t\t\t\tif ( $( this )[ 0 ].wpcreateFromTemplateCheckbox.checked === true ) {\n\t\t\t\t\tformobj.wpnewBannerTemplate.value = $( this )[ 0 ].wpnewBannerTemplate.value;\n\t\t\t\t}\n\n\t\t\t\tformobj.wpnewBannerEditSummary.value =\n\t\t\t\t\t$( this )[ 0 ].wpnewBannerEditSummary.value;\n\t\t\t\tformobj.submit();\n\t\t\t};\n\n\t\t\t// Create the dialog by copying the textfield element into a new form\n\t\t\t$dialogObj[ 0 ].name = 'addBannerDialog';\n\t\t\t// FIXME: Don't use jQuery#ready\n\t\t\t// eslint-disable-next-line no-jquery/no-ready-shorthand\n\t\t\t$dialogObj.append(\n\t\t\t\t$( '#cn-formsection-addBanner' ).children( 'div' ).clone().show()\n\t\t\t).ready( () => {\n\t\t\t\t$( $dialogObj[ 0 ].wpcreateFromTemplateCheckbox )\n\t\t\t\t\t.on( 'click', bm.toggleBannerTemplatesDropdown );\n\t\t\t} );\n\n\t\t\t$dialogObj.dialog( {\n\t\t\t\ttitle: title.escaped(),\n\t\t\t\tmodal: true,\n\t\t\t\tbuttons: buttons,\n\t\t\t\twidth: 400\n\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\ttoggleBannerTemplatesDropdown: function () {\n\t\t\tif ( this.checked === true ) {\n\t\t\t\t$( '.mw-htmlform-field-HTMLSelectLimitField' ).removeClass( 'banner-template-dropdown-hidden' );\n\t\t\t\t$( 'select[name=wpnewBannerTemplate]' ).removeClass( 'banner-template-dropdown-hidden' );\n\t\t\t} else {\n\t\t\t\t$( '.mw-htmlform-field-HTMLSelectLimitField' ).addClass( 'banner-template-dropdown-hidden' );\n\t\t\t\t$( 'select[name=wpnewBannerTemplate]' ).addClass( 'banner-template-dropdown-hidden' );\n\t\t\t}\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\tdoRemoveBanners: 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-manager' )[ 0 ];\n\t\t\t\tformobj.wpaction.value = 'remove';\n\n\t\t\t\tformobj.wpremoveBannerEditSummary.value =\n\t\t\t\t\t$( this )[ 0 ].wpremoveBannerEditSummary.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$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-removeBanner' ).children( 'div' ).clone().show() )\n\t\t\t\t.dialog( {\n\t\t\t\t\ttitle: mw.message(\n\t\t\t\t\t\t'centralnotice-delete-banner-title',\n\t\t\t\t\t\tbm.selectedItemCount\n\t\t\t\t\t).escaped(),\n\t\t\t\t\twidth: '35em',\n\t\t\t\t\tmodal: true,\n\t\t\t\t\tbuttons: buttons\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\tdoArchiveBanners: 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-manager' )[ 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(\n\t\t\t\t\t'centralnotice-archive-banner-title',\n\t\t\t\t\tbm.selectedItemCount\n\t\t\t\t).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 all the banner check boxes when the 'checkAll' check box is clicked\n\t\t */\n\t\tcheckAllStateAltered: function () {\n\t\t\tconst $checkBoxes = $( 'input.cn-bannerlist-check-applyto' );\n\t\t\tif ( $( '#mw-input-wpselectAllBanners' ).prop( 'checked' ) ) {\n\t\t\t\tbm.selectedItemCount = bm.totalSelectableItems;\n\t\t\t\t$checkBoxes.each( function () {\n\t\t\t\t\t$( this ).prop( 'checked', true );\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\tbm.selectedItemCount = 0;\n\t\t\t\t$checkBoxes.each( function () {\n\t\t\t\t\t$( this ).prop( 'checked', false );\n\t\t\t\t} );\n\t\t\t}\n\t\t\tbm.checkedCountUpdated();\n\t\t},\n\n\t\t/**\n\t\t * Updates the 'checkAll' check box if any of the banner check boxes are checked\n\t\t */\n\t\tselectCheckStateAltered: function () {\n\t\t\tif ( $( this ).prop( 'checked' ) === true ) {\n\t\t\t\tbm.selectedItemCount++;\n\t\t\t} else {\n\t\t\t\tbm.selectedItemCount--;\n\t\t\t}\n\t\t\tbm.checkedCountUpdated();\n\t\t},\n\n\t\tcheckedCountUpdated: function () {\n\t\t\tconst $selectAllCheck = $( '#mw-input-wpselectAllBanners' ),\n\t\t\t\t$deleteButton = $( ' #mw-input-wpdeleteSelectedBanners' );\n\n\t\t\tif ( bm.selectedItemCount === bm.totalSelectableItems ) {\n\t\t\t\t// Everything selected\n\t\t\t\t$selectAllCheck.prop( 'checked', true );\n\t\t\t\t$selectAllCheck.prop( 'indeterminate', false );\n\t\t\t\t$deleteButton.prop( 'disabled', false );\n\t\t\t} else if ( bm.selectedItemCount === 0 ) {\n\t\t\t\t// Nothing selected\n\t\t\t\t$selectAllCheck.prop( 'checked', false );\n\t\t\t\t$selectAllCheck.prop( 'indeterminate', false );\n\t\t\t\t$deleteButton.prop( 'disabled', true );\n\t\t\t} else {\n\t\t\t\t// Some things selected\n\t\t\t\t$selectAllCheck.prop( 'checked', true );\n\t\t\t\t$selectAllCheck.prop( 'indeterminate', true );\n\t\t\t\t$deleteButton.prop( 'disabled', false );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Reload the page with a URL query for the requested banner name\n\t\t * filter (or lack thereof).\n\t\t */\n\t\tapplyFilter: function () {\n\t\t\tlet filterStr = $( '#mw-input-wpbannerNameFilter' ).val();\n\t\t\tconst newUri = new mw.Uri();\n\n\t\t\t// If there's a filter, reload with a filter query param.\n\t\t\t// If there's no filter, reload with no such param.\n\t\t\tif ( filterStr.length > 0 ) {\n\t\t\t\tfilterStr = bm.sanitizeFilterStr( filterStr );\n\t\t\t\tnewUri.extend( { filter: filterStr } );\n\t\t\t} else {\n\t\t\t\tdelete newUri.query.filter;\n\t\t\t}\n\n\t\t\tlocation.replace( newUri.toString() );\n\t\t},\n\n\t\t/**\n\t\t * Filter text box keypress handler; applies the filter when enter is\n\t\t * pressed.\n\t\t *\n\t\t * @param e\n\t\t */\n\t\tfilterTextBoxKeypress: function ( e ) {\n\t\t\tif ( e.which === 13 ) {\n\t\t\t\tbm.applyFilter();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove characters not allowed in banner names. See server-side\n\t\t * Banner::isValidBannerName() and\n\t\t * SpecialCentralNotice::sanitizeSearchTerms().\n\t\t *\n\t\t * @param $origFilterStr\n\t\t */\n\t\tsanitizeFilterStr: function ( $origFilterStr ) {\n\t\t\treturn $origFilterStr.replace( /[^0-9a-zA-Z_-]/g, '' );\n\t\t}\n\t};\n\n\t// Attach event handlers\n\t$( '#mw-input-wpaddNewBanner' ).on( 'click', bm.doAddBannerDialog );\n\t$( '#mw-input-wpdeleteSelectedBanners' ).on( 'click', bm.doRemoveBanners );\n\t$( '#mw-input-wparchiveSelectedBanners' ).on( 'click', bm.doArchiveBanners );\n\t$( '#mw-input-wpselectAllBanners' ).on( 'click', bm.checkAllStateAltered );\n\t$( '#mw-input-wpfilterApply' ).on( 'click', bm.applyFilter );\n\t$( '#mw-input-wpbannerNameFilter' ).on( 'keypress', bm.filterTextBoxKeypress );\n\n\t$( 'input.cn-bannerlist-check-applyto' ).each( function () {\n\t\t$( this ).on( 'click', bm.selectCheckStateAltered );\n\t\tbm.totalSelectableItems++;\n\t} );\n\n\t// Some initial display work\n\tbm.checkAllStateAltered();\n\t$( '#cn-js-error-warn' ).hide();\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/campaignManager.js","messages":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 111. Maximum allowed is 100.","line":797,"column":1,"nodeType":"Program","messageId":"max","endLine":797,"endColumn":106},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":805,"column":3,"nodeType":"CallExpression","endLine":805,"endColumn":62}],"suppressedMessages":[{"ruleId":"no-jquery/no-each-util","severity":2,"message":"Prefer Array#forEach to $.each","line":427,"column":3,"nodeType":"CallExpression","endLine":518,"endColumn":6,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":431,"column":15,"nodeType":"CallExpression","endLine":431,"endColumn":46,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":514,"column":30,"nodeType":"CallExpression","endLine":514,"endColumn":60,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/msg-doc","severity":2,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":569,"column":38,"nodeType":"CallExpression","endLine":569,"endColumn":58,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-map-util","severity":2,"message":"Prefer Array#map to $.map","line":797,"column":33,"nodeType":"CallExpression","endLine":797,"endColumn":105,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":1,"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\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\t\t\t\t// eslint-disable-next-line mediawiki/msg-doc\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};\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\t\t\t\t// eslint-disable-next-line mediawiki/msg-doc\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\t\t\t\t// eslint-disable-next-line mediawiki/msg-doc\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( mixinDefs, ( mixinDef ) => mixinDef.customAdminUIControlsModule );\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":103,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":103,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'show_only_matches_children' is not in camel case.","line":105,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":105,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":170,"column":4,"nodeType":"CallExpression","endLine":170,"endColumn":68,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeIn","line":176,"column":5,"nodeType":"CallExpression","endLine":176,"endColumn":68,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":180,"column":5,"nodeType":"CallExpression","endLine":180,"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":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":543,"column":2,"nodeType":"Block","endLine":545,"endColumn":5},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":550,"column":2,"nodeType":"Block","endLine":552,"endColumn":5},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":561,"column":2,"nodeType":"Block","endLine":563,"endColumn":5},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":568,"column":2,"nodeType":"Block","endLine":570,"endColumn":5},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":575,"column":2,"nodeType":"Block","endLine":577,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"seq\" type.","line":611,"column":1,"nodeType":"Block","endLine":611,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"step\" type.","line":635,"column":1,"nodeType":"Block","endLine":635,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":690,"column":2,"nodeType":"Block","endLine":693,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"n\" type.","line":691,"column":1,"nodeType":"Block","endLine":691,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"controller\" type.","line":734,"column":1,"nodeType":"Block","endLine":734,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"model\" type.","line":735,"column":1,"nodeType":"Block","endLine":735,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":802,"column":5,"nodeType":"CallExpression","endLine":814,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":802,"column":5,"nodeType":"CallExpression","endLine":824,"endColumn":8},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":892,"column":1,"nodeType":"Block","endLine":892,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"stepNum\" type.","line":893,"column":1,"nodeType":"Block","endLine":893,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":918,"column":1,"nodeType":"Block","endLine":918,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"banners\" type.","line":919,"column":1,"nodeType":"Block","endLine":919,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":932,"column":1,"nodeType":"Block","endLine":932,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"controller\" type.","line":944,"column":1,"nodeType":"Block","endLine":944,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"model\" type.","line":945,"column":1,"nodeType":"Block","endLine":945,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"config\" type.","line":946,"column":1,"nodeType":"Block","endLine":946,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"stepModel\" type.","line":1050,"column":1,"nodeType":"Block","endLine":1050,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"index\" type.","line":1051,"column":1,"nodeType":"Block","endLine":1051,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"banners\" type.","line":1126,"column":1,"nodeType":"Block","endLine":1126,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"controller\" type.","line":1181,"column":1,"nodeType":"Block","endLine":1181,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"model\" type.","line":1182,"column":1,"nodeType":"Block","endLine":1182,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":1183,"column":1,"nodeType":"Block","endLine":1183,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"config\" type.","line":1184,"column":1,"nodeType":"Block","endLine":1184,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":1317,"column":5,"nodeType":"CallExpression","endLine":1334,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":1317,"column":5,"nodeType":"CallExpression","endLine":1344,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":1353,"column":5,"nodeType":"CallExpression","endLine":1370,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":1353,"column":5,"nodeType":"CallExpression","endLine":1380,"endColumn":8},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":1509,"column":2,"nodeType":"Block","endLine":1512,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"banners\" type.","line":1510,"column":1,"nodeType":"Block","endLine":1510,"endColumn":1}],"suppressedMessages":[],"errorCount":6,"fatalErrorCount":0,"warningCount":28,"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 */\n\tBannerSequenceUiModel.prototype.defaultBktSeq = function () {\n\t\treturn [ this.defaultStep() ];\n\t};\n\n\t/**\n\t * @private\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 */\n\tBannerSequenceUiModel.prototype.defaultBanner = function () {\n\t\treturn null;\n\t};\n\n\t/**\n\t * @private\n\t */\n\tBannerSequenceUiModel.prototype.defaultNumPageViews = function () {\n\t\treturn 1;\n\t};\n\n\t/**\n\t * @private\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 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 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 n\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 controller\n\t * @param 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 bucket\n\t * @param 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 bucket\n\t * @param 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 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 controller\n\t * @param model\n\t * @param 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 stepModel\n\t * @param 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 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 controller\n\t * @param model\n\t * @param bucket\n\t * @param config\n\t * @class StepWidget\n\t * @constructor\n\t */\n\tStepWidget = function ( controller, model, bucket, config ) {\n\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 banners\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":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"campaignName\" type.","line":13,"column":1,"nodeType":"Block","endLine":13,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"property\" type.","line":14,"column":1,"nodeType":"Block","endLine":14,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"value\" type.","line":15,"column":1,"nodeType":"Block","endLine":15,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"initialValue\" type.","line":16,"column":1,"nodeType":"Block","endLine":16,"endColumn":1}],"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":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * CentralNotice Administrative UI - Campaign pager\n */\n/* eslint-disable no-unused-vars */\n// TODO Check whether we can use Object.keys() now, to remove the unused k var and above rule.\n( function () {\n\n\tconst changes = {};\n\n\t/**\n\t * Update changes object as needed\n\t *\n\t * @param campaignName\n\t * @param property\n\t * @param value\n\t * @param initialValue\n\t */\n\tfunction setChange( campaignName, property, value, initialValue ) {\n\n\t\t// If we're returning to the initial value, don't set a change, but\n\t\t// maybe mop up\n\t\tif ( value === initialValue ) {\n\n\t\t\t// The check for changes[campaignName] here shouldn't be necessary,\n\t\t\t// but let's be safe\n\t\t\tif ( ( changes[ campaignName ] !== undefined ) &&\n\t\t\t\t( changes[ campaignName ][ property ] !== undefined ) ) {\n\n\t\t\t\tdelete changes[ campaignName ][ property ];\n\n\t\t\t\t// Remove campaign object from changes, if empty\n\t\t\t\tlet keysCount = 0;\n\t\t\t\tfor ( const k in changes[ campaignName ] ) {\n\t\t\t\t\tkeysCount++;\n\t\t\t\t}\n\n\t\t\t\tif ( !keysCount ) {\n\t\t\t\t\tdelete changes[ campaignName ];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Ensure a campaign object in changes\n\t\tif ( changes[ campaignName ] === undefined ) {\n\t\t\tchanges[ campaignName ] = {};\n\t\t}\n\n\t\tchanges[ campaignName ][ property ] = value;\n\t}\n\n\t/**\n\t * Handler for changes to checkboxes\n\t */\n\tfunction updateCheckboxChanges() {\n\t\tconst $this = $( this );\n\n\t\tsetChange(\n\t\t\t$this.data( 'campaignName' ),\n\t\t\t$this.attr( 'name' ),\n\t\t\t$this.prop( 'checked' ),\n\t\t\tBoolean( $this.data( 'initialValue' ) ) // Comes in as '' or 1\n\t\t);\n\t}\n\n\t/**\n\t * Handler for changes to priority dropdowns\n\t */\n\tfunction updatePriorityChanges() {\n\t\tconst $this = $( this );\n\n\t\tsetChange(\n\t\t\t$this.data( 'campaignName' ),\n\t\t\t'priority',\n\t\t\tparseInt( $this.val(), 10 ), // Munge to int for comparison needs\n\t\t\t$this.data( 'initialValue' ) // jQuery munges to int\n\t\t);\n\t}\n\n\t/**\n\t * Handler for changes to campaign type dropdowns\n\t */\n\tfunction updateCampaignTypeChanges() {\n\t\tconst $this = $( this );\n\n\t\tsetChange(\n\t\t\t$this.data( 'campaignName' ),\n\t\t\t'campaign_type',\n\t\t\t$this.val(),\n\t\t\t$this.data( 'initialValue' )\n\t\t);\n\t}\n\n\t/**\n\t * Click handler for faux submit button, to submit just what's changed\n\t */\n\tfunction submitChanges() {\n\t\tconst $form = $( '<form>' ).attr( 'method', 'post' ),\n\t\t\t$authtokenField = $( '<input>' ).attr( { type: 'hidden', name: 'authtoken' } ),\n\t\t\t$summaryField = $( '<input>' ).attr( { type: 'hidden', name: 'changeSummary' } ),\n\t\t\t$changesField = $( '<input>' ).attr( { type: 'hidden', name: 'changes' } );\n\n\t\t$authtokenField.val( mw.user.tokens.get( 'csrfToken' ) );\n\t\t$summaryField.val(\n\t\t\t$( '#cn-campaign-pager input.cn-change-summary-input' ).val()\n\t\t);\n\t\t$changesField.val( JSON.stringify( changes ) );\n\n\t\t$form.append( $authtokenField, $summaryField, $changesField );\n\t\t$( document.body ).append( $form );\n\t\t$form.trigger( 'submit' );\n\t}\n\n\t$( () => {\n\n\t\tconst CHECKBOX_NAMES = [ 'enabled', 'locked', 'archived' ];\n\n\t\t// Keep data-sort-value attributes for jquery.tablesorter in sync\n\t\t$( '.mw-cn-input-check-sort' ).on( 'change click blur', function () {\n\t\t\t$( this ).parent( 'td' )\n\t\t\t\t.data( 'sortValue', Number( this.checked ) );\n\t\t} );\n\n\t\t// If the table is editable, attach handlers to controls\n\t\tif ( $( '#cn-campaign-pager' ).data( 'editable' ) ) {\n\n\t\t\t// Go through all the fields with checkbox controls\n\t\t\tfor ( let i = 0; i < CHECKBOX_NAMES.length; i++ ) {\n\n\t\t\t\t// Select enabled checkboxes with this name\n\t\t\t\t// See CNCampaignPager::formatValue()\n\t\t\t\tconst selector = '#cn-campaign-pager input[name=\"' +\n\t\t\t\t\tCHECKBOX_NAMES[ i ] + '\"]:not([disabled])';\n\n\t\t\t\t// When checked or unchecked, update changes to send\n\t\t\t\t$( selector ).on( 'change', updateCheckboxChanges );\n\t\t\t}\n\n\t\t\t// Attach handler to priority dropdowns\n\t\t\t// See CentralNotice::prioritySelector()\n\t\t\t$( '#cn-campaign-pager select[name=\"priority\"]:not([disabled])' )\n\t\t\t\t.on( 'change', updatePriorityChanges );\n\n\t\t\t// Attach handler to campaign type dropdowns\n\t\t\t// See CentralNotice::campaignTypeSelector()\n\t\t\t$( '#cn-campaign-pager select[name=\"campaign_type\"]:not([disabled])' )\n\t\t\t\t.on( 'change', updateCampaignTypeChanges );\n\n\t\t\t// Attach handler to \"Submit\" button\n\t\t\t// See CNCampaignPager::getEndBody()\n\t\t\t$( '#cn-campaign-pager-submit' ).on( 'click', submitChanges );\n\t\t}\n\t} );\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.bannerHistoryLogger.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"elData\" type.","line":225,"column":1,"nodeType":"Block","endLine":225,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":232,"column":2,"nodeType":"Block","endLine":237,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"elData\" type.","line":236,"column":1,"nodeType":"Block","endLine":236,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":300,"column":4,"nodeType":"CallExpression","endLine":333,"endColumn":7},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":371,"column":4,"nodeType":"CallExpression","endLine":385,"endColumn":7}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'waitLogNoSendBeacon' is assigned a value but never used.","line":13,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":13,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'inSample' is assigned a value but never used.","line":17,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":17,"endColumn":11,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":2,"fatalErrorCount":0,"warningCount":3,"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\n\tlet bhLogger,\n\t\twaitLogNoSendBeacon,\n\t\tlog,\n\t\tlogSent = false,\n\t\talreadyRun = false,\n\t\tinSample;\n\tconst cn = mw.centralNotice, // Guaranteed to exist; we depend on display RL module\n\t\tmixin = new cn.Mixin( 'bannerHistoryLogger' ),\n\t\tdoNotTrackEnabled =\n\t\t\t// Support: Firefox < 32 (yes/no)\n\t\t\t/1|yes/.test( navigator.doNotTrack ) ||\n\t\t\t// Support: IE 11, Safari 7.1.3+ (window.doNotTrack)\n\t\t\twindow.doNotTrack === '1',\n\t\tnow = Math.round( Date.now() / 1000 ),\n\t\treadyToLogDeferredObj = $.Deferred(),\n\n\t\tBANNER_HISTORY_KV_STORE_KEY = 'banner_history',\n\n\t\t// Maximum time (in days) that the banner history store KV store item\n\t\t// will persist if no entries are added to it.\n\t\tBANNER_HISTORY_KV_STORE_TTL = 365,\n\t\tBANNER_HISTORY_LOG_ENTRY_VERSION = 1, // Update when log format changes\n\t\tEVENT_LOGGING_SCHEMA = 'CentralNoticeBannerHistory',\n\n\t\t// The maximum random shift applied to timestamps, for user privacy\n\t\tTIMESTAMP_RANDOM_SHIFT_MAX = 60;\n\n\t/**\n\t * Load the banner history log from KV storage\n\t */\n\tfunction loadLog() {\n\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\n\t\tconst data = cn.data,\n\n\t\t\t// Randomly shift timestamp +/- 0 to 10 seconds, so logs can't be\n\t\t\t// linked to specific Web requests. This is to strengthen user\n\t\t\t// privacy.\n\t\t\trandomTimeShift =\n\t\t\t\tMath.round( Math.random() * TIMESTAMP_RANDOM_SHIFT_MAX ) -\n\t\t\t\t( TIMESTAMP_RANDOM_SHIFT_MAX / 2 ),\n\n\t\t\ttime = now + randomTimeShift,\n\n\t\t\tlogEntry = {\n\t\t\t\tversion: BANNER_HISTORY_LOG_ENTRY_VERSION,\n\t\t\t\tlanguage: data.uselang,\n\t\t\t\tcountry: data.country,\n\t\t\t\tisAnon: data.anonymous,\n\t\t\t\tcampaign: data.campaign,\n\t\t\t\tcampaignCategory: data.campaignCategory,\n\t\t\t\tbucket: data.bucket,\n\t\t\t\ttime: time,\n\t\t\t\tstatus: data.status,\n\t\t\t\tstatusCode: data.statusCode,\n\t\t\t\tbannersNotGuaranteedToDisplay: !!data.bannersNotGuaranteedToDisplay\n\t\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\n\t\tconst elData = {},\n\t\t\tkvError = 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 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 elData\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\twaitLogNoSendBeacon = mixinParams.waitLogNoSendBeacon;\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":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":124,"column":30,"nodeType":"CallExpression","endLine":124,"endColumn":53},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":159,"column":2,"nodeType":"CallExpression","endLine":161,"endColumn":5}],"suppressedMessages":[],"errorCount":1,"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\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":[],"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.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":[{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":37,"column":2,"nodeType":"Block","endLine":43,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"contextAndOutput\" type.","line":41,"column":1,"nodeType":"Block","endLine":41,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bucket\" type.","line":42,"column":1,"nodeType":"Block","endLine":42,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"choices\" type.","line":197,"column":1,"nodeType":"Block","endLine":197,"endColumn":1}],"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":58,"column":4,"nodeType":"CallExpression","endLine":67,"endColumn":7,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\t'use strict';\n\n\tconst testFixtures = mw.centralNoticeTestFixtures,\n\t\ttestCases = testFixtures.test_cases,\n\t\tnumBuckets = testFixtures.mock_config_values.NoticeNumberOfBuckets,\n\t\tchooser = mw.centralNotice.internal.chooser;\n\n\t// FIXME: fail hard if there is no fixture data\n\n\tQUnit.module( 'ext.centralNotice.display.chooser', QUnit.newMwEnvironment() );\n\n\t// Cycle through test cases, contexts and outputs, and buckets and set up\n\t// allocation tests. For JSLint-happiness, drizzle toasted closure sauce.\n\t// eslint-disable-next-line no-jquery/no-each-util\n\t$.each( testCases, ( testCaseName, testCase ) => {\n\t\t// eslint-disable-next-line no-jquery/no-each-util\n\t\t$.each( testCase.contexts_and_outputs,\n\t\t\t( contextAndOutputName, contextAndOutput ) => {\n\n\t\t\t\t// Note: numBuckets isn't available via mw.config here, only in tests\n\t\t\t\tfor ( let i = 0; i < numBuckets; i++ ) {\n\t\t\t\t\tconst testName = testCaseName + '/' + contextAndOutputName + '/bucket_' + i;\n\n\t\t\t\t\t// Use a deep copy of contextAndOutput for each test, since\n\t\t\t\t\t// properties get added in tests\n\t\t\t\t\tconst allocationTestFunction = makeAllocationTestFunction(\n\t\t\t\t\t\t$.extend( true, {}, contextAndOutput ), i\n\t\t\t\t\t);\n\n\t\t\t\t\tQUnit.test( testName, allocationTestFunction );\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t} );\n\n\t/**\n\t * Return a function for an allocation test, with the required values\n\t * closured in.\n\t *\n\t * @param contextAndOutput\n\t * @param bucket\n\t */\n\tfunction makeAllocationTestFunction( contextAndOutput, bucket ) {\n\t\treturn function ( assert ) {\n\t\t\tconst choices = contextAndOutput.choices,\n\t\t\t\tcontext = contextAndOutput.context,\n\t\t\t\texpectedAllocations = contextAndOutput.allocations;\n\n\t\t\t// Calculate how many assertions to expect:\n\t\t\t// 3 per campaign (existence, allocation and number of banners)\n\t\t\t// plus 2 per banner in each campaign (existence and allocation)\n\t\t\t// plus 1 (number of campaigns)... except if a campaign expects\n\t\t\t// 0 allocation, in which case just 2 assertion per campaign.\n\t\t\tlet expectedAssertCount = 1;\n\t\t\tlet expectedBanners;\n\t\t\t// eslint-disable-next-line no-jquery/no-each-util\n\t\t\t$.each( expectedAllocations, ( key, camp ) => {\n\n\t\t\t\tif ( camp.allocation === 0 ) {\n\t\t\t\t\texpectedAssertCount += 2;\n\t\t\t\t} else {\n\t\t\t\t\texpectedBanners = camp.banners[ bucket ];\n\t\t\t\t\texpectedAssertCount +=\n\t\t\t\t\t\t3 + ( 2 * Object.keys( expectedBanners ).length );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\tassert.expect( expectedAssertCount );\n\n\t\t\t// Munge magic start and end properties into timestamps\n\t\t\tsetChoicesStartEnd( choices );\n\n\t\t\tconst anonymous =\n\t\t\t\tisAnonymousFromLoggedInStatus( context.logged_in_status );\n\n\t\t\tconst availableCampaigns = chooser.makeAvailableCampaigns(\n\t\t\t\tchoices,\n\t\t\t\tcontext.country,\n\t\t\t\tcontext.region,\n\t\t\t\tanonymous,\n\t\t\t\tcontext.device,\n\t\t\t\t0\n\t\t\t);\n\n\t\t\t// Instead of testing the return value of this method, we'll test\n\t\t\t// the allocation properties that are set on the campaigns in\n\t\t\t// choices.\n\t\t\t// TODO Test return value, too.\n\t\t\tchooser.chooseCampaign(\n\t\t\t\tavailableCampaigns,\n\t\t\t\t0\n\t\t\t);\n\n\t\t\tlet allocatedCampaignsCount = 0;\n\n\t\t\t// Cycle through the campaigns in choices and test expected allocation\n\t\t\tfor ( let j = 0; j < choices.length; j++ ) {\n\t\t\t\tconst campaign = choices[ j ];\n\t\t\t\tconst campaignName = campaign.name;\n\n\t\t\t\t// Continue if this campaign wasn't allocated\n\t\t\t\tif ( !( 'allocation' in campaign ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tallocatedCampaignsCount++;\n\n\t\t\t\t// Test that the campaign was expected and was correctly allocated\n\t\t\t\tassert.ok(\n\t\t\t\t\tcampaignName in expectedAllocations,\n\t\t\t\t\t'Allocated campaign ' + campaignName + ' was expected.'\n\t\t\t\t);\n\n\t\t\t\tconst expectedCampaign = expectedAllocations[ campaignName ];\n\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\tcampaign.allocation.toFixed( 3 ),\n\t\t\t\t\texpectedCampaign.allocation.toFixed( 3 ),\n\t\t\t\t\t'Expected allocation for campaign ' + campaignName\n\t\t\t\t);\n\n\t\t\t\t// By not testing banner allocation for unallocated campaigns,\n\t\t\t\t// we make test fixtures more readable :)\n\t\t\t\tif ( expectedCampaign.allocation === 0 ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// As above, instead of testing the return value of this method,\n\t\t\t\t// we'll test the allocation properties that are set on the\n\t\t\t\t// banners in campaign.\n\t\t\t\t// TODO Test return value, too.\n\t\t\t\tchooser.chooseBanner(\n\t\t\t\t\tcampaign,\n\n\t\t\t\t\t// bucket argument mocks internal.bucketer.getReducedBucket()\n\t\t\t\t\tbucket % campaign.bucket_count,\n\t\t\t\t\tanonymous,\n\t\t\t\t\tcontext.device,\n\t\t\t\t\t0\n\t\t\t\t);\n\n\t\t\t\texpectedBanners = expectedCampaign.banners[ bucket ];\n\n\t\t\t\tlet allocatedBannersCount = 0;\n\n\t\t\t\tfor ( let k = 0; k < campaign.banners.length; k++ ) {\n\n\t\t\t\t\tconst banner = campaign.banners[ k ];\n\t\t\t\t\tconst bannerName = banner.name;\n\n\t\t\t\t\t// Continue if this banner wasn't allocated\n\t\t\t\t\tif ( !( 'allocation' in banner ) ) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tallocatedBannersCount++;\n\n\t\t\t\t\t// Test the banner was expected and was correctly allocated\n\t\t\t\t\tassert.ok(\n\t\t\t\t\t\tbannerName in expectedBanners,\n\t\t\t\t\t\t'Allocated banner ' + bannerName + ' was expected.'\n\t\t\t\t\t);\n\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tbanner.allocation.toFixed( 3 ),\n\t\t\t\t\t\texpectedBanners[ bannerName ].toFixed( 3 ),\n\t\t\t\t\t\t'Expected allocation for banner ' + bannerName\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Test that the expected number of banners for this campaign\n\t\t\t\t// were allocated\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\tallocatedBannersCount,\n\t\t\t\t\tObject.keys( expectedBanners ).length,\n\t\t\t\t\t'Number of banners allocated.'\n\t\t\t\t);\n\n\t\t\t}\n\n\t\t\t// Test that the expected number of campaigns were allocated\n\t\t\tassert.strictEqual(\n\t\t\t\tallocatedCampaignsCount,\n\t\t\t\tObject.keys( expectedAllocations ).length,\n\t\t\t\t'Number of campaigns allocated.'\n\t\t\t);\n\t\t};\n\t}\n\n\t/**\n\t * Prepare a test case for use in a test. Currently just substitutes UNIX\n\t * timestamps for times in the fixtures, which are represented as offsets\n\t * in days from the current time. Note: this logic is repeated in PHP for\n\t * PHPUnit tests that use the same fixtures.\n\t *\n\t * @param choices\n\t * @see CentralNoticeTestFixtures::setTestCaseStartEnd()\n\t */\n\tfunction setChoicesStartEnd( choices ) {\n\t\tconst now = new Date();\n\n\t\tfor ( let i = 0; i < choices.length; i++ ) {\n\t\t\tconst choice = choices[ i ];\n\n\t\t\tchoice.start = makeTimestamp( now, choice.start_days_from_now );\n\t\t\tchoice.end = makeTimestamp( now, choice.end_days_from_now );\n\n\t\t\t// Remove these special properties from choices, to make the\n\t\t\t// choices data mirror the real data structure.\n\t\t\tdelete choice.start_days_from_now;\n\t\t\tdelete choice.end_days_from_now;\n\t\t}\n\t}\n\n\t/**\n\t * Return a UNIX timestamp for refDate offset by the number of days\n\t * indicated.\n\t *\n\t * @param {Date} refDate The date to calculate the offset from\n\t * @param {number} offsetInDays\n\t * @return {number}\n\t */\n\tfunction makeTimestamp( refDate, offsetInDays ) {\n\t\tconst date = new Date();\n\t\tdate.setDate( refDate.getDate() + offsetInDays );\n\t\treturn Math.round( date.getTime() / 1000 );\n\t}\n\n\tfunction isAnonymousFromLoggedInStatus( loggedInStatus ) {\n\t\tswitch ( loggedInStatus ) {\n\t\t\tcase 'anonymous':\n\t\t\t\treturn true;\n\t\t\tcase 'logged_in':\n\t\t\t\treturn false;\n\t\t\tdefault:\n\t\t\t\tthrow new Error( 'Non-existent logged-in status.' );\n\t\t}\n\t}\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/tests/qunit/ext.centralNotice.display/index.tests.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"campaignsData\" type.","line":392,"column":1,"nodeType":"Block","endLine":392,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":441,"column":3,"nodeType":"CallExpression","endLine":443,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":481,"column":3,"nodeType":"CallExpression","endLine":483,"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":2,"fatalErrorCount":0,"warningCount":1,"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 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\n\t\t\t// mediawiki.Uri is already a dependency of ext.centralNotice.display\n\t\t\tconst url = new mw.Uri( urlString );\n\t\t\tassert.strictEqual( url.query.campaign, 'campaign1', 'record impression campaign' );\n\t\t\tassert.strictEqual( url.query.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 mw.Uri( urlString );\n\t\t\tassert.strictEqual( url.query.campaign, 'campaign1', 'record impression campaign' );\n\t\t\tassert.strictEqual( url.query.banner, 'banner1', 'record impression banner' );\n\n\t\t\tassert.strictEqual(\n\t\t\t\turl.query.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 mw.Uri( urlString ),\n\t\t\t\tdelay = Date.now() - start;\n\n\t\t\tassert.strictEqual( url.query.campaign, 'campaign1', 'record impression campaign' );\n\t\t\tassert.strictEqual( url.query.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 mw.Uri( urlString ),\n\t\t\t\tstatuses = JSON.parse( url.query.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}() );\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/tests/qunit/ext.centralNotice.kvStore/kvStore.tests.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/tests/qunit/ext.centralNotice.startUp/kvStoreMaintenance.tests.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/tests/qunit/subscribing/ext.centralNotice.bannerSequence.tests.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/tests/qunit/subscribing/ext.centralNotice.geoIP.tests.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":49,"column":3,"nodeType":"CallExpression","endLine":51,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":49,"column":3,"nodeType":"CallExpression","endLine":53,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .fail","line":63,"column":3,"nodeType":"CallExpression","endLine":65,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":2,"message":"Prefer .then to .done","line":63,"column":3,"nodeType":"CallExpression","endLine":67,"endColumn":6}],"suppressedMessages":[],"errorCount":4,"fatalErrorCount":0,"warningCount":0,"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":"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/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 ---
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in resources/.eslintrc.json) on resources/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in tests/qunit/.eslintrc.json) on tests/qunit/.eslintrc.json
Disabling eslint rule 'no-jquery/no-done-fail' (broken in tests/qunit/.eslintrc.json) on tests/qunit/.eslintrc.json
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated @types/easy-table@1.2.0: This is a stub types definition. easy-table provides its own type definitions, so you do not need this installed.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@7.1.7: 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
--- stdout ---

added 773 packages, and audited 774 packages in 9s

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

15 vulnerabilities (4 moderate, 11 high)

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

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

Some issues need review, and may require choosing
a different dependency.

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/bucketer.js
  45:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  48:1  warning  Missing JSDoc @param "name" type   jsdoc/require-param-type
  54:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  57:1  warning  Missing JSDoc @param "name" type   jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/chooser.js
   23:1  warning  Missing JSDoc @param "availableCampaigns" type         jsdoc/require-param-type
  131:2  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  140:1  warning  Missing JSDoc @param "campaign" type                   jsdoc/require-param-type
  141:1  warning  Missing JSDoc @param "bucket" type                     jsdoc/require-param-type
  142:1  warning  Missing JSDoc @param "anon" type                       jsdoc/require-param-type
  143:1  warning  Missing JSDoc @param "device" type                     jsdoc/require-param-type
  184:1  warning  Missing JSDoc @param "possibleBanners" type            jsdoc/require-param-type
  261:1  warning  Missing JSDoc @param "choiceData" type                 jsdoc/require-param-type
  262:1  warning  Missing JSDoc @param "country" type                    jsdoc/require-param-type
  263:1  warning  Missing JSDoc @param "region" type                     jsdoc/require-param-type
  264:1  warning  Missing JSDoc @param "anon" type                       jsdoc/require-param-type
  265:1  warning  Missing JSDoc @param "device" type                     jsdoc/require-param-type
  331:1  warning  This line has a length of 101. Maximum allowed is 100  max-len
  345:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  350:1  warning  Missing JSDoc @param "choiceData" type                 jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/index.js
   60:1  warning  Missing JSDoc @param "name" type         jsdoc/require-param-type
   73:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
   83:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
   94:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
  106:1  warning  Missing JSDoc @param "campaign" type     jsdoc/require-param-type
  246:3  warning  Prefer .then to .fail                    no-jquery/no-done-fail
  278:3  warning  Prefer .then to .done                    no-jquery/no-done-fail
  557:1  warning  Missing JSDoc @param "iteration" type    jsdoc/require-param-type
  626:1  warning  Missing JSDoc @param "bannerJson" type   jsdoc/require-param-type
  723:1  warning  Missing JSDoc @param "reason" type       jsdoc/require-param-type
  733:3  warning  Missing JSDoc @return declaration        jsdoc/require-returns
  748:1  warning  Missing JSDoc @param "reason" type       jsdoc/require-param-type
  760:1  warning  Missing JSDoc @param "rate" type         jsdoc/require-param-type
  772:1  warning  Missing JSDoc @param "rate" type         jsdoc/require-param-type
  820:4  warning  Prefer .then to .fail                    no-jquery/no-done-fail
  820:4  warning  Prefer .then to .done                    no-jquery/no-done-fail
  830:4  warning  Prefer .then to .fail                    no-jquery/no-done-fail
  830:4  warning  Prefer .then to .done                    no-jquery/no-done-fail
  884:1  warning  Missing JSDoc @param "bucket" type       jsdoc/require-param-type
  945:3  warning  Missing JSDoc @return declaration        jsdoc/require-returns
  949:1  warning  Missing JSDoc @param "prop" type         jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/state.js
  107:1  warning  This line has a length of 128. Maximum allowed is 100  max-len
  117:2  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  301:1  warning  Missing JSDoc @param "geo" type                        jsdoc/require-param-type
  335:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  345:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  380:1  warning  Missing JSDoc @param "availableCampaigns" type         jsdoc/require-param-type
  410:1  warning  This line has a length of 101. Maximum allowed is 100  max-len
  447:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  486:1  warning  Missing JSDoc @param "reason" type                     jsdoc/require-param-type
  497:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  548:1  warning  Missing JSDoc @param "bannerCount" type                jsdoc/require-param-type
  558:1  warning  Missing JSDoc @param "rate" type                       jsdoc/require-param-type
  571:1  warning  Missing JSDoc @param "rate" type                       jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.kvStore/kvStore.js
   50:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
   67:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  394:3  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  459:3  warning  Missing JSDoc @return declaration  jsdoc/require-returns

/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
  51:1  warning  Missing JSDoc @param "queue" type  jsdoc/require-param-type

/src/repo/resources/infrastructure/bannereditor.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
  116:3  warning  Prefer .then to .fail  no-jquery/no-done-fail

/src/repo/resources/infrastructure/bannermanager.js
  249:3  warning  Missing JSDoc @return declaration           jsdoc/require-returns
  253:1  warning  Missing JSDoc @param "e" type               jsdoc/require-param-type
  262:3  warning  Missing JSDoc @return declaration           jsdoc/require-returns
  267:1  warning  Missing JSDoc @param "$origFilterStr" type  jsdoc/require-param-type

/src/repo/resources/infrastructure/campaignManager.js
  797:1  warning  This line has a length of 111. Maximum allowed is 100  max-len
  805:3  warning  Prefer .then to .done                                  no-jquery/no-done-fail

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
   543:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   550:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   561:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   568:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   575:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   611:1  warning  Missing JSDoc @param "seq" type         jsdoc/require-param-type
   635:1  warning  Missing JSDoc @param "step" type        jsdoc/require-param-type
   690:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   691:1  warning  Missing JSDoc @param "n" type           jsdoc/require-param-type
   734:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
   735:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
   802:5  warning  Prefer .then to .done                   no-jquery/no-done-fail
   802:5  warning  Prefer .then to .fail                   no-jquery/no-done-fail
   892:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   893:1  warning  Missing JSDoc @param "stepNum" type     jsdoc/require-param-type
   918:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   919:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type
   932:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   944:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
   945:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
   946:1  warning  Missing JSDoc @param "config" type      jsdoc/require-param-type
  1050:1  warning  Missing JSDoc @param "stepModel" type   jsdoc/require-param-type
  1051:1  warning  Missing JSDoc @param "index" type       jsdoc/require-param-type
  1126:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type
  1181:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
  1182:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
  1183:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
  1184:1  warning  Missing JSDoc @param "config" type      jsdoc/require-param-type
  1317:5  warning  Prefer .then to .done                   no-jquery/no-done-fail
  1317:5  warning  Prefer .then to .fail                   no-jquery/no-done-fail
  1353:5  warning  Prefer .then to .done                   no-jquery/no-done-fail
  1353:5  warning  Prefer .then to .fail                   no-jquery/no-done-fail
  1509:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
  1510:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.campaignPager.js
  13:1  warning  Missing JSDoc @param "campaignName" type  jsdoc/require-param-type
  14:1  warning  Missing JSDoc @param "property" type      jsdoc/require-param-type
  15:1  warning  Missing JSDoc @param "value" type         jsdoc/require-param-type
  16:1  warning  Missing JSDoc @param "initialValue" type  jsdoc/require-param-type

/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
  225:1  warning  Missing JSDoc @param "elData" type  jsdoc/require-param-type
  232:2  warning  Missing JSDoc @return declaration   jsdoc/require-returns
  236:1  warning  Missing JSDoc @param "elData" type  jsdoc/require-param-type
  300:4  warning  Prefer .then to .done               no-jquery/no-done-fail
  371:4  warning  Prefer .then to .done               no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.geoIP.js
  124:30  warning  Found non-literal argument in require  security/detect-non-literal-require
  159:2   warning  Prefer .then to .done                  no-jquery/no-done-fail

/src/repo/tests/qunit/ext.centralNotice.display/chooser.tests.js
   37:2  warning  Missing JSDoc @return declaration             jsdoc/require-returns
   41:1  warning  Missing JSDoc @param "contextAndOutput" type  jsdoc/require-param-type
   42:1  warning  Missing JSDoc @param "bucket" type            jsdoc/require-param-type
  197:1  warning  Missing JSDoc @param "choices" type           jsdoc/require-param-type

/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js
  392:1  warning  Missing JSDoc @param "campaignsData" type  jsdoc/require-param-type
  441:3  warning  Prefer .then to .done                      no-jquery/no-done-fail
  481: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

✖ 124 problems (0 errors, 124 warnings)


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

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

Done.

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "@wdio/cli": {
      "name": "@wdio/cli",
      "severity": "high",
      "isDirect": true,
      "via": [
        "webdriverio",
        "yarn-install"
      ],
      "effects": [
        "@wdio/junit-reporter",
        "@wdio/local-runner",
        "@wdio/spec-reporter"
      ],
      "range": "5.4.10 - 8.43.0",
      "nodes": [
        "node_modules/@wdio/cli"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/junit-reporter": {
      "name": "@wdio/junit-reporter",
      "severity": "high",
      "isDirect": true,
      "via": [
        "@wdio/cli"
      ],
      "effects": [],
      "range": "6.0.4 - 8.0.0-alpha.631",
      "nodes": [
        "node_modules/@wdio/junit-reporter"
      ],
      "fixAvailable": {
        "name": "@wdio/junit-reporter",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/local-runner": {
      "name": "@wdio/local-runner",
      "severity": "high",
      "isDirect": true,
      "via": [
        "@wdio/cli",
        "@wdio/runner"
      ],
      "effects": [],
      "range": "6.0.4 - 8.43.0",
      "nodes": [
        "node_modules/@wdio/local-runner"
      ],
      "fixAvailable": {
        "name": "@wdio/local-runner",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/runner": {
      "name": "@wdio/runner",
      "severity": "high",
      "isDirect": false,
      "via": [
        "webdriverio"
      ],
      "effects": [
        "@wdio/local-runner"
      ],
      "range": "7.16.5 - 8.43.0",
      "nodes": [
        "node_modules/@wdio/runner"
      ],
      "fixAvailable": {
        "name": "@wdio/local-runner",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "@wdio/spec-reporter": {
      "name": "@wdio/spec-reporter",
      "severity": "high",
      "isDirect": true,
      "via": [
        "@wdio/cli"
      ],
      "effects": [],
      "range": "6.0.4 - 8.0.0-alpha.631",
      "nodes": [
        "node_modules/@wdio/spec-reporter"
      ],
      "fixAvailable": {
        "name": "@wdio/spec-reporter",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "cross-spawn": {
      "name": "cross-spawn",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1100562,
          "name": "cross-spawn",
          "dependency": "cross-spawn",
          "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn",
          "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275",
          "severity": "high",
          "cwe": [
            "CWE-1333"
          ],
          "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": "<6.0.6"
        }
      ],
      "effects": [
        "yarn-install"
      ],
      "range": "<6.0.6",
      "nodes": [
        "node_modules/yarn-install/node_modules/cross-spawn"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "devtools": {
      "name": "devtools",
      "severity": "high",
      "isDirect": false,
      "via": [
        "puppeteer-core"
      ],
      "effects": [],
      "range": ">=7.16.5",
      "nodes": [
        "node_modules/devtools"
      ],
      "fixAvailable": true
    },
    "mwbot": {
      "name": "mwbot",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "request"
      ],
      "effects": [
        "wdio-mediawiki"
      ],
      "range": ">=0.1.6",
      "nodes": [
        "node_modules/mwbot"
      ],
      "fixAvailable": false
    },
    "puppeteer-core": {
      "name": "puppeteer-core",
      "severity": "high",
      "isDirect": false,
      "via": [
        "ws"
      ],
      "effects": [
        "devtools",
        "webdriverio"
      ],
      "range": "11.0.0 - 22.11.1",
      "nodes": [
        "node_modules/devtools/node_modules/puppeteer-core",
        "node_modules/puppeteer-core"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "request": {
      "name": "request",
      "severity": "moderate",
      "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"
        },
        "tough-cookie"
      ],
      "effects": [
        "mwbot"
      ],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": false
    },
    "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": false
    },
    "wdio-mediawiki": {
      "name": "wdio-mediawiki",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "mwbot"
      ],
      "effects": [],
      "range": "*",
      "nodes": [
        "node_modules/wdio-mediawiki"
      ],
      "fixAvailable": false
    },
    "webdriverio": {
      "name": "webdriverio",
      "severity": "high",
      "isDirect": false,
      "via": [
        "devtools",
        "puppeteer-core"
      ],
      "effects": [
        "@wdio/cli",
        "@wdio/runner"
      ],
      "range": "7.16.5 - 8.43.0",
      "nodes": [
        "node_modules/webdriverio"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "ws": {
      "name": "ws",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1098392,
          "name": "ws",
          "dependency": "ws",
          "title": "ws affected by a DoS when handling a request with many HTTP headers",
          "url": "https://github.com/advisories/GHSA-3h5v-q93c-6h6q",
          "severity": "high",
          "cwe": [
            "CWE-476"
          ],
          "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": ">=8.0.0 <8.17.1"
        }
      ],
      "effects": [
        "puppeteer-core"
      ],
      "range": "8.0.0 - 8.17.0",
      "nodes": [
        "node_modules/devtools/node_modules/ws",
        "node_modules/ws"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    },
    "yarn-install": {
      "name": "yarn-install",
      "severity": "high",
      "isDirect": false,
      "via": [
        "cross-spawn"
      ],
      "effects": [
        "@wdio/cli"
      ],
      "range": "*",
      "nodes": [
        "node_modules/yarn-install"
      ],
      "fixAvailable": {
        "name": "@wdio/cli",
        "version": "9.12.2",
        "isSemVerMajor": true
      }
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 4,
      "high": 11,
      "critical": 0,
      "total": 15
    },
    "dependencies": {
      "prod": 1,
      "dev": 774,
      "optional": 2,
      "peer": 1,
      "peerOptional": 0,
      "total": 774
    }
  }
}

--- 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": 1,
  "removed": 0,
  "changed": 0,
  "audited": 775,
  "funding": 131,
  "audit": {
    "auditReportVersion": 2,
    "vulnerabilities": {
      "@wdio/cli": {
        "name": "@wdio/cli",
        "severity": "high",
        "isDirect": true,
        "via": [
          "webdriverio",
          "yarn-install"
        ],
        "effects": [
          "@wdio/junit-reporter",
          "@wdio/local-runner",
          "@wdio/spec-reporter"
        ],
        "range": "5.4.10 - 8.43.0",
        "nodes": [
          "node_modules/@wdio/cli"
        ],
        "fixAvailable": {
          "name": "@wdio/cli",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "@wdio/junit-reporter": {
        "name": "@wdio/junit-reporter",
        "severity": "high",
        "isDirect": true,
        "via": [
          "@wdio/cli"
        ],
        "effects": [],
        "range": "6.0.4 - 8.0.0-alpha.631",
        "nodes": [
          "node_modules/@wdio/junit-reporter"
        ],
        "fixAvailable": {
          "name": "@wdio/junit-reporter",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "@wdio/local-runner": {
        "name": "@wdio/local-runner",
        "severity": "high",
        "isDirect": true,
        "via": [
          "@wdio/cli",
          "@wdio/runner"
        ],
        "effects": [],
        "range": "6.0.4 - 8.43.0",
        "nodes": [
          "node_modules/@wdio/local-runner"
        ],
        "fixAvailable": {
          "name": "@wdio/local-runner",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "@wdio/runner": {
        "name": "@wdio/runner",
        "severity": "high",
        "isDirect": false,
        "via": [
          "webdriverio"
        ],
        "effects": [
          "@wdio/local-runner"
        ],
        "range": "7.16.5 - 8.43.0",
        "nodes": [
          "node_modules/@wdio/runner"
        ],
        "fixAvailable": {
          "name": "@wdio/local-runner",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "@wdio/spec-reporter": {
        "name": "@wdio/spec-reporter",
        "severity": "high",
        "isDirect": true,
        "via": [
          "@wdio/cli"
        ],
        "effects": [],
        "range": "6.0.4 - 8.0.0-alpha.631",
        "nodes": [
          "node_modules/@wdio/spec-reporter"
        ],
        "fixAvailable": {
          "name": "@wdio/spec-reporter",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "cross-spawn": {
        "name": "cross-spawn",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1100562,
            "name": "cross-spawn",
            "dependency": "cross-spawn",
            "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn",
            "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275",
            "severity": "high",
            "cwe": [
              "CWE-1333"
            ],
            "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": "<6.0.6"
          }
        ],
        "effects": [
          "yarn-install"
        ],
        "range": "<6.0.6",
        "nodes": [
          "node_modules/yarn-install/node_modules/cross-spawn"
        ],
        "fixAvailable": {
          "name": "@wdio/cli",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "devtools": {
        "name": "devtools",
        "severity": "high",
        "isDirect": false,
        "via": [
          "puppeteer-core"
        ],
        "effects": [],
        "range": ">=7.16.5",
        "nodes": [
          "node_modules/devtools"
        ],
        "fixAvailable": true
      },
      "mwbot": {
        "name": "mwbot",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "request"
        ],
        "effects": [
          "wdio-mediawiki"
        ],
        "range": ">=0.1.6",
        "nodes": [
          "node_modules/mwbot"
        ],
        "fixAvailable": false
      },
      "puppeteer-core": {
        "name": "puppeteer-core",
        "severity": "high",
        "isDirect": false,
        "via": [
          "ws"
        ],
        "effects": [
          "devtools",
          "webdriverio"
        ],
        "range": "11.0.0 - 22.11.1",
        "nodes": [
          "node_modules/devtools/node_modules/puppeteer-core",
          "node_modules/puppeteer-core"
        ],
        "fixAvailable": {
          "name": "@wdio/cli",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "request": {
        "name": "request",
        "severity": "moderate",
        "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"
          },
          "tough-cookie"
        ],
        "effects": [
          "mwbot"
        ],
        "range": "*",
        "nodes": [
          "node_modules/request"
        ],
        "fixAvailable": false
      },
      "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": false
      },
      "wdio-mediawiki": {
        "name": "wdio-mediawiki",
        "severity": "moderate",
        "isDirect": true,
        "via": [
          "mwbot"
        ],
        "effects": [],
        "range": "*",
        "nodes": [
          "node_modules/wdio-mediawiki"
        ],
        "fixAvailable": false
      },
      "webdriverio": {
        "name": "webdriverio",
        "severity": "high",
        "isDirect": false,
        "via": [
          "devtools",
          "puppeteer-core"
        ],
        "effects": [
          "@wdio/cli",
          "@wdio/runner"
        ],
        "range": "7.16.5 - 8.43.0",
        "nodes": [
          "node_modules/webdriverio"
        ],
        "fixAvailable": {
          "name": "@wdio/cli",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "ws": {
        "name": "ws",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1098392,
            "name": "ws",
            "dependency": "ws",
            "title": "ws affected by a DoS when handling a request with many HTTP headers",
            "url": "https://github.com/advisories/GHSA-3h5v-q93c-6h6q",
            "severity": "high",
            "cwe": [
              "CWE-476"
            ],
            "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": ">=8.0.0 <8.17.1"
          }
        ],
        "effects": [
          "puppeteer-core"
        ],
        "range": "8.0.0 - 8.17.0",
        "nodes": [
          "node_modules/devtools/node_modules/ws",
          "node_modules/ws"
        ],
        "fixAvailable": {
          "name": "@wdio/cli",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      },
      "yarn-install": {
        "name": "yarn-install",
        "severity": "high",
        "isDirect": false,
        "via": [
          "cross-spawn"
        ],
        "effects": [
          "@wdio/cli"
        ],
        "range": "*",
        "nodes": [
          "node_modules/yarn-install"
        ],
        "fixAvailable": {
          "name": "@wdio/cli",
          "version": "9.12.2",
          "isSemVerMajor": true
        }
      }
    },
    "metadata": {
      "vulnerabilities": {
        "info": 0,
        "low": 0,
        "moderate": 4,
        "high": 11,
        "critical": 0,
        "total": 15
      },
      "dependencies": {
        "prod": 1,
        "dev": 774,
        "optional": 2,
        "peer": 1,
        "peerOptional": 0,
        "total": 774
      }
    }
  }
}

--- end ---
{"added": 1, "removed": 0, "changed": 0, "audited": 775, "funding": 131, "audit": {"auditReportVersion": 2, "vulnerabilities": {"@wdio/cli": {"name": "@wdio/cli", "severity": "high", "isDirect": true, "via": ["webdriverio", "yarn-install"], "effects": ["@wdio/junit-reporter", "@wdio/local-runner", "@wdio/spec-reporter"], "range": "5.4.10 - 8.43.0", "nodes": ["node_modules/@wdio/cli"], "fixAvailable": {"name": "@wdio/cli", "version": "9.12.2", "isSemVerMajor": true}}, "@wdio/junit-reporter": {"name": "@wdio/junit-reporter", "severity": "high", "isDirect": true, "via": ["@wdio/cli"], "effects": [], "range": "6.0.4 - 8.0.0-alpha.631", "nodes": ["node_modules/@wdio/junit-reporter"], "fixAvailable": {"name": "@wdio/junit-reporter", "version": "9.12.2", "isSemVerMajor": true}}, "@wdio/local-runner": {"name": "@wdio/local-runner", "severity": "high", "isDirect": true, "via": ["@wdio/cli", "@wdio/runner"], "effects": [], "range": "6.0.4 - 8.43.0", "nodes": ["node_modules/@wdio/local-runner"], "fixAvailable": {"name": "@wdio/local-runner", "version": "9.12.2", "isSemVerMajor": true}}, "@wdio/runner": {"name": "@wdio/runner", "severity": "high", "isDirect": false, "via": ["webdriverio"], "effects": ["@wdio/local-runner"], "range": "7.16.5 - 8.43.0", "nodes": ["node_modules/@wdio/runner"], "fixAvailable": {"name": "@wdio/local-runner", "version": "9.12.2", "isSemVerMajor": true}}, "@wdio/spec-reporter": {"name": "@wdio/spec-reporter", "severity": "high", "isDirect": true, "via": ["@wdio/cli"], "effects": [], "range": "6.0.4 - 8.0.0-alpha.631", "nodes": ["node_modules/@wdio/spec-reporter"], "fixAvailable": {"name": "@wdio/spec-reporter", "version": "9.12.2", "isSemVerMajor": true}}, "cross-spawn": {"name": "cross-spawn", "severity": "high", "isDirect": false, "via": [{"source": 1100562, "name": "cross-spawn", "dependency": "cross-spawn", "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn", "url": "https://github.com/advisories/GHSA-3xgq-45jj-v275", "severity": "high", "cwe": ["CWE-1333"], "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": "<6.0.6"}], "effects": ["yarn-install"], "range": "<6.0.6", "nodes": ["node_modules/yarn-install/node_modules/cross-spawn"], "fixAvailable": {"name": "@wdio/cli", "version": "9.12.2", "isSemVerMajor": true}}, "devtools": {"name": "devtools", "severity": "high", "isDirect": false, "via": ["puppeteer-core"], "effects": [], "range": ">=7.16.5", "nodes": ["node_modules/devtools"], "fixAvailable": true}, "mwbot": {"name": "mwbot", "severity": "moderate", "isDirect": false, "via": ["request"], "effects": ["wdio-mediawiki"], "range": ">=0.1.6", "nodes": ["node_modules/mwbot"], "fixAvailable": false}, "puppeteer-core": {"name": "puppeteer-core", "severity": "high", "isDirect": false, "via": ["ws"], "effects": ["devtools", "webdriverio"], "range": "11.0.0 - 22.11.1", "nodes": ["node_modules/devtools/node_modules/puppeteer-core", "node_modules/puppeteer-core"], "fixAvailable": {"name": "@wdio/cli", "version": "9.12.2", "isSemVerMajor": true}}, "request": {"name": "request", "severity": "moderate", "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"}, "tough-cookie"], "effects": ["mwbot"], "range": "*", "nodes": ["node_modules/request"], "fixAvailable": false}, "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": false}, "wdio-mediawiki": {"name": "wdio-mediawiki", "severity": "moderate", "isDirect": true, "via": ["mwbot"], "effects": [], "range": "*", "nodes": ["node_modules/wdio-mediawiki"], "fixAvailable": false}, "webdriverio": {"name": "webdriverio", "severity": "high", "isDirect": false, "via": ["devtools", "puppeteer-core"], "effects": ["@wdio/cli", "@wdio/runner"], "range": "7.16.5 - 8.43.0", "nodes": ["node_modules/webdriverio"], "fixAvailable": {"name": "@wdio/cli", "version": "9.12.2", "isSemVerMajor": true}}, "ws": {"name": "ws", "severity": "high", "isDirect": false, "via": [{"source": 1098392, "name": "ws", "dependency": "ws", "title": "ws affected by a DoS when handling a request with many HTTP headers", "url": "https://github.com/advisories/GHSA-3h5v-q93c-6h6q", "severity": "high", "cwe": ["CWE-476"], "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": ">=8.0.0 <8.17.1"}], "effects": ["puppeteer-core"], "range": "8.0.0 - 8.17.0", "nodes": ["node_modules/devtools/node_modules/ws", "node_modules/ws"], "fixAvailable": {"name": "@wdio/cli", "version": "9.12.2", "isSemVerMajor": true}}, "yarn-install": {"name": "yarn-install", "severity": "high", "isDirect": false, "via": ["cross-spawn"], "effects": ["@wdio/cli"], "range": "*", "nodes": ["node_modules/yarn-install"], "fixAvailable": {"name": "@wdio/cli", "version": "9.12.2", "isSemVerMajor": true}}}, "metadata": {"vulnerabilities": {"info": 0, "low": 0, "moderate": 4, "high": 11, "critical": 0, "total": 15}, "dependencies": {"prod": 1, "dev": 774, "optional": 2, "peer": 1, "peerOptional": 0, "total": 774}}}}
$ /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 ---

up to date, audited 774 packages in 3s

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

# npm audit report

cross-spawn  <6.0.6
Severity: high
Regular Expression Denial of Service (ReDoS) in cross-spawn - https://github.com/advisories/GHSA-3xgq-45jj-v275
fix available via `npm audit fix --force`
Will install @wdio/cli@9.12.2, which is a breaking change
node_modules/yarn-install/node_modules/cross-spawn
  yarn-install  *
  Depends on vulnerable versions of cross-spawn
  node_modules/yarn-install
    @wdio/cli  5.4.10 - 8.43.0
    Depends on vulnerable versions of webdriverio
    Depends on vulnerable versions of yarn-install
    node_modules/@wdio/cli
      @wdio/junit-reporter  6.0.4 - 8.0.0-alpha.631
      Depends on vulnerable versions of @wdio/cli
      node_modules/@wdio/junit-reporter
      @wdio/local-runner  6.0.4 - 8.43.0
      Depends on vulnerable versions of @wdio/cli
      Depends on vulnerable versions of @wdio/runner
      node_modules/@wdio/local-runner
      @wdio/spec-reporter  6.0.4 - 8.0.0-alpha.631
      Depends on vulnerable versions of @wdio/cli
      node_modules/@wdio/spec-reporter

request  *
Severity: moderate
Server-Side Request Forgery in Request - https://github.com/advisories/GHSA-p8p7-x288-28g6
Depends on vulnerable versions of tough-cookie
No fix available
node_modules/request
  mwbot  >=0.1.6
  Depends on vulnerable versions of request
  node_modules/mwbot
    wdio-mediawiki  *
    Depends on vulnerable versions of mwbot
    node_modules/wdio-mediawiki

tough-cookie  <4.1.3
Severity: moderate
tough-cookie Prototype Pollution vulnerability - https://github.com/advisories/GHSA-72xf-g2v4-qvf3
No fix available
node_modules/tough-cookie

ws  8.0.0 - 8.17.0
Severity: high
ws affected by a DoS when handling a request with many HTTP headers - https://github.com/advisories/GHSA-3h5v-q93c-6h6q
fix available via `npm audit fix --force`
Will install @wdio/cli@9.12.2, which is a breaking change
node_modules/devtools/node_modules/ws
node_modules/ws
  puppeteer-core  11.0.0 - 22.11.1
  Depends on vulnerable versions of ws
  node_modules/devtools/node_modules/puppeteer-core
  node_modules/puppeteer-core
    devtools  >=7.16.5
    Depends on vulnerable versions of puppeteer-core
    node_modules/devtools
    webdriverio  7.16.5 - 8.43.0
    Depends on vulnerable versions of devtools
    Depends on vulnerable versions of puppeteer-core
    node_modules/webdriverio
      @wdio/runner  7.16.5 - 8.43.0
      Depends on vulnerable versions of webdriverio
      node_modules/@wdio/runner

15 vulnerabilities (4 moderate, 11 high)

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

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

Some issues need review, and may require choosing
a different dependency.

--- end ---
Verifying that tests still pass
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated @types/easy-table@1.2.0: This is a stub types definition. easy-table provides its own type definitions, so you do not need this installed.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@8.1.0: Glob versions prior to v9 are no longer supported
npm WARN deprecated glob@7.1.7: 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
--- stdout ---

added 773 packages, and audited 774 packages in 10s

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

15 vulnerabilities (4 moderate, 11 high)

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

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

Some issues need review, and may require choosing
a different dependency.

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/bucketer.js
  45:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  48:1  warning  Missing JSDoc @param "name" type   jsdoc/require-param-type
  54:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  57:1  warning  Missing JSDoc @param "name" type   jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/chooser.js
   23:1  warning  Missing JSDoc @param "availableCampaigns" type         jsdoc/require-param-type
  131:2  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  140:1  warning  Missing JSDoc @param "campaign" type                   jsdoc/require-param-type
  141:1  warning  Missing JSDoc @param "bucket" type                     jsdoc/require-param-type
  142:1  warning  Missing JSDoc @param "anon" type                       jsdoc/require-param-type
  143:1  warning  Missing JSDoc @param "device" type                     jsdoc/require-param-type
  184:1  warning  Missing JSDoc @param "possibleBanners" type            jsdoc/require-param-type
  261:1  warning  Missing JSDoc @param "choiceData" type                 jsdoc/require-param-type
  262:1  warning  Missing JSDoc @param "country" type                    jsdoc/require-param-type
  263:1  warning  Missing JSDoc @param "region" type                     jsdoc/require-param-type
  264:1  warning  Missing JSDoc @param "anon" type                       jsdoc/require-param-type
  265:1  warning  Missing JSDoc @param "device" type                     jsdoc/require-param-type
  331:1  warning  This line has a length of 101. Maximum allowed is 100  max-len
  345:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  350:1  warning  Missing JSDoc @param "choiceData" type                 jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/index.js
   60:1  warning  Missing JSDoc @param "name" type         jsdoc/require-param-type
   73:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
   83:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
   94:1  warning  Missing JSDoc @param "handlerFunc" type  jsdoc/require-param-type
  106:1  warning  Missing JSDoc @param "campaign" type     jsdoc/require-param-type
  246:3  warning  Prefer .then to .fail                    no-jquery/no-done-fail
  278:3  warning  Prefer .then to .done                    no-jquery/no-done-fail
  557:1  warning  Missing JSDoc @param "iteration" type    jsdoc/require-param-type
  626:1  warning  Missing JSDoc @param "bannerJson" type   jsdoc/require-param-type
  723:1  warning  Missing JSDoc @param "reason" type       jsdoc/require-param-type
  733:3  warning  Missing JSDoc @return declaration        jsdoc/require-returns
  748:1  warning  Missing JSDoc @param "reason" type       jsdoc/require-param-type
  760:1  warning  Missing JSDoc @param "rate" type         jsdoc/require-param-type
  772:1  warning  Missing JSDoc @param "rate" type         jsdoc/require-param-type
  820:4  warning  Prefer .then to .fail                    no-jquery/no-done-fail
  820:4  warning  Prefer .then to .done                    no-jquery/no-done-fail
  830:4  warning  Prefer .then to .fail                    no-jquery/no-done-fail
  830:4  warning  Prefer .then to .done                    no-jquery/no-done-fail
  884:1  warning  Missing JSDoc @param "bucket" type       jsdoc/require-param-type
  945:3  warning  Missing JSDoc @return declaration        jsdoc/require-returns
  949:1  warning  Missing JSDoc @param "prop" type         jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.display/state.js
  107:1  warning  This line has a length of 128. Maximum allowed is 100  max-len
  117:2  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  301:1  warning  Missing JSDoc @param "geo" type                        jsdoc/require-param-type
  335:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  345:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  380:1  warning  Missing JSDoc @param "availableCampaigns" type         jsdoc/require-param-type
  410:1  warning  This line has a length of 101. Maximum allowed is 100  max-len
  447:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  486:1  warning  Missing JSDoc @param "reason" type                     jsdoc/require-param-type
  497:3  warning  Missing JSDoc @return declaration                      jsdoc/require-returns
  548:1  warning  Missing JSDoc @param "bannerCount" type                jsdoc/require-param-type
  558:1  warning  Missing JSDoc @param "rate" type                       jsdoc/require-param-type
  571:1  warning  Missing JSDoc @param "rate" type                       jsdoc/require-param-type

/src/repo/resources/ext.centralNotice.kvStore/kvStore.js
   50:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
   67:2  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  394:3  warning  Missing JSDoc @return declaration  jsdoc/require-returns
  459:3  warning  Missing JSDoc @return declaration  jsdoc/require-returns

/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
  51:1  warning  Missing JSDoc @param "queue" type  jsdoc/require-param-type

/src/repo/resources/infrastructure/bannereditor.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
  116:3  warning  Prefer .then to .fail  no-jquery/no-done-fail

/src/repo/resources/infrastructure/bannermanager.js
  249:3  warning  Missing JSDoc @return declaration           jsdoc/require-returns
  253:1  warning  Missing JSDoc @param "e" type               jsdoc/require-param-type
  262:3  warning  Missing JSDoc @return declaration           jsdoc/require-returns
  267:1  warning  Missing JSDoc @param "$origFilterStr" type  jsdoc/require-param-type

/src/repo/resources/infrastructure/campaignManager.js
  797:1  warning  This line has a length of 111. Maximum allowed is 100  max-len
  805:3  warning  Prefer .then to .done                                  no-jquery/no-done-fail

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
   543:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   550:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   561:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   568:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   575:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   611:1  warning  Missing JSDoc @param "seq" type         jsdoc/require-param-type
   635:1  warning  Missing JSDoc @param "step" type        jsdoc/require-param-type
   690:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
   691:1  warning  Missing JSDoc @param "n" type           jsdoc/require-param-type
   734:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
   735:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
   802:5  warning  Prefer .then to .done                   no-jquery/no-done-fail
   802:5  warning  Prefer .then to .fail                   no-jquery/no-done-fail
   892:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   893:1  warning  Missing JSDoc @param "stepNum" type     jsdoc/require-param-type
   918:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   919:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type
   932:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
   944:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
   945:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
   946:1  warning  Missing JSDoc @param "config" type      jsdoc/require-param-type
  1050:1  warning  Missing JSDoc @param "stepModel" type   jsdoc/require-param-type
  1051:1  warning  Missing JSDoc @param "index" type       jsdoc/require-param-type
  1126:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type
  1181:1  warning  Missing JSDoc @param "controller" type  jsdoc/require-param-type
  1182:1  warning  Missing JSDoc @param "model" type       jsdoc/require-param-type
  1183:1  warning  Missing JSDoc @param "bucket" type      jsdoc/require-param-type
  1184:1  warning  Missing JSDoc @param "config" type      jsdoc/require-param-type
  1317:5  warning  Prefer .then to .done                   no-jquery/no-done-fail
  1317:5  warning  Prefer .then to .fail                   no-jquery/no-done-fail
  1353:5  warning  Prefer .then to .done                   no-jquery/no-done-fail
  1353:5  warning  Prefer .then to .fail                   no-jquery/no-done-fail
  1509:2  warning  Missing JSDoc @return declaration       jsdoc/require-returns
  1510:1  warning  Missing JSDoc @param "banners" type     jsdoc/require-param-type

/src/repo/resources/infrastructure/ext.centralNotice.adminUi.campaignPager.js
  13:1  warning  Missing JSDoc @param "campaignName" type  jsdoc/require-param-type
  14:1  warning  Missing JSDoc @param "property" type      jsdoc/require-param-type
  15:1  warning  Missing JSDoc @param "value" type         jsdoc/require-param-type
  16:1  warning  Missing JSDoc @param "initialValue" type  jsdoc/require-param-type

/src/repo/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
  225:1  warning  Missing JSDoc @param "elData" type  jsdoc/require-param-type
  232:2  warning  Missing JSDoc @return declaration   jsdoc/require-returns
  236:1  warning  Missing JSDoc @param "elData" type  jsdoc/require-param-type
  300:4  warning  Prefer .then to .done               no-jquery/no-done-fail
  371:4  warning  Prefer .then to .done               no-jquery/no-done-fail

/src/repo/resources/subscribing/ext.centralNotice.geoIP.js
  124:30  warning  Found non-literal argument in require  security/detect-non-literal-require
  159:2   warning  Prefer .then to .done                  no-jquery/no-done-fail

/src/repo/tests/qunit/ext.centralNotice.display/chooser.tests.js
   37:2  warning  Missing JSDoc @return declaration             jsdoc/require-returns
   41:1  warning  Missing JSDoc @param "contextAndOutput" type  jsdoc/require-param-type
   42:1  warning  Missing JSDoc @param "bucket" type            jsdoc/require-param-type
  197:1  warning  Missing JSDoc @param "choices" type           jsdoc/require-param-type

/src/repo/tests/qunit/ext.centralNotice.display/index.tests.js
  392:1  warning  Missing JSDoc @param "campaignsData" type  jsdoc/require-param-type
  441:3  warning  Prefer .then to .done                      no-jquery/no-done-fail
  481: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

✖ 124 problems (0 errors, 124 warnings)


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

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

Done.

--- end ---
{}
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
build: Updating npm dependencies

* @wdio/cli: 7.33.0 → 7.40.0
* @wdio/junit-reporter: 7.33.0 → 7.40.0
* @wdio/local-runner: 7.33.0 → 7.40.0
* @wdio/mocha-framework: 7.33.0 → 7.40.0
* eslint-config-wikimedia: 0.28.2 → 0.29.1
  The following rules are failing and were disabled:
  * resources:
    * no-jquery/no-done-fail* tests/qunit:
    * no-jquery/no-done-fail
* @wdio/spec-reporter: 7.33.0 → 7.40.0

$ git add .
--- stdout ---

--- end ---
$ git commit -F /tmp/tmpnmcuu9t4
--- stdout ---
[master b34a0d3] build: Updating npm dependencies
 12 files changed, 254 insertions(+), 261 deletions(-)

--- end ---
$ git format-patch HEAD~1 --stdout
--- stdout ---
From b34a0d3532f6843f56292ae81caae4b2a4a0ed66 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Mon, 31 Mar 2025 01:03:42 +0000
Subject: [PATCH] build: Updating npm dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* @wdio/cli: 7.33.0 → 7.40.0
* @wdio/junit-reporter: 7.33.0 → 7.40.0
* @wdio/local-runner: 7.33.0 → 7.40.0
* @wdio/mocha-framework: 7.33.0 → 7.40.0
* eslint-config-wikimedia: 0.28.2 → 0.29.1
  The following rules are failing and were disabled:
  * resources:
    * no-jquery/no-done-fail* tests/qunit:
    * no-jquery/no-done-fail
* @wdio/spec-reporter: 7.33.0 → 7.40.0

Change-Id: Ia96888a13eaa3f5e4adb7865298b9444e2ef01cd
---
 package-lock.json                             | 469 +++++++++---------
 package.json                                  |  12 +-
 resources/.eslintrc.json                      |   3 +-
 .../ext.centralNotice.display/chooser.js      |   8 +-
 resources/ext.centralNotice.display/index.js  |   2 +-
 resources/ext.centralNotice.display/state.js  |   4 +-
 .../ext.centralNotice.kvStore/kvStore.js      |   4 +-
 .../kvStoreMaintenance.js                     |   2 +-
 resources/infrastructure/bannereditor.js      |   2 +-
 resources/infrastructure/campaignManager.js   |   2 +-
 ...xt.centralNotice.adminUi.bannerSequence.js |   4 +-
 tests/qunit/.eslintrc.json                    |   3 +-
 12 files changed, 254 insertions(+), 261 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 40681e2..47a15bd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,12 +6,12 @@
 		"": {
 			"name": "CentralNotice",
 			"devDependencies": {
-				"@wdio/cli": "7.33.0",
-				"@wdio/junit-reporter": "7.33.0",
-				"@wdio/local-runner": "7.33.0",
-				"@wdio/mocha-framework": "7.33.0",
-				"@wdio/spec-reporter": "7.33.0",
-				"eslint-config-wikimedia": "0.28.2",
+				"@wdio/cli": "7.40.0",
+				"@wdio/junit-reporter": "7.40.0",
+				"@wdio/local-runner": "7.40.0",
+				"@wdio/mocha-framework": "7.40.0",
+				"@wdio/spec-reporter": "7.40.0",
+				"eslint-config-wikimedia": "0.29.1",
 				"grunt": "1.6.1",
 				"grunt-banana-checker": "0.13.0",
 				"grunt-eslint": "24.3.0",
@@ -496,9 +496,9 @@
 			}
 		},
 		"node_modules/@types/diff": {
-			"version": "5.2.1",
-			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz",
-			"integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==",
+			"version": "5.2.3",
+			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz",
+			"integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
 			"dev": true
 		},
 		"node_modules/@types/easy-table": {
@@ -937,9 +937,9 @@
 			"dev": true
 		},
 		"node_modules/@wdio/cli": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.33.0.tgz",
-			"integrity": "sha512-S5Iy4AVcbcJDMhAP4k/Yf18mKma9NGFM8A5bafcGRpFlIj97rpnb0/cpmJVVEr4v/wr3XCu0k38ooJw0B/D3nw==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.40.0.tgz",
+			"integrity": "sha512-M0txYEqqamBvJe4FEuqwWq1jd879sElF047BXSv2GRu4R1/iEBPYJHjn9KuL60Fkkpp/L1NMHTl7gW9i445edQ==",
 			"dev": true,
 			"dependencies": {
 				"@types/ejs": "^3.0.5",
@@ -950,11 +950,11 @@
 				"@types/lodash.union": "^4.6.6",
 				"@types/node": "^18.0.0",
 				"@types/recursive-readdir": "^2.2.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"chalk": "^4.0.0",
 				"chokidar": "^3.0.0",
@@ -967,7 +967,7 @@
 				"lodash.union": "^4.6.0",
 				"mkdirp": "^3.0.0",
 				"recursive-readdir": "^2.2.2",
-				"webdriverio": "7.33.0",
+				"webdriverio": "7.40.0",
 				"yargs": "^17.0.0",
 				"yarn-install": "^1.0.0"
 			},
@@ -979,15 +979,15 @@
 			}
 		},
 		"node_modules/@wdio/config": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz",
-			"integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.40.0.tgz",
+			"integrity": "sha512-ayQELXyxa+k9/2a509F5a1oTsCa/w8D1nDrd+hzm+1mYb4Te2lceWCCzm+atGKkMpvjLH4GvhrEBYLh3rIWk2A==",
 			"dev": true,
 			"dependencies": {
 				"@types/glob": "^8.1.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"glob": "^8.0.3"
 			},
@@ -1037,15 +1037,15 @@
 			}
 		},
 		"node_modules/@wdio/junit-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.33.0.tgz",
-			"integrity": "sha512-0Gj+lvUmscTjXbC+ziiG/1W64h2Z1Lgy04rHn4vU3xNp771+KJ13Ry1nxY5bUbOsfD1Ix6R1gKSz98nCoZCZpg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.40.0.tgz",
+			"integrity": "sha512-nGxzvdCBHUQOtKbCrihO+MjLdfyeYPVeoCAWBNbPHP06nnjsoVDT7k1Ic7BwAbrDZn1SUOVhwdGOxqDdc1E8Fg==",
 			"dev": true,
 			"dependencies": {
 				"@types/json-stringify-safe": "^5.0.0",
 				"@types/validator": "^13.1.3",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"json-stringify-safe": "^5.0.1",
 				"junit-report-builder": "^3.0.0",
 				"validator": "^13.0.0"
@@ -1058,16 +1058,16 @@
 			}
 		},
 		"node_modules/@wdio/local-runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.33.0.tgz",
-			"integrity": "sha512-oZLLyOizlX2mV3FIxRLWgN0J2sDL+6LhC71CwFxcV8iVjXvp16my9jbKrgtkIgdo1BsaWIqq+tZlCr9e9NUUjA==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.40.0.tgz",
+			"integrity": "sha512-OBuN7TlFhbPUH7Wbh2S8OKZOjeW4rHXOfuGzJfaKkzjHje2Dqide/uC3Gd25MwmzgZcVkOo9DUYiGFCHXc44ug==",
 			"dev": true,
 			"dependencies": {
 				"@types/stream-buffers": "^3.0.3",
 				"@wdio/logger": "7.26.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/runner": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/runner": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"split2": "^4.0.0",
 				"stream-buffers": "^3.0.2"
@@ -1095,15 +1095,15 @@
 			}
 		},
 		"node_modules/@wdio/mocha-framework": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.33.0.tgz",
-			"integrity": "sha512-y6+iBF+QrqeiXC+mNwW/o0vRsB+qaRznxoh+ds6Xz9V0tui55cn4kl2gYkBu3oHX8h+9R52ykLyaY9wv+r2aeg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.40.0.tgz",
+			"integrity": "sha512-Pc+c4M07qhz3CdhitETWq8htMPb3xwmmQF5CKUpcy+F6nBTy4Q3wDOSLRQnFD7iP+JqnpJ2o3k1NPeuNYc7+CQ==",
 			"dev": true,
 			"dependencies": {
 				"@types/mocha": "^10.0.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"expect-webdriverio": "^3.0.0",
 				"mocha": "^10.0.0"
 			},
@@ -1121,21 +1121,21 @@
 			}
 		},
 		"node_modules/@wdio/repl": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz",
-			"integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.40.0.tgz",
+			"integrity": "sha512-6tzT7lOMxBwdqMVdW4QxlzrQadGPta4HedFcJo4LyRz9PkXPTF68qeIGs0GyZvy/5AqspNWaAJvIR7f3T3tCyw==",
 			"dev": true,
 			"dependencies": {
-				"@wdio/utils": "7.33.0"
+				"@wdio/utils": "7.40.0"
 			},
 			"engines": {
 				"node": ">=12.0.0"
 			}
 		},
 		"node_modules/@wdio/reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.33.0.tgz",
-			"integrity": "sha512-iL3SwP+hVmu1qj54YPwRCK+ZpVN75xpltYihjpuZCWZKJ0qpQuE2oBlNauFQWgrrd74ta20EDV4mSIhXm9lX6g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.40.0.tgz",
+			"integrity": "sha512-nWVh20JONsN4xf2PRWAS+81r1a6t6M5OtlVOti7G8/pODCul1kxmi9l07s0JaU9g64C1nDc4bOxvAPOWR3/wIw==",
 			"dev": true,
 			"dependencies": {
 				"@types/diff": "^5.0.0",
@@ -1143,7 +1143,7 @@
 				"@types/object-inspect": "^1.8.0",
 				"@types/supports-color": "^8.1.0",
 				"@types/tmp": "^0.2.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"diff": "^5.0.0",
 				"fs-extra": "^11.1.1",
 				"object-inspect": "^1.10.3",
@@ -1154,33 +1154,33 @@
 			}
 		},
 		"node_modules/@wdio/runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.33.0.tgz",
-			"integrity": "sha512-3B+29EanAdRFh4vT3E4XnHQga/apdLIDZq5pGEbqnDA5LarbIvsNWbJjeJzWM6XaZmEwrPfjOunjOevJt5yvdg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.40.0.tgz",
+			"integrity": "sha512-3dGn8sU9Oc0kTq+hcxNSqkF1acqiTAzamyNWsWXAX7V0FOfZxp0wmD9aMqY+sVT6g8mUE5aePT1ydONE5o+6QA==",
 			"dev": true,
 			"dependencies": {
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"gaze": "^1.1.2",
-				"webdriver": "7.33.0",
-				"webdriverio": "7.33.0"
+				"webdriver": "7.40.0",
+				"webdriverio": "7.40.0"
 			},
 			"engines": {
 				"node": ">=12.0.0"
 			}
 		},
 		"node_modules/@wdio/spec-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.33.0.tgz",
-			"integrity": "sha512-+BTJE6p82EaQMK+2t3lmXlpxF0Q72EJwUSEqY6RPyPUZL7fB+AZdHKQcxcmCR8bYyOUp68H45Yj4PuCKRS6hAg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.40.0.tgz",
+			"integrity": "sha512-DhkfnWrN/X0DKpj/maIsk76yr5iG0t/ZbbajtBXLv9lMn8j+ALY34dfj0mvvTKX77wlzDtgeuC+8BzxPKBWU6g==",
 			"dev": true,
 			"dependencies": {
 				"@types/easy-table": "^1.2.0",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"chalk": "^4.0.0",
 				"easy-table": "^1.1.1",
 				"pretty-ms": "^7.0.0"
@@ -1193,9 +1193,9 @@
 			}
 		},
 		"node_modules/@wdio/types": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz",
-			"integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.40.0.tgz",
+			"integrity": "sha512-MWMbU+8uk+JrF7ygP/TJDsaSvFozKauiW6EnG7rxx9+GvU1Q1B3l4UjAc7GDbgLKjwt8T2y5GDRiDoD3UOjVyw==",
 			"dev": true,
 			"dependencies": {
 				"@types/node": "^18.0.0",
@@ -1214,13 +1214,13 @@
 			}
 		},
 		"node_modules/@wdio/utils": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz",
-			"integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.40.0.tgz",
+			"integrity": "sha512-jLF57xHmz5nnGuM6ZRWjVYa/LQb22CS7yG50dUFa9wJ509mC1HlUzaA01Gjk9TV5jf9vnwE/yZfUMCoecTgG9w==",
 			"dev": true,
 			"dependencies": {
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"p-iteration": "^1.1.8"
 			},
 			"engines": {
@@ -1440,12 +1440,12 @@
 			"dev": true
 		},
 		"node_modules/aria-query": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
-			"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+			"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
 			"dev": true,
-			"dependencies": {
-				"dequal": "^2.0.3"
+			"engines": {
+				"node": ">= 0.4"
 			}
 		},
 		"node_modules/array-differ": {
@@ -2403,9 +2403,9 @@
 			}
 		},
 		"node_modules/css-shorthand-properties": {
-			"version": "1.1.1",
-			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz",
-			"integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==",
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz",
+			"integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==",
 			"dev": true
 		},
 		"node_modules/css-tokenize": {
@@ -2643,15 +2643,6 @@
 				"node": ">=0.4.0"
 			}
 		},
-		"node_modules/dequal": {
-			"version": "2.0.3",
-			"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
-			"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
-			"dev": true,
-			"engines": {
-				"node": ">=6"
-			}
-		},
 		"node_modules/detect-file": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -2662,18 +2653,18 @@
 			}
 		},
 		"node_modules/devtools": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.33.0.tgz",
-			"integrity": "sha512-9sxWcdZLOUtgvw4kotL8HqvIFkO/yuHUecgqCYXnqIzwdWSoxWCeKAyZhOJNMeFtzjEnHGvIrUIquEuifk2STg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.40.0.tgz",
+			"integrity": "sha512-hiDPCNG/mpD+bSgegxoe5nwyxWav+QpIvT+7H9D0dUwjB0q04OF473qGflSQ1QpGig6l4qG92tA7dVnLsdP75A==",
 			"dev": true,
 			"dependencies": {
 				"@types/node": "^18.0.0",
 				"@types/ua-parser-js": "^0.7.33",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"chrome-launcher": "^0.15.0",
 				"edge-paths": "^2.1.0",
 				"puppeteer-core": "13.1.3",
@@ -2686,9 +2677,9 @@
 			}
 		},
 		"node_modules/devtools-protocol": {
-			"version": "0.0.1203626",
-			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz",
-			"integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==",
+			"version": "0.0.1260888",
+			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz",
+			"integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==",
 			"dev": true
 		},
 		"node_modules/devtools/node_modules/debug": {
@@ -3118,9 +3109,9 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.28.2",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
-			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
+			"version": "0.29.1",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.29.1.tgz",
+			"integrity": "sha512-4dbL5o3hKGSvreyrGZWLPoTDLFubZ575IQOPhUaTcpbTsi0u05TBEMsOyYkthTaK21vsFQqhSYtxp/xU93BSdA==",
 			"dev": true,
 			"dependencies": {
 				"browserslist-config-wikimedia": "^0.7.0",
@@ -3133,13 +3124,16 @@
 				"eslint-plugin-mediawiki": "^0.7.0",
 				"eslint-plugin-mocha": "^10.4.3",
 				"eslint-plugin-n": "^17.7.0",
-				"eslint-plugin-no-jquery": "^3.0.1",
+				"eslint-plugin-no-jquery": "^3.1.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
 				"eslint-plugin-unicorn": "^53.0.0",
 				"eslint-plugin-vue": "^9.26.0",
 				"eslint-plugin-wdio": "^8.24.12",
 				"eslint-plugin-yml": "^1.14.0"
+			},
+			"engines": {
+				"node": ">=18 <23"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
@@ -3363,9 +3357,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-no-jquery": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.0.1.tgz",
-			"integrity": "sha512-GrzdjIxox/3x8hpSwpxiMuEQFipiJHTGiVsp0T1TI6GH+KVSbXa4z/56xTV1WiIe66u3iRgvCIipu9CRthecpQ==",
+			"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==",
 			"dev": true,
 			"peerDependencies": {
 				"eslint": ">=8.0.0"
@@ -6166,10 +6160,13 @@
 			}
 		},
 		"node_modules/object-inspect": {
-			"version": "1.13.1",
-			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-			"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+			"version": "1.13.4",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+			"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
 			"dev": true,
+			"engines": {
+				"node": ">= 0.4"
+			},
 			"funding": {
 				"url": "https://github.com/sponsors/ljharb"
 			}
@@ -6829,9 +6826,9 @@
 			"dev": true
 		},
 		"node_modules/pump": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-			"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+			"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
 			"dev": true,
 			"dependencies": {
 				"end-of-stream": "^1.1.0",
@@ -8427,9 +8424,9 @@
 			}
 		},
 		"node_modules/ua-parser-js": {
-			"version": "1.0.38",
-			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz",
-			"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==",
+			"version": "1.0.40",
+			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
+			"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
 			"dev": true,
 			"funding": [
 				{
@@ -8445,6 +8442,9 @@
 					"url": "https://github.com/sponsors/faisalman"
 				}
 			],
+			"bin": {
+				"ua-parser-js": "script/cli.js"
+			},
 			"engines": {
 				"node": "*"
 			}
@@ -8666,17 +8666,17 @@
 			}
 		},
 		"node_modules/webdriver": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz",
-			"integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.40.0.tgz",
+			"integrity": "sha512-CKi3cDWgNVE/ibcsBfdtA+pQVeZ4oYlecLlwemulVxJdgr4l5bv+nXuoIhnYeVb6aAI4naK772vmWQ0XuRYhDQ==",
 			"dev": true,
 			"dependencies": {
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"got": "^11.0.2",
 				"ky": "0.30.0",
 				"lodash.merge": "^4.6.1"
@@ -8686,25 +8686,25 @@
 			}
 		},
 		"node_modules/webdriverio": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.33.0.tgz",
-			"integrity": "sha512-9MRPYkOEdsvsBpDJRSMAR+dLID6I65vKjpzNTTFJSjRLSHF6MByOH3mV2trlpIyV+TIp87GysYUVf3Cmufg9eg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.40.0.tgz",
+			"integrity": "sha512-UswBOjpWwk7ziGi9beZGX/XFrp4m1Ws0ni5HI9mzAkOlpKKKWhnX6i95pWQV6sPF4Urv4RJf8WXayHhTbzXzdA==",
 			"dev": true,
 			"dependencies": {
 				"@types/aria-query": "^5.0.0",
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"archiver": "^5.0.0",
 				"aria-query": "^5.2.1",
 				"css-shorthand-properties": "^1.1.1",
 				"css-value": "^0.0.1",
-				"devtools": "7.33.0",
-				"devtools-protocol": "^0.0.1203626",
+				"devtools": "7.40.0",
+				"devtools-protocol": "^0.0.1260888",
 				"fs-extra": "^11.1.1",
 				"grapheme-splitter": "^1.0.2",
 				"lodash.clonedeep": "^4.5.0",
@@ -8717,7 +8717,7 @@
 				"resq": "^1.9.1",
 				"rgb2hex": "0.2.5",
 				"serialize-error": "^8.0.0",
-				"webdriver": "7.33.0"
+				"webdriver": "7.40.0"
 			},
 			"engines": {
 				"node": ">=12.0.0"
@@ -9483,9 +9483,9 @@
 			}
 		},
 		"@types/diff": {
-			"version": "5.2.1",
-			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz",
-			"integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==",
+			"version": "5.2.3",
+			"resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.3.tgz",
+			"integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==",
 			"dev": true
 		},
 		"@types/easy-table": {
@@ -9870,9 +9870,9 @@
 			"dev": true
 		},
 		"@wdio/cli": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.33.0.tgz",
-			"integrity": "sha512-S5Iy4AVcbcJDMhAP4k/Yf18mKma9NGFM8A5bafcGRpFlIj97rpnb0/cpmJVVEr4v/wr3XCu0k38ooJw0B/D3nw==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.40.0.tgz",
+			"integrity": "sha512-M0txYEqqamBvJe4FEuqwWq1jd879sElF047BXSv2GRu4R1/iEBPYJHjn9KuL60Fkkpp/L1NMHTl7gW9i445edQ==",
 			"dev": true,
 			"requires": {
 				"@types/ejs": "^3.0.5",
@@ -9883,11 +9883,11 @@
 				"@types/lodash.union": "^4.6.6",
 				"@types/node": "^18.0.0",
 				"@types/recursive-readdir": "^2.2.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"chalk": "^4.0.0",
 				"chokidar": "^3.0.0",
@@ -9900,21 +9900,21 @@
 				"lodash.union": "^4.6.0",
 				"mkdirp": "^3.0.0",
 				"recursive-readdir": "^2.2.2",
-				"webdriverio": "7.33.0",
+				"webdriverio": "7.40.0",
 				"yargs": "^17.0.0",
 				"yarn-install": "^1.0.0"
 			}
 		},
 		"@wdio/config": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz",
-			"integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.40.0.tgz",
+			"integrity": "sha512-ayQELXyxa+k9/2a509F5a1oTsCa/w8D1nDrd+hzm+1mYb4Te2lceWCCzm+atGKkMpvjLH4GvhrEBYLh3rIWk2A==",
 			"dev": true,
 			"requires": {
 				"@types/glob": "^8.1.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"glob": "^8.0.3"
 			},
@@ -9953,31 +9953,31 @@
 			}
 		},
 		"@wdio/junit-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.33.0.tgz",
-			"integrity": "sha512-0Gj+lvUmscTjXbC+ziiG/1W64h2Z1Lgy04rHn4vU3xNp771+KJ13Ry1nxY5bUbOsfD1Ix6R1gKSz98nCoZCZpg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/junit-reporter/-/junit-reporter-7.40.0.tgz",
+			"integrity": "sha512-nGxzvdCBHUQOtKbCrihO+MjLdfyeYPVeoCAWBNbPHP06nnjsoVDT7k1Ic7BwAbrDZn1SUOVhwdGOxqDdc1E8Fg==",
 			"dev": true,
 			"requires": {
 				"@types/json-stringify-safe": "^5.0.0",
 				"@types/validator": "^13.1.3",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"json-stringify-safe": "^5.0.1",
 				"junit-report-builder": "^3.0.0",
 				"validator": "^13.0.0"
 			}
 		},
 		"@wdio/local-runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.33.0.tgz",
-			"integrity": "sha512-oZLLyOizlX2mV3FIxRLWgN0J2sDL+6LhC71CwFxcV8iVjXvp16my9jbKrgtkIgdo1BsaWIqq+tZlCr9e9NUUjA==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.40.0.tgz",
+			"integrity": "sha512-OBuN7TlFhbPUH7Wbh2S8OKZOjeW4rHXOfuGzJfaKkzjHje2Dqide/uC3Gd25MwmzgZcVkOo9DUYiGFCHXc44ug==",
 			"dev": true,
 			"requires": {
 				"@types/stream-buffers": "^3.0.3",
 				"@wdio/logger": "7.26.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/runner": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/runner": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"async-exit-hook": "^2.0.1",
 				"split2": "^4.0.0",
 				"stream-buffers": "^3.0.2"
@@ -9996,15 +9996,15 @@
 			}
 		},
 		"@wdio/mocha-framework": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.33.0.tgz",
-			"integrity": "sha512-y6+iBF+QrqeiXC+mNwW/o0vRsB+qaRznxoh+ds6Xz9V0tui55cn4kl2gYkBu3oHX8h+9R52ykLyaY9wv+r2aeg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.40.0.tgz",
+			"integrity": "sha512-Pc+c4M07qhz3CdhitETWq8htMPb3xwmmQF5CKUpcy+F6nBTy4Q3wDOSLRQnFD7iP+JqnpJ2o3k1NPeuNYc7+CQ==",
 			"dev": true,
 			"requires": {
 				"@types/mocha": "^10.0.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"expect-webdriverio": "^3.0.0",
 				"mocha": "^10.0.0"
 			}
@@ -10016,18 +10016,18 @@
 			"dev": true
 		},
 		"@wdio/repl": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz",
-			"integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.40.0.tgz",
+			"integrity": "sha512-6tzT7lOMxBwdqMVdW4QxlzrQadGPta4HedFcJo4LyRz9PkXPTF68qeIGs0GyZvy/5AqspNWaAJvIR7f3T3tCyw==",
 			"dev": true,
 			"requires": {
-				"@wdio/utils": "7.33.0"
+				"@wdio/utils": "7.40.0"
 			}
 		},
 		"@wdio/reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.33.0.tgz",
-			"integrity": "sha512-iL3SwP+hVmu1qj54YPwRCK+ZpVN75xpltYihjpuZCWZKJ0qpQuE2oBlNauFQWgrrd74ta20EDV4mSIhXm9lX6g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.40.0.tgz",
+			"integrity": "sha512-nWVh20JONsN4xf2PRWAS+81r1a6t6M5OtlVOti7G8/pODCul1kxmi9l07s0JaU9g64C1nDc4bOxvAPOWR3/wIw==",
 			"dev": true,
 			"requires": {
 				"@types/diff": "^5.0.0",
@@ -10035,7 +10035,7 @@
 				"@types/object-inspect": "^1.8.0",
 				"@types/supports-color": "^8.1.0",
 				"@types/tmp": "^0.2.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"diff": "^5.0.0",
 				"fs-extra": "^11.1.1",
 				"object-inspect": "^1.10.3",
@@ -10043,39 +10043,39 @@
 			}
 		},
 		"@wdio/runner": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.33.0.tgz",
-			"integrity": "sha512-3B+29EanAdRFh4vT3E4XnHQga/apdLIDZq5pGEbqnDA5LarbIvsNWbJjeJzWM6XaZmEwrPfjOunjOevJt5yvdg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.40.0.tgz",
+			"integrity": "sha512-3dGn8sU9Oc0kTq+hcxNSqkF1acqiTAzamyNWsWXAX7V0FOfZxp0wmD9aMqY+sVT6g8mUE5aePT1ydONE5o+6QA==",
 			"dev": true,
 			"requires": {
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"deepmerge": "^4.0.0",
 				"gaze": "^1.1.2",
-				"webdriver": "7.33.0",
-				"webdriverio": "7.33.0"
+				"webdriver": "7.40.0",
+				"webdriverio": "7.40.0"
 			}
 		},
 		"@wdio/spec-reporter": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.33.0.tgz",
-			"integrity": "sha512-+BTJE6p82EaQMK+2t3lmXlpxF0Q72EJwUSEqY6RPyPUZL7fB+AZdHKQcxcmCR8bYyOUp68H45Yj4PuCKRS6hAg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.40.0.tgz",
+			"integrity": "sha512-DhkfnWrN/X0DKpj/maIsk76yr5iG0t/ZbbajtBXLv9lMn8j+ALY34dfj0mvvTKX77wlzDtgeuC+8BzxPKBWU6g==",
 			"dev": true,
 			"requires": {
 				"@types/easy-table": "^1.2.0",
-				"@wdio/reporter": "7.33.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/reporter": "7.40.0",
+				"@wdio/types": "7.40.0",
 				"chalk": "^4.0.0",
 				"easy-table": "^1.1.1",
 				"pretty-ms": "^7.0.0"
 			}
 		},
 		"@wdio/types": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz",
-			"integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.40.0.tgz",
+			"integrity": "sha512-MWMbU+8uk+JrF7ygP/TJDsaSvFozKauiW6EnG7rxx9+GvU1Q1B3l4UjAc7GDbgLKjwt8T2y5GDRiDoD3UOjVyw==",
 			"dev": true,
 			"requires": {
 				"@types/node": "^18.0.0",
@@ -10083,13 +10083,13 @@
 			}
 		},
 		"@wdio/utils": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz",
-			"integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.40.0.tgz",
+			"integrity": "sha512-jLF57xHmz5nnGuM6ZRWjVYa/LQb22CS7yG50dUFa9wJ509mC1HlUzaA01Gjk9TV5jf9vnwE/yZfUMCoecTgG9w==",
 			"dev": true,
 			"requires": {
 				"@wdio/logger": "7.26.0",
-				"@wdio/types": "7.33.0",
+				"@wdio/types": "7.40.0",
 				"p-iteration": "^1.1.8"
 			}
 		},
@@ -10259,13 +10259,10 @@
 			"dev": true
 		},
 		"aria-query": {
-			"version": "5.3.0",
-			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
-			"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
-			"dev": true,
-			"requires": {
-				"dequal": "^2.0.3"
-			}
+			"version": "5.3.2",
+			"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+			"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+			"dev": true
 		},
 		"array-differ": {
 			"version": "3.0.0",
@@ -10945,9 +10942,9 @@
 			}
 		},
 		"css-shorthand-properties": {
-			"version": "1.1.1",
-			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz",
-			"integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==",
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz",
+			"integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==",
 			"dev": true
 		},
 		"css-tokenize": {
@@ -11127,12 +11124,6 @@
 			"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
 			"dev": true
 		},
-		"dequal": {
-			"version": "2.0.3",
-			"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
-			"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
-			"dev": true
-		},
 		"detect-file": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -11140,18 +11131,18 @@
 			"dev": true
 		},
 		"devtools": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.33.0.tgz",
-			"integrity": "sha512-9sxWcdZLOUtgvw4kotL8HqvIFkO/yuHUecgqCYXnqIzwdWSoxWCeKAyZhOJNMeFtzjEnHGvIrUIquEuifk2STg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/devtools/-/devtools-7.40.0.tgz",
+			"integrity": "sha512-hiDPCNG/mpD+bSgegxoe5nwyxWav+QpIvT+7H9D0dUwjB0q04OF473qGflSQ1QpGig6l4qG92tA7dVnLsdP75A==",
 			"dev": true,
 			"requires": {
 				"@types/node": "^18.0.0",
 				"@types/ua-parser-js": "^0.7.33",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"chrome-launcher": "^0.15.0",
 				"edge-paths": "^2.1.0",
 				"puppeteer-core": "13.1.3",
@@ -11221,9 +11212,9 @@
 			}
 		},
 		"devtools-protocol": {
-			"version": "0.0.1203626",
-			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz",
-			"integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==",
+			"version": "0.0.1260888",
+			"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz",
+			"integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==",
 			"dev": true
 		},
 		"diff": {
@@ -11500,9 +11491,9 @@
 			}
 		},
 		"eslint-config-wikimedia": {
-			"version": "0.28.2",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
-			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
+			"version": "0.29.1",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.29.1.tgz",
+			"integrity": "sha512-4dbL5o3hKGSvreyrGZWLPoTDLFubZ575IQOPhUaTcpbTsi0u05TBEMsOyYkthTaK21vsFQqhSYtxp/xU93BSdA==",
 			"dev": true,
 			"requires": {
 				"browserslist-config-wikimedia": "^0.7.0",
@@ -11515,7 +11506,7 @@
 				"eslint-plugin-mediawiki": "^0.7.0",
 				"eslint-plugin-mocha": "^10.4.3",
 				"eslint-plugin-n": "^17.7.0",
-				"eslint-plugin-no-jquery": "^3.0.1",
+				"eslint-plugin-no-jquery": "^3.1.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
 				"eslint-plugin-unicorn": "^53.0.0",
@@ -11671,9 +11662,9 @@
 			}
 		},
 		"eslint-plugin-no-jquery": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.0.1.tgz",
-			"integrity": "sha512-GrzdjIxox/3x8hpSwpxiMuEQFipiJHTGiVsp0T1TI6GH+KVSbXa4z/56xTV1WiIe66u3iRgvCIipu9CRthecpQ==",
+			"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==",
 			"dev": true,
 			"requires": {}
 		},
@@ -13766,9 +13757,9 @@
 			"dev": true
 		},
 		"object-inspect": {
-			"version": "1.13.1",
-			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-			"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+			"version": "1.13.4",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+			"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
 			"dev": true
 		},
 		"object.defaults": {
@@ -14247,9 +14238,9 @@
 			"dev": true
 		},
 		"pump": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-			"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+			"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
 			"dev": true,
 			"requires": {
 				"end-of-stream": "^1.1.0",
@@ -15414,9 +15405,9 @@
 			"peer": true
 		},
 		"ua-parser-js": {
-			"version": "1.0.38",
-			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz",
-			"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==",
+			"version": "1.0.40",
+			"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
+			"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
 			"dev": true
 		},
 		"unbzip2-stream": {
@@ -15575,42 +15566,42 @@
 			}
 		},
 		"webdriver": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz",
-			"integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.40.0.tgz",
+			"integrity": "sha512-CKi3cDWgNVE/ibcsBfdtA+pQVeZ4oYlecLlwemulVxJdgr4l5bv+nXuoIhnYeVb6aAI4naK772vmWQ0XuRYhDQ==",
 			"dev": true,
 			"requires": {
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"got": "^11.0.2",
 				"ky": "0.30.0",
 				"lodash.merge": "^4.6.1"
 			}
 		},
 		"webdriverio": {
-			"version": "7.33.0",
-			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.33.0.tgz",
-			"integrity": "sha512-9MRPYkOEdsvsBpDJRSMAR+dLID6I65vKjpzNTTFJSjRLSHF6MByOH3mV2trlpIyV+TIp87GysYUVf3Cmufg9eg==",
+			"version": "7.40.0",
+			"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.40.0.tgz",
+			"integrity": "sha512-UswBOjpWwk7ziGi9beZGX/XFrp4m1Ws0ni5HI9mzAkOlpKKKWhnX6i95pWQV6sPF4Urv4RJf8WXayHhTbzXzdA==",
 			"dev": true,
 			"requires": {
 				"@types/aria-query": "^5.0.0",
 				"@types/node": "^18.0.0",
-				"@wdio/config": "7.33.0",
+				"@wdio/config": "7.40.0",
 				"@wdio/logger": "7.26.0",
 				"@wdio/protocols": "7.27.0",
-				"@wdio/repl": "7.33.0",
-				"@wdio/types": "7.33.0",
-				"@wdio/utils": "7.33.0",
+				"@wdio/repl": "7.40.0",
+				"@wdio/types": "7.40.0",
+				"@wdio/utils": "7.40.0",
 				"archiver": "^5.0.0",
 				"aria-query": "^5.2.1",
 				"css-shorthand-properties": "^1.1.1",
 				"css-value": "^0.0.1",
-				"devtools": "7.33.0",
-				"devtools-protocol": "^0.0.1203626",
+				"devtools": "7.40.0",
+				"devtools-protocol": "^0.0.1260888",
 				"fs-extra": "^11.1.1",
 				"grapheme-splitter": "^1.0.2",
 				"lodash.clonedeep": "^4.5.0",
@@ -15623,7 +15614,7 @@
 				"resq": "^1.9.1",
 				"rgb2hex": "0.2.5",
 				"serialize-error": "^8.0.0",
-				"webdriver": "7.33.0"
+				"webdriver": "7.40.0"
 			},
 			"dependencies": {
 				"brace-expansion": {
diff --git a/package.json b/package.json
index 03daffa..d13cc88 100644
--- a/package.json
+++ b/package.json
@@ -8,12 +8,12 @@
 		"test": "grunt test"
 	},
 	"devDependencies": {
-		"@wdio/cli": "7.33.0",
-		"@wdio/junit-reporter": "7.33.0",
-		"@wdio/local-runner": "7.33.0",
-		"@wdio/mocha-framework": "7.33.0",
-		"@wdio/spec-reporter": "7.33.0",
-		"eslint-config-wikimedia": "0.28.2",
+		"@wdio/cli": "7.40.0",
+		"@wdio/junit-reporter": "7.40.0",
+		"@wdio/local-runner": "7.40.0",
+		"@wdio/mocha-framework": "7.40.0",
+		"@wdio/spec-reporter": "7.40.0",
+		"eslint-config-wikimedia": "0.29.1",
 		"grunt": "1.6.1",
 		"grunt-banana-checker": "0.13.0",
 		"grunt-eslint": "24.3.0",
diff --git a/resources/.eslintrc.json b/resources/.eslintrc.json
index 66584d8..4a067f4 100644
--- a/resources/.eslintrc.json
+++ b/resources/.eslintrc.json
@@ -10,6 +10,7 @@
 	},
 	"rules": {
 		"no-jquery/no-global-selector": "off",
-		"compat/compat": "warn"
+		"compat/compat": "warn",
+		"no-jquery/no-done-fail": "warn"
 	}
 }
diff --git a/resources/ext.centralNotice.display/chooser.js b/resources/ext.centralNotice.display/chooser.js
index ada4ba2..0961aa8 100644
--- a/resources/ext.centralNotice.display/chooser.js
+++ b/resources/ext.centralNotice.display/chooser.js
@@ -165,7 +165,7 @@
 			}
 
 			// Filter for device
-			if ( banner.devices.indexOf( device ) === -1 ) {
+			if ( !banner.devices.includes( device ) ) {
 				continue;
 			}
 
@@ -279,8 +279,8 @@
 
 				// Filter for country if geotargeted
 				if ( campaign.geotargeted && (
-					campaign.countries.indexOf( country ) === -1 && // No country wide match
-					campaign.regions.indexOf( uniqueRegionCode ) === -1 // And no region match
+					!campaign.countries.includes( country ) && // No country wide match
+					!campaign.regions.includes( uniqueRegionCode ) // And no region match
 				) ) {
 					continue;
 				}
@@ -298,7 +298,7 @@
 					}
 
 					// Device
-					if ( banner.devices.indexOf( device ) === -1 ) {
+					if ( !banner.devices.includes( device ) ) {
 						continue;
 					}
 
diff --git a/resources/ext.centralNotice.display/index.js b/resources/ext.centralNotice.display/index.js
index 4b6b30c..bb61fe2 100644
--- a/resources/ext.centralNotice.display/index.js
+++ b/resources/ext.centralNotice.display/index.js
@@ -424,7 +424,7 @@
 				// Do not check user preferences on anon users
 				if (
 					campaign.type === 0 ||
-					state.getData().optedOutCampaigns.indexOf( campaign.type ) !== -1
+					state.getData().optedOutCampaigns.includes( campaign.type )
 				) {
 					// User opted out of viewing this type of campaigns
 					// or campaign does not have a type set
diff --git a/resources/ext.centralNotice.display/state.js b/resources/ext.centralNotice.display/state.js
index e2c1f89..c270997 100644
--- a/resources/ext.centralNotice.display/state.js
+++ b/resources/ext.centralNotice.display/state.js
@@ -441,7 +441,7 @@
 			// Is the campaign category among the categories configured to use
 			// legacy mechanisms?
 			state.data.campaignCategoryUsesLegacy =
-				config.categoriesUsingLegacy.indexOf( campaignCategory ) !== -1;
+				config.categoriesUsingLegacy.includes( campaignCategory );
 		},
 
 		/**
@@ -609,7 +609,7 @@
 			const tests = state.data.tests = state.data.tests || [];
 
 			// Add if it isn't already registered.
-			if ( tests.indexOf( identifier ) === -1 ) {
+			if ( !tests.includes( identifier ) ) {
 				tests.push( identifier );
 
 				if ( tests.length === 1 ) {
diff --git a/resources/ext.centralNotice.kvStore/kvStore.js b/resources/ext.centralNotice.kvStore/kvStore.js
index c6d9564..b668dfb 100644
--- a/resources/ext.centralNotice.kvStore/kvStore.js
+++ b/resources/ext.centralNotice.kvStore/kvStore.js
@@ -365,8 +365,8 @@
 		 */
 		setItem: function ( key, value, context, ttl, multiStorageOption ) {
 			// Check validity of key
-			if ( ( key.indexOf( SEPARATOR ) !== -1 ) ||
-				( key.indexOf( SEPARATOR_IN_COOKIES ) !== -1 ) ) {
+			if ( ( key.includes( SEPARATOR ) ) ||
+				( key.includes( SEPARATOR_IN_COOKIES ) ) ) {
 
 				setError( 'Invalid key', key, value, context );
 				return false;
diff --git a/resources/ext.centralNotice.startUp/kvStoreMaintenance.js b/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
index b78c1bc..a9495b6 100644
--- a/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
+++ b/resources/ext.centralNotice.startUp/kvStoreMaintenance.js
@@ -114,7 +114,7 @@
 			}
 
 			// Fallback cookies? LocalStorage seems to work, so purge them.
-			if ( document.cookie.indexOf( PREFIX_AND_SEPARATOR_IN_COOKIES ) !== -1 ) {
+			if ( document.cookie.includes( PREFIX_AND_SEPARATOR_IN_COOKIES ) ) {
 				purgeFallbackCookies();
 			}
 
diff --git a/resources/infrastructure/bannereditor.js b/resources/infrastructure/bannereditor.js
index db12a0f..263743a 100644
--- a/resources/infrastructure/bannereditor.js
+++ b/resources/infrastructure/bannereditor.js
@@ -184,7 +184,7 @@
 		 * @return {boolean}
 		 */
 		doSaveBanner: function () {
-			if ( $( '#mw-input-wpbanner-body' ).prop( 'value' ).indexOf( 'document.write' ) > -1 ) {
+			if ( $( '#mw-input-wpbanner-body' ).prop( 'value' ).includes( 'document.write' ) ) {
 				// eslint-disable-next-line no-alert
 				alert( mw.msg( 'centralnotice-documentwrite-error' ) );
 			} else {
diff --git a/resources/infrastructure/campaignManager.js b/resources/infrastructure/campaignManager.js
index 6077efd..9c4e029 100644
--- a/resources/infrastructure/campaignManager.js
+++ b/resources/infrastructure/campaignManager.js
@@ -626,7 +626,7 @@
 			const $this = $( this ),
 				assignedBucket = +$this.val(),
 				bannerName = $this.data( 'banner-name' ),
-				removed = ( removedBanners.indexOf( bannerName ) !== -1 );
+				removed = ( removedBanners.includes( bannerName ) );
 
 			// Iterate over all buckets, adding banners to the index or removeing them,
 			// as needed. (assignedBanners has elements for all possible buckets.)
diff --git a/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js b/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
index 00589dd..43b3bfb 100644
--- a/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
+++ b/resources/infrastructure/ext.centralNotice.adminUi.bannerSequence.js
@@ -680,7 +680,7 @@
 	};
 
 	BannerSequenceUiModel.prototype.validateSkipWithIdentifier = function ( id ) {
-		return ( typeof id === 'string' && id.indexOf( '|' ) === -1 ) || id === null;
+		return ( typeof id === 'string' && !id.includes( '|' ) ) || id === null;
 	};
 
 	BannerSequenceUiModel.prototype.validateDays = function ( days ) {
@@ -719,7 +719,7 @@
 		for ( let i = 0; i < sequence.length; i++ ) {
 			const banner = sequence[ i ].banner;
 
-			if ( banner !== null && assignedBanners.indexOf( banner ) === -1 ) {
+			if ( banner !== null && !assignedBanners.includes( banner ) ) {
 				stepsWithMissingBanners.push( i );
 				sequence[ i ].banner = this.defaultBanner();
 			}
diff --git a/tests/qunit/.eslintrc.json b/tests/qunit/.eslintrc.json
index 839f531..91964e4 100644
--- a/tests/qunit/.eslintrc.json
+++ b/tests/qunit/.eslintrc.json
@@ -5,6 +5,7 @@
 		"wikimedia/qunit"
 	],
 	"rules": {
-		"no-jquery/no-parse-html-literal": "off"
+		"no-jquery/no-parse-html-literal": "off",
+		"no-jquery/no-done-fail": "warn"
 	}
 }
-- 
2.39.2


--- end ---

composer dependencies

Development dependencies

npm dependencies

Development dependencies

Logs

Source code is licensed under the AGPL.