From 8ac06b7a154d5c61c5e0e47c8c28f3e452b35828 Mon Sep 17 00:00:00 2001
From: Dmytro Bogatov <dmytro@dbogatov.org>
Date: Fri, 26 Jun 2020 02:31:52 -0400
Subject: [PATCH] Add ESLint.

---
 .eslintrc.yml        |  29 ++
 .gitlab-ci.yml       |   9 +
 package-lock.json    | 718 +++++++++++++++++++++++++++++++++++++------
 package.json         |   5 +-
 src/index.ts         |   6 +-
 src/inspector.ts     |  68 ++--
 src/report.ts        |  48 ++-
 src/result.ts        |   9 +-
 test/extract-urls.ts |  24 +-
 test/process-url.ts  |  83 ++---
 10 files changed, 808 insertions(+), 191 deletions(-)
 create mode 100644 .eslintrc.yml

diff --git a/.eslintrc.yml b/.eslintrc.yml
new file mode 100644
index 0000000..cef8a67
--- /dev/null
+++ b/.eslintrc.yml
@@ -0,0 +1,29 @@
+env:
+  browser: true
+  es2020: true
+extends:
+  - "eslint:recommended"
+  - "plugin:@typescript-eslint/recommended"
+parser: "@typescript-eslint/parser"
+parserOptions:
+  ecmaVersion: 11
+  sourceType: module
+plugins:
+  - "@typescript-eslint"
+rules:
+  indent:
+    - error
+    - tab
+    - SwitchCase: 1
+  linebreak-style:
+    - error
+    - unix
+  quotes:
+    - error
+    - double
+  semi:
+    - error
+    - never
+  "@typescript-eslint/no-unused-vars":
+    - error
+    - argsIgnorePattern: "^_"
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b41917f..03cdc5d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,15 @@ stages:
   - test
   - release
 
+lint:
+  image: dbogatov/docker-sources:node--14.4-alpine3.12
+  stage: test
+  script:
+    - npm -g install eslint
+    - eslint ./{src,test}/**/*.ts -c .eslintrc.yml
+  tags:
+    - docker
+
 test:
   image: dbogatov/docker-sources:node--14.4-alpine3.12
   stage: test
diff --git a/package-lock.json b/package-lock.json
index bbb42b3..7e62307 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
 	"name": "broken-links-inspector",
-	"version": "1.0.0",
+	"version": "1.1.1",
 	"lockfileVersion": 1,
 	"requires": true,
 	"dependencies": {
@@ -314,46 +314,6 @@
 			"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
 			"dev": true
 		},
-		"@sinonjs/commons": {
-			"version": "1.8.0",
-			"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz",
-			"integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==",
-			"requires": {
-				"type-detect": "4.0.8"
-			}
-		},
-		"@sinonjs/fake-timers": {
-			"version": "6.0.1",
-			"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
-			"integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
-			"requires": {
-				"@sinonjs/commons": "^1.7.0"
-			}
-		},
-		"@sinonjs/formatio": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz",
-			"integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==",
-			"requires": {
-				"@sinonjs/commons": "^1",
-				"@sinonjs/samsam": "^5.0.2"
-			}
-		},
-		"@sinonjs/samsam": {
-			"version": "5.0.3",
-			"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz",
-			"integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==",
-			"requires": {
-				"@sinonjs/commons": "^1.6.0",
-				"lodash.get": "^4.4.2",
-				"type-detect": "^4.0.8"
-			}
-		},
-		"@sinonjs/text-encoding": {
-			"version": "0.7.1",
-			"resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
-			"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ=="
-		},
 		"@types/chai": {
 			"version": "4.2.11",
 			"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz",
@@ -365,11 +325,23 @@
 			"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
 			"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
 		},
+		"@types/eslint-visitor-keys": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+			"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
+			"dev": true
+		},
 		"@types/intercept-stdout": {
 			"version": "0.1.0",
 			"resolved": "https://registry.npmjs.org/@types/intercept-stdout/-/intercept-stdout-0.1.0.tgz",
 			"integrity": "sha512-b4+N4+pHcUWaK75k4GDavB5ZS6aHdlsyxKaU82JEq7mzY0+kziTiT6nKm+OCkMlGvL6RNk2rera2InJKVP5OiQ=="
 		},
+		"@types/json-schema": {
+			"version": "7.0.5",
+			"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
+			"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
+			"dev": true
+		},
 		"@types/mocha": {
 			"version": "7.0.2",
 			"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz",
@@ -396,6 +368,105 @@
 			"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
 			"dev": true
 		},
+		"@typescript-eslint/eslint-plugin": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.4.0.tgz",
+			"integrity": "sha512-wfkpiqaEVhZIuQRmudDszc01jC/YR7gMSxa6ulhggAe/Hs0KVIuo9wzvFiDbG3JD5pRFQoqnf4m7REDsUvBnMQ==",
+			"dev": true,
+			"requires": {
+				"@typescript-eslint/experimental-utils": "3.4.0",
+				"debug": "^4.1.1",
+				"functional-red-black-tree": "^1.0.1",
+				"regexpp": "^3.0.0",
+				"semver": "^7.3.2",
+				"tsutils": "^3.17.1"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "4.1.1",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+					"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"semver": {
+					"version": "7.3.2",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+					"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+					"dev": true
+				}
+			}
+		},
+		"@typescript-eslint/experimental-utils": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.4.0.tgz",
+			"integrity": "sha512-rHPOjL43lOH1Opte4+dhC0a/+ks+8gOBwxXnyrZ/K4OTAChpSjP76fbI8Cglj7V5GouwVAGaK+xVwzqTyE/TPw==",
+			"dev": true,
+			"requires": {
+				"@types/json-schema": "^7.0.3",
+				"@typescript-eslint/typescript-estree": "3.4.0",
+				"eslint-scope": "^5.0.0",
+				"eslint-utils": "^2.0.0"
+			}
+		},
+		"@typescript-eslint/parser": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.4.0.tgz",
+			"integrity": "sha512-ZUGI/de44L5x87uX5zM14UYcbn79HSXUR+kzcqU42gH0AgpdB/TjuJy3m4ezI7Q/jk3wTQd755mxSDLhQP79KA==",
+			"dev": true,
+			"requires": {
+				"@types/eslint-visitor-keys": "^1.0.0",
+				"@typescript-eslint/experimental-utils": "3.4.0",
+				"@typescript-eslint/typescript-estree": "3.4.0",
+				"eslint-visitor-keys": "^1.1.0"
+			}
+		},
+		"@typescript-eslint/typescript-estree": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.4.0.tgz",
+			"integrity": "sha512-zKwLiybtt4uJb4mkG5q2t6+W7BuYx2IISiDNV+IY68VfoGwErDx/RfVI7SWL4gnZ2t1A1ytQQwZ+YOJbHHJ2rw==",
+			"dev": true,
+			"requires": {
+				"debug": "^4.1.1",
+				"eslint-visitor-keys": "^1.1.0",
+				"glob": "^7.1.6",
+				"is-glob": "^4.0.1",
+				"lodash": "^4.17.15",
+				"semver": "^7.3.2",
+				"tsutils": "^3.17.1"
+			},
+			"dependencies": {
+				"debug": {
+					"version": "4.1.1",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+					"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"semver": {
+					"version": "7.3.2",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+					"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+					"dev": true
+				}
+			}
+		},
+		"acorn": {
+			"version": "7.3.1",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
+			"integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==",
+			"dev": true
+		},
+		"acorn-jsx": {
+			"version": "5.2.0",
+			"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
+			"integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
+			"dev": true
+		},
 		"aggregate-error": {
 			"version": "3.0.1",
 			"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz",
@@ -406,6 +477,18 @@
 				"indent-string": "^4.0.0"
 			}
 		},
