From 37e3e14499c1d42c0c420dfbabac3a10a9a82925 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:31:44 +0900 Subject: [PATCH 1/7] chore: update dependency @eslint/core to ^0.15.0 (#227) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- templates/package/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/package/package.json b/templates/package/package.json index 72cdf1de..4c17a7e3 100644 --- a/templates/package/package.json +++ b/templates/package/package.json @@ -46,7 +46,7 @@ }, "homepage": "https://github.com/eslint/rewrite/tree/main/packages/<%= name %>#readme", "devDependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "c8": "^9.1.0", "eslint": "^9.27.0", "mocha": "^10.4.0", From f5e6d683ee00b24b98777291c0a9a83719fe3402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A3=A8=EB=B0=80LuMir?= Date: Fri, 13 Jun 2025 23:13:02 +0900 Subject: [PATCH 2/7] chore: hoist cli tools to root level (#224) --- package.json | 3 +++ packages/compat/package.json | 6 +----- packages/config-array/package.json | 6 +----- packages/config-helpers/package.json | 6 +----- packages/core/package.json | 3 +-- packages/mcp/package.json | 5 ----- packages/migrate-config/package.json | 4 +--- packages/object-schema/package.json | 6 +----- packages/plugin-kit/package.json | 6 +----- templates/package/package.json | 6 +----- 10 files changed, 11 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 97a25f86..78cafa69 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,14 @@ "devDependencies": { "@eslint/config-helpers": "file:packages/config-helpers", "@types/mocha": "^10.0.7", + "c8": "^10.1.3", "eslint": "^9.27.0", "eslint-config-eslint": "^11.0.0", "got": "^14.4.1", "lint-staged": "^15.2.0", + "mocha": "^11.5.0", "prettier": "^3.4.1", + "rollup": "^4.42.0", "typescript": "^5.8.3", "typescript-eslint": "^8.0.0", "yorkie": "^2.0.0" diff --git a/packages/compat/package.json b/packages/compat/package.json index cb410317..77ad83ff 100644 --- a/packages/compat/package.json +++ b/packages/compat/package.json @@ -50,11 +50,7 @@ "homepage": "https://github.com/eslint/rewrite/tree/main/packages/compat#readme", "devDependencies": { "@eslint/core": "^0.15.0", - "c8": "^9.1.0", - "eslint": "^9.27.0", - "mocha": "^10.4.0", - "rollup": "^4.16.2", - "typescript": "^5.4.5" + "eslint": "^9.27.0" }, "peerDependencies": { "eslint": "^9.10.0" diff --git a/packages/config-array/package.json b/packages/config-array/package.json index 6bba334c..60cfa3f0 100644 --- a/packages/config-array/package.json +++ b/packages/config-array/package.json @@ -55,11 +55,7 @@ "devDependencies": { "@jsr/std__path": "^1.0.4", "@types/minimatch": "^3.0.5", - "c8": "^9.1.0", - "mocha": "^10.4.0", - "rollup": "^4.16.2", - "rollup-plugin-copy": "^3.5.0", - "typescript": "^5.4.5" + "rollup-plugin-copy": "^3.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/packages/config-helpers/package.json b/packages/config-helpers/package.json index 384da890..15d0f005 100644 --- a/packages/config-helpers/package.json +++ b/packages/config-helpers/package.json @@ -48,12 +48,8 @@ "homepage": "https://github.com/eslint/rewrite/tree/main/packages/config-helpers#readme", "devDependencies": { "@eslint/core": "^0.15.0", - "c8": "^9.1.0", "eslint": "^9.27.0", - "mocha": "^10.4.0", - "rollup": "^4.16.2", - "rollup-plugin-copy": "^3.5.0", - "typescript": "^5.4.5" + "rollup-plugin-copy": "^3.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/packages/core/package.json b/packages/core/package.json index 8952f32d..a31268d7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -41,8 +41,7 @@ "@types/json-schema": "^7.0.15" }, "devDependencies": { - "json-schema": "^0.4.0", - "typescript": "^5.8.3" + "json-schema": "^0.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 66d05d83..505682b8 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -32,11 +32,6 @@ "url": "https://github.com/eslint/rewrite/issues" }, "homepage": "https://github.com/eslint/rewrite/tree/main/packages/mcp#readme", - "devDependencies": { - "c8": "^9.1.0", - "mocha": "^10.4.0", - "typescript": "^5.4.5" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, diff --git a/packages/migrate-config/package.json b/packages/migrate-config/package.json index 560cee41..c354fd8d 100644 --- a/packages/migrate-config/package.json +++ b/packages/migrate-config/package.json @@ -40,9 +40,7 @@ "homepage": "https://github.com/eslint/rewrite/tree/main/packages/migrate-config#readme", "devDependencies": { "@types/eslint": "^9.6.0", - "eslint": "^9.27.0", - "mocha": "^10.4.0", - "typescript": "^5.4.5" + "eslint": "^9.27.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/packages/object-schema/package.json b/packages/object-schema/package.json index 794af474..ea0d939a 100644 --- a/packages/object-schema/package.json +++ b/packages/object-schema/package.json @@ -49,11 +49,7 @@ }, "homepage": "https://github.com/eslint/rewrite/tree/main/packages/object-schema#readme", "devDependencies": { - "c8": "^9.1.0", - "mocha": "^10.4.0", - "rollup": "^4.16.2", - "rollup-plugin-copy": "^3.5.0", - "typescript": "^5.4.5" + "rollup-plugin-copy": "^3.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/packages/plugin-kit/package.json b/packages/plugin-kit/package.json index 54e4446d..549fee33 100644 --- a/packages/plugin-kit/package.json +++ b/packages/plugin-kit/package.json @@ -53,11 +53,7 @@ }, "devDependencies": { "@types/levn": "^0.4.0", - "c8": "^9.1.0", - "mocha": "^10.4.0", - "rollup": "^4.16.2", - "rollup-plugin-copy": "^3.5.0", - "typescript": "^5.4.5" + "rollup-plugin-copy": "^3.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/templates/package/package.json b/templates/package/package.json index 4c17a7e3..77d48c0e 100644 --- a/templates/package/package.json +++ b/templates/package/package.json @@ -47,12 +47,8 @@ "homepage": "https://github.com/eslint/rewrite/tree/main/packages/<%= name %>#readme", "devDependencies": { "@eslint/core": "^0.15.0", - "c8": "^9.1.0", "eslint": "^9.27.0", - "mocha": "^10.4.0", - "rollup": "^4.16.2", - "rollup-plugin-copy": "^3.5.0", - "typescript": "^5.4.5" + "rollup-plugin-copy": "^3.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" From c5f92fa147ecad74164266c374f47ee217c7ccb7 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 18 Jun 2025 00:43:26 -0400 Subject: [PATCH 3/7] fix: Allow RuleConfig to have array with just severity (#228) --- packages/core/src/types.ts | 2 +- packages/core/tests/types/types.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f2c7d277..5dd9b3c0 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -679,7 +679,7 @@ export interface LinterOptionsConfig { */ export type RuleConfig = | Severity - | [Severity, ...RuleOptions]; + | [Severity, ...Partial]; /* eslint-disable @typescript-eslint/consistent-indexed-object-style -- needed to allow extension */ /** diff --git a/packages/core/tests/types/types.test.ts b/packages/core/tests/types/types.test.ts index dc2c3081..938ffd14 100644 --- a/packages/core/tests/types/types.test.ts +++ b/packages/core/tests/types/types.test.ts @@ -77,6 +77,7 @@ const ruleConfig5: RuleConfig<[{ available: "widely" | "newly" }]> = [ "error", { available: "widely" }, ]; +const ruleConfig6: RuleConfig<["always" | "never"]> = ["error"]; const linterConfig: LinterOptionsConfig = { noInlineConfig: true, From b96ec0c2ed6006add49c9c83a599a7d5a284348e Mon Sep 17 00:00:00 2001 From: Kelly Selden <602423+kellyselden@users.noreply.github.com> Date: Sat, 21 Jun 2025 00:31:58 -0700 Subject: [PATCH 4/7] fix: relax `@eslint/compat` eslint peerDependencies constraint (#215) --- packages/compat/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compat/package.json b/packages/compat/package.json index 77ad83ff..2f1fd49c 100644 --- a/packages/compat/package.json +++ b/packages/compat/package.json @@ -53,7 +53,7 @@ "eslint": "^9.27.0" }, "peerDependencies": { - "eslint": "^9.10.0" + "eslint": "^8.40 || 9" }, "peerDependenciesMeta": { "eslint": { From d0e5570b262920ebecc7a1f9b88d4199d82f6831 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Mon, 23 Jun 2025 14:12:30 -0400 Subject: [PATCH 5/7] ci: Switch to custom release script (#232) --- .github/workflows/release-please.yml | 237 +-------------------------- 1 file changed, 1 insertion(+), 236 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 1374f0c6..9190217f 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -54,7 +54,7 @@ jobs: #----------------------------------------------------------------------------- - name: Publish using new script - run: node scripts/publish.js --dry-run + run: node scripts/publish.js if: ${{ steps.release.outputs.releases_created == 'true' }} env: STEPS_RELEASE_OUTPUTS: ${{ toJson(steps.release.outputs) }} @@ -69,238 +69,3 @@ jobs: BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # NOTE: Packages are released in order of dependency. The packages with the - # fewest internal dependencies are released first and the packages with the - # most internal dependencies are released last. - #----------------------------------------------------------------------------- - - #----------------------------------------------------------------------------- - # @eslint/compat - #----------------------------------------------------------------------------- - - - name: Publish @eslint/compat package to npm - run: npm publish -w packages/compat --provenance - if: ${{ steps.release.outputs['packages/compat--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish @eslint/compat package to JSR - run: | - npm run build --if-present - npx jsr publish - working-directory: packages/compat - if: ${{ steps.release.outputs['packages/compat--release_created'] }} - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/compat v${{ steps.release.outputs['packages/compat--major'] }}.${{ steps.release.outputs['packages/compat--minor'] }}.${{ steps.release.outputs['packages/compat--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/compat--tag_name'] }}" - if: ${{ steps.release.outputs['packages/compat--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/core - #----------------------------------------------------------------------------- - - - name: Publish @eslint/core package to npm - run: npm publish -w packages/core --provenance - if: ${{ steps.release.outputs['packages/core--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish @eslint/core package to JSR - run: | - npm run build --if-present - npx jsr publish - working-directory: packages/core - if: ${{ steps.release.outputs['packages/core--release_created'] }} - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/core v${{ steps.release.outputs['packages/core--major'] }}.${{ steps.release.outputs['packages/core--minor'] }}.${{ steps.release.outputs['packages/core--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/core--tag_name'] }}" - if: ${{ steps.release.outputs['packages/core--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/migrate-config - #----------------------------------------------------------------------------- - - - name: Publish @eslint/migrate-config package to npm - run: npm publish -w packages/migrate-config --provenance - if: ${{ steps.release.outputs['packages/migrate-config--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - # NOTE: No JSR package because JSR doesn't support CLIs - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/migrate-config v${{ steps.release.outputs['packages/migrate-config--major'] }}.${{ steps.release.outputs['packages/migrate-config--minor'] }}.${{ steps.release.outputs['packages/migrate-config--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/migrate-config--tag_name'] }}" - if: ${{ steps.release.outputs['packages/migrate-config--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/object-schema - #----------------------------------------------------------------------------- - - - name: Publish @eslint/object-schema package to npm - run: npm publish -w packages/object-schema --provenance - if: ${{ steps.release.outputs['packages/object-schema--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish @eslint/object-schema package to JSR - run: npx jsr publish - working-directory: packages/object-schema - if: ${{ steps.release.outputs['packages/object-schema--release_created'] }} - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/object-schema v${{ steps.release.outputs['packages/object-schema--major'] }}.${{ steps.release.outputs['packages/object-schema--minor'] }}.${{ steps.release.outputs['packages/object-schema--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/object-schema--tag_name'] }}" - if: ${{ steps.release.outputs['packages/object-schema--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/config-array - #----------------------------------------------------------------------------- - - - name: Publish @eslint/config-array package to npm - run: npm publish -w packages/config-array --provenance - if: ${{ steps.release.outputs['packages/config-array--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish @eslint/config-array package to JSR - run: npx jsr publish - working-directory: packages/config-array - if: ${{ steps.release.outputs['packages/config-array--release_created'] }} - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/config-array v${{ steps.release.outputs['packages/config-array--major'] }}.${{ steps.release.outputs['packages/config-array--minor'] }}.${{ steps.release.outputs['packages/config-array--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/config-array--tag_name'] }}" - if: ${{ steps.release.outputs['packages/config-array--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/plugin-kit - #----------------------------------------------------------------------------- - - - name: Publish @eslint/plugin-kit package to npm - run: npm publish -w packages/plugin-kit --provenance - if: ${{ steps.release.outputs['packages/plugin-kit--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish @eslint/plugin-kit package to JSR - run: npx jsr publish - working-directory: packages/plugin-kit - if: ${{ steps.release.outputs['packages/plugin-kit--release_created'] }} - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/plugin-kit v${{ steps.release.outputs['packages/plugin-kit--major'] }}.${{ steps.release.outputs['packages/plugin-kit--minor'] }}.${{ steps.release.outputs['packages/plugin-kit--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/plugin-kit--tag_name'] }}" - if: ${{ steps.release.outputs['packages/plugin-kit--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/config-helpers - #----------------------------------------------------------------------------- - - - name: Publish @eslint/config-helpers package to npm - run: npm publish -w packages/config-helpers --provenance - if: ${{ steps.release.outputs['packages/config-helpers--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish @eslint/config-helpers package to JSR - run: npx jsr publish - working-directory: packages/config-helpers - if: ${{ steps.release.outputs['packages/config-helpers--release_created'] }} - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/config-helpers v${{ steps.release.outputs['packages/config-helpers--major'] }}.${{ steps.release.outputs['packages/config-helpers--minor'] }}.${{ steps.release.outputs['packages/config-helpers--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/config-helpers--tag_name'] }}" - if: ${{ steps.release.outputs['packages/config-helpers--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} - - #----------------------------------------------------------------------------- - # @eslint/mcp - #----------------------------------------------------------------------------- - - name: Publish @eslint/mcp package to npm - run: npm publish -w packages/mcp --provenance - if: ${{ steps.release.outputs['packages/mcp--release_created'] }} - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - # Note: No JSR package because JSR doesn't support CLIs - - - name: Post Release Announcement - run: npx @humanwhocodes/crosspost -t -b -m "eslint/mcp v${{ steps.release.outputs['packages/mcp--major'] }}.${{ steps.release.outputs['packages/mcp--minor'] }}.${{ steps.release.outputs['packages/mcp--patch'] }} has been released!\n\n${{ github.event.repository.html_url }}/releases/tag/${{ steps.release.outputs['packages/mcp--tag_name'] }}" - if: ${{ steps.release.outputs['packages/mcp--release_created'] }} - env: - TWITTER_API_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_API_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }} - MASTODON_HOST: ${{ secrets.MASTODON_HOST }} - BLUESKY_IDENTIFIER: ${{ vars.BLUESKY_IDENTIFIER }} - BLUESKY_PASSWORD: ${{ secrets.BLUESKY_PASSWORD }} - BLUESKY_HOST: ${{ vars.BLUESKY_HOST }} From 40d31ba42a9fe0da10b6ca5e1b10f1f2b10c5f90 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 25 Jun 2025 10:10:54 +0200 Subject: [PATCH 6/7] feat!: Support `basePath` property in config objects (#223) --- packages/config-array/README.md | 14 +- packages/config-array/src/base-schema.js | 1 + packages/config-array/src/config-array.js | 242 +- .../src/files-and-ignores-schema.js | 11 + packages/config-array/src/types.ts | 5 + .../config-array/tests/config-array.test.js | 2835 ++++++++++++++++- packages/config-helpers/src/define-config.js | 12 +- .../tests/define-config.test.js | 137 + 8 files changed, 3036 insertions(+), 221 deletions(-) diff --git a/packages/config-array/README.md b/packages/config-array/README.md index 5c3feb48..09ac6179 100644 --- a/packages/config-array/README.md +++ b/packages/config-array/README.md @@ -76,7 +76,7 @@ const configs = new ConfigArray(rawConfigs, { }); ``` -This example reads in an object or array from `my.config.js` and passes it into the `ConfigArray` constructor as the first argument. The second argument is an object specifying the `basePath` (the directory in which `my.config.js` is found) and a `schema` to define the additional properties of a config object beyond `files`, `ignores`, and `name`. +This example reads in an object or array from `my.config.js` and passes it into the `ConfigArray` constructor as the first argument. The second argument is an object specifying the `basePath` (the directory in which `my.config.js` is found) and a `schema` to define the additional properties of a config object beyond `files`, `ignores`, `basePath`, and `name`. ### Specifying a Schema @@ -165,6 +165,16 @@ export default [ js: false, }, }, + + // specific settings for files inside `src` directory + { + name: "Source files", + basePath: "src", + files: ["**/*"], + settings: { + source: true, + }, + }, ]; ``` @@ -284,7 +294,7 @@ The config array always returns an object, even if there are no configs matching A few things to keep in mind: - If a filename is not an absolute path, it will be resolved relative to the base path directory. -- The returned config object never has `files`, `ignores`, or `name` properties; the only properties on the object will be the other configuration options specified. +- The returned config object never has `files`, `ignores`, `basePath`, or `name` properties; the only properties on the object will be the other configuration options specified. - The config array caches configs, so subsequent calls to `getConfig()` with the same filename will return in a fast lookup rather than another calculation. - A config will only be generated if the filename matches an entry in a `files` key. A config will not be generated without matching a `files` key (configs without a `files` key are only applied when another config with a `files` key is applied; configs without `files` are never applied on their own). Any config with a `files` key entry that is `*` or ends with `/**` or `/*` will only be applied if another entry in the same `files` key matches or another config matches. diff --git a/packages/config-array/src/base-schema.js b/packages/config-array/src/base-schema.js index 782be15f..e60310db 100644 --- a/packages/config-array/src/base-schema.js +++ b/packages/config-array/src/base-schema.js @@ -46,6 +46,7 @@ export const baseSchema = Object.freeze({ } }, }, + basePath: NOOP_STRATEGY, files: NOOP_STRATEGY, ignores: NOOP_STRATEGY, }); diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index a9c0d177..075cef70 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -75,7 +75,7 @@ const CONFIG_TYPES = new Set(["array", "function"]); * Fields that are considered metadata and not part of the config object. * @type {Set} */ -const META_FIELDS = new Set(["name"]); +const META_FIELDS = new Set(["name", "basePath"]); /** * A schema containing just files and ignores for early validation. @@ -201,6 +201,10 @@ function assertValidBaseConfig(config, index) { const validateConfig = {}; + if ("basePath" in config) { + validateConfig.basePath = config.basePath; + } + if ("files" in config) { validateConfig.files = config.files; } @@ -279,16 +283,24 @@ function needsPatternNormalization(pattern) { /** * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes. * @param {Object} config The config object to normalize patterns in. + * @param {string} namespacedBasePath The namespaced base path of the directory to which config base path is relative. + * @param {PathImpl} path Path-handling implementation. * @returns {Object} The normalized config object. */ -function normalizeConfigPatterns(config) { +function normalizeConfigPatterns(config, namespacedBasePath, path) { if (!config) { return config; } + const hasBasePath = typeof config.basePath === "string"; + let needsNormalization = false; - if (Array.isArray(config.files)) { + if (hasBasePath) { + needsNormalization = true; + } + + if (!needsNormalization && Array.isArray(config.files)) { needsNormalization = config.files.some(pattern => { if (Array.isArray(pattern)) { return pattern.some(needsPatternNormalization); @@ -307,6 +319,17 @@ function normalizeConfigPatterns(config) { const newConfig = { ...config }; + if (hasBasePath) { + if (path.isAbsolute(config.basePath)) { + newConfig.basePath = path.toNamespacedPath(config.basePath); + } else { + newConfig.basePath = path.resolve( + namespacedBasePath, + config.basePath, + ); + } + } + if (Array.isArray(newConfig.files)) { newConfig.files = newConfig.files.map(pattern => { if (Array.isArray(pattern)) { @@ -330,10 +353,18 @@ function normalizeConfigPatterns(config) { * @param {Object} context The context object to pass into any function * found. * @param {Array} extraConfigTypes The config types to check. + * @param {string} namespacedBasePath The namespaced base path of the directory to which config base paths are relative. + * @param {PathImpl} path Path-handling implementation. * @returns {Promise} A flattened array containing only config objects. * @throws {TypeError} When a config function returns a function. */ -async function normalize(items, context, extraConfigTypes) { +async function normalize( + items, + context, + extraConfigTypes, + namespacedBasePath, + path, +) { const allowFunctions = extraConfigTypes.includes("function"); const allowArrays = extraConfigTypes.includes("array"); @@ -373,7 +404,7 @@ async function normalize(items, context, extraConfigTypes) { const configs = []; for await (const config of asyncIterable) { - configs.push(normalizeConfigPatterns(config)); + configs.push(normalizeConfigPatterns(config, namespacedBasePath, path)); } return configs; @@ -386,10 +417,18 @@ async function normalize(items, context, extraConfigTypes) { * @param {Object} context The context object to pass into any function * found. * @param {Array} extraConfigTypes The config types to check. + * @param {string} namespacedBasePath The namespaced base path of the directory to which config base paths are relative. + * @param {PathImpl} path Path-handling implementation * @returns {Array} A flattened array containing only config objects. * @throws {TypeError} When a config function returns a function. */ -function normalizeSync(items, context, extraConfigTypes) { +function normalizeSync( + items, + context, + extraConfigTypes, + namespacedBasePath, + path, +) { const allowFunctions = extraConfigTypes.includes("function"); const allowArrays = extraConfigTypes.includes("array"); @@ -427,63 +466,94 @@ function normalizeSync(items, context, extraConfigTypes) { const configs = []; for (const config of flatTraverse(items)) { - configs.push(normalizeConfigPatterns(config)); + configs.push(normalizeConfigPatterns(config, namespacedBasePath, path)); } return configs; } +/** + * Converts a given path to a relative path with all separator characters replaced by forward slashes (`"/"`). + * @param {string} fileOrDirPath The unprocessed path to convert. + * @param {string} namespacedBasePath The namespaced base path of the directory to which the calculated path shall be relative. + * @param {PathImpl} path Path-handling implementations. + * @returns {string} A relative path with all separator characters replaced by forward slashes. + */ +function toRelativePath(fileOrDirPath, namespacedBasePath, path) { + const fullPath = path.resolve(namespacedBasePath, fileOrDirPath); + const namespacedFullPath = path.toNamespacedPath(fullPath); + const relativePath = path.relative(namespacedBasePath, namespacedFullPath); + return relativePath.replaceAll(path.SEPARATOR, "/"); +} + /** * Determines if a given file path should be ignored based on the given * matcher. - * @param {Array boolean)>} ignores The ignore patterns to check. + * @param {Array<{ basePath?: string, ignores: Array boolean)>}>} configs Configuration objects containing `ignores`. * @param {string} filePath The unprocessed file path to check. * @param {string} relativeFilePath The path of the file to check relative to the base path, * using forward slash (`"/"`) as a separator. + * @param {Object} [basePathData] Additional data needed to recalculate paths for configuration objects + * that have `basePath` property. + * @param {string} [basePathData.basePath] Namespaced path to witch `relativeFilePath` is relative. + * @param {PathImpl} [basePathData.path] Path-handling implementation. * @returns {boolean} True if the path should be ignored and false if not. */ -function shouldIgnorePath(ignores, filePath, relativeFilePath) { - return ignores.reduce((ignored, matcher) => { - if (!ignored) { - if (typeof matcher === "function") { - return matcher(filePath); - } +function shouldIgnorePath( + configs, + filePath, + relativeFilePath, + { basePath, path } = {}, +) { + let shouldIgnore = false; + + for (const config of configs) { + let relativeFilePathToCheck = relativeFilePath; + if (config.basePath) { + relativeFilePathToCheck = toRelativePath( + path.resolve(basePath, relativeFilePath), + config.basePath, + path, + ); - // don't check negated patterns because we're not ignored yet - if (!matcher.startsWith("!")) { - return doMatch(relativeFilePath, matcher); + if ( + relativeFilePathToCheck === "" || + EXTERNAL_PATH_REGEX.test(relativeFilePathToCheck) + ) { + continue; } - // otherwise we're still not ignored - return false; + if (relativeFilePath.endsWith("/")) { + relativeFilePathToCheck += "/"; + } } + shouldIgnore = config.ignores.reduce((ignored, matcher) => { + if (!ignored) { + if (typeof matcher === "function") { + return matcher(filePath); + } - // only need to check negated patterns because we're ignored - if (typeof matcher === "string" && matcher.startsWith("!")) { - return !doMatch(relativeFilePath, matcher, { - flipNegate: true, - }); - } + // don't check negated patterns because we're not ignored yet + if (!matcher.startsWith("!")) { + return doMatch(relativeFilePathToCheck, matcher); + } - return ignored; - }, false); -} + // otherwise we're still not ignored + return false; + } -/** - * Determines if a given file path is matched by a config based on - * `ignores` only. - * @param {string} filePath The unprocessed file path to check. - * @param {string} relativeFilePath The path of the file to check relative to the base path, - * using forward slash (`"/"`) as a separator. - * @param {Object} config The config object to check. - * @returns {boolean} True if the file path is matched by the config, - * false if not. - */ -function pathMatchesIgnores(filePath, relativeFilePath, config) { - return ( - Object.keys(config).filter(key => !META_FIELDS.has(key)).length > 1 && - !shouldIgnorePath(config.ignores, filePath, relativeFilePath) - ); + // only need to check negated patterns because we're ignored + if (typeof matcher === "string" && matcher.startsWith("!")) { + return !doMatch(relativeFilePathToCheck, matcher, { + flipNegate: true, + }); + } + + return ignored; + }, shouldIgnore); + } + + return shouldIgnore; } /** @@ -526,8 +596,12 @@ function pathMatches(filePath, relativeFilePath, config) { * if there are any files to ignore. */ if (filePathMatchesPattern && config.ignores) { + /* + * Pass config object without `basePath`, because `relativeFilePath` is already + * calculated as relative to it. + */ filePathMatchesPattern = !shouldIgnorePath( - config.ignores, + [{ ignores: config.ignores }], filePath, relativeFilePath, ); @@ -597,20 +671,6 @@ function getPathImpl(fileOrDirPath) { ); } -/** - * Converts a given path to a relative path with all separator characters replaced by forward slashes (`"/"`). - * @param {string} fileOrDirPath The unprocessed path to convert. - * @param {string} namespacedBasePath The namespaced base path of the directory to which the calculated path shall be relative. - * @param {PathImpl} path Path-handling implementations. - * @returns {string} A relative path with all separator characters replaced by forward slashes. - */ -function toRelativePath(fileOrDirPath, namespacedBasePath, path) { - const fullPath = path.resolve(namespacedBasePath, fileOrDirPath); - const namespacedFullPath = path.toNamespacedPath(fullPath); - const relativePath = path.relative(namespacedBasePath, namespacedFullPath); - return relativePath.replaceAll(path.SEPARATOR, "/"); -} - //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -791,7 +851,7 @@ export class ConfigArray extends Array { * the matching `files` fields in any configs. This is necessary to mimic * the behavior of things like .gitignore and .eslintignore, allowing a * globbing operation to be faster. - * @returns {string[]} An array of string patterns and functions to be ignored. + * @returns {Object[]} An array of config objects representing global ignores. */ get ignores() { assertNormalized(this); @@ -818,7 +878,7 @@ export class ConfigArray extends Array { Object.keys(config).filter(key => !META_FIELDS.has(key)) .length === 1 ) { - result.push(...config.ignores); + result.push(config); } } @@ -849,6 +909,8 @@ export class ConfigArray extends Array { this, context, this.extraConfigTypes, + this.#namespacedBasePath, + this.#path, ); this.length = 0; this.push( @@ -878,6 +940,8 @@ export class ConfigArray extends Array { this, context, this.extraConfigTypes, + this.#namespacedBasePath, + this.#path, ); this.length = 0; this.push( @@ -942,13 +1006,13 @@ export class ConfigArray extends Array { // check to see if the file is outside the base path - const relativeFilePath = toRelativePath( + const relativeToBaseFilePath = toRelativePath( filePath, this.#namespacedBasePath, this.#path, ); - if (EXTERNAL_PATH_REGEX.test(relativeFilePath)) { + if (EXTERNAL_PATH_REGEX.test(relativeToBaseFilePath)) { debug(`No config for file ${filePath} outside of base path`); // cache and return result @@ -967,7 +1031,12 @@ export class ConfigArray extends Array { return CONFIG_WITH_STATUS_IGNORED; } - if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) { + if ( + shouldIgnorePath(this.ignores, filePath, relativeToBaseFilePath, { + basePath: this.#namespacedBasePath, + path: this.#path, + }) + ) { debug(`Ignoring ${filePath} based on file pattern`); // cache and return result @@ -982,6 +1051,21 @@ export class ConfigArray extends Array { const universalPattern = /^\*$|^!|\/\*{1,2}$/u; this.forEach((config, index) => { + const relativeFilePath = config.basePath + ? toRelativePath( + this.#path.resolve(this.#namespacedBasePath, filePath), + config.basePath, + this.#path, + ) + : relativeToBaseFilePath; + + if (config.basePath && EXTERNAL_PATH_REGEX.test(relativeFilePath)) { + debug( + `Skipped config found for ${filePath} (based on config's base path: ${config.basePath}`, + ); + return; + } + if (!config.files) { if (!config.ignores) { debug(`Universal config found for ${filePath}`); @@ -989,17 +1073,37 @@ export class ConfigArray extends Array { return; } - if (pathMatchesIgnores(filePath, relativeFilePath, config)) { + if ( + Object.keys(config).filter(key => !META_FIELDS.has(key)) + .length === 1 + ) { debug( - `Matching config found for ${filePath} (based on ignores: ${config.ignores})`, + `Skipped config found for ${filePath} (global ignores)`, + ); + return; + } + + /* + * Pass config object without `basePath`, because `relativeFilePath` is already + * calculated as relative to it. + */ + if ( + shouldIgnorePath( + [{ ignores: config.ignores }], + filePath, + relativeFilePath, + ) + ) { + debug( + `Skipped config found for ${filePath} (based on ignores: ${config.ignores})`, ); - matchingConfigIndices.push(index); return; } debug( - `Skipped config found for ${filePath} (based on ignores: ${config.ignores})`, + `Matching config found for ${filePath} (based on ignores: ${config.ignores})`, ); + matchingConfigIndices.push(index); return; } @@ -1228,6 +1332,10 @@ export class ConfigArray extends Array { this.ignores, this.#path.join(this.basePath, relativeDirectoryToCheck), relativeDirectoryToCheck, + { + basePath: this.#namespacedBasePath, + path: this.#path, + }, ); cache.set(relativeDirectoryToCheck, result); diff --git a/packages/config-array/src/files-and-ignores-schema.js b/packages/config-array/src/files-and-ignores-schema.js index 88b285d0..3c3adb46 100644 --- a/packages/config-array/src/files-and-ignores-schema.js +++ b/packages/config-array/src/files-and-ignores-schema.js @@ -67,6 +67,17 @@ function assertIsNonEmptyArray(value) { * @type {ObjectDefinition} */ export const filesAndIgnoresSchema = Object.freeze({ + basePath: { + required: false, + merge() { + return undefined; + }, + validate(value) { + if (typeof value !== "string") { + throw new TypeError("Expected value to be a string."); + } + }, + }, files: { required: false, merge() { diff --git a/packages/config-array/src/types.ts b/packages/config-array/src/types.ts index 1af40113..eea2e47f 100644 --- a/packages/config-array/src/types.ts +++ b/packages/config-array/src/types.ts @@ -4,6 +4,11 @@ */ export interface ConfigObject { + /** + * The base path for files and ignores. + */ + basePath?: string; + /** * The files to include. */ diff --git a/packages/config-array/tests/config-array.test.js b/packages/config-array/tests/config-array.test.js index 88a57722..11fab3d5 100644 --- a/packages/config-array/tests/config-array.test.js +++ b/packages/config-array/tests/config-array.test.js @@ -10,6 +10,7 @@ import { ConfigArray, ConfigArraySymbol } from "../src/config-array.js"; import assert from "node:assert"; import { fileURLToPath } from "node:url"; +import path from "node:path"; //----------------------------------------------------------------------------- // Helpers @@ -400,6 +401,29 @@ describe("ConfigArray", () => { /Config "foo": Key "ignores": Expected array to only contain strings and functions\./u, }); + testValidationError({ + title: "should throw an error when basePath is undefined", + configs: [ + { + name: "foo", + basePath: undefined, + }, + ], + expectedError: + /Config "foo": Key "basePath": Expected value to be a string\./u, + }); + + testValidationError({ + title: "should throw an error when basePath is not a string", + configs: [ + { + basePath: 5, + }, + ], + expectedError: + /Config \(unnamed\): Key "basePath": Expected value to be a string\./u, + }); + it("should throw an error when a config is not an object", () => { configs = new ConfigArray( [ @@ -543,6 +567,296 @@ describe("ConfigArray", () => { }); }); + describe("Config objects `basePath` normalization", () => { + it("should create a new object with absolute `basePath` when normalizing relative `basePath` on posix (async)", async () => { + const config = { + basePath: "baz/qux", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "/foo/bar", + schema, + }); + + await configs.normalize(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "/foo/bar/baz/qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with absolute `basePath` when normalizing relative `basePath` on posix (sync)", () => { + const config = { + basePath: "baz/qux", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "/foo/bar", + schema, + }); + + configs.normalizeSync(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "/foo/bar/baz/qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with absolute `basePath` without trailing slash when normalizing relative `basePath` with trailing slash on posix (async)", async () => { + const config = { + basePath: "baz/qux/", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "/foo/bar", + schema, + }); + + await configs.normalize(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "/foo/bar/baz/qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with absolute `basePath` without trailing slash when normalizing relative `basePath` with trailing slash on posix (sync)", () => { + const config = { + basePath: "baz/qux/", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "/foo/bar", + schema, + }); + + configs.normalizeSync(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "/foo/bar/baz/qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with namespaced `basePath` when normalizing relative `basePath` on windows (async)", async () => { + const config = { + basePath: "baz/qux", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "C:\\foo\\bar", + schema, + }); + + await configs.normalize(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "\\\\?\\C:\\foo\\bar\\baz\\qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with namespaced `basePath` when normalizing relative `basePath` on windows (sync)", () => { + const config = { + basePath: "baz/qux", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "C:\\foo\\bar", + schema, + }); + + configs.normalizeSync(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "\\\\?\\C:\\foo\\bar\\baz\\qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with namespaced `basePath` without trailing slash when normalizing relative `basePath` with trailing slash on windows (async)", async () => { + const config = { + basePath: "baz/qux/", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "C:\\foo\\bar", + schema, + }); + + await configs.normalize(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "\\\\?\\C:\\foo\\bar\\baz\\qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with namespaced `basePath` without trailing slash when normalizing relative `basePath` with trailing slash on windows (sync)", () => { + const config = { + basePath: "baz/qux/", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "C:\\foo\\bar", + schema, + }); + + configs.normalizeSync(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "\\\\?\\C:\\foo\\bar\\baz\\qux", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with absolute `basePath` when normalizing absolute `basePath` on posix (async)", async () => { + const config = { + basePath: "/foo", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "/foo/bar", + schema, + }); + + await configs.normalize(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "/foo", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with absolute `basePath` when normalizing absolute `basePath` on posix (sync)", () => { + const config = { + basePath: "/foo", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "/foo/bar", + schema, + }); + + configs.normalizeSync(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "/foo", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with namespaced `basePath` when normalizing absolute `basePath` on windows (async)", async () => { + const config = { + basePath: "C:\\foo", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "C:\\foo\\bar", + schema, + }); + + await configs.normalize(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "\\\\?\\C:\\foo", + defs: { + "test-def": "test-value", + }, + }); + }); + + it("should create a new object with namespaced `basePath` when normalizing absolute `basePath` on windows (sync)", () => { + const config = { + basePath: "C:\\foo", + defs: { + "test-def": "test-value", + }, + }; + + configs = new ConfigArray([config], { + basePath: "C:\\foo\\bar", + schema, + }); + + configs.normalizeSync(); + + assert.notStrictEqual(configs[0], config); + assert.deepStrictEqual(configs[0], { + basePath: "\\\\?\\C:\\foo", + defs: { + "test-def": "test-value", + }, + }); + }); + }); + describe("ConfigArray members", () => { beforeEach(() => { configs = createConfigArray(); @@ -696,6 +1010,37 @@ describe("ConfigArray", () => { }, /normalized/u); }); + it("should return config without meta fields `name`, `basePath`, `files`, and `ignores`", () => { + configs = new ConfigArray( + [ + { + name: "test config", + basePath, + ignores: ["b.js"], + files: ["*.js"], + defs: { + "test-def": "test-value", + }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.deepStrictEqual(configs.getConfigWithStatus("a.js"), { + status: "matched", + config: { + defs: { + "test-def": "test-value", + }, + }, + }); + }); + describe("should return expected results", () => { it("for a file outside the base path", () => { const filename = "../foo.js"; @@ -1461,15 +1806,406 @@ describe("ConfigArray", () => { ); }); }); - }); - describe("getConfigStatus()", () => { - it("should throw an error when not normalized", () => { - const filename = "foo.js"; - assert.throws(() => { - unnormalizedConfigs.getConfigStatus(filename); - }, /normalized/u); - }); + describe("config objects with `basePath` property", () => { + it("should apply config object without `files` or `ignores` to the `basePath` directory and its subdirectories only (relative paths)", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + defs: { severity: "warning" }, + }, + { + basePath: "src", + defs: { severity: "error" }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfig("foo.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("src/foo.js").defs.severity, + "error", + ); + assert.strictEqual( + configs.getConfig("src/subdir/foo.js").defs.severity, + "error", + ); + }); + + it("should apply config object without `files` or `ignores` to the `basePath` directory and its subdirectories only (absolute paths)", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + defs: { severity: "warning" }, + }, + { + basePath: "src", + defs: { severity: "error" }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "foo.js")).defs + .severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/foo.js")) + .defs.severity, + "error", + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "src/subdir/foo.js"), + ).defs.severity, + "error", + ); + }); + + it("should intepret `files` and `ignores` as relative to the config's `basePath` (relative paths)", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + defs: { severity: "warning" }, + }, + { + basePath: "src", + files: ["**/*.js"], + defs: { severity: "error" }, + }, + { + basePath: "src", + ignores: ["foo.js"], + defs: { severity: "fatal" }, + }, + { + basePath: "src", + files: ["**/*.js"], + ignores: ["foo.js", "bar.js"], + defs: { severity: "info" }, + }, + { + basePath: "src", + files: ["*.js"], + ignores: ["{foo,bar,baz}.js"], + defs: { severity: "log" }, + }, + { + basePath: "src", + files: ["quux.js"], + defs: { severity: "catastrophic" }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfig("foo.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("bar.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("baz.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("qux.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("quux.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("subdir/quux.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("src/foo.js").defs.severity, + "error", + ); + assert.strictEqual( + configs.getConfig("src/bar.js").defs.severity, + "fatal", + ); + assert.strictEqual( + configs.getConfig("src/baz.js").defs.severity, + "info", + ); + assert.strictEqual( + configs.getConfig("src/qux.js").defs.severity, + "log", + ); + assert.strictEqual( + configs.getConfig("src/quux.js").defs.severity, + "catastrophic", + ); + assert.strictEqual( + configs.getConfig("src/subdir/quux.js").defs.severity, + "info", + ); + }); + + it("should intepret `files` and `ignores` as relative to the config's `basePath` (absolute paths)", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + defs: { severity: "warning" }, + }, + { + basePath: "src", + files: ["**/*.js"], + defs: { severity: "error" }, + }, + { + basePath: "src", + ignores: ["foo.js"], + defs: { severity: "fatal" }, + }, + { + basePath: "src", + files: ["**/*.js"], + ignores: ["foo.js", "bar.js"], + defs: { severity: "info" }, + }, + { + basePath: "src", + files: ["*.js"], + ignores: ["{foo,bar,baz}.js"], + defs: { severity: "log" }, + }, + { + basePath: "src", + files: ["quux.js"], + defs: { severity: "catastrophic" }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "foo.js")).defs + .severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "bar.js")).defs + .severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "baz.js")).defs + .severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "qux.js")).defs + .severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "quux.js")) + .defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "subdir/quux.js"), + ).defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/foo.js")) + .defs.severity, + "error", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/bar.js")) + .defs.severity, + "fatal", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/baz.js")) + .defs.severity, + "info", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/qux.js")) + .defs.severity, + "log", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/quux.js")) + .defs.severity, + "catastrophic", + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "src/subdir/quux.js"), + ).defs.severity, + "info", + ); + }); + + it("should work correctly with both universal and non-universal `files` patterns (relative paths)", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + defs: { severity: "warning" }, + }, + { + basePath: "src", + files: ["code/**", "docs/**/*.md"], + defs: { severity: "error" }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfig("foo.js").defs.severity, + "warning", + ); + assert.strictEqual(configs.getConfig("foo.md"), undefined); + assert.strictEqual( + configs.getConfig("src/foo.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("src/foo.md"), + undefined, + ); + assert.strictEqual( + configs.getConfig("src/code/foo.js").defs.severity, + "error", + ); + assert.strictEqual( + configs.getConfig("src/code/foo.md"), + undefined, + ); + assert.strictEqual( + configs.getConfig("src/docs/foo.js").defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig("src/docs/foo.md").defs.severity, + "error", + ); + }); + + it("should work correctly with both universal and non-universal `files` patterns (absolute paths)", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + defs: { severity: "warning" }, + }, + { + basePath: "src", + files: ["code/**", "docs/**/*.md"], + defs: { severity: "error" }, + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "foo.js")).defs + .severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "foo.md")), + undefined, + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/foo.js")) + .defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig(path.resolve(basePath, "src/foo.md")), + undefined, + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "src/code/foo.js"), + ).defs.severity, + "error", + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "src/code/foo.md"), + ), + undefined, + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "src/docs/foo.js"), + ).defs.severity, + "warning", + ); + assert.strictEqual( + configs.getConfig( + path.resolve(basePath, "src/docs/foo.md"), + ).defs.severity, + "error", + ); + }); + }); + }); + + describe("getConfigStatus()", () => { + it("should throw an error when not normalized", () => { + const filename = "foo.js"; + assert.throws(() => { + unnormalizedConfigs.getConfigStatus(filename); + }, /normalized/u); + }); it('should return "matched" when passed JS filename', () => { const filename = "foo.js"; @@ -2392,15 +3128,317 @@ describe("ConfigArray", () => { ); }); }); - }); - describe("isIgnored()", () => { - it("should throw an error when not normalized", () => { - const filename = "foo.js"; - assert.throws(() => { - unnormalizedConfigs.isIgnored(filename); - }, /normalized/u); - }); + describe("config objects with `basePath` property", () => { + it(`should return "matched" for a file that is matched by a non-universal pattern (relative paths)`, () => { + configs = new ConfigArray( + [ + { + basePath: "src", + files: ["code/*.js"], + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("code/foo.js"), + "unconfigured", + ); + assert.strictEqual( + configs.getConfigStatus("src/foo.js"), + "unconfigured", + ); + assert.strictEqual( + configs.getConfigStatus("src/code/foo.js"), + "matched", + ); + }); + + it(`should return "matched" for a file that is matched by a non-universal pattern (absolute paths)`, () => { + configs = new ConfigArray( + [ + { + basePath: "src", + files: ["code/*.js"], + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "code/foo.js"), + ), + "unconfigured", + ); + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/foo.js"), + ), + "unconfigured", + ); + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/code/foo.js"), + ), + "matched", + ); + }); + + it(`should return "unconfigured" for a file under the config's base path if it isn't matched by a non-universal pattern (relative paths)`, () => { + configs = new ConfigArray( + [ + { + basePath: "src", + }, + { + basePath: "src", + files: ["*"], + }, + { + basePath: "src", + files: ["!bar.js"], + }, + { + basePath: "src", + files: ["code/*"], + }, + { + basePath: "src", + files: ["code/**"], + }, + { + basePath: "src", + files: ["!code/bar.js"], + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("src/foo.js"), + "unconfigured", + ); + assert.strictEqual( + configs.getConfigStatus("src/code/foo.js"), + "unconfigured", + ); + }); + + it(`should return "unconfigured" for a file under the config's base path if it isn't matched by a non-universal pattern (absolute paths)`, () => { + configs = new ConfigArray( + [ + { + basePath: "src", + }, + { + basePath: "src", + files: ["*"], + }, + { + basePath: "src", + files: ["!bar.js"], + }, + { + basePath: "src", + files: ["code/*"], + }, + { + basePath: "src", + files: ["code/**"], + }, + { + basePath: "src", + files: ["!code/bar.js"], + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/foo.js"), + ), + "unconfigured", + ); + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/code/foo.js"), + ), + "unconfigured", + ); + }); + + it(`should return "external" for a file that is outside config array's base path even though it is inside config's base path`, () => { + configs = new ConfigArray( + [ + { + basePath: "..", + files: ["**/*.js"], + }, + { + basePath: "../", + files: ["**/*.js"], + }, + { + basePath: "/", + files: ["**/*.js"], + }, + { + basePath: "/project", + files: ["**/*.js"], + }, + { + basePath: "/project/", + files: ["**/*.js"], + }, + ], + { + basePath: "/project/my", + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("/project/foo.js"), + "external", + ); + + assert.strictEqual( + configs.getConfigStatus("/project/notmy/foo.js"), + "external", + ); + + assert.strictEqual( + configs.getConfigStatus("/project/my/foo.js"), + "matched", + ); + }); + + it(`should return "ignored" for a file that is ignored or in an ignored directory (relative paths)`, () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + }, + { + basePath: "src", + ignores: [ + "a.js", + "tools/*.js", + "code/b.js", + "docs", + ], + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("src/a.js"), + "ignored", + ); + assert.strictEqual( + configs.getConfigStatus("src/tools/foo.js"), + "ignored", + ); + assert.strictEqual( + configs.getConfigStatus("src/code/b.js"), + "ignored", + ); + assert.strictEqual( + configs.getConfigStatus("src/docs/foo.js"), + "ignored", + ); + }); + + it(`should return "ignored" for a file that is ignored or in an ignored directory (absolute paths)`, () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + }, + { + basePath: "src", + ignores: [ + "a.js", + "tools/*.js", + "code/b.js", + "docs", + ], + }, + ], + { + basePath, + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/a.js"), + ), + "ignored", + ); + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/tools/foo.js"), + ), + "ignored", + ); + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/code/b.js"), + ), + "ignored", + ); + assert.strictEqual( + configs.getConfigStatus( + path.resolve(basePath, "src/docs/foo.js"), + ), + "ignored", + ); + }); + }); + }); + + describe("isIgnored()", () => { + it("should throw an error when not normalized", () => { + const filename = "foo.js"; + assert.throws(() => { + unnormalizedConfigs.isIgnored(filename); + }, /normalized/u); + }); it("should return false when passed JS filename", () => { const filename = "foo.js"; @@ -3003,34 +4041,1101 @@ describe("ConfigArray", () => { assert.strictEqual(configs.isFileIgnored(filename), true); }); }); - }); - - describe("isDirectoryIgnored()", () => { - it("should return true when a function return false in ignores", () => { - configs = new ConfigArray( - [ - { - ignores: [ - directoryPath => - directoryPath.includes("node_modules"), - ], - }, - ], - { - basePath, - }, - ); - - configs.normalizeSync(); - assert.strictEqual( - configs.isDirectoryIgnored("node_modules"), // No trailing slash - true, - ); - assert.strictEqual( - configs.isDirectoryIgnored("node_modules/"), // Trailing slash - true, - ); + describe("config objects with `basePath` property", () => { + it("should intepret `ignores` as relative to the config's `basePath` when ignoring directories (relative paths)", () => { + configs = new ConfigArray( + [ + { + ignores: ["src/*"], + }, + { + basePath: "src", + ignores: ["!bar"], + }, + { + basePath, + ignores: ["!projects/my/src/baz/"], + }, + { + basePath: "tools", + ignores: ["*", "!baz"], + }, + { + ignores: ["!tools/qux"], + }, + { + basePath: "scripts", + ignores: ["qux", "quux"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["my/tests"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["**/.*"], + }, + { + basePath: path.resolve(basePath, "projects/my"), + ignores: ["fixtures"], + }, + { + basePath: path.resolve( + basePath, + "projects/my/misc", + ), + ignores: ["**/tmp", "bar"], + }, + ], + { + basePath: path.resolve(basePath, "projects/my"), + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual(configs.isFileIgnored("a.js"), false); + assert.strictEqual( + configs.isFileIgnored("foo/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("src/foo/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("src/bar/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("src/baz/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("tools/foo/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("tools/baz/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("tools/qux/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("scripts/foo/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("scripts/qux/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("scripts/quux/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("tests/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored(".coverage/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("foo/.coverage/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("fixtures/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("tmp/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("foo/tmp/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/bar/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/tmp/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/tmp/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/tmp/bar/a.js"), + true, + ); + }); + + it("should intepret `ignores` as relative to the config's `basePath` when ignoring directories (absolute paths)", () => { + configs = new ConfigArray( + [ + { + ignores: ["src/*"], + }, + { + basePath: "src", + ignores: ["!bar"], + }, + { + basePath, + ignores: ["!projects/my/src/baz/"], + }, + { + basePath: "tools", + ignores: ["*", "!baz"], + }, + { + ignores: ["!tools/qux"], + }, + { + basePath: "scripts", + ignores: ["qux", "quux"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["my/tests"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["**/.*"], + }, + { + basePath: path.resolve(basePath, "projects/my"), + ignores: ["fixtures"], + }, + { + basePath: path.resolve( + basePath, + "projects/my/misc", + ), + ignores: ["**/tmp", "bar"], + }, + ], + { + basePath: path.resolve(basePath, "projects/my"), + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve(basePath, "projects/my", "a.js"), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "src/foo", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "src/bar", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "src/baz", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tools/foo", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tools/baz", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tools/qux", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "scripts/foo", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "scripts/qux", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "scripts/quux", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tests", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + ".coverage", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo/.coverage", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "fixtures", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tmp", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo/tmp", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc/foo", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc/bar", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc/tmp", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc/foo/tmp", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc/foo/tmp/bar", + "a.js", + ), + ), + true, + ); + }); + + it("should intepret `ignores` as relative to the config's `basePath` when ignoring files (relative paths)", () => { + configs = new ConfigArray( + [ + { + ignores: ["src/*"], + }, + { + basePath: "src", + ignores: ["!a.js"], + }, + { + basePath, + ignores: ["!projects/my/src/b.js"], + }, + { + basePath: "tools", + ignores: ["*", "!a.js"], + }, + { + ignores: ["!tools/b.js"], + }, + { + basePath: "scripts", + ignores: ["a.js", "b.js"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["my/tests"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["**/.*/**/*.js"], + }, + { + basePath: path.resolve(basePath, "projects/my"), + ignores: ["fixtures/*.js"], + }, + { + basePath: path.resolve( + basePath, + "projects/my/misc", + ), + ignores: ["**/tmp/*.js", "b.js"], + }, + ], + { + basePath: path.resolve(basePath, "projects/my"), + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual(configs.isFileIgnored("a.js"), false); + assert.strictEqual(configs.isFileIgnored("b.js"), false); + assert.strictEqual(configs.isFileIgnored("c.js"), false); + assert.strictEqual( + configs.isFileIgnored("foo/a.js"), + false, + ); + assert.strictEqual( + configs.isFileIgnored("foo/b.js"), + false, + ); + assert.strictEqual( + configs.isFileIgnored("foo/c.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("src/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("src/b.js"), + false, + ); + + assert.strictEqual(configs.isFileIgnored("src/c.js"), true); + + assert.strictEqual( + configs.isFileIgnored("tools/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("tools/b.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("tools/c.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("scripts/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("scripts/b.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("scripts/c.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("tests/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored(".coverage/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("foo/.coverage/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("fixtures/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("tmp/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("tmp/b.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("foo/tmp/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("foo/tmp/b.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/b.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/a.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/b.js"), + false, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/tmp/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/tmp/a.js"), + true, + ); + + assert.strictEqual( + configs.isFileIgnored("misc/foo/tmp/bar/a.js"), + false, + ); + }); + + it("should intepret `ignores` as relative to the config's `basePath` when ignoring files (absolute paths)", () => { + configs = new ConfigArray( + [ + { + ignores: ["src/*"], + }, + { + basePath: "src", + ignores: ["!a.js"], + }, + { + basePath, + ignores: ["!projects/my/src/b.js"], + }, + { + basePath: "tools", + ignores: ["*", "!a.js"], + }, + { + ignores: ["!tools/b.js"], + }, + { + basePath: "scripts", + ignores: ["a.js", "b.js"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["my/tests"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["**/.*/**/*.js"], + }, + { + basePath: path.resolve(basePath, "projects/my"), + ignores: ["fixtures/*.js"], + }, + { + basePath: path.resolve( + basePath, + "projects/my/misc", + ), + ignores: ["**/tmp/*.js", "b.js"], + }, + ], + { + basePath: path.resolve(basePath, "projects/my"), + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve(basePath, "projects/my", "a.js"), + ), + false, + ); + assert.strictEqual( + configs.isFileIgnored( + path.resolve(basePath, "projects/my", "b.js"), + ), + false, + ); + assert.strictEqual( + configs.isFileIgnored( + path.resolve(basePath, "projects/my", "c.js"), + ), + false, + ); + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + "a.js", + ), + ), + false, + ); + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + "b.js", + ), + ), + false, + ); + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + "c.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "src", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "src", + "b.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "src", + "c.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tools", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tools", + "b.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tools", + "c.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "scripts", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "scripts", + "b.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "scripts", + "c.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tests", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + ".coverage", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + ".coverage", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "fixtures", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tmp", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "tmp", + "b.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + "tmp", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "foo", + "tmp", + "b.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "b.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "foo", + "a.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "foo", + "b.js", + ), + ), + false, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "tmp", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "foo", + "tmp", + "a.js", + ), + ), + true, + ); + + assert.strictEqual( + configs.isFileIgnored( + path.resolve( + basePath, + "projects/my", + "misc", + "foo", + "tmp", + "bar", + "a.js", + ), + ), + false, + ); + }); + }); + }); + + describe("isDirectoryIgnored()", () => { + it("should return true when a function return false in ignores", () => { + configs = new ConfigArray( + [ + { + ignores: [ + directoryPath => + directoryPath.includes("node_modules"), + ], + }, + ], + { + basePath, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isDirectoryIgnored("node_modules"), // No trailing slash + true, + ); + assert.strictEqual( + configs.isDirectoryIgnored("node_modules/"), // Trailing slash + true, + ); }); it("should always return false for basePath", () => { @@ -3375,149 +5480,535 @@ describe("ConfigArray", () => { ); }); - it("should return true when a directory's content is later negated with *", () => { - configs = new ConfigArray( - [ - { - files: ["**/*.js"], - }, - { - ignores: ["**/node_modules/**"], - }, - { - ignores: ["!node_modules/*"], - }, - ], - { - basePath, - }, - ); + it("should return true when a directory's content is later negated with *", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + }, + { + ignores: ["**/node_modules/**"], + }, + { + ignores: ["!node_modules/*"], + }, + ], + { + basePath, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isDirectoryIgnored("node_modules"), + true, + ); + assert.strictEqual( + configs.isDirectoryIgnored("node_modules/"), + true, + ); + }); + + it("should return true when an ignored directory is later unignored with *", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + }, + { + ignores: ["**/node_modules/**"], + }, + { + ignores: ["!node_modules/package/*"], + }, + ], + { + basePath, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isDirectoryIgnored("node_modules/package"), + true, + ); + assert.strictEqual( + configs.isDirectoryIgnored("node_modules/package/"), + true, + ); + }); + + // https://github.com/eslint/eslint/pull/16579/files + describe("gitignore-style unignores", () => { + it("should return false when first-level subdirectories are ignored and then one is negated", () => { + configs = new ConfigArray( + [ + { + ignores: [ + "**/node_modules/*", + "!**/node_modules/foo/", + ], + }, + ], + { basePath }, + ); + + configs.normalizeSync(); + const directoryPath = "node_modules/foo"; + + assert.strictEqual( + configs.isDirectoryIgnored(directoryPath), + false, + ); + }); + + it("should return false when attempting to ignore first-level subdirectories with leading slash", () => { + configs = new ConfigArray( + [ + { + ignores: ["/**/node_modules/*"], + }, + ], + { basePath }, + ); + + configs.normalizeSync(); + const directoryPath = "node_modules/foo"; + + assert.strictEqual( + configs.isDirectoryIgnored(directoryPath), + false, + ); + }); + + it("should return true when all descendant subdirectories are ignored and then one is negated", () => { + configs = new ConfigArray( + [ + { + ignores: [ + "**/node_modules/**", + "!**/node_modules/foo/", + ], + }, + ], + { basePath }, + ); + + configs.normalizeSync(); + const directoryPath = "node_modules/foo"; + + assert.strictEqual( + configs.isDirectoryIgnored(directoryPath), + true, + ); + }); + + it("should return true when all descendant subdirectories are ignored and then other descendants are negated", () => { + configs = new ConfigArray( + [ + { + ignores: [ + "**/node_modules/**", + "!**/node_modules/foo/**", + ], + }, + ], + { basePath }, + ); + + configs.normalizeSync(); + const directoryPath = "node_modules/foo"; + + assert.strictEqual( + configs.isDirectoryIgnored(directoryPath), + true, + ); + }); + }); + + describe("config objects with `basePath` property", () => { + it("should intepret `ignores` as relative to the config's `basePath` (relative paths)", () => { + configs = new ConfigArray( + [ + { + ignores: ["src/*"], + }, + { + basePath: "src", + ignores: ["!bar"], + }, + { + basePath, + ignores: ["!projects/my/src/baz/"], + }, + { + basePath: "tools", + ignores: ["*", "!baz"], + }, + { + ignores: ["!tools/qux"], + }, + { + basePath: "scripts", + ignores: ["qux", "quux"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["my/tests"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["**/.*"], + }, + { + basePath: path.resolve(basePath, "projects/my"), + ignores: ["fixtures"], + }, + { + basePath: path.resolve( + basePath, + "projects/my/misc", + ), + ignores: ["**/tmp", "bar"], + }, + ], + { + basePath: path.resolve(basePath, "projects/my"), + schema, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isDirectoryIgnored("foo"), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("src/foo"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("src/bar"), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("src/baz"), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("tools/foo"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("tools/baz"), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("tools/qux"), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("scripts/foo"), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("scripts/qux"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("scripts/quux"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("tests"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored(".coverage"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("foo/.coverage"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("fixtures"), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored("tmp"), + false, + ); - configs.normalizeSync(); + assert.strictEqual( + configs.isDirectoryIgnored("foo/tmp"), + false, + ); - assert.strictEqual( - configs.isDirectoryIgnored("node_modules"), - true, - ); - assert.strictEqual( - configs.isDirectoryIgnored("node_modules/"), - true, - ); - }); + assert.strictEqual( + configs.isDirectoryIgnored("misc/foo"), + false, + ); - it("should return true when an ignored directory is later unignored with *", () => { - configs = new ConfigArray( - [ - { - files: ["**/*.js"], - }, - { - ignores: ["**/node_modules/**"], - }, - { - ignores: ["!node_modules/package/*"], - }, - ], - { - basePath, - }, - ); + assert.strictEqual( + configs.isDirectoryIgnored("misc/bar"), + true, + ); - configs.normalizeSync(); + assert.strictEqual( + configs.isDirectoryIgnored("misc/tmp"), + true, + ); - assert.strictEqual( - configs.isDirectoryIgnored("node_modules/package"), - true, - ); - assert.strictEqual( - configs.isDirectoryIgnored("node_modules/package/"), - true, - ); - }); + assert.strictEqual( + configs.isDirectoryIgnored("misc/foo/tmp"), + true, + ); - // https://github.com/eslint/eslint/pull/16579/files - describe("gitignore-style unignores", () => { - it("should return false when first-level subdirectories are ignored and then one is negated", () => { + assert.strictEqual( + configs.isDirectoryIgnored("misc/foo/tmp/bar"), + true, + ); + }); + + it("should intepret `ignores` as relative to the config's `basePath` (absolute paths)", () => { configs = new ConfigArray( [ { - ignores: [ - "**/node_modules/*", - "!**/node_modules/foo/", - ], + ignores: ["src/*"], + }, + { + basePath: "src", + ignores: ["!bar"], + }, + { + basePath, + ignores: ["!projects/my/src/baz/"], + }, + { + basePath: "tools", + ignores: ["*", "!baz"], + }, + { + ignores: ["!tools/qux"], + }, + { + basePath: "scripts", + ignores: ["qux", "quux"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["my/tests"], + }, + { + basePath: path.resolve(basePath, "projects"), + ignores: ["**/.*"], + }, + { + basePath: path.resolve(basePath, "projects/my"), + ignores: ["fixtures"], + }, + { + basePath: path.resolve( + basePath, + "projects/my/misc", + ), + ignores: ["**/tmp", "bar"], }, ], - { basePath }, + { + basePath: path.resolve(basePath, "projects/my"), + schema, + }, ); configs.normalizeSync(); - const directoryPath = "node_modules/foo"; assert.strictEqual( - configs.isDirectoryIgnored(directoryPath), + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "foo"), + ), false, ); - }); - it("should return false when attempting to ignore first-level subdirectories with leading slash", () => { - configs = new ConfigArray( - [ - { - ignores: ["/**/node_modules/*"], - }, - ], - { basePath }, + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "src/foo"), + ), + true, ); - configs.normalizeSync(); - const directoryPath = "node_modules/foo"; + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "src/bar"), + ), + false, + ); assert.strictEqual( - configs.isDirectoryIgnored(directoryPath), + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "src/baz"), + ), false, ); - }); - it("should return true when all descendant subdirectories are ignored and then one is negated", () => { - configs = new ConfigArray( - [ - { - ignores: [ - "**/node_modules/**", - "!**/node_modules/foo/", - ], - }, - ], - { basePath }, + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "tools/foo"), + ), + true, ); - configs.normalizeSync(); - const directoryPath = "node_modules/foo"; + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "tools/baz"), + ), + false, + ); assert.strictEqual( - configs.isDirectoryIgnored(directoryPath), + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "tools/qux"), + ), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve( + basePath, + "projects/my", + "scripts/foo", + ), + ), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve( + basePath, + "projects/my", + "scripts/qux", + ), + ), true, ); - }); - it("should return true when all descendant subdirectories are ignored and then other descendants are negated", () => { - configs = new ConfigArray( - [ - { - ignores: [ - "**/node_modules/**", - "!**/node_modules/foo/**", - ], - }, - ], - { basePath }, + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve( + basePath, + "projects/my", + "scripts/quux", + ), + ), + true, ); - configs.normalizeSync(); - const directoryPath = "node_modules/foo"; + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "tests"), + ), + true, + ); assert.strictEqual( - configs.isDirectoryIgnored(directoryPath), + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", ".coverage"), + ), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve( + basePath, + "projects/my", + "foo/.coverage", + ), + ), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "fixtures"), + ), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "tmp"), + ), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "foo/tmp"), + ), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "misc/foo"), + ), + false, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "misc/bar"), + ), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve(basePath, "projects/my", "misc/tmp"), + ), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve( + basePath, + "projects/my", + "misc/foo/tmp", + ), + ), + true, + ); + + assert.strictEqual( + configs.isDirectoryIgnored( + path.resolve( + basePath, + "projects/my", + "misc/foo/tmp/bar", + ), + ), true, ); }); @@ -3554,7 +6045,7 @@ describe("ConfigArray", () => { it("should return all ignores from all configs without files when called", () => { const expectedIgnores = configs.reduce((list, config) => { if (config.ignores && Object.keys(config).length === 1) { - list.push(...config.ignores); + list.push(config); } return list; @@ -3582,7 +6073,49 @@ describe("ConfigArray", () => { configs.isFileIgnored("ignoreme/foo.js"), true, ); - assert.deepStrictEqual(configs.ignores, ["ignoreme"]); + assert.deepStrictEqual(configs.ignores, [ + { + name: "foo", + ignores: ["ignoreme"], + }, + ]); + }); + + it("should ignore basePath field when considering global ignores", () => { + configs = new ConfigArray( + [ + { + basePath: "src", + ignores: ["ignoreme1"], + }, + { + name: "foo", + basePath: "tools", + ignores: ["ignoreme2"], + }, + ], + { + basePath, + }, + ); + + configs.normalizeSync(); + + assert.deepStrictEqual(configs.ignores, [ + { + basePath: path.toNamespacedPath( + path.join(basePath, "src"), + ), + ignores: ["ignoreme1"], + }, + { + name: "foo", + basePath: path.toNamespacedPath( + path.join(basePath, "tools"), + ), + ignores: ["ignoreme2"], + }, + ]); }); }); diff --git a/packages/config-helpers/src/define-config.js b/packages/config-helpers/src/define-config.js index b04141a5..eaf06f85 100644 --- a/packages/config-helpers/src/define-config.js +++ b/packages/config-helpers/src/define-config.js @@ -34,7 +34,7 @@ const eslintrcKeys = [ "root", ]; -const allowedGlobalIgnoreKeys = new Set(["ignores", "name"]); +const allowedGlobalIgnoreKeys = new Set(["basePath", "ignores", "name"]); /** * Gets the name of a config object. @@ -380,6 +380,12 @@ function extendConfig(baseConfig, baseConfigName, extension, extensionName) { result.name = `${baseConfigName} > ${extensionName}`; + // @ts-ignore -- ESLint types aren't updated yet + if (baseConfig.basePath) { + // @ts-ignore -- ESLint types aren't updated yet + result.basePath = baseConfig.basePath; + } + return result; } @@ -439,6 +445,10 @@ function processExtends(config, configNames) { )) { const extension = /** @type {Config} */ (extendsElement); + if ("basePath" in extension) { + throw new TypeError("'basePath' in `extends` is not allowed."); + } + if ("extends" in extension) { throw new TypeError("Nested 'extends' is not allowed."); } diff --git a/packages/config-helpers/tests/define-config.test.js b/packages/config-helpers/tests/define-config.test.js index 489f2425..af5df3cd 100644 --- a/packages/config-helpers/tests/define-config.test.js +++ b/packages/config-helpers/tests/define-config.test.js @@ -1517,6 +1517,143 @@ describe("defineConfig()", () => { }); }); + describe("basePath", () => { + it("should apply `basePath` from the base config to all resulting configs", () => { + const config = defineConfig({ + basePath: "my-base-path", + extends: [ + { rules: { "no-console": "error" } }, + [{ rules: { "no-alert": "error" } }, { ignores: ["foo"] }], + { ignores: ["bar"] }, + { files: ["src/**"], rules: { "no-console": "warn" } }, + ], + rules: { + "no-debugger": "error", + }, + }); + + assert.deepStrictEqual(config, [ + { + name: "UserConfig[0] > ExtendedConfig[0]", + basePath: "my-base-path", + rules: { "no-console": "error" }, + }, + { + name: "UserConfig[0] > ExtendedConfig[1][0]", + basePath: "my-base-path", + rules: { "no-alert": "error" }, + }, + { + name: "UserConfig[0] > ExtendedConfig[1][1]", + basePath: "my-base-path", + ignores: ["foo"], + }, + { + name: "UserConfig[0] > ExtendedConfig[2]", + basePath: "my-base-path", + ignores: ["bar"], + }, + { + name: "UserConfig[0] > ExtendedConfig[3]", + basePath: "my-base-path", + files: ["src/**"], + rules: { "no-console": "warn" }, + }, + { + basePath: "my-base-path", + rules: { + "no-debugger": "error", + }, + }, + ]); + }); + + it("should omit base config when it only has ignores", () => { + const config = defineConfig({ + basePath: "my-base-path", + ignores: ["test/*.js"], + extends: [{ rules: { "no-console": "error" } }], + }); + + assert.deepStrictEqual(config, [ + { + name: "UserConfig[0] > ExtendedConfig[0]", + basePath: "my-base-path", + ignores: ["test/*.js"], + rules: { "no-console": "error" }, + }, + ]); + }); + + it("should throw an error when an extended config has `basePath`", () => { + assert.throws(() => { + defineConfig({ + extends: [ + { + basePath: "my-base-path", + rules: { "no-console": "error" }, + }, + ], + rules: { + "no-debugger": "error", + }, + }); + }, /'basePath' in `extends` is not allowed\./u); + + assert.throws(() => { + defineConfig({ + extends: [ + { + rules: { "no-alert": "error" }, + }, + [ + { + basePath: "my-base-path", + rules: { "no-console": "error" }, + }, + ], + ], + rules: { + "no-debugger": "error", + }, + }); + }, /'basePath' in `extends` is not allowed\./u); + + assert.throws(() => { + defineConfig({ + extends: [ + { + basePath: "my-base-path", + ignores: ["foo"], + }, + ], + rules: { + "no-debugger": "error", + }, + }); + }, /'basePath' in `extends` is not allowed\./u); + + assert.throws(() => { + defineConfig({ + extends: [ + { + rules: { "no-alert": "error" }, + }, + [ + { + basePath: "my-base-path", + ignores: ["foo"], + }, + ], + ], + rules: { + "no-debugger": "error", + }, + }); + }, /'basePath' in `extends` is not allowed\./u); + }); + }); + describe("Errors", () => { it("should throw an error when null is passed to defineConfig", () => { assert.throws(() => { From 0496201974aad87fdcf3aa2a63ec74e91b54825e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:02:16 -0400 Subject: [PATCH 7/7] chore: release main (#229) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-please-manifest.json | 12 ++++++------ packages/compat/CHANGELOG.md | 14 ++++++++++++++ packages/compat/jsr.json | 2 +- packages/compat/package.json | 4 ++-- packages/config-array/CHANGELOG.md | 11 +++++++++++ packages/config-array/jsr.json | 2 +- packages/config-array/package.json | 2 +- packages/config-helpers/CHANGELOG.md | 18 ++++++++++++++++++ packages/config-helpers/jsr.json | 2 +- packages/config-helpers/package.json | 4 ++-- packages/core/CHANGELOG.md | 7 +++++++ packages/core/jsr.json | 2 +- packages/core/package.json | 2 +- packages/migrate-config/CHANGELOG.md | 9 +++++++++ packages/migrate-config/package.json | 4 ++-- packages/plugin-kit/CHANGELOG.md | 9 +++++++++ packages/plugin-kit/jsr.json | 2 +- packages/plugin-kit/package.json | 4 ++-- 18 files changed, 89 insertions(+), 21 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 63e9ee89..db87109f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,10 +1,10 @@ { - "packages/compat": "1.3.0", - "packages/config-array": "0.20.1", - "packages/config-helpers": "0.2.3", - "packages/core": "0.15.0", + "packages/compat": "1.3.1", + "packages/config-array": "0.21.0", + "packages/config-helpers": "0.3.0", + "packages/core": "0.15.1", "packages/mcp": "0.1.0", - "packages/migrate-config": "1.5.1", + "packages/migrate-config": "1.5.2", "packages/object-schema": "2.1.6", - "packages/plugin-kit": "0.3.2" + "packages/plugin-kit": "0.3.3" } diff --git a/packages/compat/CHANGELOG.md b/packages/compat/CHANGELOG.md index e258a897..a0787263 100644 --- a/packages/compat/CHANGELOG.md +++ b/packages/compat/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.3.1](https://github.com/eslint/rewrite/compare/compat-v1.3.0...compat-v1.3.1) (2025-06-25) + + +### Bug Fixes + +* relax `@eslint/compat` eslint peerDependencies constraint ([#215](https://github.com/eslint/rewrite/issues/215)) ([b96ec0c](https://github.com/eslint/rewrite/commit/b96ec0c2ed6006add49c9c83a599a7d5a284348e)) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @eslint/core bumped from ^0.15.0 to ^0.15.1 + ## [1.3.0](https://github.com/eslint/rewrite/compare/compat-v1.2.9...compat-v1.3.0) (2025-06-09) diff --git a/packages/compat/jsr.json b/packages/compat/jsr.json index b38aa4d0..27007e2d 100644 --- a/packages/compat/jsr.json +++ b/packages/compat/jsr.json @@ -1,6 +1,6 @@ { "name": "@eslint/compat", - "version": "1.3.0", + "version": "1.3.1", "exports": "./dist/esm/index.js", "publish": { "include": [ diff --git a/packages/compat/package.json b/packages/compat/package.json index 2f1fd49c..1f0479bd 100644 --- a/packages/compat/package.json +++ b/packages/compat/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/compat", - "version": "1.3.0", + "version": "1.3.1", "description": "Compatibility utilities for ESLint", "type": "module", "main": "dist/esm/index.js", @@ -49,7 +49,7 @@ }, "homepage": "https://github.com/eslint/rewrite/tree/main/packages/compat#readme", "devDependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "eslint": "^9.27.0" }, "peerDependencies": { diff --git a/packages/config-array/CHANGELOG.md b/packages/config-array/CHANGELOG.md index 4ede3a8d..e498595e 100644 --- a/packages/config-array/CHANGELOG.md +++ b/packages/config-array/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.21.0](https://github.com/eslint/rewrite/compare/config-array-v0.20.1...config-array-v0.21.0) (2025-06-25) + + +### ⚠ BREAKING CHANGES + +* Support `basePath` property in config objects ([#223](https://github.com/eslint/rewrite/issues/223)) + +### Features + +* Support `basePath` property in config objects ([#223](https://github.com/eslint/rewrite/issues/223)) ([40d31ba](https://github.com/eslint/rewrite/commit/40d31ba42a9fe0da10b6ca5e1b10f1f2b10c5f90)) + ## [0.20.1](https://github.com/eslint/rewrite/compare/config-array-v0.20.0...config-array-v0.20.1) (2025-06-09) diff --git a/packages/config-array/jsr.json b/packages/config-array/jsr.json index e8630642..c68018a7 100644 --- a/packages/config-array/jsr.json +++ b/packages/config-array/jsr.json @@ -1,6 +1,6 @@ { "name": "@eslint/config-array", - "version": "0.20.1", + "version": "0.21.0", "exports": "./dist/esm/index.js", "publish": { "include": [ diff --git a/packages/config-array/package.json b/packages/config-array/package.json index 60cfa3f0..f9fc01eb 100644 --- a/packages/config-array/package.json +++ b/packages/config-array/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/config-array", - "version": "0.20.1", + "version": "0.21.0", "description": "General purpose glob-based configuration matching.", "author": "Nicholas C. Zakas", "type": "module", diff --git a/packages/config-helpers/CHANGELOG.md b/packages/config-helpers/CHANGELOG.md index f71d7635..adf8e775 100644 --- a/packages/config-helpers/CHANGELOG.md +++ b/packages/config-helpers/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [0.3.0](https://github.com/eslint/rewrite/compare/config-helpers-v0.2.3...config-helpers-v0.3.0) (2025-06-25) + + +### ⚠ BREAKING CHANGES + +* Support `basePath` property in config objects ([#223](https://github.com/eslint/rewrite/issues/223)) + +### Features + +* Support `basePath` property in config objects ([#223](https://github.com/eslint/rewrite/issues/223)) ([40d31ba](https://github.com/eslint/rewrite/commit/40d31ba42a9fe0da10b6ca5e1b10f1f2b10c5f90)) + + +### Dependencies + +* The following workspace dependencies were updated + * devDependencies + * @eslint/core bumped from ^0.15.0 to ^0.15.1 + ## [0.2.3](https://github.com/eslint/rewrite/compare/config-helpers-v0.2.2...config-helpers-v0.2.3) (2025-06-09) diff --git a/packages/config-helpers/jsr.json b/packages/config-helpers/jsr.json index 9159f84e..2ff2cf24 100644 --- a/packages/config-helpers/jsr.json +++ b/packages/config-helpers/jsr.json @@ -1,6 +1,6 @@ { "name": "@eslint/config-helpers", - "version": "0.2.3", + "version": "0.3.0", "exports": "./dist/esm/index.js", "publish": { "include": [ diff --git a/packages/config-helpers/package.json b/packages/config-helpers/package.json index 15d0f005..7c455ea3 100644 --- a/packages/config-helpers/package.json +++ b/packages/config-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/config-helpers", - "version": "0.2.3", + "version": "0.3.0", "description": "Helper utilities for creating ESLint configuration", "type": "module", "main": "dist/esm/index.js", @@ -47,7 +47,7 @@ }, "homepage": "https://github.com/eslint/rewrite/tree/main/packages/config-helpers#readme", "devDependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "eslint": "^9.27.0", "rollup-plugin-copy": "^3.5.0" }, diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index ab8df483..54c2d24b 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.15.1](https://github.com/eslint/rewrite/compare/core-v0.15.0...core-v0.15.1) (2025-06-25) + + +### Bug Fixes + +* Allow RuleConfig to have array with just severity ([#228](https://github.com/eslint/rewrite/issues/228)) ([c5f92fa](https://github.com/eslint/rewrite/commit/c5f92fa147ecad74164266c374f47ee217c7ccb7)) + ## [0.15.0](https://github.com/eslint/rewrite/compare/core-v0.14.0...core-v0.15.0) (2025-06-09) diff --git a/packages/core/jsr.json b/packages/core/jsr.json index 34707a86..b87c6a0e 100644 --- a/packages/core/jsr.json +++ b/packages/core/jsr.json @@ -1,6 +1,6 @@ { "name": "@eslint/core", - "version": "0.15.0", + "version": "0.15.1", "exports": "./dist/esm/types.d.ts", "publish": { "include": [ diff --git a/packages/core/package.json b/packages/core/package.json index a31268d7..3033322e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/core", - "version": "0.15.0", + "version": "0.15.1", "description": "Runtime-agnostic core of ESLint", "type": "module", "types": "./dist/esm/types.d.ts", diff --git a/packages/migrate-config/CHANGELOG.md b/packages/migrate-config/CHANGELOG.md index de806dfc..3d44999e 100644 --- a/packages/migrate-config/CHANGELOG.md +++ b/packages/migrate-config/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [1.5.2](https://github.com/eslint/rewrite/compare/migrate-config-v1.5.1...migrate-config-v1.5.2) (2025-06-25) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @eslint/compat bumped from ^1.3.0 to ^1.3.1 + ## [1.5.1](https://github.com/eslint/rewrite/compare/migrate-config-v1.5.0...migrate-config-v1.5.1) (2025-06-09) diff --git a/packages/migrate-config/package.json b/packages/migrate-config/package.json index c354fd8d..b6e12317 100644 --- a/packages/migrate-config/package.json +++ b/packages/migrate-config/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/migrate-config", - "version": "1.5.1", + "version": "1.5.2", "description": "Configuration migration for ESLint", "type": "module", "bin": { @@ -46,7 +46,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "dependencies": { - "@eslint/compat": "^1.3.0", + "@eslint/compat": "^1.3.1", "@eslint/eslintrc": "^3.1.0", "camelcase": "^8.0.0", "espree": "^10.3.0", diff --git a/packages/plugin-kit/CHANGELOG.md b/packages/plugin-kit/CHANGELOG.md index 9d28ec28..89780143 100644 --- a/packages/plugin-kit/CHANGELOG.md +++ b/packages/plugin-kit/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [0.3.3](https://github.com/eslint/rewrite/compare/plugin-kit-v0.3.2...plugin-kit-v0.3.3) (2025-06-25) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @eslint/core bumped from ^0.15.0 to ^0.15.1 + ## [0.3.2](https://github.com/eslint/rewrite/compare/plugin-kit-v0.3.1...plugin-kit-v0.3.2) (2025-06-09) diff --git a/packages/plugin-kit/jsr.json b/packages/plugin-kit/jsr.json index fc1cec12..6ff6c1ae 100644 --- a/packages/plugin-kit/jsr.json +++ b/packages/plugin-kit/jsr.json @@ -1,6 +1,6 @@ { "name": "@eslint/plugin-kit", - "version": "0.3.2", + "version": "0.3.3", "exports": "./dist/esm/index.js", "publish": { "include": [ diff --git a/packages/plugin-kit/package.json b/packages/plugin-kit/package.json index 549fee33..a9cba60b 100644 --- a/packages/plugin-kit/package.json +++ b/packages/plugin-kit/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/plugin-kit", - "version": "0.3.2", + "version": "0.3.3", "description": "Utilities for building ESLint plugins.", "author": "Nicholas C. Zakas", "type": "module", @@ -48,7 +48,7 @@ ], "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "devDependencies": { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy