MM-52624/MM-57094 Update ESLint and our ESLint plugin (#26398)

* Update ESLint and plugins

* Move most channels-specific ESLint configuration into ESLint plugin

* Add ESLint to types and client packages

* Add ESLint to components package
This commit is contained in:
Harrison Healey 2024-03-13 18:07:28 -04:00 committed by GitHub
parent 67f815e373
commit 4d03becdd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 1149 additions and 989 deletions

View File

@ -1,91 +1,16 @@
{
"root": true,
"extends": [
"plugin:@mattermost/react",
"plugin:react-hooks/recommended"
"plugin:@mattermost/react"
],
"plugins": [
"@mattermost",
"import",
"no-only-tests",
"@typescript-eslint",
"formatjs"
"formatjs",
"no-only-tests"
],
"parser": "@typescript-eslint/parser",
"env": {
"jest": true
},
"settings": {
"import/resolver": "webpack",
"react": {
"pragma": "React",
"version": "detect"
}
"import/resolver": "webpack"
},
"rules": {
"@mattermost/use-external-link": 2,
"header/header": [
2,
"line",
" Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information.",
2
],
"no-duplicate-imports": 0,
"import/no-duplicates": 2,
"max-nested-callbacks": ["error", 10],
"no-unused-expressions": 0,
"eol-last": ["error", "always"],
"import/no-unresolved": 2,
"import/order": [
2,
{
"newlines-between": "always",
"groups": [
"builtin",
"external",
"internal",
"sibling",
"parent",
"index"
],
"pathGroups": [
{
"pattern": "@mattermost/**",
"group": "external",
"position": "after"
},
{
"pattern": "mattermost-redux/**",
"group": "external",
"position": "after"
},
{
"pattern": "@(selectors|actions|stores|store|reducers){,/**}",
"group": "external",
"position": "after"
},
{
"pattern": "components/**",
"group": "external",
"position": "after"
},
{
"pattern": "types{,/**}",
"group": "internal",
"position": "after"
}
],
"alphabetize": {
"order": "asc",
"caseInsensitive": true
},
"distinctGroup": true,
"pathGroupsExcludedImportTypes": ["builtin"]
}
],
"no-undefined": 0,
"no-use-before-define": 0,
"react/jsx-filename-extension": 0,
"react/prop-types": [
2,
{
@ -104,116 +29,16 @@
]
}
],
"react/no-string-refs": 2,
"no-only-tests/no-only-tests": ["error", {"focus": ["only", "skip"]}],
"react/style-prop-object": [2, {
"allow": ["Timestamp"]
}],
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["@mattermost/compass-components/*"],
"message": "compass-components is now archived."
}
],
"paths": [
{
"name": "react-bootstrap",
"importNames": ["OverlayTrigger"],
"message": "Use OverlayTrigger from '/components/overlay_trigger' instead."
},
{
"name": "redux",
"importNames": ["DeepPartial"],
"message": "Use DeepPartial from '@mattermost/types/utilities instead."
},
{
"name": "lodash",
"message": "Import individual functions from lodash/<function> instead."
}
]
}
],
"max-lines": ["warn", {"max": 800, "skipBlankLines": true, "skipComments": true}],
"formatjs/no-multiple-whitespaces": 2,
"@typescript-eslint/consistent-type-imports": ["error", {"disallowTypeAnnotations": false}]
"formatjs/no-multiple-whitespaces": 2
},
"overrides": [
{
"files": ["**/*.tsx", "**/*.ts"],
"extends": [
"plugin:@typescript-eslint/recommended"
],
"files": ["*.test.*", "src/tests/**"],
"rules": {
"camelcase": 0,
"no-shadow": 0,
"import/no-unresolved": 0, // ts handles this better
"@typescript-eslint/naming-convention": [
2,
{
"selector": "function",
"format": ["camelCase", "PascalCase"]
},
{
"selector": "variable",
"format": ["camelCase", "PascalCase", "UPPER_CASE"]
},
{
"selector": "parameter",
"format": ["camelCase", "PascalCase"],
"leadingUnderscore": "allow"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
}
],
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/prefer-interface": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/indent": [
2,
4,
{
"SwitchCase": 0
}
],
"@typescript-eslint/no-use-before-define": [
2,
{
"classes": false,
"functions": false,
"variables": false
}
]
}
},
{
"files": ["src/tests/**", "**/*.test.*", "src/tests/*.js", "src/packages/mattermost-redux/test/*"],
"env": {
"jest": true
},
"rules": {
"func-names": 0,
"global-require": 0,
"max-lines": 0,
"new-cap": 0,
"no-empty-function": 0,
"no-import-assign": 0,
"no-process-env": 0,
"prefer-arrow-callback": 0
"no-only-tests/no-only-tests": ["error", {"focus": ["only", "skip"]}]
}
}
]