+		"ajv": {
+			"version": "6.12.2",
+			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+			"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+			"dev": true,
+			"requires": {
+				"fast-deep-equal": "^3.1.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			}
+		},
 		"ansi-colors": {
 			"version": "4.1.1",
 			"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
@@ -485,6 +568,12 @@
 			"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
 			"dev": true
 		},
+		"astral-regex": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+			"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+			"dev": true
+		},
 		"axios": {
 			"version": "0.19.2",
 			"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
@@ -548,6 +637,12 @@
 				"write-file-atomic": "^3.0.0"
 			}
 		},
+		"callsites": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+			"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+			"dev": true
+		},
 		"camelcase": {
 			"version": "5.3.1",
 			"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -730,6 +825,12 @@
 				"type-detect": "^4.0.0"
 			}
 		},
+		"deep-is": {
+			"version": "0.1.3",
+			"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+			"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+			"dev": true
+		},
 		"default-require-extensions": {
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz",
@@ -751,7 +852,17 @@
 		"diff": {
 			"version": "4.0.2",
 			"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
-			"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
+			"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+			"dev": true
+		},
+		"doctrine": {
+			"version": "3.0.0",
+			"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+			"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+			"dev": true,
+			"requires": {
+				"esutils": "^2.0.2"
+			}
 		},
 		"dom-serializer": {
 			"version": "0.2.2",
@@ -791,6 +902,23 @@
 			"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
 			"dev": true
 		},
+		"enquirer": {
+			"version": "2.3.5",
+			"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz",
+			"integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==",
+			"dev": true,
+			"requires": {
+				"ansi-colors": "^3.2.1"
+			},
+			"dependencies": {
+				"ansi-colors": {
+					"version": "3.2.4",
+					"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
+					"integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==",
+					"dev": true
+				}
+			}
+		},
 		"entities": {
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
@@ -859,12 +987,204 @@
 			"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
 			"dev": true
 		},
+		"eslint": {
+			"version": "7.3.1",
+			"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.3.1.tgz",
+			"integrity": "sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==",
+			"dev": true,
+			"requires": {
+				"@babel/code-frame": "^7.0.0",
+				"ajv": "^6.10.0",
+				"chalk": "^4.0.0",
+				"cross-spawn": "^7.0.2",
+				"debug": "^4.0.1",
+				"doctrine": "^3.0.0",
+				"enquirer": "^2.3.5",
+				"eslint-scope": "^5.1.0",
+				"eslint-utils": "^2.0.0",
+				"eslint-visitor-keys": "^1.2.0",
+				"espree": "^7.1.0",
+				"esquery": "^1.2.0",
+				"esutils": "^2.0.2",
+				"file-entry-cache": "^5.0.1",
+				"functional-red-black-tree": "^1.0.1",
+				"glob-parent": "^5.0.0",
+				"globals": "^12.1.0",
+				"ignore": "^4.0.6",
+				"import-fresh": "^3.0.0",
+				"imurmurhash": "^0.1.4",
+				"is-glob": "^4.0.0",
+				"js-yaml": "^3.13.1",
+				"json-stable-stringify-without-jsonify": "^1.0.1",
+				"levn": "^0.4.1",
+				"lodash": "^4.17.14",
+				"minimatch": "^3.0.4",
+				"natural-compare": "^1.4.0",
+				"optionator": "^0.9.1",
+				"progress": "^2.0.0",
+				"regexpp": "^3.1.0",
+				"semver": "^7.2.1",
+				"strip-ansi": "^6.0.0",
+				"strip-json-comments": "^3.1.0",
+				"table": "^5.2.3",
+				"text-table": "^0.2.0",
+				"v8-compile-cache": "^2.0.3"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "5.0.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+					"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+					"dev": true
+				},
+				"debug": {
+					"version": "4.1.1",
+					"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+					"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+					"dev": true,
+					"requires": {
+						"ms": "^2.1.1"
+					}
+				},
+				"globals": {
+					"version": "12.4.0",
+					"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+					"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+					"dev": true,
+					"requires": {
+						"type-fest": "^0.8.1"
+					}
+				},
+				"semver": {
+					"version": "7.3.2",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+					"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+					"dev": true
+				},
+				"strip-ansi": {
+					"version": "6.0.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+					"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^5.0.0"
+					}
+				},
+				"strip-json-comments": {
+					"version": "3.1.0",
+					"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
+					"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
+					"dev": true
+				}
+			}
+		},
+		"eslint-scope": {
+			"version": "5.1.0",
+			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
+			"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
+			"dev": true,
+			"requires": {
+				"esrecurse": "^4.1.0",
+				"estraverse": "^4.1.1"
+			}
+		},
+		"eslint-utils": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+			"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+			"dev": true,
+			"requires": {
+				"eslint-visitor-keys": "^1.1.0"
+			}
+		},
+		"eslint-visitor-keys": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+			"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+			"dev": true
+		},
+		"espree": {
+			"version": "7.1.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz",
+			"integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==",
+			"dev": true,
+			"requires": {
+				"acorn": "^7.2.0",
+				"acorn-jsx": "^5.2.0",
+				"eslint-visitor-keys": "^1.2.0"
+			}
+		},
 		"esprima": {
 			"version": "4.0.1",
 			"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
 			"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
 			"dev": true
 		},
