Last active
May 22, 2024 18:09
-
-
Save elektronik2k5/d751168a2fca66546c2d21ee4cf14646 to your computer and use it in GitHub Desktop.
Static analysis config
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const rootConfig = require('../../.eslintrc.cjs'); | |
/* eslint-disable */ | |
// cspell:ignore singleline linebreak multilines paren | |
const OFF = 'off'; | |
const WARN = 'warn'; | |
const ERROR = 'error'; | |
module.exports = { | |
extends: [ | |
...rootConfig.extends, | |
'plugin:eslint-plugin-storybook/recommended', | |
'plugin:eslint-plugin-react-hooks/recommended', | |
'plugin:eslint-plugin-jsx-a11y/recommended', | |
'plugin:eslint-plugin-vitest/recommended', | |
'plugin:eslint-plugin-compat/recommended', | |
'plugin:eslint-comments/recommended', | |
], | |
plugins: [...rootConfig.plugins, 'compat', 'vitest', 'storybook', '@emotion', 'jsx-a11y', 'react-hooks'], | |
settings: { | |
// https://github.com/amilajack/eslint-plugin-compat#linting-es-apis-experimental | |
lintAllEsApis: true, | |
}, | |
parser: '@typescript-eslint/parser', | |
parserOptions: { | |
ecmaFeatures: { | |
jsx: true, | |
}, | |
ecmaVersion: 2022, | |
sourceType: 'module', | |
project: './tsconfig.json', | |
}, | |
ignorePatterns: [ | |
'/.history/', | |
'/build/', | |
// https://stackoverflow.com/a/65063702/915875 | |
'.eslintrc.cjs', | |
'/storybook-static/', | |
'!/.storybook/', | |
'.storybook/main.ts', | |
'/coverage/', | |
'src/__generated__/', | |
], | |
rules: { | |
...rootConfig.rules, | |
'no-empty': OFF, | |
'no-debugger': WARN, | |
'prefer-const': WARN, | |
'prefer-rest-params': WARN, | |
'no-unused-labels': OFF, | |
'vitest/valid-title': OFF, // Cause we don't wanna be limited to string literals. | |
'eslint-comments/require-description': [ERROR, { ignore: ['eslint-enable'] }], | |
'@typescript-eslint/ban-ts-comment': OFF, | |
'@typescript-eslint/no-empty-function': OFF, | |
'@typescript-eslint/no-empty-interface': OFF, | |
'@typescript-eslint/no-unsafe-assignment': WARN, | |
'@typescript-eslint/no-misused-promises': [WARN, { checksVoidReturn: false }], | |
'@typescript-eslint/switch-exhaustiveness-check': ERROR, | |
'@typescript-eslint/no-unused-vars': [ | |
WARN, | |
{ args: 'all', argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', varsIgnorePattern: '^_' }, | |
], | |
'no-restricted-syntax': [ | |
ERROR, | |
{ selector: 'WithStatement', message: 'with is not allowed' }, | |
{ selector: "CallExpression[callee.name='eval']", message: 'eval is not allowed' }, | |
{ | |
// Docs: https://eslint.org/docs/developer-guide/selectors | |
// Playground: https://astexplorer.net/#/gist/4d51bf7236eec2a73dcd0cb81d132305/6e36b2ce6f431de9d75620dd5db70fcf5a73f0b9 | |
selector: 'ClassBody > MethodDefinition[kind=method]', | |
message: | |
"Methods like `foo() {}` aren't allowed due to dynamic `this` binding. Use lexically bound field initializers instead: `foo = () => {}`.", | |
}, | |
{ | |
selector: 'NewExpression[callee.name=/(Model|Store)$/]', | |
message: "Instantiation via the `new` operator of Models or Stores isn't allowed. Use the static create() method instead.", | |
}, | |
{ | |
selector: 'ClassDeclaration[superClass]', | |
message: "Extending other classes via inheritance isn't allowed. Use composition instead.", | |
}, | |
{ | |
selector: 'UnaryExpression[operator="!"][argument.type="UnaryExpression"][argument.operator="!"]', | |
message: 'Use `Boolean(operand)` or preferably `operand !== specificValue` instead of `!!operand`', | |
}, | |
], | |
'import/no-extraneous-dependencies': [ | |
WARN, | |
{ | |
packageDir: __dirname, | |
// devDependencies - should enable on source code, probably by separating stories and tests to different directory and then using glob | |
optionalDependencies: false, | |
peerDependencies: false, | |
bundledDependencies: false, | |
includeInternal: true, | |
includeTypes: true, | |
}, | |
], | |
'import/no-restricted-paths': [ | |
ERROR, | |
{ | |
zones: [ | |
{ | |
target: './src/**/!(*.stories.tsx|*.test.ts)', | |
from: './.storybook', | |
message: `Don't import storybook files from source files`, | |
}, | |
{ | |
target: './src/**/!(*.test.*)', | |
from: '**/*.test.*', | |
message: `Don't import test files from source files`, | |
}, | |
{ | |
target: './src/**/!(*.test.*)', | |
from: './tests', | |
message: `Don't import test files from source files`, | |
}, | |
], | |
}, | |
], | |
'no-restricted-imports': OFF, | |
'@typescript-eslint/no-restricted-imports': [ | |
ERROR, | |
{ | |
patterns: [ | |
{ | |
group: ['react-toastify'], | |
importNames: ['ToastContainer'], | |
message: `Use "import { ToastContainer } from '../ToasterContainer/ToasterContainer'" instead.`, | |
}, | |
{ | |
group: ['**/assets/**/*.svg'], | |
importNames: ['ReactComponent'], | |
message: `Use 'import { SomeIcon } from "components/Icon"' instead of 'import { ReactComponent as SomeIcon } from "path.to.svg"'.`, | |
}, | |
{ | |
group: ['dayjs/**'], | |
message: `All plugins, locales etc. should be imported inside helpers/dateFormatting.ts and assigned to dayjs there only.`, | |
}, | |
{ | |
group: ['**/DropdownMenuController/DropdownMenuController'], | |
message: `Use "import { Whatever } from 'components/DropdownMenu/DropdownMenu'" instead.`, | |
}, | |
], | |
paths: [ | |
{ | |
name: 'dayjs', | |
message: `Don't "import dayjs" directly, use "import { dayjs } from 'helpers/dateFormatting'" instead.`, | |
}, | |
{ | |
name: 'lodash', | |
message: `Don't "import from 'lodash'" directly, use "lodash.[method]" packages instead.`, | |
}, | |
{ | |
name: 'lodash.debounce', | |
message: `Don't "import { debounce } from 'lodash.debounce'" directly, use "import { asyncDebounce } from '../../helpers/debounce'" instead.`, | |
}, | |
// '@emotion/whatever/macro' rules cause build errors. | |
{ | |
name: '@emotion/styled/macro', | |
message: 'Use "@emotion/styled" instead.', | |
}, | |
{ | |
name: '@emotion/react/macro', | |
message: 'Use "@emotion/react" instead.', | |
}, | |
{ | |
name: 'styled-components', | |
message: 'Use "@emotion/styled" instead.', | |
}, | |
{ | |
name: 'graphql/jsutils/Maybe', | |
message: 'Use `Maybe` directly, which is defined in `vite-env.d.ts` instead, without importing.', | |
}, | |
...[ | |
'assert', | |
'buffer', | |
'child_process', | |
'cluster', | |
'crypto', | |
'dgram', | |
'dns', | |
'domain', | |
'events', | |
// cspell:disable-next-line | |
'freelist', | |
'fs', | |
'http', | |
'https', | |
'module', | |
'net', | |
'os', | |
'path', | |
'punycode', | |
'querystring', | |
'readline', | |
'repl', | |
// cspell:disable-next-line | |
'smalloc', | |
'stream', | |
'string_decoder', | |
'sys', | |
'timers', | |
'tls', | |
'tracing', | |
'tty', | |
'url', | |
'util', | |
'vm', | |
'zlib', | |
].map(function getBanRuleFromModuleName(name) { | |
return { | |
name, | |
message: "Don't import Node built-in modules in the web app. (List is from https://eslint.org/docs/rules/no-restricted-imports).", | |
}; | |
}), | |
{ | |
name: '@hello-pangea/dnd', | |
importNames: ['Droppable', 'DroppableProps'], | |
message: `Use 'import { Droppable /or/ DroppableWithoutContext } from "components/DraggableList"' instead.`, | |
}, | |
{ | |
name: '@sentry/utils', | |
importNames: ['logger'], | |
message: `Use 'import { logger } from "../logger"' instead.`, | |
}, | |
], | |
}, | |
], | |
'no-shadow': OFF, | |
'@typescript-eslint/no-shadow': WARN, | |
'no-console': WARN, | |
}, | |
overrides: [ | |
...rootConfig.overrides, | |
{ | |
files: ['src/__generated__/*.ts'], | |
rules: { | |
// TODO: investigate whether apollo codegen can generate code without duplicate identifiers. | |
'@typescript-eslint/no-redeclare': OFF, | |
}, | |
}, | |
{ | |
files: ['**/*.stories.*'], | |
rules: { | |
// Faker (used in stories) sometimes uses unbound methods | |
'@typescript-eslint/unbound-method': OFF, | |
// Storybook's CSF requires default exports. | |
'import/no-anonymous-default-export': OFF, | |
'no-console': OFF, | |
}, | |
}, | |
{ | |
files: ['src/icons/*.ts'], | |
rules: { | |
// Ignore .svg file imports in the legacy icons setup. | |
'@typescript-eslint/no-restricted-imports': OFF, | |
}, | |
}, | |
], | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: add JSDoc types. | |
const { browserslist } = require('./package.json'); | |
const ON = true; | |
const withWarningSeverity = { severity: 'warning' }; | |
/** @type {null} */ | |
const OFF = null; | |
module.exports = { | |
extends: ['stylelint-config-recommended', 'stylelint-config-standard-scss', 'stylelint-config-html', 'stylelint-prettier/recommended'], | |
plugins: [ | |
'stylelint-declaration-block-no-ignored-properties', | |
'stylelint-no-unsupported-browser-features', | |
'stylelint-use-logical-spec', | |
'stylelint-no-nested-media', | |
'stylelint-use-nesting', | |
'stylelint-scss', | |
'stylelint-prettier', | |
], | |
// cspell:ignore stylelintignore | |
/** | |
* 1. ignoreFiles can only be used in the root config: | |
* https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configure.md#ignorefiles | |
* 2. Both ignoreFiles and .stylelintignore failed to work, no matter what I tried :( | |
*/ | |
ignoreFiles: ['.history/**/*.*', './build/**/*.*', './node_modules/**/*.*', 'coverage/**/*.*', 'dist/**/*.*', 'public/**/*.*'], | |
overrides: [ | |
{ | |
files: ['**/*.{jsx,tsx}'], | |
customSyntax: '@stylelint/postcss-css-in-js', | |
rules: { | |
// Causes false positives for expressions in `${someStyle}`; interpolations. | |
'no-extra-semicolons': OFF, | |
// We need this for interpolations. | |
'no-empty-source': OFF, | |
'value-keyword-case': ['lower', { ignoreProperties: ['container-name', 'container'], camelCaseSvgKeywords: true }], | |
// cspell:ignore unspaced | |
'scss/operator-no-unspaced': OFF, | |
'scss/operator-no-newline-after': OFF, | |
'function-whitespace-after': OFF, | |
// 'function-name-case': OFF, | |
}, | |
}, | |
{ | |
files: ['**/*.html'], | |
customSyntax: 'postcss-html', | |
rules: { | |
// This triggers false positives cause nesting isn't supported in HTML, yet. | |
// cspell:ignore csstools | |
'csstools/use-nesting': OFF, | |
}, | |
}, | |
{ | |
files: ['**/*.scss'], | |
rules: { | |
// These rules are disabled cause of legacy code, which will be deleted soon. | |
'color-function-notation': OFF, | |
'custom-property-pattern': OFF, | |
'color-named': OFF, | |
'color-hex-length': OFF, | |
'declaration-no-important': OFF, // 🤦 | |
'liberty/use-logical-spec': OFF, | |
}, | |
}, | |
// { | |
// files: ['**/*.md'], | |
// customSyntax: 'postcss-markdown', | |
// }, | |
], | |
reportNeedlessDisables: ON, | |
reportInvalidScopeDisables: ON, | |
// cspell:ignore Descriptionless | |
reportDescriptionlessDisables: ON, | |
rules: { | |
'prettier/prettier': ON, | |
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: add JSDoc types. | |
'plugin/no-unsupported-browser-features': [ON, { browsers: browserslist, ignore: ['css-nesting', 'css-has'], ignorePartialSupport: true }], | |
'scss/comment-no-empty': [ON, withWarningSeverity], | |
'scss/double-slash-comment-empty-line-before': OFF, | |
'csstools/use-nesting': [ON, { syntax: '@stylelint/postcss-css-in-js' }], | |
'block-no-empty': [ON, withWarningSeverity], | |
'pitcher/no-nested-media': ON, | |
'liberty/use-logical-spec': ON, | |
'alpha-value-notation': OFF, | |
'rule-empty-line-before': OFF, | |
'at-rule-empty-line-before': OFF, | |
'comment-empty-line-before': OFF, | |
'declaration-empty-line-before': OFF, | |
'custom-property-empty-line-before': OFF, | |
// These prefixes are necessary for webkit/Safari. | |
'property-no-vendor-prefix': [ON, { ignoreProperties: ['appearance', 'box-shadow', 'backdrop-filter'] }], | |
'string-quotes': ['single', { ...withWarningSeverity, avoidEscape: true }], | |
'declaration-block-no-redundant-longhand-properties': OFF, | |
'selector-max-id': [0, { ignoreContextFunctionalPseudoClasses: [':not', '/^:(h|H)as$/'] }], | |
// It seems like using any value for "ignoreTypes" option breaks this rule :( | |
'selector-max-type': [1, { ignore: ['next-sibling'] }], | |
'selector-pseudo-class-disallowed-list': [ | |
['first-child', 'enabled', 'disabled', 'readonly', 'read-write'], | |
{ | |
// https://stylelint.io/user-guide/configure/#message | |
/** @param {string} disallowedPseudoClass */ | |
message(disallowedPseudoClass) { | |
// Joining strings to avoid escaping backticks. | |
const avoidDisallowedPseudoClassMessageParts = ['Avoid `', disallowedPseudoClass]; | |
if (disallowedPseudoClass === ':first-child') { | |
return [...avoidDisallowedPseudoClassMessageParts, " because of Emotion's SSR. Use `:first-of-type` instead."].join(''); | |
} | |
const enabledPseudo = ':enabled'; | |
const readWritePseudo = ':read-write'; | |
if ([enabledPseudo, readWritePseudo].includes(disallowedPseudoClass)) { | |
const replacementAttributeSuggestion = disallowedPseudoClass === enabledPseudo ? ':not([disabled])' : ':not([readonly])'; | |
return [ | |
...avoidDisallowedPseudoClassMessageParts, | |
'` because buttons must work within `fieldset[disabled]` (it makes anything nested `:disabled`). Use `', | |
replacementAttributeSuggestion, | |
'` instead.', | |
].join(''); | |
} | |
return [ | |
...avoidDisallowedPseudoClassMessageParts, | |
'` because buttons must work within `fieldset[disabled]` (it makes anything nested `:disabled`). Use ', | |
'`:is([', | |
disallowedPseudoClass.replace(':', ''), | |
'])` instead.', | |
].join(''); | |
}, | |
}, | |
], | |
'no-unknown-animations': ON, | |
// For those rare -webkit- prefixes that are necessary. | |
'value-no-vendor-prefix': [ON, { ignoreValues: ['box'] }], | |
'declaration-no-important': ON, | |
'at-rule-no-vendor-prefix': ON, | |
'selector-no-vendor-prefix': ON, | |
'color-named': 'always-where-possible', | |
'media-feature-name-no-vendor-prefix': ON, | |
// Disabled cause interpolations fail this rule. | |
'media-query-no-invalid': OFF, | |
'shorthand-property-no-redundant-values': ON, | |
'font-weight-notation': 'named-where-possible', | |
'property-disallowed-list': ['background', 'font'], | |
'no-descending-specificity': [ON, { ignore: ['selectors-within-list'] }], | |
'selector-pseudo-element-colon-notation': 'double', | |
'font-family-name-quotes': 'always-unless-keyword', | |
// cspell:ignore blockless | |
'max-nesting-depth': [2, { ignore: ['blockless-at-rules'] }], | |
'plugin/declaration-block-no-ignored-properties': ON, | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment