diff --git a/common/changes/@rushstack/eslint-config/eslint-config-type-aware-rules_2026-09-13-04-00-00.json b/common/changes/@rushstack/eslint-config/eslint-config-type-aware-rules_2026-09-13-04-00-00.json new file mode 100644 index 00000000000..3a31b8f141f --- /dev/null +++ b/common/changes/@rushstack/eslint-config/eslint-config-type-aware-rules_2026-09-13-04-00-00.json @@ -0,0 +1,9 @@ +{ + "changes": [ + { + "packageName": "@rushstack/eslint-config", + "comment": "Group the profile's type-aware rules into an exported `typeAwareRules` set and add a `flat/without-type-information` helper for linting files that are not part of the TypeScript program with only the non-type-aware rules.", + "type": "minor" + } + ] +} diff --git a/common/changes/@rushstack/eslint-config/eslint-config-typescript_2026-09-13-04-30-00.json b/common/changes/@rushstack/eslint-config/eslint-config-typescript_2026-09-13-04-30-00.json new file mode 100644 index 00000000000..3bd5463b84e --- /dev/null +++ b/common/changes/@rushstack/eslint-config/eslint-config-typescript_2026-09-13-04-30-00.json @@ -0,0 +1,9 @@ +{ + "changes": [ + { + "packageName": "@rushstack/eslint-config", + "comment": "Author the package in TypeScript (built to CommonJS via Heft). The existing subpath entry points are preserved via a `package.json` `\"exports\"` map and the compiled configuration is runtime-identical to the previous JavaScript.", + "type": "patch" + } + ] +} diff --git a/common/changes/@rushstack/eslint-plugin-packlets/eslint-config-typescript_2026-09-14-04-00-00.json b/common/changes/@rushstack/eslint-plugin-packlets/eslint-config-typescript_2026-09-14-04-00-00.json new file mode 100644 index 00000000000..188b364da5d --- /dev/null +++ b/common/changes/@rushstack/eslint-plugin-packlets/eslint-config-typescript_2026-09-14-04-00-00.json @@ -0,0 +1,9 @@ +{ + "changes": [ + { + "packageName": "@rushstack/eslint-plugin-packlets", + "comment": "Type the plugin's default export as `ESLint.Plugin` so that it can be referenced in ESLint flat-config `plugins` maps without a cast. This is a type-only change; the runtime export is unchanged.", + "type": "patch" + } + ] +} diff --git a/common/changes/@rushstack/eslint-plugin-security/eslint-config-typescript_2026-09-14-04-00-00.json b/common/changes/@rushstack/eslint-plugin-security/eslint-config-typescript_2026-09-14-04-00-00.json new file mode 100644 index 00000000000..79793b0a962 --- /dev/null +++ b/common/changes/@rushstack/eslint-plugin-security/eslint-config-typescript_2026-09-14-04-00-00.json @@ -0,0 +1,9 @@ +{ + "changes": [ + { + "packageName": "@rushstack/eslint-plugin-security", + "comment": "Type the plugin's default export as `ESLint.Plugin` so that it can be referenced in ESLint flat-config `plugins` maps without a cast. This is a type-only change; the runtime export is unchanged.", + "type": "patch" + } + ] +} diff --git a/common/changes/@rushstack/eslint-plugin/eslint-config-typescript_2026-09-14-04-00-00.json b/common/changes/@rushstack/eslint-plugin/eslint-config-typescript_2026-09-14-04-00-00.json new file mode 100644 index 00000000000..791e582f809 --- /dev/null +++ b/common/changes/@rushstack/eslint-plugin/eslint-config-typescript_2026-09-14-04-00-00.json @@ -0,0 +1,9 @@ +{ + "changes": [ + { + "packageName": "@rushstack/eslint-plugin", + "comment": "Type the plugin's default export as `ESLint.Plugin` so that it can be referenced in ESLint flat-config `plugins` maps without a cast. This is a type-only change; the runtime export is unchanged.", + "type": "patch" + } + ] +} diff --git a/common/config/rush/pnpm-config.json b/common/config/rush/pnpm-config.json index 95be7286441..2c1f6a9b597 100644 --- a/common/config/rush/pnpm-config.json +++ b/common/config/rush/pnpm-config.json @@ -476,6 +476,17 @@ } }, + "eslint-plugin-tsdoc": { + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + }, + "peerDependencies": { + "eslint": "*" + } + }, + "query-ast": { "dependencies": { "lodash": "~4.17.15" diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index 928ffa7350d..1b399279287 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -11,7 +11,7 @@ overrides: loader-utils@^2.0.0: 2.0.4 fast-xml-parser@^5.3.3: 5.3.5 -packageExtensionsChecksum: sha256-8fXYR9X9qRA57SZJJSADz6C9KMP6QQYYut4DHyehah0= +packageExtensionsChecksum: sha256-oUDDH9n0S+YRKY1TEmsTuFsNCNlmI4mpaLdAtVDhjgY= pnpmfileChecksum: sha256-E1T7OJ3DLTjpDqf4RdJzK9VDtAxgm4gDEQCLYdHD8nI= @@ -1970,6 +1970,11 @@ packages: eslint-plugin-tsdoc@0.5.2: resolution: {integrity: sha512-BlvqjWZdBJDIPO/YU3zcPCF23CvjYT3gyu63yo6b609NNV3D1b6zceAREy2xnweuBoDpZcLNuPyAUq9cvx6bbQ==} + peerDependencies: + eslint: '*' + peerDependenciesMeta: + eslint: + optional: true eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -6213,8 +6218,9 @@ snapshots: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.1 '@typescript-eslint/utils': 8.56.1(eslint@9.25.1)(typescript@4.9.5) + optionalDependencies: + eslint: 9.25.1 transitivePeerDependencies: - - eslint - supports-color - typescript @@ -6223,8 +6229,9 @@ snapshots: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.1 '@typescript-eslint/utils': 8.56.1(eslint@9.37.0)(typescript@5.8.3) + optionalDependencies: + eslint: 9.37.0 transitivePeerDependencies: - - eslint - supports-color - typescript diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index a8b0af670ab..30a9f67ff2c 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "50a1f3c8d2270f840d49426b54c028e26de05189", + "pnpmShrinkwrapHash": "a677ee686d23fba9fabdb7e97011613afc372b04", "preferredVersionsHash": "550b4cee0bef4e97db6c6aad726df5149d20e7d9", - "packageJsonInjectedDependenciesHash": "af9e972a5d86601391889a0ff0ae8349679a6a10" + "packageJsonInjectedDependenciesHash": "d7c7e7ed81eabefe053347ab6e27d121c3d0c2db" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index a9c01ddb734..8c8146a1138 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -11,7 +11,7 @@ overrides: loader-utils@^2.0.0: 2.0.4 fast-xml-parser@^5.3.3: 5.3.5 -packageExtensionsChecksum: sha256-8fXYR9X9qRA57SZJJSADz6C9KMP6QQYYut4DHyehah0= +packageExtensionsChecksum: sha256-oUDDH9n0S+YRKY1TEmsTuFsNCNlmI4mpaLdAtVDhjgY= pnpmfileChecksum: sha256-E1T7OJ3DLTjpDqf4RdJzK9VDtAxgm4gDEQCLYdHD8nI= @@ -2990,6 +2990,12 @@ importers: specifier: ~0.5.1 version: 0.5.2(eslint@9.37.0)(typescript@5.8.2) devDependencies: + '@rushstack/heft': + specifier: 1.2.25 + version: 1.2.25(@types/node@22.9.3) + decoupled-local-node-rig: + specifier: workspace:* + version: link:../../rigs/decoupled-local-node-rig eslint: specifier: ~9.37.0 version: 9.37.0 @@ -4743,7 +4749,7 @@ importers: version: 1.2.25(@types/node@20.17.19) '@rushstack/heft-node-rig': specifier: 2.11.48 - version: 2.11.48(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0) + version: 2.11.48(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0) '@types/jest': specifier: 30.0.0 version: 30.0.0 @@ -8870,7 +8876,7 @@ packages: '@pnpm/logger': ^5.0.0 '@pnpm/lockfile-types@5.1.5': - resolution: {integrity: sha1-FLhcl23c90dPWmopNRy1eZWdDsg=, tarball: https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@pnpm/lockfile-types/-/lockfile-types-5.1.5.tgz} + resolution: {integrity: sha1-FLhcl23c90dPWmopNRy1eZWdDsg=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@pnpm/lockfile-types/-/lockfile-types-5.1.5.tgz} engines: {node: '>=16.14'} '@pnpm/lockfile.fs@1001.1.32': @@ -12380,7 +12386,7 @@ packages: hasBin: true cross-spawn@6.0.6: - resolution: {integrity: sha1-MNDvoHEt2361p24ehyG/+vprXVc=, tarball: https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/cross-spawn/-/cross-spawn-6.0.6.tgz} + resolution: {integrity: sha1-MNDvoHEt2361p24ehyG/+vprXVc=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/cross-spawn/-/cross-spawn-6.0.6.tgz} engines: {node: '>=4.8'} cross-spawn@7.0.6: @@ -13260,9 +13266,19 @@ packages: eslint-plugin-tsdoc@0.3.0: resolution: {integrity: sha512-0MuFdBrrJVBjT/gyhkP2BqpD0np1NxNLfQ38xXDlSs/KVVpKI2A6vN7jx2Rve/CyUsvOsMGwp9KKrinv7q9g3A==} + peerDependencies: + eslint: '*' + peerDependenciesMeta: + eslint: + optional: true eslint-plugin-tsdoc@0.5.2: resolution: {integrity: sha512-BlvqjWZdBJDIPO/YU3zcPCF23CvjYT3gyu63yo6b609NNV3D1b6zceAREy2xnweuBoDpZcLNuPyAUq9cvx6bbQ==} + peerDependencies: + eslint: '*' + peerDependenciesMeta: + eslint: + optional: true eslint-scope@4.0.3: resolution: {integrity: sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==} @@ -13444,7 +13460,7 @@ packages: engines: {node: '>=6'} execa@5.1.1: - resolution: {integrity: sha1-+ArZy/Qpj3vR1MlVXCHpN0HEEd0=, tarball: https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/execa/-/execa-5.1.1.tgz} + resolution: {integrity: sha1-+ArZy/Qpj3vR1MlVXCHpN0HEEd0=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/execa/-/execa-5.1.1.tgz} engines: {node: '>=10'} exit-x@0.2.2: @@ -13914,15 +13930,15 @@ packages: engines: {node: '>= 0.4'} get-stream@4.1.0: - resolution: {integrity: sha1-wbJVV189wh1Zv8ec09K0axw6VLU=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-stream/-/get-stream-4.1.0.tgz} + resolution: {integrity: sha1-wbJVV189wh1Zv8ec09K0axw6VLU=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-stream/-/get-stream-4.1.0.tgz} engines: {node: '>=6'} get-stream@5.2.0: - resolution: {integrity: sha1-SWaheV7lrOZecGxLe+txJX1uItM=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-stream/-/get-stream-5.2.0.tgz} + resolution: {integrity: sha1-SWaheV7lrOZecGxLe+txJX1uItM=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-stream/-/get-stream-5.2.0.tgz} engines: {node: '>=8'} get-stream@6.0.1: - resolution: {integrity: sha1-omLY7vZ6ztV8KFKtYWdSakPL97c=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-stream/-/get-stream-6.0.1.tgz} + resolution: {integrity: sha1-omLY7vZ6ztV8KFKtYWdSakPL97c=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-stream/-/get-stream-6.0.1.tgz} engines: {node: '>=10'} get-symbol-description@1.1.0: @@ -14292,7 +14308,7 @@ packages: engines: {node: '>= 14'} human-signals@2.1.0: - resolution: {integrity: sha1-3JH8ukLk0G5Kuu0zs+ejwC9RTqA=, tarball: https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/human-signals/-/human-signals-2.1.0.tgz} + resolution: {integrity: sha1-3JH8ukLk0G5Kuu0zs+ejwC9RTqA=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/human-signals/-/human-signals-2.1.0.tgz} engines: {node: '>=10.17.0'} humanize-ms@1.2.1: @@ -14689,11 +14705,11 @@ packages: engines: {node: '>= 0.4'} is-stream@1.1.0: - resolution: {integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=, tarball: https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-stream/-/is-stream-1.1.0.tgz} + resolution: {integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-stream/-/is-stream-1.1.0.tgz} engines: {node: '>=0.10.0'} is-stream@2.0.1: - resolution: {integrity: sha1-+sHj1TuXrVqdCunO8jifWBClwHc=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-stream/-/is-stream-2.0.1.tgz} + resolution: {integrity: sha1-+sHj1TuXrVqdCunO8jifWBClwHc=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-stream/-/is-stream-2.0.1.tgz} engines: {node: '>=8'} is-string@1.1.1: @@ -14766,7 +14782,7 @@ packages: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=, tarball: https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/isexe/-/isexe-2.0.0.tgz} + resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/isexe/-/isexe-2.0.0.tgz} isobject@2.1.0: resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} @@ -15662,7 +15678,7 @@ packages: hasBin: true mimic-fn@2.1.0: - resolution: {integrity: sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/mimic-fn/-/mimic-fn-2.1.0.tgz} + resolution: {integrity: sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/mimic-fn/-/mimic-fn-2.1.0.tgz} engines: {node: '>=6'} mimic-fn@3.1.0: @@ -15969,7 +15985,7 @@ packages: engines: {node: '>=4'} npm-run-path@4.0.1: - resolution: {integrity: sha1-t+zR5e1T2o43pV4cImnguX7XSOo=, tarball: https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/npm-run-path/-/npm-run-path-4.0.1.tgz} + resolution: {integrity: sha1-t+zR5e1T2o43pV4cImnguX7XSOo=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/npm-run-path/-/npm-run-path-4.0.1.tgz} engines: {node: '>=8'} npmlog@4.1.2: @@ -16080,7 +16096,7 @@ packages: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} onetime@5.1.2: - resolution: {integrity: sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/onetime/-/onetime-5.1.2.tgz} + resolution: {integrity: sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/onetime/-/onetime-5.1.2.tgz} engines: {node: '>=6'} open@10.2.0: @@ -16299,7 +16315,7 @@ packages: engines: {node: '>=0.10.0'} path-key@2.0.1: - resolution: {integrity: sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=, tarball: https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/path-key/-/path-key-2.0.1.tgz} + resolution: {integrity: sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/path-key/-/path-key-2.0.1.tgz} engines: {node: '>=4'} path-key@3.1.1: @@ -17765,10 +17781,10 @@ packages: engines: {node: '>= 0.4'} signal-exit@3.0.7: - resolution: {integrity: sha1-qaF2f4r4QVURTqq9c/mSc8j1mtk=, tarball: https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/signal-exit/-/signal-exit-3.0.7.tgz} + resolution: {integrity: sha1-qaF2f4r4QVURTqq9c/mSc8j1mtk=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/signal-exit/-/signal-exit-3.0.7.tgz} signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + resolution: {integrity: sha1-lSGIwcvVRgcOLdIND0HArgUwywQ=, tarball: https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/signal-exit/-/signal-exit-4.1.0.tgz} engines: {node: '>=14'} simple-concat@1.0.1: @@ -24570,7 +24586,7 @@ snapshots: eslint: 7.11.0 eslint-plugin-promise: 6.1.1(eslint@7.11.0) eslint-plugin-react: 7.33.2(eslint@7.11.0) - eslint-plugin-tsdoc: 0.3.0 + eslint-plugin-tsdoc: 0.3.0(eslint@7.11.0) typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -24588,7 +24604,7 @@ snapshots: eslint: 7.30.0 eslint-plugin-promise: 6.1.1(eslint@7.30.0) eslint-plugin-react: 7.33.2(eslint@7.30.0) - eslint-plugin-tsdoc: 0.3.0 + eslint-plugin-tsdoc: 0.3.0(eslint@7.30.0) typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -24606,7 +24622,7 @@ snapshots: eslint: 7.7.0 eslint-plugin-promise: 6.1.1(eslint@7.7.0) eslint-plugin-react: 7.33.2(eslint@7.7.0) - eslint-plugin-tsdoc: 0.3.0 + eslint-plugin-tsdoc: 0.3.0(eslint@7.7.0) typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -24624,7 +24640,7 @@ snapshots: eslint: 8.57.1 eslint-plugin-promise: 6.1.1(eslint@8.57.1) eslint-plugin-react: 7.33.2(eslint@8.57.1) - eslint-plugin-tsdoc: 0.3.0 + eslint-plugin-tsdoc: 0.3.0(eslint@8.57.1) typescript: 5.8.2 transitivePeerDependencies: - supports-color @@ -24857,7 +24873,7 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@rushstack/heft-jest-plugin@2.0.15(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/jest@30.0.0)(@types/node@20.17.19)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0)(jest-environment-node@30.3.0)': + '@rushstack/heft-jest-plugin@2.0.15(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/jest@30.0.0)(@types/node@20.17.19)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0)(jest-environment-node@30.3.0)': dependencies: '@jest/core': 30.3.0(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.0)) '@jest/reporters': 30.3.0 @@ -24890,13 +24906,13 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@rushstack/heft-node-rig@2.11.48(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0)': + '@rushstack/heft-node-rig@2.11.48(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0)': dependencies: '@microsoft/api-extractor': 7.59.0(@types/node@20.17.19) '@rushstack/eslint-config': 4.6.5(eslint@9.37.0)(typescript@5.8.2) '@rushstack/heft': 1.2.25(@types/node@20.17.19) '@rushstack/heft-api-extractor-plugin': 1.3.25(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19) - '@rushstack/heft-jest-plugin': 2.0.15(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/jest@30.0.0)(@types/node@20.17.19)(babel-plugin-macros@3.1.0)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0)(jest-environment-node@30.3.0) + '@rushstack/heft-jest-plugin': 2.0.15(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/jest@30.0.0)(@types/node@20.17.19)(esbuild-register@3.6.0(esbuild@0.28.0))(jest-environment-jsdom@30.3.0)(jest-environment-node@30.3.0) '@rushstack/heft-lint-plugin': 1.2.25(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19) '@rushstack/heft-typescript-plugin': 1.3.20(@rushstack/heft@1.2.25(@types/node@20.17.19))(@types/node@20.17.19) '@types/jest': 30.0.0 @@ -30799,18 +30815,42 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-tsdoc@0.3.0: + eslint-plugin-tsdoc@0.3.0(eslint@7.11.0): dependencies: '@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc-config': 0.17.0 + optionalDependencies: + eslint: 7.11.0 + + eslint-plugin-tsdoc@0.3.0(eslint@7.30.0): + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + optionalDependencies: + eslint: 7.30.0 + + eslint-plugin-tsdoc@0.3.0(eslint@7.7.0): + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + optionalDependencies: + eslint: 7.7.0 + + eslint-plugin-tsdoc@0.3.0(eslint@8.57.1): + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@microsoft/tsdoc-config': 0.17.0 + optionalDependencies: + eslint: 8.57.1 eslint-plugin-tsdoc@0.5.2(eslint@8.57.1)(typescript@4.9.5): dependencies: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.1 '@typescript-eslint/utils': 8.56.1(eslint@8.57.1)(typescript@4.9.5) + optionalDependencies: + eslint: 8.57.1 transitivePeerDependencies: - - eslint - supports-color - typescript @@ -30819,8 +30859,9 @@ snapshots: '@microsoft/tsdoc': 0.16.0 '@microsoft/tsdoc-config': 0.18.1 '@typescript-eslint/utils': 8.56.1(eslint@9.37.0)(typescript@5.8.2) + optionalDependencies: + eslint: 9.37.0 transitivePeerDependencies: - - eslint - supports-color - typescript diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index 298e1a8a29e..44f44f58b5c 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "f9acc34af9572dd5baa4246a14a8b0482730a5be", + "pnpmShrinkwrapHash": "02dd9ec20215ff4da03645a5df79e9dc3d83b735", "preferredVersionsHash": "029c99bd6e65c5e1f25e2848340509811ff9753c" } diff --git a/eslint/eslint-config/.npmignore b/eslint/eslint-config/.npmignore index 31e20769649..ec65d0fb23c 100644 --- a/eslint/eslint-config/.npmignore +++ b/eslint/eslint-config/.npmignore @@ -35,28 +35,4 @@ # DO NOT MODIFY ABOVE THIS LINE! Add any project-specific overrides below. # --------------------------------------------------------------------------- -!/*.js -!/*.[cm]js -!/*.d.ts -!/*.d.[cm]ts - -!flat/**/*.js -!flat/**/*.[cm]js -!flat/**/*.d.ts -!flat/**/*.d.[cm]ts - -!mixins/*.js -!mixins/*.[cm]js -!mixins/*.d.ts -!mixins/*.d.[cm]ts - -!patch/*.js -!patch/*.[cm]js -!patch/*.d.ts -!patch/*.d.[cm]ts - -!profile/*.js -!profile/*.[cm]js -!profile/*.d.ts -!profile/*.d.[cm]ts diff --git a/eslint/eslint-config/config/rig.json b/eslint/eslint-config/config/rig.json new file mode 100644 index 00000000000..cc98dea43dd --- /dev/null +++ b/eslint/eslint-config/config/rig.json @@ -0,0 +1,7 @@ +{ + // The "rig.json" file directs tools to look for their config files in an external package. + // Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + "rigPackageName": "decoupled-local-node-rig" +} diff --git a/eslint/eslint-config/config/typescript.json b/eslint/eslint-config/config/typescript.json new file mode 100644 index 00000000000..9bcead41dfc --- /dev/null +++ b/eslint/eslint-config/config/typescript.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/typescript.schema.json", + + "extends": "decoupled-local-node-rig/profiles/default/config/typescript.json", + + // These ESLint configuration modules are consumed via CommonJS require(), so only emit CommonJS (which also + // works for ESM consumers via Node.js interop). Overrides the rig's additional ESM module kind. + "additionalModuleKindsToEmit": [] +} diff --git a/eslint/eslint-config/eslint.config.js b/eslint/eslint-config/eslint.config.js new file mode 100644 index 00000000000..f83aea7d1b7 --- /dev/null +++ b/eslint/eslint-config/eslint.config.js @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +const nodeTrustedToolProfile = require('decoupled-local-node-rig/profiles/default/includes/eslint/flat/profile/node-trusted-tool'); +const friendlyLocalsMixin = require('decoupled-local-node-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals'); +const tsdocMixin = require('decoupled-local-node-rig/profiles/default/includes/eslint/flat/mixins/tsdoc'); + +module.exports = [ + ...nodeTrustedToolProfile, + ...friendlyLocalsMixin, + ...tsdocMixin, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname + } + } + } +]; diff --git a/eslint/eslint-config/package.json b/eslint/eslint-config/package.json index 4623afe2aca..4c70cd7e047 100644 --- a/eslint/eslint-config/package.json +++ b/eslint/eslint-config/package.json @@ -12,9 +12,29 @@ "node": ">=20.9.0" }, "homepage": "https://rushstack.io", + "main": "./lib-commonjs/index.js", + "types": "./lib-dts/index.d.ts", + "exports": { + ".": { + "types": "./lib-dts/index.d.ts", + "default": "./lib-commonjs/index.js" + }, + "./*": { + "types": "./lib-dts/*.d.ts", + "default": "./lib-commonjs/*.js" + }, + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "*": [ + "lib-dts/*" + ] + } + }, "scripts": { - "build": "", - "_phase:build": "", + "build": "heft build --clean", + "_phase:build": "heft run --only build -- --clean", "_phase:pack": "rush-pnpm pack" }, "keywords": [ @@ -44,6 +64,8 @@ "eslint-plugin-tsdoc": "~0.5.1" }, "devDependencies": { + "@rushstack/heft": "1.2.25", + "decoupled-local-node-rig": "workspace:*", "eslint": "~9.37.0", "typescript": "~5.8.2" }, diff --git a/eslint/eslint-config/flat/mixins/friendly-locals.js b/eslint/eslint-config/src/flat/mixins/friendly-locals.ts similarity index 80% rename from eslint/eslint-config/flat/mixins/friendly-locals.js rename to eslint/eslint-config/src/flat/mixins/friendly-locals.ts index e63e96783ca..f22e7f9f53f 100644 --- a/eslint/eslint-config/flat/mixins/friendly-locals.js +++ b/eslint/eslint-config/src/flat/mixins/friendly-locals.ts @@ -22,13 +22,20 @@ // // IMPORTANT: Mixins must be included in your ESLint configuration AFTER the profile -const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin'); +import type { ESLint, Linter } from 'eslint'; +import typescriptEslintPlugin from '@typescript-eslint/eslint-plugin'; -module.exports = [ +// The third-party @typescript-eslint plugin does not present itself as an `ESLint.Plugin` (its typescript-eslint +// `RuleModule` types are intentionally not assignable to ESLint's `RuleDefinition`), so widen it through +// `object` to reference it in a flat-config `plugins` map. +const typescriptEslintPluginAsEslintPlugin: ESLint.Plugin = + typescriptEslintPlugin as unknown as ESLint.Plugin; + +const config: Linter.Config[] = [ { files: ['**/*.ts', '**/*.tsx'], plugins: { - '@typescript-eslint': typescriptEslintPlugin + '@typescript-eslint': typescriptEslintPluginAsEslintPlugin }, rules: { '@rushstack/typedef-var': 'off', // <--- disabled by the mixin @@ -69,7 +76,7 @@ module.exports = [ '**/test/**/*.tsx' ], plugins: { - '@typescript-eslint': typescriptEslintPlugin + '@typescript-eslint': typescriptEslintPluginAsEslintPlugin }, rules: { '@typescript-eslint/typedef': [ @@ -88,3 +95,5 @@ module.exports = [ } } ]; + +export = config; diff --git a/eslint/eslint-config/flat/mixins/packlets.js b/eslint/eslint-config/src/flat/mixins/packlets.ts similarity index 79% rename from eslint/eslint-config/flat/mixins/packlets.js rename to eslint/eslint-config/src/flat/mixins/packlets.ts index 0c1f22487dd..57f811d32de 100644 --- a/eslint/eslint-config/flat/mixins/packlets.js +++ b/eslint/eslint-config/src/flat/mixins/packlets.ts @@ -7,9 +7,11 @@ // // IMPORTANT: Mixins must be included in your ESLint configuration AFTER the profile -const rushstackPackletsEslintPlugin = require('@rushstack/eslint-plugin-packlets'); +import type { Linter } from 'eslint'; -module.exports = { +import rushstackPackletsEslintPlugin from '@rushstack/eslint-plugin-packlets'; + +const config: Linter.Config = { files: ['**/*.ts', '**/*.tsx'], plugins: { '@rushstack/packlets': rushstackPackletsEslintPlugin @@ -19,3 +21,5 @@ module.exports = { '@rushstack/packlets/circular-deps': 'warn' } }; + +export = config; diff --git a/eslint/eslint-config/flat/mixins/react.js b/eslint/eslint-config/src/flat/mixins/react.ts similarity index 95% rename from eslint/eslint-config/flat/mixins/react.js rename to eslint/eslint-config/src/flat/mixins/react.ts index 8ee798c1e0b..155896c1790 100644 --- a/eslint/eslint-config/flat/mixins/react.js +++ b/eslint/eslint-config/src/flat/mixins/react.ts @@ -6,9 +6,10 @@ // // IMPORTANT: Mixins must be included in your ESLint configuration AFTER the profile -const reactEslintPlugin = require('eslint-plugin-react'); +import type { Linter } from 'eslint'; +import reactEslintPlugin from 'eslint-plugin-react'; -module.exports = [ +const config: Linter.Config[] = [ { files: ['**/*.ts', '**/*.tsx'], plugins: { @@ -75,3 +76,5 @@ module.exports = [ } } ]; + +export = config; diff --git a/eslint/eslint-config/flat/mixins/tsdoc.js b/eslint/eslint-config/src/flat/mixins/tsdoc.ts similarity index 79% rename from eslint/eslint-config/flat/mixins/tsdoc.js rename to eslint/eslint-config/src/flat/mixins/tsdoc.ts index 2b1009259bf..096fd2e053d 100644 --- a/eslint/eslint-config/flat/mixins/tsdoc.js +++ b/eslint/eslint-config/src/flat/mixins/tsdoc.ts @@ -6,9 +6,10 @@ // // IMPORTANT: Mixins must be included in your ESLint configuration AFTER the profile -const tsdocEslintPlugin = require('eslint-plugin-tsdoc'); +import type { Linter } from 'eslint'; +import tsdocEslintPlugin from 'eslint-plugin-tsdoc'; -module.exports = [ +const config: Linter.Config[] = [ { files: ['**/*.ts', '**/*.tsx'], plugins: { @@ -19,3 +20,5 @@ module.exports = [ } } ]; + +export = config; diff --git a/eslint/eslint-config/flat/patch/eslint-bulk-suppressions.js b/eslint/eslint-config/src/flat/patch/eslint-bulk-suppressions.ts similarity index 71% rename from eslint/eslint-config/flat/patch/eslint-bulk-suppressions.js rename to eslint/eslint-config/src/flat/patch/eslint-bulk-suppressions.ts index 12c37b253da..448a68224e6 100644 --- a/eslint/eslint-config/flat/patch/eslint-bulk-suppressions.js +++ b/eslint/eslint-config/src/flat/patch/eslint-bulk-suppressions.ts @@ -1,4 +1,4 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -require('@rushstack/eslint-patch/eslint-bulk-suppressions'); +import '@rushstack/eslint-patch/eslint-bulk-suppressions'; diff --git a/eslint/eslint-config/flat/profile/_common.js b/eslint/eslint-config/src/flat/profile/_common.ts similarity index 88% rename from eslint/eslint-config/flat/profile/_common.js rename to eslint/eslint-config/src/flat/profile/_common.ts index 4513f97c146..ae4812001b8 100644 --- a/eslint/eslint-config/flat/profile/_common.js +++ b/eslint/eslint-config/src/flat/profile/_common.ts @@ -20,15 +20,29 @@ // - An issue that catches code that is likely to malfunction (e.g. unterminated promise chain) // - An obsolete language feature that nobody should be using for any good reason -const { globalIgnores } = require('eslint/config'); -const promiseEslintPlugin = require('eslint-plugin-promise'); -const typescriptEslintPlugin = require('@typescript-eslint/eslint-plugin'); -const typescriptEslintParser = require('@typescript-eslint/parser'); -const rushstackEslintPlugin = require('@rushstack/eslint-plugin'); -const rushstackSecurityEslintPlugin = require('@rushstack/eslint-plugin-security'); -const { expandNamingConventionSelectors } = require('./_macros'); - -const commonNamingConventionSelectors = [ +import type { ESLint, Linter } from 'eslint'; +import { globalIgnores } from 'eslint/config'; +import promiseEslintPlugin from 'eslint-plugin-promise'; +import typescriptEslintPlugin from '@typescript-eslint/eslint-plugin'; +// `@typescript-eslint/parser` marks its CommonJS export with `__esModule` but exposes the parser API +// (`parseForESLint`) directly on `module.exports` with no `default` export. A default `import` would therefore +// resolve to `undefined` at runtime (ESLint would silently fall back to its built-in parser and fail to parse +// TypeScript), so use a namespace import to bind the module export itself. +import * as typescriptEslintParser from '@typescript-eslint/parser'; + +import rushstackEslintPlugin from '@rushstack/eslint-plugin'; +import rushstackSecurityEslintPlugin from '@rushstack/eslint-plugin-security'; + +import { expandNamingConventionSelectors } from './_macros'; + +interface INamingConventionSelectorMacroBlock { + selectors: string[]; + enforceLeadingUnderscoreWhenPrivate?: boolean; + modifiers?: string[]; + [optionName: string]: unknown; +} + +const commonNamingConventionSelectors: INamingConventionSelectorMacroBlock[] = [ { // We should be stricter about 'enumMember', but it often functions legitimately as an ad hoc namespace. selectors: ['variable', 'enumMember', 'function'], @@ -183,11 +197,50 @@ const commonNamingConventionSelectors = [ } ]; -const commonConfig = [ +// These are the only rules in this profile that require type information (i.e. the TypeScript program). +// They are grouped separately so that TypeScript files which are NOT part of the project's TypeScript program +// (for example config files or tests that are not included by tsconfig.json) can be linted with only the +// non-type-aware rules. See the "without-type-information" helper (flat/without-type-information.js), which +// disables these rules and type-aware parsing for a given set of files. +// +// Use `satisfies` (rather than a type annotation) so that the literal rule names are preserved in the inferred +// type, which lets "./without-type-information" derive `keyof typeof typeAwareRules`. +// eslint-disable-next-line @typescript-eslint/typedef +const typeAwareRules = { + // NOTE: This new rule replaces several deprecated rules from @typescript-eslint/eslint-plugin@2.3.3: + // + // - @typescript-eslint/camelcase + // - @typescript-eslint/class-name-casing + // - @typescript-eslint/interface-name-prefix + // - @typescript-eslint/member-naming + // + // Docs: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md + '@typescript-eslint/naming-convention': [ + 'warn', + ...expandNamingConventionSelectors(commonNamingConventionSelectors) + ], + + // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() + // handler. Thus wherever a Promise arises, the code must either append a catch handler, + // or else return the object to a caller (who assumes this responsibility). Unterminated + // promise chains are a serious issue. Besides causing errors to be silently ignored, + // they can also cause a NodeJS process to terminate unexpectedly. + '@typescript-eslint/no-floating-promises': [ + 'error', + { + checkThenables: true + } + ], + + // RATIONALE: Catches a common coding mistake. + '@typescript-eslint/no-for-in-array': 'error' +} as const satisfies Linter.RulesRecord; + +const commonConfig: Linter.Config[] = [ // Manually authored .d.ts files are generally used to describe external APIs that are not expected // to follow our coding conventions. Linting those files tends to produce a lot of spurious suppressions, // so we simply ignore them. - globalIgnores(['**/*.d.ts']), + globalIgnores(['**/*.d.ts']) as Linter.Config, { files: ['**/*.ts', '**/*.tsx'], @@ -209,7 +262,10 @@ const commonConfig = [ plugins: { '@rushstack': rushstackEslintPlugin, '@rushstack/security': rushstackSecurityEslintPlugin, - '@typescript-eslint': typescriptEslintPlugin, + // Unlike the @rushstack plugins, the third-party @typescript-eslint plugin does not present itself as an + // `ESLint.Plugin` (its typescript-eslint `RuleModule` types are intentionally not assignable to ESLint's + // `RuleDefinition`), so widen it through `object` here. + '@typescript-eslint': typescriptEslintPlugin as object as ESLint.Plugin, promise: promiseEslintPlugin }, rules: { @@ -291,18 +347,9 @@ const commonConfig = [ } ], - // NOTE: This new rule replaces several deprecated rules from @typescript-eslint/eslint-plugin@2.3.3: - // - // - @typescript-eslint/camelcase - // - @typescript-eslint/class-name-casing - // - @typescript-eslint/interface-name-prefix - // - @typescript-eslint/member-naming - // - // Docs: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md - '@typescript-eslint/naming-convention': [ - 'warn', - ...expandNamingConventionSelectors(commonNamingConventionSelectors) - ], + // Type-aware rules (require the TypeScript program) are grouped in typeAwareRules so that files outside + // the TypeScript program can be linted with only the non-type-aware rules. + ...typeAwareRules, // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json '@typescript-eslint/no-array-constructor': 'warn', @@ -315,21 +362,6 @@ const commonConfig = [ // may be more appropriate such as "unknown", "{}", or "Record". '@typescript-eslint/no-explicit-any': 'warn', - // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() - // handler. Thus wherever a Promise arises, the code must either append a catch handler, - // or else return the object to a caller (who assumes this responsibility). Unterminated - // promise chains are a serious issue. Besides causing errors to be silently ignored, - // they can also cause a NodeJS process to terminate unexpectedly. - '@typescript-eslint/no-floating-promises': [ - 'error', - { - checkThenables: true - } - ], - - // RATIONALE: Catches a common coding mistake. - '@typescript-eslint/no-for-in-array': 'error', - // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json '@typescript-eslint/no-misused-new': 'error', @@ -774,4 +806,4 @@ const commonConfig = [ } ]; -module.exports = { commonNamingConventionSelectors, commonConfig }; +export { commonNamingConventionSelectors, commonConfig, typeAwareRules }; diff --git a/eslint/eslint-config/profile/_macros.js b/eslint/eslint-config/src/flat/profile/_macros.ts similarity index 70% rename from eslint/eslint-config/profile/_macros.js rename to eslint/eslint-config/src/flat/profile/_macros.ts index 87c8487b314..bb23daf68a9 100644 --- a/eslint/eslint-config/profile/_macros.js +++ b/eslint/eslint-config/src/flat/profile/_macros.ts @@ -60,31 +60,48 @@ // }, // . . . // ] -function expandNamingConventionSelectors(inputBlocks) { - const firstPassBlocks = []; + +interface INamingConventionSelectorBlockBase { + enforceLeadingUnderscoreWhenPrivate?: boolean; + modifiers?: string[]; + [optionName: string]: unknown; +} + +interface INamingConventionSelectorMacroBlock extends INamingConventionSelectorBlockBase { + selectors: string[]; +} + +export interface INamingConventionSelectorBlock extends INamingConventionSelectorBlockBase { + selector: string; + selectors?: string[]; +} + +function expandNamingConventionSelectors( + inputBlocks: INamingConventionSelectorMacroBlock[] +): INamingConventionSelectorBlock[] { + const firstPassBlocks: INamingConventionSelectorBlock[] = []; // Expand "selectors" --> "selector" for (const block of inputBlocks) { for (const selector of block.selectors) { - const expandedBlock = { ...block }; + const expandedBlock: INamingConventionSelectorBlock = { ...block, selector: selector }; delete expandedBlock.selectors; - expandedBlock.selector = selector; firstPassBlocks.push(expandedBlock); } } // Expand "enforceLeadingUnderscoreWhenPrivate" --> "leadingUnderscore" - const secondPassBlocks = []; + const secondPassBlocks: INamingConventionSelectorBlock[] = []; for (const block of firstPassBlocks) { if (block.enforceLeadingUnderscoreWhenPrivate) { - const expandedBlock1 = { + const expandedBlock1: INamingConventionSelectorBlock = { ...block, leadingUnderscore: 'allow' }; delete expandedBlock1.enforceLeadingUnderscoreWhenPrivate; secondPassBlocks.push(expandedBlock1); - const expandedBlock2 = { + const expandedBlock2: INamingConventionSelectorBlock = { ...block, modifiers: [...(block.modifiers ?? []), 'private'], leadingUnderscore: 'require' @@ -99,4 +116,4 @@ function expandNamingConventionSelectors(inputBlocks) { return secondPassBlocks; } -exports.expandNamingConventionSelectors = expandNamingConventionSelectors; +export { expandNamingConventionSelectors }; diff --git a/eslint/eslint-config/flat/profile/node-trusted-tool.js b/eslint/eslint-config/src/flat/profile/node-trusted-tool.ts similarity index 88% rename from eslint/eslint-config/flat/profile/node-trusted-tool.js rename to eslint/eslint-config/src/flat/profile/node-trusted-tool.ts index a6a05c4b061..bd2ec43a56a 100644 --- a/eslint/eslint-config/flat/profile/node-trusted-tool.js +++ b/eslint/eslint-config/src/flat/profile/node-trusted-tool.ts @@ -12,9 +12,11 @@ // DO NOT use this profile for a library project that might also be loaded by a Node.js service; // use "@rushstack/eslint-config/profiles/node" instead. -const { commonConfig } = require('./_common'); +import type { Linter } from 'eslint'; -module.exports = [ +import { commonConfig } from './_common'; + +const config: Linter.Config[] = [ ...commonConfig, { files: ['**/*.ts', '**/*.tsx'], @@ -24,3 +26,5 @@ module.exports = [ } } ]; + +export = config; diff --git a/eslint/eslint-config/profile/node.js b/eslint/eslint-config/src/flat/profile/node.ts similarity index 74% rename from eslint/eslint-config/profile/node.js rename to eslint/eslint-config/src/flat/profile/node.ts index df3b0dc79fa..2d52b407930 100644 --- a/eslint/eslint-config/profile/node.js +++ b/eslint/eslint-config/src/flat/profile/node.ts @@ -5,7 +5,10 @@ // It enables security rules that assume the service could receive malicious inputs from an // untrusted user. If that is not the case, consider using the "node-trusted-tool" profile instead. -const { buildRules } = require('./_common'); +import type { Linter } from 'eslint'; -const rules = buildRules('node'); -module.exports = rules; +import { commonConfig } from './_common'; + +const config: Linter.Config[] = [...commonConfig]; + +export = config; diff --git a/eslint/eslint-config/flat/profile/web-app.js b/eslint/eslint-config/src/flat/profile/web-app.ts similarity index 73% rename from eslint/eslint-config/flat/profile/web-app.js rename to eslint/eslint-config/src/flat/profile/web-app.ts index 8d254fdd8d9..784e5c4e34a 100644 --- a/eslint/eslint-config/flat/profile/web-app.js +++ b/eslint/eslint-config/src/flat/profile/web-app.ts @@ -7,6 +7,10 @@ // Also use this profile if you are creating a library that can be consumed by both Node.js // and web applications. -const { commonConfig } = require('./_common'); +import type { Linter } from 'eslint'; -module.exports = [...commonConfig]; +import { commonConfig } from './_common'; + +const config: Linter.Config[] = [...commonConfig]; + +export = config; diff --git a/eslint/eslint-config/src/flat/without-type-information.ts b/eslint/eslint-config/src/flat/without-type-information.ts new file mode 100644 index 00000000000..3d0089ac47e --- /dev/null +++ b/eslint/eslint-config/src/flat/without-type-information.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { Linter } from 'eslint'; + +import type { typeAwareRules } from './profile/_common'; + +// The profile's type-aware rules, turned off. Typing this as `Record` keeps +// it in sync with the typeAwareRules group in ./profile/_common: adding a type-aware rule there becomes a compile +// error until it is disabled here as well. +const disabledTypeAwareRules: Record = { + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-for-in-array': 'off' +}; + +// Returns ESLint flat-config objects that lint the specified files WITHOUT type information: type-aware parsing +// is disabled and the profile's type-aware rules are turned off, leaving only the non-type-aware rules in effect. +// +// Use this for TypeScript files that are selected by your ESLint configuration but are NOT part of the project's +// TypeScript program -- for example configuration files or tests that are not included by tsconfig.json. Without +// this, typescript-eslint reports a fatal parsing error because it cannot associate those files with the project, +// and any type-aware rule would be unable to run. +// +// IMPORTANT: These config objects must be included in your ESLint configuration AFTER the profile, so that they +// override the profile's type-aware parser options and rules for the specified files. +// +// Example (eslint.config.js): +// +// const { withoutTypeInformation } = require('@rushstack/eslint-config/flat/without-type-information'); +// +// module.exports = [ +// ...nodeTrustedToolProfile, +// ...withoutTypeInformation({ files: ['tests/**/*.ts', 'playwright.config.ts'] }) +// ]; +interface IWithoutTypeInformationOptions { + files: string[]; +} + +function withoutTypeInformation(options: IWithoutTypeInformationOptions): Linter.Config[] { + const { files } = options; + return [ + { + files, + languageOptions: { + parserOptions: { + // Disable type-aware parsing so that files outside the TypeScript program do not fail to resolve + // against it. + project: false, + projectService: false + } + }, + rules: disabledTypeAwareRules + } + ]; +} + +export { withoutTypeInformation }; diff --git a/eslint/eslint-config/index.js b/eslint/eslint-config/src/index.ts similarity index 97% rename from eslint/eslint-config/index.js rename to eslint/eslint-config/src/index.ts index b8eedd9c41f..b86fb87e387 100644 --- a/eslint/eslint-config/index.js +++ b/eslint/eslint-config/src/index.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +export {}; + throw new Error( 'The index.js entry point has been removed. Please update your ESLint configuration to import one of the' + ' profile paths such as "@rushstack/eslint-config/profile/web-app" or "@rushstack/eslint-config/profile/node.' + diff --git a/eslint/eslint-config/mixins/friendly-locals.js b/eslint/eslint-config/src/mixins/friendly-locals.ts similarity index 97% rename from eslint/eslint-config/mixins/friendly-locals.js rename to eslint/eslint-config/src/mixins/friendly-locals.ts index 35b1dcd686f..672c1fa56ce 100644 --- a/eslint/eslint-config/mixins/friendly-locals.js +++ b/eslint/eslint-config/src/mixins/friendly-locals.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { Linter } from 'eslint'; + // For the first 5 years of Rush, our lint rules required explicit types for most declarations // such as function parameters, function return values, and exported variables. Although more verbose, // declaring types (instead of relying on type inference) encourages engineers to create interfaces @@ -21,7 +23,7 @@ // It will restore the requirement that local variables should have explicit type declarations. // // IMPORTANT: Your .eslintrc.js "extends" field must load mixins AFTER the profile. -module.exports = { +const config: Linter.LegacyConfig = { overrides: [ { files: ['*.ts', '*.tsx'], @@ -83,3 +85,5 @@ module.exports = { } ] }; + +export = config; diff --git a/eslint/eslint-config/mixins/packlets.js b/eslint/eslint-config/src/mixins/packlets.ts similarity index 87% rename from eslint/eslint-config/mixins/packlets.js rename to eslint/eslint-config/src/mixins/packlets.ts index 9c6b791e546..46ac8b3a73f 100644 --- a/eslint/eslint-config/mixins/packlets.js +++ b/eslint/eslint-config/src/mixins/packlets.ts @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { Linter } from 'eslint'; + // This mixin implements the "packlet" formalism for organizing source files. // For more information, see the documentation here: // https://www.npmjs.com/package/@rushstack/eslint-plugin-packlets -module.exports = { +const config: Linter.LegacyConfig = { plugins: ['@rushstack/eslint-plugin-packlets'], overrides: [ @@ -19,3 +21,5 @@ module.exports = { } ] }; + +export = config; diff --git a/eslint/eslint-config/mixins/react.js b/eslint/eslint-config/src/mixins/react.ts similarity index 96% rename from eslint/eslint-config/mixins/react.js rename to eslint/eslint-config/src/mixins/react.ts index 00e06aa0074..d8e615057b4 100644 --- a/eslint/eslint-config/mixins/react.js +++ b/eslint/eslint-config/src/mixins/react.ts @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { Linter } from 'eslint'; + // This mixin applies some additional checks for projects using the React library. For more information, // please see the README.md for "@rushstack/eslint-config". -module.exports = { +const config: Linter.LegacyConfig = { // Plugin documentation: https://www.npmjs.com/package/eslint-plugin-react plugins: ['eslint-plugin-react'], @@ -75,3 +77,5 @@ module.exports = { } ] }; + +export = config; diff --git a/eslint/eslint-config/mixins/tsdoc.js b/eslint/eslint-config/src/mixins/tsdoc.ts similarity index 86% rename from eslint/eslint-config/mixins/tsdoc.js rename to eslint/eslint-config/src/mixins/tsdoc.ts index 5e07fc79e28..07168bbfca3 100644 --- a/eslint/eslint-config/mixins/tsdoc.js +++ b/eslint/eslint-config/src/mixins/tsdoc.ts @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { Linter } from 'eslint'; + // This mixin validates code comments to ensure that they follow the TSDoc standard. For more // information please see the README.md for @rushstack/eslint-config. -module.exports = { +const config: Linter.LegacyConfig = { // The plugin documentation is here: https://www.npmjs.com/package/eslint-plugin-tsdoc plugins: ['eslint-plugin-tsdoc'], @@ -18,3 +20,5 @@ module.exports = { } ] }; + +export = config; diff --git a/eslint/eslint-config/src/module-declarations.d.ts b/eslint/eslint-config/src/module-declarations.d.ts new file mode 100644 index 00000000000..fb7c6a59711 --- /dev/null +++ b/eslint/eslint-config/src/module-declarations.d.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +declare module 'eslint-plugin-promise'; +declare module 'eslint-plugin-react'; diff --git a/eslint/eslint-config/patch-eslint6.js b/eslint/eslint-config/src/patch-eslint6.ts similarity index 97% rename from eslint/eslint-config/patch-eslint6.js rename to eslint/eslint-config/src/patch-eslint6.ts index 336494cb3b9..cc49f1a6fd2 100644 --- a/eslint/eslint-config/patch-eslint6.js +++ b/eslint/eslint-config/src/patch-eslint6.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +export {}; + throw new Error( 'The patch-eslint6.js script has been rewritten to support both ESLint 6.x and 7.x.' + ' Please update your ESLint configuration to use this path instead:\n\n' + diff --git a/eslint/eslint-config/patch/custom-config-package-names.js b/eslint/eslint-config/src/patch/custom-config-package-names.ts similarity index 70% rename from eslint/eslint-config/patch/custom-config-package-names.js rename to eslint/eslint-config/src/patch/custom-config-package-names.ts index 20341195020..858b48be393 100644 --- a/eslint/eslint-config/patch/custom-config-package-names.js +++ b/eslint/eslint-config/src/patch/custom-config-package-names.ts @@ -1,4 +1,4 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -require('@rushstack/eslint-patch/custom-config-package-names'); +import '@rushstack/eslint-patch/custom-config-package-names'; diff --git a/eslint/eslint-config/patch/modern-module-resolution.js b/eslint/eslint-config/src/patch/eslint-bulk-suppressions.ts similarity index 71% rename from eslint/eslint-config/patch/modern-module-resolution.js rename to eslint/eslint-config/src/patch/eslint-bulk-suppressions.ts index d4ba8827123..448a68224e6 100644 --- a/eslint/eslint-config/patch/modern-module-resolution.js +++ b/eslint/eslint-config/src/patch/eslint-bulk-suppressions.ts @@ -1,4 +1,4 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -require('@rushstack/eslint-patch/modern-module-resolution'); +import '@rushstack/eslint-patch/eslint-bulk-suppressions'; diff --git a/eslint/eslint-config/patch/eslint-bulk-suppressions.js b/eslint/eslint-config/src/patch/modern-module-resolution.ts similarity index 71% rename from eslint/eslint-config/patch/eslint-bulk-suppressions.js rename to eslint/eslint-config/src/patch/modern-module-resolution.ts index 12c37b253da..8ccac47ea6b 100644 --- a/eslint/eslint-config/patch/eslint-bulk-suppressions.js +++ b/eslint/eslint-config/src/patch/modern-module-resolution.ts @@ -1,4 +1,4 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -require('@rushstack/eslint-patch/eslint-bulk-suppressions'); +import '@rushstack/eslint-patch/modern-module-resolution'; diff --git a/eslint/eslint-config/profile/_common.js b/eslint/eslint-config/src/profile/_common.ts similarity index 98% rename from eslint/eslint-config/profile/_common.js rename to eslint/eslint-config/src/profile/_common.ts index 584628cd15f..f8ed284de81 100644 --- a/eslint/eslint-config/profile/_common.js +++ b/eslint/eslint-config/src/profile/_common.ts @@ -1,9 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -const macros = require('./_macros'); +import type { Linter } from 'eslint'; -const namingConventionRuleOptions = [ +import { expandNamingConventionSelectors } from './_macros'; + +interface INamingConventionSelectorMacroBlock { + selectors: string[]; + enforceLeadingUnderscoreWhenPrivate?: boolean; + modifiers?: string[]; + [optionName: string]: unknown; +} + +const namingConventionRuleOptions: INamingConventionSelectorMacroBlock[] = [ { // We should be stricter about 'enumMember', but it often functions legitimately as an ad hoc namespace. selectors: ['variable', 'enumMember', 'function'], @@ -177,7 +186,7 @@ const namingConventionRuleOptions = [ // - An issue that catches code that is likely to malfunction (e.g. unterminated promise chain) // - An obsolete language feature that nobody should be using for any good reason -function buildRules(profile) { +function buildRules(profile: 'node' | 'node-trusted-tool' | 'web-app'): Linter.LegacyConfig { return { // After an .eslintrc.js file is loaded, ESLint will normally continue visiting all parent folders // to look for other .eslintrc.js files, and also consult a personal file ~/.eslintrc.js. If any files @@ -318,7 +327,7 @@ function buildRules(profile) { // Docs: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md '@typescript-eslint/naming-convention': [ 'warn', - ...macros.expandNamingConventionSelectors(namingConventionRuleOptions) + ...expandNamingConventionSelectors(namingConventionRuleOptions) ], // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json @@ -800,5 +809,4 @@ function buildRules(profile) { }; } -exports.buildRules = buildRules; -exports.namingConventionRuleOptions = namingConventionRuleOptions; +export { buildRules, namingConventionRuleOptions }; diff --git a/eslint/eslint-config/flat/profile/_macros.js b/eslint/eslint-config/src/profile/_macros.ts similarity index 71% rename from eslint/eslint-config/flat/profile/_macros.js rename to eslint/eslint-config/src/profile/_macros.ts index 4d95857abc1..6579dd48b4b 100644 --- a/eslint/eslint-config/flat/profile/_macros.js +++ b/eslint/eslint-config/src/profile/_macros.ts @@ -60,31 +60,47 @@ // }, // . . . // ] -function expandNamingConventionSelectors(inputBlocks) { - const firstPassBlocks = []; +interface INamingConventionSelectorMacroBlock { + selectors: string[]; + enforceLeadingUnderscoreWhenPrivate?: boolean; + modifiers?: string[]; + [optionName: string]: unknown; +} + +interface INamingConventionSelectorBlock { + selector: string; + selectors?: string[]; + enforceLeadingUnderscoreWhenPrivate?: boolean; + modifiers?: string[]; + [optionName: string]: unknown; +} + +function expandNamingConventionSelectors( + inputBlocks: INamingConventionSelectorMacroBlock[] +): INamingConventionSelectorBlock[] { + const firstPassBlocks: INamingConventionSelectorBlock[] = []; // Expand "selectors" --> "selector" for (const block of inputBlocks) { for (const selector of block.selectors) { - const expandedBlock = { ...block }; + const expandedBlock: INamingConventionSelectorBlock = { ...block, selector: selector }; delete expandedBlock.selectors; - expandedBlock.selector = selector; firstPassBlocks.push(expandedBlock); } } // Expand "enforceLeadingUnderscoreWhenPrivate" --> "leadingUnderscore" - const secondPassBlocks = []; + const secondPassBlocks: INamingConventionSelectorBlock[] = []; for (const block of firstPassBlocks) { if (block.enforceLeadingUnderscoreWhenPrivate) { - const expandedBlock1 = { + const expandedBlock1: INamingConventionSelectorBlock = { ...block, leadingUnderscore: 'allow' }; delete expandedBlock1.enforceLeadingUnderscoreWhenPrivate; secondPassBlocks.push(expandedBlock1); - const expandedBlock2 = { + const expandedBlock2: INamingConventionSelectorBlock = { ...block, modifiers: [...(block.modifiers ?? []), 'private'], leadingUnderscore: 'require' @@ -99,6 +115,4 @@ function expandNamingConventionSelectors(inputBlocks) { return secondPassBlocks; } -module.exports = { - expandNamingConventionSelectors: expandNamingConventionSelectors -}; +export { expandNamingConventionSelectors }; diff --git a/eslint/eslint-config/profile/node-trusted-tool.js b/eslint/eslint-config/src/profile/node-trusted-tool.ts similarity index 83% rename from eslint/eslint-config/profile/node-trusted-tool.js rename to eslint/eslint-config/src/profile/node-trusted-tool.ts index f185f532b70..f6b7d4ca271 100644 --- a/eslint/eslint-config/profile/node-trusted-tool.js +++ b/eslint/eslint-config/src/profile/node-trusted-tool.ts @@ -12,7 +12,9 @@ // DO NOT use this profile for a library project that might also be loaded by a Node.js service; // use "@rushstack/eslint-config/profiles/node" instead. -const { buildRules } = require('./_common'); +import type { Linter } from 'eslint'; -const rules = buildRules('node-trusted-tool'); -module.exports = rules; +import { buildRules } from './_common'; + +const rules: Linter.LegacyConfig = buildRules('node-trusted-tool'); +export = rules; diff --git a/eslint/eslint-config/flat/profile/node.js b/eslint/eslint-config/src/profile/node.ts similarity index 74% rename from eslint/eslint-config/flat/profile/node.js rename to eslint/eslint-config/src/profile/node.ts index e18325a793a..7fd6eb072e5 100644 --- a/eslint/eslint-config/flat/profile/node.js +++ b/eslint/eslint-config/src/profile/node.ts @@ -5,6 +5,9 @@ // It enables security rules that assume the service could receive malicious inputs from an // untrusted user. If that is not the case, consider using the "node-trusted-tool" profile instead. -const { commonConfig } = require('./_common'); +import type { Linter } from 'eslint'; -module.exports = [...commonConfig]; +import { buildRules } from './_common'; + +const rules: Linter.LegacyConfig = buildRules('node'); +export = rules; diff --git a/eslint/eslint-config/profile/web-app.js b/eslint/eslint-config/src/profile/web-app.ts similarity index 73% rename from eslint/eslint-config/profile/web-app.js rename to eslint/eslint-config/src/profile/web-app.ts index 916b888ec6e..a0d35c13edd 100644 --- a/eslint/eslint-config/profile/web-app.js +++ b/eslint/eslint-config/src/profile/web-app.ts @@ -7,7 +7,9 @@ // Also use this profile if you are creating a library that can be consumed by both Node.js // and web applications. -const { buildRules } = require('./_common'); +import type { Linter } from 'eslint'; -const rules = buildRules('web-app'); -module.exports = rules; +import { buildRules } from './_common'; + +const rules: Linter.LegacyConfig = buildRules('web-app'); +export = rules; diff --git a/eslint/eslint-config/react.js b/eslint/eslint-config/src/react.ts similarity index 97% rename from eslint/eslint-config/react.js rename to eslint/eslint-config/src/react.ts index 14516d2ff32..f4b4e005289 100644 --- a/eslint/eslint-config/react.js +++ b/eslint/eslint-config/src/react.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +export {}; + throw new Error( 'The react.js entry point has moved. Please update your ESLint configuration to reference' + ' "@rushstack/eslint-config/mixins/react" instead.' + diff --git a/eslint/eslint-config/tsconfig.json b/eslint/eslint-config/tsconfig.json new file mode 100644 index 00000000000..d10a9d761a1 --- /dev/null +++ b/eslint/eslint-config/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "./node_modules/decoupled-local-node-rig/profiles/default/tsconfig-base.json", + + "compilerOptions": { + // The rig's base config uses "module": "commonjs", which implies the legacy "node" module resolution. + // That resolution mode ignores the "exports" maps in dependencies' package.json files, so imports that are + // only reachable through an "exports" subpath (for example `eslint/config`, which provides `globalIgnores`, + // and various `@typescript-eslint/*` entry points) fail to resolve. "nodenext" switches to Node16 module + // resolution, which honors "exports" maps. It still emits CommonJS here because this package's package.json + // has no "type": "module", so the require()-compatible output is preserved. + "module": "nodenext" + } +} diff --git a/eslint/eslint-plugin-packlets/src/index.ts b/eslint/eslint-plugin-packlets/src/index.ts index 7958fa842df..a3ee2d1bdb6 100644 --- a/eslint/eslint-plugin-packlets/src/index.ts +++ b/eslint/eslint-plugin-packlets/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { ESLint } from 'eslint'; import type { TSESLint } from '@typescript-eslint/utils'; import { mechanics } from './mechanics'; @@ -32,4 +33,8 @@ const plugin: IPlugin = { } }; -export = plugin; +// The rule modules are authored with typescript-eslint's types, whose `RuleModule` is intentionally not +// structurally assignable to ESLint's `RuleDefinition`. Widen through `object` (rather than `unknown`) to +// present the plugin as an `ESLint.Plugin` so that consumers (such as flat-config `plugins` maps) can +// reference it without a cast of their own. +export = plugin as object as ESLint.Plugin; diff --git a/eslint/eslint-plugin-security/src/index.ts b/eslint/eslint-plugin-security/src/index.ts index 8e2d433001c..8a2d41f2d46 100644 --- a/eslint/eslint-plugin-security/src/index.ts +++ b/eslint/eslint-plugin-security/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { ESLint } from 'eslint'; import type { TSESLint } from '@typescript-eslint/utils'; import { noUnsafeRegExp } from './no-unsafe-regexp'; @@ -16,4 +17,8 @@ const plugin: IPlugin = { } }; -export = plugin; +// The rule modules are authored with typescript-eslint's types, whose `RuleModule` is intentionally not +// structurally assignable to ESLint's `RuleDefinition`. Widen through `object` (rather than `unknown`) to +// present the plugin as an `ESLint.Plugin` so that consumers (such as flat-config `plugins` maps) can +// reference it without a cast of their own. +export = plugin as object as ESLint.Plugin; diff --git a/eslint/eslint-plugin/src/index.ts b/eslint/eslint-plugin/src/index.ts index 61f0c64f23e..7787b59a5d0 100644 --- a/eslint/eslint-plugin/src/index.ts +++ b/eslint/eslint-plugin/src/index.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { ESLint } from 'eslint'; import type { TSESLint } from '@typescript-eslint/utils'; import { hoistJestMock } from './hoist-jest-mock'; @@ -56,4 +57,8 @@ const plugin: IPlugin = { } }; -export = plugin; +// The rule modules are authored with typescript-eslint's types, whose `RuleModule` is intentionally not +// structurally assignable to ESLint's `RuleDefinition`. Widen through `object` (rather than `unknown`) to +// present the plugin as an `ESLint.Plugin` so that consumers (such as flat-config `plugins` maps) can +// reference it without a cast of their own. +export = plugin as unknown as ESLint.Plugin; diff --git a/rush.json b/rush.json index 985396fe793..5f6bc73d8fd 100644 --- a/rush.json +++ b/rush.json @@ -812,7 +812,8 @@ "packageName": "@rushstack/eslint-config", "projectFolder": "eslint/eslint-config", "reviewCategory": "libraries", - "shouldPublish": true + "shouldPublish": true, + "decoupledLocalDependencies": ["@rushstack/heft"] }, { "packageName": "local-eslint-config",