1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #5320 from jellyfin/renovate/major-linters

This commit is contained in:
Bill Thornton 2025-02-20 13:47:01 -05:00 committed by GitHub
commit c516534ce3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1765 additions and 920 deletions

View file

@ -1,5 +0,0 @@
node_modules
coverage
dist
.idea
.vscode

View file

@ -1,325 +0,0 @@
const restrictedGlobals = require('confusing-browser-globals');
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@stylistic',
'@typescript-eslint',
'react',
'import',
'sonarjs'
],
env: {
node: true,
es6: true,
es2017: true,
es2020: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:import/errors',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:compat/recommended',
'plugin:sonarjs/recommended'
],
rules: {
'array-callback-return': ['error', { 'checkForEach': true }],
'curly': ['error', 'multi-line', 'consistent'],
'default-case-last': ['error'],
'max-params': ['error', 7],
'new-cap': [
'error',
{
'capIsNewExceptions': ['jQuery.Deferred'],
'newIsCapExceptionPattern': '\\.default$'
}
],
'no-duplicate-imports': ['error'],
'no-empty-function': ['error'],
'no-extend-native': ['error'],
'no-lonely-if': ['error'],
'no-nested-ternary': ['error'],
'no-redeclare': ['off'],
'@typescript-eslint/no-redeclare': ['error', { builtinGlobals: false }],
'no-restricted-globals': ['error'].concat(restrictedGlobals),
'no-return-assign': ['error'],
'no-return-await': ['error'],
'no-sequences': ['error', { 'allowInParentheses': false }],
'no-shadow': ['off'],
'@typescript-eslint/no-shadow': ['error'],
'no-throw-literal': ['error'],
'no-undef-init': ['error'],
'no-unneeded-ternary': ['error'],
'no-unused-expressions': ['off'],
'@typescript-eslint/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
'no-unused-private-class-members': ['error'],
'no-useless-rename': ['error'],
'no-useless-constructor': ['off'],
'@typescript-eslint/no-useless-constructor': ['error'],
'no-var': ['error'],
'no-void': ['error', { 'allowAsStatement': true }],
'no-warning-comments': ['warn', { 'terms': ['fixme', 'hack', 'xxx'] }],
'one-var': ['error', 'never'],
'prefer-const': ['error', { 'destructuring': 'all' }],
'prefer-promise-reject-errors': ['warn', { 'allowEmptyReject': true }],
'@typescript-eslint/prefer-for-of': ['error'],
'radix': ['error'],
'yoda': 'error',
'react/jsx-filename-extension': ['error', { 'extensions': ['.jsx', '.tsx'] }],
'react/jsx-no-bind': ['error'],
'react/jsx-no-useless-fragment': ['error'],
'react/jsx-no-constructed-context-values': ['error'],
'react/no-array-index-key': ['error'],
'sonarjs/no-inverted-boolean-check': ['error'],
// TODO: Enable the following rules and fix issues
'sonarjs/cognitive-complexity': ['off'],
'sonarjs/no-duplicate-string': ['off'],
'@stylistic/block-spacing': ['error'],
'@stylistic/brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'@stylistic/comma-dangle': ['error', 'never'],
'@stylistic/comma-spacing': ['error'],
'@stylistic/eol-last': ['error'],
'@stylistic/indent': ['error', 4, { 'SwitchCase': 1 }],
'@stylistic/jsx-quotes': ['error', 'prefer-single'],
'@stylistic/keyword-spacing': ['error'],
'@stylistic/max-statements-per-line': ['error'],
'@stylistic/no-floating-decimal': ['error'],
'@stylistic/no-multi-spaces': ['error'],
'@stylistic/no-multiple-empty-lines': ['error', { 'max': 1 }],
'@stylistic/no-trailing-spaces': ['error'],
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/operator-linebreak': ['error', 'before', { overrides: { '?': 'after', ':': 'after', '=': 'after' } }],
'@stylistic/padded-blocks': ['error', 'never'],
'@stylistic/quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'@stylistic/semi': ['error'],
'@stylistic/space-before-blocks': ['error'],
'@stylistic/space-infix-ops': ['error']
},
settings: {
react: {
version: 'detect'
},
'import/parsers': {
'@typescript-eslint/parser': [ '.ts', '.tsx' ]
},
'import/resolver': {
node: {
extensions: [
'.js',
'.ts',
'.jsx',
'.tsx'
],
moduleDirectory: [
'node_modules',
'src'
]
}
},
polyfills: [
// Native Promises Only
'Promise',
// whatwg-fetch
'fetch',
// document-register-element
'document.registerElement',
// resize-observer-polyfill
'ResizeObserver',
// fast-text-encoding
'TextEncoder',
// intersection-observer
'IntersectionObserver',
// Core-js
'Object.assign',
'Object.is',
'Object.setPrototypeOf',
'Object.toString',
'Object.freeze',
'Object.seal',
'Object.preventExtensions',
'Object.isFrozen',
'Object.isSealed',
'Object.isExtensible',
'Object.getOwnPropertyDescriptor',
'Object.getPrototypeOf',
'Object.keys',
'Object.entries',
'Object.getOwnPropertyNames',
'Function.name',
'Function.hasInstance',
'Array.from',
'Array.arrayOf',
'Array.copyWithin',
'Array.fill',
'Array.find',
'Array.findIndex',
'Array.iterator',
'String.fromCodePoint',
'String.raw',
'String.iterator',
'String.codePointAt',
'String.endsWith',
'String.includes',
'String.repeat',
'String.startsWith',
'String.trim',
'String.anchor',
'String.big',
'String.blink',
'String.bold',
'String.fixed',
'String.fontcolor',
'String.fontsize',
'String.italics',
'String.link',
'String.small',
'String.strike',
'String.sub',
'String.sup',
'RegExp',
'Number',
'Math',
'Date',
'async',
'Symbol',
'Map',
'Set',
'WeakMap',
'WeakSet',
'ArrayBuffer',
'DataView',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'Reflect',
// Temporary while eslint-compat-plugin is buggy
'document.querySelector'
]
},
overrides: [
// Config files and development scripts
{
files: [
'./babel.config.js',
'./.eslintrc.js',
'./postcss.config.js',
'./webpack.*.js',
'./scripts/**/*.js'
]
},
// JavaScript source files
{
files: [
'./src/**/*.{js,jsx,ts,tsx}'
],
parserOptions: {
project: ['./tsconfig.json']
},
env: {
node: false,
browser: true,
es6: true,
es2017: true,
es2020: true
},
globals: {
// Tizen globals
'tizen': 'readonly',
'webapis': 'readonly',
// WebOS globals
'webOS': 'readonly',
// Dependency globals
'$': 'readonly',
'jQuery': 'readonly',
// Jellyfin globals
'ApiClient': 'writable',
'Events': 'writable',
'chrome': 'writable',
'Emby': 'readonly',
'Hls': 'writable',
'LibraryMenu': 'writable',
'Windows': 'readonly',
// Build time definitions
__COMMIT_SHA__: 'readonly',
__JF_BUILD_VERSION__: 'readonly',
__PACKAGE_JSON_NAME__: 'readonly',
__PACKAGE_JSON_VERSION__: 'readonly',
__USE_SYSTEM_FONTS__: 'readonly',
__WEBPACK_SERVE__: 'readonly'
},
rules: {
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'default',
format: [ 'camelCase', 'PascalCase' ],
leadingUnderscore: 'allow'
},
{
selector: 'variable',
format: [ 'camelCase', 'PascalCase', 'UPPER_CASE' ],
leadingUnderscore: 'allowSingleOrDouble',
trailingUnderscore: 'allowSingleOrDouble'
},
{
selector: 'typeLike',
format: [ 'PascalCase' ]
},
{
selector: 'enumMember',
format: [ 'PascalCase', 'UPPER_CASE' ]
},
{
selector: [ 'objectLiteralProperty', 'typeProperty' ],
format: [ 'camelCase', 'PascalCase' ],
leadingUnderscore: 'allowSingleOrDouble',
trailingUnderscore: 'allowSingleOrDouble'
},
// Ignore numbers, locale strings (en-us), aria/data attributes, CSS selectors,
// and api_key parameter
{
selector: [ 'objectLiteralProperty', 'typeProperty' ],
format: null,
filter: {
regex: '[ &\\-]|^([0-9]+)$|^api_key$',
match: true
}
}
],
'@typescript-eslint/prefer-string-starts-ends-with': ['error']
}
},
// TypeScript source files
{
files: [
'./src/**/*.{ts,tsx}'
],
extends: [
'eslint:recommended',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended'
],
rules: {
'@typescript-eslint/no-floating-promises': ['error'],
'@typescript-eslint/no-unused-vars': ['error'],
'sonarjs/cognitive-complexity': ['error']
}
}
]
};

385
eslint.config.mjs Normal file
View file

@ -0,0 +1,385 @@
// @ts-check
import eslint from '@eslint/js';
import comments from '@eslint-community/eslint-plugin-eslint-comments/configs';
import compat from 'eslint-plugin-compat';
import globals from 'globals';
// @ts-expect-error Missing type definition
import importPlugin from 'eslint-plugin-import';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import reactPlugin from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import restrictedGlobals from 'confusing-browser-globals';
import sonarjs from 'eslint-plugin-sonarjs';
import stylistic from '@stylistic/eslint-plugin';
// eslint-disable-next-line import/no-unresolved
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
// @ts-expect-error Harmless type mismatch in dependency
comments.recommended,
compat.configs['flat/recommended'],
importPlugin.flatConfigs.errors,
sonarjs.configs.recommended,
reactPlugin.configs.flat.recommended,
{
settings: {
react: {
version: 'detect'
}
}
},
jsxA11y.flatConfigs.recommended,
// Global ignores
{
ignores: [
'node_modules',
'coverage',
'dist',
'.idea',
'.vscode'
]
},
// Global style rules
{
plugins: {
'@stylistic': stylistic
},
extends: [ importPlugin.flatConfigs.typescript ],
rules: {
'array-callback-return': ['error', { 'checkForEach': true }],
'curly': ['error', 'multi-line', 'consistent'],
'default-case-last': 'error',
'max-params': ['error', 7],
'new-cap': [
'error',
{
'capIsNewExceptions': ['jQuery.Deferred'],
'newIsCapExceptionPattern': '\\.default$'
}
],
'no-duplicate-imports': 'error',
'no-empty-function': 'error',
'no-extend-native': 'error',
'no-lonely-if': 'error',
'no-nested-ternary': 'error',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': ['error', { builtinGlobals: false }],
'no-restricted-globals': ['error'].concat(restrictedGlobals),
'no-return-assign': 'error',
'no-return-await': 'error',
'no-sequences': ['error', { 'allowInParentheses': false }],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'no-throw-literal': 'error',
'no-undef-init': 'error',
'no-unneeded-ternary': 'error',
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
'no-unused-private-class-members': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'no-useless-rename': 'error',
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': 'error',
'no-var': 'error',
'no-void': ['error', { 'allowAsStatement': true }],
'no-warning-comments': ['warn', { 'terms': ['hack', 'xxx'] }],
'one-var': ['error', 'never'],
'prefer-const': ['error', { 'destructuring': 'all' }],
'prefer-promise-reject-errors': ['warn', { 'allowEmptyReject': true }],
'@typescript-eslint/prefer-for-of': 'error',
'radix': 'error',
'yoda': 'error',
'sonarjs/fixme-tag': 'warn',
'sonarjs/todo-tag': 'off',
'sonarjs/deprecation': 'warn',
'sonarjs/no-alphabetical-sort': 'warn',
'sonarjs/no-inverted-boolean-check': 'error',
'sonarjs/no-selector-parameter': 'off',
'sonarjs/pseudo-random': 'warn',
// TODO: Enable the following sonarjs rules and fix issues
'sonarjs/no-duplicate-string': 'off',
'sonarjs/no-nested-functions': 'warn',
// TODO: Replace with stylistic.configs.customize()
'@stylistic/block-spacing': 'error',
'@stylistic/brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'@stylistic/comma-dangle': ['error', 'never'],
'@stylistic/comma-spacing': 'error',
'@stylistic/eol-last': 'error',
'@stylistic/indent': ['error', 4, { 'SwitchCase': 1 }],
'@stylistic/jsx-quotes': ['error', 'prefer-single'],
'@stylistic/keyword-spacing': 'error',
'@stylistic/max-statements-per-line': 'error',
'@stylistic/no-floating-decimal': 'error',
'@stylistic/no-mixed-spaces-and-tabs': 'error',
'@stylistic/no-multi-spaces': 'error',
'@stylistic/no-multiple-empty-lines': ['error', { 'max': 1 }],
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/operator-linebreak': ['error', 'before', { overrides: { '?': 'after', ':': 'after', '=': 'after' } }],
'@stylistic/padded-blocks': ['error', 'never'],
'@stylistic/quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'@stylistic/semi': 'error',
'@stylistic/space-before-blocks': 'error',
'@stylistic/space-infix-ops': 'error'
}
},
// Config files use node globals
{
ignores: [ 'src' ],
languageOptions: {
globals: {
...globals.node
}
}
},
// Config files are commonjs by default
{
files: [ '**/*.{cjs,js}' ],
ignores: [ 'src' ],
languageOptions: {
sourceType: 'commonjs'
},
rules: {
'@typescript-eslint/no-require-imports': 'off'
}
},
// App files
{
files: [
'src/**/*.{js,jsx,ts,tsx}'
],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
},
globals: {
...globals.browser,
// Tizen globals
'tizen': false,
'webapis': false,
// WebOS globals
'webOS': false,
// Dependency globals
'$': false,
'jQuery': false,
// Jellyfin globals
'ApiClient': true,
'Events': true,
'chrome': true,
'Emby': false,
'Hls': true,
'LibraryMenu': true,
'Windows': false,
// Build time definitions
__COMMIT_SHA__: false,
__JF_BUILD_VERSION__: false,
__PACKAGE_JSON_NAME__: false,
__PACKAGE_JSON_VERSION__: false,
__USE_SYSTEM_FONTS__: false,
__WEBPACK_SERVE__: false
}
},
settings: {
'import/resolver': {
node: {
extensions: [
'.js',
'.ts',
'.jsx',
'.tsx'
],
moduleDirectory: [
'node_modules',
'src'
]
}
},
polyfills: [
'Promise',
// whatwg-fetch
'fetch',
// document-register-element
'document.registerElement',
// resize-observer-polyfill
'ResizeObserver',
// fast-text-encoding
'TextEncoder',
// intersection-observer
'IntersectionObserver',
// Core-js
'Object.assign',
'Object.is',
'Object.setPrototypeOf',
'Object.toString',
'Object.freeze',
'Object.seal',
'Object.preventExtensions',
'Object.isFrozen',
'Object.isSealed',
'Object.isExtensible',
'Object.getOwnPropertyDescriptor',
'Object.getPrototypeOf',
'Object.keys',
'Object.entries',
'Object.getOwnPropertyNames',
'Function.name',
'Function.hasInstance',
'Array.from',
'Array.arrayOf',
'Array.copyWithin',
'Array.fill',
'Array.find',
'Array.findIndex',
'Array.iterator',
'String.fromCodePoint',
'String.raw',
'String.iterator',
'String.codePointAt',
'String.endsWith',
'String.includes',
'String.repeat',
'String.startsWith',
'String.trim',
'String.anchor',
'String.big',
'String.blink',
'String.bold',
'String.fixed',
'String.fontcolor',
'String.fontsize',
'String.italics',
'String.link',
'String.small',
'String.strike',
'String.sub',
'String.sup',
'RegExp',
'Number',
'Math',
'Date',
'async',
'Symbol',
'Map',
'Set',
'WeakMap',
'WeakSet',
'ArrayBuffer',
'DataView',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'Reflect'
]
},
rules: {
// TODO: Add typescript recommended typed rules
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'default',
format: [ 'camelCase', 'PascalCase' ],
leadingUnderscore: 'allow'
},
{
selector: 'variable',
format: [ 'camelCase', 'PascalCase', 'UPPER_CASE' ],
leadingUnderscore: 'allowSingleOrDouble',
trailingUnderscore: 'allowSingleOrDouble'
},
{
selector: 'typeLike',
format: [ 'PascalCase' ]
},
{
selector: 'enumMember',
format: [ 'PascalCase', 'UPPER_CASE' ]
},
{
selector: [ 'objectLiteralProperty', 'typeProperty' ],
format: [ 'camelCase', 'PascalCase' ],
leadingUnderscore: 'allowSingleOrDouble',
trailingUnderscore: 'allowSingleOrDouble'
},
// Ignore numbers, locale strings (en-us), aria/data attributes, CSS selectors,
// and api_key parameter
{
selector: [ 'objectLiteralProperty', 'typeProperty' ],
format: null,
filter: {
regex: '[ &\\-]|^([0-9]+)$|^api_key$',
match: true
}
}
],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/prefer-string-starts-ends-with': 'error'
}
},
// React files
{
files: [ 'src/**/*.{jsx,tsx}' ],
plugins: {
'react-hooks': reactHooks
},
rules: {
'react/jsx-filename-extension': ['error', { 'extensions': ['.jsx', '.tsx'] }],
'react/jsx-no-bind': 'error',
'react/jsx-no-useless-fragment': 'error',
'react/no-array-index-key': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
}
},
// Service worker
{
files: [ 'src/serviceworker.js' ],
languageOptions: {
globals: {
...globals.serviceworker
}
}
},
// Legacy JS (less strict)
{
files: [ 'src/**/*.{js,jsx}' ],
rules: {
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-this-alias': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'sonarjs/public-static-readonly': 'off',
// TODO: Enable the following rules and fix issues
'sonarjs/cognitive-complexity': 'off',
'sonarjs/constructor-for-side-effects': 'off',
'sonarjs/function-return-type': 'off',
'sonarjs/no-async-constructor': 'off',
'sonarjs/no-duplicate-string': 'off',
'sonarjs/no-ignored-exceptions': 'off',
'sonarjs/no-invariant-returns': 'warn',
'sonarjs/no-nested-functions': 'off',
'sonarjs/void-use': 'off'
}
}
);