View File

@ -141,8 +141,6 @@
"@types/shallow-equals": "1.0.3",
"@types/styled-components": "5.1.32",
"@types/tinycolor2": "1.4.3",
"@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.1",
"copy-webpack-plugin": "11.0.0",
"emoji-datasource": "6.1.1",
"emoji-datasource-apple": "6.1.1",
@ -150,11 +148,7 @@
"enzyme": "3.11.0",
"enzyme-adapter-react-17-updated": "1.0.2",
"enzyme-to-json": "3.6.2",
"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"external-remotes-plugin": "1.0.0",
"html-webpack-plugin": "5.5.0",
"identity-obj-proxy": "3.0.0",

View File

@ -287,7 +287,7 @@ describe('rhs view actions', () => {
jest.resetModules();
const {submitCommand: remockedSubmitCommand} = require('actions/views/create_comment'); // eslint-disable-like @typescript-eslint/no-var-requires
const {submitCommand: remockedSubmitCommand} = require('actions/views/create_comment');
await store.dispatch(remockedSubmitCommand(channelId, rootId, draft));

View File

@ -283,7 +283,7 @@ describe('handleUserRemovedEvent', () => {
let redirectUserToDefaultTeam;
beforeEach(async () => {
const globalActions = require('actions/global_actions'); // eslint-disable-line global-require
const globalActions = require('actions/global_actions');
redirectUserToDefaultTeam = globalActions.redirectUserToDefaultTeam;
redirectUserToDefaultTeam.mockReset();
});

916
webapp/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -36,9 +36,11 @@
"concurrently": "7.6.0",
"cross-env": "7.0.3",
"css-loader": "6.7.3",
"eslint": "8.37.0",
"eslint-import-resolver-webpack": "0.13.2",
"eslint": "8.57.0",
"eslint-import-resolver-webpack": "0.13.8",
"eslint-plugin-formatjs": "4.12.2",
"eslint-plugin-react": "7.34.0",
"eslint-plugin-react-hooks": "4.6.0",
"mini-css-extract-plugin": "2.7.5",
"sass": "1.71.1",
"sass-loader": "14.1.1",

View File

@ -0,0 +1,6 @@
{
"root": true,
"extends": [
"plugin:@mattermost/base"
]
}

View File

@ -21,6 +21,7 @@
"form-data": "^4.0.0"
},
"devDependencies": {
"@types/jest": "28.1.8",
"jest": "27.1.0",
"typescript": "^5.0.0"
},
@ -34,10 +35,11 @@
}
},
"scripts": {
"build": "tsc --build --verbose",
"build": "tsc --build tsconfig.build.json --verbose",
"check": "eslint --ext .js,.jsx,.tsx,.ts ./src --quiet",
"run": "tsc --watch --preserveWatchOutput",
"test": "jest",
"test-ci": "jest --ci --forceExit --detectOpenHandles --maxWorkers=100%",
"clean": "rm -rf lib node_modules tsconfig.tsbuildinfo"
"clean": "rm -rf lib node_modules *.tsbuildinfo"
}
}

View File

