diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index ac098ae699c..10d20acc8f7 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -28,7 +28,7 @@ jobs: - name: Oxfmt install: normal command: "lint:fmt" - - name: ESLint + - name: Oxlint install: normal command: "lint:js" - name: Style Lint diff --git a/.lintstagedrc b/.lintstagedrc index 1f28a28d5c9..1f902220671 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,3 +1,4 @@ { - "*": "oxfmt --no-error-on-unmatched-pattern" + "*": "oxfmt --no-error-on-unmatched-pattern", + "*.{js,jsx,ts,tsx,mjs,cjs}": "oxlint --no-error-on-unmatched-pattern" } diff --git a/apps/desktop/.eslintrc.cjs b/apps/desktop/.eslintrc.cjs deleted file mode 100644 index 7a1d06729c2..00000000000 --- a/apps/desktop/.eslintrc.cjs +++ /dev/null @@ -1,97 +0,0 @@ -module.exports = { - plugins: ["matrix-org", "n"], - extends: ["plugin:matrix-org/javascript"], - parserOptions: { - ecmaVersion: 2021, - project: ["tsconfig.json"], - }, - env: { - es6: true, - node: true, - // we also have some browser code (ie. the preload script) - browser: true, - }, - // NOTE: These rules are frozen and new rules should not be added here. - // New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/ - rules: { - "quotes": "off", - "indent": "off", - "prefer-promise-reject-errors": "off", - "no-async-promise-executor": "off", - - "n/file-extension-in-import": ["error", "always"], - "unicorn/prefer-node-protocol": ["error"], - }, - overrides: [ - { - files: ["src/**/*.ts"], - extends: ["plugin:matrix-org/typescript"], - rules: { - // Things we do that break the ideal style - "prefer-promise-reject-errors": "off", - "quotes": "off", - - "@typescript-eslint/no-explicit-any": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - }, - }, - { - files: ["hak/**/*.ts"], - extends: ["plugin:matrix-org/typescript"], - parserOptions: { - project: ["hak/tsconfig.json"], - }, - rules: { - // Things we do that break the ideal style - "prefer-promise-reject-errors": "off", - "quotes": "off", - "n/file-extension-in-import": "off", - - "@typescript-eslint/no-explicit-any": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - }, - }, - { - files: ["scripts/**/*.ts"], - extends: ["plugin:matrix-org/typescript"], - parserOptions: { - project: ["scripts/tsconfig.json"], - }, - rules: { - // Things we do that break the ideal style - "prefer-promise-reject-errors": "off", - "quotes": "off", - "n/file-extension-in-import": "off", - - "@typescript-eslint/no-explicit-any": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - }, - }, - { - files: ["playwright/**/*.ts"], - extends: ["plugin:matrix-org/typescript"], - parserOptions: { - project: ["playwright/tsconfig.json"], - }, - rules: { - // Things we do that break the ideal style - "prefer-promise-reject-errors": "off", - "quotes": "off", - - "@typescript-eslint/no-explicit-any": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - }, - }, - { - files: ["src/**/*.test.ts", "electron-builder.ts", "vitest.config.ts"], - extends: ["plugin:matrix-org/typescript"], - parserOptions: { - project: ["tsconfig.node.json"], - }, - }, - ], -}; diff --git a/apps/desktop/.lintstagedrc b/apps/desktop/.lintstagedrc deleted file mode 100644 index 8043a7f78cd..00000000000 --- a/apps/desktop/.lintstagedrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "*": "oxfmt --no-error-on-unmatched-pattern", - "*.(ts|tsx)": ["eslint --fix"] -} diff --git a/apps/desktop/babel.config.cjs b/apps/desktop/babel.config.cjs index 9545b5983d7..c603e3cf987 100644 --- a/apps/desktop/babel.config.cjs +++ b/apps/desktop/babel.config.cjs @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + module.exports = { presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"], }; diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 9bcd771a737..013991ce950 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + import * as os from "node:os"; import * as fs from "node:fs"; import * as path from "node:path"; diff --git a/apps/desktop/hak/tsconfig.json b/apps/desktop/hak/tsconfig.json index 2dab26fbe82..2a3a2f7bddd 100644 --- a/apps/desktop/hak/tsconfig.json +++ b/apps/desktop/hak/tsconfig.json @@ -9,7 +9,8 @@ "lib": ["es2022"], "types": ["node"], "allowImportingTsExtensions": true, - "skipLibCheck": true + "skipLibCheck": true, + "noEmit": true }, "include": ["../scripts/@types/*.d.ts", "./**/*.ts"] } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c76e382b445..d25dd81edf6 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -29,9 +29,7 @@ "fetch": "pnpm run mkdirs && node scripts/fetch-package.ts", "asar-webapp": "asar p webapp webapp.asar", "start": "nx start", - "lint": "pnpm lint:types && pnpm lint:js", - "lint:js": "eslint --max-warnings 0 src hak playwright scripts", - "lint:js-fix": "eslint --fix --max-warnings 0 src hak playwright scripts", + "lint": "pnpm lint:types", "lint:types": "pnpm lint:types:src && pnpm lint:types:node && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak", "lint:types:src": "tsc --noEmit", "lint:types:node": "tsc --noEmit -p tsconfig.node.json", @@ -75,14 +73,11 @@ "@electron/asar": "4.2.0", "@electron/fuses": "^2.1.1", "@playwright/test": "catalog:", - "@stylistic/eslint-plugin": "^5.0.0", "@types/auto-launch": "^5.0.1", "@types/counterpart": "^0.18.1", "@types/minimist": "^1.2.1", "@types/node": "catalog:", "@types/pacote": "^11.1.1", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", "app-builder-lib": "26.15.3", "chokidar": "^5.0.0", "detect-libc": "^2.0.0", @@ -90,12 +85,6 @@ "electron-builder": "26.15.3", "electron-builder-squirrel-windows": "26.15.3", "electron-devtools-installer": "^4.0.0", - "eslint": "^8.26.0", - "eslint-config-google": "^0.14.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-matrix-org": "^3.0.0", - "eslint-plugin-n": "^17.12.0", - "eslint-plugin-unicorn": "^56.0.0", "glob": "^13.0.0", "matrix-web-i18n": "catalog:", "memfs": "^4.57.2", @@ -112,8 +101,7 @@ }, "nx": { "includedScripts": [ - "lint:types", - "lint:js" + "lint:types" ] } } diff --git a/apps/desktop/scripts/copy-res.ts b/apps/desktop/scripts/copy-res.ts index 18ce877d11a..39b8169cdd5 100755 --- a/apps/desktop/scripts/copy-res.ts +++ b/apps/desktop/scripts/copy-res.ts @@ -1,5 +1,12 @@ #!/usr/bin/env node +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + // copies resources into the lib directory. import parseArgs from "minimist"; diff --git a/apps/desktop/scripts/fetch-package.ts b/apps/desktop/scripts/fetch-package.ts index 21876c2a690..c5eb76cf95a 100644 --- a/apps/desktop/scripts/fetch-package.ts +++ b/apps/desktop/scripts/fetch-package.ts @@ -1,4 +1,10 @@ #!/usr/bin/env node +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ import * as path from "node:path"; import { createWriteStream, promises as fs } from "node:fs"; diff --git a/apps/desktop/scripts/generate-nightly-version.ts b/apps/desktop/scripts/generate-nightly-version.ts index eef0ab997bb..aa8245d99e6 100755 --- a/apps/desktop/scripts/generate-nightly-version.ts +++ b/apps/desktop/scripts/generate-nightly-version.ts @@ -1,4 +1,10 @@ #!/usr/bin/env node +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ /** * Script to generate incremental Nightly build versions, based on the latest Nightly build version of that kind. diff --git a/apps/desktop/scripts/get-version.ts b/apps/desktop/scripts/get-version.ts index 2d28b3a24d2..90e2b4d9be9 100755 --- a/apps/desktop/scripts/get-version.ts +++ b/apps/desktop/scripts/get-version.ts @@ -1,4 +1,10 @@ #!/usr/bin/env node +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ /* * Checks for the presence of a webapp, inspects its version and prints it diff --git a/apps/desktop/scripts/set-version.ts b/apps/desktop/scripts/set-version.ts index 01e50278029..8f5041c1d3f 100755 --- a/apps/desktop/scripts/set-version.ts +++ b/apps/desktop/scripts/set-version.ts @@ -1,4 +1,10 @@ #!/usr/bin/env node +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ /* * Checks for the presence of a webapp, inspects its version and sets the diff --git a/apps/desktop/scripts/tsconfig.json b/apps/desktop/scripts/tsconfig.json index bff3b8ae81c..5b5a8b70963 100644 --- a/apps/desktop/scripts/tsconfig.json +++ b/apps/desktop/scripts/tsconfig.json @@ -10,7 +10,8 @@ "strict": true, "lib": ["es2022"], "types": ["node"], - "allowImportingTsExtensions": true + "allowImportingTsExtensions": true, + "noEmit": true }, "include": ["../src/@types", "./**/*.ts"] } diff --git a/apps/desktop/src/electron-main.ts b/apps/desktop/src/electron-main.ts index b3c89b717e8..261a2a390f2 100644 --- a/apps/desktop/src/electron-main.ts +++ b/apps/desktop/src/electron-main.ts @@ -385,11 +385,13 @@ app.on("ready", async () => { desktopCapturer .getSources({ types: ["screen", "window"] }) .then((sources) => { + // oxlint-disable-next-line promise/no-callback-in-promise callback({ video: sources[0] }); }) .catch((err) => { // If the user cancels the dialog an error occurs "Failed to get sources" console.error("Wayland: failed to get user-selected source:", err); + // oxlint-disable-next-line promise/no-callback-in-promise callback({ video: { id: "", name: "" } }); // The promise does not return if no dummy is passed here as source }); } else { diff --git a/apps/desktop/src/ipc.ts b/apps/desktop/src/ipc.ts index 047b2199e1b..229a525662b 100644 --- a/apps/desktop/src/ipc.ts +++ b/apps/desktop/src/ipc.ts @@ -144,7 +144,7 @@ ipcMain.on("ipcCall", async function (_ev: IpcMainEvent, payload) { })); break; case "callDisplayMediaCallback": - await getDisplayMediaCallback()?.({ video: args[0] }); + getDisplayMediaCallback()?.({ video: args[0] }); setDisplayMediaCallback(null); ret = null; break; diff --git a/apps/desktop/src/language-helper.ts b/apps/desktop/src/language-helper.ts index 23a859a902e..45d86215848 100644 --- a/apps/desktop/src/language-helper.ts +++ b/apps/desktop/src/language-helper.ts @@ -113,7 +113,7 @@ export class AppLocalization { locales = [locales]; } - const loadedLocales = locales.filter((locale) => { + const chosenLocale = locales.find((locale) => { const translations = this.fetchTranslationJson(locale); if (translations !== null) { counterpart.registerTranslations(locale, translations); @@ -121,7 +121,7 @@ export class AppLocalization { return !!translations; }); - counterpart.setLocale(loadedLocales[0]); + counterpart.setLocale(chosenLocale!); this.store.set(AppLocalization.STORE_KEY, locales); this.resetLocalizedUI(); diff --git a/apps/desktop/src/preload.cts b/apps/desktop/src/preload.cts index 18a9afe2756..8425b7b82e3 100644 --- a/apps/desktop/src/preload.cts +++ b/apps/desktop/src/preload.cts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. // This file is compiled to CommonJS rather than ESM otherwise the browser chokes on the import statement. -import { ipcRenderer, contextBridge, IpcRendererEvent } from "electron"; +import { ipcRenderer, contextBridge, type IpcRendererEvent } from "electron"; import type { ConfigOptions } from "./config.js" with { "resolution-mode": "import" }; // Expose only expected IPC wrapper APIs to the renderer process to avoid diff --git a/apps/web/.eslintignore b/apps/web/.eslintignore deleted file mode 100644 index 8c739b4d777..00000000000 --- a/apps/web/.eslintignore +++ /dev/null @@ -1,18 +0,0 @@ -src/vector/modernizr.cjs -test/end-to-end-tests/node_modules/ -test/end-to-end-tests/element/ -test/end-to-end-tests/synapse/ -test/end-to-end-tests/lib/ -# Legacy skinning file that some people might still have -src/component-index.js -# Auto-generated file -src/modules.ts -src/modules.js -# Test result files -/playwright/test-results/ -/playwright/html-report/ - -# Shared components generated files -/packages/shared-components/dist/ -/packages/shared-components/src/i18n/i18nKeys.d.ts -/packages/shared-components/typedoc/ diff --git a/apps/web/.eslintrc.cjs b/apps/web/.eslintrc.cjs deleted file mode 100644 index 097c3c8ebd8..00000000000 --- a/apps/web/.eslintrc.cjs +++ /dev/null @@ -1,349 +0,0 @@ -/* -Copyright 2025 Element Creations Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -module.exports = { - plugins: ["matrix-org", "eslint-plugin-react-compiler"], - extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"], - parserOptions: { - project: ["./tsconfig.json"], - tsconfigRootDir: __dirname, - }, - env: { - browser: true, - node: true, - }, - globals: { - LANGUAGES_FILE: "readonly", - }, - rules: { - // Things we do that break the ideal style - "no-constant-condition": "off", - "prefer-promise-reject-errors": "off", - "no-async-promise-executor": "off", - "no-extra-boolean-cast": "off", - - // Bind or arrow functions in props causes performance issues (but we - // currently use them in some places). - // It's disabled here, but we should using it sparingly. - "react/jsx-no-bind": "off", - "react/jsx-key": ["error"], - - "no-restricted-properties": [ - "error", - ...buildRestrictedPropertiesOptions( - ["window.innerHeight", "window.innerWidth", "window.visualViewport"], - "Use UIStore to access window dimensions instead.", - ), - ...buildRestrictedPropertiesOptions( - ["React.forwardRef", "*.forwardRef", "forwardRef"], - "Use ref props instead.", - ), - ...buildRestrictedPropertiesOptions( - ["*.mxcUrlToHttp", "*.getHttpUriForMxc"], - "Use Media helper instead to centralise access for customisation.", - ), - ...buildRestrictedPropertiesOptions(["window.setImmediate"], "Use setTimeout instead."), - ], - "no-restricted-globals": [ - "error", - { - name: "setImmediate", - message: "Use setTimeout instead.", - }, - { - name: "Buffer", - message: "Buffer is not available in the web.", - }, - ], - - "import/no-duplicates": ["error"], - // Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell. - // Ban compound-design-tokens raw svg imports in favour of their React component counterparts - "no-restricted-imports": [ - "error", - { - paths: [ - { - name: "react", - importNames: ["forwardRef"], - message: "Use ref props instead.", - }, - { - name: "@testing-library/react", - message: "Please use jest-matrix-react instead", - }, - { - name: "matrix-js-sdk", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/src", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/src/", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/src/index", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "emojibase-regex", - message: - "This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.", - }, - ], - patterns: [ - { - group: [ - "matrix-js-sdk/src/**", - "!matrix-js-sdk/src/matrix", - "!matrix-js-sdk/src/crypto-api", - "!matrix-js-sdk/src/types", - "!matrix-js-sdk/src/testing", - "!matrix-js-sdk/src/utils/**", - "matrix-js-sdk/src/utils/internal/**", - "matrix-js-sdk/lib", - "matrix-js-sdk/lib/", - "matrix-js-sdk/lib/**", - // XXX: Temporarily allow these as they are not available via the main export - "!matrix-js-sdk/src/logger", - "!matrix-js-sdk/src/errors", - "!matrix-js-sdk/src/utils", - "!matrix-js-sdk/src/version-support", - "!matrix-js-sdk/src/randomstring", - "!matrix-js-sdk/src/sliding-sync", - "!matrix-js-sdk/src/browser-index", - "!matrix-js-sdk/src/feature", - "!matrix-js-sdk/src/NamespacedValue", - "!matrix-js-sdk/src/ReEmitter", - "!matrix-js-sdk/src/event-mapper", - "!matrix-js-sdk/src/interactive-auth", - "!matrix-js-sdk/src/secret-storage", - "!matrix-js-sdk/src/room-hierarchy", - "!matrix-js-sdk/src/rendezvous", - "!matrix-js-sdk/src/indexeddb-worker", - "!matrix-js-sdk/src/pushprocessor", - "!matrix-js-sdk/src/extensible_events_v1", - "!matrix-js-sdk/src/extensible_events_v1/PollStartEvent", - "!matrix-js-sdk/src/extensible_events_v1/PollResponseEvent", - "!matrix-js-sdk/src/extensible_events_v1/PollEndEvent", - "!matrix-js-sdk/src/extensible_events_v1/InvalidEventError", - "!matrix-js-sdk/src/webrtc", - "!matrix-js-sdk/src/webrtc/call", - "!matrix-js-sdk/src/webrtc/callFeed", - "!matrix-js-sdk/src/webrtc/mediaHandler", - "!matrix-js-sdk/src/webrtc/callEventTypes", - "!matrix-js-sdk/src/webrtc/callEventHandler", - "!matrix-js-sdk/src/webrtc/groupCallEventHandler", - "!matrix-js-sdk/src/models", - "!matrix-js-sdk/src/models/read-receipt", - "!matrix-js-sdk/src/models/relations-container", - "!matrix-js-sdk/src/models/related-relations", - "!matrix-js-sdk/src/matrixrtc", - ], - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - group: ["emojibase-regex/emoji*"], - message: - "This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.", - }, - { - group: ["@vector-im/compound-design-tokens/icons/*"], - message: "Please use @vector-im/compound-design-tokens/assets/web/icons/* instead", - }, - { - group: ["**/packages/shared-components/**", "../packages/shared-components/**"], - message: "Please use @element-hq/web-shared-components", - }, - ], - }, - ], - - // There are too many a11y violations to fix at once - // Turn violated rules off until they are fixed - "jsx-a11y/aria-activedescendant-has-tabindex": "off", - "jsx-a11y/click-events-have-key-events": "off", - "jsx-a11y/interactive-supports-focus": "off", - "jsx-a11y/media-has-caption": "off", - "jsx-a11y/mouse-events-have-key-events": "off", - "jsx-a11y/no-autofocus": "off", - "jsx-a11y/no-noninteractive-element-interactions": "off", - "jsx-a11y/no-noninteractive-element-to-interactive-role": "off", - "jsx-a11y/no-noninteractive-tabindex": "off", - "jsx-a11y/no-static-element-interactions": "off", - "jsx-a11y/role-supports-aria-props": "off", - - "matrix-org/require-copyright-header": "error", - - "react-compiler/react-compiler": "error", - }, - overrides: [ - { - files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}", "playwright/**/*.ts", "*.ts"], - extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"], - rules: { - "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }], - "@typescript-eslint/explicit-function-return-type": [ - "error", - { - allowExpressions: true, - }, - ], - - // Things we do that break the ideal style - "prefer-promise-reject-errors": "off", - "no-extra-boolean-cast": "off", - - // Remove Babel things manually due to override limitations - "@babel/no-invalid-this": ["off"], - - // We're okay being explicit at the moment - "@typescript-eslint/no-empty-interface": "off", - // We disable this while we're transitioning - "@typescript-eslint/no-explicit-any": "off", - // We'd rather not do this but we do - "@typescript-eslint/ban-ts-comment": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-empty-object-type": [ - "error", - { - // We do this sometimes to brand interfaces - allowInterfaces: "with-single-extends", - }, - ], - }, - }, - { - files: [ - "src/**/*.test.{ts,tsx}", - "src/test/**/*.{ts,tsx}", - "src/**/__mocks__/*.{ts,tsx}", - "test/**/*.{ts,tsx}", - "playwright/**/*.ts", - ], - extends: [], - rules: { - // We don't need super strict typing in test utilities - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-member-accessibility": "off", - "@typescript-eslint/no-empty-object-type": "off", - "@typescript-eslint/unbound-method": "off", - // These are fine in tests - "no-restricted-globals": "off", - "react-compiler/react-compiler": "off", - }, - }, - { - files: ["test/**/*.{ts,tsx}"], - extends: ["plugin:matrix-org/jest"], - rules: { - // Disabled tests are a reality for now but as soon as all of the xits are - // eliminated, we should enforce this. - "jest/no-disabled-tests": "off", - // Also treat "oldBackendOnly" as a test function. - // Used in some crypto tests. - "jest/no-standalone-expect": [ - "error", - { - additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"], - }, - ], - }, - }, - { - files: ["playwright/**/*.ts"], - parserOptions: { - project: ["./playwright/tsconfig.json"], - }, - rules: { - "react-hooks/rules-of-hooks": ["off"], - "@typescript-eslint/no-floating-promises": ["error"], - }, - }, - { - files: ["module_system/**/*.{ts,tsx}"], - parserOptions: { - project: ["./tsconfig.module_system.json"], - }, - extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react"], - // NOTE: These rules are frozen and new rules should not be added here. - // New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/ - rules: { - // Things we do that break the ideal style - "prefer-promise-reject-errors": "off", - "quotes": "off", - - // We disable this while we're transitioning - "@typescript-eslint/no-explicit-any": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - - // Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell. - "no-restricted-imports": [ - "error", - { - paths: [ - { - name: "matrix-js-sdk", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/src", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/src/", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - { - name: "matrix-js-sdk/src/index", - message: "Please use matrix-js-sdk/src/matrix instead", - }, - ], - patterns: [ - { - group: ["matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**"], - message: "Please use matrix-js-sdk/src/* instead", - }, - ], - }, - ], - }, - }, - ], - settings: { - react: { - version: "detect", - }, - }, -}; - -function buildRestrictedPropertiesOptions(properties, message) { - return properties.map((prop) => { - let [object, property] = prop.split("."); - if (object === "*") { - object = undefined; - } - return { - object, - property, - message, - }; - }); -} diff --git a/apps/web/.lintstagedrc b/apps/web/.lintstagedrc index b417cb03624..ae8a7fc7e94 100644 --- a/apps/web/.lintstagedrc +++ b/apps/web/.lintstagedrc @@ -1,7 +1,5 @@ { "*": "oxfmt --no-error-on-unmatched-pattern", - "src/**/*.(ts|tsx)": ["eslint --fix"], - "scripts/**/*.(ts|tsx)": ["eslint --fix"], - "module_system/**/*.(ts|tsx)": ["eslint --fix"], + "*.{js,jsx,ts,tsx,mjs,cjs}": "oxlint --no-error-on-unmatched-pattern", "*.pcss": ["stylelint --fix"] } diff --git a/apps/web/.stylelintrc.cjs b/apps/web/.stylelintrc.cjs index ba4820f38dd..4388f7919d6 100644 --- a/apps/web/.stylelintrc.cjs +++ b/apps/web/.stylelintrc.cjs @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + module.exports = { extends: ["stylelint-config-standard"], customSyntax: "postcss-scss", diff --git a/apps/web/I18nWebpackPlugin.ts b/apps/web/I18nWebpackPlugin.ts index 732f03dda1e..f0892a70295 100644 --- a/apps/web/I18nWebpackPlugin.ts +++ b/apps/web/I18nWebpackPlugin.ts @@ -62,7 +62,7 @@ export class I18nWebpackPlugin { } const primaryPath = paths[0]; - const includeLangs = [...new Set([...(await fs.readdir(primaryPath))])] + const includeLangs = [...new Set(await fs.readdir(primaryPath))] .filter((fn) => fn.endsWith(".json")) .map((f) => f.slice(0, -5)); diff --git a/apps/web/__mocks__/cssMock.js b/apps/web/__mocks__/cssMock.js index 9b5d9b34764..a85e0e6eb10 100644 --- a/apps/web/__mocks__/cssMock.js +++ b/apps/web/__mocks__/cssMock.js @@ -1 +1,8 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + module.exports = "css-file-stub"; diff --git a/apps/web/__mocks__/empty.js b/apps/web/__mocks__/empty.js index 51fb4fe9371..ef57358c345 100644 --- a/apps/web/__mocks__/empty.js +++ b/apps/web/__mocks__/empty.js @@ -1,2 +1,9 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + // Yes, this is empty. module.exports = {}; diff --git a/apps/web/__mocks__/imageMock.js b/apps/web/__mocks__/imageMock.js index 474ac702b48..2d381a609d7 100644 --- a/apps/web/__mocks__/imageMock.js +++ b/apps/web/__mocks__/imageMock.js @@ -1 +1,8 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + module.exports = "image-file-stub"; diff --git a/apps/web/__mocks__/svg.js b/apps/web/__mocks__/svg.js index ee2ab11a014..73925a79099 100644 --- a/apps/web/__mocks__/svg.js +++ b/apps/web/__mocks__/svg.js @@ -1,2 +1,9 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + export const Icon = "div"; export default "image-file-stub"; diff --git a/apps/web/babel.config.cjs b/apps/web/babel.config.cjs index 58df067b795..ee3c95ccd05 100644 --- a/apps/web/babel.config.cjs +++ b/apps/web/babel.config.cjs @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + module.exports = { sourceMaps: true, presets: [ diff --git a/apps/web/module_system/installer.ts b/apps/web/module_system/installer.ts index e2da8eb2293..b179f394cab 100644 --- a/apps/web/module_system/installer.ts +++ b/apps/web/module_system/installer.ts @@ -134,9 +134,9 @@ function getOptionalDepNames(pkgJsonStr: string): string[] { function findDepVersionInPackageJson(dep: string, pkgJsonStr: string): string { const pkgJson = JSON.parse(pkgJsonStr); const packages = { - ...(pkgJson["optionalDependencies"] ?? {}), - ...(pkgJson["devDependencies"] ?? {}), - ...(pkgJson["dependencies"] ?? {}), + ...pkgJson["optionalDependencies"], + ...pkgJson["devDependencies"], + ...pkgJson["dependencies"], }; return packages[dep]; } diff --git a/apps/web/package.json b/apps/web/package.json index e9185fd8ae7..1126f22a3fc 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -24,9 +24,7 @@ "vendor:jitsi": "curl -s https://meet.element.io/libs/external_api.min.js > ./res/jitsi_external_api.min.js", "dist": "./scripts/package.sh", "start": "nx start", - "lint": "pnpm lint:types && pnpm lint:js && pnpm lint:style", - "lint:js": "eslint --max-warnings 0 src test playwright module_system", - "lint:js-fix": "eslint --fix src test playwright module_system", + "lint": "pnpm lint:types && pnpm lint:style", "lint:types": "nx lint:types", "lint:style": "stylelint \"res/css/**/*.pcss\"", "test": "nx test:unit", @@ -109,8 +107,6 @@ }, "devDependencies": { "@babel/core": "^7.12.10", - "@babel/eslint-parser": "^7.12.10", - "@babel/eslint-plugin": "^7.12.10", "@babel/plugin-proposal-decorators": "^7.25.9", "@babel/plugin-proposal-export-default-from": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.3", @@ -134,7 +130,6 @@ "@playwright/test": "catalog:", "@principalstudio/html-webpack-inject-preload": "^1.2.7", "@sentry/webpack-plugin": "^5.0.0", - "@stylistic/eslint-plugin": "^5.0.0", "@svgr/webpack": "^8.0.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.8", @@ -164,8 +159,6 @@ "@types/semver": "^7.5.8", "@types/tar-js": "^0.3.5", "@types/ua-parser-js": "^0.7.36", - "@typescript-eslint/eslint-plugin": "^8.19.0", - "@typescript-eslint/parser": "^8.19.0", "@vitest/spy": "catalog:", "babel-jest": "^30.0.0", "babel-loader": "^10.0.0", @@ -175,17 +168,6 @@ "css-loader": "^7.0.0", "css-minimizer-webpack-plugin": "^8.0.0", "dotenv": "^17.0.0", - "eslint": "8.57.1", - "eslint-config-google": "^0.14.0", - "eslint-plugin-deprecate": "0.9.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^29.0.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "^3.0.0", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124", - "eslint-plugin-react-hooks": "^7.0.0", - "eslint-plugin-unicorn": "^56.0.0", "express": "^5.0.0", "fake-indexeddb": "^6.0.0", "file-loader": "^6.0.0", diff --git a/apps/web/playwright/e2e/modules/custom-component.spec.ts b/apps/web/playwright/e2e/modules/custom-component.spec.ts index 32615ea6b1e..568e01963a4 100644 --- a/apps/web/playwright/e2e/modules/custom-component.spec.ts +++ b/apps/web/playwright/e2e/modules/custom-component.spec.ts @@ -52,7 +52,7 @@ test.describe("Custom Component API", () => { async ({ page, room, app }) => { await app.viewRoomById(room.roomId); await app.client.sendMessage(room.roomId, "Simple message"); - await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( "custom-component-tile.png", screenshotOptions(page), ); @@ -64,7 +64,7 @@ test.describe("Custom Component API", () => { async ({ page, room, app }) => { await app.viewRoomById(room.roomId); await app.client.sendMessage(room.roomId, "Fall through here"); - await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( "custom-component-tile-fall-through.png", screenshotOptions(page), ); @@ -76,7 +76,7 @@ test.describe("Custom Component API", () => { async ({ page, room, app }) => { await app.viewRoomById(room.roomId); await app.client.sendMessage(room.roomId, "Do not replace me"); - await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( "custom-component-tile-original.png", screenshotOptions(page), ); @@ -87,7 +87,7 @@ test.describe("Custom Component API", () => { await app.client.sendMessage(room.roomId, "Do not show edits"); await page.getByText("Do not show edits").hover(); await expect( - await page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }), + page.getByRole("toolbar", { name: "Message Actions" }).getByRole("button", { name: "Edit" }), ).not.toBeVisible(); }); test("should disallow downloading media when the allowDownloading hint is set to false", async ({ @@ -148,7 +148,7 @@ test.describe("Custom Component API", () => { async ({ page, room, app }) => { await app.viewRoomById(room.roomId); await app.client.sendMessage(room.roomId, "Crash the filter!"); - await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( "custom-component-crash-handle-filter.png", screenshotOptions(page), ); @@ -160,7 +160,7 @@ test.describe("Custom Component API", () => { async ({ page, room, app }) => { await app.viewRoomById(room.roomId); await app.client.sendMessage(room.roomId, "Crash the renderer!"); - await expect(await page.locator(".mx_EventTile_last")).toMatchScreenshot( + await expect(page.locator(".mx_EventTile_last")).toMatchScreenshot( "custom-component-crash-handle-renderer.png", screenshotOptions(page), ); diff --git a/apps/web/playwright/e2e/read-receipts/reactions-in-threads.spec.ts b/apps/web/playwright/e2e/read-receipts/reactions-in-threads.spec.ts index b2cd2e554aa..5073b78e4a9 100644 --- a/apps/web/playwright/e2e/read-receipts/reactions-in-threads.spec.ts +++ b/apps/web/playwright/e2e/read-receipts/reactions-in-threads.spec.ts @@ -193,7 +193,7 @@ test.describe("Read receipts", { tag: "@mergequeue" }, () => { await page.getByRole("button", { name: "React" }).click(); await page.locator(".mx_EmojiPicker_body").getByText("😀").first().click(); await page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀").click(); - await expect(await page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀")).not.toBeVisible(); + await expect(page.locator(".mx_ThreadPanel").getByLabel("Mae reacted with 😀")).not.toBeVisible(); }); }); }); diff --git a/apps/web/playwright/e2e/right-panel/right-panel.spec.ts b/apps/web/playwright/e2e/right-panel/right-panel.spec.ts index 6a6aba8fd57..fade4afdd22 100644 --- a/apps/web/playwright/e2e/right-panel/right-panel.spec.ts +++ b/apps/web/playwright/e2e/right-panel/right-panel.spec.ts @@ -75,7 +75,7 @@ test.describe("RightPanel", () => { test("should have padding under leave room", { tag: "@screenshot" }, async ({ page, app }) => { await viewRoomSummaryByName(page, app, ROOM_NAME); - const leaveButton = await page.getByRole("menuitem", { name: "Leave Room" }); + const leaveButton = page.getByRole("menuitem", { name: "Leave Room" }); await leaveButton.scrollIntoViewIfNeeded(); await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("with-leave-room.png"); @@ -177,7 +177,7 @@ test.describe("RightPanel", () => { await viewRoomSummaryByName(page, app, ROOM_NAME); await page.getByRole("menuitem", { name: "Report room" }).click(); - const dialog = await page.getByRole("dialog", { name: "Report Room" }); + const dialog = page.getByRole("dialog", { name: "Report Room" }); await dialog.getByLabel("reason").fill("This room should be reported"); await expect(dialog).toMatchScreenshot("room-report-dialog.png"); await dialog.getByRole("button", { name: "Send report" }).click(); @@ -189,7 +189,7 @@ test.describe("RightPanel", () => { await viewRoomSummaryByName(page, app, ROOM_NAME); await page.getByRole("menuitem", { name: "Report room" }).click(); - const dialog = await page.getByRole("dialog", { name: "Report room" }); + const dialog = page.getByRole("dialog", { name: "Report room" }); await dialog.getByRole("switch", { name: "Leave room" }).click(); await dialog.getByLabel("reason").fill("This room should be reported"); await dialog.getByRole("button", { name: "Send report" }).click(); diff --git a/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts b/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts index c646e613c44..89709c5207e 100644 --- a/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/room-settings/roles-permissions-room-settings-tab.spec.ts @@ -38,7 +38,7 @@ test.describe("Roles & Permissions room settings tab", () => { await combobox.selectOption("Moderator"); // Should display a modal to warn that we are demoting the only admin user - const modal = await page.locator(".mx_Dialog", { + const modal = page.locator(".mx_Dialog", { hasText: "Warning", }); await expect(modal).toBeVisible(); diff --git a/apps/web/playwright/e2e/settings/room-settings/room-security-tab.spec.ts b/apps/web/playwright/e2e/settings/room-settings/room-security-tab.spec.ts index 4a00bae29f1..82a6d5df0c1 100644 --- a/apps/web/playwright/e2e/settings/room-settings/room-security-tab.spec.ts +++ b/apps/web/playwright/e2e/settings/room-settings/room-security-tab.spec.ts @@ -126,7 +126,7 @@ test.describe("Roles & Permissions room settings tab", () => { // Element should have automatically set the room to "sharing" history visibility const errorDialog = page.getByRole("heading", { name: "Cannot make room private" }); await expect(errorDialog).toBeVisible(); - await errorDialog.getByLabel("OK"); + errorDialog.getByLabel("OK"); await expect(settingsGroupHistory.getByText("Anyone")).toBeChecked(); }, ); diff --git a/apps/web/playwright/e2e/share-dialog/share-by-url.spec.ts b/apps/web/playwright/e2e/share-dialog/share-by-url.spec.ts index 49024d79d94..909b4c9c723 100644 --- a/apps/web/playwright/e2e/share-dialog/share-by-url.spec.ts +++ b/apps/web/playwright/e2e/share-dialog/share-by-url.spec.ts @@ -30,6 +30,6 @@ test.describe("share from URL", () => { const lastMessage = page.locator(".mx_RoomView_MessageList .mx_EventTile_last"); await expect(lastMessage).toBeVisible(); const lastMessageText = await lastMessage.locator(".mx_EventTile_body").innerText(); - await expect(lastMessageText).toBe("Hello world"); + expect(lastMessageText).toBe("Hello world"); }); }); diff --git a/apps/web/playwright/e2e/timeline/timeline.spec.ts b/apps/web/playwright/e2e/timeline/timeline.spec.ts index 03f0c8e8e7a..64ca9e07b2a 100644 --- a/apps/web/playwright/e2e/timeline/timeline.spec.ts +++ b/apps/web/playwright/e2e/timeline/timeline.spec.ts @@ -71,7 +71,7 @@ const sendImage = async ( ): Promise => { const upload = await client.uploadContent(pngBytes, { name: "image.png", type: "image/png" }); return client.sendEvent(roomId, null, "m.room.message" as EventType, { - ...(additionalContent ?? {}), + ...additionalContent, msgtype: "m.image" as MsgType, body: "image.png", diff --git a/apps/web/playwright/e2e/voip/element-call.spec.ts b/apps/web/playwright/e2e/voip/element-call.spec.ts index 9394cc47fa6..a0103d8e2ee 100644 --- a/apps/web/playwright/e2e/voip/element-call.spec.ts +++ b/apps/web/playwright/e2e/voip/element-call.spec.ts @@ -153,7 +153,7 @@ test.describe("Element Call", () => { await page.getByRole("menuitem", { name: "Element Call" }).click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); // Ensure we set the correct parameters for ECall. const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -179,7 +179,7 @@ test.describe("Element Call", () => { await page.keyboard.up("Shift"); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -204,7 +204,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -254,7 +254,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -283,7 +283,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -317,7 +317,7 @@ test.describe("Element Call", () => { await page.getByRole("menuitem", { name: "Element Call" }).click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -335,7 +335,7 @@ test.describe("Element Call", () => { await page.keyboard.up("Shift"); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -351,7 +351,7 @@ test.describe("Element Call", () => { await page.getByRole("menuitem", { name: "Element Call" }).click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); // The call should be presented in the picture-in-picture container, right in the room we started it // from, rather than taking over the room view. @@ -369,7 +369,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -409,7 +409,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -443,7 +443,7 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); assertCommonCallParameters(url.searchParams, hash, user, room); @@ -475,7 +475,7 @@ test.describe("Element Call", () => { const roomId = new URL(page.url()).hash.slice("#/room/".length); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - await expect(frameUrlStr).toBeDefined(); + expect(frameUrlStr).toBeDefined(); // Ensure we set the correct parameters for ECall. const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -518,7 +518,7 @@ test.describe("Element Call", () => { await expect(callFrame.getByText("In call", { exact: true })).toBeVisible(); // Wait for Element Web to pickup the RTC session and update the room list entry. - await expect(await page.getByTestId("notification-decoration")).toBeVisible(); + await expect(page.getByTestId("notification-decoration")).toBeVisible(); } test("should be able to switch rooms and have the call persist", async ({ page, user, room, app }) => { @@ -558,9 +558,9 @@ test.describe("Element Call", () => { await expect(pipContainer).not.toBeVisible(); // Wait for call to stop. - await expect(await page.getByTestId("notification-decoration")).not.toBeVisible(); + await expect(page.getByTestId("notification-decoration")).not.toBeVisible(); await app.viewRoomById(room.roomId); - await expect(await page.getByTestId("join-call-button")).not.toBeVisible(); + await expect(page.getByTestId("join-call-button")).not.toBeVisible(); // Join the call again. await openAndJoinCall(page); @@ -590,8 +590,8 @@ test.describe("Element Call", () => { await expect(pipContainer).not.toBeVisible(); // Wait for call to stop. - await expect(await page.getByTestId("notification-decoration")).not.toBeVisible(); - await expect(await page.getByTestId("join-call-button")).not.toBeVisible(); + await expect(page.getByTestId("notification-decoration")).not.toBeVisible(); + await expect(page.getByTestId("join-call-button")).not.toBeVisible(); // Join the call again, but from the other room. await openAndJoinCall(page); diff --git a/apps/web/playwright/pages/client.ts b/apps/web/playwright/pages/client.ts index 39275085f73..4c063cc09e9 100644 --- a/apps/web/playwright/pages/client.ts +++ b/apps/web/playwright/pages/client.ts @@ -202,7 +202,7 @@ export class Client { /** * Create a room with given options. * @param options the options to apply when creating the room - * @return the ID of the newly created room + * @returns the ID of the newly created room */ public async createRoom(options: ICreateRoomOpts): Promise { const client = await this.prepareClient(); @@ -217,7 +217,7 @@ export class Client { /** * Create a space with given options. * @param options the options to apply when creating the space - * @return the ID of the newly created space (room) + * @returns the ID of the newly created space (room) */ public async createSpace(options: ICreateRoomOpts): Promise { return this.createRoom({ @@ -412,8 +412,8 @@ export class Client { /** * @param {string} name * @param {module:client.callback} callback Optional. - * @return {Promise} Resolves: {} an empty object. - * @return {module:http-api.MatrixError} Rejects: with an error response. + * @returns {Promise} Resolves: {} an empty object. + * @returns {module:http-api.MatrixError} Rejects: with an error response. */ public async setDisplayName(name: string): Promise { const client = await this.prepareClient(); @@ -423,8 +423,8 @@ export class Client { /** * @param {string} url * @param {module:client.callback} callback Optional. - * @return {Promise} Resolves: {} an empty object. - * @return {module:http-api.MatrixError} Rejects: with an error response. + * @returns {Promise} Resolves: {} an empty object. + * @returns {module:http-api.MatrixError} Rejects: with an error response. */ public async setAvatarUrl(url: string): Promise { const client = await this.prepareClient(); diff --git a/apps/web/playwright/pages/settings.ts b/apps/web/playwright/pages/settings.ts index c871bbeeafc..5b836efa47d 100644 --- a/apps/web/playwright/pages/settings.ts +++ b/apps/web/playwright/pages/settings.ts @@ -41,7 +41,7 @@ export class Settings { * null. * @param {SettingLevel} level The level to change the value at. * @param {*} value The new value of the setting, may be null. - * @return {Promise} Resolves when the setting has been changed. + * @returns {Promise} Resolves when the setting has been changed. */ public async setValue(settingName: string, roomId: string | null, level: SettingLevel, value: any): Promise { return this.page.evaluate< diff --git a/apps/web/playwright/tsconfig.json b/apps/web/playwright/tsconfig.json index 85ab919b487..00f45c05054 100644 --- a/apps/web/playwright/tsconfig.json +++ b/apps/web/playwright/tsconfig.json @@ -10,7 +10,8 @@ "allowImportingTsExtensions": true, "strictNullChecks": false, "noImplicitAny": false, - "types": ["node"] + "types": ["node"], + "noEmit": true }, "include": [ "**/*.ts", diff --git a/apps/web/res/decoder-ring/datatypes.js b/apps/web/res/decoder-ring/datatypes.js index 35d251a8dea..07fdcc72e99 100644 --- a/apps/web/res/decoder-ring/datatypes.js +++ b/apps/web/res/decoder-ring/datatypes.js @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + /* * Quick-n-dirty algebraic datatypes. * diff --git a/apps/web/res/decoder-ring/decoder.js b/apps/web/res/decoder-ring/decoder.js index 8a62ed2c8b0..866ff8be85b 100644 --- a/apps/web/res/decoder-ring/decoder.js +++ b/apps/web/res/decoder-ring/decoder.js @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + class StartupError extends Error {} /* @@ -165,7 +172,7 @@ function BundlePicker() { setBundle(name); } }, console.log.bind(console)); - }, [baseUrl]); + }, [baseUrl, bundle]); /* ------------------------- */ /* Follow user state changes */ diff --git a/apps/web/src/@types/global.d.ts b/apps/web/src/@types/global.d.ts index e3e49fad701..606ed57eb40 100644 --- a/apps/web/src/@types/global.d.ts +++ b/apps/web/src/@types/global.d.ts @@ -70,8 +70,8 @@ declare global { // so we don't accidentally use the methods on NodeJS.Timeout - they only exist in a subset of environments. // The overload for clear{Interval,Timeout} is resolved as expected. // We use `ReturnType` in the code to be agnostic of if this definition gets loaded. - function setInterval(handler: TimerHandler, timeout: number, ...arguments: any[]): number; - function setTimeout(handler: TimerHandler, timeout: number, ...arguments: any[]): number; + function setInterval(handler: TimerHandler, timeout: number, ...args: any[]): number; + function setTimeout(handler: TimerHandler, timeout: number, ...args: any[]): number; interface Window { mxSendRageshake: (text: string, withLogs?: boolean) => Promise; diff --git a/apps/web/src/AddThreepid.ts b/apps/web/src/AddThreepid.ts index 877a0c5c4a1..c2bf87a6c8e 100644 --- a/apps/web/src/AddThreepid.ts +++ b/apps/web/src/AddThreepid.ts @@ -69,7 +69,7 @@ export default class AddThreepid { * Attempt to add an email threepid to the homeserver. * This will trigger a side-effect of sending an email to the provided email address. * @param {string} emailAddress The email address to add - * @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked(). + * @returns {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked(). */ public async addEmailAddress(emailAddress: string): Promise { try { @@ -91,7 +91,7 @@ export default class AddThreepid { * Attempt to bind an email threepid on the identity server via the homeserver. * This will trigger a side-effect of sending an email to the provided email address. * @param {string} emailAddress The email address to add - * @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked(). + * @returns {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked(). */ public async bindEmailAddress(emailAddress: string): Promise { this.bind = true; @@ -122,7 +122,7 @@ export default class AddThreepid { * This will trigger a side-effect of sending an SMS to the provided phone number. * @param {string} phoneCountry The ISO 2 letter code of the country to resolve phoneNumber in * @param {string} phoneNumber The national or international formatted phone number to add - * @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). + * @returns {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). * * @throws {UserFriendlyError} An appropriate user-friendly error if the verification code could not be sent. */ @@ -155,7 +155,7 @@ export default class AddThreepid { * This will trigger a side-effect of sending an SMS to the provided phone number. * @param {string} phoneCountry The ISO 2 letter code of the country to resolve phoneNumber in * @param {string} phoneNumber The national or international formatted phone number to add - * @return {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). + * @returns {Promise} Resolves when the text message has been sent. Then call haveMsisdnToken(). */ public async bindMsisdn(phoneCountry: string, phoneNumber: string): Promise { this.bind = true; @@ -184,7 +184,7 @@ export default class AddThreepid { /** * Checks if the email link has been clicked by attempting to add the threepid - * @return {Promise} Resolves if the email address was added. Rejects with an object + * @returns {Promise} Resolves if the email address was added. Rejects with an object * with a "message" property which contains a human-readable message detailing why * the request failed. */ @@ -254,7 +254,7 @@ export default class AddThreepid { /** * @param {{type: string, session?: string}} auth UI auth object - * @return {Promise} Response from /3pid/add call (in current spec, an empty object) + * @returns {Promise} Response from /3pid/add call (in current spec, an empty object) */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type private makeAddThreepidOnlyRequest = (auth?: IAddThreePidOnlyBody["auth"] | null): Promise<{}> => { @@ -269,7 +269,7 @@ export default class AddThreepid { * Takes a phone number verification code as entered by the user and validates * it with the identity server, then if successful, adds the phone number. * @param {string} msisdnToken phone number verification code as entered by the user - * @return {Promise} Resolves if the phone number was added. Rejects with an object + * @returns {Promise} Resolves if the phone number was added. Rejects with an object * with a "message" property which contains a human-readable message detailing why * the request failed. */ diff --git a/apps/web/src/Avatar.ts b/apps/web/src/Avatar.ts index 8852578f285..f2995b3325d 100644 --- a/apps/web/src/Avatar.ts +++ b/apps/web/src/Avatar.ts @@ -137,7 +137,7 @@ export function defaultAvatarUrlForString(s: string): string { * returns the first (non-sigil) character of 'name', * converted to uppercase * @param {string} name - * @return {string} the first letter + * @returns {string} the first letter */ export function getInitialLetter(name: string): string | undefined { if (!name) { diff --git a/apps/web/src/BasePlatform.ts b/apps/web/src/BasePlatform.ts index f95767fa00b..290c4f311d1 100644 --- a/apps/web/src/BasePlatform.ts +++ b/apps/web/src/BasePlatform.ts @@ -279,7 +279,7 @@ export default abstract class BasePlatform { /** * Get our platform specific EventIndexManager. * - * @return {BaseEventIndexManager} The EventIndex manager for our platform, + * @returns {BaseEventIndexManager} The EventIndex manager for our platform, * can be null if the platform doesn't support event indexing. */ public getEventIndexingManager(): BaseEventIndexManager | null { diff --git a/apps/web/src/ContentMessages.ts b/apps/web/src/ContentMessages.ts index 15a43d79b67..601027c6916 100644 --- a/apps/web/src/ContentMessages.ts +++ b/apps/web/src/ContentMessages.ts @@ -77,7 +77,7 @@ interface IMediaConfig { * Load a file into a newly created image element. * * @param {File} imageFile The file to load in an image element. - * @return {Promise} A promise that resolves with the html image element. + * @returns {Promise} A promise that resolves with the html image element. */ async function loadImageElement(imageFile: File): Promise<{ width: number; @@ -148,7 +148,7 @@ const ALWAYS_INCLUDE_THUMBNAIL = ["image/avif", "image/webp", "image/svg+xml"]; * @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with. * @param {String} roomId The ID of the room the image will be uploaded in. * @param {File} imageFile The image to read and thumbnail. - * @return {Promise} A promise that resolves with the attachment info. + * @returns {Promise} A promise that resolves with the attachment info. */ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File): Promise { let thumbnailType = "image/png"; @@ -196,7 +196,7 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag * Load a file into a newly created audio element and load the metadata * * @param {File} audioFile The file to load in an audio element. - * @return {Promise} A promise that resolves with the audio element. + * @returns {Promise} A promise that resolves with the audio element. */ function loadAudioElement(audioFile: File): Promise { return new Promise((resolve, reject) => { @@ -228,7 +228,7 @@ function loadAudioElement(audioFile: File): Promise { * Read the metadata for an audio file. * * @param {File} audioFile The audio to read. - * @return {Promise} A promise that resolves with the attachment info. + * @returns {Promise} A promise that resolves with the attachment info. */ async function infoForAudioFile(audioFile: File): Promise { const audio = await loadAudioElement(audioFile); @@ -240,7 +240,7 @@ async function infoForAudioFile(audioFile: File): Promise { * in an attempt to guarantee the first frame will be showing. * * @param {File} videoFile The file to load in a video element. - * @return {Promise} A promise that resolves with the video element. + * @returns {Promise} A promise that resolves with the video element. */ function loadVideoElement(videoFile: File): Promise { return new Promise((resolve, reject) => { @@ -286,7 +286,7 @@ function loadVideoElement(videoFile: File): Promise { * @param {MatrixClient} matrixClient A matrixClient to upload the thumbnail with. * @param {String} roomId The ID of the room the video will be uploaded to. * @param {File} videoFile The video to read and thumbnail. - * @return {Promise} A promise that resolves with the attachment info. + * @returns {Promise} A promise that resolves with the attachment info. */ function infoForVideoFile(matrixClient: MatrixClient, roomId: string, videoFile: File): Promise { const thumbnailType = "image/jpeg"; @@ -311,7 +311,7 @@ function infoForVideoFile(matrixClient: MatrixClient, roomId: string, videoFile: /** * Read the file as an ArrayBuffer. * @param {File} file The file to read - * @return {Promise} A promise that resolves with an ArrayBuffer when the file + * @returns {Promise} A promise that resolves with an ArrayBuffer when the file * is read. */ function readFileAsArrayBuffer(file: File | Blob): Promise { @@ -337,7 +337,7 @@ function readFileAsArrayBuffer(file: File | Blob): Promise { * @param {Function?} progressHandler optional callback to be called when a chunk of * data is uploaded. * @param {AbortController?} controller optional abortController to use for this upload. - * @return {Promise} A promise that resolves with an object. + * @returns {Promise} A promise that resolves with an object. * If the file is unencrypted then the object will have a "url" key. * If the file is encrypted then the object will have a "file" key. */ diff --git a/apps/web/src/HtmlUtils.tsx b/apps/web/src/HtmlUtils.tsx index abf092cbb8f..9a8d885d254 100644 --- a/apps/web/src/HtmlUtils.tsx +++ b/apps/web/src/HtmlUtils.tsx @@ -79,7 +79,7 @@ function mightContainEmoji(str?: string): boolean { * Returns the shortcode for an emoji character. * * @param {String} char The emoji character - * @return {String} The shortcode (such as :thumbup:) + * @returns {String} The shortcode (such as :thumbup:) */ export function unicodeToShortcode(char: string): string { const shortcodes = getEmojiFromUnicode(char)?.shortcodes; @@ -119,7 +119,7 @@ export function getHtmlText(insaneHtml: string): string { * Note that the HTML sanitiser library has its own internal logic for * doing this, to which we pass the same list of schemes. This is used in * other places we need to sanitise URLs. - * @return true if permitted, otherwise false + * @returns true if permitted, otherwise false */ export function isUrlPermitted(inputUrl: string): boolean { try { @@ -497,7 +497,7 @@ export function bodyToHtml(content: IContent, highlights?: string[], opts: Event * @param htmlTopic optional html topic * @param ref React ref to attach to any React components returned * @param allowExtendedHtml whether to allow extended HTML tags such as headings and lists - * @return The HTML-ified node. + * @returns The HTML-ified node. */ export function topicToHtml( topic?: string, diff --git a/apps/web/src/MediaDeviceHandler.ts b/apps/web/src/MediaDeviceHandler.ts index 7a50024d1aa..c77fd87377a 100644 --- a/apps/web/src/MediaDeviceHandler.ts +++ b/apps/web/src/MediaDeviceHandler.ts @@ -54,7 +54,7 @@ export default class MediaDeviceHandler extends EventEmitter { * (https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) * is ready for primetime, it might help make this simpler. * - * @return Promise The available media devices + * @returns Promise The available media devices */ public static async getDevices(): Promise { try { diff --git a/apps/web/src/Modal.tsx b/apps/web/src/Modal.tsx index d92f83d7f17..313818af464 100644 --- a/apps/web/src/Modal.tsx +++ b/apps/web/src/Modal.tsx @@ -185,7 +185,7 @@ export class ModalManager extends TypedEventEmitter} whether the dialog should close + * @returns {Promise} whether the dialog should close */ /** diff --git a/apps/web/src/Notifier.ts b/apps/web/src/Notifier.ts index 411092bbe30..5f0ccd18d56 100644 --- a/apps/web/src/Notifier.ts +++ b/apps/web/src/Notifier.ts @@ -393,6 +393,7 @@ export default class Notifier extends TypedEventEmitter({ diff --git a/apps/web/src/PasswordReset.ts b/apps/web/src/PasswordReset.ts index 9fb841ed4b2..00927e173cf 100644 --- a/apps/web/src/PasswordReset.ts +++ b/apps/web/src/PasswordReset.ts @@ -73,7 +73,7 @@ export default class PasswordReset { /** * Checks if the email link has been clicked by attempting to change the password * for the mxid linked to the email. - * @return {Promise} Resolves if the password was reset. Rejects with an object + * @returns {Promise} Resolves if the password was reset. Rejects with an object * with a "message" property which contains a human-readable message detailing why * the reset failed, e.g. "There is no mapped matrix user ID for the given email address". */ diff --git a/apps/web/src/PosthogAnalytics.test.ts b/apps/web/src/PosthogAnalytics.test.ts index a70c91bbee8..561d3735c90 100644 --- a/apps/web/src/PosthogAnalytics.test.ts +++ b/apps/web/src/PosthogAnalytics.test.ts @@ -127,7 +127,7 @@ describe("PosthogAnalytics", () => { it("Should not track events if anonymous", async () => { analytics.setAnonymity(Anonymity.Anonymous); - await analytics.trackEvent({ + analytics.trackEvent({ eventName: "TestEvents", foo: "bar", }); diff --git a/apps/web/src/ScalarAuthClient.ts b/apps/web/src/ScalarAuthClient.ts index 26ee94de408..37a0861b469 100644 --- a/apps/web/src/ScalarAuthClient.ts +++ b/apps/web/src/ScalarAuthClient.ts @@ -227,7 +227,7 @@ export default class ScalarAuthClient { * This can be useful to temporarily prevent purchased assets from being displayed. * @param {WidgetType} widgetType The Widget Type to disable assets for * @param {string} widgetId The widget ID to disable assets for - * @return {Promise} Resolves on completion + * @returns {Promise} Resolves on completion */ public async disableWidgetAssets(widgetType: WidgetType, widgetId: string): Promise { const url = new URL(this.getStarterLink(this.apiUrl + "/widgets/set_assets_state")); diff --git a/apps/web/src/Searching.ts b/apps/web/src/Searching.ts index 28f67522ad6..47b54563543 100644 --- a/apps/web/src/Searching.ts +++ b/apps/web/src/Searching.ts @@ -412,7 +412,7 @@ function combineEventSources( * index. * @param {object} serverEvents An unprocessed search result from the server. * - * @return {object} A response object that combines the events from the + * @returns {object} A response object that combines the events from the * different event sources. * */ @@ -476,7 +476,7 @@ function combineEvents( * index. * @param {object} serverEvents An unprocessed search result from the server. * - * @return {object} A response object that combines the events from the + * @returns {object} A response object that combines the events from the * different event sources. */ function combineResponses( diff --git a/apps/web/src/SlidingSyncManager.ts b/apps/web/src/SlidingSyncManager.ts index 815e438da75..e92bf83fa0a 100644 --- a/apps/web/src/SlidingSyncManager.ts +++ b/apps/web/src/SlidingSyncManager.ts @@ -253,9 +253,9 @@ export class SlidingSyncManager { try { // if we only have range changes then call a different function so we don't nuke the list from before if (updateArgs.ranges && Object.keys(updateArgs).length === 1) { - await this.slidingSync!.setListRanges(listKey, updateArgs.ranges); + this.slidingSync!.setListRanges(listKey, updateArgs.ranges); } else { - await this.slidingSync!.setList(listKey, list); + this.slidingSync!.setList(listKey, list); } } catch (err) { logger.debug("ensureListRegistered: update failed txn_id=", err); @@ -391,7 +391,7 @@ export class SlidingSyncManager { /** * Check if the server "natively" supports sliding sync (with an unstable endpoint). * @param client The MatrixClient to use - * @return Whether the "native" (unstable) endpoint is supported + * @returns Whether the "native" (unstable) endpoint is supported */ public async nativeSlidingSyncSupport(client: MatrixClient): Promise { // Per https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1589542561 diff --git a/apps/web/src/TimezoneHandler.ts b/apps/web/src/TimezoneHandler.ts index eb1bacef5f4..cef4b15e4f6 100644 --- a/apps/web/src/TimezoneHandler.ts +++ b/apps/web/src/TimezoneHandler.ts @@ -22,7 +22,6 @@ export function getUserTimezone(): string | undefined { /** * Set in the settings the given timezone - * @timezone */ export function setUserTimezone(timezone: string): Promise { return SettingsStore.setValue(USER_TIMEZONE_KEY, null, SettingLevel.DEVICE, timezone); diff --git a/apps/web/src/audio/VoiceRecording.ts b/apps/web/src/audio/VoiceRecording.ts index 44a016b9957..ea8ca96e4ef 100644 --- a/apps/web/src/audio/VoiceRecording.ts +++ b/apps/web/src/audio/VoiceRecording.ts @@ -299,7 +299,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable { // Finally do our post-processing and clean up this.recording = false; - await this.recorder!.close(); + this.recorder!.close(); this.emit(RecordingState.Ended); }); } diff --git a/apps/web/src/autocomplete/AutocompleteProvider.tsx b/apps/web/src/autocomplete/AutocompleteProvider.tsx index 94614f0ae72..b9679d66461 100644 --- a/apps/web/src/autocomplete/AutocompleteProvider.tsx +++ b/apps/web/src/autocomplete/AutocompleteProvider.tsx @@ -58,7 +58,7 @@ export default abstract class AutocompleteProvider { * @param {string} query The query string * @param {ISelectionRange} selection Selection to search * @param {boolean} force True if the user is forcing completion - * @return {object} { command, range } where both objects fields are null if no match + * @returns {object} { command, range } where both objects fields are null if no match */ public getCurrentCommand(query: string, selection: ISelectionRange, force = false): Partial { let commandRegex = this.commandRegex; diff --git a/apps/web/src/components/structures/HomePage.tsx b/apps/web/src/components/structures/HomePage.tsx index 9d4062e1867..0f74280e773 100644 --- a/apps/web/src/components/structures/HomePage.tsx +++ b/apps/web/src/components/structures/HomePage.tsx @@ -6,8 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX } from "react"; -import { useContext, useState } from "react"; +import React, { type JSX, useContext, useState } from "react"; import { ChatSolidIcon, ExploreIcon, GroupIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { AutoHideScrollbar } from "@element-hq/web-shared-components"; diff --git a/apps/web/src/components/structures/LoggedInView.tsx b/apps/web/src/components/structures/LoggedInView.tsx index 1b49bf528ff..10dad82d1c5 100644 --- a/apps/web/src/components/structures/LoggedInView.tsx +++ b/apps/web/src/components/structures/LoggedInView.tsx @@ -311,7 +311,7 @@ class LoggedInView extends React.Component { private calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit): void { const error = (syncError?.error as MatrixError)?.errcode === "M_RESOURCE_LIMIT_EXCEEDED"; if (error) { - usageLimitEventContent = (syncError?.error as MatrixError).data as IUsageLimit; + usageLimitEventContent = (syncError?.error as MatrixError)?.data as IUsageLimit; } // usageLimitDismissed is true when the user has explicitly hidden the toast diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index 721cb61d97e..04c7fb2286d 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -691,7 +691,7 @@ export default class MatrixChat extends React.PureComponent { break; case "logout": this.stores.legacyCallHandler.hangupAllCalls(); - Promise.all([...[...CallStore.instance.connectedCalls].map((call) => call.disconnect())]).finally(() => + Promise.all([...CallStore.instance.connectedCalls].map((call) => call.disconnect())).finally(() => Lifecycle.logout(), ); break; diff --git a/apps/web/src/components/structures/ReleaseAnnouncement.tsx b/apps/web/src/components/structures/ReleaseAnnouncement.tsx index 5a9eea2d688..bdeef4280c7 100644 --- a/apps/web/src/components/structures/ReleaseAnnouncement.tsx +++ b/apps/web/src/components/structures/ReleaseAnnouncement.tsx @@ -25,7 +25,6 @@ interface ReleaseAnnouncementProps extends Omit< * @param feature - the feature to announce, should be listed in {@link Feature} * @param children * @param props - * @constructor */ export function ReleaseAnnouncement({ feature, diff --git a/apps/web/src/components/structures/RoomView.tsx b/apps/web/src/components/structures/RoomView.tsx index 1b9bd52408e..4f687cc7861 100644 --- a/apps/web/src/components/structures/RoomView.tsx +++ b/apps/web/src/components/structures/RoomView.tsx @@ -366,7 +366,7 @@ interface ILocalRoomCreateLoaderProps { * Room create loader view displaying a message and a spinner. * * @param {ILocalRoomCreateLoaderProps} props Room view props - * @return {ReactElement} + * @returns {ReactElement} */ function LocalRoomCreateLoader(props: ILocalRoomCreateLoaderProps): ReactElement { const text = _t("room|creating_room_text", { names: props.names }); diff --git a/apps/web/src/components/structures/SpaceHierarchy.tsx b/apps/web/src/components/structures/SpaceHierarchy.tsx index b1920e7fedc..097ea471394 100644 --- a/apps/web/src/components/structures/SpaceHierarchy.tsx +++ b/apps/web/src/components/structures/SpaceHierarchy.tsx @@ -798,7 +798,7 @@ const SpaceHierarchy: React.FC = ({ space, initialText = "", showRoom, a // Walk back up the tree to find all parents of the direct matches to show their place in the hierarchy const visited = new Set(); - const queue = [...directMatches.map((r) => r.room_id)]; + const queue = directMatches.map((r) => r.room_id); while (queue.length) { const roomId = queue.pop()!; visited.add(roomId); diff --git a/apps/web/src/components/structures/TimelinePanel.tsx b/apps/web/src/components/structures/TimelinePanel.tsx index 939c16d96e0..51bce161c7e 100644 --- a/apps/web/src/components/structures/TimelinePanel.tsx +++ b/apps/web/src/components/structures/TimelinePanel.tsx @@ -1728,7 +1728,7 @@ class TimelinePanel extends React.Component { * have been sent by the server, not * implicit ones generated by the JS * SDK. - * @return {String} the event ID + * @returns {String} the event ID */ private getCurrentReadReceipt(ignoreSynthesized = false): string | null { const client = MatrixClientPeg.get(); @@ -1886,7 +1886,7 @@ class TimelinePanel extends React.Component { * Iterate across all of the timelineSets and timelines inside to expose all of * the event IDs contained inside. * - * @return An event ID list for every timeline in every timelineSet + * @returns An event ID list for every timeline in every timelineSet */ function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] { const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => { diff --git a/apps/web/src/components/structures/ViewSource.tsx b/apps/web/src/components/structures/ViewSource.tsx index b2e003284bb..2d3b7168391 100644 --- a/apps/web/src/components/structures/ViewSource.tsx +++ b/apps/web/src/components/structures/ViewSource.tsx @@ -116,6 +116,7 @@ export default class ViewSource extends React.Component { return ( {(cli) => ( + // oxlint-disable-next-line react/jsx-no-constructed-context-values @@ -127,6 +128,7 @@ export default class ViewSource extends React.Component { return ( {(cli) => ( + // oxlint-disable-next-line react/jsx-no-constructed-context-values diff --git a/apps/web/src/components/structures/auth/SoftLogout.tsx b/apps/web/src/components/structures/auth/SoftLogout.tsx index cfc6715eb7f..171dde37ad5 100644 --- a/apps/web/src/components/structures/auth/SoftLogout.tsx +++ b/apps/web/src/components/structures/auth/SoftLogout.tsx @@ -108,7 +108,7 @@ export default class SoftLogout extends React.Component { const loginViews = flows.map((f) => STATIC_FLOWS_TO_VIEWS[f.type]); const isSocialSignOn = loginViews.includes(LoginView.Password) && loginViews.includes(LoginView.SSO); - const firstView = loginViews.filter((f) => !!f)[0] || LoginView.Unsupported; + const firstView = loginViews.find((f) => !!f) || LoginView.Unsupported; const chosenView = isSocialSignOn ? LoginView.PasswordWithSocialSignOn : firstView; this.setState({ flows, loginView: chosenView }); } diff --git a/apps/web/src/components/structures/auth/header/AuthHeaderProvider.tsx b/apps/web/src/components/structures/auth/header/AuthHeaderProvider.tsx index 50a53b95c3d..75f792f4a2f 100644 --- a/apps/web/src/components/structures/auth/header/AuthHeaderProvider.tsx +++ b/apps/web/src/components/structures/auth/header/AuthHeaderProvider.tsx @@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import { isEqual } from "lodash"; -import React, { type JSX, type ComponentProps, type PropsWithChildren, type Reducer, useReducer } from "react"; +import React, { type JSX, type ComponentProps, type PropsWithChildren, type Reducer, useReducer, useMemo } from "react"; import { AuthHeaderContext } from "./AuthHeaderContext"; import { type AuthHeaderModifier } from "./AuthHeaderModifier"; @@ -36,5 +36,6 @@ export function AuthHeaderProvider({ children }: PropsWithChildren): JSX.Element }, [] as ComponentProps[], ); - return {children}; + const context = useMemo(() => ({ state, dispatch }), [state, dispatch]); + return {children}; } diff --git a/apps/web/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts b/apps/web/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts index ba873b23507..8de24112880 100644 --- a/apps/web/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts +++ b/apps/web/src/components/viewmodels/settings/encryption/KeyStoragePanelViewModel.ts @@ -29,7 +29,7 @@ interface KeyStoragePanelState { * A function that can be called to enable or disable key storage. * @param enable True to turn key storage on or false to turn it off */ - setEnabled: (enable: boolean) => void; + setEnabled: (enable: boolean) => Promise; /** * True if the state is still loading for the first time diff --git a/apps/web/src/components/views/dialogs/DevtoolsDialog.tsx b/apps/web/src/components/views/dialogs/DevtoolsDialog.tsx index 4c5d5cc4317..d9074f84494 100644 --- a/apps/web/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/apps/web/src/components/views/dialogs/DevtoolsDialog.tsx @@ -151,6 +151,7 @@ const DevtoolsDialog: React.FC = ({ roomId, threadRootId, onFinished }) )}
{cli.getRoom(roomId) && ( + // oxlint-disable-next-line react/jsx-no-constructed-context-values {body} diff --git a/apps/web/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx b/apps/web/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx index 29fee059dc6..c2f79a6c8a9 100644 --- a/apps/web/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx +++ b/apps/web/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx @@ -30,7 +30,7 @@ const GenericFeatureFeedbackDialog: React.FC = ({ subheading, children, rageshakeLabel, - rageshakeData = {}, + rageshakeData, onFinished, }) => { const [comment, setComment] = useState(""); diff --git a/apps/web/src/components/views/dialogs/InviteDialog.tsx b/apps/web/src/components/views/dialogs/InviteDialog.tsx index d7492369774..77ed5b33ce0 100644 --- a/apps/web/src/components/views/dialogs/InviteDialog.tsx +++ b/apps/web/src/components/views/dialogs/InviteDialog.tsx @@ -1390,7 +1390,7 @@ export default class InviteDialog extends React.PureComponent, + , usersSection, ), ]; diff --git a/apps/web/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx b/apps/web/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx index 6b68aca245e..eb6fbc5df8d 100644 --- a/apps/web/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx +++ b/apps/web/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx @@ -78,7 +78,7 @@ const addAllParents = (set: Set, room: Room): void => { }); }; -const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], onFinished }) => { +const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected, onFinished }) => { const cli = room.client; const [newSelected, setNewSelected] = useState(new Set(selected)); const [query, setQuery] = useState(""); @@ -92,7 +92,7 @@ const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], Array.from(parents), SpaceStore.instance.spacePanelSpaces.filter((s) => !parents.has(s)), filterBoolean( - selected.map((roomId) => { + selected?.map((roomId) => { const room = cli.getRoom(roomId); if (!room) { return { roomId, name: roomId } as Room; @@ -100,7 +100,7 @@ const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], if (room.getMyMembership() !== KnownMembership.Join || !room.isSpaceRoom()) { return room; } - }), + }) ?? [], ), ]; }, [cli, selected, room]); diff --git a/apps/web/src/components/views/dialogs/SpacePreferencesDialog.tsx b/apps/web/src/components/views/dialogs/SpacePreferencesDialog.tsx index 6925cadcf19..a2ca0034f54 100644 --- a/apps/web/src/components/views/dialogs/SpacePreferencesDialog.tsx +++ b/apps/web/src/components/views/dialogs/SpacePreferencesDialog.tsx @@ -65,8 +65,8 @@ const SpacePreferencesDialog: React.FC = ({ space, onFinished }) => { new Tab( SpacePreferenceTab.Appearance, _td("common|appearance"), - , - , + , + , ), ]; diff --git a/apps/web/src/components/views/dialogs/SpaceSettingsDialog.tsx b/apps/web/src/components/views/dialogs/SpaceSettingsDialog.tsx index 11cc82cadb1..7f41ed7a969 100644 --- a/apps/web/src/components/views/dialogs/SpaceSettingsDialog.tsx +++ b/apps/web/src/components/views/dialogs/SpaceSettingsDialog.tsx @@ -54,27 +54,36 @@ const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFin new Tab( SpaceSettingsTab.General, _td("common|general"), - , - , + , + , ), new Tab( SpaceSettingsTab.Visibility, _td("room_settings|visibility|title"), - , - , + , + , ), new Tab( SpaceSettingsTab.Roles, _td("room_settings|permissions|title"), - , - , + , + , ), SettingsStore.getValue(UIFeature.AdvancedSettings) ? new Tab( SpaceSettingsTab.Advanced, _td("common|advanced"), - , - , + , + , ) : null, ].filter(Boolean) as NonEmptyArray>; diff --git a/apps/web/src/components/views/dialogs/UploadFailureDialog.tsx b/apps/web/src/components/views/dialogs/UploadFailureDialog.tsx index 75ee17fda63..e3715d032ed 100644 --- a/apps/web/src/components/views/dialogs/UploadFailureDialog.tsx +++ b/apps/web/src/components/views/dialogs/UploadFailureDialog.tsx @@ -36,7 +36,6 @@ export default class UploadFailureDialog extends React.Component { public render(): React.ReactNode { let message; - let preview; let buttons; if (this.props.totalFiles === 1 && this.props.badFiles.length === 1) { message = _t( @@ -105,10 +104,7 @@ export default class UploadFailureDialog extends React.Component { title={_t("upload_file|error_title")} contentId="mx_Dialog_content" > -
- {message} - {preview} -
+
{message}
{buttons} diff --git a/apps/web/src/components/views/elements/AppTile.tsx b/apps/web/src/components/views/elements/AppTile.tsx index 597d35b9932..f3a7994f65b 100644 --- a/apps/web/src/components/views/elements/AppTile.tsx +++ b/apps/web/src/components/views/elements/AppTile.tsx @@ -300,7 +300,7 @@ export default class AppTile extends React.Component { * Set initial component state when the App wUrl (widget URL) is being updated. * Component props *must* be passed (rather than relying on this.props). * @param {Object} newProps The new properties of the component - * @return {Object} Updated component state to be set with setState + * @returns {Object} Updated component state to be set with setState */ private getNewState(newProps: IProps): IState { return { diff --git a/apps/web/src/components/views/elements/GenericEventListSummary.tsx b/apps/web/src/components/views/elements/GenericEventListSummary.tsx index 94cd64a5db2..cc5d6cc6b5c 100644 --- a/apps/web/src/components/views/elements/GenericEventListSummary.tsx +++ b/apps/web/src/components/views/elements/GenericEventListSummary.tsx @@ -43,7 +43,7 @@ const GenericEventListSummary: React.FC = ({ threshold = 3, onToggle, startExpanded = false, - summaryMembers = [], + summaryMembers, summaryText, layout = Layout.Group, "data-testid": testId, @@ -83,7 +83,7 @@ const GenericEventListSummary: React.FC = ({ ); } else { const uniqueMembers = uniqBy( - summaryMembers.filter((member) => { + summaryMembers?.filter((member) => { if (!member?.getMxcAvatarUrl) { logger.error( "EventListSummary given null summaryMember, termites may be afoot eating event senders", diff --git a/apps/web/src/components/views/elements/PersistedElement.tsx b/apps/web/src/components/views/elements/PersistedElement.tsx index bc8c282ab0a..ee0f363215b 100644 --- a/apps/web/src/components/views/elements/PersistedElement.tsx +++ b/apps/web/src/components/views/elements/PersistedElement.tsx @@ -12,7 +12,6 @@ import { TooltipProvider } from "@vector-im/compound-web"; import dis from "../../../dispatcher/dispatcher"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { type ActionPayload } from "../../../dispatcher/payloads"; import { SDKContext } from "../../../contexts/SDKContext.ts"; @@ -170,7 +169,7 @@ export default class PersistedElement extends React.Component { const content = ( - +
{this.props.children} diff --git a/apps/web/src/components/views/right_panel/BaseCard.tsx b/apps/web/src/components/views/right_panel/BaseCard.tsx index f5d91881bd8..4f62b71cc33 100644 --- a/apps/web/src/components/views/right_panel/BaseCard.tsx +++ b/apps/web/src/components/views/right_panel/BaseCard.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent } from "react"; +import React, { type ReactNode, type KeyboardEvent, type Ref, type MouseEvent, useMemo } from "react"; import classNames from "classnames"; import { IconButton, Text } from "@vector-im/compound-web"; import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; @@ -103,9 +103,10 @@ const BaseCard: React.FC = ({ } const shouldRenderHeader = header || !hideHeaderButtons; + const context = useMemo(() => ({ isCard: true }), []); return ( - +
{ { timeout: DEBOUNCE_REQUEST_TIMEOUT_MS }, ); }); - test("to reset module component override when filter function does not match ", async () => { + test("to reset module component override when filter function does not match", async () => { const modApi = { customComponents: new CustomComponentsApi(), } as ModuleApi; diff --git a/apps/web/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts b/apps/web/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts index 63fe8ca3619..ebdb0bd06b5 100644 --- a/apps/web/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts +++ b/apps/web/src/components/views/rooms/wysiwyg_composer/utils/createMessageContent.ts @@ -25,7 +25,7 @@ export const EMOTE_PREFIX = "/me "; function attachRelation(content: IContent, relation?: IEventRelation): void { if (relation) { content["m.relates_to"] = { - ...(content["m.relates_to"] || {}), + ...content["m.relates_to"], ...relation, }; } diff --git a/apps/web/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx index 42fa45e0162..ae82800aa3c 100644 --- a/apps/web/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx @@ -40,7 +40,7 @@ export default class BridgeSettingsTab extends React.Component { const roomState = client.getRoom(roomId)?.currentState; if (!roomState) return []; - return BRIDGE_EVENT_TYPES.map((typeName) => roomState.getStateEvents(typeName)).flat(1); + return BRIDGE_EVENT_TYPES.flatMap((typeName) => roomState.getStateEvents(typeName)); } public render(): React.ReactNode { diff --git a/apps/web/src/components/views/voip/AudioFeed.tsx b/apps/web/src/components/views/voip/AudioFeed.tsx index 834318f41ad..f8d28c54498 100644 --- a/apps/web/src/components/views/voip/AudioFeed.tsx +++ b/apps/web/src/components/views/voip/AudioFeed.tsx @@ -80,7 +80,7 @@ export default class AudioFeed extends React.Component { // should serialise the ones that need to be serialised but then be able to interrupt // them with another load() which will cancel the pending one, but since we don't call // load() explicitly, it shouldn't be a problem. - Dave - await element.load(); + element.load(); } catch (e) { logger.info( `Failed to play media element with feed for userId ` + diff --git a/apps/web/src/contexts/CurrentRightPanelPhaseContext.tsx b/apps/web/src/contexts/CurrentRightPanelPhaseContext.tsx index 8bc3080fc38..e05bc53bd8c 100644 --- a/apps/web/src/contexts/CurrentRightPanelPhaseContext.tsx +++ b/apps/web/src/contexts/CurrentRightPanelPhaseContext.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { createContext } from "react"; +import React, { createContext, useMemo } from "react"; import { useCurrentPhase } from "../hooks/right-panel/useCurrentPhase"; import { type RightPanelPhases } from "../stores/right-panel/RightPanelStorePhases"; @@ -26,9 +26,6 @@ export const CurrentRightPanelPhaseContextProvider: React.FC { const { currentPhase, isOpen } = useCurrentPhase(roomId); - return ( - - {children} - - ); + const context = useMemo(() => ({ currentPhase, isPanelOpen: isOpen }), [currentPhase, isOpen]); + return {children}; }; diff --git a/apps/web/src/editor/diff.ts b/apps/web/src/editor/diff.ts index dfc654996e6..508741d3295 100644 --- a/apps/web/src/editor/diff.ts +++ b/apps/web/src/editor/diff.ts @@ -54,7 +54,7 @@ export function diffDeletion(oldStr: string, newStr: string): IDiff { * @param {String} oldValue the previous value * @param {String} newValue the new value * @param {Number} caretPosition the position of the caret after `newValue` was applied. - * @return {object} an object with `at` as the offset where characters were removed and/or added, + * @returns {object} an object with `at` as the offset where characters were removed and/or added, * `added` with the added string (if any), and * `removed` with the removed string (if any) */ diff --git a/apps/web/src/editor/model.ts b/apps/web/src/editor/model.ts index 4328b666d85..d7118d8c5c7 100644 --- a/apps/web/src/editor/model.ts +++ b/apps/web/src/editor/model.ts @@ -27,13 +27,13 @@ import { type Caret } from "./caret"; * @param {DocumentPosition?} caretPosition the position where the caret should be position * @param {string?} inputType the inputType of the DOM input event * @param {object?} diff an object with `removed` and `added` strings - * @return {Number?} addedLen how many characters were added/removed (-) before the caret during the transformation step. + * @returns {Number?} addedLen how many characters were added/removed (-) before the caret during the transformation step. * This is used to adjust the caret position. */ /** * @callback ManualTransformCallback - * @return the caret position + * @returns the caret position */ type TransformCallback = (caretPosition: DocumentPosition, inputType: string | undefined, diff: IDiff) => number | void; @@ -179,7 +179,7 @@ export default class EditorModel { * Should be run inside a `model.transform()` callback. * @param {Part[]} parts the parts to replace the range with * @param {DocumentPosition} position the position to start inserting at - * @return {Number} the amount of characters added + * @returns {Number} the amount of characters added */ public insert(parts: Part[], position: IPosition): number { const insertIndex = this.splitAt(position); @@ -303,7 +303,7 @@ export default class EditorModel { * removes `len` amount of characters at `pos`. * @param {Object} pos * @param {Number} len - * @return {Number} how many characters before pos were also removed, + * @returns {Number} how many characters before pos were also removed, * usually because of non-editable parts that can only be removed in their entirety. */ public removeText(pos: IPosition, len: number): number { @@ -363,7 +363,7 @@ export default class EditorModel { * @param {Object} pos * @param {string} str * @param {string} inputType the source of the input, see html InputEvent.inputType - * @return {Number} how far from position (in characters) the insertion ended. + * @returns {Number} how far from position (in characters) the insertion ended. * This can be more than the length of `str` when crossing non-editable parts, which are skipped. */ private addText(pos: IPosition, str: string, inputType: string | undefined): number { @@ -434,7 +434,7 @@ export default class EditorModel { * Starts a range, which can span across multiple parts, to find and replace text. * @param {DocumentPosition} positionA a boundary of the range * @param {DocumentPosition?} positionB the other boundary of the range, optional - * @return {Range} + * @returns {Range} */ public startRange(positionA: DocumentPosition, positionB = positionA): Range { return new Range(this, positionA, positionB); @@ -463,7 +463,7 @@ export default class EditorModel { * Performs a transformation not part of an update cycle. * Modifying the model should only happen inside a transform call if not part of an update call. * @param {ManualTransformCallback} callback to run the transformations in - * @return {Promise} a promise when auto-complete (if applicable) is done updating + * @returns {Promise} a promise when auto-complete (if applicable) is done updating */ public transform(callback: ManualTransformCallback): Promise { const pos = callback(); diff --git a/apps/web/src/editor/range.ts b/apps/web/src/editor/range.ts index eff439ec811..eecbde5c54a 100644 --- a/apps/web/src/editor/range.ts +++ b/apps/web/src/editor/range.ts @@ -93,7 +93,7 @@ export default class Range { * Splits the model at the range boundaries and replaces with the given parts. * Should be run inside a `model.transform()` callback. * @param {Part[]} parts the parts to replace the range with - * @return {Number} the net amount of characters added, can be negative. + * @returns {Number} the net amount of characters added, can be negative. */ public replace(parts: Part[]): number { const newLength = parts.reduce((sum, part) => sum + part.text.length, 0); diff --git a/apps/web/src/hooks/useAsyncRefreshMemo.ts b/apps/web/src/hooks/useAsyncRefreshMemo.ts index a1845ac8166..83e5be90e8c 100644 --- a/apps/web/src/hooks/useAsyncRefreshMemo.ts +++ b/apps/web/src/hooks/useAsyncRefreshMemo.ts @@ -16,7 +16,7 @@ type Fn = () => Promise; * @param fn function to memoize * @param deps React hooks dependencies for the function * @param initialValue initial value - * @return tuple of cached value and refresh callback + * @returns tuple of cached value and refresh callback */ export function useAsyncRefreshMemo(fn: Fn, deps: DependencyList, initialValue: T): [T, () => void]; export function useAsyncRefreshMemo(fn: Fn, deps: DependencyList, initialValue?: T): [T | undefined, () => void]; diff --git a/apps/web/src/hooks/useEventEmitter.ts b/apps/web/src/hooks/useEventEmitter.ts index 12bb57a1eeb..f5f2f6a82bb 100644 --- a/apps/web/src/hooks/useEventEmitter.ts +++ b/apps/web/src/hooks/useEventEmitter.ts @@ -177,7 +177,7 @@ export function useEventEmitterAsyncState { @@ -88,7 +88,7 @@ export default abstract class BaseEventIndexManager { * @param {string} userId The event that should be added to the index. * @param {string} deviceId The profile of the event sender at the * - * @return {Promise} A promise that will resolve when the event index is + * @returns {Promise} A promise that will resolve when the event index is * initialized. */ public async initEventIndex(userId: string, deviceId: string): Promise { @@ -102,7 +102,7 @@ export default abstract class BaseEventIndexManager { * @param {IMatrixProfile} profile The profile of the event sender at the * time the event was received. * - * @return {Promise} A promise that will resolve when the was queued up for + * @returns {Promise} A promise that will resolve when the was queued up for * addition. */ public async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise { @@ -123,7 +123,7 @@ export default abstract class BaseEventIndexManager { * @param {string} roomId The ID of the room which we want to check if it * has been already indexed. * - * @return {Promise} Returns true if the index contains events for + * @returns {Promise} Returns true if the index contains events for * the given room, false otherwise. */ public isRoomIndexed(roomId: string): Promise { @@ -133,7 +133,7 @@ export default abstract class BaseEventIndexManager { /** * Get statistical information of the index. * - * @return {Promise} A promise that will resolve to the index + * @returns {Promise} A promise that will resolve to the index * statistics. */ public async getStats(): Promise { @@ -142,7 +142,7 @@ export default abstract class BaseEventIndexManager { /** * Get the user version of the database. - * @return {Promise} A promise that will resolve to the user stored + * @returns {Promise} A promise that will resolve to the user stored * version number. */ public async getUserVersion(): Promise { @@ -155,7 +155,7 @@ export default abstract class BaseEventIndexManager { * @param {number} version The new version that should be stored in the * database. * - * @return {Promise} A promise that will resolve once the new version + * @returns {Promise} A promise that will resolve once the new version * is stored. */ public async setUserVersion(version: number): Promise { @@ -165,7 +165,7 @@ export default abstract class BaseEventIndexManager { /** * Commit the previously queued up events to the index. * - * @return {Promise} A promise that will resolve once the queued up events + * @returns {Promise} A promise that will resolve once the queued up events * were added to the index. */ public async commitLiveEvents(): Promise { @@ -178,7 +178,7 @@ export default abstract class BaseEventIndexManager { * @param {ISearchArgs} searchArgs The search configuration for the search, * sets the search term and determines the search result contents. * - * @return {Promise} A promise that will resolve to an array + * @returns {Promise} A promise that will resolve to an array * of search results once the search is done. */ public async searchEventIndex(searchArgs: ISearchArgs): Promise { @@ -199,7 +199,7 @@ export default abstract class BaseEventIndexManager { * to fetch the current batch of events. This checkpoint will be removed * from the index. * - * @return {Promise} A promise that will resolve to true if all the events + * @returns {Promise} A promise that will resolve to true if all the events * were already added to the index, false otherwise. */ public async addHistoricEvents( @@ -216,7 +216,7 @@ export default abstract class BaseEventIndexManager { * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be added * to the index. * - * @return {Promise} A promise that will resolve once the checkpoint has + * @returns {Promise} A promise that will resolve once the checkpoint has * been stored. */ public async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise { @@ -229,7 +229,7 @@ export default abstract class BaseEventIndexManager { * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be * removed from the index. * - * @return {Promise} A promise that will resolve once the checkpoint has + * @returns {Promise} A promise that will resolve once the checkpoint has * been removed. */ public async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise { @@ -239,7 +239,7 @@ export default abstract class BaseEventIndexManager { /** * Load the stored checkpoints from the index. * - * @return {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an + * @returns {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an * array of crawler checkpoints once they have been loaded from the index. */ public async loadCheckpoints(): Promise { @@ -258,7 +258,7 @@ export default abstract class BaseEventIndexManager { * @param {string} args.direction The direction to which we should continue * loading events from. This is used only if fromEvent is used as well. * - * @return {Promise<[IEventAndProfile]>} A promise that will resolve to an + * @returns {Promise<[IEventAndProfile]>} A promise that will resolve to an * array of Matrix events that contain mxc URLs accompanied with the * historic profile of the sender. */ @@ -269,7 +269,7 @@ export default abstract class BaseEventIndexManager { /** * close our event index. * - * @return {Promise} A promise that will resolve once the event index has + * @returns {Promise} A promise that will resolve once the event index has * been closed. */ public async closeEventIndex(): Promise { @@ -279,7 +279,7 @@ export default abstract class BaseEventIndexManager { /** * Delete our current event index. * - * @return {Promise} A promise that will resolve once the event index has + * @returns {Promise} A promise that will resolve once the event index has * been deleted. */ public async deleteEventIndex(): Promise { diff --git a/apps/web/src/indexing/EventIndex.ts b/apps/web/src/indexing/EventIndex.ts index 97913bf8452..2e3539fc5aa 100644 --- a/apps/web/src/indexing/EventIndex.ts +++ b/apps/web/src/indexing/EventIndex.ts @@ -711,7 +711,7 @@ export default class EventIndex extends EventEmitter { * @param {ISearchArgs} searchArgs The search configuration for the search, * sets the search term and determines the search result contents. * - * @return {Promise} A promise that will resolve to an array + * @returns {Promise} A promise that will resolve to an array * of search results once the search is done. */ public async search(searchArgs: ISearchArgs): Promise { @@ -949,7 +949,7 @@ export default class EventIndex extends EventEmitter { /** * Get statistical information of the index. * - * @return {Promise} A promise that will resolve to the index + * @returns {Promise} A promise that will resolve to the index * statistics. */ public async getStats(): Promise { @@ -963,7 +963,7 @@ export default class EventIndex extends EventEmitter { * @param {string} roomId The ID of the room which we want to check if it * has been already indexed. * - * @return {Promise} Returns true if the index contains events for + * @returns {Promise} Returns true if the index contains events for * the given room, false otherwise. */ public async isRoomIndexed(roomId: string): Promise { diff --git a/apps/web/src/indexing/EventIndexPeg.ts b/apps/web/src/indexing/EventIndexPeg.ts index f33cbcc74ca..eb32c823cad 100644 --- a/apps/web/src/indexing/EventIndexPeg.ts +++ b/apps/web/src/indexing/EventIndexPeg.ts @@ -37,7 +37,7 @@ export class EventIndexPeg { * Initialize the EventIndexPeg and if event indexing is enabled initialize * the event index. * - * @return {Promise} A promise that will resolve to true if an + * @returns {Promise} A promise that will resolve to true if an * EventIndex was successfully initialized, false otherwise. */ public async init(): Promise { @@ -111,7 +111,7 @@ export class EventIndexPeg { /** * Check if the current platform has support for event indexing. * - * @return {boolean} True if it has support, false otherwise. Note that this + * @returns {boolean} True if it has support, false otherwise. Note that this * does not mean that support is installed. */ public platformHasSupport(): boolean { @@ -125,7 +125,7 @@ export class EventIndexPeg { * this tells us if those are installed. Note that this should only be * called after the init() method was called. * - * @return {boolean} True if support is installed, false otherwise. + * @returns {boolean} True if support is installed, false otherwise. */ public supportIsInstalled(): boolean { return this._supportIsInstalled; @@ -134,7 +134,7 @@ export class EventIndexPeg { /** * Get the current event index. * - * @return {EventIndex} The current event index. + * @returns {EventIndex} The current event index. */ public get(): EventIndex | null { return this.index; @@ -155,7 +155,7 @@ export class EventIndexPeg { * * After a call to this the init() method will need to be called again. * - * @return {Promise} A promise that will resolve once the event index is + * @returns {Promise} A promise that will resolve once the event index is * closed. */ public async unset(): Promise { @@ -169,7 +169,7 @@ export class EventIndexPeg { * * After a call to this the init() method will need to be called again. * - * @return {Promise} A promise that will resolve once the event index is + * @returns {Promise} A promise that will resolve once the event index is * deleted. */ public async deleteEventIndex(): Promise { diff --git a/apps/web/src/notifications/PushRuleVectorState.ts b/apps/web/src/notifications/PushRuleVectorState.ts index 465299095a6..ae40c70aa11 100644 --- a/apps/web/src/notifications/PushRuleVectorState.ts +++ b/apps/web/src/notifications/PushRuleVectorState.ts @@ -37,7 +37,7 @@ export class PushRuleVectorState { /** * Convert a PushRuleVectorState to a list of actions * - * @return [object] list of push-rule actions + * @returns [object] list of push-rule actions */ public static actionsFor(pushRuleVectorState?: VectorState): PushRuleAction[] { if (pushRuleVectorState === VectorState.On) { diff --git a/apps/web/src/phonenumber.ts b/apps/web/src/phonenumber.ts index dbf49e65b93..a5d67e39738 100644 --- a/apps/web/src/phonenumber.ts +++ b/apps/web/src/phonenumber.ts @@ -15,7 +15,7 @@ const PHONE_NUMBER_REGEXP = /^[0-9 -.]+$/; * @param {String} phoneNumber The string to validate. This could be * either an international format number (MSISDN or e.164) or * a national-format number. - * @return True if the number could be a valid phone number, otherwise false. + * @returns True if the number could be a valid phone number, otherwise false. */ export function looksValid(phoneNumber: string): boolean { return PHONE_NUMBER_REGEXP.test(phoneNumber); diff --git a/apps/web/src/rageshake/rageshake.ts b/apps/web/src/rageshake/rageshake.ts index fbfedf0e4c1..b4c3c1494e5 100644 --- a/apps/web/src/rageshake/rageshake.ts +++ b/apps/web/src/rageshake/rageshake.ts @@ -109,7 +109,7 @@ export class ConsoleLogger { /** * Retrieve log lines to flush to disk. * @param {boolean} keepLogs True to not delete logs after flushing. - * @return {string} \n delimited log lines to flush. + * @returns {string} \n delimited log lines to flush. */ public flush(keepLogs?: boolean): string { // The ConsoleLogger doesn't care how these end up on disk, it just @@ -139,7 +139,7 @@ export class IndexedDBLogStore { } /** - * @return {Promise} Resolves when the store is ready. + * @returns {Promise} Resolves when the store is ready. */ public connect(): Promise { const req = this.indexedDB.open("logs"); @@ -195,7 +195,7 @@ export class IndexedDBLogStore { * This guarantees that we will always eventually do a flush when flush() is * called. * - * @return {Promise} Resolved when the logs have been flushed. + * @returns {Promise} Resolved when the logs have been flushed. */ public flush(): Promise { // check if a flush() operation is ongoing @@ -250,7 +250,7 @@ export class IndexedDBLogStore { * returned are deleted at the same time, so this can be called at startup * to do house-keeping to keep the logs from growing too large. * - * @return {Promise} Resolves to an array of objects. The array is + * @returns {Promise} Resolves to an array of objects. The array is * sorted in time (oldest first) based on when the log file was created (the * log ID). The objects have said log ID in an "id" field and "lines" which * is a big string with all the new-line delimited logs. @@ -455,7 +455,7 @@ export class IndexedDBLogStore { * @param {Function} resultMapper A function which is repeatedly called with a * Cursor. * Return the data you want to keep. - * @return {Promise} Resolves to an array of whatever you returned from + * @returns {Promise} Resolves to an array of whatever you returned from * resultMapper. */ function selectQuery( @@ -487,7 +487,7 @@ function selectQuery( * Modifies globals. * @param {boolean} setUpPersistence When true (default), the persistence will * be set up immediately for the logs. - * @return {Promise} Resolves when set up. + * @returns {Promise} Resolves when set up. */ export function init(setUpPersistence = true): Promise { if (global.mx_rage_initPromise) { @@ -512,7 +512,7 @@ export function init(setUpPersistence = true): Promise { /** * Try to start up the rageshake storage for logs. If not possible (client unsupported) * then this no-ops. - * @return {Promise} Resolves when complete. + * @returns {Promise} Resolves when complete. */ export function tryInitStorage(): Promise { if (global.mx_rage_initStoragePromise) { @@ -558,7 +558,7 @@ export function flush(): void { * @deprecated There is no need to call this explicitly: it will be done as a side-effect of {@link tryInitStorage}, * or {@link init} with `setUpPersistence: true`. * - * @return {Promise} Resolves if cleaned logs. + * @returns {Promise} Resolves if cleaned logs. */ export async function cleanup(): Promise { if (!global.mx_rage_store) { @@ -570,7 +570,7 @@ export async function cleanup(): Promise { /** * Get a recent snapshot of the logs, ready for attaching to a bug report * - * @return {Array<{lines: string, id, string}>} list of log data + * @returns {Array<{lines: string, id, string}>} list of log data */ export async function getLogsForReport(): Promise<{ lines: string; id: string }[]> { if (!global.mx_rage_logger) { diff --git a/apps/web/src/rageshake/submit-rageshake.ts b/apps/web/src/rageshake/submit-rageshake.ts index 071a4986a64..e69c1429c34 100644 --- a/apps/web/src/rageshake/submit-rageshake.ts +++ b/apps/web/src/rageshake/submit-rageshake.ts @@ -340,7 +340,7 @@ async function collectLogs( * * @param {function(string)} opts.progressCallback Callback to call with progress updates * - * @return {Promise} URL returned by the rageshake server + * @returns {Promise} URL returned by the rageshake server * * @throws A RageshakeError when the rageshake server responds with an error. This will be `RS_UNKNOWN` if the * the server does not respond with an expected body format. @@ -361,7 +361,7 @@ export default async function sendBugReport(bugReportEndpoint?: string, opts: IO * Loads a bug report into a tarball. * * @param opts optional dictionary of options - * @return Resolves with a Tarball object. + * @returns Resolves with a Tarball object. */ export async function loadBugReport(opts: IOpts = {}): Promise { const Tar = (await import("tar-js")).default; @@ -395,7 +395,7 @@ export async function loadBugReport(opts: IOpts = {}): Promise { * but instead causes the browser to download the files locally. * * @param opts optional dictionary of options - * @return Resolved when the bug report is downloaded (or started). + * @returns Resolved when the bug report is downloaded (or started). */ export async function downloadBugReport(opts: IOpts = {}): Promise { const tape = await loadBugReport(opts); diff --git a/apps/web/src/resizer/resizer.ts b/apps/web/src/resizer/resizer.ts index 8a814792adf..76e922b29d9 100644 --- a/apps/web/src/resizer/resizer.ts +++ b/apps/web/src/resizer/resizer.ts @@ -72,7 +72,7 @@ export default class Resizer = Resize Gives the distributor for a specific resize handle, as if you would have started to drag that handle. Can be used to manipulate the size of an item programmatically. @param {number} handleIndex the index of the resize handle in the container - @return {FixedDistributor} a new distributor for the given handle + @returns {FixedDistributor} a new distributor for the given handle */ public forHandleAt(handleIndex: number): FixedDistributor | undefined { const handles = this.getResizeHandles(); diff --git a/apps/web/src/resizer/sizer.ts b/apps/web/src/resizer/sizer.ts index 2284781c9df..a9a8868d1cf 100644 --- a/apps/web/src/resizer/sizer.ts +++ b/apps/web/src/resizer/sizer.ts @@ -19,7 +19,7 @@ export default class Sizer { /** @param {Element} item the dom element being resized - @return {number} how far the edge of the item is from the edge of the container + @returns {number} how far the edge of the item is from the edge of the container */ public getItemOffset(item: HTMLElement): number { const offset = (this.vertical ? item.offsetTop : item.offsetLeft) - this.getOffset(); @@ -32,23 +32,23 @@ export default class Sizer { /** @param {Element} item the dom element being resized - @return {number} the width/height of an item in the container + @returns {number} the width/height of an item in the container */ public getItemSize(item: HTMLElement): number { return this.vertical ? item.offsetHeight : item.offsetWidth; } - /** @return {number} the width/height of the container */ + /** @returns {number} the width/height of the container */ public getTotalSize(): number { return this.vertical ? this.container.offsetHeight : this.container.offsetWidth; } - /** @return {number} container offset to offsetParent */ + /** @returns {number} container offset to offsetParent */ private getOffset(): number { return this.vertical ? this.container.offsetTop : this.container.offsetLeft; } - /** @return {number} container offset to document */ + /** @returns {number} container offset to document */ private getPageOffset(): number { let element = this.container; let offset = 0; @@ -90,7 +90,7 @@ export default class Sizer { /** @param {MouseEvent} event the mouse event - @return {number} the distance between the cursor and the edge of the container, + @returns {number} the distance between the cursor and the edge of the container, along the applicable axis (vertical or horizontal) */ public offsetFromEvent(event: MouseEvent): number { diff --git a/apps/web/src/settings/SettingsStore.ts b/apps/web/src/settings/SettingsStore.ts index ad8627ab059..8e28c6779d8 100644 --- a/apps/web/src/settings/SettingsStore.ts +++ b/apps/web/src/settings/SettingsStore.ts @@ -276,7 +276,7 @@ export default class SettingsStore { * @param {string} settingName The setting to look up. * @param {SettingLevel} atLevel * The level to get the display name for; Defaults to 'default'. - * @return {String} The display name for the setting, or null if not found. + * @returns {String} The display name for the setting, or null if not found. */ public static getDisplayName(settingName: SettingKey, atLevel = SettingLevel.DEFAULT): string | null { if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null; @@ -299,7 +299,7 @@ export default class SettingsStore { /** * Gets the translated description for a given setting * @param {string} settingName The setting to look up. - * @return {String} The description for the setting, or null if not found. + * @returns {String} The description for the setting, or null if not found. */ public static getDescription(settingName: SettingKey): string | ReactNode { const description = SETTINGS[settingName]?.description; @@ -311,7 +311,7 @@ export default class SettingsStore { /** * Determines if a setting is also a feature. * @param {string} settingName The setting to look up. - * @return {boolean} True if the setting is a feature. + * @returns {boolean} True if the setting is a feature. */ public static isFeature(settingName: SettingKey): boolean { if (!SETTINGS[settingName]) return false; @@ -321,7 +321,7 @@ export default class SettingsStore { /** * Determines if a setting should have a warning sign in the microcopy * @param {string} settingName The setting to look up. - * @return {boolean} True if the setting should have a warning sign. + * @returns {boolean} True if the setting should have a warning sign. */ public static shouldHaveWarning(settingName: SettingKey): boolean { if (!SETTINGS[settingName]) return false; @@ -354,7 +354,7 @@ export default class SettingsStore { * If a setting is not disabled, or no reason is given by the `SettingController`, * this will return undefined. * @param {string} settingName The setting to look up. - * @return {string} The reason the setting is disabled. + * @returns {string} The reason the setting is disabled. */ public static disabledMessage(settingName: SettingKey): string | undefined { const disabled = SETTINGS[settingName].controller?.settingDisabled; @@ -367,7 +367,7 @@ export default class SettingsStore { * @param {string} settingName The name of the setting to read the value of. * @param {String} roomId The room ID to read the setting value in, may be null. * @param {boolean} excludeDefault True to disable using the default value. - * @return {*} The value, or null if not found + * @returns {*} The value, or null if not found */ public static getValue( settingName: S, @@ -404,7 +404,7 @@ export default class SettingsStore { * @param {boolean} explicit If true, this method will not consider other levels, just the one * provided. Defaults to false. * @param {boolean} excludeDefault True to disable using the default value. - * @return {*} The value, or null if not found. + * @returns {*} The value, or null if not found. */ public static getValueAt( level: SettingLevel, @@ -460,7 +460,7 @@ export default class SettingsStore { /** * Gets the default value of a setting. * @param settingName The name of the setting to read the value of. - * @return The default value + * @returns The default value */ public static getDefaultValue(settingName: S): Settings[S]["default"] { // Verify that the setting is actually a setting @@ -498,7 +498,7 @@ export default class SettingsStore { * @param level The level * to change the value at. * @param value The new value of the setting, may be null. - * @return Resolves when the setting has been changed. + * @returns Resolves when the setting has been changed. */ public static async setValue( settingName: S, @@ -556,7 +556,7 @@ export default class SettingsStore { * @param {string} settingName The name of the setting to check. * @param {String} roomId The room ID to check in, may be null. * @param {SettingLevel} level The level to check at. - * @return {boolean} True if the user may set the setting, false otherwise. + * @returns {boolean} True if the user may set the setting, false otherwise. */ public static canSetValue(settingName: SettingKey, roomId: string | null, level: SettingLevel): boolean { const setting = SETTINGS[settingName]; @@ -610,7 +610,7 @@ export default class SettingsStore { * Determines if the given level is supported on this device. * @param {SettingLevel} level The level * to check the feasibility of. - * @return {boolean} True if the level is supported, false otherwise. + * @returns {boolean} True if the level is supported, false otherwise. */ public static isLevelSupported(level: SettingLevel): boolean { if (!LEVEL_HANDLERS[level]) return false; @@ -638,7 +638,7 @@ export default class SettingsStore { * Determines the first supported level out of all the levels that can be used for a * specific setting. * @param {string} settingName The setting name. - * @return {SettingLevel} + * @returns {SettingLevel} */ public static firstSupportedLevel(settingName: SettingKey): SettingLevel | null { // Verify that the setting is actually a setting diff --git a/apps/web/src/settings/controllers/SettingController.ts b/apps/web/src/settings/controllers/SettingController.ts index b033e9377ea..9aa75aeb40b 100644 --- a/apps/web/src/settings/controllers/SettingController.ts +++ b/apps/web/src/settings/controllers/SettingController.ts @@ -32,7 +32,7 @@ export default abstract class SettingController { * may be null. * @param {SettingLevel} calculatedAtLevel The level for which the calculated value was * calculated at. May be null. - * @return {*} The value that should be used, or null if no override is applicable. + * @returns {*} The value that should be used, or null if no override is applicable. */ public getValueOverride( level: SettingLevel, @@ -48,7 +48,7 @@ export default abstract class SettingController { * @param {string} level The level at which the setting has been modified. * @param {String} roomId The room ID, may be null. * @param {*} newValue The new value for the setting, may be null. - * @return {boolean} Whether the settings change should be accepted. + * @returns {boolean} Whether the settings change should be accepted. */ public async beforeChange(level: SettingLevel, roomId: string | null, newValue: any): Promise { return true; diff --git a/apps/web/src/slash-commands/SlashCommands.tsx b/apps/web/src/slash-commands/SlashCommands.tsx index c9a13783644..ae56c9e316f 100644 --- a/apps/web/src/slash-commands/SlashCommands.tsx +++ b/apps/web/src/slash-commands/SlashCommands.tsx @@ -896,7 +896,7 @@ interface ICmd { * Process the given text for /commands and returns a parsed command that can be used for running the operation. * @param {string} roomId The room ID where the command was issued. * @param {string} input The raw text input by the user. - * @return {ICmd} The parsed command object. + * @returns {ICmd} The parsed command object. * Returns an empty object if the input didn't match a command. */ export function getCommand(roomId: string, input: string): ICmd { diff --git a/apps/web/src/stores/AsyncStore.ts b/apps/web/src/stores/AsyncStore.ts index 86a043f84a6..8d97714cf13 100644 --- a/apps/web/src/stores/AsyncStore.ts +++ b/apps/web/src/stores/AsyncStore.ts @@ -78,7 +78,7 @@ export abstract class AsyncStore extends EventEmitter { this.storeState = Object.freeze(Object.assign({}, this.storeState, newState)); this.emit(UPDATE_EVENT, this); } finally { - await this.lock.release(); + this.lock.release(); } } @@ -93,7 +93,7 @@ export abstract class AsyncStore extends EventEmitter { this.storeState = Object.freeze((newState || {})); if (!quiet) this.emit(UPDATE_EVENT, this); } finally { - await this.lock.release(); + this.lock.release(); } } diff --git a/apps/web/src/stores/OwnBeaconStore.ts b/apps/web/src/stores/OwnBeaconStore.ts index 4b6f0ddc11e..68a4b6d6a6d 100644 --- a/apps/web/src/stores/OwnBeaconStore.ts +++ b/apps/web/src/stores/OwnBeaconStore.ts @@ -579,7 +579,9 @@ export class OwnBeaconStore extends AsyncStoreWithClient { this.lastPublishedPositionTimestamp = Date.now(); await Promise.all( this.healthyLiveBeaconIds.map((beaconId) => - this.beacons.has(beaconId) ? this.sendLocationToBeacon(this.beacons.get(beaconId)!, position) : null, + this.beacons.has(beaconId) + ? this.sendLocationToBeacon(this.beacons.get(beaconId)!, position) + : Promise.resolve(), ), ); }; diff --git a/apps/web/src/stores/RoomViewStore.tsx b/apps/web/src/stores/RoomViewStore.tsx index 73050916619..9dadd8a34d2 100644 --- a/apps/web/src/stores/RoomViewStore.tsx +++ b/apps/web/src/stores/RoomViewStore.tsx @@ -552,7 +552,7 @@ export class RoomViewStore extends EventEmitter { const joinOpts: IJoinRoomOpts = { viaServers, acceptSharedHistory: true, - ...(payload.opts ?? {}), + ...payload.opts, }; try { const cli = MatrixClientPeg.safeGet(); diff --git a/apps/web/src/stores/room-list-v3/section.ts b/apps/web/src/stores/room-list-v3/section.ts index ea1fc8927ea..09aebf3c96c 100644 --- a/apps/web/src/stores/room-list-v3/section.ts +++ b/apps/web/src/stores/room-list-v3/section.ts @@ -167,7 +167,7 @@ export function getOrderedReorderableSections(): ReorderableSection[] { * If the user confirms, it generates a unique tag for the section, saves the section data in the settings, and updates the ordered list of sections. * * @param spaceId The space in which the section is being created. Used to control visibility of the empty section. - * @return A promise that resolves to the new section tag if created, or undefined if cancelled. + * @returns A promise that resolves to the new section tag if created, or undefined if cancelled. */ export async function createSection(spaceId: SpaceKey): Promise { const modal = Modal.createDialog(CreateSectionDialog); diff --git a/apps/web/src/stores/widgets/ElementWidgetDriver.ts b/apps/web/src/stores/widgets/ElementWidgetDriver.ts index 2b36b70e123..08a08acc6a6 100644 --- a/apps/web/src/stores/widgets/ElementWidgetDriver.ts +++ b/apps/web/src/stores/widgets/ElementWidgetDriver.ts @@ -600,6 +600,7 @@ export class ElementWidgetDriver extends WidgetDriver { * Otherwise, the event ID at which only subsequent events will be returned, as many as specified * in "limit". * @returns A generator that emits events. + * @yields IRoomEvents from the room timeline */ private *readRoomTimelineIterator( room: Room, diff --git a/apps/web/src/test/setupGlobals.ts b/apps/web/src/test/setupGlobals.ts index 63c80028471..4856e83b741 100644 --- a/apps/web/src/test/setupGlobals.ts +++ b/apps/web/src/test/setupGlobals.ts @@ -24,7 +24,7 @@ if (globalThis.window === undefined) { vi.stubGlobal("window", { // Mock this as some code assumes it exists (needs to be done at the top level as // things try to access it before the beforeEach blocks run) - addEventListener: vi.fn(), + addEventListener: vi.fn(), location: locationStub, setTimeout: globalThis.setTimeout, }); diff --git a/apps/web/src/utils/DMRoomMap.ts b/apps/web/src/utils/DMRoomMap.ts index 989eea09d0e..3153ed88fab 100644 --- a/apps/web/src/utils/DMRoomMap.ts +++ b/apps/web/src/utils/DMRoomMap.ts @@ -162,11 +162,11 @@ export default class DMRoomMap { commonRooms = commonRooms.filter((r) => userRooms.includes(r)); } - const joinedRooms = commonRooms - .map((r) => this.matrixClient.getRoom(r)) - .filter((r) => r && r.getMyMembership() === KnownMembership.Join); - - return joinedRooms[0]; + return ( + commonRooms + .map((r) => this.matrixClient.getRoom(r)) + .find((r) => r && r.getMyMembership() === KnownMembership.Join) ?? null + ); } public getUserIdForRoomId(roomId: string): string | undefined { diff --git a/apps/web/src/utils/FileUtils.ts b/apps/web/src/utils/FileUtils.ts index 79584890573..a0901e91b39 100644 --- a/apps/web/src/utils/FileUtils.ts +++ b/apps/web/src/utils/FileUtils.ts @@ -31,7 +31,7 @@ export function downloadLabelForFile(content: MediaEventContent, withSize = true * @param {string} fallbackText The fallback text * @param {boolean} withSize Whether to include size information. Default true. * @param {boolean} shortened Ensure the extension of the file name is visible. Default false. - * @return {string} the human-readable link text for the attachment. + * @returns {string} the human-readable link text for the attachment. */ export function presentableTextForFile( content: MediaEventContent, diff --git a/apps/web/src/utils/LruCache.test.ts b/apps/web/src/utils/LruCache.test.ts index 155c38a9df8..76904c2653b 100644 --- a/apps/web/src/utils/LruCache.test.ts +++ b/apps/web/src/utils/LruCache.test.ts @@ -40,7 +40,7 @@ describe("LruCache", () => { }); it("delete() should not raise an error", () => { - cache.delete("a"); + expect(() => cache.delete("a")).not.toThrow(); }); describe("when the cache contains 2 items", () => { @@ -87,8 +87,8 @@ describe("LruCache", () => { cache.set("c", "c value"); }); - it("deleting an unkonwn item should not raise an error", () => { - cache.delete("unknown"); + it("deleting an unknown item should not raise an error", () => { + expect(() => cache.delete("unknown")).not.toThrow(); }); it("deleting the first item should work", () => { diff --git a/apps/web/src/utils/LruCache.ts b/apps/web/src/utils/LruCache.ts index bfff80d5d53..659d27c65d8 100644 --- a/apps/web/src/utils/LruCache.ts +++ b/apps/web/src/utils/LruCache.ts @@ -131,6 +131,7 @@ export class LruCache { /** * Returns an iterator over the cached values. + * @yields each value in the cache */ public *values(): IterableIterator { for (const item of this.map.values()) { diff --git a/apps/web/src/utils/MegolmExportEncryption.ts b/apps/web/src/utils/MegolmExportEncryption.ts index 818f20b532b..b7a5cc3bff2 100644 --- a/apps/web/src/utils/MegolmExportEncryption.ts +++ b/apps/web/src/utils/MegolmExportEncryption.ts @@ -35,7 +35,7 @@ function cryptoFailMsg(): string { * * @param {ArrayBuffer} data file to decrypt * @param {String} password - * @return {Promise} promise for decrypted output + * @returns {Promise} promise for decrypted output * * */ @@ -103,7 +103,7 @@ export async function decryptMegolmKeyFile(data: ArrayBuffer, password: string): * @param {Object=} options * @param {Number=} options.kdf_rounds Number of iterations to perform of the * key-derivation function. - * @return {Promise} promise for encrypted output + * @returns {Promise} promise for encrypted output */ export async function encryptMegolmKeyFile( data: string, @@ -178,7 +178,7 @@ export async function encryptMegolmKeyFile( * @param {Unit8Array} salt salt for pbkdf * @param {Number} iterations number of pbkdf iterations * @param {String} password password - * @return {Promise<[CryptoKey, CryptoKey]>} promise for [aes key, hmac key] + * @returns {Promise<[CryptoKey, CryptoKey]>} promise for [aes key, hmac key] */ async function deriveKeys( salt: Uint8Array, @@ -251,7 +251,7 @@ const TRAILER_LINE = "-----END MEGOLM SESSION DATA-----"; * Strips the header and trailer lines, and unbase64s the content * * @param {ArrayBuffer} data input file - * @return {Uint8Array} unbase64ed content + * @returns {Uint8Array} unbase64ed content */ function unpackMegolmKeyFile(data: ArrayBuffer): Uint8Array { // parse the file as a great big String. This should be safe, because there @@ -306,7 +306,7 @@ function unpackMegolmKeyFile(data: ArrayBuffer): Uint8Array { * base64s the content, and adds header and trailer lines * * @param {Uint8Array} data raw data - * @return {ArrayBuffer} formatted file + * @returns {ArrayBuffer} formatted file */ function packMegolmKeyFile(data: Uint8Array): ArrayBuffer { // we split into lines before base64ing, because encodeBase64 doesn't deal @@ -329,7 +329,7 @@ function packMegolmKeyFile(data: Uint8Array): ArrayBuffer { /** * Encode a typed array of uint8 as base64. * @param {Uint8Array} uint8Array The data to encode. - * @return {string} The base64. + * @returns {string} The base64. */ function encodeBase64(uint8Array: Uint8Array): string { // Misinterpt the Uint8Array as Latin-1. @@ -342,7 +342,7 @@ function encodeBase64(uint8Array: Uint8Array): string { /** * Decode a base64 string to a typed array of uint8. * @param {string} base64 The base64 to decode. - * @return {Uint8Array} The decoded data. + * @returns {Uint8Array} The decoded data. */ function decodeBase64(base64: string): Uint8Array { // window.atob returns a unicode string with codepoints in the range 0-255. diff --git a/apps/web/src/utils/MessageDiffUtils.tsx b/apps/web/src/utils/MessageDiffUtils.tsx index 53d12b25136..4ccb71a9f14 100644 --- a/apps/web/src/utils/MessageDiffUtils.tsx +++ b/apps/web/src/utils/MessageDiffUtils.tsx @@ -246,7 +246,7 @@ function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatc * Renders a message with the changes made in an edit shown visually. * @param {IContent} originalContent the content for the base message * @param {IContent} editContent the content for the edit message - * @return {JSX.Element} a react element similar to what `bodyToHtml` returns + * @returns {JSX.Element} a react element similar to what `bodyToHtml` returns */ export function editBodyDiffToHtml(originalContent: IContent, editContent: IContent): JSX.Element { // wrap the body in a div, DiffDOM needs a root element diff --git a/apps/web/src/utils/PinningUtils.ts b/apps/web/src/utils/PinningUtils.ts index 19a1a9a7265..6028bec686a 100644 --- a/apps/web/src/utils/PinningUtils.ts +++ b/apps/web/src/utils/PinningUtils.ts @@ -33,7 +33,7 @@ export default class PinningUtils { * Determines if the given event can be pinned. * This is a simple check to see if the event is of a type that can be pinned. * @param {MatrixEvent} event The event to check. - * @return {boolean} True if the event may be pinned, false otherwise. + * @returns {boolean} True if the event may be pinned, false otherwise. */ public static isPinnable(event: MatrixEvent): boolean { if (event.isRedacted()) return false; @@ -43,7 +43,7 @@ export default class PinningUtils { /** * Determines if the given event may be unpinned. * @param {MatrixEvent} event The event to check. - * @return {boolean} True if the event may be unpinned, false otherwise. + * @returns {boolean} True if the event may be unpinned, false otherwise. */ public static isUnpinnable(event: MatrixEvent): boolean { if (!event) return false; diff --git a/apps/web/src/utils/Reply.ts b/apps/web/src/utils/Reply.ts index 3b595e86934..249c901fcef 100644 --- a/apps/web/src/utils/Reply.ts +++ b/apps/web/src/utils/Reply.ts @@ -85,7 +85,7 @@ export function shouldDisplayReply(event: MatrixEvent): boolean { export function addReplyToMessageContent(content: IContent, replyToEvent: MatrixEvent): void { content["m.relates_to"] = { - ...(content["m.relates_to"] || {}), + ...content["m.relates_to"], ...makeReplyMixIn(replyToEvent), }; } diff --git a/apps/web/src/utils/RoomUpgrade.ts b/apps/web/src/utils/RoomUpgrade.ts index d92038e0db8..3bbdcb64bf0 100644 --- a/apps/web/src/utils/RoomUpgrade.ts +++ b/apps/web/src/utils/RoomUpgrade.ts @@ -132,7 +132,7 @@ export async function upgradeRoom( parent.roomId, EventType.SpaceChild, { - ...(currentEv?.getContent() || {}), // copy existing attributes like suggested + ...currentEv?.getContent(), // copy existing attributes like suggested via: [cli.getDomain()!], }, newRoomId, diff --git a/apps/web/src/utils/Timer.ts b/apps/web/src/utils/Timer.ts index c5d9084c310..f078bcbe55e 100644 --- a/apps/web/src/utils/Timer.ts +++ b/apps/web/src/utils/Timer.ts @@ -105,7 +105,7 @@ export default class Timer { /** *promise that will resolve when the timer elapses, *or is rejected when abort is called - *@return {Promise} + *@returns {Promise} */ public finished(): Promise { return this.deferred.promise; diff --git a/apps/web/src/utils/WidgetUtils.ts b/apps/web/src/utils/WidgetUtils.ts index 41232db3c44..86e5af1412b 100644 --- a/apps/web/src/utils/WidgetUtils.ts +++ b/apps/web/src/utils/WidgetUtils.ts @@ -44,7 +44,7 @@ export default class WidgetUtils { * (Does not apply to non-room-based / user widgets) * @param client The matrix client of the logged-in user * @param roomId -- The ID of the room to check - * @return Boolean -- true if the user can modify widgets in this room + * @returns Boolean -- true if the user can modify widgets in this room * @throws Error -- specifies the error reason */ public static canUserModifyWidgets(client: MatrixClient, roomId?: string): boolean { @@ -84,7 +84,7 @@ export default class WidgetUtils { * Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api * @param matrixClient The matrix client of the logged-in user * @param {[type]} testUrlString URL to check - * @return {Boolean} True if specified URL is a scalar URL + * @returns {Boolean} True if specified URL is a scalar URL */ public static isScalarUrl(testUrlString?: string): boolean { if (!testUrlString) { @@ -339,7 +339,7 @@ export default class WidgetUtils { /** * Get room specific widgets * @param {Room} room The room to get widgets force - * @return {[object]} Array containing current / active room widgets + * @returns {[object]} Array containing current / active room widgets */ public static getRoomWidgets(room: Room): MatrixEvent[] { // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) @@ -356,7 +356,7 @@ export default class WidgetUtils { /** * Get user specific widgets (not linked to a specific room) * @param client The matrix client of the logged-in user - * @return {object} Event content object containing current / active user widgets + * @returns {object} Event content object containing current / active user widgets */ public static getUserWidgets(client: MatrixClient | undefined): Record { if (!client) { @@ -372,7 +372,7 @@ export default class WidgetUtils { /** * Get user specific widgets (not linked to a specific room) as an array * @param client The matrix client of the logged-in user - * @return {[object]} Array containing current / active user widgets + * @returns {[object]} Array containing current / active user widgets */ public static getUserWidgetsArray(client: MatrixClient | undefined): UserWidget[] { return Object.values(WidgetUtils.getUserWidgets(client)); @@ -381,7 +381,7 @@ export default class WidgetUtils { /** * Get active stickerpicker widgets (stickerpickers are user widgets by nature) * @param client The matrix client of the logged-in user - * @return {[object]} Array containing current / active stickerpicker widgets + * @returns {[object]} Array containing current / active stickerpicker widgets */ public static getStickerpickerWidgets(client: MatrixClient | undefined): UserWidget[] { const widgets = WidgetUtils.getUserWidgetsArray(client); @@ -401,7 +401,7 @@ export default class WidgetUtils { /** * Remove all stickerpicker widgets (stickerpickers are user widgets by nature) * @param client The matrix client of the logged-in user - * @return {Promise} Resolves on account data updated + * @returns {Promise} Resolves on account data updated */ public static async removeStickerpickerWidgets(client: MatrixClient | undefined): Promise { if (!client) { diff --git a/apps/web/src/utils/image-media.ts b/apps/web/src/utils/image-media.ts index 03c34fb39ea..78ba51276cb 100644 --- a/apps/web/src/utils/image-media.ts +++ b/apps/web/src/utils/image-media.ts @@ -38,7 +38,7 @@ const MAX_HEIGHT = 600; * @param {number} inputHeight the width of the image in the input element. * @param {string} mimeType The mimeType to save the blob as. * @param {boolean} calculateBlurhash Whether to calculate a blurhash of the given image too. - * @return {Promise} A promise that resolves with an object with an info key + * @returns {Promise} A promise that resolves with an object with an info key * and a thumbnail key. */ export async function createThumbnail( diff --git a/apps/web/src/utils/local-room.ts b/apps/web/src/utils/local-room.ts index 90d25bd4c01..5d48ed116ff 100644 --- a/apps/web/src/utils/local-room.ts +++ b/apps/web/src/utils/local-room.ts @@ -124,7 +124,7 @@ export async function waitForRoomReadyAndApplyAfterCreateCallbacks( */ async function applyAfterCreateCallbacks(localRoom: LocalRoom, roomId: string): Promise { for (const afterCreateCallback of localRoom.afterCreateCallbacks) { - await afterCreateCallback(roomId); + afterCreateCallback(roomId); } localRoom.afterCreateCallbacks = []; diff --git a/apps/web/src/utils/messages.ts b/apps/web/src/utils/messages.ts index b6040b536ba..2a5641f37a1 100644 --- a/apps/web/src/utils/messages.ts +++ b/apps/web/src/utils/messages.ts @@ -102,7 +102,7 @@ export function attachMentions( export function attachRelation(content: IContent, relation?: IEventRelation): void { if (relation) { content["m.relates_to"] = { - ...(content["m.relates_to"] || {}), + ...content["m.relates_to"], ...relation, }; } diff --git a/apps/web/src/utils/tokens/pickling.ts b/apps/web/src/utils/tokens/pickling.ts index 3415624e64c..f0658ccee30 100644 --- a/apps/web/src/utils/tokens/pickling.ts +++ b/apps/web/src/utils/tokens/pickling.ts @@ -46,7 +46,7 @@ export interface EncryptedPickleKey { * * @param {string} userId The user ID who owns the pickle key. * @param {string} deviceId The device ID which owns the pickle key. - * @return {Uint8Array} The additional data as a Uint8Array. + * @returns {Uint8Array} The additional data as a Uint8Array. */ export function getPickleAdditionalData(userId: string, deviceId: string): Uint8Array { const additionalData = new Uint8Array(userId.length + deviceId.length + 1); diff --git a/apps/web/src/vector/url_utils.ts b/apps/web/src/vector/url_utils.ts index ea0c8dc6543..b004aff4e5f 100644 --- a/apps/web/src/vector/url_utils.ts +++ b/apps/web/src/vector/url_utils.ts @@ -94,7 +94,7 @@ export type URLParams = Partial<{ * Utility to parse parameters held in the app's URL. * Currently focusing only on at-load URL parameters. * @param url - the URL to parse. - * @return an object keyed by the groups defined in {@link urlParameterConfig} with values for each key listed, + * @returns an object keyed by the groups defined in {@link urlParameterConfig} with values for each key listed, * sourced from the location (query/fragment/either) specified. If no parameters in a group are found the entire group * will be omitted from the returned object to simplify presence checking. */ diff --git a/apps/web/src/viewmodels/message-body/FileBodyViewModel.ts b/apps/web/src/viewmodels/message-body/FileBodyViewModel.ts index b86dbc13913..d5033d37426 100644 --- a/apps/web/src/viewmodels/message-body/FileBodyViewModel.ts +++ b/apps/web/src/viewmodels/message-body/FileBodyViewModel.ts @@ -85,7 +85,7 @@ cacheDownloadIcon(); /** * Get the current CSS style for a DOMElement. * @param {HTMLElement} element The element to get the current style of. - * @return {string} The CSS style encoded as a string. + * @returns {string} The CSS style encoded as a string. */ function computedStyle(element: HTMLElement | null): string { if (!element) { diff --git a/apps/web/test/test-utils/room.ts b/apps/web/test/test-utils/room.ts index 60eaed5b4db..458d5efe301 100644 --- a/apps/web/test/test-utils/room.ts +++ b/apps/web/test/test-utils/room.ts @@ -10,8 +10,7 @@ import { type MockedObject } from "jest-mock-vitest-adapter"; import { type EventTimeline, EventType, type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; -import { MainSplitContentType } from "../../src/contexts/RoomContext"; -import { type RoomContextType, TimelineRenderingType } from "../../src/contexts/RoomContext"; +import { MainSplitContentType, type RoomContextType, TimelineRenderingType } from "../../src/contexts/RoomContext"; import { Layout } from "../../src/settings/enums/Layout"; import { mkEvent } from "./test-utils"; import { SDKContextClass } from "../../src/contexts/SDKContextClass"; diff --git a/apps/web/test/unit-tests/components/structures/auth/Login-test.tsx b/apps/web/test/unit-tests/components/structures/auth/Login-test.tsx index f889f7363a7..38c8116b6e2 100644 --- a/apps/web/test/unit-tests/components/structures/auth/Login-test.tsx +++ b/apps/web/test/unit-tests/components/structures/auth/Login-test.tsx @@ -13,10 +13,10 @@ import { OAUTH_AWARE_PREFERRED_FLOW_FIELD, IdentityProviderBrand, type ValidatedAuthMetadata, + OAuth2Error, } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import * as Matrix from "matrix-js-sdk/src/matrix"; -import { OAuth2Error } from "matrix-js-sdk/src/matrix"; import SdkConfig from "../../../../../src/SdkConfig"; import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../../test-utils"; diff --git a/apps/web/test/unit-tests/components/views/dialogs/devtools/Users-test.tsx b/apps/web/test/unit-tests/components/views/dialogs/devtools/Users-test.tsx index 76ef540e6ff..f8d8bb0c278 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/devtools/Users-test.tsx +++ b/apps/web/test/unit-tests/components/views/dialogs/devtools/Users-test.tsx @@ -7,9 +7,16 @@ import React from "react"; import { mocked } from "jest-mock"; -import { Device, DeviceVerification, type MatrixClient, MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix"; +import { + Device, + DeviceVerification, + type MatrixClient, + MatrixEvent, + RoomMember, + Room, + PendingEventOrdering, +} from "matrix-js-sdk/src/matrix"; import { render, screen, waitFor } from "jest-matrix-react"; -import { Room, PendingEventOrdering } from "matrix-js-sdk/src/matrix"; import { type DeviceVerificationStatus, type UserVerificationStatus } from "matrix-js-sdk/src/crypto-api"; import { createTestClient } from "../../../../../test-utils"; diff --git a/apps/web/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx b/apps/web/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx index 96199f5ad94..e51fc0c0956 100644 --- a/apps/web/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx +++ b/apps/web/test/unit-tests/components/views/rooms/SendMessageComposer-test.tsx @@ -17,7 +17,11 @@ import SendMessageComposer, { isQuickReaction, } from "../../../../../src/components/views/rooms/SendMessageComposer"; import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; -import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext"; +import { + type RoomContextType, + TimelineRenderingType, + MainSplitContentType, +} from "../../../../../src/contexts/RoomContext"; import EditorModel from "../../../../../src/editor/model"; import { createPartCreator } from "../../../editor/mock"; import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../../test-utils"; @@ -25,7 +29,6 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg"; import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; import DocumentOffset from "../../../../../src/editor/offset"; import { Layout } from "../../../../../src/settings/enums/Layout"; -import { MainSplitContentType } from "../../../../../src/contexts/RoomContext"; import { mockPlatformPeg } from "../../../../test-utils/platform"; import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room"; import { addTextToComposer } from "../../../../test-utils/composer"; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 6cb24220756..16e41c0535e 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -11,7 +11,7 @@ "target": "es2022", "noUnusedLocals": true, "sourceMap": false, - "outDir": "./lib", + "noEmit": true, "declaration": true, "jsx": "react", "lib": ["es2022", "es2024.promise", "dom", "dom.iterable", "ESNext.Collection"], diff --git a/apps/web/webpack.config.ts b/apps/web/webpack.config.ts index 00e6059c8a9..e41abc28a00 100644 --- a/apps/web/webpack.config.ts +++ b/apps/web/webpack.config.ts @@ -825,7 +825,7 @@ export default (env: string, argv: Record): webpack.Configuration = * * @param url The adjusted name of the file, such as `warning.1234567.svg`. * @param resourcePath The absolute path to the source file with unmodified name. - * @return The returned paths will look like `img/warning.1234567.svg`. + * @returns The returned paths will look like `img/warning.1234567.svg`. */ function getAssetOutputPath(url: string, resourcePath: string): string { const isKaTeX = resourcePath.includes("KaTeX"); diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 6a59f394153..f8e0d39ed7c 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + import { withMermaid } from "vitepress-plugin-mermaid"; function customPathResolver(href: string, currentPath: string) { diff --git a/docs/generated/[id].paths.ts b/docs/generated/[id].paths.ts index 58531ec8bd5..acb0715397f 100644 --- a/docs/generated/[id].paths.ts +++ b/docs/generated/[id].paths.ts @@ -1,3 +1,10 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + import genWorkflowMermaid from "../../scripts/gen-workflow-mermaid"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/knip.ts b/knip.ts index 893226ec417..d15d9f46369 100644 --- a/knip.ts +++ b/knip.ts @@ -1,4 +1,11 @@ -import { KnipConfig } from "knip"; +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { type KnipConfig } from "knip"; // Specify this as knip loads config files which may conditionally load plugins process.env.GITHUB_ACTIONS = "1"; diff --git a/modules/.eslintrc.cjs b/modules/.eslintrc.cjs deleted file mode 100644 index 0c4c544a6a9..00000000000 --- a/modules/.eslintrc.cjs +++ /dev/null @@ -1,75 +0,0 @@ -module.exports = { - plugins: ["matrix-org", "eslint-plugin-react-compiler"], - extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react", "plugin:matrix-org/a11y"], - parserOptions: { - project: true, - }, - env: { - browser: true, - node: true, - }, - rules: { - "react/jsx-key": ["error"], - - "no-restricted-properties": [ - "error", - ...buildRestrictedPropertiesOptions( - ["window.innerHeight", "window.innerWidth", "window.visualViewport"], - "Use UIStore to access window dimensions instead.", - ), - ...buildRestrictedPropertiesOptions( - ["*.mxcUrlToHttp", "*.getHttpUriForMxc"], - "Use Media helper instead to centralise access for customisation.", - ), - ...buildRestrictedPropertiesOptions(["window.setImmediate"], "Use setTimeout instead."), - ], - "no-restricted-globals": [ - "error", - { - name: "setImmediate", - message: "Use setTimeout instead.", - }, - { - name: "Buffer", - message: "Buffer is not available in the web.", - }, - ], - - "import/no-duplicates": ["error"], - "matrix-org/require-copyright-header": "error", - - "react-compiler/react-compiler": "error", - }, - overrides: [ - { - files: ["playwright/**/*.ts", "*/e2e/**/*.{ts,tsx}", "*/src/**/*.test.{ts,tsx}"], - rules: { - // This is necessary for Playwright fixtures - "no-empty-pattern": "off", - // This is necessary for Playwright fixtures - "react-hooks/rules-of-hooks": "off", - // This just gets annoying in test code - "@typescript-eslint/explicit-function-return-type": "off", - }, - }, - ], - settings: { - react: { - version: "19", - }, - }, -}; - -function buildRestrictedPropertiesOptions(properties, message) { - return properties.map((prop) => { - let [object, property] = prop.split("."); - if (object === "*") { - object = undefined; - } - return { - object, - property, - message, - }; - }); -} diff --git a/modules/.lintstagedrc b/modules/.lintstagedrc deleted file mode 100644 index 1f28a28d5c9..00000000000 --- a/modules/.lintstagedrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "*": "oxfmt --no-error-on-unmatched-pattern" -} diff --git a/modules/banner/e2e/banner.spec.ts b/modules/banner/e2e/banner.spec.ts index 3892865aa1d..93db62482c9 100644 --- a/modules/banner/e2e/banner.spec.ts +++ b/modules/banner/e2e/banner.spec.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import { input } from "zod/mini"; +import { type input } from "zod/mini"; import { test as base, expect } from "../../playwright/element-web-test.ts"; import { type ConfigSchema } from "../src/config.ts"; @@ -160,7 +160,6 @@ test.describe("Banner", () => { await expect(sidebar).toMatchScreenshot(`${type}_menu_loading.png`); navigationJsonResolver.resolve(); } - await page.pause(); const emailApp = page.getByText("E-Mail"); await expect(emailApp).toHaveAttribute("href", "https://example.com/email"); diff --git a/modules/banner/src/Univention/SilentLogin.tsx b/modules/banner/src/Univention/SilentLogin.tsx index cc2a0f7e1b0..0395410136f 100644 --- a/modules/banner/src/Univention/SilentLogin.tsx +++ b/modules/banner/src/Univention/SilentLogin.tsx @@ -14,7 +14,7 @@ const HiddenIFrame = styled.iframe` interface Props { icsUrl: string; - onLoggedIn(success: boolean): void; + onLoggedIn(this: void, success: boolean): void; } const SilentLogin: FC = ({ onLoggedIn, icsUrl }) => { diff --git a/modules/banner/tsconfig.json b/modules/banner/tsconfig.json index ada1a94c2af..4678d3975af 100644 --- a/modules/banner/tsconfig.json +++ b/modules/banner/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "lib", + "noEmit": true, "jsx": "react-jsx" }, "include": ["src"] diff --git a/modules/package.json b/modules/package.json index 981a9227418..7f18fa985b1 100644 --- a/modules/package.json +++ b/modules/package.json @@ -3,21 +3,13 @@ "version": "0.0.0", "type": "module", "scripts": { - "lint:js": "eslint --max-warnings 0 playwright */src -c ./.eslintrc.cjs", "lint:types": "nx lint:types --", "test:playwright": "nx test:playwright --", "test:playwright:open": "nx test:playwright -- --ui", "test:playwright:screenshots": "nx test:playwright:screenshots --" }, "devDependencies": { - "@element-hq/element-web-playwright-common": "workspace:*", - "eslint": "8", - "eslint-config-google": "^0.14.0", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-matrix-org": "^3.0.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-compiler": "^19.1.0-rc.2", - "eslint-plugin-unicorn": "^56.0.0" + "@element-hq/element-web-playwright-common": "workspace:*" }, "private": true } diff --git a/modules/restricted-guests/e2e/services.ts b/modules/restricted-guests/e2e/services.ts index f99b15f9506..5e556b1ebc9 100644 --- a/modules/restricted-guests/e2e/services.ts +++ b/modules/restricted-guests/e2e/services.ts @@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details. */ import { - StartedSynapseContainer, + type StartedSynapseContainer, SynapseContainer, } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; import { Readable } from "node:stream"; diff --git a/modules/restricted-guests/src/AuthFooter.tsx b/modules/restricted-guests/src/AuthFooter.tsx index 5ff6b118514..be6bf18d34f 100644 --- a/modules/restricted-guests/src/AuthFooter.tsx +++ b/modules/restricted-guests/src/AuthFooter.tsx @@ -16,7 +16,7 @@ import RegisterDialog from "./RegisterDialog.tsx"; interface Props { api: Api; config: ModuleConfig; - onLoggedIn(data: AccountAuthInfo): void; + onLoggedIn(this: void, data: AccountAuthInfo): void; } const Container = styled.aside` diff --git a/modules/tsconfig.json b/modules/tsconfig.json index d1b893ecd8d..03d3f708a87 100644 --- a/modules/tsconfig.json +++ b/modules/tsconfig.json @@ -10,7 +10,8 @@ "moduleResolution": "bundler", "module": "ESNext", "allowImportingTsExtensions": true, - "skipLibCheck": true + "skipLibCheck": true, + "noEmit": true }, "include": ["playwright.config.ts", "playwright"] } diff --git a/modules/widget-toggles/tsconfig.json b/modules/widget-toggles/tsconfig.json index ada1a94c2af..32abda69d76 100644 --- a/modules/widget-toggles/tsconfig.json +++ b/modules/widget-toggles/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "lib", - "jsx": "react-jsx" + "jsx": "react-jsx", + "noEmit": true }, "include": ["src"] } diff --git a/oxlint.config.ts b/oxlint.config.ts new file mode 100644 index 00000000000..703e6cbd816 --- /dev/null +++ b/oxlint.config.ts @@ -0,0 +1,464 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { defineConfig } from "oxlint"; + +const defaultRestrictedProperties = [ + { object: "window", property: "setImmediate", message: "Use setTimeout instead" }, + // TODO we will enable this in a follow up PR + // ...buildRestrictedPropertiesOptions(["React.forwardRef", "*.forwardRef", "forwardRef"], "Use ref props instead."), +] as const; +const defaultRestrictedGlobals = [ + { + name: "setImmediate", + message: "Use setTimeout instead.", + }, +]; + +export default defineConfig({ + $schema: "./node_modules/oxlint/configuration_schema.json", + plugins: [ + "eslint", + "typescript", + "unicorn", + "import", + "jsdoc", + "node", + "promise", + "vitest", + "react", + "react-perf", + "jsx-a11y", + ], + jsPlugins: ["eslint-plugin-element-call"], + categories: { + correctness: "error", + perf: "error", + }, + options: { + typeAware: true, + reportUnusedDisableDirectives: "off", + maxWarnings: 0, + denyWarnings: true, + }, + env: { + es6: true, + }, + ignorePatterns: [ + "**/lib", + "**/dist", + "**/node_modules", + "**/coverage", + "apps/web/src/vector/modernizr.cjs", + // Legacy skinning file that some people might still have + "apps/web/src/component-index.js", + // Auto-generated files + "apps/web/src/modules.ts", + "apps/web/src/modules.js", + // Test result files + "**/test-results", + "**/html-report", + // Shared components generated files + "/packages/shared-components/dist/", + "/packages/shared-components/src/i18n/i18nKeys.d.ts", + "/packages/shared-components/typedoc/", + ], + settings: { + jsdoc: { + tagNamePreference: { + remark: "remarks", + privateRemarks: "privateRemarks", + experimental: "experimental", + deprecated: "deprecated", + typeParam: "typeParam", + defaultValue: "defaultValue", + packageDocumentation: "packageDocumentation", + alpha: "alpha", + knipignore: "knipignore", + resolves: "resolves", + }, + }, + }, + rules: { + "no-constant-condition": ["error", { checkLoops: "allExceptWhileTrue" }], + "typescript/unbound-method": ["error", { ignoreStatic: true }], + "typescript/no-empty-object-type": [ + "error", + { + allowInterfaces: "with-single-extends", + }, + ], + "prefer-const": ["error", { destructuring: "all" }], + "import/first": "error", + + "unicorn/no-instanceof-array": "error", + "no-restricted-globals": ["error", ...defaultRestrictedGlobals], + "no-restricted-properties": ["error", ...defaultRestrictedProperties], + "import/no-duplicates": ["error"], + + "element-call/copyright-header": [ + "error", + "/*\nCopyright %%CURRENT_YEAR%% Element Creations Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE in the repository root for full details.\n*/\n\n", + ], + + // Allow the use of underscore to show args are not used. + // This is helpful for seeing that a function implements + // an interface but won't be using one of it's arguments. + "typescript/no-unused-vars": ["error", { args: "none", ignoreRestSiblings: true }], + + // Disable some rules here, but they are enabled for src + "typescript/explicit-function-return-type": "off", + "typescript/explicit-member-accessibility": "off", + + // Require us to be more explicit about type conversions to help prevent bugs + "typescript/no-base-to-string": ["error"], + + // Prevent invalid non-type re-exports of types, these can cause downstream build failures + "typescript/consistent-type-exports": ["error"], + + // Prevent unnecessary runtime dependencies between files + "typescript/consistent-type-imports": ["error", { fixStyle: "inline-type-imports" }], + + // Disable some perf rules + "no-await-in-loop": "off", + + // Disable some opinionated rules + "unicorn/switch-case-braces": "off", + "sort-keys": "off", + "typescript/require-array-sort-compare": "off", + "eslint/no-extra-boolean-cast": "off", + + // These would be nice to enable at some point + "unicorn/prefer-set-has": "off", + "unicorn/prefer-string-slice": "off", + "unicorn/prefer-number-properties": "off", + "unicorn/prefer-at": "off", + "unicorn/no-new-array": "off", + "unicorn/no-array-for-each": "off", + "unicorn/explicit-length-check": "off", + "unicorn/catch-error-name": "off", + "require-unicode-regexp": "off", + "typescript/restrict-template-expressions": "off", + "typescript/no-redundant-type-constituents": "off", + "typescript/no-useless-default-assignment": "off", + "typescript/no-duplicate-type-constituents": "off", + "no-unused-vars": "off", + "eslint/no-unused-vars": "off", + "typescript/no-floating-promises": "off", + "typescript/no-implied-eval": "off", + "typescript/no-misused-spread": "off", + "promise/valid-params": "off", + "no-extra-boolean-cast": "off", + "react-perf/jsx-no-new-function-as-prop": "off", + "react-perf/jsx-no-new-object-as-prop": "off", + "react-perf/jsx-no-jsx-as-prop": "off", + "jsx-a11y/prefer-tag-over-role": "off", + "jsx-a11y/no-autofocus": "off", + "react/no-children-prop": "off", + "jsx-a11y/no-noninteractive-tabindex": "off", + "react-perf/jsx-no-new-array-as-prop": "off", + "react/no-did-update-set-state": "off", + "react/no-did-mount-set-state": "off", + "jsx-a11y/interactive-supports-focus": "off", + "jsx-a11y/no-static-element-interactions": "off", + "jsx-a11y/click-events-have-key-events": "off", + "vitest/no-conditional-tests": "off", + "jsx-a11y/no-noninteractive-element-interactions": "off", + "react/no-array-index-key": "off", + "jsx-a11y/control-has-associated-label": "off", + "jsx-a11y/role-supports-aria-props": "off", + "jsx-a11y/media-has-caption": "off", + "jsx-a11y/no-noninteractive-element-to-interactive-role": "off", + "jsx-a11y/aria-activedescendant-has-tabindex": "off", + "jsx-a11y/role-has-required-aria-props": "off", + }, + overrides: [ + { + files: ["apps/web/src/**/*"], + rules: { + "no-restricted-properties": [ + "error", + ...defaultRestrictedProperties, + ...buildRestrictedPropertiesOptions( + ["window.innerHeight", "window.innerWidth", "window.visualViewport"], + "Use UIStore to access window dimensions instead.", + ), + ...buildRestrictedPropertiesOptions( + ["*.mxcUrlToHttp", "*.getHttpUriForMxc"], + "Use Media helper instead to centralise access for customisation.", + ), + ], + + // Ban matrix-js-sdk/src imports in favour of matrix-js-sdk/src/matrix imports to prevent unleashing hell. + // Ban compound-design-tokens raw svg imports in favour of their React component counterparts + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "react", + importNames: ["forwardRef"], + message: "Use ref props instead.", + }, + { + name: "@testing-library/react", + message: "Please use jest-matrix-react instead", + }, + { + name: "matrix-js-sdk", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/src", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/src/", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "matrix-js-sdk/src/index", + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + name: "emojibase-regex", + message: + "This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.", + }, + ], + patterns: [ + { + group: [ + "matrix-js-sdk/src/**", + "!matrix-js-sdk/src/matrix", + "!matrix-js-sdk/src/crypto-api", + "!matrix-js-sdk/src/types", + "!matrix-js-sdk/src/testing", + "!matrix-js-sdk/src/utils/**", + "matrix-js-sdk/src/utils/internal/**", + "matrix-js-sdk/lib", + "matrix-js-sdk/lib/", + "matrix-js-sdk/lib/**", + // XXX: Temporarily allow these as they are not available via the main export + "!matrix-js-sdk/src/logger", + "!matrix-js-sdk/src/errors", + "!matrix-js-sdk/src/utils", + "!matrix-js-sdk/src/version-support", + "!matrix-js-sdk/src/randomstring", + "!matrix-js-sdk/src/sliding-sync", + "!matrix-js-sdk/src/browser-index", + "!matrix-js-sdk/src/feature", + "!matrix-js-sdk/src/NamespacedValue", + "!matrix-js-sdk/src/ReEmitter", + "!matrix-js-sdk/src/event-mapper", + "!matrix-js-sdk/src/interactive-auth", + "!matrix-js-sdk/src/secret-storage", + "!matrix-js-sdk/src/room-hierarchy", + "!matrix-js-sdk/src/rendezvous", + "!matrix-js-sdk/src/indexeddb-worker", + "!matrix-js-sdk/src/pushprocessor", + "!matrix-js-sdk/src/extensible_events_v1", + "!matrix-js-sdk/src/extensible_events_v1/PollStartEvent", + "!matrix-js-sdk/src/extensible_events_v1/PollResponseEvent", + "!matrix-js-sdk/src/extensible_events_v1/PollEndEvent", + "!matrix-js-sdk/src/extensible_events_v1/InvalidEventError", + "!matrix-js-sdk/src/oidc", + "!matrix-js-sdk/src/oidc/discovery", + "!matrix-js-sdk/src/oidc/authorize", + "!matrix-js-sdk/src/oidc/validate", + "!matrix-js-sdk/src/oidc/error", + "!matrix-js-sdk/src/oidc/register", + "!matrix-js-sdk/src/webrtc", + "!matrix-js-sdk/src/webrtc/call", + "!matrix-js-sdk/src/webrtc/callFeed", + "!matrix-js-sdk/src/webrtc/mediaHandler", + "!matrix-js-sdk/src/webrtc/callEventTypes", + "!matrix-js-sdk/src/webrtc/callEventHandler", + "!matrix-js-sdk/src/webrtc/groupCallEventHandler", + "!matrix-js-sdk/src/models", + "!matrix-js-sdk/src/models/read-receipt", + "!matrix-js-sdk/src/models/relations-container", + "!matrix-js-sdk/src/models/related-relations", + "!matrix-js-sdk/src/matrixrtc", + ], + message: "Please use matrix-js-sdk/src/matrix instead", + }, + { + group: ["emojibase-regex/emoji*"], + message: + "This regex doesn't actually test for emoji. See the docs at https://emojibase.dev/docs/regex/ and prefer our own EMOJI_REGEX from HtmlUtils.", + }, + { + group: ["@vector-im/compound-design-tokens/icons/*"], + message: "Please use @vector-im/compound-design-tokens/assets/web/icons/* instead", + }, + { + group: ["**/packages/shared-components/**", "../packages/shared-components/**"], + message: "Please use @element-hq/web-shared-components", + }, + ], + }, + ], + }, + }, + { + files: ["apps/web/src/**/*", "{packages,modules}/*/src/**/*"], + rules: { + "no-restricted-globals": [ + "error", + defaultRestrictedGlobals, + { + name: "Buffer", + message: "Buffer is not available in the web.", + }, + ], + }, + }, + { + files: ["packages/shared-components/**/*"], + rules: { + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "react", + importNames: ["act"], + message: "Please use @test-utils instead.", + }, + ], + }, + ], + + // This would be good to apply globally in the future + "react/forbid-elements": [ + "error", + { + forbid: [ + { element: "h1", message: "Use Compound instead" }, + { element: "h2", message: "Use Compound instead" }, + { element: "h3", message: "Use Compound instead" }, + { element: "h4", message: "Use Compound instead" }, + { element: "h5", message: "Use Compound instead" }, + { element: "h6", message: "Use Compound instead" }, + ], + }, + ], + }, + }, + { + files: ["{packages,apps,modules/*/src/**/*"], + rules: { + "no-console": "error", + // Require method signatures to be explicit to help make signature changes more obvious in review + "typescript/explicit-function-return-type": [ + "error", + { + allowExpressions: true, + }, + ], + "typescript/explicit-member-accessibility": "error", + + "no-restricted-imports": [ + "error", + { + name: "events", + message: "Please use TypedEventEmitter instead", + }, + ], + + "react/react-compiler": "error", + }, + }, + { + files: [ + "{packages,apps,modules}/*/src/**/*.{test,stories}.{ts,tsx}", + "{packages,apps,modules}/*/src/{tests,__mocks__}/*.{ts,tsx}", + "{packages,apps,modules}/*/{test,playwright,e2e}/**/*", + "{packages,apps,modules}/*/playwright.config.ts", + "{packages,apps,modules}/*/.storybook/**/*", + "packages/playwright-common/src/**/*", + ], + rules: { + // Tests can be linted a little more flexibly + // We don't need super strict typing in test utilities + "no-empty-pattern": "off", + "no-import-assign": "off", + "no-unsafe-optional-chaining": "off", + "typescript/no-empty-object-type": "off", + "typescript/unbound-method": "off", + "typescript/no-floating-promises": "off", + "typescript/no-misused-spread": "off", + "vitest/require-mock-type-parameters": "off", + "vitest/no-disabled-tests": "off", + "vitest/no-conditional-expect": "off", + "vitest/warn-todo": "off", + "vitest/require-to-throw-message": "off", + "vitest/prefer-snapshot-hint": "off", + "vitest/no-standalone-expect": [ + "error", + { + additionalTestBlockFunctions: ["beforeAll", "beforeEach"], + }, + ], + "vitest/expect-expect": [ + "error", + { + assertFunctionNames: ["expect*", "*Test", "assert*", "test*Factory"], + }, + ], + "jsdoc/check-tag-names": "off", + + "react/jsx-no-constructed-context-values": "off", + "jsx-a11y/interactive-supports-focus": "off", + "jsx-a11y/no-static-element-interactions": "off", + "react/no-array-index-key": "off", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/media-has-caption": "off", + "jsx-a11y/no-noninteractive-element-to-interactive-role": "off", + "react/forbid-elements": "off", + // This would be good to enable in the future + "typescript/await-thenable": "off", + "promise/no-callback-in-promise": "off", + }, + }, + { + files: ["{packages,apps,modules}/*/src/**/*.stories.{ts,tsx}"], + jsPlugins: ["eslint-plugin-element-call", "eslint-plugin-storybook"], + rules: { + "storybook/meta-satisfies-type": "error", + "storybook/default-exports": "error", + "storybook/hierarchy-separator": "error", + "storybook/no-redundant-story-name": "error", + "storybook/no-renderer-packages": "error", + "storybook/no-stories-of": "error", + "storybook/story-exports": "error", + "storybook/use-storybook-expect": "error", + "storybook/use-storybook-testing-library": "error", + "storybook/no-uninstalled-addons": "error", + }, + }, + ], +}); + +function buildRestrictedPropertiesOptions( + properties: string[], + message: string, +): { object?: string; property: string; message: string }[] { + return properties.map((prop) => { + const [object, property] = prop.split("."); + return { + object: object === "*" ? undefined : object, + property, + message, + }; + }); +} diff --git a/package.json b/package.json index d76f265bad5..a58ba0e8f40 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,11 @@ "i18n": "pnpm -r i18n", "i18n:sort": "pnpm -r i18n:sort", "i18n:lint": "pnpm -r i18n:lint", - "lint": "pnpm -r --workspace-concurrency=1 lint:types && pnpm lint:fmt && pnpm -r lint:js && pnpm -r lint:style && pnpm lint:workflows && pnpm lint:knip", + "lint": "pnpm -r --workspace-concurrency=1 lint:types && pnpm lint:fmt && pnpm lint:js && pnpm -r lint:style && pnpm lint:workflows && pnpm lint:knip", "lint:fmt": "oxfmt --check", - "lint:fmt-fix": "oxfmt", + "lint:fmt:fix": "oxfmt", + "lint:js": "oxlint", + "lint:js:fix": "oxlint --fix", "lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) -print -exec action-validator {} ';'", "lint:knip": "knip --no-tag-hints && knip --strict --exclude unlisted,dependencies,binaries", "lint:unused-settings": "node scripts/find-unused-settings.ts", @@ -35,6 +37,8 @@ "@types/node": "25", "@vitest/coverage-v8": "catalog:", "cronstrue": "^3.0.0", + "eslint-plugin-element-call": "github:element-hq/element-call#livekit&path:/eslint", + "eslint-plugin-storybook": "^10.4.6", "husky": "^9.0.0", "knip": "6.24.0", "lint-staged": "^17.0.0", @@ -43,6 +47,8 @@ "minimist": "^1.2.6", "nx": "23.0.1", "oxfmt": "0.56.0", + "oxlint": "^1.70.0", + "oxlint-tsgolint": "^0.23.0", "typescript": "catalog:", "vitepress": "^1.6.4", "vitepress-plugin-mermaid": "^2.0.17", diff --git a/packages/module-api/src/api/composer.ts b/packages/module-api/src/api/composer.ts index ba1ac94db46..c37af72fe86 100644 --- a/packages/module-api/src/api/composer.ts +++ b/packages/module-api/src/api/composer.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { ComponentType, SVGAttributes } from "react"; +import { type ComponentType, type SVGAttributes } from "react"; /** * An option presented to the user for uploading a file. diff --git a/packages/module-api/src/api/custom-components.ts b/packages/module-api/src/api/custom-components.ts index 0f456815008..5ca2be000fa 100644 --- a/packages/module-api/src/api/custom-components.ts +++ b/packages/module-api/src/api/custom-components.ts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import type { JSX, ReactNode } from "react"; import type { MatrixEvent } from "../models/event"; import type { AccountAuthInfo } from "./auth.ts"; -import { ComposerApiTarget } from "./composer.ts"; +import type { ComposerApiTarget } from "./composer.ts"; /** * Properties for all message components. @@ -129,12 +129,12 @@ export type CustomLoginComponentProps = { * Function to complete login * @param data - the data to authenticate the user with */ - onLoggedIn(data: AccountAuthInfo): void; + onLoggedIn(this: void, data: AccountAuthInfo): void; /** * Function to change the selected server * @param config - new server configuration details */ - onServerConfigChange(config: CustomLoginComponentPropsServerConfig): void; + onServerConfigChange(this: void, config: CustomLoginComponentPropsServerConfig): void; }; /** diff --git a/packages/module-api/src/api/dialog.ts b/packages/module-api/src/api/dialog.ts index cd9a2c867c4..cfbf9da6f41 100644 --- a/packages/module-api/src/api/dialog.ts +++ b/packages/module-api/src/api/dialog.ts @@ -30,7 +30,7 @@ export type DialogHandle = { /** * Method to close the dialog. */ - close(): void; + close(this: void): void; }; /** @@ -42,11 +42,11 @@ export type DialogProps = { * Callback to submit the dialog. * @param model - The model to submit with the dialog. This is typically the data collected. */ - onSubmit(model: M): void; + onSubmit(this: void, model: M): void; /** * Cancel the dialog programmatically. */ - onCancel(): void; + onCancel(this: void): void; }; /** diff --git a/packages/module-api/tsconfig.json b/packages/module-api/tsconfig.json index 79beeb700df..322d3c502af 100644 --- a/packages/module-api/tsconfig.json +++ b/packages/module-api/tsconfig.json @@ -10,7 +10,7 @@ "module": "es2022", "moduleResolution": "bundler", "types": [], - "outDir": "lib", + "noEmit": true, "jsx": "react-jsx", "declarationMap": true, "allowImportingTsExtensions": true diff --git a/packages/playwright-common/src/expect/screenshot.ts b/packages/playwright-common/src/expect/screenshot.ts index 8c2165a1cb3..ad8dc1a7b79 100644 --- a/packages/playwright-common/src/expect/screenshot.ts +++ b/packages/playwright-common/src/expect/screenshot.ts @@ -22,6 +22,7 @@ import { ANNOTATION } from "../stale-screenshot-reporter.js"; // Taken from playwright utils, but it's not importable function sanitizeForFilePath(s: string): string { + // oxlint-disable-next-line no-control-regex return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-"); } diff --git a/packages/playwright-common/src/flaky-reporter.ts b/packages/playwright-common/src/flaky-reporter.ts index 0a88f6b03c5..449d6c649b8 100644 --- a/packages/playwright-common/src/flaky-reporter.ts +++ b/packages/playwright-common/src/flaky-reporter.ts @@ -24,6 +24,7 @@ type PaginationLinks = { first?: string; }; +// oxlint-disable-next-line no-control-regex const ANSI_COLOUR_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; // We see quite a few test flakes which are caused by the app exploding diff --git a/packages/playwright-common/src/utils/release_accouncement.ts b/packages/playwright-common/src/utils/release_accouncement.ts index 0435f58585f..c297ca13626 100644 --- a/packages/playwright-common/src/utils/release_accouncement.ts +++ b/packages/playwright-common/src/utils/release_accouncement.ts @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -import { Page } from "playwright-core"; +import { type Page } from "playwright-core"; /** * Close the release announcement with the given name. diff --git a/packages/shared-components/.eslintrc.cjs b/packages/shared-components/.eslintrc.cjs deleted file mode 100644 index 1d1b657fd97..00000000000 --- a/packages/shared-components/.eslintrc.cjs +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2025 Element Creations Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -module.exports = { - root: true, - plugins: ["matrix-org", "eslint-plugin-react-compiler"], - extends: [ - "plugin:matrix-org/react", - "plugin:matrix-org/a11y", - "plugin:matrix-org/typescript", - "plugin:matrix-org/react", - "plugin:storybook/recommended", - ], - parserOptions: { - project: ["./tsconfig.json"], - tsconfigRootDir: __dirname, - }, - env: { - browser: true, - node: true, - }, - rules: { - // Bind or arrow functions in props causes performance issues (but we - // currently use them in some places). - // It's disabled here, but we should using it sparingly. - "react/jsx-no-bind": "off", - "react/jsx-key": ["error"], - "matrix-org/require-copyright-header": "error", - "react-compiler/react-compiler": "error", - "no-restricted-imports": [ - "error", - { - paths: [ - { - name: "react", - importNames: ["act"], - message: "Please use @test-utils instead.", - }, - ], - }, - ], - - "@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }], - "@typescript-eslint/explicit-function-return-type": [ - "error", - { - allowExpressions: true, - }, - ], - - // We're okay being explicit at the moment - // "@typescript-eslint/no-empty-interface": "off", - // We'd rather not do this but we do - // "@typescript-eslint/ban-ts-comment": "off", - // We're okay with assertion errors when we ask for them - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-empty-object-type": [ - "error", - { - // We do this sometimes to brand interfaces - allowInterfaces: "with-single-extends", - }, - ], - "storybook/meta-satisfies-type": "error", - - "react/forbid-elements": [ - "error", - { - forbid: [ - { element: "h1", message: "Use Compound instead" }, - { element: "h2", message: "Use Compound instead" }, - { element: "h3", message: "Use Compound instead" }, - { element: "h4", message: "Use Compound instead" }, - { element: "h5", message: "Use Compound instead" }, - { element: "h6", message: "Use Compound instead" }, - ], - }, - ], - }, - overrides: [ - { - files: ["src/**/*.test.{ts,tsx}", "src/**/*.stories.tsx"], - rules: { - "@typescript-eslint/unbound-method": "off", - "@typescript-eslint/no-explicit-any": "off", - "react/forbid-elements": "off", - }, - }, - ], - settings: { - react: { - version: "detect", - }, - }, -}; diff --git a/packages/shared-components/.storybook/languageAddon.tsx b/packages/shared-components/.storybook/languageAddon.tsx index dcb9d8d814a..fb5c13ab060 100644 --- a/packages/shared-components/.storybook/languageAddon.tsx +++ b/packages/shared-components/.storybook/languageAddon.tsx @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -import { Addon, types, useGlobals } from "storybook/manager-api"; +import { type Addon, types, useGlobals } from "storybook/manager-api"; import { WithTooltip, IconButton, TooltipLinkList } from "storybook/internal/components"; import React from "react"; import { GlobeIcon } from "@storybook/icons"; diff --git a/packages/shared-components/.storybook/preview.tsx b/packages/shared-components/.storybook/preview.tsx index 2f1d683b49b..0b53a02d153 100644 --- a/packages/shared-components/.storybook/preview.tsx +++ b/packages/shared-components/.storybook/preview.tsx @@ -19,7 +19,7 @@ import { TooltipProvider } from "@vector-im/compound-web"; import { EventPresentationProvider, type EventDensity, type EventLayout, I18nApi, I18nContext } from "../src"; import { setLanguage } from "../src/core/i18n/i18n"; -import { StoryContext } from "storybook/internal/csf"; +import { type StoryContext } from "storybook/internal/csf"; import { DragDropProvider } from "@dnd-kit/react"; import { PointerActivationConstraints, PointerSensor } from "@dnd-kit/dom"; diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index cc1267b6708..db26d20930d 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -58,8 +58,7 @@ "storybook": "storybook dev -p 6007", "build:storybook": "nx storybook", "build:doc": "nx typedoc", - "lint": "pnpm lint:types && pnpm lint:js", - "lint:js": "eslint --max-warnings 0 src", + "lint": "pnpm lint:types", "lint:types": "nx lint:types" }, "dependencies": { @@ -98,7 +97,6 @@ "@storybook/addon-vitest": "^10.1.11", "@storybook/icons": "^2.0.0", "@storybook/react-vite": "^10.0.7", - "@stylistic/eslint-plugin": "^5.7.0", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", @@ -106,22 +104,9 @@ "@types/lodash": "^4.17.20", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "@typescript-eslint/eslint-plugin": "^8.53.1", - "@typescript-eslint/parser": "^8.53.1", "@vector-im/compound-web": "catalog:", "@vitejs/plugin-react": "catalog:", "@vitest/browser-playwright": "catalog:", - "eslint": "8", - "eslint-config-google": "^0.14.0", - "eslint-plugin-deprecate": "^0.9.0", - "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-matrix-org": "^3.0.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-compiler": "^19.1.0-rc.2", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-storybook": "^10.0.7", - "eslint-plugin-unicorn": "^56.0.0", "oxfmt": "0.56.0", "storybook": "^10.0.7", "storybook-addon-vis": "^4.0.0", diff --git a/packages/shared-components/scripts/storybook-build-i18n.ts b/packages/shared-components/scripts/storybook-build-i18n.ts index 9fcdd09ef55..677c7626665 100644 --- a/packages/shared-components/scripts/storybook-build-i18n.ts +++ b/packages/shared-components/scripts/storybook-build-i18n.ts @@ -18,7 +18,7 @@ const I18N_BASE_PATH = "./src/i18n/strings/"; const I18N_DEST = "storybook-static/i18n/"; // List of languages to include -const INCLUDE_LANGS = [...new Set([...fs.readdirSync(I18N_BASE_PATH)])] +const INCLUDE_LANGS = [...new Set(fs.readdirSync(I18N_BASE_PATH))] .filter((fn) => fn.endsWith(".json")) .map((f) => f.slice(0, -5)); diff --git a/packages/shared-components/src/core/VirtualizedList/virtualized-list.tsx b/packages/shared-components/src/core/VirtualizedList/virtualized-list.tsx index 11b7f669266..cb4656e1f66 100644 --- a/packages/shared-components/src/core/VirtualizedList/virtualized-list.tsx +++ b/packages/shared-components/src/core/VirtualizedList/virtualized-list.tsx @@ -68,7 +68,7 @@ export interface VirtualizedListProps extends Omit< /** * Function to get the key to use for focusing an item. * @param item - The item to get the key for - * @return The key to use for focusing the item + * @returns The key to use for focusing the item */ getItemKey: (item: Item) => string; diff --git a/packages/shared-components/src/core/i18n/i18n.tsx b/packages/shared-components/src/core/i18n/i18n.tsx index aea70f0783a..46e96dbf1e4 100644 --- a/packages/shared-components/src/core/i18n/i18n.tsx +++ b/packages/shared-components/src/core/i18n/i18n.tsx @@ -19,7 +19,7 @@ * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable * substitution to insert React components, but you can't use it to translate text between tags. * - * @return a React component if any non-strings were used in substitutions, otherwise a string + * @returns a React component if any non-strings were used in substitutions, otherwise a string */ import React from "react"; import { KEY_SEPARATOR } from "matrix-web-i18n"; @@ -204,7 +204,7 @@ export function lookupString(key: TranslationKey): string { * @param {object} variables Variable substitutions, e.g { foo: 'bar' } * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} } * - * @return a React component if any non-strings were used in substitutions + * @returns a React component if any non-strings were used in substitutions * or translation used a fallback locale, otherwise a string */ // eslint-next-line @typescript-eslint/naming-convention @@ -243,7 +243,7 @@ export function sanitizeForTranslation(text: string): string { * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as * the argument the text inside the element corresponding to the tag. * - * @return a React component if any non-strings were used in substitutions, otherwise a string + * @returns a React component if any non-strings were used in substitutions, otherwise a string */ export function substitute(text: string, variables?: StringVariables): string; export function substitute(text: string, variables?: RichVariables): React.ReactNode; @@ -277,7 +277,7 @@ export function substitute(text: string, variables?: IVariables, tags?: Tags): s * function which will receive as the argument the capture groups defined in the regexp. E.g. * { 'Hello (.?) World': (sub) => sub.toUpperCase() } * - * @return a React component if any non-strings were used in substitutions, otherwise a string + * @returns a React component if any non-strings were used in substitutions, otherwise a string */ export function replaceByRegexes(text: string, mapping: IVariables): string; export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode; diff --git a/packages/shared-components/src/core/roving/RovingTabIndex.test.tsx b/packages/shared-components/src/core/roving/RovingTabIndex.test.tsx index 48035582edb..2b605d574e4 100644 --- a/packages/shared-components/src/core/roving/RovingTabIndex.test.tsx +++ b/packages/shared-components/src/core/roving/RovingTabIndex.test.tsx @@ -25,7 +25,7 @@ const Button = (props: HTMLAttributes): React.JSX.Element => return