diff --git a/.eslintrc.js b/.eslintrc.js index 183a27a0..eed0584b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,7 +18,16 @@ module.exports = { sourceType: "module", }, plugins: ["react", "@typescript-eslint"], - rules: {}, + rules: { + // TODO: Remove warn rules when not needed anymore + "@typescript-eslint/no-this-alias": "warn", + "no-fallthrough": "warn", + "no-self-assign": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/ban-types": "warn", + }, settings: { react: { version: "detect", diff --git a/package.json b/package.json index 4e85e35c..c47b602e 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,9 @@ "dev": "npm run build:dev && concurrently \"npm run web-run\" \"npm run build:watch\"", "dev:firefox": "npm run build:dev:firefox && concurrently \"npm run web-run:firefox\" \"npm run build:watch:firefox\"", "clean": "rimraf dist", - "test": "npx jest" + "test": "npx jest", + "lint": "eslint src", + "lint:fix": "eslint src --fix" }, "repository": { "type": "git", diff --git a/src/components/CategoryChooserComponent.tsx b/src/components/CategoryChooserComponent.tsx index ac18188d..19d9d589 100644 --- a/src/components/CategoryChooserComponent.tsx +++ b/src/components/CategoryChooserComponent.tsx @@ -23,7 +23,7 @@ class CategoryChooserComponent extends React.Component diff --git a/src/components/CategorySkipOptionsComponent.tsx b/src/components/CategorySkipOptionsComponent.tsx index a60303d3..b190223d 100644 --- a/src/components/CategorySkipOptionsComponent.tsx +++ b/src/components/CategorySkipOptionsComponent.tsx @@ -29,7 +29,7 @@ class CategorySkipOptionsComponent extends React.Component, preview: boolean) { + setColorState(event: React.FormEvent, preview: boolean): void { if (preview) { this.setState({ previewColor: event.currentTarget.value diff --git a/src/components/NoticeComponent.tsx b/src/components/NoticeComponent.tsx index 96fe51d1..94b3fd56 100644 --- a/src/components/NoticeComponent.tsx +++ b/src/components/NoticeComponent.tsx @@ -60,11 +60,11 @@ class NoticeComponent extends React.Component { } } - componentDidMount() { + componentDidMount(): void { this.startCountdown(); } - render() { + render(): React.ReactElement { const noticeStyle: React.CSSProperties = { zIndex: this.props.zIndex || (50 + this.amountOfPreviousNotices) } @@ -124,19 +124,19 @@ class NoticeComponent extends React.Component { ); } - timerMouseEnter() { + timerMouseEnter(): void { if (this.state.countdownManuallyPaused) return; this.pauseCountdown(); } - timerMouseLeave() { + timerMouseLeave(): void { if (this.state.countdownManuallyPaused) return; this.startCountdown(); } - toggleManualPause() { + toggleManualPause(): void { this.setState({ countdownManuallyPaused: !this.state.countdownManuallyPaused }, () => { @@ -149,7 +149,7 @@ class NoticeComponent extends React.Component { } //called every second to lower the countdown before hiding the notice - countdown() { + countdown(): void { if (!this.props.timed) return; const countdownTime = this.state.countdownTime - 1; @@ -176,7 +176,7 @@ class NoticeComponent extends React.Component { }) } - pauseCountdown() { + pauseCountdown(): void { if (!this.props.timed) return; //remove setInterval @@ -195,7 +195,7 @@ class NoticeComponent extends React.Component { notice.style.animation = "none"; } - startCountdown() { + startCountdown(): void { if (!this.props.timed) return; //if it has already started, don't start it again @@ -209,7 +209,7 @@ class NoticeComponent extends React.Component { this.countdownInterval = setInterval(this.countdown.bind(this), 1000); } - resetCountdown() { + resetCountdown(): void { if (!this.props.timed) return; this.setState({ @@ -221,20 +221,20 @@ class NoticeComponent extends React.Component { /** * @param silent If true, the close listener will not be called */ - close(silent?: boolean) { + close(silent?: boolean): void { //remove setInterval if (this.countdownInterval !== null) clearInterval(this.countdownInterval); if (!silent) this.props.closeListener(); } - changeNoticeTitle(title) { + changeNoticeTitle(title: string): void { this.setState({ noticeTitle: title }); } - addNoticeInfoMessage(message: string, message2 = "") { + addNoticeInfoMessage(message: string, message2 = ""): void { //TODO: Replace const previousInfoMessage = document.getElementById("sponsorTimesInfoMessage" + this.idSuffix); @@ -270,4 +270,4 @@ class NoticeComponent extends React.Component { } } -export default NoticeComponent; \ No newline at end of file +export default NoticeComponent; diff --git a/src/components/NoticeTextSectionComponent.tsx b/src/components/NoticeTextSectionComponent.tsx index 3126f8ca..cbcbb7b2 100644 --- a/src/components/NoticeTextSectionComponent.tsx +++ b/src/components/NoticeTextSectionComponent.tsx @@ -16,7 +16,7 @@ class NoticeTextSelectionComponent extends React.Component this.forceUpdate(); Config.configListeners.push(this.configListener); @@ -396,7 +396,7 @@ class SkipNoticeComponent extends React.Component) { + categorySelectionChange(event: React.ChangeEvent): void { // See if show more categories was pressed if (event.target.value === "moreCategories") { // Open options page @@ -433,14 +433,14 @@ class SkipNoticeComponent extends React.Component { this.noticeRef.current.resetCountdown(); @@ -468,7 +468,7 @@ class SkipNoticeComponent extends React.Component any, ...messages: string[]) { + setNoticeInfoMessageWithOnClick(onClick: (event: React.MouseEvent) => any, ...messages: string[]): void { this.setState({ messages, messageOnClick: (event) => onClick(event) }); } - setNoticeInfoMessage(...messages: string[]) { + setNoticeInfoMessage(...messages: string[]): void { this.setState({ messages }); } - addVoteButtonInfo(message) { + addVoteButtonInfo(message): void { this.setState({ thanksForVotingText: message }); } - resetVoteButtonInfo() { + resetVoteButtonInfo(): void { this.setState({ thanksForVotingText: null }); } - closeListener() { + closeListener(): void { this.clearConfigListener(); this.props.closeListener(); diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 39fe73c7..89b5d48e 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -44,7 +44,7 @@ class SponsorTimeEditComponent extends React.Component @@ -245,7 +245,7 @@ class SponsorTimeEditComponent extends React.Component) { + categorySelectionChange(event: React.ChangeEvent): void { // See if show more categories was pressed if (event.target.value === "moreCategories") { // Open options page @@ -259,15 +259,15 @@ class SponsorTimeEditComponent extends React.Component { @@ -61,13 +61,13 @@ class SubmissionNoticeComponent extends React.Component { return new Promise((resolve, reject) => { chrome.storage.sync.get(null, function(items) { Config.localConfig = items; // Data is ready @@ -453,11 +453,11 @@ function convertJSON(): void { // Add defaults function addDefaults() { for (const key in Config.defaults) { - if(!Config.localConfig.hasOwnProperty(key)) { - Config.localConfig[key] = Config.defaults[key]; + if(!Object.prototype.hasOwnProperty.call(Config.localConfig, key)) { + Config.localConfig[key] = Config.defaults[key]; } else if (key === "barTypes") { for (const key2 in Config.defaults[key]) { - if(!Config.localConfig[key].hasOwnProperty(key2)) { + if(!Object.prototype.hasOwnProperty.call(Config.localConfig[key], key2)) { Config.localConfig[key][key2] = Config.defaults[key][key2]; } } diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 4d6b1b15..d7ec7a43 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -18,7 +18,7 @@ class PreviewBar { timestamps: number[][]; types: string; - constructor(parent, onMobileYouTube, onInvidious) { + constructor(parent: any, onMobileYouTube: boolean, onInvidious: boolean) { this.container = document.createElement('ul'); this.container.id = 'previewbar'; this.parent = parent; @@ -31,7 +31,7 @@ class PreviewBar { this.setupHoverText(); } - setupHoverText() { + setupHoverText(): void { if (this.onMobileYouTube || this.onInvidious) return; const seekBar = document.querySelector(".ytp-progress-bar-container"); @@ -112,7 +112,7 @@ class PreviewBar { }); } - updatePosition(parent) { + updatePosition(parent: any): void { //below the seek bar // this.parent.insertAdjacentElement("afterEnd", this.container); @@ -129,7 +129,7 @@ class PreviewBar { this.parent.insertAdjacentElement("afterBegin", this.container); } - updateColor(segment, color, opacity) { + updateColor(segment: string, color: string, opacity: string): void { const bars = > document.querySelectorAll('[data-vs-segment-type=' + segment + ']'); for (const bar of bars) { bar.style.backgroundColor = color; @@ -137,7 +137,7 @@ class PreviewBar { } } - set(timestamps, types, duration) { + set(timestamps: number[][], types: string, duration: number): void { while (this.container.firstChild) { this.container.removeChild(this.container.firstChild); } @@ -171,14 +171,14 @@ class PreviewBar { } } - createBar() { + createBar(): HTMLLIElement { const bar = document.createElement('li'); bar.classList.add('previewbar'); bar.innerHTML = ' '; return bar; } - remove() { + remove(): void { this.container.remove(); this.container = undefined; } diff --git a/src/options.ts b/src/options.ts index 876c01b9..979dac6b 100644 --- a/src/options.ts +++ b/src/options.ts @@ -32,7 +32,7 @@ async function init() { for (let i = 0; i < optionsElements.length; i++) { switch (optionsElements[i].getAttribute("option-type")) { - case "toggle": + case "toggle": { const option = optionsElements[i].getAttribute("sync-option"); const optionResult = Config.config[option]; @@ -84,7 +84,8 @@ async function init() { } }); break; - case "text-change": + } + case "text-change": { const textChangeOption = optionsElements[i].getAttribute("sync-option"); const textChangeInput = optionsElements[i].querySelector(".option-text-box"); @@ -95,7 +96,7 @@ async function init() { textChangeSetButton.addEventListener("click", async () => { // See if anything extra must be done switch (textChangeOption) { - case "serverAddress": + case "serverAddress": { const result = validateServerAddress(textChangeInput.value); if (result !== null) { @@ -117,6 +118,7 @@ async function init() { } break; + } } Config.config[textChangeOption] = textChangeInput.value; @@ -133,7 +135,8 @@ async function init() { }); break; - case "private-text-change": + } + case "private-text-change": { const button = optionsElements[i].querySelector(".trigger-button"); button.addEventListener("click", () => activatePrivateTextChange( optionsElements[i])); @@ -145,7 +148,8 @@ async function init() { } break; - case "button-press": + } + case "button-press": { const actionButton = optionsElements[i].querySelector(".trigger-button"); switch(optionsElements[i].getAttribute("sync-option")) { @@ -155,16 +159,18 @@ async function init() { } break; - case "keybind-change": + } + case "keybind-change": { const keybindButton = optionsElements[i].querySelector(".trigger-button"); keybindButton.addEventListener("click", () => activateKeybindChange( optionsElements[i])); break; - case "display": + } + case "display":{ updateDisplayElement( optionsElements[i]) - break; - case "number-change": + } + case "number-change": { const numberChangeOption = optionsElements[i].getAttribute("sync-option"); const configValue = Config.config[numberChangeOption]; const numberInput = optionsElements[i].querySelector("input"); @@ -180,6 +186,7 @@ async function init() { }); break; + } case "react-CategoryChooserComponent": new CategoryChooser(optionsElements[i]); break; @@ -298,7 +305,7 @@ function invidiousInit(checkbox: HTMLInputElement, option: string) { * @param checkbox * @param option */ -async function invidiousOnClick(checkbox: HTMLInputElement, option: string) { +async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Promise { return new Promise((resolve) => { if (checkbox.checked) { utils.setupExtraSitePermissions(function (granted) { @@ -427,7 +434,7 @@ function activatePrivateTextChange(element: HTMLElement) { // See if anything extra must be done switch (option) { - case "*": + case "*": { const jsonData = JSON.parse(JSON.stringify(Config.localConfig)); // Fix segmentTimes data as it is destroyed from the JSON stringify @@ -435,6 +442,7 @@ function activatePrivateTextChange(element: HTMLElement) { result = JSON.stringify(jsonData); break; + } } textBox.value = result; @@ -528,7 +536,7 @@ function copyDebugOutputToClipboard() { .then(() => { alert(chrome.i18n.getMessage("copyDebugInformationComplete")); }) - .catch(err => { + .catch((err) => { alert(chrome.i18n.getMessage("copyDebugInformationFailed")); }); } \ No newline at end of file diff --git a/src/popup.ts b/src/popup.ts index 9de49cad..961a5bdd 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -38,7 +38,7 @@ class MessageHandler { } //make this a function to allow this to run on the content page -async function runThePopup(messageListener?: MessageListener) { +async function runThePopup(messageListener?: MessageListener): Promise { const messageHandler = new MessageHandler(messageListener); utils.localizeHtmlPage(); @@ -233,17 +233,17 @@ async function runThePopup(messageListener?: MessageListener) { }, onTabs); function onTabs(tabs) { - messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) { - if (result != undefined && result.videoID) { - currentVideoID = result.videoID; - loadTabData(tabs); - } else if (result == undefined && chrome.runtime.lastError) { - //this isn't a YouTube video then, or at least the content script is not loaded - displayNoVideo(); - } - }); + messageHandler.sendMessage(tabs[0].id, {message: 'getVideoID'}, function(result) { + if (result != undefined && result.videoID) { + currentVideoID = result.videoID; + loadTabData(tabs); + } else if (result == undefined && chrome.runtime.lastError) { + // this isn't a YouTube video then, or at least the content script is not loaded + displayNoVideo(); + } + }); } - + function loadTabData(tabs) { if (!currentVideoID) { //this isn't a YouTube video then diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index 0f417990..ac66cae4 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -63,7 +63,7 @@ class SkipNotice { ); } - close() { + close(): void { ReactDOM.unmountComponentAtNode(this.noticeElement); this.noticeElement.remove(); diff --git a/src/render/SubmissionNotice.tsx b/src/render/SubmissionNotice.tsx index 443c20e8..2d600f56 100644 --- a/src/render/SubmissionNotice.tsx +++ b/src/render/SubmissionNotice.tsx @@ -51,11 +51,11 @@ class SubmissionNotice { ); } - update() { + update(): void { this.noticeRef.current.forceUpdate(); } - close() { + close(): void { ReactDOM.unmountComponentAtNode(this.noticeElement); this.noticeElement.remove(); diff --git a/src/utils.ts b/src/utils.ts index 5eea0a71..9a515e38 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,7 +24,7 @@ class Utils { } // Function that can be used to wait for a condition before returning - async wait(condition, timeout = 5000, check = 100) { + async wait(condition: () => boolean, timeout = 5000, check = 100): Promise { return await new Promise((resolve, reject) => { setTimeout(() => reject("TIMEOUT"), timeout); @@ -51,7 +51,7 @@ class Utils { * * @param {CallableFunction} callback */ - setupExtraSitePermissions(callback) { + setupExtraSitePermissions(callback: (granted: boolean) => void): void { // Request permission let permissions = ["declarativeContent"]; if (this.isFirefox()) permissions = []; @@ -79,7 +79,7 @@ class Utils { * * For now, it is just SB.config.invidiousInstances. */ - setupExtraSiteContentScripts() { + setupExtraSiteContentScripts(): void { const self = this; if (this.isFirefox()) { @@ -135,7 +135,7 @@ class Utils { /** * Removes the permission and content script registration. */ - removeExtraSiteRegistration() { + removeExtraSiteRegistration(): void { if (this.isFirefox()) { const id = "invidious"; @@ -193,7 +193,7 @@ class Utils { } } - localizeHtmlPage() { + localizeHtmlPage(): void { //Localize by replacing __MSG_***__ meta tags const objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children; for (let j = 0; j < objects.length; j++) { @@ -204,7 +204,7 @@ class Utils { } } - getLocalizedMessage(text) { + getLocalizedMessage(text: string): string | false { const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) { return v1 ? chrome.i18n.getMessage(v1) : ""; }); @@ -219,8 +219,8 @@ class Utils { /** * @returns {String[]} Invidious Instances in regex form */ - getInvidiousInstancesRegex() { - const invidiousInstancesRegex = []; + getInvidiousInstancesRegex(): string[] { + const invidiousInstancesRegex: string[] = []; for (const url of Config.config.invidiousInstances) { invidiousInstancesRegex.push("https://*." + url + "/*"); invidiousInstancesRegex.push("http://*." + url + "/*"); @@ -229,7 +229,7 @@ class Utils { return invidiousInstancesRegex; } - generateUserID(length = 36) { + generateUserID(length = 36): string { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; if (window.crypto && window.crypto.getRandomValues) { @@ -253,7 +253,7 @@ class Utils { * @param {int} statusCode * @returns {string} errorMessage */ - getErrorMessage(statusCode) { + getErrorMessage(statusCode: number): string { let errorMessage = ""; if([400, 429, 409, 502, 0].includes(statusCode)) { @@ -310,7 +310,7 @@ class Utils { * @param address The address to add to the SponsorBlock server address * @param callback */ - sendRequestToServer(type: string, address: string, callback?: (response: FetchResponse) => void) { + sendRequestToServer(type: string, address: string, callback?: (response: FetchResponse) => void): void { const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress; // Ask the background script to do the work