diff --git a/eslint.config.js b/eslint.config.js index c98c1e3..1ac4d5e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,6 +7,10 @@ const reactHooksPlugin = require('eslint-plugin-react-hooks'); const tsPlugin = require('@typescript-eslint/eslint-plugin'); const tsParser = require('@typescript-eslint/parser'); const unusedImportsPlugin = require('eslint-plugin-unused-imports'); +const sonarjsPluginModule = require('eslint-plugin-sonarjs'); +const unicornPluginModule = require('eslint-plugin-unicorn'); +const sonarjsPlugin = sonarjsPluginModule.default ?? sonarjsPluginModule; +const unicornPlugin = unicornPluginModule.default ?? unicornPluginModule; module.exports = [ { @@ -60,6 +64,30 @@ module.exports = [ ], }, }, + { + files: ['src/**/*.{js,jsx,ts,tsx}'], + ignores: [ + 'src/**/__tests__/**', + 'src/**/*.test.{js,jsx,ts,tsx}', + 'src/**/*.spec.{js,jsx,ts,tsx}', + ], + plugins: { + sonarjs: sonarjsPlugin, + unicorn: unicornPlugin, + }, + rules: { + 'sonarjs/no-collapsible-if': 'error', + 'sonarjs/no-identical-conditions': 'error', + 'sonarjs/no-identical-expressions': 'error', + 'sonarjs/no-ignored-return': 'error', + 'sonarjs/no-inverted-boolean-check': 'error', + 'unicorn/no-array-callback-reference': 'error', + 'unicorn/no-invalid-fetch-options': 'error', + 'unicorn/prefer-array-some': 'error', + 'unicorn/prefer-optional-catch-binding': 'error', + 'unicorn/prefer-string-starts-ends-with': 'error', + }, + }, { files: ['**/*.{jsx,tsx}'], languageOptions: { diff --git a/package-lock.json b/package-lock.json index 2a6f6ec..a938ab4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,9 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-sonarjs": "^3.0.7", "eslint-plugin-tailwindcss": "^4.0.0-beta.0", + "eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.3.0", "husky": "^9.1.7", @@ -7007,6 +7009,29 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cacache": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", @@ -7226,6 +7251,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -7319,6 +7351,29 @@ "dev": true, "license": "MIT" }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -9330,6 +9385,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-sonarjs": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.7.tgz", + "integrity": "sha512-62jB20krIPvcwBLAyG3VVKa2ce2j2lL1yCb8Y0ylMRR/dLvCCTiQx8gQbXb+G81k1alPZ2/I3muZinqWQdBbzw==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "@eslint-community/regexpp": "4.12.2", + "builtin-modules": "3.3.0", + "bytes": "3.1.2", + "functional-red-black-tree": "1.0.1", + "jsx-ast-utils-x": "0.1.0", + "lodash.merge": "4.6.2", + "minimatch": "10.1.2", + "scslre": "0.3.0", + "semver": "7.7.4", + "typescript": ">=5" + }, + "peerDependencies": { + "eslint": "^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-sonarjs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-tailwindcss": { "version": "4.0.0-beta.0", "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-4.0.0-beta.0.tgz", @@ -9349,6 +9439,92 @@ "tailwindcss": "^3.4.0 || ^4.0.0" } }, + "node_modules/eslint-plugin-unicorn": { + "version": "63.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-63.0.0.tgz", + "integrity": "sha512-Iqecl9118uQEXYh7adylgEmGfkn5es3/mlQTLLkd4pXkIk9CTGrAbeUux+YljSa2ohXCBmQQ0+Ej1kZaFgcfkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "@eslint-community/eslint-utils": "^4.9.0", + "change-case": "^5.4.4", + "ci-info": "^4.3.1", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.46.0", + "find-up-simple": "^1.0.1", + "globals": "^16.4.0", + "indent-string": "^5.0.0", + "is-builtin-module": "^5.0.0", + "jsesc": "^3.1.0", + "pluralize": "^8.0.0", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.13.0", + "semver": "^7.7.3", + "strip-indent": "^4.1.1" + }, + "engines": { + "node": "^20.10.0 || >=21.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=9.38.0" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/strip-indent": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-unused-imports": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", @@ -9893,6 +10069,19 @@ "node": ">=8" } }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -10102,6 +10291,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -11011,6 +11207,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-builtin-module": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-5.0.0.tgz", + "integrity": "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-modules": "^5.0.0" + }, + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-builtin-module/node_modules/builtin-modules": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.0.0.tgz", + "integrity": "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -12859,6 +13084,16 @@ "node": ">=4.0" } }, + "node_modules/jsx-ast-utils-x": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils-x/-/jsx-ast-utils-x-0.1.0.tgz", + "integrity": "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -15721,6 +15956,16 @@ "node": ">=10.4.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -16317,6 +16562,19 @@ "node": ">=8" } }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -16360,6 +16618,30 @@ "node": ">=4" } }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -16910,6 +17192,21 @@ "dev": true, "license": "MIT" }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/package.json b/package.json index 31797a8..ee47d6e 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,9 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-sonarjs": "^3.0.7", "eslint-plugin-tailwindcss": "^4.0.0-beta.0", + "eslint-plugin-unicorn": "^63.0.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.3.0", "husky": "^9.1.7", diff --git a/src/renderer/components/App.tsx b/src/renderer/components/App.tsx index bc7bc79..e65e57f 100755 --- a/src/renderer/components/App.tsx +++ b/src/renderer/components/App.tsx @@ -544,12 +544,8 @@ const App = () => { let folders: string[] = []; for (const item of folder.children ?? []) { - if (item.type === 'directory') { - // Validate each folder is within current root - if (isPathWithinRootBoundary(item.path)) { - folders.push(item.path); - folders = [...folders, ...getAllSubFolders(item)]; - } + if (item.type === 'directory' && isPathWithinRootBoundary(item.path)) { + folders.push(item.path, ...getAllSubFolders(item)); } } diff --git a/src/utils/file-analyzer.ts b/src/utils/file-analyzer.ts index 1d8048a..bfd5215 100755 --- a/src/utils/file-analyzer.ts +++ b/src/utils/file-analyzer.ts @@ -85,11 +85,10 @@ class FileAnalyzer { this.config.use_custom_includes !== false && this.config.include_extensions && Array.isArray(this.config.include_extensions) && - ext + ext && + !this.config.include_extensions.includes(ext.toLowerCase()) ) { - if (!this.config.include_extensions.includes(ext.toLowerCase())) { - return false; // Exclude files with extensions not in the include list - } + return false; // Exclude files with extensions not in the include list } // 2. Build patterns array with proper structure and priority diff --git a/tests/catalog.md b/tests/catalog.md index b143a10..ef9a6e0 100644 --- a/tests/catalog.md +++ b/tests/catalog.md @@ -38,6 +38,7 @@ Purpose: quick map of what is covered, why it exists, and which command to run. | `tests/unit/utils/token-counter.test.ts` | `src/utils/token-counter.ts` | Token counting basics, empty/null input handling | | `tests/unit/scripts/security.test.js` | `scripts/lib/security.js` | Command safety validation, Windows path acceptance for approved executables | | `tests/unit/scripts/actions-freshness.test.js` | `scripts/lib/actions-freshness.js` | Workflow `uses:` reference parsing, pinning classification, freshness markdown report output | +| `tests/unit/scripts/eslint-config.test.js` | `eslint.config.js` | Guard scoped unicorn/sonarjs strict-pack configuration and test exclusions | | `tests/unit/scripts/sonar-options.test.js` | `scripts/lib/sonar-options.js` | Sonar scanner option merge behavior and CPD exclusion defaults | | `tests/unit/scripts/publish-stress-metrics.test.js` | `scripts/publish-stress-metrics.js` | Prometheus payload generation and Pushgateway publication safeguards | | `tests/unit/scripts/verify-prometheus-metrics.test.js` | `scripts/verify-prometheus-metrics.js` | Prometheus scrape verification retries, timeouts, and parsing | diff --git a/tests/unit/scripts/eslint-config.test.js b/tests/unit/scripts/eslint-config.test.js new file mode 100644 index 0000000..611f44b --- /dev/null +++ b/tests/unit/scripts/eslint-config.test.js @@ -0,0 +1,27 @@ +const fs = jest.requireActual('fs'); +const path = jest.requireActual('path'); + +const eslintConfigPath = path.resolve(__dirname, '../../../eslint.config.js'); +const eslintConfigSource = fs.readFileSync(eslintConfigPath, 'utf8'); + +describe('eslint phase 2 strict packs config', () => { + test('scopes strict packs to source files and excludes test files', () => { + expect(eslintConfigSource).toContain("files: ['src/**/*.{js,jsx,ts,tsx}']"); + expect(eslintConfigSource).toContain("'src/**/__tests__/**'"); + expect(eslintConfigSource).toContain("'src/**/*.test.{js,jsx,ts,tsx}'"); + expect(eslintConfigSource).toContain("'src/**/*.spec.{js,jsx,ts,tsx}'"); + }); + + test('enforces selected sonarjs and unicorn rules', () => { + expect(eslintConfigSource).toContain("'sonarjs/no-collapsible-if': 'error'"); + expect(eslintConfigSource).toContain("'sonarjs/no-identical-conditions': 'error'"); + expect(eslintConfigSource).toContain("'sonarjs/no-identical-expressions': 'error'"); + expect(eslintConfigSource).toContain("'sonarjs/no-ignored-return': 'error'"); + expect(eslintConfigSource).toContain("'sonarjs/no-inverted-boolean-check': 'error'"); + expect(eslintConfigSource).toContain("'unicorn/no-array-callback-reference': 'error'"); + expect(eslintConfigSource).toContain("'unicorn/no-invalid-fetch-options': 'error'"); + expect(eslintConfigSource).toContain("'unicorn/prefer-array-some': 'error'"); + expect(eslintConfigSource).toContain("'unicorn/prefer-optional-catch-binding': 'error'"); + expect(eslintConfigSource).toContain("'unicorn/prefer-string-starts-ends-with': 'error'"); + }); +});