1878
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,8 @@
"@babel/preset-env": "7.25.8", "@babel/preset-env": "7.25.8",
"@babel/preset-react": "7.25.7", "@babel/preset-react": "7.25.7",
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
"@stylistic/eslint-plugin": "2.13.0", "@eslint/js": "9.20.0",
"@stylistic/eslint-plugin": "3.1.0",
"@stylistic/stylelint-plugin": "3.1.1", "@stylistic/stylelint-plugin": "3.1.1",
"@types/dompurify": "3.0.5", "@types/dompurify": "3.0.5",
"@types/escape-html": "1.0.4", "@types/escape-html": "1.0.4",
@ -21,8 +22,7 @@
"@types/react-dom": "18.3.1", "@types/react-dom": "18.3.1",
"@types/react-lazy-load-image-component": "1.6.4", "@types/react-lazy-load-image-component": "1.6.4",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "8.21.0", "@typescript-eslint/parser": "8.24.1",
"@typescript-eslint/parser": "8.21.0",
"@uupaa/dynamic-import-polyfill": "1.0.2", "@uupaa/dynamic-import-polyfill": "1.0.2",
"@vitest/coverage-v8": "3.0.4", "@vitest/coverage-v8": "3.0.4",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
@ -34,15 +34,16 @@
"css-loader": "7.1.2", "css-loader": "7.1.2",
"cssnano": "7.0.6", "cssnano": "7.0.6",
"es-check": "7.2.1", "es-check": "7.2.1",
"eslint": "8.57.1", "eslint": "9.20.1",
"eslint-plugin-compat": "4.2.0", "eslint-plugin-compat": "6.0.2",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-react": "7.37.4", "eslint-plugin-react": "7.37.4",
"eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-sonarjs": "0.25.1", "eslint-plugin-sonarjs": "3.0.2",
"expose-loader": "5.0.0", "expose-loader": "5.0.0",
"fork-ts-checker-webpack-plugin": "9.0.2", "fork-ts-checker-webpack-plugin": "9.0.2",
"globals": "15.15.0",
"html-loader": "5.1.0", "html-loader": "5.1.0",
"html-webpack-plugin": "5.6.3", "html-webpack-plugin": "5.6.3",
"jsdom": "25.0.1", "jsdom": "25.0.1",
@ -63,6 +64,7 @@
"stylelint-scss": "6.10.1", "stylelint-scss": "6.10.1",
"ts-loader": "9.5.2", "ts-loader": "9.5.2",
"typescript": "5.7.3", "typescript": "5.7.3",
"typescript-eslint": "8.24.1",
"vitest": "3.0.4", "vitest": "3.0.4",
"webpack": "5.97.1", "webpack": "5.97.1",
"webpack-bundle-analyzer": "4.10.2", "webpack-bundle-analyzer": "4.10.2",

