UNPKG

@fluentui/eslint-plugin

Version:

ESLint configuration and custom rules for Fluent UI

360 lines (347 loc) 12.3 kB
// @ts-check const { __internal } = require('../internal'); const configHelpers = require('../utils/configHelpers'); /** @type {import("eslint").Linter.Config} */ const config = { root: true, extends: [ // Provides both rules and some parser options and other settings 'airbnb/base', // add typescript support for import plugin - https://github.com/import-js/eslint-plugin-import/blob/main/config/typescript.js 'plugin:import/typescript', // Extended configs are applied in order, so these configs that turn other rules off should come last 'prettier', ], parser: '@typescript-eslint/parser', plugins: ['import', '@fluentui', '@rnx-kit', '@typescript-eslint', 'jest', 'jsdoc', ...__internal.plugins], settings: { 'import/resolver': { // @see https://github.com/alexgorbatchev/eslint-import-resolver-typescript#configuration typescript: { alwaysTryTypes: true, project: './tsconfig.json', }, }, jsdoc: { ignoreInternal: true, tagNamePreference: { // Allow any of @default, @defaultvalue, @defaultValue until we settle on a preferred one default: 'default', defaultvalue: 'defaultvalue', defaultValue: 'defaultValue', // Allow either @return or @returns until we settle on a preferred one return: 'return', returns: 'returns', }, }, }, env: { browser: true, 'jest/globals': true, }, // We have to disable this when running lint-staged, or it will incorrectly flag eslint-disable // directives for rules which are disabled only in that context. reportUnusedDisableDirectives: !configHelpers.isLintStaged, // matched relative to cwd ignorePatterns: [ 'coverage', 'dist', 'dist-storybook', 'etc', 'lib', 'lib-amd', 'lib-commonjs', 'node_modules', 'temp', 'bundle-size', '**/__snapshots__', '**/*.scss.ts', ], rules: { /** * core eslint rules * @see https://eslint.org/docs/rules */ curly: ['error', 'all'], 'dot-notation': 'error', eqeqeq: ['error', 'always'], 'guard-for-in': 'error', 'no-alert': 'error', 'no-bitwise': 'error', 'no-caller': 'error', 'no-console': 'error', 'no-constant-condition': 'error', 'no-debugger': 'error', 'no-duplicate-case': 'error', 'no-empty': 'error', 'no-eval': 'error', 'no-new-wrappers': 'error', 'no-restricted-globals': [ 'error', ...['blur', 'close', 'focus', 'length', 'name', 'parent', 'self', 'stop'].map(name => ({ name, message: `"${name}" refers to a DOM global. Did you mean to reference a local value instead?`, })), ], 'no-restricted-properties': [ 'error', { object: 'describe', property: 'only', message: 'describe.only should only be used during test development' }, { object: 'it', property: 'only', message: 'it.only should only be used during test development' }, { object: 'React', property: 'useLayoutEffect', message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', }, ], 'no-shadow': ['error', { hoist: 'all' }], 'no-var': 'error', 'prefer-const': 'error', 'prefer-arrow-callback': 'error', // tslint: no-function-expression radix: ['error', 'always'], 'lines-between-class-members': 'off', 'max-classes-per-file': 'off', 'no-case-declarations': 'off', 'no-cond-assign': 'off', 'no-continue': 'off', 'no-control-regex': 'off', 'no-else-return': 'off', 'no-lonely-if': 'off', 'no-loop-func': 'off', 'no-multi-assign': 'off', 'no-nested-ternary': 'off', 'no-param-reassign': 'off', 'no-plusplus': 'off', 'no-prototype-builtins': 'off', 'no-return-assign': 'off', 'no-template-curly-in-string': 'off', 'no-undef-init': 'off', 'no-underscore-dangle': 'off', 'no-unneeded-ternary': 'off', 'no-unused-expressions': 'off', 'no-use-before-define': 'off', 'no-useless-computed-key': 'off', 'no-useless-concat': 'off', 'no-useless-constructor': 'off', 'no-useless-escape': 'off', 'no-useless-rename': 'off', 'no-useless-return': 'off', 'object-shorthand': 'warn', 'operator-assignment': 'off', 'prefer-destructuring': 'off', 'prefer-template': 'off', // airbnb or other config overrides (some temporary) // TODO: determine which rules we want to enable, and make needed changes (separate PR) 'arrow-body-style': 'off', 'class-methods-use-this': 'off', 'consistent-return': 'off', 'default-case': 'off', 'func-names': 'off', 'global-require': 'off', 'spaced-comment': 'off', // airbnb options ban for-of which is unnecessary for TS and modern node (https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/style.js#L334) // but this is a very powerful rule we may want to use in other ways 'no-restricted-syntax': 'off', // permanently disable because we disagree with these rules 'no-await-in-loop': 'off', // contrary to rule docs, awaited things often are NOT parallelizable // permanently disable due to performance issues (using custom rule `@fluentui/max-len` instead) 'max-len': 'off', // permanently disable due to perf problems and limited benefit // see here for perf testing (note that you must run eslint directly) // https://eslint.org/docs/developer-guide/working-with-rules#per-rule-performance 'no-empty-character-class': 'off', /** * `@typescript-eslint`plugin eslint rules * @see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin */ '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error', /** * `@rnx-kit` eslint rules * @see https://github.com/microsoft/rnx-kit/tree/main/packages/eslint-plugin */ '@rnx-kit/no-export-all': ['error', { expand: 'external-only' }], /** * `@fluentui` eslint rules / This package * @see https://www.npmjs.com/package/@fluentui/eslint-plugin */ '@fluentui/ban-imports': [ 'error', { path: 'react', names: ['useLayoutEffect'], message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', }, ], '@fluentui/no-global-react': 'error', '@fluentui/max-len': [ 'error', { ignorePatterns: [ 'require(<.*?>)?\\(', 'https?:\\/\\/', '^(import|export) ', '^\\s+(<path )?d=', '!raw-loader', '\\bdata:image/', ], max: 120, }, ], '@fluentui/no-tslint-comments': 'error', /** * jsdoc plugin rules * @see https://github.com/gajus/eslint-plugin-jsdoc */ 'jsdoc/check-tag-names': [ 'error', { // Allow TSDoc tags @remarks and @defaultValue definedTags: ['remarks', 'defaultValue'], }, ], /** * import plugin rules * @see https://github.com/import-js/eslint-plugin-import */ 'import/no-extraneous-dependencies': ['error', { devDependencies: false }], 'import/no-duplicates': 'off', 'import/first': 'off', 'import/order': 'off', 'import/extensions': 'off', 'import/newline-after-import': 'off', 'import/no-dynamic-require': 'off', 'import/no-mutable-exports': 'off', 'import/no-unresolved': 'off', 'import/no-useless-path-segments': 'off', 'import/prefer-default-export': 'off', // may cause perf problems per https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#eslint-plugin-import 'import/no-cycle': 'off', 'import/no-deprecated': 'off', 'import/no-named-as-default': 'off', 'import/no-unused-modules': 'off', // these ones aren't needed for TS and may cause perf problems 'import/default': 'off', 'import/namespace': 'off', 'import/no-named-as-default-member': 'off', 'import/export': 'off', }, }; /** @type {import("eslint").Linter.RulesRecord} */ const typeAwareRules = { /** * plugin: https://github.com/gund/eslint-plugin-deprecation */ '@typescript-eslint/no-deprecated': 'error', }; /** * Override definitions for `config`. See explanation at bottom of file for why/how this function is used. * @returns {import("eslint").Linter.ConfigOverride[]} */ const getOverrides = () => [ // Enable rules requiring type info only for appropriate files/circumstances ...configHelpers.getTypeInfoRuleOverrides(typeAwareRules), { files: '**/*.{ts,tsx}', // This turns off a few rules that don't work or are unnecessary for TS, and enables a few // that make sense for TS: no-var, prefer-const, prefer-rest-params, prefer-spread // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/eslint-recommended.ts extends: ['plugin:@typescript-eslint/eslint-recommended'], // and manually enable rules that only work on TS rules: { '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/explicit-member-accessibility': [ 'error', { accessibility: 'explicit', overrides: { constructors: 'off' }, }, ], '@typescript-eslint/member-ordering': [ 'error', { default: [ 'public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field', 'public-static-method', 'protected-static-method', 'private-static-method', 'public-constructor', 'public-instance-method', 'protected-constructor', 'protected-instance-method', 'private-constructor', 'private-instance-method', ], }, ], '@typescript-eslint/no-shadow': 'error', // permanently disable due to using other rules which do the same thing camelcase: 'off', // redundant with @typescript-eslint/naming-convention // permanently disable due to improper TS handling or unnecessary for TS // (and not covered by plugin:@typescript-eslint/eslint-recommended) 'no-empty-function': 'off', 'no-shadow': 'off', 'no-unused-vars': 'off', }, }, { // Test overrides files: [...configHelpers.testFiles], rules: { 'no-console': 'off', }, }, { files: 'src/**/*.deprecated.test.{ts,tsx}', rules: { '@typescript-eslint/no-deprecated': 'off', }, }, { // Example overrides files: '**/*.{Example,stories}.tsx', rules: { 'no-alert': 'off', 'no-console': 'off', }, }, { // Docs overrides (excluding examples) files: [...configHelpers.docsFiles], rules: { 'import/no-webpack-loader-syntax': 'off', // this is ok in docs }, }, { files: [...configHelpers.configFiles], rules: { 'no-console': 'off', }, }, { files: [...configHelpers.devDependenciesFiles], rules: { // TODO: https://github.com/microsoft/fluentui/issues/21999 'import/no-extraneous-dependencies': 'off', }, }, ]; // Why use `defineProperty` for `overrides`? // // By default, any logic in this file will be run every time the plugin is loaded (even if this // config is not used) due to it being included by necessity in the package index file. // These overrides include some more complex logic which should only run when requested, since it's // more costly and can cause build errors if run in a package it wasn't designed for. // If ESLint supported exporting a function from a config file, that would be an easy solution. // Since that's not supported, we work around it by defining overrides as a property with getter. // @ts-ignore -- `overrides?` is declared in `eslint.Linter.Config` but our `config` object doesn't define it until now Object.defineProperty(config, 'overrides', { enumerable: true, get: getOverrides, }); module.exports = config;