@ -4,7 +4,7 @@
import nock from 'nock';
import Client4, {ClientError, HEADER_X_VERSION_ID} from './client4';
import {TelemetryHandler} from './telemetry';
import type {TelemetryHandler} from './telemetry';
describe('Client4', () => {
beforeAll(() => {

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
// See LICENSE.txt for license information.
import Client4 from './client4';
import {cleanUrlForLogging} from './errors';
describe('cleanUrlForLogging', () => {

View File

@ -189,16 +189,16 @@ export default class WebSocketClient {
console.log('long timeout, or server restart, or sequence number is not found.'); //eslint-disable-line no-console
this.missedEventCallback?.();
for (const listener of this.missedMessageListeners) {
for (const listener of this.missedMessageListeners) {
try {
listener();
} catch (e) {
console.log(`missed message listener "${listener.name}" failed: ${e}`); // eslint-disable-line no-console
}
}
this.serverSequence = 0;
this.serverSequence = 0;
}
// If it's a fresh connection, we have to set the connectionId regardless.

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "es2022",
"declaration": true,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"jsx": "react",
"outDir": "./lib",
"rootDir": "./src",
"composite": true,
},
"include": [
"./src/**/*"
],
"exclude": [
"**/*.test.*"
],
"references": [
{"path": "../types"}
]
}

View File

@ -12,14 +12,11 @@
"jsx": "react",
"outDir": "./lib",
"rootDir": "./src",
"composite": true
"composite": true,
},
"include": [
"./src/**/*"
],
"exclude": [
"**/*.test.*"
],
"references": [
{"path": "../types"}
]

View File

@ -0,0 +1,6 @@
{
"root": true,
"extends": [
"plugin:@mattermost/base"
]
}

View File

@ -7,6 +7,7 @@
"styles": "dist/index.esm.css",
"scripts": {
"build": "rollup -c",
"check": "eslint --ext .js,.jsx,.tsx,.ts ./src --quiet",
"run": "rollup -c --watch",
"test": "cross-env TZ=Etc/UTC jest",
"test:updatesnapshot": "cross-env TZ=Etc/UTC jest --updateSnapshot",

View File

@ -1,7 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MutableRefObject, useEffect} from 'react';
import type {MutableRefObject} from 'react';
import {useEffect} from 'react';
export function useClickOutsideRef(ref: MutableRefObject<HTMLElement | null>, handler: (event: MouseEvent) => void): void {
useEffect(() => {

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useLayoutEffect, useMemo, useState} from 'react';
import throttle from 'lodash/throttle';
import {useLayoutEffect, useMemo, useState} from 'react';
import {useElementAvailable} from './useElementAvailable';

View File

@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {render, screen} from '@testing-library/react';
import React from 'react';
import {FooterPagination} from './footer_pagination';
import {wrapIntl} from '../testUtils'
import {wrapIntl} from '../testUtils';
describe('LegacyGenericModal/FooterPagination', () => {
const baseProps = {
@ -28,7 +29,7 @@ describe('LegacyGenericModal/FooterPagination', () => {
page: 0,
total: 17,
itemsPerPage: 10,
}
};
render(wrapIntl(<FooterPagination {...props}/>));

View File

@ -1,10 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {render, screen} from '@testing-library/react';
import React from 'react';
import {GenericModal} from './generic_modal';
import {wrapIntl} from '../testUtils';
describe('GenericModal', () => {
@ -32,7 +33,7 @@ describe('GenericModal', () => {
render(
wrapIntl(<GenericModal {...props}/>),
);
expect(screen.getByText('Confirm')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
});

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import classNames from 'classnames';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
@ -73,7 +73,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
onHide = () => {
this.setState({show: false});
}
};
handleCancel = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
@ -83,7 +83,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
if (this.props.handleCancel) {
this.props.handleCancel();
}
}
};
handleConfirm = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
@ -93,7 +93,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
if (this.props.handleConfirm) {
this.props.handleConfirm();
}
}
};
private onEnterKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Enter') {
@ -108,7 +108,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
}
}
this.props.handleKeydown?.(event);
}
};
render() {
let confirmButton;

View File

@ -3,7 +3,7 @@
import React from 'react';
import {Coords} from '../common/hooks/useMeasurePunchouts';
import type {Coords} from '../common/hooks/useMeasurePunchouts';
import './pulsating_dot.scss';

View File

@ -4,14 +4,14 @@
import styled, {keyframes} from 'styled-components';
const skeletonFade = keyframes`
0% {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
0% {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
}
50% {
background-color: rgba(var(--center-channel-color-rgb), 0.16);
50% {
background-color: rgba(var(--center-channel-color-rgb), 0.16);
}
100% {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
100% {
background-color: rgba(var(--center-channel-color-rgb), 0.08);
}
`;

View File

@ -1,13 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactNode} from 'react'
import {IntlProvider, createIntl} from 'react-intl'
import type {ReactNode} from 'react';
import React from 'react';
import {IntlProvider, createIntl} from 'react-intl';
export const defaultIntl = createIntl({
locale: 'en',
defaultLocale: 'en',
messages: {},
})
});
export const wrapIntl = (children?: ReactNode) => <IntlProvider {...defaultIntl}>{children}</IntlProvider>
export const wrapIntl = (children?: ReactNode) => <IntlProvider {...defaultIntl}>{children}</IntlProvider>;

View File

@ -1,21 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useRef, useState} from 'react';
import {FormattedMessage} from 'react-intl';
import Tippy from '@tippyjs/react';
import {Placement} from 'tippy.js';
import classNames from 'classnames';
import React, {useRef} from 'react';
import {FormattedMessage} from 'react-intl';
import type {Placement} from 'tippy.js';
import {TourTipBackdrop} from './tour_tip_backdrop';
import type {Props as PunchOutCoordsHeightAndWidth} from '../common/hooks/useMeasurePunchouts';
import {PulsatingDot} from '../pulsating_dot';
import 'tippy.js/dist/tippy.css';
import 'tippy.js/themes/light-border.css';
import 'tippy.js/animations/scale-subtle.css';
import 'tippy.js/animations/perspective-subtle.css';
import {PulsatingDot} from '../pulsating_dot';
import {TourTipBackdrop} from './tour_tip_backdrop';
import './tour_tip.scss';
export type TourTipEventSource = 'next' | 'prev' | 'dismiss' | 'jump' | 'skipped' | 'open' | 'punchOut'

View File

@ -1,10 +1,17 @@
{
"extends": [
"plugin:react/recommended"
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"plugins": [
"react"
],
"settings": {
"react": {
"pragma": "React",
"version": "detect"
}
},
"rules": {
"react/display-name": [
0,
@ -39,7 +46,15 @@
2,
"never"
],
"react/jsx-filename-extension": 2,
"react/jsx-filename-extension": [
2,
{
"extensions": [
".jsx",
".tsx"
]
}
],
"react/jsx-first-prop-new-line": [
2,
"multiline"
@ -101,7 +116,7 @@
],
"react/no-render-return-value": 2,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-string-refs": 2,
"react/no-unescaped-entities": 2,
"react/no-unknown-property": 2,
"react/no-unused-prop-types": [

View File

@ -1,6 +1,7 @@
{
"extends": [
"eslint:recommended"
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 8,
@ -11,9 +12,12 @@
"modules": true
}
},
"parser": "babel-eslint",
"parser": "@typescript-eslint/parser",
"plugins": [
"header"
"@mattermost",
"@typescript-eslint",
"header",
"import"
],
"env": {
"browser": true,
@ -23,6 +27,59 @@
},
"rules": {
"@mattermost/no-dispatch-getstate": 2,
"@mattermost/use-external-link": 2,
"@typescript-eslint/array-type": [2, {"default": "array-simple"}],
"@typescript-eslint/consistent-type-imports": ["error", {"disallowTypeAnnotations": false}],
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/indent": [
2,
4,
{
"SwitchCase": 0
}
],
"@typescript-eslint/member-delimiter-style": 2,
"@typescript-eslint/naming-convention": [
2,
{
"selector": "function",
"format": ["camelCase", "PascalCase"]
},
{
"selector": "variable",
"format": ["camelCase", "PascalCase", "UPPER_CASE"]
},
{
"selector": "parameter",
"format": ["camelCase", "PascalCase"],
"leadingUnderscore": "allow"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
}
],
"@typescript-eslint/no-dupe-class-members": 2,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
],
"@typescript-eslint/no-use-before-define": [
2,
{
"classes": false,
"functions": false,
"variables": false
}
],
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/type-annotation-spacing": 2,
"array-bracket-spacing": [
2,
"never"
@ -48,12 +105,7 @@
"allowSingleLine": false
}
],
"camelcase": [
2,
{
"properties": "never"
}
],
"camelcase": 0, // Handled by @typescript-eslint/naming-convention
"capitalized-comments": 0,
"class-methods-use-this": 0,
"comma-dangle": [
@ -94,6 +146,7 @@
"object"
],
"dot-notation": 2,
"eol-last": ["error", "always"],
"eqeqeq": [
2,
"smart"
@ -123,16 +176,60 @@
"header/header": [
2,
"line",
" Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information."
" Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information.",
2
],
"id-blacklist": 0,
"indent": [
"import/no-duplicates": 2,
"import/no-unresolved": 0, // Handled better by TS
"import/order": [
2,
4,
{
"SwitchCase": 0
"newlines-between": "always",
"groups": [
"builtin",
"external",
"internal",
"sibling",
"parent",
"index"
],
"pathGroups": [
{
"pattern": "@mattermost/**",
"group": "external",
"position": "after"
},
{
"pattern": "mattermost-redux/**",
"group": "external",
"position": "after"
},
{
"pattern": "@(selectors|actions|stores|store|reducers){,/**}",
"group": "external",
"position": "after"
},
{
"pattern": "components/**",
"group": "external",
"position": "after"
},
{
"pattern": "types{,/**}",
"group": "internal",
"position": "after"
}
],
"alphabetize": {
"order": "asc",
"caseInsensitive": true
},
"distinctGroup": true,
"pathGroupsExcludedImportTypes": ["builtin"]
}
],
"indent": 0, // Handled by @typescript-eslint/indent
"jsx-quotes": [
2,
"prefer-single"
@ -165,19 +262,14 @@
}
],
"max-lines": [
1,
"warn",
{
"max": 450,
"max": 800,
"skipBlankLines": true,
"skipComments": false
}
],
"max-nested-callbacks": [
2,
{
"max": 2
}
],
"max-nested-callbacks": ["error", 10],
"max-statements-per-line": [
2,
{
@ -211,18 +303,13 @@
"no-debugger": 2,
"no-div-regex": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-class-members": 0, // Handled by @typescript-eslint/no-dupe-class-members
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-duplicate-imports": [
2,
{
"includeExports": true
}
],
"no-duplicate-imports": 0, // Handled by import/no-duplicates
"no-else-return": 2,
"no-empty": 2,
"no-empty-function": 2,
"no-empty-function": 0,
"no-empty-pattern": 2,
"no-eval": 2,
"no-ex-assign": 2,
@ -284,7 +371,33 @@
"no-process-exit": 2,
"no-proto": 2,
"no-prototype-builtins": 1,
"no-redeclare": 2,
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["@mattermost/compass-components/*"],
"message": "compass-components is now archived."
}
],
"paths": [
{
"name": "react-bootstrap",
"importNames": ["OverlayTrigger"],
"message": "Use OverlayTrigger from '/components/overlay_trigger' instead."
},
{
"name": "redux",
"importNames": ["DeepPartial"],
"message": "Use DeepPartial from '@mattermost/types/utilities instead."
},
{
"name": "lodash",
"message": "Import individual functions from lodash/<function> instead."
}
]
}
],
"no-return-assign": [
2,
"always"
@ -299,12 +412,7 @@
],
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": [
2,
{
"hoist": "functions"
}
],
"no-shadow": 0, // This isn't currently enabled, but it probably should be
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-tabs": 0,
@ -319,7 +427,7 @@
}
],
"no-undef-init": 2,
"no-undefined": 2,
"no-undefined": 0,
"no-underscore-dangle": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
@ -333,21 +441,8 @@
"no-unsafe-finally": 2,
"no-unsafe-negation": 2,
"no-unused-expressions": 2,
"no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
],
"no-use-before-define": [
2,
{
"classes": false,
"functions": false,
"variables": false
}
],
"no-unused-vars": 0, // Handled by @typescript-eslint/no-unused-vars
"no-use-before-define": 0, // Handled by @typescript-eslint/no-use-before-define
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-constructor": 2,
@ -475,14 +570,14 @@
"exceptRange": false,
"onlyEquality": false
}
],
"@typescript-eslint/array-type": [2, {"default": "array-simple"}],
"@typescript-eslint/member-delimiter-style": 2,
"@typescript-eslint/type-annotation-spacing": 2
]
},
"overrides": [
{
"files": ["*.test.js", "*.test.jsx", "*.test.ts", "*.test.tsx", "tests/**"],
"files": ["*.test.*", "src/tests/**"],
"env": {
"jest": true
},
"globals": {
"after": true,
"afterAll": true,
@ -497,10 +592,14 @@
"test": true
},
"rules": {
"no-empty-function": 0,
"func-names": 0,
"global-require": 0,
"no-console": 0,
"no-import-assign": 0,
"max-lines": 0,
"max-nested-callbacks": 0,
"no-undefined": 0
"new-cap": 0,
"prefer-arrow-callback": 0
}
}
]