View file

@ -17,6 +17,7 @@ const GenresItemsContainer: FC<GenresItemsContainerProps> = ({
parentId, parentId,
collectionType, collectionType,
itemType itemType
// eslint-disable-next-line sonarjs/function-return-type
}) => { }) => {
const { isLoading, data: genresResult } = useGetGenres(itemType, parentId); const { isLoading, data: genresResult } = useGetGenres(itemType, parentId);

View file

@ -6,6 +6,7 @@ import SectionContainer from 'components/common/SectionContainer';
import { CardShape } from 'utils/card'; import { CardShape } from 'utils/card';
import type { LibraryViewProps } from 'types/library'; import type { LibraryViewProps } from 'types/library';
// eslint-disable-next-line sonarjs/function-return-type
const UpcomingView: FC<LibraryViewProps> = ({ parentId }) => { const UpcomingView: FC<LibraryViewProps> = ({ parentId }) => {
const { isLoading, data: groupsUpcomingEpisodes } = const { isLoading, data: groupsUpcomingEpisodes } =
useGetGroupsUpcomingEpisodes(parentId); useGetGroupsUpcomingEpisodes(parentId);

View file

@ -17,6 +17,7 @@ export interface MovedItem {
playlistItemId: string playlistItemId: string
} }
// eslint-disable-next-line sonarjs/redundant-type-aliases
export type PlayerErrorCode = string; export type PlayerErrorCode = string;
export interface PlayerStopInfo { export interface PlayerStopInfo {

View file

@ -16,6 +16,7 @@ const MarkdownBox: FC<MarkdownBoxProps> = ({
<Box <Box
dangerouslySetInnerHTML={ dangerouslySetInnerHTML={
markdown ? markdown ?
// eslint-disable-next-line sonarjs/disabled-auto-escaping
{ __html: DOMPurify.sanitize(markdownIt({ html: true }).render(markdown)) } : { __html: DOMPurify.sanitize(markdownIt({ html: true }).render(markdown)) } :
undefined undefined
} }

View file

@ -9,6 +9,7 @@ interface CardTextProps {
const CardText: FC<CardTextProps> = ({ className, textLine }) => { const CardText: FC<CardTextProps> = ({ className, textLine }) => {
const { title, titleAction } = textLine; const { title, titleAction } = textLine;
// eslint-disable-next-line sonarjs/function-return-type
const renderCardText = () => { const renderCardText = () => {
if (titleAction) { if (titleAction) {
return ( return (

View file

@ -323,7 +323,7 @@ function shouldShowMediaTitle(
} }
function shouldShowExtraType(itemExtraType: NullableString) { function shouldShowExtraType(itemExtraType: NullableString) {
return itemExtraType && itemExtraType !== 'Unknown'; return !!(itemExtraType && itemExtraType !== 'Unknown');
} }
function shouldShowSeriesYearOrYear( function shouldShowSeriesYearOrYear(
@ -351,7 +351,7 @@ function shouldShowPersonRoleOrType(
showPersonRoleOrType: boolean | undefined, showPersonRoleOrType: boolean | undefined,
item: ItemDto item: ItemDto
) { ) {
return showPersonRoleOrType && (item as BaseItemPerson).Role; return !!(showPersonRoleOrType && (item as BaseItemPerson).Role);
} }
function shouldShowParentTitle( function shouldShowParentTitle(

View file

@ -195,6 +195,7 @@ function buildCardsHtmlInternal(items, options) {
if (isVertical) { if (isVertical) {
html += '</div>'; html += '</div>';
} }
// eslint-disable-next-line sonarjs/no-dead-store
hasOpenSection = false; hasOpenSection = false;
} }
@ -215,6 +216,7 @@ function buildCardsHtmlInternal(items, options) {
if (options.rows && itemsInRow === 0) { if (options.rows && itemsInRow === 0) {
if (hasOpenRow) { if (hasOpenRow) {
html += '</div>'; html += '</div>';
// eslint-disable-next-line sonarjs/no-dead-store
hasOpenRow = false; hasOpenRow = false;
} }

View file

@ -4,7 +4,7 @@ import globalize from '../../../lib/globalize';
import IconButtonElement from '../../../elements/IconButtonElement'; import IconButtonElement from '../../../elements/IconButtonElement';
type AccessScheduleListProps = { type AccessScheduleListProps = {
index: number; index?: number;
DayOfWeek?: string; DayOfWeek?: string;
StartHour?: number ; StartHour?: number ;
EndHour?: number; EndHour?: number;

View file

@ -3,7 +3,6 @@ import globalize from 'lib/globalize';
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
import { getParameterByName } from 'utils/url'; import { getParameterByName } from 'utils/url';
import { appHost } from './apphost';
import cardBuilder from './cardbuilder/cardBuilder'; import cardBuilder from './cardbuilder/cardBuilder';
import imageLoader from './images/imageLoader'; import imageLoader from './images/imageLoader';
import layoutManager from './layoutManager'; import layoutManager from './layoutManager';
@ -160,8 +159,10 @@ function loadSection(elem, userId, topParentId, section, isSingleSection) {
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">'; html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">';
} }
let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; // NOTE: Why is card layout always disabled?
cardLayout = false; // let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
const cardLayout = false;
html += cardBuilder.getCardsHtml(result.Items, { html += cardBuilder.getCardsHtml(result.Items, {
preferThumb: section.preferThumb, preferThumb: section.preferThumb,
shape: section.shape, shape: section.shape,

View file

@ -74,6 +74,7 @@ function fetchWithTimeout(url, options, timeoutMs) {
*/ */
function paramsToString(params) { function paramsToString(params) {
return Object.entries(params) return Object.entries(params)
// eslint-disable-next-line sonarjs/different-types-comparison
.filter(([, v]) => v !== null && v !== undefined && v !== '') .filter(([, v]) => v !== null && v !== undefined && v !== '')
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&'); .join('&');

View file

@ -389,6 +389,7 @@ function intersectsInternal(a1, a2, b1, b2) {
} }
function intersects(a1, a2, b1, b2) { function intersects(a1, a2, b1, b2) {
// eslint-disable-next-line sonarjs/arguments-order
return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2); return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2);
} }

View file

@ -80,7 +80,7 @@ export function handleHlsJsMediaError(instance, reject) {
let now = Date.now(); let now = Date.now();
if (window.performance?.now) { if (window.performance?.now) {
now = performance.now(); // eslint-disable-line compat/compat now = performance.now();
} }
if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) {
@ -373,6 +373,7 @@ export function getBufferedRanges(instance, elem) {
start = 0; start = 0;
} }
if (!isValidDuration(end)) { if (!isValidDuration(end)) {
// eslint-disable-next-line sonarjs/no-dead-store
end = 0; end = 0;
continue; continue;
} }

View file

@ -417,6 +417,7 @@ export function getListViewHtml(options) {
} }
if (enableOverview && item.Overview) { if (enableOverview && item.Overview) {
// eslint-disable-next-line sonarjs/disabled-auto-escaping
const overview = DOMPurify.sanitize(markdownIt({ html: true }).render(item.Overview || '')); const overview = DOMPurify.sanitize(markdownIt({ html: true }).render(item.Overview || ''));
html += '<div class="secondary listItem-overview listItemBodyText">'; html += '<div class="secondary listItem-overview listItemBodyText">';
html += '<bdi>' + overview + '</bdi>'; html += '<bdi>' + overview + '</bdi>';

View file

@ -13,6 +13,7 @@ interface MediaInfoItemProps {
const MediaInfoItem: FC<MediaInfoItemProps> = ({ className, miscInfo }) => { const MediaInfoItem: FC<MediaInfoItemProps> = ({ className, miscInfo }) => {
const { text, textAction, cssClass, type } = miscInfo; const { text, textAction, cssClass, type } = miscInfo;
// eslint-disable-next-line sonarjs/function-return-type
const renderText = () => { const renderText = () => {
if (textAction) { if (textAction) {
return ( return (

View file

@ -208,6 +208,7 @@ function getMimeType(type, container) {
} }
function getParam(name, url) { function getParam(name, url) {
// eslint-disable-next-line sonarjs/single-char-in-character-classes
name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]'); name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
const regexS = '[\\?&]' + name + '=([^&#]*)'; const regexS = '[\\?&]' + name + '=([^&#]*)';
const regex = new RegExp(regexS, 'i'); const regex = new RegExp(regexS, 'i');
@ -2115,6 +2116,7 @@ export class PlaybackManager {
if (!state) { if (!state) {
playerStates[player.name] = {}; playerStates[player.name] = {};
// eslint-disable-next-line sonarjs/no-dead-store
state = playerStates[player.name]; state = playerStates[player.name];
} }

View file

@ -90,6 +90,7 @@ class SkipSegment extends PlaybackSubscriber {
elem.classList.remove('no-transition'); elem.classList.remove('no-transition');
} }
// eslint-disable-next-line sonarjs/void-use
void elem.offsetWidth; void elem.offsetWidth;
const hasFocus = document.activeElement && focusManager.isCurrentlyFocusable(document.activeElement); const hasFocus = document.activeElement && focusManager.isCurrentlyFocusable(document.activeElement);
@ -111,6 +112,7 @@ class SkipSegment extends PlaybackSubscriber {
const elem = this.skipElement; const elem = this.skipElement;
if (elem) { if (elem) {
elem.classList.remove('no-transition'); elem.classList.remove('no-transition');
// eslint-disable-next-line sonarjs/void-use
void elem.offsetWidth; void elem.offsetWidth;
requestAnimationFrame(() => { requestAnimationFrame(() => {

View file

@ -1,11 +1,14 @@
// TODO: Check if needed and move to external dependency // TODO: Check if needed and move to external dependency
// From https://github.com/parshap/node-sanitize-filename // From https://github.com/parshap/node-sanitize-filename
// eslint-disable-next-line sonarjs/duplicates-in-character-class
const illegalRe = /[/?<>\\:*|":]/g; const illegalRe = /[/?<>\\:*|":]/g;
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex, sonarjs/no-control-regex
const controlRe = /[\x00-\x1f\x80-\x9f]/g; const controlRe = /[\x00-\x1f\x80-\x9f]/g;
const reservedRe = /^\.+$/; const reservedRe = /^\.+$/;
// eslint-disable-next-line sonarjs/concise-regex
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
// eslint-disable-next-line sonarjs/slow-regex
const windowsTrailingRe = /[. ]+$/; const windowsTrailingRe = /[. ]+$/;
function isHighSurrogate(codePoint) { function isHighSurrogate(codePoint) {
@ -64,6 +67,7 @@ function truncate(string, byteLength) {
segment = string[i]; segment = string[i];
if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) { if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
// eslint-disable-next-line sonarjs/updated-loop-counter
i += 1; i += 1;
segment += string[i]; segment += string[i];
} }

View file

@ -1,4 +1,3 @@
import { appHost } from 'components/apphost';
import cardBuilder from 'components/cardbuilder/cardBuilder'; import cardBuilder from 'components/cardbuilder/cardBuilder';
import focusManager from 'components/focusManager'; import focusManager from 'components/focusManager';
import layoutManager from 'components/layoutManager'; import layoutManager from 'components/layoutManager';
@ -202,8 +201,9 @@ function getRouteUrl(section, serverId) {
function getItemsHtmlFn(section) { function getItemsHtmlFn(section) {
return function (items) { return function (items) {
let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; // NOTE: Why is card layout always disabled?
cardLayout = false; // let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
const cardLayout = false;
const serverId = this.apiClient.serverId(); const serverId = this.apiClient.serverId();
const leadingButtons = layoutManager.tv ? [{ const leadingButtons = layoutManager.tv ? [{
name: globalize.translate('All'), name: globalize.translate('All'),

View file

@ -877,6 +877,7 @@ function renderOverview(page, item) {
const overviewElements = page.querySelectorAll('.overview'); const overviewElements = page.querySelectorAll('.overview');
if (overviewElements.length > 0) { if (overviewElements.length > 0) {
// eslint-disable-next-line sonarjs/disabled-auto-escaping
const overview = DOMPurify.sanitize(markdownIt({ html: true }).render(item.Overview || '')); const overview = DOMPurify.sanitize(markdownIt({ html: true }).render(item.Overview || ''));
if (overview) { if (overview) {
@ -1378,6 +1379,7 @@ function renderChildren(page, item) {
if (item.Type == 'MusicAlbum') { if (item.Type == 'MusicAlbum') {
let showArtist = false; let showArtist = false;
for (const track of result.Items) { for (const track of result.Items) {
// eslint-disable-next-line sonarjs/no-alphabetical-sort
if (!isEqual(track.ArtistItems.map(x => x.Id).sort(), track.AlbumArtists.map(x => x.Id).sort())) { if (!isEqual(track.ArtistItems.map(x => x.Id).sort(), track.AlbumArtists.map(x => x.Id).sort())) {
showArtist = true; showArtist = true;
break; break;

View file

@ -36,6 +36,7 @@ function handleConnectionResult(page, result) {
function submitServer(page) { function submitServer(page) {
loading.show(); loading.show();
// eslint-disable-next-line sonarjs/slow-regex
const host = page.querySelector('#txtServerHost').value.replace(/\/+$/, ''); const host = page.querySelector('#txtServerHost').value.replace(/\/+$/, '');
ServerConnections.connectToAddress(host, { ServerConnections.connectToAddress(host, {
enableAutoLogin: appSettings.enableAutoLogin() enableAutoLogin: appSettings.enableAutoLogin()

View file

@ -292,6 +292,7 @@ export default function (view, params) {
apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) { apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) {
const loginDisclaimer = view.querySelector('.loginDisclaimer'); const loginDisclaimer = view.querySelector('.loginDisclaimer');
// eslint-disable-next-line sonarjs/disabled-auto-escaping
loginDisclaimer.innerHTML = DOMPurify.sanitize(markdownIt({ html: true }).render(options.LoginDisclaimer || '')); loginDisclaimer.innerHTML = DOMPurify.sanitize(markdownIt({ html: true }).render(options.LoginDisclaimer || ''));
for (const elem of loginDisclaimer.querySelectorAll('a')) { for (const elem of loginDisclaimer.querySelectorAll('a')) {

View file

@ -13,7 +13,8 @@ function slideDownToShow(button, elem) {
elem.style.height = '0'; elem.style.height = '0';
// trigger reflow // trigger reflow
// TODO: Find a better way to do this // TODO: Find a better way to do this
const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, sonarjs/no-unused-vars, sonarjs/no-dead-store
const newHeight = elem.offsetHeight;
elem.style.height = height; elem.style.height = height;
setTimeout(function () { setTimeout(function () {
@ -35,7 +36,8 @@ function slideUpToHide(button, elem) {
elem.style.height = elem.offsetHeight + 'px'; elem.style.height = elem.offsetHeight + 'px';
// trigger reflow // trigger reflow
// TODO: Find a better way to do this // TODO: Find a better way to do this
const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, sonarjs/no-unused-vars, sonarjs/no-dead-store
const newHeight = elem.offsetHeight;
elem.classList.remove('expanded'); elem.classList.remove('expanded');
elem.style.height = '0'; elem.style.height = '0';

View file

@ -161,6 +161,7 @@ function updateValues(isValueSet) {
if (!!isValueSet && !supportsValueAutoSnap) { if (!!isValueSet && !supportsValueAutoSnap) {
const value = snapValue(this, parseFloat(this.value)).toString(); const value = snapValue(this, parseFloat(this.value)).toString();
// eslint-disable-next-line sonarjs/different-types-comparison
if (this.value !== value) { if (this.value !== value) {
this.value = value; this.value = value;

View file

@ -11,6 +11,7 @@
const realPlay = HTMLMediaElementPrototype.play; const realPlay = HTMLMediaElementPrototype.play;
HTMLMediaElementPrototype.play = function () { HTMLMediaElementPrototype.play = function () {
// eslint-disable-next-line sonarjs/no-try-promise
try { try {
const promise = realPlay.apply(this, arguments); const promise = realPlay.apply(this, arguments);

View file

@ -24,6 +24,7 @@ function type(value) {
} }
if (typeof value === 'object' || typeof value === 'function') { if (typeof value === 'object' || typeof value === 'function') {
// eslint-disable-next-line sonarjs/prefer-regexp-exec
return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
} }

View file

@ -86,7 +86,7 @@ function iOSversion() {
/Version\/(\d+)/ /Version\/(\d+)/
]; ];
for (const test of tests) { for (const test of tests) {
const matches = (navigator.appVersion).match(test); const matches = RegExp(test).exec(navigator.appVersion);
if (matches) { if (matches) {
return [ return [
parseInt(matches[1], 10), parseInt(matches[1], 10),
@ -163,6 +163,7 @@ function supportsCssAnimation(allowPrefix) {
const domPrefixes = ['Webkit', 'O', 'Moz']; const domPrefixes = ['Webkit', 'O', 'Moz'];
const elm = document.createElement('div'); const elm = document.createElement('div');
// eslint-disable-next-line sonarjs/different-types-comparison
if (elm.style.animationName !== undefined) { if (elm.style.animationName !== undefined) {
animation = true; animation = true;
} }
@ -298,7 +299,7 @@ if (browser.web0s) {
delete browser.chrome; delete browser.chrome;
delete browser.safari; delete browser.safari;
} else if (browser.tizen) { } else if (browser.tizen) {
const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/); const v = RegExp(/Tizen (\d+).(\d+)/).exec(navigator.appVersion);
browser.tizenVersion = parseInt(v[1], 10); browser.tizenVersion = parseInt(v[1], 10);
// UserAgent string contains 'Chrome' and 'Safari', but we only want 'tizen' to be true // UserAgent string contains 'Chrome' and 'Safari', but we only want 'tizen' to be true
@ -319,7 +320,6 @@ if (browser.mobile || browser.tv) {
browser.slow = true; browser.slow = true;
} }
/* eslint-disable-next-line compat/compat */
if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) { if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
browser.touch = true; browser.touch = true;
} }

View file

@ -230,7 +230,8 @@ function supportsVc1(videoTestElement) {
} }
function supportsHdr10(options) { function supportsHdr10(options) {
return options.supportsHdr10 ?? (false // eslint-disable-line sonarjs/no-redundant-boolean // eslint-disable-next-line no-constant-binary-expression, sonarjs/no-redundant-boolean
return options.supportsHdr10 ?? (false
|| browser.vidaa || browser.vidaa
|| browser.tizen || browser.tizen
|| browser.web0s || browser.web0s
@ -253,7 +254,8 @@ function supportsHlg(options) {
} }
function supportsDolbyVision(options) { function supportsDolbyVision(options) {
return options.supportsDolbyVision ?? (false // eslint-disable-line sonarjs/no-redundant-boolean // eslint-disable-next-line no-constant-binary-expression, sonarjs/no-redundant-boolean
return options.supportsDolbyVision ?? (false
|| browser.safari && ((browser.iOS && browser.iOSVersion >= 13) || browser.osx) || browser.safari && ((browser.iOS && browser.iOSVersion >= 13) || browser.osx)
); );
} }
@ -512,10 +514,8 @@ export default function (options) {
} }
} }
/* eslint-disable compat/compat */
let maxVideoWidth = browser.xboxOne ? window.screen?.width : null; let maxVideoWidth = browser.xboxOne ? window.screen?.width : null;
/* eslint-enable compat/compat */
if (options.maxVideoWidth) { if (options.maxVideoWidth) {
maxVideoWidth = options.maxVideoWidth; maxVideoWidth = options.maxVideoWidth;
} }

View file

@ -33,7 +33,7 @@ function textAreaCopy(text) {
} else { } else {
ret = Promise.reject(); ret = Promise.reject();
} }
} catch (_) { } catch {
ret = Promise.reject(); ret = Promise.reject();
} }
@ -48,11 +48,10 @@ function textAreaCopy(text) {
* @returns {Promise<void>} Promise. * @returns {Promise<void>} Promise.
*/ */
export function copy(text) { export function copy(text) {
/* eslint-disable-next-line compat/compat */ // eslint-disable-next-line sonarjs/different-types-comparison
if (navigator.clipboard === undefined) { if (navigator.clipboard === undefined) {
return textAreaCopy(text); return textAreaCopy(text);
} else { } else {
/* eslint-disable-next-line compat/compat */
return navigator.clipboard.writeText(text).catch(() => { return navigator.clipboard.writeText(text).catch(() => {
return textAreaCopy(text); return textAreaCopy(text);
}); });

View file

@ -23,6 +23,7 @@ const DEV_MODE = process.env.NODE_ENV !== 'production';
let COMMIT_SHA = ''; let COMMIT_SHA = '';
try { try {
COMMIT_SHA = require('child_process') COMMIT_SHA = require('child_process')
// eslint-disable-next-line sonarjs/no-os-command-from-path
.execSync('git describe --always --dirty') .execSync('git describe --always --dirty')
.toString() .toString()
.trim(); .trim();