// @ts-check import nest from '@darraghor/eslint-plugin-nestjs-typed'; import eslint from '@eslint/js'; import stylistic from '@stylistic/eslint-plugin'; import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'; import { importX } from 'eslint-plugin-import-x'; import jest from 'eslint-plugin-jest'; import node from 'eslint-plugin-n'; import perfectionist from 'eslint-plugin-perfectionist'; import regexp from 'eslint-plugin-regexp'; import security from 'eslint-plugin-security'; import sonarjs from 'eslint-plugin-sonarjs'; import { defineConfig, globalIgnores } from 'eslint/config'; import globals from 'globals'; import tseslint from 'typescript-eslint'; const tests = ['**/*.spec.ts', 'test/**/*.ts']; export default defineConfig( globalIgnores(['dist/**', 'dist-dev/**', 'build/**', 'generated/**', 'coverage/**', 'node_modules/**']), { files: ['**/*.{ts,js,cjs,mjs}'], extends: [ eslint.configs.recommended, node.configs['flat/recommended'], importX.flatConfigs.recommended, sonarjs.configs.recommended, regexp.configs['flat/recommended'], security.configs.recommended, perfectionist.configs['recommended-natural'], stylistic.configs.customize({ indent: 2, quotes: 'single', semi: true, jsx: false }), ], languageOptions: { globals: globals.node }, linterOptions: { reportUnusedDisableDirectives: 'error' }, settings: { 'import-x/resolver-next': [createTypeScriptImportResolver()], 'node': { tryExtensions: ['.ts', '.js', '.json', '.node'], version: '24.21.0' }, }, rules: { 'curly': ['error', 'multi-line'], // This private application is deployed as a whole, never published to npm. 'n/no-unpublished-import': 'off', 'n/no-unpublished-require': 'off', // Decorator execution and object insertion order can be observable at runtime. 'perfectionist/sort-decorators': 'off', 'perfectionist/sort-objects': 'off', 'perfectionist/sort-modules': 'off', 'perfectionist/sort-classes': 'off', '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }], '@stylistic/arrow-parens': ['error', 'always'], '@stylistic/operator-linebreak': ['error', 'after'], 'import-x/no-duplicates': ['error', { 'prefer-inline': true }], }, }, { files: ['**/*.mjs'], extends: [node.configs['flat/recommended-module']], }, { files: ['**/*.ts'], extends: [tseslint.configs.recommendedTypeChecked, importX.flatConfigs.typescript], languageOptions: { sourceType: 'module', parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname }, }, rules: { // TypeScript resolves source imports; import-x checks them with the TS resolver. 'n/no-missing-import': 'off', 'no-magic-numbers': 'off', '@typescript-eslint/no-magic-numbers': [ 'error', { ignore: [ -60, -24, -10, -2, -1, 0, 1, 2, 10, 24, 60, '-60n', '-24n', '-10n', '-2n', '-1n', '0n', '1n', '2n', '10n', '24n', '60n', ], ignoreArrayIndexes: true, ignoreDefaultValues: true, ignoreEnums: true, ignoreNumericLiteralTypes: true, ignoreReadonlyClassProperties: true, ignoreTypeIndexes: true, enforceConst: true, }, ], '@typescript-eslint/no-non-null-assertion': 'error', 'no-restricted-syntax': ['error', { selector: 'PropertyDefinition[definite=true], VariableDeclarator[definite=true]', message: 'Definite assignment assertions are forbidden. Initialize owned values; use declare only for externally populated DTO fields.', }], }, }, { files: ['src/**/*.ts'], ignores: tests, extends: [nest.configs.flatRecommended], rules: { // Domain string unions are validated with IsIn rather than runtime TS enums. '@darraghor/nestjs-typed/validated-non-primitive-property-needs-type-decorator': ['error', { additionalTypeDecorators: ['IsIn'] }], }, }, { files: tests, extends: [jest.configs['flat/recommended']], rules: { '@typescript-eslint/no-magic-numbers': 'off', 'jest/expect-expect': ['error', { assertFunctionNames: ['expect', '**.expect'] }], }, }, );