+		"esquery": {
+			"version": "1.3.1",
+			"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
+			"integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+			"dev": true,
+			"requires": {
+				"estraverse": "^5.1.0"
+			},
+			"dependencies": {
+				"estraverse": {
+					"version": "5.1.0",
+					"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
+					"integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==",
+					"dev": true
+				}
+			}
+		},
+		"esrecurse": {
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+			"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+			"dev": true,
+			"requires": {
+				"estraverse": "^4.1.0"
+			}
+		},
+		"estraverse": {
+			"version": "4.3.0",
+			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+			"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+			"dev": true
+		},
+		"esutils": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+			"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+			"dev": true
+		},
+		"fast-deep-equal": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+			"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+			"dev": true
+		},
+		"fast-json-stable-stringify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+			"dev": true
+		},
+		"fast-levenshtein": {
+			"version": "2.0.6",
+			"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+			"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+			"dev": true
+		},
+		"file-entry-cache": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+			"integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+			"dev": true,
+			"requires": {
+				"flat-cache": "^2.0.1"
+			}
+		},
 		"fill-range": {
 			"version": "7.0.1",
 			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -904,6 +1224,34 @@
 				"is-buffer": "~2.0.3"
 			}
 		},
+		"flat-cache": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+			"integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+			"dev": true,
+			"requires": {
+				"flatted": "^2.0.0",
+				"rimraf": "2.6.3",
+				"write": "1.0.3"
+			},
+			"dependencies": {
+				"rimraf": {
+					"version": "2.6.3",
+					"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+					"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+					"dev": true,
+					"requires": {
+						"glob": "^7.1.3"
+					}
+				}
+			}
+		},
+		"flatted": {
+			"version": "2.0.2",
+			"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+			"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+			"dev": true
+		},
 		"follow-redirects": {
 			"version": "1.5.10",
 			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
@@ -962,6 +1310,12 @@
 			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
 			"dev": true
 		},
+		"functional-red-black-tree": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+			"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+			"dev": true
+		},
 		"gensync": {
 			"version": "1.0.0-beta.1",
 			"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
@@ -1080,6 +1434,30 @@
 				"entities": "^2.0.0"
 			}
 		},
+		"ignore": {
+			"version": "4.0.6",
+			"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+			"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+			"dev": true
+		},
+		"import-fresh": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+			"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+			"dev": true,
+			"requires": {
+				"parent-module": "^1.0.0",
+				"resolve-from": "^4.0.0"
+			},
+			"dependencies": {
+				"resolve-from": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+					"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+					"dev": true
+				}
+			}
+		},
 		"imurmurhash": {
 			"version": "0.1.4",
 			"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -1379,6 +1757,18 @@
 			"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
 			"dev": true
 		},
+		"json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+			"dev": true
+		},
+		"json-stable-stringify-without-jsonify": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+			"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+			"dev": true
+		},
 		"json5": {
 			"version": "2.1.3",
 			"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
@@ -1388,10 +1778,15 @@
 				"minimist": "^1.2.5"
 			}
 		},
-		"just-extend": {
-			"version": "4.1.0",
-			"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz",
-			"integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA=="
+		"levn": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+			"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+			"dev": true,
+			"requires": {
+				"prelude-ls": "^1.2.1",
+				"type-check": "~0.4.0"
+			}
 		},
 		"locate-path": {
 			"version": "5.0.0",
@@ -1429,11 +1824,6 @@
 			"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
 			"dev": true
 		},
-		"lodash.get": {
-			"version": "4.4.2",
-			"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
-			"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
-		},
 		"lodash.isarguments": {
 			"version": "3.1.0",
 			"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
@@ -1667,17 +2057,11 @@
 			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
 			"dev": true
 		},
-		"nise": {
-			"version": "4.0.3",
-			"resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz",
-			"integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==",
-			"requires": {
-				"@sinonjs/commons": "^1.7.0",
-				"@sinonjs/fake-timers": "^6.0.0",
-				"@sinonjs/text-encoding": "^0.7.1",
-				"just-extend": "^4.0.2",
-				"path-to-regexp": "^1.7.0"
-			}
+		"natural-compare": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+			"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+			"dev": true
 		},
 		"node-preload": {
 			"version": "0.2.1",
@@ -1853,6 +2237,20 @@
 				"wrappy": "1"
 			}
 		},
+		"optionator": {
+			"version": "0.9.1",
+			"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+			"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+			"dev": true,
+			"requires": {
+				"deep-is": "^0.1.3",
+				"fast-levenshtein": "^2.0.6",
+				"levn": "^0.4.1",
+				"prelude-ls": "^1.2.1",
+				"type-check": "^0.4.0",
+				"word-wrap": "^1.2.3"
+			}
+		},
 		"p-limit": {
 			"version": "2.3.0",
 			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -1898,6 +2296,15 @@
 				"release-zalgo": "^1.0.0"
 			}
 		},
+		"parent-module": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+			"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+			"dev": true,
+			"requires": {
+				"callsites": "^3.0.0"
+			}
+		},
 		"path-exists": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -1922,21 +2329,6 @@
 			"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
 			"dev": true
 		},
-		"path-to-regexp": {
-			"version": "1.8.0",
-			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
-			"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
-			"requires": {
-				"isarray": "0.0.1"
-			},
-			"dependencies": {
-				"isarray": {
-					"version": "0.0.1",
-					"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-					"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
-				}
-			}
-		},
 		"pathval": {
 			"version": "1.1.0",
 			"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
@@ -1958,6 +2350,12 @@
 				"find-up": "^4.0.0"
 			}
 		},
+		"prelude-ls": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+			"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+			"dev": true
+		},
 		"process-on-spawn": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz",
