glitch-soc/eslint.config.mjs

420 lines
11 KiB
JavaScript

// @ts-check
import js from '@eslint/js';
import { globalIgnores } from 'eslint/config';
import formatjs from 'eslint-plugin-formatjs';
// @ts-expect-error -- No typings
import importPlugin from 'eslint-plugin-import';
import jsdoc from 'eslint-plugin-jsdoc';
import jsxA11Y from 'eslint-plugin-jsx-a11y';
import promisePlugin from 'eslint-plugin-promise';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import globals from 'globals';
import tseslint from 'typescript-eslint';
/** @type {import('typescript-eslint').ConfigArray} */
export const baseConfig = [
js.configs.recommended,
importPlugin.flatConfigs.recommended,
jsdoc.configs['flat/recommended'],
promisePlugin.configs['flat/recommended'],
{
linterOptions: {
reportUnusedDisableDirectives: 'error',
reportUnusedInlineConfigs: 'error',
},
rules: {
'consistent-return': 'error',
'dot-notation': 'error',
eqeqeq: [
'error',
'always',
{
null: 'ignore',
},
],
'no-console': [
'warn',
{
allow: ['error', 'warn'],
},
],
'no-empty': [
'error',
{
allowEmptyCatch: true,
},
],
'no-restricted-properties': [
'error',
{
property: 'substring',
message: 'Use .slice instead of .substring.',
},
{
property: 'substr',
message: 'Use .slice instead of .substr.',
},
],
'no-unused-expressions': 'error',
'no-unused-vars': 'off',
'valid-typeof': 'error',
'import/extensions': [
'error',
'always',
{
js: 'never',
jsx: 'never',
mjs: 'never',
ts: 'never',
mts: 'never',
tsx: 'never',
},
],
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-relative-packages': 'error',
'import/no-self-import': 'error',
'import/no-useless-path-segments': 'error',
'import/order': [
'error',
{
alphabetize: {
order: 'asc',
},
'newlines-between': 'always',
groups: [
'builtin',
'external',
'internal',
'parent',
['index', 'sibling'],
'object',
],
pathGroups: [
{
pattern: '{react,react-dom,react-dom/client,prop-types}',
group: 'builtin',
position: 'after',
},
{
pattern: '{react-intl,intl-messageformat}',
group: 'builtin',
position: 'after',
},
{
pattern:
'{classnames,react-helmet,react-router,react-router-dom}',
group: 'external',
position: 'before',
},
{
pattern:
'{immutable,@reduxjs/toolkit,react-redux,react-immutable-proptypes,react-immutable-pure-component}',
group: 'external',
position: 'before',
},
{
pattern: '{mastodon/**}',
group: 'internal',
position: 'after',
},
{
pattern: '{flavours/glitch-soc/**}',
group: 'internal',
position: 'after',
},
],
pathGroupsExcludedImportTypes: [],
},
],
'jsdoc/check-types': 'off',
'jsdoc/no-undefined-types': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-property-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/require-returns': 'off',
// Forbid imports from vanilla in glitch flavour
'import/no-restricted-paths': [
'error',
{
zones: [
{
target: 'app/javascript/flavours/glitch/',
from: 'app/javascript/mastodon/',
message: 'Import from /flavours/glitch/ instead',
},
],
},
],
'promise/always-return': 'off',
'promise/catch-or-return': [
'error',
{
allowFinally: true,
},
],
'promise/no-callback-in-promise': 'off',
'promise/no-nesting': 'off',
'promise/no-promise-in-callback': 'off',
},
},
];
export default tseslint.config([
baseConfig,
globalIgnores([
'build/**/*',
'coverage/**/*',
'db/**/*',
'lib/**/*',
'log/**/*',
'node_modules/**/*',
'public/**/*',
'!public/embed.js',
'spec/**/*',
'tmp/**/*',
'vendor/**/*',
'streaming/**/*',
]),
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
reactHooks.configs['recommended-latest'],
jsxA11Y.flatConfigs.recommended,
importPlugin.flatConfigs.react,
// @ts-expect-error -- For some reason the formatjs package exports an empty object?
formatjs.configs.strict,
{
languageOptions: {
globals: {
...globals.browser,
},
parser: tseslint.parser,
ecmaVersion: 2021,
sourceType: 'module',
},
settings: {
react: {
version: 'detect',
},
'import/ignore': ['node_modules', '\\.(css|scss|json)$'],
'import/resolver': {
typescript: {},
},
},
rules: {
'no-restricted-syntax': [
'error',
{
// eslint-disable-next-line no-restricted-syntax
selector: 'Literal[value=/•/], JSXText[value=/•/]',
// eslint-disable-next-line no-restricted-syntax
message: "Use '·' (middle dot) instead of '•' (bullet)",
},
],
'formatjs/enforce-description': 'off', // description values not currently used
'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-plurals': 'off', // Should be looked at
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'jsx-a11y/media-has-caption': 'off',
'jsx-a11y/no-autofocus': 'off',
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off',
'jsx-a11y/no-noninteractive-tabindex': 'off',
'jsx-a11y/no-static-element-interactions': [
'warn',
{
handlers: ['onClick'],
},
],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'eslint.config.mjs',
'config/webpack/**',
'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js',
'app/javascript/**/__tests__/**',
],
},
],
'import/no-webpack-loader-syntax': 'error',
'react/jsx-filename-extension': [
'error',
{
extensions: ['.jsx', 'tsx'],
},
],
'react/jsx-boolean-value': 'error',
'react/display-name': 'off',
'react/jsx-fragments': ['error', 'syntax'],
'react/jsx-equals-spacing': 'error',
'react/jsx-no-bind': 'error',
'react/jsx-no-useless-fragment': 'error',
'react/jsx-no-target-blank': [
'error',
{
allowReferrer: true,
},
],
'react/jsx-tag-spacing': 'error',
'react/jsx-wrap-multilines': 'error',
'react/self-closing-comp': 'error',
},
},
{
files: [
'app/javascript/mastodon/common.js',
'app/javascript/mastodon/features/emoji/unicode_to_unified_name.js',
'app/javascript/mastodon/features/emoji/emoji_compressed.js',
'app/javascript/mastodon/features/emoji/unicode_to_filename.js',
'app/javascript/flavours/glitch/common.js',
'app/javascript/flavours/glitch/entrypoints/common.js',
'app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js',
'app/javascript/flavours/glitch/features/emoji/emoji_compressed.js',
'app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js',
'app/javascript/mastodon/service_worker/web_push_locales.js',
'**/*.config.js',
'**/.*rc.js',
'**/ide-helper.js',
'config/webpack/**/*',
'config/formatjs-formatter.js',
],
languageOptions: {
globals: {
...globals.commonjs,
...globals.node,
},
ecmaVersion: 5,
sourceType: 'commonjs',
},
rules: {
'import/no-commonjs': 'off',
},
},
{
files: ['**/*.ts', '**/*.tsx'],
extends: [
tseslint.configs.strictTypeChecked,
tseslint.configs.stylisticTypeChecked,
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
reactHooks.configs['recommended-latest'],
jsxA11Y.flatConfigs.recommended,
importPlugin.flatConfigs.react,
importPlugin.flatConfigs.typescript,
jsdoc.configs['flat/recommended-typescript'],
],
languageOptions: {
parserOptions: {
projectService: true,
},
},
rules: {
// This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
'consistent-return': 'off',
'formatjs/enforce-plural-rules': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'import/no-default-export': 'warn',
'jsdoc/require-jsdoc': 'off',
'react/prefer-stateless-function': 'warn',
'react/function-component-definition': [
'error',
{
namedComponents: 'arrow-function',
},
],
'react/prop-types': 'off',
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/prefer-nullish-coalescing': [
'error',
{
ignorePrimitives: {
boolean: true,
},
},
],
'@typescript-eslint/no-restricted-imports': [
'warn',
{
name: 'react-redux',
importNames: ['useSelector', 'useDispatch'],
message:
'Use typed hooks `useAppDispatch` and `useAppSelector` instead.',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/restrict-template-expressions': [
'warn',
{
allowNumber: true,
},
],
},
},
{
files: ['**/__tests__/*.js', '**/__tests__/*.jsx'],
languageOptions: {
globals: {
...globals.jest,
},
},
},
]);