Skip to content

Instantly share code, notes, and snippets.

@elektronik2k5
Last active May 22, 2024 18:09
Show Gist options
  • Save elektronik2k5/d751168a2fca66546c2d21ee4cf14646 to your computer and use it in GitHub Desktop.
Save elektronik2k5/d751168a2fca66546c2d21ee4cf14646 to your computer and use it in GitHub Desktop.
Static analysis config
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,
},
},
],
};
// 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