@@ -1967,6 +2365,12 @@
 				"fromentries": "^1.2.0"
 			}
 		},
+		"progress": {
+			"version": "2.0.3",
+			"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+			"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+			"dev": true
+		},
 		"promise.allsettled": {
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz",
@@ -1980,6 +2384,12 @@
 				"iterate-value": "^1.0.0"
 			}
 		},
+		"punycode": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+			"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+			"dev": true
+		},
 		"readdirp": {
 			"version": "3.3.0",
 			"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
@@ -1989,6 +2399,12 @@
 				"picomatch": "^2.0.7"
 			}
 		},
+		"regexpp": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
+			"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+			"dev": true
+		},
 		"release-zalgo": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
@@ -2079,18 +2495,41 @@
 			"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
 			"dev": true
 		},
-		"sinon": {
-			"version": "9.0.2",
-			"resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz",
-			"integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==",
+		"slice-ansi": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+			"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+			"dev": true,
 			"requires": {
-				"@sinonjs/commons": "^1.7.2",
-				"@sinonjs/fake-timers": "^6.0.1",
-				"@sinonjs/formatio": "^5.0.1",
-				"@sinonjs/samsam": "^5.0.3",
-				"diff": "^4.0.2",
-				"nise": "^4.0.1",
-				"supports-color": "^7.1.0"
+				"ansi-styles": "^3.2.0",
+				"astral-regex": "^1.0.0",
+				"is-fullwidth-code-point": "^2.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "3.2.1",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+					"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+					"dev": true,
+					"requires": {
+						"color-convert": "^1.9.0"
+					}
+				},
+				"color-convert": {
+					"version": "1.9.3",
+					"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+					"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+					"dev": true,
+					"requires": {
+						"color-name": "1.1.3"
+					}
+				},
+				"color-name": {
+					"version": "1.1.3",
+					"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+					"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+					"dev": true
+				}
 			}
 		},
 		"source-map": {
@@ -2196,6 +2635,46 @@
 				"has-flag": "^4.0.0"
 			}
 		},
+		"table": {
+			"version": "5.4.6",
+			"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+			"integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+			"dev": true,
+			"requires": {
+				"ajv": "^6.10.2",
+				"lodash": "^4.17.14",
+				"slice-ansi": "^2.1.0",
+				"string-width": "^3.0.0"
+			},
+			"dependencies": {
+				"ansi-regex": {
+					"version": "4.1.0",
+					"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+					"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+					"dev": true
+				},
+				"string-width": {
+					"version": "3.1.0",
+					"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+					"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+					"dev": true,
+					"requires": {
+						"emoji-regex": "^7.0.1",
+						"is-fullwidth-code-point": "^2.0.0",
+						"strip-ansi": "^5.1.0"
+					}
+				},
+				"strip-ansi": {
+					"version": "5.2.0",
+					"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+					"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+					"dev": true,
+					"requires": {
+						"ansi-regex": "^4.1.0"
+					}
+				}
+			}
+		},
 		"test-exclude": {
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -2207,6 +2686,12 @@
 				"minimatch": "^3.0.4"
 			}
 		},
+		"text-table": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+			"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+			"dev": true
+		},
 		"to-fast-properties": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -2235,10 +2720,35 @@
 				"yn": "3.1.1"
 			}
 		},
+		"tslib": {
+			"version": "1.13.0",
+			"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
+			"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
+			"dev": true
+		},
+		"tsutils": {
+			"version": "3.17.1",
+			"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
+			"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
+			"dev": true,
+			"requires": {
+				"tslib": "^1.8.1"
+			}
+		},
+		"type-check": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+			"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+			"dev": true,
+			"requires": {
+				"prelude-ls": "^1.2.1"
+			}
+		},
 		"type-detect": {
 			"version": "4.0.8",
 			"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
-			"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
+			"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+			"dev": true
 		},
 		"type-fest": {
 			"version": "0.8.1",
@@ -2261,12 +2771,27 @@
 			"integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
 			"dev": true
 		},
+		"uri-js": {
+			"version": "4.2.2",
+			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+			"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+			"dev": true,
+			"requires": {
+				"punycode": "^2.1.0"
+			}
+		},
 		"uuid": {
 			"version": "3.4.0",
 			"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
 			"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
 			"dev": true
 		},
+		"v8-compile-cache": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+			"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+			"dev": true
+		},
 		"which": {
 			"version": "2.0.2",
 			"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -2291,6 +2816,12 @@
 				"string-width": "^1.0.2 || 2"
 			}
 		},
+		"word-wrap": {
+			"version": "1.2.3",
+			"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+			"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+			"dev": true
+		},
 		"workerpool": {
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz",
@@ -2366,6 +2897,15 @@
 			"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
 			"dev": true
 		},
+		"write": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+			"integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+			"dev": true,
+			"requires": {
+				"mkdirp": "^0.5.1"
+			}
+		},
 		"write-file-atomic": {
 			"version": "3.0.3",
 			"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
diff --git a/package.json b/package.json
index 54ac39a..20b1d62 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "broken-links-inspector",
-	"version": "1.1.1",
+	"version": "1.1.2",
 	"description": "Extract and recursively check all URLs reporting broken ones",
 	"main": "dist/inspector.js",
 	"types": "dist/inspector.d.ts",
@@ -59,7 +59,10 @@
 		"@types/chai": "^4.2.11",
 		"@types/mocha": "^7.0.2",
 		"@types/sinon": "^9.0.4",
+		"@typescript-eslint/eslint-plugin": "^3.4.0",
+		"@typescript-eslint/parser": "^3.4.0",
 		"chai": "^4.2.0",
+		"eslint": "^7.3.1",
 		"mocha": "^8.0.1",
 		"mocha-junit-reporter": "^2.0.0",
 		"nyc": "^15.1.0",
diff --git a/src/index.ts b/src/index.ts
index e815687..b0a9d94 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,7 +6,7 @@ import { Inspector, URLsMatchingSet } from "./inspector"
 import { ConsoleReporter, JUnitReporter } from "./report"
 
 commander
-	.version("1.1.1")
+	.version("1.1.2")
 	.description("Extract and recursively check all URLs reporting broken ones")
 
 commander
@@ -30,7 +30,7 @@ commander
 			process.exit(1)
 		}
 