View File

@ -1,6 +1,6 @@
{
"name": "@mattermost/eslint-plugin",
"version": "1.0.0",
"version": "1.1.0",
"description": "ESLint configuration and custom rules used by Mattermost",
"repository": {
"type": "git",
@ -12,17 +12,23 @@
"homepage": "https://github.com/mattermost/mattermost/tree/master/webapp/platform/eslint-plugin#readme",
"main": "index.js",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"jsx-ast-utils": "^3.3.3"
},
"peerDependencies": {
"@typescript-eslint/eslint-plugin": "^5.57.1",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-react": "^7.33.2"
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0"
},
"peerDependenciesMeta": {
"eslint-plugin-react": {
"optional": true
},
"eslint-plugin-react-hooks": {
"optional": true
}
}
}

View File

@ -0,0 +1,6 @@
{
"root": true,
"extends": [
"plugin:@mattermost/base"
]
}

View File

@ -38,7 +38,8 @@
},
"scripts": {
"build": "tsc --build --verbose",
"check": "eslint --ext .js,.jsx,.tsx,.ts ./src --quiet",
"run": "tsc --watch --preserveWatchOutput",
"clean": "rm -rf tsconfig.tsbuildinfo ./lib"
"clean": "rm -rf lib *.tsbuildinfo"
}
}

