diff --git a/.eslintrc.js b/.eslintrc.js index e97ae69e6cdab..510e38e06c8c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,13 +11,24 @@ module.exports = { // https://eslint.org/docs/user-guide/configuring#specifying-parser parser: 'vue-eslint-parser', - // https://vuejs.github.io/eslint-plugin-vue/user-guide/#faq + // https://eslint.vuejs.org/user-guide/#faq parserOptions: { - parser: 'babel-eslint', - ecmaVersion: 2018, + parser: '@babel/eslint-parser', + ecmaVersion: 2022, sourceType: 'module' }, + overrides: [ + { + files: ['*.json'], + parser: 'jsonc-eslint-parser', + rules: { + 'no-tabs': 'off', + 'comma-spacing': 'off' + } + } + ], + // https://eslint.org/docs/user-guide/configuring#extending-configuration-files // order matters: from least important to most important in terms of overriding // Prettier + Vue: https://medium.com/@gogl.alex/how-to-properly-set-up-eslint-with-prettier-for-vue-or-nuxt-in-vscode-e42532099a9c @@ -25,12 +36,13 @@ module.exports = { 'prettier', 'eslint:recommended', 'plugin:vue/recommended', - 'standard' + 'standard', + 'plugin:jsonc/recommended-with-json', // 'plugin:vuejs-accessibility/recommended' // uncomment once issues are fixed ], // https://eslint.org/docs/user-guide/configuring#configuring-plugins - plugins: ['vue', 'vuejs-accessibility'], + plugins: ['vue', 'vuejs-accessibility', 'n', 'unicorn'], rules: { 'space-before-function-paren': 'off', @@ -39,6 +51,7 @@ module.exports = { 'no-console': ['error', { allow: ['warn', 'error'] }], 'no-unused-vars': 'warn', 'no-undef': 'warn', + 'object-shorthand': 'off', 'vue/no-template-key': 'warn', 'vue/no-useless-template-attributes': 'off', 'vue/multi-word-component-names': 'off', @@ -47,6 +60,13 @@ module.exports = { required: { some: ['nesting', 'id'] } - }] + }], + 'n/no-callback-literal': 'warn', + 'n/no-path-concat': 'warn', + 'unicorn/better-regex': 'error', + 'unicorn/no-array-push-push': 'error', + 'unicorn/prefer-keyboard-event-key': 'error', + 'unicorn/prefer-regexp-test': 'error', + 'unicorn/prefer-string-replace-all': 'error' } } diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000000000..18256bd0f5487 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,7 @@ +src/data/ +src/datastores/ +src/main/ +src/renderer/videoJS.css +dist/ +static/ +node_modules/ diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000000000..ea45b8ebd0836 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,34 @@ +{ + "plugins": ["stylelint-high-performance-animation", "@double-great/stylelint-a11y"], + "extends": ["stylelint-config-standard", "stylelint-config-sass-guidelines"], + "overrides": [ + { + "files": ["**/*.scss"], + "customSyntax": "postcss-scss", + "rules": { + "max-nesting-depth": null, + "selector-max-compound-selectors": null + } + }, + { + "files": ["**/*.css"], + "rules": { + "a11y/media-prefers-reduced-motion": true, + "a11y/no-outline-none": true, + "a11y/selector-pseudo-class-focus": true, + "a11y/font-size-is-readable": true + } + } + ], + "rules": { + "selector-class-pattern": null, + "selector-id-pattern": null, + "plugin/no-low-performance-animation-properties": true, + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["deep"] + } + ] + } +} diff --git a/_scripts/webpack.renderer.config.js b/_scripts/webpack.renderer.config.js index ecf1249adc2df..723661f8abf87 100644 --- a/_scripts/webpack.renderer.config.js +++ b/_scripts/webpack.renderer.config.js @@ -48,7 +48,7 @@ const config = { loader: 'vue-loader', }, { - test: /\.s(c|a)ss$/, + test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, @@ -62,11 +62,7 @@ const config = { { loader: 'sass-loader', options: { - // eslint-disable-next-line - implementation: require('sass'), - sassOptions: { - indentedSyntax: true - } + implementation: require('sass') } }, ], diff --git a/_scripts/webpack.web.config.js b/_scripts/webpack.web.config.js index a18115e95f873..ac15675428feb 100644 --- a/_scripts/webpack.web.config.js +++ b/_scripts/webpack.web.config.js @@ -39,7 +39,7 @@ const config = { loader: 'vue-loader' }, { - test: /\.s(c|a)ss$/, + test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, @@ -53,11 +53,7 @@ const config = { { loader: 'sass-loader', options: { - // eslint-disable-next-line - implementation: require('sass'), - sassOptions: { - indentedSyntax: true - } + implementation: require('sass') } }, ], diff --git a/lefthook.yml b/lefthook.yml index 7f6f4e3d68548..6a09c9530afc2 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,4 +1,3 @@ - # Refer for explanation to following link: # https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md pre-commit: @@ -12,8 +11,6 @@ pre-commit: skip: - rebase - - # EXAMPLE USAGE # # pre-push: diff --git a/package.json b/package.json index b427cb1aad37b..8184e4c69dff7 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,15 @@ "dev": "run-s rebuild:electron dev-runner", "dev:web": "node _scripts/dev-runner.js --web", "dev-runner": "node _scripts/dev-runner.js", + "lint-all": "run-p lint lint-json lint-style", "lint-fix": "eslint --fix --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./", + "lint-json": "eslint --ext .json ./", + "lint-style": "run-p lint-style:scss lint-style:css", + "lint-style:scss": "stylelint \"**/*.scss\"", + "lint-style:css": "stylelint \"**/*.css\"", + "lint-style-fix:scss": "stylelint --fix \"**/*.scss\"", + "lint-style-fix:css": "stylelint --fix \"**/*.css\"", "pack": "run-p pack:main pack:renderer", "pack:main": "webpack --mode=production --node-env=production --config _scripts/webpack.main.config.js", "pack:renderer": "webpack --mode=production --node-env=production --config _scripts/webpack.renderer.config.js", @@ -81,23 +88,25 @@ }, "devDependencies": { "@babel/core": "^7.20.7", + "@babel/eslint-parser": "^7.19.1", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/preset-env": "^7.20.2", - "babel-eslint": "^10.1.0", + "@double-great/stylelint-a11y": "^2.0.2", "babel-loader": "^9.1.0", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^4.2.2", "electron": "^22.0.0", "electron-builder": "^23.6.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-node": "^11.1.0", + "eslint": "^8.31.0", + "eslint-config-prettier": "^8.6.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsonc": "^2.5.0", + "eslint-plugin-n": "^15.6.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-standard": "^5.0.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-unicorn": "^45.0.2", "eslint-plugin-vue": "^9.8.0", "eslint-plugin-vuejs-accessibility": "^2.0.0", "html-webpack-plugin": "^5.3.2", @@ -106,10 +115,16 @@ "lefthook": "^1.2.4", "mini-css-extract-plugin": "^2.7.2", "npm-run-all": "^4.1.5", + "postcss": "^8.4.20", + "postcss-scss": "^4.0.6", "prettier": "^2.8.1", "rimraf": "^3.0.2", "sass": "^1.57.1", "sass-loader": "^13.2.0", + "stylelint": "^14.16.1", + "stylelint-config-sass-guidelines": "^9.0.1", + "stylelint-config-standard": "^29.0.0", + "stylelint-high-performance-animation": "^1.7.0", "tree-kill": "1.2.2", "vue-devtools": "^5.1.4", "vue-eslint-parser": "^9.1.0", diff --git a/src/main/ImageCache.js b/src/main/ImageCache.js index 4a635b7d92e9f..8228f38dc63ed 100644 --- a/src/main/ImageCache.js +++ b/src/main/ImageCache.js @@ -51,7 +51,7 @@ export class ImageCache { * @returns a timestamp in seconds */ export function extractExpiryTimestamp(headers) { - const maxAgeRegex = /max-age=([0-9]+)/ + const maxAgeRegex = /max-age=(\d+)/ const cacheControl = headers['cache-control'] if (cacheControl && maxAgeRegex.test(cacheControl)) { diff --git a/src/main/index.js b/src/main/index.js index c6ca1a703400e..b911fef6f67a5 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -283,7 +283,7 @@ function runApp() { session.defaultSession.webRequest.onBeforeSendHeaders(innertubeRequestFilter, ({ requestHeaders }, callback) => { requestHeaders.referer = 'https://www.youtube.com' - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ requestHeaders }) }) @@ -298,7 +298,7 @@ function runApp() { if (imageCache.has(url)) { const cached = imageCache.get(url) - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ mimeType: cached.mimeType, data: cached.data @@ -336,7 +336,7 @@ function runApp() { imageCache.add(url, mimeType, data, expiryTimestamp) - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ mimeType, data: data @@ -364,7 +364,7 @@ function runApp() { return value }) - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ statusCode: response.statusCode ?? 400, mimeType: 'application/json', @@ -385,12 +385,12 @@ function runApp() { // the requests made by the imagecache:// handler to fetch the image, // are allowed through, as their resourceType is 'other' if (details.resourceType === 'image') { - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ redirectURL: `imagecache://${encodeURIComponent(details.url)}` }) } else { - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({}) } }) diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js index ceac3eea40af6..14b7ad9829712 100644 --- a/src/renderer/components/data-settings/data-settings.js +++ b/src/renderer/components/data-settings/data-settings.js @@ -228,7 +228,7 @@ export default Vue.extend({ let count = 0 const ytsubs = youtubeSubscriptions.slice(1).map(yt => { - const splitCSVRegex = /(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^",\n]*|(?:\n|$))/g + const splitCSVRegex = /(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^\n",]*|(?:\n|$))/g return [...yt.matchAll(splitCSVRegex)].map(s => { let newVal = s[1] if (newVal.startsWith('"')) { @@ -623,11 +623,11 @@ export default Vue.extend({ this.profileList[0].subscriptions.forEach((channel) => { const escapedName = channel.name - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll('\'', ''') const channelOpmlString = `` opmlData += channelOpmlString diff --git a/src/renderer/components/download-settings/download-settings.sass b/src/renderer/components/download-settings/download-settings.sass deleted file mode 100644 index 5d475ff8871e4..0000000000000 --- a/src/renderer/components/download-settings/download-settings.sass +++ /dev/null @@ -1,2 +0,0 @@ -.folderDisplay - width: 50vh diff --git a/src/renderer/components/download-settings/download-settings.scss b/src/renderer/components/download-settings/download-settings.scss new file mode 100644 index 0000000000000..0c5ccdad0b5ac --- /dev/null +++ b/src/renderer/components/download-settings/download-settings.scss @@ -0,0 +1,3 @@ +.folderDisplay { + width: 50vh; +} diff --git a/src/renderer/components/download-settings/download-settings.vue b/src/renderer/components/download-settings/download-settings.vue index 1620a2134e23f..b4cd9dc1167e9 100644 --- a/src/renderer/components/download-settings/download-settings.vue +++ b/src/renderer/components/download-settings/download-settings.vue @@ -44,4 +44,4 @@