From d5d70946a15a8f51b98ada16a0c18da35745a80e Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 1 Oct 2026 21:01:58 -0700 Subject: [PATCH 1/5] feat(eslint-config)!: upgrade typescript-eslint to v8 typescript-eslint v7 does not support TypeScript 6 or later, so it blocks the TypeScript upgrade. Rules that v8 renamed or split are disabled to keep the rule set this configuration already had: `no-require-imports` replaces `no-var-requires`, and `ban-types` is split into `no-empty-object-type`, `no-unsafe-function-type` and `no-wrapper-object-types`. `prefer-nullish-coalescing` now ignores primitive operands. The rule runs without `strictNullChecks`, so it cannot tell whether a `string`, `number` or `boolean` operand may be nullish, and v8 reports `||` on such operands, where switching to `??` changes the result for `''`, `0` and `false`. BREAKING CHANGE: `@loopback/eslint-config` requires `@typescript-eslint/parser` and `@typescript-eslint/eslint-plugin` v8. Projects using it can get new findings, such as unused `catch` bindings (`no-unused-vars`), `prefer-optional-chain`, and `prefer-nullish-coalescing` for `if (x == null) x = y`. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Raymond Feng --- package-lock.json | 287 ++++++++++++++++------------ package.json | 4 +- packages/eslint-config/eslintrc.js | 13 +- packages/eslint-config/package.json | 4 +- 4 files changed, 182 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcb6b1fa4702..768850683bad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,8 +24,8 @@ "@npmcli/map-workspaces": "^6.0.0", "@npmcli/package-json": "^8.0.0", "@types/mocha": "^10.0.10", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "cross-env": "^10.1.0", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.57.1", @@ -3454,23 +3454,28 @@ "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -9929,197 +9934,236 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.0.tgz", + "integrity": "sha512-pqcS9c1HxZTHt7End4nXqd0s5lJrrFzrgCkKFJrsbUnaL6M3+6oBFZaslg6Gjsl3argl2DDRFROnXARaZ2e4Nw==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/type-utils": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.71.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.11.tgz", + "integrity": "sha512-YChdK5txDjwGUvgR7oCJcLGkwi3LDh8Zx8tS7ndYciLEg/oOCL26VUBSUEtgS7EjiybcBqFxh5j0rAdDX7/bbg==", + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.0.tgz", + "integrity": "sha512-CG4nPk1f2zc8yw4pALqHsFYH2hdo+h1T9daSp21+Hnxi9LOE3GT9hAfTKJCBXVNM2GmYs1eMEP615wPoeOgk3A==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "debug": "^4.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.0.tgz", + "integrity": "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@typescript-eslint/tsconfig-utils": "^8.71.0", + "@typescript-eslint/types": "^8.71.0", + "debug": "^4.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.0.tgz", + "integrity": "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==", + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz", + "integrity": "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==", + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.0.tgz", + "integrity": "sha512-i8uO1qbdxeKgRnS5sCRt6On3/nfo2d2DwQe3Yvjx543zLy7r8ySqRuPPiIIXAhS03U0v5NfAFx+rUgxFzKKwNw==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0", + "@typescript-eslint/utils": "8.71.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", + "node_modules/@typescript-eslint/types": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.0.tgz", + "integrity": "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.0.tgz", + "integrity": "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.2" + "@typescript-eslint/project-service": "8.71.0", + "@typescript-eslint/tsconfig-utils": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/visitor-keys": "8.71.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.0.tgz", + "integrity": "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.0", + "@typescript-eslint/types": "8.71.0", + "@typescript-eslint/typescript-estree": "8.71.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "version": "8.71.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.0.tgz", + "integrity": "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.71.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -35300,14 +35344,15 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-graphviz": { @@ -40527,8 +40572,8 @@ "version": "16.0.1", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "eslint-config-prettier": "^9.1.1", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-mocha": "^10.5.0" diff --git a/package.json b/package.json index 2231d7536514..39f30978975a 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@npmcli/map-workspaces": "^6.0.0", "@npmcli/package-json": "^8.0.0", "@types/mocha": "^10.0.10", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "cross-env": "^10.1.0", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.57.1", diff --git a/packages/eslint-config/eslintrc.js b/packages/eslint-config/eslintrc.js index 6a9385df6153..2cb1bad308fe 100644 --- a/packages/eslint-config/eslintrc.js +++ b/packages/eslint-config/eslintrc.js @@ -69,6 +69,8 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-member-accessibility': 'off', '@typescript-eslint/no-var-requires': 'off', + // `no-require-imports` replaces `no-var-requires` in typescript-eslint v8 + '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-object-literal-type-assertion': 'off', '@typescript-eslint/no-parameter-properties': 'off', @@ -76,6 +78,10 @@ module.exports = { '@typescript-eslint/prefer-interface': 'off', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/ban-types': 'off', + // `ban-types` was split into the following rules in typescript-eslint v8 + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/no-wrapper-object-types': 'off', '@typescript-eslint/no-triple-slash-reference': 'off', '@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', @@ -133,7 +139,12 @@ module.exports = { '@typescript-eslint/prefer-optional-chain': 'error', '@typescript-eslint/prefer-nullish-coalescing': [ 'error', - {allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: true}, // See https://github.com/typescript-eslint/typescript-eslint/pull/6174 + { + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: true, // See https://github.com/typescript-eslint/typescript-eslint/pull/6174 + // Without `strictNullChecks`, the rule cannot tell whether a primitive + // may be nullish, and `??` differs from `||` for `''`, `0` and `false` + ignorePrimitives: {string: true, number: true, boolean: true}, + }, ], '@typescript-eslint/no-extra-non-null-assertion': 'error', diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 9ddee37a0f6a..1319f5af0470 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -21,8 +21,8 @@ "eslint": "^8.57.1" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "eslint-config-prettier": "^9.1.1", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-mocha": "^10.5.0" From d6a43053b37e4903b13e13d6f36170ee716b7af5 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 1 Oct 2026 21:02:03 -0700 Subject: [PATCH 2/5] feat(cli): generate projects with typescript-eslint v8 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Raymond Feng --- packages/cli/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 15f0584ab8d2..46450f72de8c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -152,8 +152,8 @@ "eslint-plugin-mocha": "^10.5.0", "@loopback/example-greeting-app": "^8.0.16", "@loopback/example-context": "^8.0.15", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-config-prettier": "^9.1.1", "@loopback/repository-tests": "^0.25.16", From 9699b9a6fe063f779ef77de0e54fd648dd26cbef Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 1 Oct 2026 21:02:04 -0700 Subject: [PATCH 3/5] refactor: resolve typescript-eslint v8 findings - Drop unused `catch` bindings (`no-unused-vars`). - Use optional chains where they are equivalent (`prefer-optional-chain`). - Use `??=` for `if (x == null) x = y` and for `!x` guards on values that are objects or arrays, where it is equivalent (`prefer-nullish-coalescing`). - Remove the `route()` overload that `RestApplication` declared twice (`unified-signatures`). - Remove `eslint-disable` directives that no longer suppress anything. `||` is kept wherever the left operand may be `''` or `0`. The passport-login example keeps its explicit `!user.credentials` guard, since `user.credentials?.password !== password` accepts a user without credentials when `password` is `undefined`. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Raymond Feng --- .../repository-cloudant/docker.setup.js | 2 +- benchmark/src/rest-routing/routing-table.ts | 2 +- .../src/custom-configuration-resolver.ts | 2 +- .../src/services/file-upload.service.ts | 6 ++--- .../src/decorators/log.decorator.ts | 2 +- .../src/authentication-strategies/basic.ts | 3 +++ .../src/authentication-strategies/local.ts | 3 +++ .../src/services/refreshtoken.service.ts | 2 +- .../sequelize/sequelize.repository.base.ts | 11 +++----- extensions/socketio/src/socketio.server.ts | 15 ++++------- .../authorization.options.acceptance.ts | 4 +-- packages/build/bin/compile-package.js | 2 +- packages/build/bin/utils.js | 2 +- packages/cli/lib/utils.js | 2 +- .../__tests__/unit/try-catch-finally.unit.ts | 3 --- packages/context/src/context-view.ts | 4 +-- packages/context/src/resolution-session.ts | 4 +-- packages/filter/src/query.ts | 10 +++---- packages/metadata/src/decorator-factory.ts | 4 +-- .../src/openapi-spec-builder.ts | 26 +++++++++---------- packages/openapi-v3/src/controller-spec.ts | 22 +++++++--------- .../src/decorators/request-body.decorator.ts | 6 ++--- .../src/build-schema.ts | 2 +- .../caching-interceptor.ts | 2 +- packages/rest/src/rest.application.ts | 18 ------------- packages/rest/src/rest.server.ts | 19 +++++--------- packages/rest/src/router/controller-route.ts | 4 +-- packages/rest/src/router/router-spec.ts | 6 ++--- packages/rest/src/router/routing-table.ts | 4 +-- packages/testlab/src/http-server-config.ts | 8 +++--- 30 files changed, 77 insertions(+), 123 deletions(-) diff --git a/acceptance/repository-cloudant/docker.setup.js b/acceptance/repository-cloudant/docker.setup.js index 9fb1453a331a..c985c0149d2f 100644 --- a/acceptance/repository-cloudant/docker.setup.js +++ b/acceptance/repository-cloudant/docker.setup.js @@ -43,7 +43,7 @@ async function createDB() { // retry if socket hangs up too early try { dbRequest(opts); - } catch (error) { + } catch { dbRequest(opts); } return new Promise(() => { diff --git a/benchmark/src/rest-routing/routing-table.ts b/benchmark/src/rest-routing/routing-table.ts index d5f46b3edf41..631b5da0177a 100644 --- a/benchmark/src/rest-routing/routing-table.ts +++ b/benchmark/src/rest-routing/routing-table.ts @@ -99,7 +99,7 @@ function givenRouter(router: RestRouter, spec: OpenApiSpec, count: number) { try { table.find(request); found++; - } catch (e) { + } catch { missed++; } } diff --git a/examples/context/src/custom-configuration-resolver.ts b/examples/context/src/custom-configuration-resolver.ts index 4d7d536f122c..19f15f124ccd 100644 --- a/examples/context/src/custom-configuration-resolver.ts +++ b/examples/context/src/custom-configuration-resolver.ts @@ -47,7 +47,7 @@ class EnvConfigResolver ); try { return JSON.parse(val); - } catch (err) { + } catch { return val; } } diff --git a/examples/file-transfer/src/services/file-upload.service.ts b/examples/file-transfer/src/services/file-upload.service.ts index 897a5c3d3586..facedb8171df 100644 --- a/examples/file-transfer/src/services/file-upload.service.ts +++ b/examples/file-transfer/src/services/file-upload.service.ts @@ -23,10 +23,8 @@ import {FileUploadHandler} from '../types'; }) export class FileUploadProvider implements Provider { constructor(@config() private options: multer.Options = {}) { - if (!this.options.storage) { - // Default to in-memory storage - this.options.storage = multer.memoryStorage(); - } + // Default to in-memory storage + this.options.storage ??= multer.memoryStorage(); } value(): FileUploadHandler { diff --git a/examples/log-extension/src/decorators/log.decorator.ts b/examples/log-extension/src/decorators/log.decorator.ts index 615f4f92900e..aeb29f37bcc5 100644 --- a/examples/log-extension/src/decorators/log.decorator.ts +++ b/examples/log-extension/src/decorators/log.decorator.ts @@ -19,7 +19,7 @@ import {LevelMetadata} from '../types'; * @param level - The Log Level at or above it should log */ export function log(level?: number) { - if (level === undefined) level = LOG_LEVEL.WARN; + level ??= LOG_LEVEL.WARN; return MethodDecoratorFactory.createDecorator( EXAMPLE_LOG_METADATA_KEY, { diff --git a/examples/passport-login/src/authentication-strategies/basic.ts b/examples/passport-login/src/authentication-strategies/basic.ts index bf4fd64efbae..05a1e7dc4446 100644 --- a/examples/passport-login/src/authentication-strategies/basic.ts +++ b/examples/passport-login/src/authentication-strategies/basic.ts @@ -91,6 +91,9 @@ export class BasicStrategy implements AuthenticationStrategy { return done(null, false); } const user = users[0]; + // Keep the explicit guard, `credentials?.password !== password` would + // accept a user without credentials when `password` is `undefined` + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (!user.credentials || user.credentials.password !== password) { return done(null, false); } diff --git a/examples/passport-login/src/authentication-strategies/local.ts b/examples/passport-login/src/authentication-strategies/local.ts index 8b907e72ca7d..b75c07bf9002 100644 --- a/examples/passport-login/src/authentication-strategies/local.ts +++ b/examples/passport-login/src/authentication-strategies/local.ts @@ -87,6 +87,9 @@ export class LocalAuthStrategy implements AuthenticationStrategy { return done(null, null, {message: AUTH_FAILED_MESSAGE}); } const user = users[0]; + // Keep the explicit guard, `credentials?.password !== password` would + // accept a user without credentials when `password` is `undefined` + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (!user.credentials || user.credentials.password !== password) { return done(null, null, {message: AUTH_FAILED_MESSAGE}); } diff --git a/extensions/authentication-jwt/src/services/refreshtoken.service.ts b/extensions/authentication-jwt/src/services/refreshtoken.service.ts index 9f65887db7ce..5ab5ef1643c0 100644 --- a/extensions/authentication-jwt/src/services/refreshtoken.service.ts +++ b/extensions/authentication-jwt/src/services/refreshtoken.service.ts @@ -95,7 +95,7 @@ export class RefreshtokenService { await this.refreshTokenRepository.delete( new RefreshToken({refreshToken: refreshToken}), ); - } catch (e) { + } catch { // ignore } } diff --git a/extensions/sequelize/src/sequelize/sequelize.repository.base.ts b/extensions/sequelize/src/sequelize/sequelize.repository.base.ts index 839672ed6b64..277d52bc5d54 100644 --- a/extensions/sequelize/src/sequelize/sequelize.repository.base.ts +++ b/extensions/sequelize/src/sequelize/sequelize.repository.base.ts @@ -550,9 +550,7 @@ export class SequelizeCrudRepository< return []; } - if (!sourceModel) { - sourceModel = this.sequelizeModel; - } + sourceModel ??= this.sequelizeModel; if (sourceModel === this.sequelizeModel) { const invalidInclusions = inclusionFilters.filter( @@ -663,8 +661,7 @@ export class SequelizeCrudRepository< } const entityClassCol = this.entityClass.definition.properties[columnName]; - const isBooleanColumn = - entityClassCol && entityClassCol.type === 'boolean'; + const isBooleanColumn = entityClassCol?.type === 'boolean'; if (isTruelyObject(conditionValue)) { sequelizeWhere[columnName] = {}; @@ -934,7 +931,7 @@ export class SequelizeCrudRepository< const stringTypeArray = definition[propName].itemType === String || ['String', 'string'].includes( - definition[propName].itemType?.toString() || '', + definition[propName].itemType?.toString() ?? '', ); dataType = stringTypeArray ? DataTypes.ARRAY(DataTypes.STRING) @@ -1008,7 +1005,7 @@ export class SequelizeCrudRepository< if (typeof value === 'string') { try { return JSON.parse(value); - } catch (_error) { + } catch { return null; } } diff --git a/extensions/socketio/src/socketio.server.ts b/extensions/socketio/src/socketio.server.ts index db2e4398b788..75f73ad2ddb2 100644 --- a/extensions/socketio/src/socketio.server.ts +++ b/extensions/socketio/src/socketio.server.ts @@ -144,9 +144,7 @@ export class SocketIoServer extends Context { if (meta instanceof RegExp || typeof meta === 'string') { meta = {namespace: meta} as SocketIoMetadata; } - if (meta == null) { - meta = getSocketIoMetadata(controllerClass) as SocketIoMetadata; - } + meta ??= getSocketIoMetadata(controllerClass) as SocketIoMetadata; const nsp = meta?.namespace ? this.io.of(meta.namespace) : this.io; if (meta?.name) { this.app.bind(getNamespaceKeyForName(meta.name)).to(nsp); @@ -280,14 +278,11 @@ function resolveHttpServerConfig( ); // Can't check falsiness, 0 is a valid port. - if (result.port == null) { - result.port = 3000; - } + result.port ??= 3000; - if (result.host == null) { - // Set it to '' so that the http server will listen on all interfaces - result.host = undefined; - } + // Normalize a `null` host to `undefined` so that the http server listens on + // all interfaces + result.host ??= undefined; return result; } diff --git a/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts b/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts index 62c3a8abf0d2..5a9d2078877b 100644 --- a/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts +++ b/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts @@ -163,7 +163,7 @@ describe('Authorization', () => { try { await invokeMethod(controller, 'cancelOrder', reqCtx, ['order-01']); finalDecision = Allow; - } catch (err) { + } catch { finalDecision = Deny; } return finalDecision; @@ -174,7 +174,7 @@ describe('Authorization', () => { try { await invokeMethod(controller, 'placeOrder', reqCtx, ['prod-01', 10]); finalDecision = Allow; - } catch (err) { + } catch { finalDecision = Deny; } return finalDecision; diff --git a/packages/build/bin/compile-package.js b/packages/build/bin/compile-package.js index 808d47227d85..e387e77643ab 100755 --- a/packages/build/bin/compile-package.js +++ b/packages/build/bin/compile-package.js @@ -53,7 +53,7 @@ function run(argv, options) { try { require.resolve('ttypescript'); TSC_CLI = 'ttypescript/lib/tsc'; - } catch (e) { + } catch { if (isUseTtscSet) { console.error( 'Error using the --use-ttypescript option - ttypescript is not installed', diff --git a/packages/build/bin/utils.js b/packages/build/bin/utils.js index 135d7bef7f6c..2daeb243a573 100644 --- a/packages/build/bin/utils.js +++ b/packages/build/bin/utils.js @@ -70,7 +70,7 @@ function resolveCLI(cli, options = {resolveFromProjectFirst: true}) { const pkgDir = getPackageDir(); const resolved = resolveCLIFromProject(cli, pkgDir); if (resolved != null) return resolved; - } catch (e) { + } catch { // Ignore errors } return require.resolve(cli); diff --git a/packages/cli/lib/utils.js b/packages/cli/lib/utils.js index a4a3ee26b4a9..e2ecc645509b 100644 --- a/packages/cli/lib/utils.js +++ b/packages/cli/lib/utils.js @@ -454,7 +454,7 @@ exports.validateStringObject = function (type) { if (type === 'array' && !Array.isArray(result)) { return err; } - } catch (e) { + } catch { return err; } diff --git a/packages/context/src/__tests__/unit/try-catch-finally.unit.ts b/packages/context/src/__tests__/unit/try-catch-finally.unit.ts index 697d04162252..3aded70d7ae5 100644 --- a/packages/context/src/__tests__/unit/try-catch-finally.unit.ts +++ b/packages/context/src/__tests__/unit/try-catch-finally.unit.ts @@ -19,7 +19,6 @@ describe('tryWithFinally', () => { let finalActionInvoked = false; const action = () => 1; const finalAction = () => (finalActionInvoked = true); - // eslint-disable-next-line @typescript-eslint/no-floating-promises tryWithFinally(action, finalAction); expect(finalActionInvoked).to.be.true(); }); @@ -56,7 +55,6 @@ describe('tryCatchFinally', () => { let finalActionInvoked = false; const action = () => 1; const finalAction = () => (finalActionInvoked = true); - // eslint-disable-next-line @typescript-eslint/no-floating-promises tryCatchFinally(action, undefined, finalAction); expect(finalActionInvoked).to.be.true(); }); @@ -79,7 +77,6 @@ describe('tryCatchFinally', () => { errorActionInvoked = true; throw err; }; - // eslint-disable-next-line @typescript-eslint/no-floating-promises tryCatchFinally(action, errorAction); expect(errorActionInvoked).to.be.false(); }); diff --git a/packages/context/src/context-view.ts b/packages/context/src/context-view.ts index 22be275203dd..1f013cc29945 100644 --- a/packages/context/src/context-view.ts +++ b/packages/context/src/context-view.ts @@ -127,9 +127,7 @@ export class ContextView */ get bindings(): Readonly>[] { debug('Reading bindings'); - if (this._cachedBindings == null) { - this._cachedBindings = this.findBindings(); - } + this._cachedBindings ??= this.findBindings(); return this._cachedBindings; } diff --git a/packages/context/src/resolution-session.ts b/packages/context/src/resolution-session.ts index 0ed8af400b22..a264840d05d3 100644 --- a/packages/context/src/resolution-session.ts +++ b/packages/context/src/resolution-session.ts @@ -55,7 +55,7 @@ export type ResolutionElement = BindingElement | InjectionElement; function isBinding( element: ResolutionElement | undefined, ): element is BindingElement { - return element != null && element.type === 'binding'; + return element?.type === 'binding'; } /** @@ -65,7 +65,7 @@ function isBinding( function isInjection( element: ResolutionElement | undefined, ): element is InjectionElement { - return element != null && element.type === 'injection'; + return element?.type === 'injection'; } /** diff --git a/packages/filter/src/query.ts b/packages/filter/src/query.ts index bdd4afe5c972..5d036bb888e5 100644 --- a/packages/filter/src/query.ts +++ b/packages/filter/src/query.ts @@ -610,9 +610,7 @@ export class FilterBuilder { * or an Order object for the field/direction pairs */ order(...o: (string | string[] | Order)[]): this { - if (!this.filter.order) { - this.filter.order = []; - } + this.filter.order ??= []; o.forEach(order => { if (typeof order === 'string') { this.validateOrder(order); @@ -646,9 +644,7 @@ export class FilterBuilder { * object for the relation/scope definitions */ include(...i: (string | string[] | Inclusion)[]): this { - if (this.filter.include == null) { - this.filter.include = []; - } + this.filter.include ??= []; for (const include of i) { if (typeof include === 'string') { this.filter.include.push({relation: include}); @@ -745,7 +741,7 @@ export function filterTemplate(strings: TemplateStringsArray, ...keys: any[]) { const result = tokens.join(''); try { return JSON.parse(result); - } catch (e) { + } catch { throw new Error('Invalid JSON: ' + result); } }; diff --git a/packages/metadata/src/decorator-factory.ts b/packages/metadata/src/decorator-factory.ts index 0ba155e43d1e..e641d4a55d9d 100644 --- a/packages/metadata/src/decorator-factory.ts +++ b/packages/metadata/src/decorator-factory.ts @@ -579,7 +579,7 @@ export class ParameterDecoratorFactory extends DecoratorFactory< target: Object, methodName?: string, ) { - const method = methodName ? methodName : ''; + const method = methodName ?? ''; let methodMeta = meta[method]; if (methodMeta == null) { // Initialize the method metadata @@ -700,7 +700,7 @@ export class MethodParameterDecoratorFactory extends DecoratorFactory< methodName, ); // Default to the last parameter - if (index == null) index = numOfParams - 1; + index ??= numOfParams - 1; if (index < 0) { // Excessive decorations than the number of parameters detected const method = DecoratorFactory.getTargetName( diff --git a/packages/openapi-spec-builder/src/openapi-spec-builder.ts b/packages/openapi-spec-builder/src/openapi-spec-builder.ts index cb798873a3b9..6a6b752fc502 100644 --- a/packages/openapi-spec-builder/src/openapi-spec-builder.ts +++ b/packages/openapi-spec-builder/src/openapi-spec-builder.ts @@ -114,7 +114,7 @@ export class OpenApiSpecBuilder extends BuilderBase { spec: OperationObject | OperationSpecBuilder, ): this { if (spec instanceof OperationSpecBuilder) spec = spec.build(); - if (!this._spec.paths[path]) this._spec.paths[path] = {}; + this._spec.paths[path] ??= {}; this._spec.paths[path][verb] = spec; return this; } @@ -145,7 +145,7 @@ export class OpenApiSpecBuilder extends BuilderBase { */ withComponents(spec: ComponentsObject | ComponentsSpecBuilder): this { if (spec instanceof ComponentsSpecBuilder) spec = spec.build(); - if (!this._spec.components) this._spec.components = spec; + this._spec.components ??= spec; return this; } } @@ -190,7 +190,7 @@ export class OperationSpecBuilder extends BuilderBase { * @param parameterSpecs */ withParameter(...parameterSpecs: ParameterObject[]): this { - if (!this._spec.parameters) this._spec.parameters = []; + this._spec.parameters ??= []; this._spec.parameters.push(...parameterSpecs); return this; } @@ -251,7 +251,7 @@ export class OperationSpecBuilder extends BuilderBase { * @param tags */ withTags(tags: string | string[]): this { - if (!this._spec.tags) this._spec.tags = []; + this._spec.tags ??= []; if (typeof tags === 'string') tags = [tags]; this._spec.tags.push(...tags); return this; @@ -274,7 +274,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withSchema(name: string, schema: SchemaObject | ReferenceObject): this { - if (!this._spec.schemas) this._spec.schemas = {}; + this._spec.schemas ??= {}; this._spec.schemas[name] = schema; return this; } @@ -287,7 +287,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withResponse(name: string, response: ResponseObject | ReferenceObject): this { - if (!this._spec.responses) this._spec.responses = {}; + this._spec.responses ??= {}; this._spec.responses[name] = response; return this; } @@ -303,7 +303,7 @@ export class ComponentsSpecBuilder extends BuilderBase { name: string, parameter: ParameterObject | ReferenceObject, ): this { - if (!this._spec.parameters) this._spec.parameters = {}; + this._spec.parameters ??= {}; this._spec.parameters[name] = parameter; return this; } @@ -316,7 +316,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withExample(name: string, example: ExampleObject | ReferenceObject): this { - if (!this._spec.examples) this._spec.examples = {}; + this._spec.examples ??= {}; this._spec.examples[name] = example; return this; } @@ -332,7 +332,7 @@ export class ComponentsSpecBuilder extends BuilderBase { name: string, requestBody: RequestBodyObject | ReferenceObject, ): this { - if (!this._spec.requestBodies) this._spec.requestBodies = {}; + this._spec.requestBodies ??= {}; this._spec.requestBodies[name] = requestBody; return this; } @@ -345,7 +345,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withHeader(name: string, header: HeaderObject | ReferenceObject): this { - if (!this._spec.headers) this._spec.headers = {}; + this._spec.headers ??= {}; this._spec.headers[name] = header; return this; } @@ -361,7 +361,7 @@ export class ComponentsSpecBuilder extends BuilderBase { name: string, securityScheme: SecuritySchemeObject | ReferenceObject, ): this { - if (!this._spec.securitySchemes) this._spec.securitySchemes = {}; + this._spec.securitySchemes ??= {}; this._spec.securitySchemes[name] = securityScheme; return this; } @@ -374,7 +374,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withLink(name: string, link: LinkObject | ReferenceObject): this { - if (!this._spec.links) this._spec.links = {}; + this._spec.links ??= {}; this._spec.links[name] = link; return this; } @@ -387,7 +387,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withCallback(name: string, callback: CallbackObject | ReferenceObject): this { - if (!this._spec.callbacks) this._spec.callbacks = {}; + this._spec.callbacks ??= {}; this._spec.callbacks[name] = callback; return this; } diff --git a/packages/openapi-v3/src/controller-spec.ts b/packages/openapi-v3/src/controller-spec.ts index 424c8be32056..eef70a714c1a 100644 --- a/packages/openapi-v3/src/controller-spec.ts +++ b/packages/openapi-v3/src/controller-spec.ts @@ -357,19 +357,15 @@ function resolveControllerSpec(constructor: Function): ControllerSpec { operationSpec['x-controller-name'] = operationSpec['x-controller-name'] || constructor.name; - if (operationSpec.operationId == null) { - // Build the operationId as `.` - // Please note API explorer (https://github.com/swagger-api/swagger-js/) - // will normalize it as `_` - operationSpec.operationId = - operationSpec['x-controller-name'] + - '.' + - operationSpec['x-operation-name']; - } - - if (!spec.paths[path]) { - spec.paths[path] = {}; - } + // Build the operationId as `.` + // Please note API explorer (https://github.com/swagger-api/swagger-js/) + // will normalize it as `_` + operationSpec.operationId ??= + operationSpec['x-controller-name'] + + '.' + + operationSpec['x-operation-name']; + + spec.paths[path] ??= {}; if (spec.paths[path][verb]) { // Operations from subclasses override those from the base diff --git a/packages/openapi-v3/src/decorators/request-body.decorator.ts b/packages/openapi-v3/src/decorators/request-body.decorator.ts index 8a4345df6872..76972391b636 100644 --- a/packages/openapi-v3/src/decorators/request-body.decorator.ts +++ b/packages/openapi-v3/src/decorators/request-body.decorator.ts @@ -93,7 +93,7 @@ export function requestBody(requestBodySpec?: Partial) { // Get the design time method parameter metadata const methodSig = MetadataInspector.getDesignTypeForMethod(target, member); - const paramTypes = methodSig?.parameterTypes || []; + const paramTypes = methodSig?.parameterTypes ?? []; const paramType = paramTypes[index]; const schema = resolveSchema(paramType); @@ -101,9 +101,7 @@ export function requestBody(requestBodySpec?: Partial) { if (debug.enabled) debug(' inferred schema: %s', inspect(schema, {depth: null})); requestBodySpec.content = _.mapValues(requestBodySpec.content, c => { - if (!c.schema) { - c.schema = schema; - } + c.schema ??= schema; return c; }); diff --git a/packages/repository-json-schema/src/build-schema.ts b/packages/repository-json-schema/src/build-schema.ts index 74f143369ea2..468ac642c810 100644 --- a/packages/repository-json-schema/src/build-schema.ts +++ b/packages/repository-json-schema/src/build-schema.ts @@ -489,7 +489,7 @@ export function modelToJsonSchema( indexInfo = {[p]: index}; } if (indexInfo && Object.keys(indexInfo).length) { - if (result.description === undefined) result.description = ''; + result.description ??= ''; if (result.description.includes('indexInfo')) { const indexInfoMatched = result.description.match(/\{"indexInfo".*$/s); if (indexInfoMatched) { diff --git a/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts b/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts index 8b83ae646d11..b4100718522d 100644 --- a/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts +++ b/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts @@ -70,7 +70,7 @@ export async function cache( const req = await invocationCtx.get(RestBindings.Http.REQUEST, { optional: true, }); - if (!req || req.method.toLowerCase() !== 'get') { + if (req?.method.toLowerCase() !== 'get') { // The method is not invoked by an http request, no caching return next(); } diff --git a/packages/rest/src/rest.application.ts b/packages/rest/src/rest.application.ts index 7041addd9c1e..970d203f3e13 100644 --- a/packages/rest/src/rest.application.ts +++ b/packages/rest/src/rest.application.ts @@ -285,24 +285,6 @@ export class RestApplication extends Application implements HttpServerLike { */ route(route: RouteEntry): Binding; - /** - * Register a new route. - * - * @example - * ```ts - * function greet(name: string) { - * return `hello ${name}`; - * } - * app.route('get', '/', operationSpec, greet); - * ``` - */ - route( - verb: string, - path: string, - spec: OperationObject, - handler: Function, - ): Binding; - route( routeOrVerb: RouteEntry | string, path?: string, diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 0d398e2fdaa5..8d8d876000d1 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -1227,20 +1227,15 @@ function resolveRestServerConfig( ); // Can't check falsiness, 0 is a valid port. - if (result.port == null) { - result.port = 3000; - } + result.port ??= 3000; - if (result.host == null) { - // Set it to '' so that the http server will listen on all interfaces - result.host = undefined; - } + // Normalize a `null` host to `undefined` so that the http server listens on + // all interfaces + result.host ??= undefined; - if (!result.openApiSpec.endpointMapping) { - // mapping may be mutated by addOpenApiSpecEndpoint, be sure that doesn't - // pollute the default mapping configuration - result.openApiSpec.endpointMapping = cloneDeep(OPENAPI_SPEC_MAPPING); - } + // mapping may be mutated by addOpenApiSpecEndpoint, be sure that doesn't + // pollute the default mapping configuration + result.openApiSpec.endpointMapping ??= cloneDeep(OPENAPI_SPEC_MAPPING); result.apiExplorer = normalizeApiExplorerConfig(config.apiExplorer); diff --git a/packages/rest/src/router/controller-route.ts b/packages/rest/src/router/controller-route.ts index 33e204f9fffd..f76e64eff9fc 100644 --- a/packages/rest/src/router/controller-route.ts +++ b/packages/rest/src/router/controller-route.ts @@ -170,9 +170,7 @@ export function createControllerFactoryForClass( let inst = await ctx.get(`controllers.${controllerCtor.name}`, { optional: true, }); - if (inst === undefined) { - inst = await instantiateClass(controllerCtor, ctx); - } + inst ??= await instantiateClass(controllerCtor, ctx); return inst; }; } diff --git a/packages/rest/src/router/router-spec.ts b/packages/rest/src/router/router-spec.ts index 1de5b781ca73..3621d01371af 100644 --- a/packages/rest/src/router/router-spec.ts +++ b/packages/rest/src/router/router-spec.ts @@ -9,9 +9,9 @@ export type RouterSpec = Pick; export function assignRouterSpec(target: RouterSpec, additions: RouterSpec) { if (additions.components) { - if (!target.components) target.components = {}; + target.components ??= {}; for (const key in additions.components) { - if (!target.components[key]) target.components[key] = {}; + target.components[key] ??= {}; Object.assign(target.components[key], additions.components[key]); } } @@ -26,7 +26,7 @@ export function assignRouterSpec(target: RouterSpec, additions: RouterSpec) { } if (additions.tags && additions.tags.length > 0) { - if (!target.tags) target.tags = []; + target.tags ??= []; for (const tag of additions.tags) { // tags defined earlier take precedence if (target.tags.some(t => t.name === tag.name)) continue; diff --git a/packages/rest/src/router/routing-table.ts b/packages/rest/src/router/routing-table.ts index bc0677559a9e..3dc2313eab54 100644 --- a/packages/rest/src/router/routing-table.ts +++ b/packages/rest/src/router/routing-table.ts @@ -83,9 +83,7 @@ export class RoutingTable { for (const route of this._router.list()) { if (route.spec['x-visibility'] === 'undocumented') continue; - if (!paths[route.path]) { - paths[route.path] = {}; - } + paths[route.path] ??= {}; paths[route.path][route.verb] = route.spec; } diff --git a/packages/testlab/src/http-server-config.ts b/packages/testlab/src/http-server-config.ts index 45f5f02c456a..793be72d718a 100644 --- a/packages/testlab/src/http-server-config.ts +++ b/packages/testlab/src/http-server-config.ts @@ -56,8 +56,8 @@ export function givenHttpServerConfig( if (isHttpsConfig(customConfig)) { const config: T = {...customConfig}; - if (config.host == null) config.host = defaults.host; - if (config.port == null) config.port = defaults.port; + config.host ??= defaults.host; + config.port ??= defaults.port; setupTlsConfig(config as HttpsOptions); assertHostPort(config); return config; @@ -65,8 +65,8 @@ export function givenHttpServerConfig( assertHttpConfig(customConfig); const config: T = {...customConfig}; - if (config.host == null) config.host = defaults.host; - if (config.port == null) config.port = defaults.port; + config.host ??= defaults.host; + config.port ??= defaults.port; assertHostPort(config); return config; } From 1d5387eab20b24dfb7c69ce919704405e2e5d502 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 1 Oct 2026 22:15:23 -0700 Subject: [PATCH 4/5] fix(example-todo): update geopoint data in tests The US Census geocoder now returns slightly different coordinates for the test address, so `GeoLookupService` and `TodoApplication` tests fail on every platform, on master as well. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Raymond Feng --- examples/todo/src/__tests__/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/todo/src/__tests__/helpers.ts b/examples/todo/src/__tests__/helpers.ts index a9a76ccc9f53..9359e0256b61 100644 --- a/examples/todo/src/__tests__/helpers.ts +++ b/examples/todo/src/__tests__/helpers.ts @@ -48,7 +48,7 @@ export function givenTodo(todo?: Partial) { export const aLocation = { address: '1 New Orchard Road, Armonk, 10504', - geopoint: {y: 41.109728357749, x: -73.72462031805}, + geopoint: {y: 41.109725723771, x: -73.724620709372}, get geostring() { return `${this.geopoint.y},${this.geopoint.x}`; }, From e33ff49e6ca098c599456d5baf2c009b3c21813f Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 1 Oct 2026 22:15:24 -0700 Subject: [PATCH 5/5] fix(example-webpack): allow more time to launch the browser in tests The hook that launches puppeteer and loads the page intermittently exceeds 15 seconds on the ubuntu-latest runners. It now gets 30 seconds, the same as the hook that generates the bundle. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Raymond Feng --- .../src/__tests__/integration/bundle-web.integration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/webpack/src/__tests__/integration/bundle-web.integration.ts b/examples/webpack/src/__tests__/integration/bundle-web.integration.ts index 7e1a1be70d02..08d33222fe98 100644 --- a/examples/webpack/src/__tests__/integration/bundle-web.integration.ts +++ b/examples/webpack/src/__tests__/integration/bundle-web.integration.ts @@ -39,7 +39,8 @@ skipIf<[(this: Suite) => void], void>( let browser: Browser; let html: string; before(async function (this: Mocha.Context) { - this.timeout(15000); + // Launching the browser can be slow on CI + this.timeout(30000); browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'],