/* eslint-env node */ // eslint-disable-next-line @typescript-eslint/no-var-requires const guildConfig = require('@theguild/eslint-config/base'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { REACT_RESTRICTED_SYNTAX, RESTRICTED_SYNTAX } = require('@theguild/eslint-config/constants'); const path = require('path'); const SCHEMA_PATH = './packages/services/api/src/modules/*/module.graphql.ts'; const OPERATIONS_PATHS = [ './packages/web/app/**/*.ts', './packages/web/app/**/*.tsx', './packages/web/app/**/*.graphql', ]; const rulesToExtends = Object.fromEntries( Object.entries(guildConfig.rules).filter(([key]) => [ 'import/first', 'no-restricted-globals', '@typescript-eslint/no-unused-vars', 'unicorn/no-array-push-push', 'no-else-return', 'no-lonely-if', 'unicorn/prefer-includes', 'no-extra-boolean-cast', ].includes(key), ), ); const HIVE_RESTRICTED_SYNTAX = [ { // ❌ '0.0.0.0' or `0.0.0.0` selector: ':matches(Literal[value="0.0.0.0"], TemplateElement[value.raw="0.0.0.0"])', message: 'Use "::" to make it compatible with both IPv4 and IPv6', }, ]; const tailwindCallees = ['clsx', 'cn', 'cva', 'cx']; /** * @type {import('eslint').Linter.Config} */ module.exports = { ignorePatterns: [ 'scripts', 'rules', 'out', '.hive', 'packages/web/app/.ladle/**', 'public', 'packages/web/app/src/graphql/index.ts', 'packages/libraries/cli/src/sdk.ts', 'packages/libraries/cli/src/gql/**/*', 'packages/services/storage/src/db/types.ts', 'packages/web/app/src/gql/**/*', 'codegen.cjs', 'tsup', 'packages/libraries/render-laboratory/src/laboratory.ts', 'packages/web/app/vite.config.ts', ], overrides: [ { // Setup GraphQL Parser files: '*.{graphql,gql}', parser: '@graphql-eslint/eslint-plugin', plugins: ['@graphql-eslint'], parserOptions: { schema: SCHEMA_PATH, operations: OPERATIONS_PATHS, }, rules: { '@graphql-eslint/no-deprecated': 'error', '@graphql-eslint/require-id-when-available': 'error', }, }, { // Setup processor for operations/fragments definitions on code-files files: ['packages/web/app/**/*.tsx', 'packages/web/app/**/*.ts'], processor: '@graphql-eslint/graphql', }, { files: ['packages/web/app/**/*.graphql'], plugins: ['@graphql-eslint'], rules: { '@graphql-eslint/no-deprecated': 'error', '@graphql-eslint/require-id-when-available': 'error', }, }, { files: ['*.cjs'], parserOptions: { ecmaVersion: 2020 }, }, { files: ['packages/**/*.ts', 'packages/**/*.tsx', 'cypress/**/*.ts', 'cypress/**/*.tsx'], reportUnusedDisableDirectives: true, parserOptions: { ecmaVersion: 2020, sourceType: 'module', project: [path.join(__dirname, './tsconfig.eslint.json')], }, parser: '@typescript-eslint/parser', plugins: [...guildConfig.plugins, 'hive'], extends: guildConfig.extends, rules: { 'no-process-env': 'error', 'no-empty': ['error', { allowEmptyCatch: true }], 'import/no-absolute-path': 'error', 'import/no-self-import': 'error', 'import/no-extraneous-dependencies': [ 'error', { devDependencies: [ 'packages/services/storage/tools/*.js', 'packages/services/**', 'packages/migrations/**', // We bundle it all anyway, so there are no node_modules 'packages/web/app/**', // We bundle it all anyway, so there are no node_modules 'packages/libraries/laboratory/**', '**/*.spec.ts', '**/*.test.ts', '**/*.e2e.ts', ], optionalDependencies: false, }, ], 'hive/enforce-deps-in-dev': [ 'error', { scopes: ['@hive', '@graphql-hive'], ignored: ['packages/libraries/**', 'packages/web/**'], }, ], '@typescript-eslint/no-floating-promises': 'error', 'sonarjs/no-unused-collection': 'warn', 'sonarjs/no-inverted-boolean-check': 'warn', ...rulesToExtends, 'no-lonely-if': 'off', 'object-shorthand': 'off', 'no-restricted-syntax': ['error', ...HIVE_RESTRICTED_SYNTAX, ...RESTRICTED_SYNTAX], 'prefer-destructuring': 'off', 'prefer-const': 'off', 'no-useless-escape': 'off', 'no-inner-declarations': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/ban-types': 'off', '@typescript-eslint/triple-slash-reference': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', caughtErrors: 'none', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], }, }, { files: ['packages/web/**'], extends: [ '@theguild', '@theguild/eslint-config/react', 'plugin:better-tailwindcss/legacy-recommended', 'plugin:@next/next/recommended', ], settings: { 'import/resolver': { typescript: { project: ['packages/web/app/tsconfig.json'], }, }, }, rules: { // conflicts with official prettier-plugin-tailwindcss and tailwind v3 'better-tailwindcss/enforce-consistent-class-order': 'off', 'better-tailwindcss/enforce-canonical-classes': 'off', // keeping classes in one line helps prettier-plugin-tailwindcss // enable wrapping in text editors to make classes human readable 'better-tailwindcss/enforce-consistent-line-wrapping': 'off', 'better-tailwindcss/enforce-shorthand-classes': 'off', 'react/display-name': 'off', 'react/prop-types': 'off', 'react/no-unknown-property': 'off', 'jsx-a11y/anchor-is-valid': ['off', { components: ['Link', 'NextLink'] }], 'jsx-a11y/alt-text': ['warn', { elements: ['img'], img: ['Image', 'NextImage'] }], 'no-restricted-syntax': ['error', ...HIVE_RESTRICTED_SYNTAX, ...REACT_RESTRICTED_SYNTAX], 'prefer-destructuring': 'off', 'no-console': 'off', 'no-useless-escape': 'off', '@typescript-eslint/no-non-null-assertion': 'off', 'react/jsx-no-useless-fragment': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-empty-function': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'off', 'unicorn/filename-case': 'off', 'import/no-default-export': 'off', '@next/next/no-img-element': 'off', '@typescript-eslint/ban-types': 'off', 'jsx-a11y/label-has-associated-control': 'off', 'jsx-a11y/click-events-have-key-events': 'off', 'jsx-a11y/no-static-element-interactions': 'off', '@next/next/no-html-link-for-pages': 'off', 'unicorn/no-negated-condition': 'off', 'no-implicit-coercion': 'off', 'react/jsx-key': 'warn', }, }, { files: ['packages/web/app/**'], settings: { 'better-tailwindcss': { // tailwindcss 4: the path to the entry file of the css based tailwind config (eg: `src/global.css`) entryPoint: 'packages/web/app/src/index.css', callees: tailwindCallees, }, }, rules: { // better-tailwindcss assumes you're using v4...we're being explicit here due to our dual tailwind setups 'better-tailwindcss/no-deprecated-classes': 'error', // Tailwind v4 uses CSS variables without var() syntax 'better-tailwindcss/enforce-consistent-variable-syntax': 'off', 'better-tailwindcss/no-unknown-classes': [ 'error', { ignore: [ 'drag-none', // Animation utilities (from index.css, replaces tailwindcss-animate) 'animate-in', 'animate-out', 'fade-in-.*', 'fade-out-.*', 'zoom-in-.*', 'zoom-out-.*', 'slide-in-from-.*', 'slide-out-to-.*', // Custom radius from @theme 'rounded-xs', // ring-offset with semantic colors 'ring-offset-.*', // Data attribute variants with custom animations (for Radix UI components) 'data-\\[side=(top|right|bottom|left)\\]:animate-slide-(up|down|left|right)-fade', // GraphiQL classes 'graphiql-.*', // hive classes 'hive-.*', // Schema diff custom classes (defined in index.css) 'schema-doc-row-.*', // No scrollbar utility (defined in index.css) 'no-scrollbar', // Tailwind v4 CSS variable syntax with parentheses '.*-\\(--.*\\)', ], }, ], }, }, { files: ['packages/web/app/**/*.stories.tsx', 'packages/web/docs/**'], rules: { 'react-hooks/rules-of-hooks': 'off', }, }, { files: ['packages/web/docs/**'], settings: { next: { rootDir: 'packages/web/docs', }, 'better-tailwindcss': { // tailwindcss 3: the path to the tailwind config file (eg: `tailwind.config.js`) tailwindConfig: 'packages/web/docs/tailwind.config.ts', callees: tailwindCallees, }, }, rules: { 'import/extensions': 'off', // better-tailwindcss assumes you're using v4...we're being explicit here due to our dual tailwind setups 'better-tailwindcss/no-deprecated-classes': 'off', 'better-tailwindcss/no-unknown-classes': [ 'error', { ignore: [ 'light', 'hive-focus', 'hive-focus-within', 'nextra-focus', 'nextra-scrollbar', 'no-scrollbar', // from Nextra 'hive-slider', 'hive-prose', 'subheader', 'subheading-anchor', 'duration-\\[.*\\]', // Allow arbitrary duration values like duration-[.8s] 'ease-\\[var\\(--.*\\)\\]', // Allow CSS variables in arbitrary ease values 'x:.*', // Allow Nextra 4 custom variant prefix ], }, ], // Allow CSS variables in arbitrary values for Tailwind v3 'better-tailwindcss/enforce-consistent-variable-syntax': 'off', }, }, { files: 'cypress/**', extends: 'plugin:cypress/recommended', rules: { 'cypress/no-unnecessary-waiting': 'off', 'cypress/unsafe-to-chain-command': 'off', }, }, { files: [ // environment should be parsed to avoid global dependencies and sacred .env files 'packages/**/environment.ts', // - environment is inlined and must be "registered" in next.config.js // - `import.meta.env` is not supported in Next.js yet 'packages/web/docs/**', ], rules: { 'no-process-env': 'off', }, }, ], };