View File

@ -1,16 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Audit} from './audits';
import {Compliance} from './compliance';
import {AdminConfig, AllowedIPRange, ClientLicense, EnvironmentConfig} from './config';
import {DataRetentionCustomPolicies} from './data_retention';
import {MixedUnlinkedGroupRedux} from './groups';
import {PluginRedux, PluginStatusRedux} from './plugins';
import {SamlCertificateStatus, SamlMetadataResponse} from './saml';
import {Team} from './teams';
import {UserAccessToken, UserProfile} from './users';
import {RelationOneToOne} from './utilities';
import type {Audit} from './audits';
import type {Compliance} from './compliance';
import type {AdminConfig, ClientLicense, EnvironmentConfig} from './config';
import type {DataRetentionCustomPolicies} from './data_retention';
import type {MixedUnlinkedGroupRedux} from './groups';
import type {PluginRedux, PluginStatusRedux} from './plugins';
import type {SamlCertificateStatus, SamlMetadataResponse} from './saml';
import type {Team} from './teams';
import type {UserAccessToken, UserProfile} from './users';
import type {RelationOneToOne} from './utilities';
export enum LogLevelEnum {
SILLY = 'silly',

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ProductScope} from './products';
import type {ProductScope} from './products';
export enum Permission {
UserJoinedChannelNotification = 'user_joined_channel_notification',

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {UserProfile} from './users';
import type {UserProfile} from './users';
export type UserAutocomplete = {
users: UserProfile[];

View File

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Channel} from './channels';
import {Team} from './teams';
import {UserProfile} from './users';
import {IDMappedObjects, RelationOneToOne} from './utilities';
import type {Channel} from './channels';
import type {Team} from './teams';
import type {UserProfile} from './users';
import type {IDMappedObjects, RelationOneToOne} from './utilities';
export type ChannelCategoryType = 'favorites' | 'channels' | 'direct_messages' | 'custom';

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IDMappedObjects, RelationOneToManyUnique, RelationOneToOne} from './utilities';
import {Team} from './teams';
import type {Team} from './teams';
import type {IDMappedObjects, RelationOneToManyUnique, RelationOneToOne} from './utilities';
// e.g.
// **O**pen channel,

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AllowedIPRange} from './config';
import {ValueOf} from './utilities';
import type {AllowedIPRange} from './config';
import type {ValueOf} from './utilities';
export type CloudState = {
subscription?: Subscription;

View File

@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
export type ClientConfig = {
AboutLink: string;
AllowBannerDismissal: string;
@ -838,7 +836,7 @@ export type DataRetentionSettings = {
FileRetentionHours: number;
DeletionJobStartTime: string;
BatchSize: number;
EnableBoardsDeletion: boolean,
EnableBoardsDeletion: boolean;
BoardsRetentionDays: number;
TimeBetweenBatchesMilliseconds: number;
RetentionIdsBatchSize: number;
@ -869,8 +867,7 @@ export type JobSettings = {
CleanupConfigThresholdDays: number;
};
export type ProductSettings = {
};
export type ProductSettings = Record<string, never>;
export type PluginSettings = {
Enable: boolean;
@ -1008,7 +1005,6 @@ export enum ServiceEnvironment {
DEV = 'dev',
}
export type AllowedIPRange = {
cidr_block: string;
description: string;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostMetadata, PostPriorityMetadata} from './posts';
import type {PostMetadata, PostPriorityMetadata} from './posts';
export type Draft = {
create_at: number;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ClientConfig, ClientLicense, WarnMetricStatus} from './config';
import type {ClientConfig, ClientLicense} from './config';
export type GeneralState = {
config: Partial<ClientConfig>;

View File

@ -1,9 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {UserProfile} from './users';
import {RelationOneToOne} from './utilities';
import type {UserProfile} from './users';
import type {RelationOneToOne} from './utilities';
export enum SyncableType {
Team = 'team',

View File

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import type {Address, Product, Invoice} from './cloud';
import {ValueOf} from './utilities';
import type {ValueOf} from './utilities';
export const SelfHostedSignupProgress = {
START: 'START',

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MessageAttachment} from './message_attachments';
import {IDMappedObjects} from './utilities';
import type {MessageAttachment} from './message_attachments';
import type {IDMappedObjects} from './utilities';
export type IncomingWebhook = {
id: string;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IDMappedObjects} from './utilities';
import type {IDMappedObjects} from './utilities';
export type JobType = 'data_retention' | 'elasticsearch_post_indexing' | 'bleve_post_indexing' | 'ldap_sync' | 'message_export';
export type JobStatus = 'pending' | 'in_progress' | 'success' | 'error' | 'cancel_requested' | 'canceled' | 'warning';

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PluginManifest} from './plugins';
import {AppManifest} from './apps';
import type {AppManifest} from './apps';
import type {PluginManifest} from './plugins';
export type MarketplaceLabel = {
name: string;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostAction} from './integration_actions';
import type {PostAction} from './integration_actions';
export type MessageAttachment = {
id: number;

View File

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Channel, ChannelType} from './channels';
import {CustomEmoji} from './emojis';
import {FileInfo} from './files';
import {Reaction} from './reactions';
import { TeamType } from './teams';
import {UserProfile} from './users';
import {
import type {Channel, ChannelType} from './channels';
import type {CustomEmoji} from './emojis';
import type {FileInfo} from './files';
import type {Reaction} from './reactions';
import type {TeamType} from './teams';
import type {UserProfile} from './users';
import type {
RelationOneToOne,
RelationOneToMany,
IDMappedObjects,

View File

@ -1,27 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {UserProfile} from './users';
import type {UserProfile} from './users';
export enum UserReportSortColumns {
username = "Username",
email = "Email",
createAt = "CreateAt",
firstName = "FirstName",
lastName = "LastName",
nickname = "Nickname",
username = 'Username',
email = 'Email',
createAt = 'CreateAt',
firstName = 'FirstName',
lastName = 'LastName',
nickname = 'Nickname',
}
export enum ReportSortDirection {
ascending = "asc",
descending = "desc",
ascending = 'asc',
descending = 'desc',
}
export enum ReportDuration {
AllTime = "all_time",
Last30Days = "last_30_days",
PreviousMonth = "previous_month",
Last6Months = "last_6_months",
AllTime = 'all_time',
Last30Days = 'last_30_days',
PreviousMonth = 'previous_month',
Last6Months = 'last_6_months',
}
export enum CursorPaginationDirection {
@ -30,12 +30,12 @@ export enum CursorPaginationDirection {
}
export type UserReportFilter = {
role_filter?: string,
has_no_team?: boolean,
team_filter?: string,
hide_active?: boolean,
hide_inactive?: boolean,
search_term?: string,
role_filter?: string;
has_no_team?: boolean;
team_filter?: string;
hide_active?: boolean;
hide_inactive?: boolean;
search_term?: string;
}
export type UserReportOptions = UserReportFilter & {
@ -55,8 +55,8 @@ export type UserReportOptions = UserReportFilter & {
// Following are optional pagination parameters
/**
* The direction to paginate in. Either "up" or "down". Use the CursorPaginationDirection enum.
*/
direction?: CursorPaginationDirection,
*/
direction?: CursorPaginationDirection;
/**
* The cursor to paginate from.
@ -77,8 +77,8 @@ export type UserReportOptions = UserReportFilter & {
export type UserReport = UserProfile & {
last_login_at: number;
last_status_at?: number;
last_post_date?: number;
days_active?: number;
total_posts?: number;
last_status_at?: number;
last_post_date?: number;
days_active?: number;
total_posts?: number;
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {TeamMembership} from './teams';
import type {TeamMembership} from './teams';
export type Session = {
id: string;

View File

@ -1,35 +1,35 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AdminState} from './admin';
import {Bot} from './bots';
import {ChannelsState} from './channels';
import {ChannelCategoriesState} from './channel_categories';
import {CloudState, CloudUsage} from './cloud';
import {HostedCustomerState} from './hosted_customer';
import {EmojisState} from './emojis';
import {FilesState} from './files';
import {GeneralState} from './general';
import {GroupsState} from './groups';
import {IntegrationsState} from './integrations';
import {JobsState} from './jobs';
import {PostsState} from './posts';
import {PreferenceType} from './preferences';
import {
import type {AdminState} from './admin';
import type {AppsState} from './apps';
import type {Bot} from './bots';
import type {ChannelCategoriesState} from './channel_categories';
import type {ChannelsState} from './channels';
import type {CloudState, CloudUsage} from './cloud';
import type {EmojisState} from './emojis';
import type {FilesState} from './files';
import type {GeneralState} from './general';
import type {GroupsState} from './groups';
import type {HostedCustomerState} from './hosted_customer';
import type {IntegrationsState} from './integrations';
import type {JobsState} from './jobs';
import type {LimitsState} from './limits';
import type {PostsState} from './posts';
import type {PreferenceType} from './preferences';
import type {
AdminRequestsStatuses, ChannelsRequestsStatuses,
FilesRequestsStatuses, GeneralRequestsStatuses,
PostsRequestsStatuses, RolesRequestsStatuses,
TeamsRequestsStatuses, UsersRequestsStatuses,
} from './requests';
import {Role} from './roles';
import {SchemesState} from './schemes';
import {SearchState} from './search';
import {TeamsState} from './teams';
import {ThreadsState} from './threads';
import {Typing} from './typing';
import {UsersState} from './users';
import {AppsState} from './apps';
import {LimitsState} from './limits';
import type {Role} from './roles';
import type {SchemesState} from './schemes';
import type {SearchState} from './search';
import type {TeamsState} from './teams';
import type {ThreadsState} from './threads';
import type {Typing} from './typing';
import type {UsersState} from './users';
export type GlobalState = {
entities: {

View File

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ServerError} from './errors';
import {UserProfile} from './users';
import {RelationOneToOne} from './utilities';
import type {ServerError} from './errors';
import type {UserProfile} from './users';
import type {RelationOneToOne} from './utilities';
export type TeamMembership = TeamUnread & {
user_id: string;

View File

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Channel} from './channels';
import type {Post} from './posts';
import type {Team} from './teams';
import type {Channel} from './channels';
import type {UserProfile} from './users';
import type {IDMappedObjects, RelationOneToMany, RelationOneToOne} from './utilities';

View File

@ -1,12 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Audit} from './audits';
import {Channel} from './channels';
import {Group} from './groups';
import {Session} from './sessions';
import {Team} from './teams';
import {IDMappedObjects, RelationOneToMany, RelationOneToManyUnique, RelationOneToOne} from './utilities';
import type {Audit} from './audits';
import type {Channel} from './channels';
import type {Group} from './groups';
import type {Session} from './sessions';
import type {Team} from './teams';
import type {IDMappedObjects, RelationOneToManyUnique, RelationOneToOne} from './utilities';
export type UserNotifyProps = {
desktop: 'default' | 'all' | 'mention' | 'none';

View File

@ -14,17 +14,23 @@ export type RelationOneToManyUnique<E1 extends {id: string}, E2 extends {id: str
export type IDMappedObjects<E extends {id: string}> = RelationOneToOne<E, E>;
export type DeepPartial<T> = {
// For each field of T, make it optional and...
[K in keyof T]?:
[K in keyof T]?: (
// If that field is a Set or a Map, don't go further
T[K] extends Set<any> ? T[K] :
T[K] extends Map<any, any> ? T[K] :
// If that field is an object, make it a deep partial object
T[K] extends object ? DeepPartial<T[K]> :
// Else if that field is an optional object, make that a deep partial object
T[K] extends object | undefined ? DeepPartial<T[K]> :
// Else leave it as an optional primitive
T[K];
T[K] extends Map<any, any> ? T[K] :
// If that field is an object, make it a deep partial object
T[K] extends object ? DeepPartial<T[K]> :
// Else if that field is an optional object, make that a deep partial object
T[K] extends object | undefined ? DeepPartial<T[K]> :
// Else leave it as an optional primitive
T[K]
);
}
export type ValueOf<T> = T[keyof T];