-		let inspector = new Inspector(new URLsMatchingSet(), {
+		const inspector = new Inspector(new URLsMatchingSet(), {
 			acceptedCodes: inspectObj.acceptCodes as number[],
 			timeout: parseInt(inspectObj.timeout as string),
 			ignoredPrefixes: inspectObj.ignorePrefixes as string[],
@@ -41,7 +41,7 @@ commander
 			disablePrint: false
 		})
 
-		let result = await inspector.processURL(new URL(url), inspectObj.recursive as boolean)
+		const result = await inspector.processURL(new URL(url), inspectObj.recursive as boolean)
 
 		for (const reporter of inspectObj.reporters as string[]) {
 			switch (reporter) {
diff --git a/src/inspector.ts b/src/inspector.ts
index 9a27996..965e84b 100644
--- a/src/inspector.ts
+++ b/src/inspector.ts
@@ -1,6 +1,6 @@
 import * as parser from "htmlparser2"
 import axios, { AxiosError } from "axios"
-import { Result, CheckStatus } from "./result";
+import { Result, CheckStatus } from "./result"
 import { isMatch } from "matcher"
 
 export interface IHttpClient {
@@ -21,7 +21,7 @@ export class AxiosHttpClient implements IHttpClient {
 		readonly acceptedCodes: number[]
 	) { }
 
-	private async timeoutWrapper<T>(timeoutMs: number, promise: () => Promise<T>, failureMessage: string = "timeout"): Promise<T> {
+	private async timeoutWrapper<T>(timeoutMs: number, promise: () => Promise<T>, failureMessage = "timeout"): Promise<T> {
 		let timeoutHandle: NodeJS.Timeout | undefined
 		const timeoutPromise = new Promise<never>((_, reject) => {
 			timeoutHandle = setTimeout(() => reject(new Error(failureMessage)), timeoutMs)
@@ -30,9 +30,11 @@ export class AxiosHttpClient implements IHttpClient {
 		const result = await Promise.race([
 			promise(),
 			timeoutPromise
-		]);
-		clearTimeout(timeoutHandle!);
-		return result;
+		])
+		if (timeoutHandle) {
+			clearTimeout(timeoutHandle)
+		}
+		return result
 	}
 
 	async request(get: boolean, url: string): Promise<string> {
@@ -43,13 +45,14 @@ export class AxiosHttpClient implements IHttpClient {
 			return (await this.timeoutWrapper(this.timeout, () => get ? instance.get(url) : instance.head(url))).data as string
 		} catch (exception) {
 
-			const error: AxiosError = exception;
+			const error: AxiosError = exception
 
 			if ((exception.message as string).includes("timeout")) {
 				throw new HttpClientFailure(true, -1)
 			} else if (!error.response) {
 				throw new HttpClientFailure(false, -1)
 			} else {
+				// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 				if (this.acceptedCodes.some(code => code == error.response!.status)) {
 					return ""
 				} else {
@@ -70,11 +73,11 @@ export class Inspector {
 
 	async processURL(originalUrl: URL, recursive: boolean): Promise<Result> {
 
-		let result = new Result(this.config.ignoreSkipped, this.config.disablePrint);
+		const result = new Result(this.config.ignoreSkipped, this.config.disablePrint)
 		// [url, GET, parent?]
-		let urlsToCheck: [string, boolean, string?][] = [[originalUrl.href, true, undefined]]
+		const urlsToCheck: [string, boolean, string?][] = [[originalUrl.href, true, undefined]]
 
-		let processingRoutine = async (url: string, useGet: boolean, parent?: string) => {
+		const processingRoutine = async (url: string, useGet: boolean, parent?: string) => {
 
 			try {
 				try {
@@ -85,7 +88,7 @@ export class Inspector {
 				if (url.includes("#")) {
 					url = url.split("#")[0]
 				}
-				let shouldParse = url == originalUrl.href || (recursive && originalUrl.origin == new URL(url).origin)
+				const shouldParse = url == originalUrl.href || (recursive && originalUrl.origin == new URL(url).origin)
 
 				if (
 					result.isChecked(url) ||
@@ -94,13 +97,13 @@ export class Inspector {
 				) {
 					result.add({ url: url, status: CheckStatus.Skipped }, parent)
 				} else {
-					let urlToCheck = parent ? new URL(url, parent).href : url
+					const urlToCheck = parent ? new URL(url, parent).href : url
 
-					let html = await this.httpClient.request(useGet || shouldParse, urlToCheck)
+					const html = await this.httpClient.request(useGet || shouldParse, urlToCheck)
 
 					if (shouldParse) {
 
-						let discoveredURLs = this.extractURLs(html)
+						const discoveredURLs = this.extractURLs(html)
 
 						for (const discovered of discoveredURLs) {
 							urlsToCheck.push([discovered, this.config.get, url])
@@ -111,7 +114,7 @@ export class Inspector {
 				}
 
 			} catch (exception) {
-				const error: HttpClientFailure = exception;
+				const error: HttpClientFailure = exception
 
 				// if HEAD was used, retry with GET
 				if (!useGet) {
@@ -128,11 +131,12 @@ export class Inspector {
 			}
 		}
 
-		let promises: Promise<void>[] = []
+		const promises: Promise<void>[] = []
 
 		while (urlsToCheck.length > 0) {
 
-			let [url, useGet, parent] = urlsToCheck.pop()!
+			// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+			const [url, useGet, parent] = urlsToCheck.pop()!
 
 			promises.push(processingRoutine(url, useGet, parent))
 
@@ -147,13 +151,13 @@ export class Inspector {
 
 	extractURLs(html: string): Set<string> {
 
-		let urls = new Set<string>();
-		let matcher = this.matcher
+		const urls = new Set<string>()
+		const matcher = this.matcher
 
-		let parserInstance = new parser.Parser(
+		const parserInstance = new parser.Parser(
 			{
 				onopentag(name, attributes) {
-					const match = matcher.match(name, attributes);
+					const match = matcher.match(name, attributes)
 					if (match && match !== "" && !match.startsWith("#")) {
 						urls.add(match)
 					}
@@ -171,13 +175,13 @@ export class Inspector {
 
 export class Config {
 	acceptedCodes: number[] = [999]
-	timeout: number = 2000
+	timeout = 2000
 	ignoredPrefixes: string[] = ["mailto", "tel"]
 	skipURLs: string[] = []
-	verbose: boolean = false
-	get: boolean = false
-	ignoreSkipped: boolean = false
-	disablePrint: boolean = false
+	verbose = false
+	get = false
+	ignoreSkipped = false
+	disablePrint = false
 }
 
 export enum URLMatchingRule {
@@ -192,7 +196,7 @@ export class URLsMatchingSet {
 	private rules: URLMatchingRule[]
 
 	constructor(...rules: URLMatchingRule[]) {
-		this.rules = rules.length > 0 ? rules : Object.values(URLMatchingRule);
+		this.rules = rules.length > 0 ? rules : Object.values(URLMatchingRule)
 	}
 
 	public match(name: string, attributes: { [s: string]: string }): string | undefined {
@@ -203,29 +207,29 @@ export class URLsMatchingSet {
 					if (name === "a" && "href" in attributes) {
 						return attributes.href
 					}
-					break;
+					break
 				case URLMatchingRule.ScriptSrc:
 					if (name === "script" && "src" in attributes) {
 						return attributes.src
 					}
-					break;
+					break
 				case URLMatchingRule.LinkHref:
 					if (name === "link" && "href" in attributes) {
 						return attributes.href
 					}
-					break;
+					break
 				case URLMatchingRule.ImgSrc:
 					if (name === "img" && "src" in attributes) {
 						return attributes.src
 					}
-					break;
+					break
 				case URLMatchingRule.IFrameSrc:
 					if (name === "iframe" && "src" in attributes) {
 						return attributes.src
 					}
-					break;
+					break
 				default:
-					throw new Error(`unknown rule: ${rule}`);
+					throw new Error(`unknown rule: ${rule}`)
 			}
 		}
 
diff --git a/src/report.ts b/src/report.ts
index 41dab69..82fad42 100644
--- a/src/report.ts
+++ b/src/report.ts
@@ -1,10 +1,10 @@
 import { ResultItem, CheckStatus } from "./result"
 import chalk from "chalk"
 import { parse } from "js2xmlparser"
-import fs from "fs";
+import fs from "fs"
 
 export interface IReporter {
-	process(pages: Map<string, ResultItem[]>): any
+	process(pages: Map<string, ResultItem[]>): unknown
 }
 
 /**
@@ -38,11 +38,35 @@ export class JUnitReporter implements IReporter {
 
 	process(pages: Map<string, ResultItem[]>): void {
 
-		let junitObject: any[] = []
+		type TestCase = {
+			"@": {
+				name: string,
+				classname: string,
+				time: string,
+			},
+			failure?: {
+				"@": {
+					message?: string
+				}
+			},
+			skipped?: unknown
+		}
+		type TestSuite = {
+			testcase: TestCase[],
+			"@"?: {
+				name: string,
+				tests: number,
+				failures: number,
+				skipped: number,
+				time: string
+			}
+		}
+
+		const junitObject: TestSuite[] = []
 
 		for (const page of pages) {
 
-			let testsuite: any = {
+			const testsuite: TestSuite = {
 				testcase: []
 			}
 
@@ -52,7 +76,7 @@ export class JUnitReporter implements IReporter {
 
 			for (const check of page[1]) {
 
-				let testcase: any = {
+				const testcase: TestCase = {
 					"@": {
 						name: check.url,
 						classname: page[0],
@@ -108,7 +132,7 @@ export class JUnitReporter implements IReporter {
 			junitObject.push(testsuite)
 		}
 
-		let junitXml = parse("testsuites", { testsuite: junitObject })
+		const junitXml = parse("testsuites", { testsuite: junitObject })
 		if (this.toFile) {
 			fs.writeFileSync("junit-report.xml", junitXml)
 		} else {
@@ -120,7 +144,7 @@ export class JUnitReporter implements IReporter {
 
 export class ConsoleReporter implements IReporter {
 
-	private printTotals(oks: number, skipped: number, broken: number, indent: boolean = true) {
+	private printTotals(oks: number, skipped: number, broken: number, indent = true) {
 		console.log(`${indent ? "\t" : ""}${chalk.green(`OK: ${oks}`)}, ${chalk.grey(`skipped: ${skipped}`)}, ${chalk.red(`broken: ${broken}`)}`)
 	}
 
@@ -131,17 +155,17 @@ export class ConsoleReporter implements IReporter {
 		switch (check.status) {
 			case CheckStatus.OK:
 				statusLabel = chalk.green("OK".padEnd(labelWidth))
-				break;
+				break
 			case CheckStatus.Skipped:
 				statusLabel = chalk.gray("SKIP".padEnd(labelWidth))
-				break;
+				break
 			case CheckStatus.Timeout:
 				statusLabel = chalk.yellow("TIMEOUT".padEnd(labelWidth))
-				break;
+				break
 			case CheckStatus.NonSuccessCode:
 			case CheckStatus.GenericError:
 				statusLabel = chalk.red("BROKEN".padEnd(labelWidth))
-				break;
+				break
 		}
 
 		if (check.status != CheckStatus.Skipped) {
@@ -149,7 +173,7 @@ export class ConsoleReporter implements IReporter {
 		}
 	}
 
-	process(pages: Map<string, ResultItem[]>) {
+	process(pages: Map<string, ResultItem[]>): void {
 
 		let allSkipped = 0
 		let allOks = 0
diff --git a/src/result.ts b/src/result.ts
index bad7d1c..e7ed813 100644
--- a/src/result.ts
+++ b/src/result.ts
@@ -8,7 +8,7 @@ export class Result {
 
 	constructor(readonly ignoreSkipped: boolean, readonly disablePrint: boolean) { }
 
-	public add(completedCheck: ResultItem, parent: string = "original request") {
+	public add(completedCheck: ResultItem, parent = "original request"): void {
 		if (completedCheck.status == CheckStatus.Skipped && this.ignoreSkipped) {
 			return
 		}
@@ -22,6 +22,7 @@ export class Result {
 		}
 
 		if (this.pages.has(parent)) {
+			// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 			this.pages.get(parent)!.push(completedCheck)
 		} else {
 			this.pages.set(parent, [completedCheck])
@@ -49,15 +50,15 @@ export class Result {
 		return count
 	}
 
-	public report<ReporterT extends IReporter>(reporter: ReporterT): any {
+	public report<ReporterT extends IReporter>(reporter: ReporterT): unknown {
 		return reporter.process(this.pages)
 	}
 
-	public success() {
+	public success(): boolean {
 		return !this.atLeastOneBroken
 	}
 
-	public set(pages: Map<string, ResultItem[]>) {
+	public set(pages: Map<string, ResultItem[]>): void {
 		this.pages = pages
 	}
 }
diff --git a/test/extract-urls.ts b/test/extract-urls.ts
index 17d6f11..38edb7a 100644
--- a/test/extract-urls.ts
+++ b/test/extract-urls.ts
@@ -1,6 +1,6 @@
 import { Inspector, URLsMatchingSet, URLMatchingRule, Config } from "../src/inspector"
-import { expect, assert } from "chai";
-import "mocha";
+import { expect, assert } from "chai"
+import "mocha"
 
 describe("extractURLs", () => {
 
@@ -9,27 +9,27 @@ describe("extractURLs", () => {
 	it("works for <a href=...>", () => {
 		const result = new Inspector(new URLsMatchingSet(URLMatchingRule.AHRef), new Config()).extractURLs(`<html><a href="${url}">Text</a></html>`)
 		expect(result).to.eql(new Set([url]))
-	});
+	})
 
 	it("works for <script src=...>", () => {
 		const result = new Inspector(new URLsMatchingSet(URLMatchingRule.ScriptSrc), new Config()).extractURLs(`<html><script src="${url}">Text</script></html>`)
 		expect(result).to.eql(new Set([url]))
-	});
+	})
 
 	it("works for <link href=...>", () => {
 		const result = new Inspector(new URLsMatchingSet(URLMatchingRule.LinkHref), new Config()).extractURLs(`<html><link href="${url}"></link></html>`)
 		expect(result).to.eql(new Set([url]))
-	});
+	})
 
 	it("works for <img src=...>", () => {
 		const result = new Inspector(new URLsMatchingSet(URLMatchingRule.ImgSrc), new Config()).extractURLs(`<html><img src="${url}">Text</img></html>`)
 		expect(result).to.eql(new Set([url]))
-	});
+	})
 
 	it("works for <iframe src=...>", () => {
 		const result = new Inspector(new URLsMatchingSet(URLMatchingRule.IFrameSrc), new Config()).extractURLs(`<html><iframe src="${url}">Text</iframe></html>`)
 		expect(result).to.eql(new Set([url]))
-	});
+	})
 
 	it("works for many rules", () => {
 		const result = new Inspector(new URLsMatchingSet(), new Config())
@@ -42,7 +42,7 @@ describe("extractURLs", () => {
 				</html>`
 			)
 		expect(result).to.eql(new Set(["1", "2", "3", "4"]))
-	});
+	})
 
 	it("does not match unless rule supplied", () => {
 		const result = new Inspector(new URLsMatchingSet(URLMatchingRule.ImgSrc), new Config())
@@ -53,7 +53,7 @@ describe("extractURLs", () => {
 				</html>`
 			)
 		expect(result).to.eql(new Set([url]))
-	});
+	})
 
 	it("filters duplicates", () => {
 		const result = new Inspector(new URLsMatchingSet(), new Config())
@@ -65,10 +65,10 @@ describe("extractURLs", () => {
 				</html>`
 			)
 		expect(result).to.eql(new Set([url, "another-url"]))
-	});
+	})
 
 	it("fails for unknown rule", () => {
 		assert.throws(() => new Inspector(new URLsMatchingSet("error" as URLMatchingRule), new Config()).extractURLs(`<html><img src="${url}">Text</img></html>`), /unknown/)
-	});
+	})
 
-});
+})
diff --git a/test/process-url.ts b/test/process-url.ts
index 282ad77..617e0aa 100644
--- a/test/process-url.ts
+++ b/test/process-url.ts
@@ -1,9 +1,9 @@
 import { Inspector, URLsMatchingSet, Config, IHttpClient, HttpClientFailure, AxiosHttpClient } from "../src/inspector"
-import { assert } from "chai";
-import "mocha";
-import { ConsoleReporter, JUnitReporter, IReporter } from "../src/report";
-import { ResultItem, CheckStatus, Result } from "../src/result";
-import intercept from "intercept-stdout";
+import { assert } from "chai"
+import "mocha"
+import { ConsoleReporter, JUnitReporter, IReporter } from "../src/report"
+import { ResultItem, CheckStatus, Result } from "../src/result"
+import intercept from "intercept-stdout"
 
 class MockHttpClient implements IHttpClient {
 
@@ -11,6 +11,7 @@ class MockHttpClient implements IHttpClient {
 	constructor(readonly map: Map<string, [string[], boolean, boolean, number]>) { }
 
 	async request(get: boolean, url: string): Promise<string> {
+		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 		const [urls, timeout, failure, code] = this.map.get(url)!
 		if (timeout) {
 			throw new HttpClientFailure(true, -1)
@@ -27,16 +28,17 @@ class MockHttpClient implements IHttpClient {
 }
 
 class MockReporter implements IReporter {
-	process(pages: Map<string, ResultItem[]>): any {
+	process(pages: Map<string, ResultItem[]>): Map<string, ResultItem[]> {
 		return pages
 	}
 }
 
-function toURL(url: string, path: string = "") {
+function toURL(url: string, path = "") {
 	return new URL(`https://${url}/${path}`).href
 }
 
 function stripEffects(text: string) {
+	// eslint-disable-next-line no-control-regex
 	return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")
 }
 
@@ -44,12 +46,15 @@ function assertEqualResults(expected: Map<string, ResultItem[]>, actual: Map<str
 
 	for (const [expectedURL, expectedChecks] of expected) {
 		assert(actual.has(expectedURL))
-		let actualChecks = actual.get(expectedURL)!
+		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+		const actualChecks = actual.get(expectedURL)!
 		assert(expectedChecks.length == actualChecks.length)
 		for (const expectedCheck of expectedChecks) {
-			let actualCheck = actualChecks.find(c => c.url === expectedCheck.url)
+			const actualCheck = actualChecks.find(c => c.url === expectedCheck.url)
 			assert(actualCheck)
+			// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 			assert(expectedCheck.status == actualCheck!.status)
+			// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 			assert(expectedCheck.message == actualCheck!.message)
 		}
 	}
@@ -102,7 +107,7 @@ describe("Axios web server", async () => {
 		try {
 			await new AxiosHttpClient(5, []).request(false, "https://dbogatov.org")
 		} catch (exception) {
-			const error: HttpClientFailure = exception;
+			const error: HttpClientFailure = exception
 			assert(error.timeout)
 		}
 	})
@@ -111,7 +116,7 @@ describe("Axios web server", async () => {
 		try {
 			await new AxiosHttpClient(2000, []).request(false, "https://dbogatov.org/not-found-123")
 		} catch (exception) {
-			const error: HttpClientFailure = exception;
+			const error: HttpClientFailure = exception
 			assert(error.code == 404)
 		}
 	})
@@ -120,7 +125,7 @@ describe("Axios web server", async () => {
 		try {
 			await new AxiosHttpClient(1000, []).request(false, "bad-url")
 		} catch (exception) {
-			const error: HttpClientFailure = exception;
+			const error: HttpClientFailure = exception
 			assert(!error.timeout)
 			assert(error.code == -1)
 		}
@@ -135,7 +140,7 @@ describe("process mock URL", function () {
 	([true, false] as boolean[]).forEach(recursive => {
 
 		it(`processes ${recursive ? "" : "non-"}recursive`, async () => {
-			let config = new Config()
+			const config = new Config()
 			config.disablePrint = true
 			config.skipURLs = ["to-skip"]
 			const inspector = new Inspector(
@@ -143,14 +148,14 @@ describe("process mock URL", function () {
 				config,
 				httpClient
 			)
-			let unhook_intercept = intercept(_ => { return "" });
+			const unhook_intercept = intercept(_ => { return "" })
 
 			const result = await inspector.processURL(new URL("https://original.com"), recursive)
 
-			unhook_intercept();
+			unhook_intercept()
 
 			const actual = result.report(new MockReporter()) as Map<string, ResultItem[]>
-			let expected = new Map(expectedNonRecursive)
+			const expected = new Map(expectedNonRecursive)
 
 			if (recursive) {
 				expected.set(
@@ -164,26 +169,26 @@ describe("process mock URL", function () {
 
 			assertEqualResults(expected, actual)
 			assert(!result.success())
-		});
+		})
 	})
 
 	describe("reporters", function () {
 
 		it("console", () => {
 
-			let log: string = ""
-			let unhook_intercept = intercept(line => {
+			let log = ""
+			const unhook_intercept = intercept(line => {
 				log += stripEffects(line)
 				return ""
-			});
+			})
 
-			let result = new Result(true, true)
+			const result = new Result(true, true)
 			result.set(expectedNonRecursive)
 			result.report(new ConsoleReporter())
 
-			unhook_intercept();
+			unhook_intercept()
 
-			let lines = log.split(/\r?\n/)
+			const lines = log.split(/\r?\n/)
 
 			for (const [expectedURL, expectedChecks] of expectedNonRecursive) {
 				assert(lines.find(l => l.startsWith(expectedURL)))
@@ -192,13 +197,15 @@ describe("process mock URL", function () {
 					if (expectedCheck.status == CheckStatus.Skipped) {
 						continue
 					}
-					let check = lines.find(l => l.includes("\t") && l.includes(expectedCheck.url + " "))
+					const check = lines.find(l => l.includes("\t") && l.includes(expectedCheck.url + " "))
 					assert(check, `${expectedCheck.url} not found`)
 					assert(
+						// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 						check!.includes(expectedCheck.status == CheckStatus.NonSuccessCode || expectedCheck.status == CheckStatus.GenericError ? "BROKEN" : expectedCheck.status),
 						`${expectedCheck.url}: status (${expectedCheck.status}) not found in "${check}"`
 					)
 					if (expectedCheck.message) {
+						// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 						assert(check!.includes(expectedCheck.message))
 					}
 				}
@@ -207,21 +214,21 @@ describe("process mock URL", function () {
 
 		it("junit", () => {
 
-			let log: string = ""
-			let unhook_intercept = intercept(line => {
+			let log = ""
+			const unhook_intercept = intercept(line => {
 				log += line
 				return ""
-			});
+			})
 
-			let result = new Result(true, true)
+			const result = new Result(true, true)
 			result.set(expectedNonRecursive)
 			result.report(new JUnitReporter(false))
 
-			unhook_intercept();
+			unhook_intercept()
 
 			result.report(new JUnitReporter())
 
-			let lines = log.split(/\r?\n/)
+			const lines = log.split(/\r?\n/)
 
 			for (const [expectedURL, expectedChecks] of expectedNonRecursive) {
 				assert(lines.find(l => l.includes("testsuite") && l.includes(expectedURL)))
@@ -235,7 +242,7 @@ describe("process mock URL", function () {
 })
 
 describe("process real URL", async () => {
-	let config = new Config()
+	const config = new Config()
 	config.disablePrint = true
 	const inspector = new Inspector(
 		new URLsMatchingSet(),
@@ -248,29 +255,29 @@ describe("process real URL", async () => {
 describe("result", () => {
 
 	it("ignores skipped", () => {
-		let result = new Result(true, true)
+		const result = new Result(true, true)
 		result.add({ status: CheckStatus.Skipped, url: "skip" })
 		result.add(new ResultItem())
 		assert(result.count() == 1)
 	})
 
 	it("print progress", () => {
-		let result = new Result(true, false)
+		const result = new Result(true, false)
 
-		let log: string = ""
-		let unhook_intercept = intercept(line => {
+		let log = ""
+		const unhook_intercept = intercept(line => {
 			log += line
 			return ""
-		});
+		})
 
 		result.add({ status: CheckStatus.GenericError, url: "" })
 		for (let index = 0; index < 120; index++) {
 			result.add({ status: CheckStatus.OK, url: `${index}` })
 		}
 
-		unhook_intercept();
+		unhook_intercept()
 
-		let lines = log.split(/\r?\n/)
+		const lines = log.split(/\r?\n/)
 
 		assert(result.count() == 121)
 		assert(lines.length == 2)
-- 
GitLab