From 74961c662420ea02a5872bfa1a0ee3284526b887 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 30 Oct 2026 14:46:23 -0700 Subject: [PATCH 1/6] build: update @angular/ng-dev to latest Updates @angular/ng-dev to include the fix for release pnpm install (angular/dev-infra@5708c4df03). --- .github/actions/deploy-docs-site/main.js | 8 +- package.json | 2 +- pnpm-lock.yaml | 360 ++++++++++------------- 3 files changed, 167 insertions(+), 203 deletions(-) diff --git a/.github/actions/deploy-docs-site/main.js b/.github/actions/deploy-docs-site/main.js index b0fabb880bfc..2909fce62c6f 100644 --- a/.github/actions/deploy-docs-site/main.js +++ b/.github/actions/deploy-docs-site/main.js @@ -64327,7 +64327,7 @@ function createPrompt(view) { } }); }; - if ("readableFlowing" in input) { + if ("readableFlowing" in input && input.isTTY) { const immediate = nativeSetImmediate(startCycle); cleanups.add(() => nativeClearImmediate(immediate)); } else { @@ -64619,7 +64619,7 @@ var ExternalEditor = class { constructor(text = "", fileOptions = {}) { this.text = text; this.fileOptions = fileOptions; - this.editor = parseEditorCommand(process.env["VISUAL"] ?? process.env["EDITOR"] ?? (process.platform.startsWith("win") ? "notepad" : "vim")); + this.editor = parseEditorCommand(process.env["VISUAL"] || process.env["EDITOR"] || (process.platform.startsWith("win") ? "notepad" : "vim")); } run() { this.createTempFile(); @@ -65319,7 +65319,7 @@ var dist_default10 = createPrompt((config, done) => { } else if (!config.mask) { formattedValue += cursorHide; } - const content = [prefix, message, formattedValue].filter(Boolean).join(" "); + const content = [prefix, message, formattedValue].join(" "); const bottomContent = [ errorMsg ? theme.style.error(errorMsg) : "", toggleMask && status === "idle" ? theme.style.keysHelpTip([["ctrl+t", "toggle visibility"]]) : "" @@ -65904,7 +65904,7 @@ content-type/dist/index.js: *) *) -@angular/ng-dev/bundles/chunk-E5VB5JIZ.mjs: +@angular/ng-dev/bundles/chunk-RJ2YUADA.mjs: (*! Bundled license information: content-type/dist/index.js: diff --git a/package.json b/package.json index 096265f9c1f3..214098edcf5e 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "devDependencies": { "@actions/core": "^3.0.0", "@actions/github": "^9.0.0", - "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#ca1613fdbd92afa1b4f786786f16bc59436b3707", + "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#a53423acd0cec521dcd8edd376b7b6b485ace6cd", "@bazel/bazelisk": "^1.7.5", "@bazel/buildifier": "^8.0.0", "@bazel/ibazel": "0.28.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bab5721e5c4..ef1b91fd2fcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -406,8 +406,8 @@ importers: specifier: ^9.0.0 version: 9.1.1 '@angular/ng-dev': - specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#ca1613fdbd92afa1b4f786786f16bc59436b3707 - version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/ca1613fdbd92afa1b4f786786f16bc59436b3707(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0)) + specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#a53423acd0cec521dcd8edd376b7b6b485ace6cd + version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/a53423acd0cec521dcd8edd376b7b6b485ace6cd(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0)) '@bazel/bazelisk': specifier: ^1.7.5 version: 1.28.1 @@ -446,7 +446,7 @@ importers: version: 16.2.1(bufferutil@4.1.0) firebase-tools: specifier: ^15.0.0 - version: 15.34.1(@types/node@20.19.43)(bufferutil@4.1.0)(encoding@0.1.13)(supports-color@11.0.0) + version: 15.34.1(@types/node@20.19.43)(bufferutil@4.1.0)(encoding@0.1.14)(supports-color@11.0.0) get-tsconfig: specifier: ^4.10.1 version: 4.14.3 @@ -1872,9 +1872,9 @@ packages: '@angular/platform-browser': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/ca1613fdbd92afa1b4f786786f16bc59436b3707': - resolution: {gitHosted: true, integrity: sha512-RnpJEUCVWc1jb4LuRO8OojP5+K2k/XTeDo7cbpwyacBZIQixf9vd00y6g4Ey71JUR/YCvMd7/nw9omeIniOfkQ==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/ca1613fdbd92afa1b4f786786f16bc59436b3707} - version: 0.0.0-a651944a7ee1d9496e7724cf1a094f26de564766 + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/a53423acd0cec521dcd8edd376b7b6b485ace6cd': + resolution: {gitHosted: true, integrity: sha512-S9R+TLW26DFNQX/ntfOFTn3Httw5Jr/ubro5vAFz3eaWq2SNqzYn3EdsKvrNgJ9u+HGIFxLoK1e9xhkwAXJ/4A==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/a53423acd0cec521dcd8edd376b7b6b485ace6cd} + version: 0.0.0-5708c4df037871bab3ce15d27e020dbdbe9fe0fc hasBin: true '@angular/ssr@22.3.0-next.4': @@ -2617,19 +2617,19 @@ packages: peerDependencies: '@firebase/app': 0.x - '@firebase/app-compat@0.5.19': - resolution: {integrity: sha512-pdT+THcv3h43LcawVJ+MUTzofL7umBaCvCgdbr01fnTXrbQU2RSHb0Po/YsF7w+tgjCycg84zCgZbd51NmoIaA==} + '@firebase/app-compat@0.5.20': + resolution: {integrity: sha512-LHB8/Vuviximj7SxERlr58AhbKM7DVnFFg0DCOjETJREUDx84wOjdPkPPcpsG2/KlXUN6m+EKtKwIr2/gD/7lw==} engines: {node: '>=24.12.0'} '@firebase/app-types@0.9.6': resolution: {integrity: sha512-yPLahy7Esfu2w/yme3msVK4xTkDXQqq6szfQn8yVOQpCKiT5GVFjqNpLbuz6NkX0WuTwUidkmPewW3r4xkvpeg==} - '@firebase/app@0.16.3': - resolution: {integrity: sha512-L2mYqVV2ilnek+W4JG7yasA97TsXCwx+f7/abweX5uSp3dkPa5Luiglt3s9YWfh1fBR3SWH/AkRgAwUpDGyCjg==} + '@firebase/app@0.16.4': + resolution: {integrity: sha512-eW9MgxKQFNif18lOGor/dqVm4tb5lySwxkid1nbU6ymS6hSVh7r/MREf9zmnGhHQ6ZGC4KJye6PCT+C//9R+hQ==} engines: {node: '>=24.12.0'} - '@firebase/auth-compat@0.6.12': - resolution: {integrity: sha512-Vspo2zx1RR6LB7zuM7TduD1clMLT/x3Wr97u2jf9s2JrhYArJAFKeEu8bRWF1DoX54z6W9N77SdPRbPAiLKPpg==} + '@firebase/auth-compat@0.6.13': + resolution: {integrity: sha512-LqX9fIm3ToeSVbjoGn0jOy2j0M8J8IV20qEVksaqnAejERxgKXeUWHG/xUDa58gqC/2lClZav/9MYLj1VQiZ8w==} engines: {node: '>=24.12.0'} peerDependencies: '@firebase/app': 0.x @@ -2644,8 +2644,8 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/auth@1.13.7': - resolution: {integrity: sha512-00wn8cMrh1ZyEPF6RO5G081Xq1b57FN0QxChiAda0hy/D3Q5TrMk30qhWszRWCgXPRipw6yM6qtqUew3u3qEjA==} + '@firebase/auth@1.13.8': + resolution: {integrity: sha512-JPW7S7Hwp5qhn8VwEEHWfI1P2kqCqpKbJVTWlKT5UWl1NNbVjrYOGKTZHCVqUeVhLs7BjMWWkCnkoa8tzUjSjQ==} engines: {node: '>=24.12.0'} peerDependencies: '@firebase/app': 0.x @@ -2682,8 +2682,8 @@ packages: resolution: {integrity: sha512-/JGpvszLoNXNgzilRXocigGfFF4hbcPA9wN1i1kjJx6oKkXgkHteZYl3lQs1lJX3ETDf6bv7zI15lPMVUp4sAQ==} engines: {node: '>=20.0.0'} - '@firebase/firestore-compat@0.4.15': - resolution: {integrity: sha512-/hWibRTNFsWkZZi1dbDS1hiJIMezYUMPrRjMmBXevel5b368XWY+NvfOaSxgy3+fDb6I+uZ7SKGFyL06gW3vTA==} + '@firebase/firestore-compat@0.4.16': + resolution: {integrity: sha512-ffI0xU10be9ykgzU4OsCH5uarMfB1gR3HuNYRiQgNAs8NGcPObh/YMeuQne4zbA9gZakppGwQz3smyra8ZFnDA==} engines: {node: '>=24.12.0'} peerDependencies: '@firebase/app': 0.x @@ -2695,8 +2695,8 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/firestore@4.18.0': - resolution: {integrity: sha512-28aKMUeM+L4fuxx5hurvQK31KLh/KRutaP1544avVWuR4mNTAC0FQoHg8gbkN5NoJj3yN9/ZFGSFSVPR8/x/BQ==} + '@firebase/firestore@4.19.0': + resolution: {integrity: sha512-PH+lpkZldrNd1XLypP/xq0SS/CCKuCVykvWdbFpR8euwJnysE7vMlYMhtRckizrwCsshUyEPP5cel7McHFfJ6w==} engines: {node: '>=24.12.0'} peerDependencies: '@firebase/app': 0.x @@ -2849,8 +2849,8 @@ packages: resolution: {integrity: sha512-OKAA98q2M25rQcqcFvbQniix4zTFcbeSyauj+PcPd+6ngn7eXvAUqpuCRVxbIGmA8r5OtE6Ru/oCJ1xwcUR2vA==} engines: {node: '>=18'} - '@google/genai@2.30.0': - resolution: {integrity: sha512-Gkv1ix8iZey/V3WF4lHaRS8C71P533jenDT/lA+fdPTCfXYfmCVHvo+0H5jTgzonqsmROQG7QpzLJkpoP/x7bg==} + '@google/genai@2.31.0': + resolution: {integrity: sha512-u1fw0qEiuWwNcjZixJFRfQF0awQnHb/rBBQUeiYGF+nmTXQCvcgpR2UMhKU9KNdnZdYhahY5jwS6nJ2V2tGtDA==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -3094,15 +3094,6 @@ packages: '@types/node': optional: true - '@inquirer/prompts@8.7.4': - resolution: {integrity: sha512-SAJt+QhY5rOJAIgZBw9TrHWfgrp6wsb9cqRCyqax+Hl6EiFfRXmD9EsIWkST8yq/dQ8H46Is2Sr/JG7Ls/LQ+Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@inquirer/prompts@8.7.5': resolution: {integrity: sha512-l/IWwC+QhOyXgxkZloOh+n5quGzc1m5ZawyzBmoizCZaxbJeLRdS3pStoqg/JVInJRXaG5aHMhjK6H6oYiBC9A==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} @@ -3175,15 +3166,6 @@ packages: '@types/node': optional: true - '@inquirer/type@4.2.0': - resolution: {integrity: sha512-2rnHX8RPo4glJYqnFc/v3nQeIhDcjyRRyaT7MGcfL/8xv/8K03SXYoA2tX06WVSKywWOE/ncbG3dtuzLjR5TCQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@inquirer/type@4.2.1': resolution: {integrity: sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} @@ -6658,8 +6640,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + encoding@0.1.14: + resolution: {integrity: sha512-pqH+jIxIhwX5U4w+Tnu3jjowTo5QTACbqef2C5Cw2RhApr7COQqgnai53u8VbtqjWRcSoAcTf7uZ62dO5kC8Iw==} + engines: {node: '>=20.0.0'} end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -6992,8 +6975,8 @@ packages: engines: {node: '>=20.0.0 || >=22.0.0 || >=24.0.0'} hasBin: true - firebase@13.0.0: - resolution: {integrity: sha512-4cChZajVtuOijT/t7Vw1GZRTuohCxw+aoc8DAwhF+CSbS+zhqHE+i5PzEVttIySw7oKerBDRZRyDt4FynrWecg==} + firebase@13.1.0: + resolution: {integrity: sha512-II/6P63vYzWdS6gTM9+OaMZD6XaCHJdI/9OVa8Z+bEs18t3CR2JlSwCI12KYMramNqTkywJocjSpGePaVPng+A==} flagged-respawn@2.0.0: resolution: {integrity: sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==} @@ -11758,14 +11741,14 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/ca1613fdbd92afa1b4f786786f16bc59436b3707(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0))': + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/a53423acd0cec521dcd8edd376b7b6b485ace6cd(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0))': dependencies: '@actions/core': 3.0.1 '@conventional-changelog/git-client': 3.2.0(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.3) '@google-cloud/spanner': 8.0.0(supports-color@11.0.0) - '@google/genai': 2.30.0(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0))(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6) - '@inquirer/prompts': 8.7.4(@types/node@24.19.2) - '@inquirer/type': 4.2.0(@types/node@24.19.2) + '@google/genai': 2.31.0(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0))(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6) + '@inquirer/prompts': 8.7.5(@types/node@24.19.2) + '@inquirer/type': 4.2.1(@types/node@24.19.2) '@octokit/auth-app': 8.3.1 '@octokit/core': 7.0.8 '@octokit/graphql': 9.0.5 @@ -11790,9 +11773,9 @@ snapshots: conventional-commits-filter: 6.0.1 conventional-commits-parser: 7.1.3 ejs: 7.0.2 - encoding: 0.1.13 + encoding: 0.1.14 fast-glob: 3.3.3 - firebase: 13.0.0 + firebase: 13.1.0 folder-hash: 4.1.4(supports-color@11.0.0) jasmine: 6.3.0 jasmine-core: 6.3.0 @@ -12581,9 +12564,9 @@ snapshots: '@externs/nodejs@1.5.0': {} - '@firebase/ai@3.0.0(@firebase/app-types@0.9.6)(@firebase/app@0.16.3)': + '@firebase/ai@3.0.0(@firebase/app-types@0.9.6)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/app-check-interop-types': 0.3.5 '@firebase/app-types': 0.9.6 '@firebase/component': 0.7.5 @@ -12591,33 +12574,33 @@ snapshots: '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/analytics-compat@0.2.31(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/analytics-compat@0.2.31(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: - '@firebase/analytics': 0.10.25(@firebase/app@0.16.3) + '@firebase/analytics': 0.10.25(@firebase/app@0.16.4) '@firebase/analytics-types': 0.8.5 - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 '@firebase/util': 1.15.3 tslib: 2.8.1 '@firebase/analytics-types@0.8.5': {} - '@firebase/analytics@0.10.25(@firebase/app@0.16.3)': + '@firebase/analytics@0.10.25(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 - '@firebase/installations': 0.6.24(@firebase/app@0.16.3) + '@firebase/installations': 0.6.24(@firebase/app@0.16.4) '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/app-check-compat@0.4.7(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/app-check-compat@0.4.7(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-check': 0.13.1(@firebase/app@0.16.3) + '@firebase/app': 0.16.4 + '@firebase/app-check': 0.13.1(@firebase/app@0.16.4) '@firebase/app-check-types': 0.5.5 - '@firebase/app-compat': 0.5.19 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 @@ -12627,17 +12610,17 @@ snapshots: '@firebase/app-check-types@0.5.5': {} - '@firebase/app-check@0.13.1(@firebase/app@0.16.3)': + '@firebase/app-check@0.13.1(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/app-compat@0.5.19': + '@firebase/app-compat@0.5.20': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 @@ -12647,7 +12630,7 @@ snapshots: dependencies: '@firebase/logger': 0.5.2 - '@firebase/app@0.16.3': + '@firebase/app@0.16.4': dependencies: '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 @@ -12655,11 +12638,11 @@ snapshots: idb: 7.1.1 tslib: 2.8.1 - '@firebase/auth-compat@0.6.12(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3)': + '@firebase/auth-compat@0.6.13(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 - '@firebase/auth': 1.13.7(@firebase/app@0.16.3) + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 + '@firebase/auth': 1.13.8(@firebase/app@0.16.4) '@firebase/auth-types': 0.13.2(@firebase/app-types@0.9.6)(@firebase/util@1.15.3) '@firebase/component': 0.7.5 '@firebase/util': 1.15.3 @@ -12675,9 +12658,9 @@ snapshots: '@firebase/app-types': 0.9.6 '@firebase/util': 1.15.3 - '@firebase/auth@1.13.7(@firebase/app@0.16.3)': + '@firebase/auth@1.13.8(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 @@ -12688,16 +12671,16 @@ snapshots: '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/data-connect@0.7.4(@firebase/app@0.16.3)': + '@firebase/data-connect@0.7.4(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/auth-interop-types': 0.2.6 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/database-compat@2.1.7(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/database-compat@2.1.7(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: '@firebase/component': 0.7.5 '@firebase/database': 1.1.5 @@ -12706,8 +12689,8 @@ snapshots: '@firebase/util': 1.15.3 tslib: 2.8.1 optionalDependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/database-types@1.0.22': dependencies: @@ -12724,12 +12707,12 @@ snapshots: faye-websocket: 0.11.4 tslib: 2.8.1 - '@firebase/firestore-compat@0.4.15(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3)': + '@firebase/firestore-compat@0.4.16(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 - '@firebase/firestore': 4.18.0(@firebase/app@0.16.3) + '@firebase/firestore': 4.19.0(@firebase/app@0.16.4) '@firebase/firestore-types': 3.0.5(@firebase/app-types@0.9.6)(@firebase/util@1.15.3) '@firebase/util': 1.15.3 tslib: 2.8.1 @@ -12741,9 +12724,9 @@ snapshots: '@firebase/app-types': 0.9.6 '@firebase/util': 1.15.3 - '@firebase/firestore@4.18.0(@firebase/app@0.16.3)': + '@firebase/firestore@4.19.0(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 @@ -12753,21 +12736,21 @@ snapshots: re2js: 2.9.0 tslib: 2.8.1 - '@firebase/functions-compat@0.5.0(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/functions-compat@0.5.0(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 - '@firebase/functions': 0.14.0(@firebase/app@0.16.3) + '@firebase/functions': 0.14.0(@firebase/app@0.16.4) '@firebase/functions-types': 0.6.5 '@firebase/util': 1.15.3 tslib: 2.8.1 '@firebase/functions-types@0.6.5': {} - '@firebase/functions@0.14.0(@firebase/app@0.16.3)': + '@firebase/functions@0.14.0(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/app-check-interop-types': 0.3.5 '@firebase/auth-interop-types': 0.2.6 '@firebase/component': 0.7.5 @@ -12775,12 +12758,12 @@ snapshots: '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/installations-compat@0.2.24(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3)': + '@firebase/installations-compat@0.2.24(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 - '@firebase/installations': 0.6.24(@firebase/app@0.16.3) + '@firebase/installations': 0.6.24(@firebase/app@0.16.4) '@firebase/installations-types': 0.5.5(@firebase/app-types@0.9.6) '@firebase/util': 1.15.3 tslib: 2.8.1 @@ -12791,9 +12774,9 @@ snapshots: dependencies: '@firebase/app-types': 0.9.6 - '@firebase/installations@0.6.24(@firebase/app@0.16.3)': + '@firebase/installations@0.6.24(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 '@firebase/util': 1.15.3 idb: 7.1.1 @@ -12803,78 +12786,78 @@ snapshots: dependencies: tslib: 2.8.1 - '@firebase/messaging-compat@0.2.30(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/messaging-compat@0.2.30(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 - '@firebase/messaging': 0.13.3(@firebase/app@0.16.3) + '@firebase/messaging': 0.13.3(@firebase/app@0.16.4) '@firebase/util': 1.15.3 tslib: 2.8.1 '@firebase/messaging-interop-types@0.2.6': {} - '@firebase/messaging@0.13.3(@firebase/app@0.16.3)': + '@firebase/messaging@0.13.3(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 - '@firebase/installations': 0.6.24(@firebase/app@0.16.3) + '@firebase/installations': 0.6.24(@firebase/app@0.16.4) '@firebase/messaging-interop-types': 0.2.6 '@firebase/util': 1.15.3 idb: 7.1.1 tslib: 2.8.1 - '@firebase/performance-compat@0.2.27(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/performance-compat@0.2.27(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 - '@firebase/performance': 0.7.14(@firebase/app@0.16.3) + '@firebase/performance': 0.7.14(@firebase/app@0.16.4) '@firebase/performance-types': 0.2.5 '@firebase/util': 1.15.3 tslib: 2.8.1 '@firebase/performance-types@0.2.5': {} - '@firebase/performance@0.7.14(@firebase/app@0.16.3)': + '@firebase/performance@0.7.14(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 - '@firebase/installations': 0.6.24(@firebase/app@0.16.3) + '@firebase/installations': 0.6.24(@firebase/app@0.16.4) '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 tslib: 2.8.1 web-vitals: 4.2.4 - '@firebase/remote-config-compat@0.2.30(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3)': + '@firebase/remote-config-compat@0.2.30(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 '@firebase/logger': 0.5.2 - '@firebase/remote-config': 0.9.3(@firebase/app@0.16.3) + '@firebase/remote-config': 0.9.3(@firebase/app@0.16.4) '@firebase/remote-config-types': 0.5.2 '@firebase/util': 1.15.3 tslib: 2.8.1 '@firebase/remote-config-types@0.5.2': {} - '@firebase/remote-config@0.9.3(@firebase/app@0.16.3)': + '@firebase/remote-config@0.9.3(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 - '@firebase/installations': 0.6.24(@firebase/app@0.16.3) + '@firebase/installations': 0.6.24(@firebase/app@0.16.4) '@firebase/logger': 0.5.2 '@firebase/util': 1.15.3 tslib: 2.8.1 - '@firebase/storage-compat@0.4.6(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3)': + '@firebase/storage-compat@0.4.6(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 - '@firebase/app-compat': 0.5.19 + '@firebase/app': 0.16.4 + '@firebase/app-compat': 0.5.20 '@firebase/component': 0.7.5 - '@firebase/storage': 0.14.6(@firebase/app@0.16.3) + '@firebase/storage': 0.14.6(@firebase/app@0.16.4) '@firebase/storage-types': 0.8.5(@firebase/app-types@0.9.6)(@firebase/util@1.15.3) '@firebase/util': 1.15.3 tslib: 2.8.1 @@ -12886,9 +12869,9 @@ snapshots: '@firebase/app-types': 0.9.6 '@firebase/util': 1.15.3 - '@firebase/storage@0.14.6(@firebase/app@0.16.3)': + '@firebase/storage@0.14.6(@firebase/app@0.16.4)': dependencies: - '@firebase/app': 0.16.3 + '@firebase/app': 0.16.4 '@firebase/component': 0.7.5 '@firebase/util': 1.15.3 tslib: 2.8.1 @@ -13001,7 +12984,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@google/genai@2.30.0(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0))(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)': + '@google/genai@2.31.0(@modelcontextprotocol/sdk@1.32.2(supports-color@11.0.0))(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)': dependencies: google-auth-library: 10.9.1(supports-color@11.0.0) p-retry: 4.6.2 @@ -13028,7 +13011,7 @@ snapshots: '@grpc/grpc-js@1.9.16': dependencies: '@grpc/proto-loader': 0.7.15 - '@types/node': 24.19.2 + '@types/node': 20.19.43 '@grpc/proto-loader@0.7.15': dependencies: @@ -13309,21 +13292,6 @@ snapshots: optionalDependencies: '@types/node': 20.19.43 - '@inquirer/prompts@8.7.4(@types/node@24.19.2)': - dependencies: - '@inquirer/checkbox': 5.2.8(@types/node@24.19.2) - '@inquirer/confirm': 6.3.5(@types/node@24.19.2) - '@inquirer/editor': 5.3.6(@types/node@24.19.2) - '@inquirer/expand': 5.1.8(@types/node@24.19.2) - '@inquirer/input': 5.1.9(@types/node@24.19.2) - '@inquirer/number': 4.2.6(@types/node@24.19.2) - '@inquirer/password': 5.2.5(@types/node@24.19.2) - '@inquirer/rawlist': 5.3.8(@types/node@24.19.2) - '@inquirer/search': 4.3.6(@types/node@24.19.2) - '@inquirer/select': 5.2.8(@types/node@24.19.2) - optionalDependencies: - '@types/node': 24.19.2 - '@inquirer/prompts@8.7.5(@types/node@20.19.43)': dependencies: '@inquirer/checkbox': 5.2.8(@types/node@20.19.43) @@ -13433,10 +13401,6 @@ snapshots: optionalDependencies: '@types/node': 20.19.43 - '@inquirer/type@4.2.0(@types/node@24.19.2)': - optionalDependencies: - '@types/node': 24.19.2 - '@inquirer/type@4.2.1(@types/node@20.19.43)': optionalDependencies: '@types/node': 20.19.43 @@ -14650,7 +14614,7 @@ snapshots: '@types/cli-progress@3.11.6': dependencies: - '@types/node': 24.19.2 + '@types/node': 20.19.43 '@types/convert-source-map@2.0.3': {} @@ -14785,7 +14749,7 @@ snapshots: '@types/duplexify@3.6.5': dependencies: - '@types/node': 24.19.2 + '@types/node': 20.19.43 '@types/ejs@3.1.5': {} @@ -14865,7 +14829,7 @@ snapshots: '@types/pumpify@1.4.5': dependencies: '@types/duplexify': 3.6.5 - '@types/node': 24.19.2 + '@types/node': 20.19.43 '@types/q@0.0.32': {} @@ -16779,9 +16743,9 @@ snapshots: encodeurl@2.0.0: {} - encoding@0.1.13: + encoding@0.1.14: dependencies: - iconv-lite: 0.6.3 + iconv-lite: 0.7.3 end-of-stream@1.4.5: dependencies: @@ -17296,7 +17260,7 @@ snapshots: object.pick: 1.3.0 parse-filepath: 1.0.2 - firebase-tools@15.34.1(@types/node@20.19.43)(bufferutil@4.1.0)(encoding@0.1.13)(supports-color@11.0.0): + firebase-tools@15.34.1(@types/node@20.19.43)(bufferutil@4.1.0)(encoding@0.1.14)(supports-color@11.0.0): dependencies: '@electric-sql/pglite': 0.3.16 '@electric-sql/pglite-tools': 0.2.21(@electric-sql/pglite@0.3.16) @@ -17326,9 +17290,9 @@ snapshots: form-data: 4.0.6 fs-extra: 10.1.0 fuzzy: 0.1.3 - gaxios: 6.7.1(encoding@0.1.13)(supports-color@11.0.0) + gaxios: 6.7.1(encoding@0.1.14)(supports-color@11.0.0) glob: 10.5.0 - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@11.0.0) + google-auth-library: 9.15.1(encoding@0.1.14)(supports-color@11.0.0) ignore: 7.0.14 js-yaml: 4.3.2 jsonwebtoken: 9.0.3 @@ -17340,7 +17304,7 @@ snapshots: mime: 2.6.0 minimatch: 3.1.5 morgan: 1.12.1(supports-color@11.0.0) - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.14) open: 6.4.0 ora: 5.4.1 pg: 8.23.1 @@ -17353,13 +17317,13 @@ snapshots: sql-formatter: 15.9.0 stream-chain: 4.3.0 stream-json: 3.7.0 - superstatic: 10.0.0(encoding@0.1.13)(supports-color@11.0.0) + superstatic: 10.0.0(encoding@0.1.14)(supports-color@11.0.0) tar: 7.5.22 tcp-port-used: 1.0.3(supports-color@11.0.0) tmp: 0.2.7 triple-beam: 1.4.1 undici: 6.29.1 - update-notifier-cjs: 5.1.7(encoding@0.1.13) + update-notifier-cjs: 5.1.7(encoding@0.1.14) winston: 3.19.0 winston-transport: 4.9.0 ws: 8.22.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) @@ -17377,35 +17341,35 @@ snapshots: - supports-color - utf-8-validate - firebase@13.0.0: + firebase@13.1.0: dependencies: - '@firebase/ai': 3.0.0(@firebase/app-types@0.9.6)(@firebase/app@0.16.3) - '@firebase/analytics': 0.10.25(@firebase/app@0.16.3) - '@firebase/analytics-compat': 0.2.31(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/app': 0.16.3 - '@firebase/app-check': 0.13.1(@firebase/app@0.16.3) - '@firebase/app-check-compat': 0.4.7(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/app-compat': 0.5.19 + '@firebase/ai': 3.0.0(@firebase/app-types@0.9.6)(@firebase/app@0.16.4) + '@firebase/analytics': 0.10.25(@firebase/app@0.16.4) + '@firebase/analytics-compat': 0.2.31(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/app': 0.16.4 + '@firebase/app-check': 0.13.1(@firebase/app@0.16.4) + '@firebase/app-check-compat': 0.4.7(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/app-compat': 0.5.20 '@firebase/app-types': 0.9.6 - '@firebase/auth': 1.13.7(@firebase/app@0.16.3) - '@firebase/auth-compat': 0.6.12(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3) - '@firebase/data-connect': 0.7.4(@firebase/app@0.16.3) + '@firebase/auth': 1.13.8(@firebase/app@0.16.4) + '@firebase/auth-compat': 0.6.13(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4) + '@firebase/data-connect': 0.7.4(@firebase/app@0.16.4) '@firebase/database': 1.1.5 - '@firebase/database-compat': 2.1.7(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/firestore': 4.18.0(@firebase/app@0.16.3) - '@firebase/firestore-compat': 0.4.15(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3) - '@firebase/functions': 0.14.0(@firebase/app@0.16.3) - '@firebase/functions-compat': 0.5.0(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/installations': 0.6.24(@firebase/app@0.16.3) - '@firebase/installations-compat': 0.2.24(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3) - '@firebase/messaging': 0.13.3(@firebase/app@0.16.3) - '@firebase/messaging-compat': 0.2.30(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/performance': 0.7.14(@firebase/app@0.16.3) - '@firebase/performance-compat': 0.2.27(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/remote-config': 0.9.3(@firebase/app@0.16.3) - '@firebase/remote-config-compat': 0.2.30(@firebase/app-compat@0.5.19)(@firebase/app@0.16.3) - '@firebase/storage': 0.14.6(@firebase/app@0.16.3) - '@firebase/storage-compat': 0.4.6(@firebase/app-compat@0.5.19)(@firebase/app-types@0.9.6)(@firebase/app@0.16.3) + '@firebase/database-compat': 2.1.7(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/firestore': 4.19.0(@firebase/app@0.16.4) + '@firebase/firestore-compat': 0.4.16(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4) + '@firebase/functions': 0.14.0(@firebase/app@0.16.4) + '@firebase/functions-compat': 0.5.0(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/installations': 0.6.24(@firebase/app@0.16.4) + '@firebase/installations-compat': 0.2.24(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4) + '@firebase/messaging': 0.13.3(@firebase/app@0.16.4) + '@firebase/messaging-compat': 0.2.30(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/performance': 0.7.14(@firebase/app@0.16.4) + '@firebase/performance-compat': 0.2.27(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/remote-config': 0.9.3(@firebase/app@0.16.4) + '@firebase/remote-config-compat': 0.2.30(@firebase/app-compat@0.5.20)(@firebase/app@0.16.4) + '@firebase/storage': 0.14.6(@firebase/app@0.16.4) + '@firebase/storage-compat': 0.4.6(@firebase/app-compat@0.5.20)(@firebase/app-types@0.9.6)(@firebase/app@0.16.4) '@firebase/util': 1.15.3 transitivePeerDependencies: - '@react-native-async-storage/async-storage' @@ -17522,12 +17486,12 @@ snapshots: fuzzy@0.1.3: {} - gaxios@6.7.1(encoding@0.1.13)(supports-color@11.0.0): + gaxios@6.7.1(encoding@0.1.14)(supports-color@11.0.0): dependencies: extend: 3.0.2 https-proxy-agent: 7.0.6(supports-color@11.0.0) is-stream: 2.0.1 - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.14) uuid: 9.0.1 transitivePeerDependencies: - encoding @@ -17558,9 +17522,9 @@ snapshots: transitivePeerDependencies: - supports-color - gcp-metadata@6.1.1(encoding@0.1.13)(supports-color@11.0.0): + gcp-metadata@6.1.1(encoding@0.1.14)(supports-color@11.0.0): dependencies: - gaxios: 6.7.1(encoding@0.1.13)(supports-color@11.0.0) + gaxios: 6.7.1(encoding@0.1.14)(supports-color@11.0.0) google-logging-utils: 0.0.2 json-bigint: 1.0.0 transitivePeerDependencies: @@ -17799,13 +17763,13 @@ snapshots: transitivePeerDependencies: - supports-color - google-auth-library@9.15.1(encoding@0.1.13)(supports-color@11.0.0): + google-auth-library@9.15.1(encoding@0.1.14)(supports-color@11.0.0): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 6.7.1(encoding@0.1.13)(supports-color@11.0.0) - gcp-metadata: 6.1.1(encoding@0.1.13)(supports-color@11.0.0) - gtoken: 7.1.0(encoding@0.1.13)(supports-color@11.0.0) + gaxios: 6.7.1(encoding@0.1.14)(supports-color@11.0.0) + gcp-metadata: 6.1.1(encoding@0.1.14)(supports-color@11.0.0) + gtoken: 7.1.0(encoding@0.1.14)(supports-color@11.0.0) jws: 4.0.1 transitivePeerDependencies: - encoding @@ -17864,9 +17828,9 @@ snapshots: '@grpc/grpc-js': 1.14.6 protobufjs: 7.6.6 - gtoken@7.1.0(encoding@0.1.13)(supports-color@11.0.0): + gtoken@7.1.0(encoding@0.1.14)(supports-color@11.0.0): dependencies: - gaxios: 6.7.1(encoding@0.1.13)(supports-color@11.0.0) + gaxios: 6.7.1(encoding@0.1.14)(supports-color@11.0.0) jws: 4.0.1 transitivePeerDependencies: - encoding @@ -18444,9 +18408,9 @@ snapshots: isobject@3.0.1: {} - isomorphic-fetch@3.0.0(encoding@0.1.13): + isomorphic-fetch@3.0.0(encoding@0.1.14): dependencies: - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.14) whatwg-fetch: 3.6.20 transitivePeerDependencies: - encoding @@ -19658,11 +19622,11 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 - node-fetch@2.7.0(encoding@0.1.13): + node-fetch@2.7.0(encoding@0.1.14): dependencies: whatwg-url: 5.0.0 optionalDependencies: - encoding: 0.1.13 + encoding: 0.1.14 node-fetch@3.3.2: dependencies: @@ -21257,7 +21221,7 @@ snapshots: tinyglobby: 0.2.17 ts-interface-checker: 0.1.13 - superstatic@10.0.0(encoding@0.1.13)(supports-color@11.0.0): + superstatic@10.0.0(encoding@0.1.14)(supports-color@11.0.0): dependencies: basic-auth-connect: 1.1.0 commander: 10.0.1 @@ -21275,7 +21239,7 @@ snapshots: on-headers: 1.1.0 path-to-regexp: 1.9.0 router: 2.2.0(supports-color@11.0.0) - update-notifier-cjs: 5.1.7(encoding@0.1.13) + update-notifier-cjs: 5.1.7(encoding@0.1.14) optionalDependencies: re2: 1.27.0 transitivePeerDependencies: @@ -21821,7 +21785,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-notifier-cjs@5.1.7(encoding@0.1.13): + update-notifier-cjs@5.1.7(encoding@0.1.14): dependencies: boxen: 5.1.2 chalk: 4.1.2 @@ -21832,7 +21796,7 @@ snapshots: is-installed-globally: 0.4.0 is-npm: 5.0.0 is-yarn-global: 0.3.0 - isomorphic-fetch: 3.0.0(encoding@0.1.13) + isomorphic-fetch: 3.0.0(encoding@0.1.14) pupa: 2.1.1 registry-auth-token: 5.1.1 registry-url: 5.1.0 From fdb4517612cf191da3c3e2df251f9f79f026448b Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 31 Oct 2026 10:50:50 +0100 Subject: [PATCH 2/6] test(compiler-cli): clean up invalid tests Cleans up some pre-existing tests that were verifying an invalid setup where inputs/output were defined on private fields. This wasn't being flagged by the compiler, because of the way the tests are set up which only exercises the template type checker. --- .../testing/src/ngp_known_failures.ts | 40 ++++------------ .../test/input_signal_diagnostics_spec.ts | 33 ------------- .../test/model_signal_diagnostics_spec.ts | 48 ------------------- .../test/output_function_diagnostics.spec.ts | 6 --- 4 files changed, 10 insertions(+), 117 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/file_system/testing/src/ngp_known_failures.ts b/packages/compiler-cli/src/ngtsc/file_system/testing/src/ngp_known_failures.ts index 02d59b127d51..e735aa9df096 100644 --- a/packages/compiler-cli/src/ngtsc/file_system/testing/src/ngp_known_failures.ts +++ b/packages/compiler-cli/src/ngtsc/file_system/testing/src/ngp_known_failures.ts @@ -38,42 +38,12 @@ export function shouldSkipInNgp(fullSpecName: string): boolean { } const normalized = fullSpecName.replace(/\s+/g, ' ').trim(); return ( - NGP_KNOWN_TYPECHECK_FAILURES.has(normalized) || NGP_REQUIRES_TYPE_CHECKER.has(normalized) || NGP_KNOWN_NGTSC_FAILURES.has(normalized) || NGP_POTENTIAL_BREAKING_CHANGES.has(normalized) ); } -/** - * Known failures in `//packages/compiler-cli/src/ngtsc/typecheck/test:test_ngp` - * (#678-#682, #690, plus `TemplateTypeChecker` IDE metadata queries). - */ -const NGP_KNOWN_TYPECHECK_FAILURES: ReadonlySet = new Set([ - 'template parse failures should handle TypeScript parse failures gracefully', - 'template parse failures should handle non-Error failures gracefully', - - // The tests below check a pattern that is not allowed (private inputs/outputs). The reason they - // pass with ngtsc is that the tests are set up in a way that bypasses some validations. - // TODO(crisbeto): clean up these tests since they aren't testing a valid pattern. - 'input signal type-check diagnostics allow access to private input if modifiers are explicitly ignored', - 'input signal type-check diagnostics allow access to private input if modifiers are explicitly ignored, but error if not assignable', - 'input signal type-check diagnostics disallows access to private input', - 'input signal type-check diagnostics with `--strict` allow access to private input if modifiers are explicitly ignored', - 'input signal type-check diagnostics with `--strict` allow access to private input if modifiers are explicitly ignored, but error if not assignable', - 'input signal type-check diagnostics with `--strict` disallows access to private input', - 'model inputs type-check diagnostics allow access to private input if modifiers are explicitly ignored', - 'model inputs type-check diagnostics allow access to private model if modifiers are explicitly ignored, but error if not assignable', - 'model inputs type-check diagnostics allows access to private output', - 'model inputs type-check diagnostics allows access to protected output', - 'model inputs type-check diagnostics disallows access to private model', - 'model inputs type-check diagnostics with `--strict` allow access to private input if modifiers are explicitly ignored', - 'model inputs type-check diagnostics with `--strict` allow access to private model if modifiers are explicitly ignored, but error if not assignable', - 'model inputs type-check diagnostics with `--strict` allows access to private output', - 'model inputs type-check diagnostics with `--strict` allows access to protected output', - 'model inputs type-check diagnostics with `--strict` disallows access to private model', -]); - /** * Tests that rely on a TypeScript `TypeChecker` during analysis or template semantics checking, * which `ngp` does not have at preprocessing time. @@ -309,6 +279,14 @@ const NGP_POTENTIAL_BREAKING_CHANGES: ReadonlySet = new Set([ 'selectorless should resolve local reference to selectorless directive', 'selectorless should treat unclaimed inputs as DOM property bindings on component nodes', 'selectorless should treat unclaimed outputs as DOM events on component nodes', + + // The two tests check behavior specific to the TS6 source + // file construction so we don't need to support them. + 'template parse failures should handle TypeScript parse failures gracefully', + 'template parse failures should handle non-Error failures gracefully', + + // Tests TypeScript `structureIsReused` across in-memory ngtsc incremental compilations. + 'ngtsc type checking stability should allow for complete program reuse during incremental compilations', ]); /** @@ -318,6 +296,8 @@ const NGP_POTENTIAL_BREAKING_CHANGES: ReadonlySet = new Set([ * and diagnostic/TCB gaps #677-#696). */ const NGP_KNOWN_NGTSC_FAILURES: ReadonlySet = new Set([ + 'model inputs type-check diagnostics allows access to protected output', + 'model inputs type-check diagnostics with `--strict` allows access to protected output', 'import generation should report an error when a library entry-point does not export the symbol', 'import generation should report an error when using a directive outside of rootDirs', diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/input_signal_diagnostics_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/input_signal_diagnostics_spec.ts index 257a82b1eacc..0ab1ef7bc02c 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/input_signal_diagnostics_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/input_signal_diagnostics_spec.ts @@ -103,16 +103,6 @@ runInEachFileSystem(() => { ], }, // restricted fields - { - id: 'disallows access to private input', - inputs: { - pattern: {type: 'InputSignal', isSignal: true, restrictionModifier: 'private'}, - }, - template: `
`, - expected: [ - `TestComponent.html(1, 11): Property 'pattern' is private and only accessible within class 'Dir'.`, - ], - }, { id: 'disallows access to protected input', inputs: { @@ -134,18 +124,6 @@ runInEachFileSystem(() => { template: `
`, expected: [], }, - // restricted fields (but opt-out of check) - { - id: 'allow access to private input if modifiers are explicitly ignored', - inputs: { - pattern: {type: 'InputSignal', isSignal: true, restrictionModifier: 'private'}, - }, - template: `
`, - expected: [], - options: { - honorAccessModifiersForInputBindings: false, - }, - }, { id: 'allow access to protected input if modifiers are explicitly ignored', inputs: { @@ -168,17 +146,6 @@ runInEachFileSystem(() => { honorAccessModifiersForInputBindings: false, }, }, - { - id: 'allow access to private input if modifiers are explicitly ignored, but error if not assignable', - inputs: { - pattern: {type: 'InputSignal', isSignal: true, restrictionModifier: 'private'}, - }, - template: `
`, - expected: [`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`], - options: { - honorAccessModifiersForInputBindings: false, - }, - }, // coercion is not supported / respected { id: 'coercion members are not respected', diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts index 80d40532a225..32fd2e225be2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts @@ -100,17 +100,6 @@ runInEachFileSystem(() => { ], }, // restricted fields - { - id: 'disallows access to private model', - inputs: { - pattern: {type: 'ModelSignal', isSignal: true, restrictionModifier: 'private'}, - }, - outputs: {patternChange: {type: 'ModelSignal'}}, - template: `
`, - expected: [ - `TestComponent.html(1, 11): Property 'pattern' is private and only accessible within class 'Dir'.`, - ], - }, { id: 'disallows access to protected model', inputs: { @@ -132,18 +121,6 @@ runInEachFileSystem(() => { expected: [], }, // restricted fields (but opt-out of check) - { - id: 'allow access to private input if modifiers are explicitly ignored', - inputs: { - pattern: {type: 'ModelSignal', isSignal: true, restrictionModifier: 'private'}, - }, - outputs: {patternChange: {type: 'ModelSignal'}}, - template: `
`, - expected: [], - options: { - honorAccessModifiersForInputBindings: false, - }, - }, { id: 'allow access to protected model if modifiers are explicitly ignored', inputs: { @@ -168,18 +145,6 @@ runInEachFileSystem(() => { honorAccessModifiersForInputBindings: false, }, }, - { - id: 'allow access to private model if modifiers are explicitly ignored, but error if not assignable', - inputs: { - pattern: {type: 'ModelSignal', isSignal: true, restrictionModifier: 'private'}, - }, - outputs: {patternChange: {type: 'ModelSignal'}}, - template: `
`, - expected: [`TestComponent.html(1, 11): Type 'boolean' is not assignable to type 'string'.`], - options: { - honorAccessModifiersForInputBindings: false, - }, - }, // coercion is not supported / respected { id: 'coercion members are not respected', @@ -612,19 +577,6 @@ runInEachFileSystem(() => { ], }, // restricted fields - { - id: 'allows access to private output', - inputs: { - evt: { - type: 'ModelSignal', - isSignal: true, - restrictionModifier: 'private', - }, - }, - outputs: {evtChange: {type: 'ModelSignal', restrictionModifier: 'private'}}, - template: `
`, - expected: [], - }, { id: 'allows access to protected output', inputs: { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts index b3c86afb7bf7..6442c7de3172 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts @@ -66,12 +66,6 @@ runInEachFileSystem(() => { ], }, // restricted fields - { - id: 'allows access to private output', - outputs: {evt: {type: 'OutputEmitterRef', restrictionModifier: 'private'}}, - template: `
`, - expected: [], - }, { id: 'allows access to protected output', outputs: {evt: {type: 'OutputEmitterRef', restrictionModifier: 'protected'}}, From 5c00d135e36adf88a08f0b114f35e4f319bc63de Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 2 Nov 2026 09:57:30 +0100 Subject: [PATCH 3/6] fix(compiler-cli): do not decorate signal APIs in preprocessor In the preprocessor we ported the logic from ngtsc that adds a decorator with `isJit: true` to signal inputs, e.g. `@Input({isJit: true} as any) someInput = input();`. This works for ngtsc since the decorator downlevel transform comes in later and moves that metadata into `propDecorators`, however it ends up breaking the signal APIs with the preprocessor. These changes switch to emitting the code directly into `propDecorators`. --- .../preprocessor/src/processor.ts | 121 +++++------------- .../golden/jit_signal_transforms/golden.md | 105 +++++++++------ .../test/jit_downlevel_decorators_spec.ts | 46 +++++++ 3 files changed, 139 insertions(+), 133 deletions(-) diff --git a/packages/compiler-cli/preprocessor/src/processor.ts b/packages/compiler-cli/preprocessor/src/processor.ts index 4e3ab02a6fcb..b984a42bbad3 100644 --- a/packages/compiler-cli/preprocessor/src/processor.ts +++ b/packages/compiler-cli/preprocessor/src/processor.ts @@ -504,97 +504,25 @@ export function processFileSync( if (isJit) { if (!ctx.emitDeclarationOnly) { - const coreNamespace = printer.getOrCreateNamespace('@angular/core'); - for (const input of inputs) { - if (input.isSignal && input.propertySpan) { - const inputArgsObj: string[] = [ - 'isSignal: true', - `alias: ${JSON.stringify(input.alias ?? input.name)}`, - `required: ${input.required ? 'true' : 'false'}`, - ]; - const inputArgs = `({${inputArgsObj.join(', ')}} as any)`; - s.appendLeft(input.propertySpan.start, `@${coreNamespace}.Input(${inputArgs}) `); - } - } - - for (const output of outputs) { - if (output.isSignal && output.propertySpan) { - const outputArg = JSON.stringify(output.alias ?? output.name); - s.appendLeft(output.propertySpan.start, `@${coreNamespace}.Output(${outputArg}) `); - } - } - - const allSignalQueries = [...(queries ?? []), ...(viewQueries ?? [])].filter( - (q) => q.isSignal && q.propertySpan, - ); - for (const query of allSignalQueries) { - const decoratorName = query.isView - ? query.first - ? 'ViewChild' - : 'ViewChildren' - : query.first - ? 'ContentChild' - : 'ContentChildren'; - const rawPred = s.original.slice(query.predicateSpan.start, query.predicateSpan.end); - // A signal query has selectors exactly when its locator is string-literal-like. - const predicateStr = query.predicateSelectors - ? rawPred - : `${coreNamespace}.forwardRef(() => ${rawPred})`; - const optionsObj: string[] = []; - if (query.optionsSpan) { - optionsObj.push( - `...${s.original.slice(query.optionsSpan.start, query.optionsSpan.end)}`, - ); + printer.getOrCreateNamespace('@angular/core'); + if (constructorParams && constructorParams.length > 0) { + for (const param of constructorParams) { + const hasNgDecorators = param.decorators.some((d) => d.isAngular); + if ((param.hasType || hasNgDecorators) && !param.isTypeOnly && param.typeName != null) { + eagerlyBoundNames.add(param.typeName.split('.')[0]); + } } - optionsObj.push('isSignal: true'); - const optionsStr = `({${optionsObj.join(', ')}} as any)`; + const ctorExpr = buildJitCtorParametersExpr(constructorParams, s); + const nocollapse = ctx.isClosureCompilerEnabled ? '/** @nocollapse */\n ' : ''; s.appendLeft( - query.propertySpan!.start, - `@${coreNamespace}.${decoratorName}(${predicateStr}, ${optionsStr}) `, + span.end - 1, + // ts-ignore is necessary to suppress errors if the base class has `ctorParameters`. + `\n ${nocollapse}// @ts-ignore\n static ctorParameters = ${printer.print(ctorExpr)};\n`, ); } - } - - if (!ctx.emitDeclarationOnly && constructorParams && constructorParams.length > 0) { - for (const param of constructorParams) { - const hasNgDecorators = param.decorators.some((d) => d.isAngular); - if ((param.hasType || hasNgDecorators) && !param.isTypeOnly && param.typeName != null) { - eagerlyBoundNames.add(param.typeName.split('.')[0]); - } - } - const ctorExpr = buildJitCtorParametersExpr(constructorParams, s); - const nocollapse = ctx.isClosureCompilerEnabled ? '/** @nocollapse */\n ' : ''; - s.appendLeft( - span.end - 1, - // ts-ignore is necessary to suppress errors if the base class has `ctorParameters`. - `\n ${nocollapse}// @ts-ignore\n static ctorParameters = ${printer.print(ctorExpr)};\n`, - ); - } - if ( - !ctx.emitDeclarationOnly && - classMeta.memberDecorators && - classMeta.memberDecorators.length > 0 - ) { - const propDecoratorsMap = new Map< - string, - {key: string; quoted: boolean; value: o.Expression} - >(); - for (const member of classMeta.memberDecorators) { - if (member.isStringLiteral) continue; - const ngDecorators = member.decorators - .filter((d) => d.isAngular) - .map((d) => buildDecoratorMetadataExpr(d.name, d.argsSpan, d.argsString, s, [])); - if (ngDecorators.length > 0) { - propDecoratorsMap.set(member.propertyName, { - key: member.propertyName, - quoted: false, - value: o.literalArr(ngDecorators), - }); - } - } - if (propDecoratorsMap.size > 0) { - const propExpr = o.literalMap(Array.from(propDecoratorsMap.values())); + const propExpr = buildPropDecoratorsExpr(classMeta, s, className, [], /* isJit */ true); + if (propExpr !== null) { const nocollapse = ctx.isClosureCompilerEnabled ? '/** @nocollapse */\n ' : ''; s.appendLeft( span.end - 1, @@ -2420,6 +2348,7 @@ function buildPropDecoratorsExpr( s: MagicString, className: string, guards: nga.SpanMetadata[], + isJit: boolean, ): o.Expression | null { const {memberDecorators, inputs, outputs, queries, viewQueries} = classMeta; const propMap = new Map< @@ -2440,10 +2369,13 @@ function buildPropDecoratorsExpr( // 1. Decorator-based members from AST const seenMemberNames = - memberDecorators && memberDecorators.length > 1 ? new Set() : null; + !isJit && memberDecorators && memberDecorators.length > 1 ? new Set() : null; const duplicateNames: string[] = []; if (memberDecorators) { for (const member of memberDecorators) { + if (isJit && member.isStringLiteral) { + continue; + } if (seenMemberNames !== null) { if (seenMemberNames.has(member.propertyName)) { duplicateNames.push(member.propertyName); @@ -2451,12 +2383,19 @@ function buildPropDecoratorsExpr( } seenMemberNames.add(member.propertyName); } - const offset = member.decorators[0]?.decoratorSpan?.start; - const entry = getOrCreateEntry(member.propertyName, member.isStringLiteral, offset); const ngDecorators = member.decorators .filter((d) => d.isAngular) .map((d) => buildDecoratorMetadataExpr(d.name, d.argsSpan, d.argsString, s, guards)); - entry.decorators.push(...ngDecorators); + if (isJit && ngDecorators.length === 0) { + continue; + } + const offset = member.decorators[0]?.decoratorSpan?.start; + const entry = getOrCreateEntry(member.propertyName, member.isStringLiteral, offset); + if (isJit) { + entry.decorators = ngDecorators; + } else { + entry.decorators.push(...ngDecorators); + } } } @@ -2677,7 +2616,7 @@ function buildSetClassMetadata( type: o.variable(className), decorators: o.literalArr(decoratorObjs), ctorParameters: buildCtorParametersExpr(classMeta.constructorParams, s), - propDecorators: buildPropDecoratorsExpr(classMeta, s, className, guards), + propDecorators: buildPropDecoratorsExpr(classMeta, s, className, guards, false), }; return compileComponentClassMetadata(classMetadata, deferredDeps).toStmt(); } diff --git a/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md b/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md index 2268e80e150b..dbb8f19981a9 100644 --- a/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md +++ b/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md @@ -30,69 +30,84 @@ export class ChildComponent {} jit: true, }) export class JitComponent { - @i0.Input({ isSignal: true, alias: 'sigInput', required: false } as any) sigInput = input( + sigInput = input( 'default', ...((ngDevMode ? [{ debugName: 'sigInput' }] : /* istanbul ignore next */ []) as []), ); - @i0.Input({ isSignal: true, alias: 'reqInput', required: true } as any) reqInput = - input.required( - ...((ngDevMode ? [{ debugName: 'reqInput' }] : /* istanbul ignore next */ []) as []), - ); - @i0.Input({ isSignal: true, alias: 'publicName', required: false } as any) aliasedInput = input( - 'val', - { - ...(ngDevMode ? { debugName: 'aliasedInput' } : /* istanbul ignore next */ {}), - alias: 'publicName', - }, + reqInput = input.required( + ...((ngDevMode ? [{ debugName: 'reqInput' }] : /* istanbul ignore next */ []) as []), ); - @i0.Input({ isSignal: true, alias: 'transformedInput', required: false } as any) + aliasedInput = input('val', { + ...(ngDevMode ? { debugName: 'aliasedInput' } : /* istanbul ignore next */ {}), + alias: 'publicName', + }); transformedInput = input('', { ...(ngDevMode ? { debugName: 'transformedInput' } : /* istanbul ignore next */ {}), transform: (v: string) => v.trim(), }); - @i0.Output('sigOutput') sigOutput = output(); - @i0.Output('customEvent') aliasedOutput = output({ alias: 'customEvent' }); + sigOutput = output(); + aliasedOutput = output({ alias: 'customEvent' }); - @i0.Input({ isSignal: true, alias: 'sigModel', required: false } as any) - @i0.Output('sigModelChange') sigModel = model( 123, ...((ngDevMode ? [{ debugName: 'sigModel' }] : /* istanbul ignore next */ []) as []), ); - @i0.Input({ isSignal: true, alias: 'publicModel', required: false } as any) - @i0.Output('publicModelChange') aliasedModel = model('str', { ...(ngDevMode ? { debugName: 'aliasedModel' } : /* istanbul ignore next */ {}), alias: 'publicModel', }); - @i0.ViewChild('el', { isSignal: true } as any) sigViewChild = viewChild( + sigViewChild = viewChild( 'el', ...((ngDevMode ? [{ debugName: 'sigViewChild' }] : /* istanbul ignore next */ []) as []), ); - @i0.ViewChild(i0.forwardRef(() => ChildComponent), { isSignal: true } as any) sigViewChildForward = viewChild( forwardRef(() => ChildComponent), ...((ngDevMode ? [{ debugName: 'sigViewChildForward' }] : /* istanbul ignore next */ []) as []), ); - @i0.ViewChildren('item', { isSignal: true } as any) sigViewChildren = viewChildren( + sigViewChildren = viewChildren( 'item', ...((ngDevMode ? [{ debugName: 'sigViewChildren' }] : /* istanbul ignore next */ []) as []), ); - @i0.ContentChild('contentEl', { isSignal: true } as any) sigContentChild = - contentChild( - 'contentEl', - ...((ngDevMode ? [{ debugName: 'sigContentChild' }] : /* istanbul ignore next */ []) as []), - ); - @i0.ContentChildren('contentItem', { isSignal: true } as any) sigContentChildren = - contentChildren( - 'contentItem', - ...((ngDevMode - ? [{ debugName: 'sigContentChildren' }] - : /* istanbul ignore next */ []) as []), - ); + sigContentChild = contentChild( + 'contentEl', + ...((ngDevMode ? [{ debugName: 'sigContentChild' }] : /* istanbul ignore next */ []) as []), + ); + sigContentChildren = contentChildren( + 'contentItem', + ...((ngDevMode ? [{ debugName: 'sigContentChildren' }] : /* istanbul ignore next */ []) as []), + ); + + // @ts-ignore + static propDecorators = { + sigInput: [{ type: i0.Input, args: [{ isSignal: true, alias: 'sigInput', required: false }] }], + reqInput: [{ type: i0.Input, args: [{ isSignal: true, alias: 'reqInput', required: true }] }], + aliasedInput: [ + { type: i0.Input, args: [{ isSignal: true, alias: 'publicName', required: false }] }, + ], + transformedInput: [ + { type: i0.Input, args: [{ isSignal: true, alias: 'transformedInput', required: false }] }, + ], + sigOutput: [{ type: i0.Output, args: ['sigOutput'] }], + aliasedOutput: [{ type: i0.Output, args: ['customEvent'] }], + sigModel: [ + { type: i0.Input, args: [{ isSignal: true, alias: 'sigModel', required: false }] }, + { type: i0.Output, args: ['sigModelChange'] }, + ], + aliasedModel: [ + { type: i0.Input, args: [{ isSignal: true, alias: 'publicModel', required: false }] }, + { type: i0.Output, args: ['publicModelChange'] }, + ], + sigViewChild: [{ type: i0.ViewChild, args: ['el', { isSignal: true }] }], + sigViewChildForward: [ + { type: i0.ViewChild, args: [forwardRef(() => ChildComponent), { isSignal: true }] }, + ], + sigViewChildren: [{ type: i0.ViewChildren, args: ['item', { isSignal: true }] }], + sigContentChild: [{ type: i0.ContentChild, args: ['contentEl', { isSignal: true }] }], + sigContentChildren: [{ type: i0.ContentChildren, args: ['contentItem', { isSignal: true }] }], + }; } @Directive({ @@ -100,18 +115,24 @@ export class JitComponent { jit: true, }) export class JitDirective { - @i0.Input({ isSignal: true, alias: 'dirInput', required: false } as any) dirInput = - input( - false, - ...((ngDevMode ? [{ debugName: 'dirInput' }] : /* istanbul ignore next */ []) as []), - ); - @i0.Output('dirOutput') dirOutput = output(); - @i0.Input({ isSignal: true, alias: 'dirModel', required: false } as any) - @i0.Output('dirModelChange') + dirInput = input( + false, + ...((ngDevMode ? [{ debugName: 'dirInput' }] : /* istanbul ignore next */ []) as []), + ); + dirOutput = output(); dirModel = model( '', ...((ngDevMode ? [{ debugName: 'dirModel' }] : /* istanbul ignore next */ []) as []), ); -} + // @ts-ignore + static propDecorators = { + dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: 'dirInput', required: false }] }], + dirOutput: [{ type: i0.Output, args: ['dirOutput'] }], + dirModel: [ + { type: i0.Input, args: [{ isSignal: true, alias: 'dirModel', required: false }] }, + { type: i0.Output, args: ['dirModelChange'] }, + ], + }; +} ``` \ No newline at end of file diff --git a/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts b/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts index 5ea6bb5459b1..68ddebcbe532 100644 --- a/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts +++ b/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts @@ -199,6 +199,52 @@ describe('jit: true Angular member and constructor parameter decorator downlevel expect(content).not.toContain('static propDecorators'); expect(content).not.toContain('static ctorParameters'); }); + + it('includes both traditional member decorators and signal APIs in static propDecorators for jit: true', async () => { + const outputs = await compileFiles({ + '/test.ts': ` + import {Component, Input, input, model, output, viewChild, contentChild, ElementRef} from '@angular/core'; + + export class ChildCmp {} + + @Component({ + selector: 'mixed-jit-cmp', + template: '', + jit: true, + }) + export class MixedJitCmp { + @Input() legacyInput = ''; + readonly sigInput = input.required(); + readonly sigModel = model(0, {alias: 'modelAlias'}); + readonly sigOut = output(); + readonly child = viewChild.required(ChildCmp); + readonly projected = contentChild('proj'); + } + `, + }); + + const content = outputs.get('/out/test.ts')!; + expect(content).not.toContain('@Input()'); + expect(content).not.toContain('@i0.Input'); + expect(content).not.toContain('@i0.Output'); + expect(content).not.toContain('@i0.ViewChild'); + expect(content).not.toContain('@i0.ContentChild'); + expect(content).toContain('static propDecorators = {'); + expect(content).toContain('legacyInput: [{type: Input}]'); + expect(content).toContain( + "sigInput: [{type: i0.Input, args: [{isSignal: true, alias: 'sigInput', required: true}]}]", + ); + expect(content).toContain( + "sigModel: [{type: i0.Input, args: [{isSignal: true, alias: 'modelAlias', required: false}]}, {type: i0.Output, args: ['modelAliasChange']}]", + ); + expect(content).toContain("sigOut: [{type: i0.Output, args: ['sigOut']}]"); + expect(content).toContain( + 'child: [{type: i0.ViewChild, args: [i0.forwardRef(() => ChildCmp), {isSignal: true}]}]', + ); + expect(content).toContain( + "projected: [{type: i0.ContentChild, args: ['proj', {isSignal: true}]}]", + ); + }); }); function makeTsconfig( From caa4aa9005f87ce1c1decfdea25eff419b8be7ae Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 2 Nov 2026 10:41:59 +0100 Subject: [PATCH 4/6] fix(compiler-cli): expand more expression types for same-file imports array An earlier fix in the preprocessor made it so we evaluate same-file `imports` arrays instead of referencing them in a `typeof` expression which ends up breaking under isolated declarations. That logic only worked if the entire array was made up of static identifiers which ended up breaking under `ModuleWithProviders` cases. These changes expand preprocessor to handle `ModuleWithProviders`. --- .../ng-analyze/src/analyzer/class_data.rs | 105 +++++++-- .../ng-analyze/src/analyzer/ngmodule.rs | 206 ++++++++++++++++-- .../ng-analyze/src/compiler/tests.rs | 18 +- .../ng-analyze/src/types/metadata.rs | 4 + .../preprocessor/src/processor.ts | 9 +- .../compiler-cli/preprocessor/src/types.ts | 5 + .../test/isolated_ngmodule_imports_spec.ts | 77 +++++++ 7 files changed, 385 insertions(+), 39 deletions(-) diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs index 2bc28592466c..42baf64962ae 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs @@ -1509,6 +1509,14 @@ pub enum IsolatedTypeElementData { CallReturnType { callee_span: oxc_span::Span }, /// `[typeof A, typeof B, ...]` — locally evaluates to an array of references. ReferenceTuple { references: Vec }, + /// `[, , ...]` — a same-file `const` array expanded element-by-element when it cannot + /// be reduced to a flat `ReferenceTuple` (e.g. contains `ModuleWithProviders` calls like + /// `RouterModule.forChild(...)`). Falls back to `fallback` (the `typeof CONST_NAME` span) if any + /// inner element fails to classify. + Tuple { + elements: Vec, + fallback: Box, + }, /// `never` — unsupported expression shape in declaration-only mode. Never { span: oxc_span::Span }, /// Evaluation that still depends on other files (e.g. `const SHARED = [ImportedPipe]`). @@ -1621,37 +1629,69 @@ impl ResolvedIsolatedElement { } } +impl IsolatedTypeElementData { + fn collect_deferred_values_mut<'a>( + &'a mut self, + out: &mut Vec<&'a mut Resolved>, + ) { + match self { + Self::Deferred { resolved, fallback } => { + out.push(resolved); + fallback.collect_deferred_values_mut(out); + } + Self::Tuple { elements, fallback } => { + for el in elements { + el.collect_deferred_values_mut(out); + } + fallback.collect_deferred_values_mut(out); + } + _ => {} + } + } + + fn collect_references(&self, out: &mut Vec) { + match self { + Self::Typeof { + reference: Some(r), .. + } => out.push(r.clone()), + Self::ReferenceTuple { references } => out.extend(references.iter().cloned()), + Self::Tuple { elements, fallback } => { + for el in elements { + el.collect_references(out); + } + fallback.collect_references(out); + } + Self::Deferred { resolved, fallback } => { + if let Some(element) = + ResolvedIsolatedElement::from_value(resolved.raw(), resolved.origin) + { + out.extend(element.references()); + } else { + fallback.collect_references(out); + } + } + _ => {} + } + } +} + impl IsolatedTypeTupleData { /// The evaluations of all `Deferred` elements. pub(crate) fn deferred_values_mut( &mut self, - ) -> impl Iterator> { - self.elements.iter_mut().filter_map(|el| match el { - IsolatedTypeElementData::Deferred { resolved, .. } => Some(resolved), - _ => None, - }) + ) -> Vec<&mut Resolved> { + let mut out = Vec::new(); + for el in &mut self.elements { + el.collect_deferred_values_mut(&mut out); + } + out } /// All references the wire projection may emit. pub(crate) fn references(&self) -> Vec { let mut out = Vec::new(); for el in &self.elements { - match el { - IsolatedTypeElementData::Typeof { - reference: Some(r), .. - } => out.push(r.clone()), - IsolatedTypeElementData::ReferenceTuple { references } => { - out.extend(references.iter().cloned()) - } - IsolatedTypeElementData::Deferred { resolved, .. } => { - if let Some(element) = - ResolvedIsolatedElement::from_value(resolved.raw(), resolved.origin) - { - out.extend(element.references()); - } - } - _ => {} - } + el.collect_references(&mut out); } out } @@ -4601,12 +4641,14 @@ impl ClassData { references: reference .as_ref() .map(|r| vec![self.to_wire_single_ref(r, current_file_path, cx)]), + elements: None, }, IsolatedTypeElementData::CallReturnType { callee_span } => { IsolatedTypeElementMetadata { kind: IsolatedTypeElementKind::CallReturnType, span: Some(SpanMetadata::new(*callee_span, cx.converter)), references: None, + elements: None, } } IsolatedTypeElementData::ReferenceTuple { references } => IsolatedTypeElementMetadata { @@ -4618,11 +4660,31 @@ impl ClassData { .map(|r| self.to_wire_single_ref(r, current_file_path, cx)) .collect(), ), + elements: None, }, + IsolatedTypeElementData::Tuple { elements, fallback } => { + let projected: Vec<_> = elements + .iter() + .map(|el| self.to_wire_isolated_element(el, current_file_path, cx)) + .collect(); + if projected + .iter() + .any(|el| matches!(el.kind, IsolatedTypeElementKind::Never)) + { + return self.to_wire_isolated_element(fallback, current_file_path, cx); + } + IsolatedTypeElementMetadata { + kind: IsolatedTypeElementKind::ReferenceTuple, + span: None, + references: None, + elements: Some(projected), + } + } IsolatedTypeElementData::Never { span } => IsolatedTypeElementMetadata { kind: IsolatedTypeElementKind::Never, span: Some(SpanMetadata::new(*span, cx.converter)), references: None, + elements: None, }, IsolatedTypeElementData::Deferred { resolved, fallback } => { let Some(element) = @@ -4662,6 +4724,7 @@ impl ClassData { kind, span: None, references: Some(references), + elements: None, } } } diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs index 70bc74543219..4e3afa4b29dd 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs @@ -369,6 +369,16 @@ fn classify_isolated_element<'a>( el: &Expression<'a>, semantic: &Semantic<'a>, eval: &EvalInput<'a, '_>, +) -> super::class_data::IsolatedTypeElementData { + let mut visited = std::collections::HashSet::new(); + classify_isolated_element_with_visited(el, semantic, eval, &mut visited) +} + +fn classify_isolated_element_with_visited<'a>( + el: &Expression<'a>, + semantic: &Semantic<'a>, + eval: &EvalInput<'a, '_>, + visited: &mut std::collections::HashSet, ) -> super::class_data::IsolatedTypeElementData { use super::class_data::IsolatedTypeElementData; use crate::evaluator::ResolvedValue; @@ -385,7 +395,7 @@ fn classify_isolated_element<'a>( if resolved.contains_incomplete() { return IsolatedTypeElementData::Deferred { resolved: Resolved::from_syntax(resolved, eval.file), - fallback: Box::new(syntactic_isolated_element(el, semantic)), + fallback: Box::new(fallback_isolated_element(el, semantic, eval, visited)), }; } match resolved.unwrap_named() { @@ -416,7 +426,156 @@ fn classify_isolated_element<'a>( _ => {} } - syntactic_isolated_element(el, semantic) + fallback_isolated_element(el, semantic, eval, visited) +} + +fn fallback_isolated_element<'a>( + el: &Expression<'a>, + semantic: &Semantic<'a>, + eval: &EvalInput<'a, '_>, + visited: &mut std::collections::HashSet, +) -> super::class_data::IsolatedTypeElementData { + use super::class_data::IsolatedTypeElementData; + + let syntactic = syntactic_isolated_element(el, semantic); + if let Some(elements) = try_expand_local_const_array(el, semantic, eval, visited) { + return IsolatedTypeElementData::Tuple { + elements, + fallback: Box::new(syntactic), + }; + } + syntactic +} + +/// If `el` resolves to a same-file variable initialized to a non-empty array literal, classify its +/// elements individually so arrays mixing references and `ModuleWithProviders` calls (e.g. +/// `RouterModule.forChild(...)`) can expand instead of falling back to `typeof CONST_NAME` +/// (which triggers TS9017 under `--isolatedDeclarations`). +fn try_expand_local_const_array<'a>( + el: &Expression<'a>, + semantic: &Semantic<'a>, + eval: &EvalInput<'a, '_>, + visited: &mut std::collections::HashSet, +) -> Option> { + use super::class_data::IsolatedTypeElementData; + use oxc_ast::ast::{ArrayExpressionElement, BindingPattern}; + use oxc_ast::AstKind; + use oxc_semantic::SymbolFlags; + + let mut current = super::utils::unwrap_expression(el); + while let Some(unwrapped) = super::utils::unwrap_forward_ref(current, semantic) { + current = super::utils::unwrap_expression(unwrapped); + } + + let mut visited_chain = Vec::new(); + let arr = loop { + let Expression::Identifier(ident) = current else { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + }; + let Some(ref_id) = ident.reference_id.get() else { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + }; + let scoping = semantic.scoping(); + let Some(symbol_id) = scoping.get_reference(ref_id).symbol_id() else { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + }; + if scoping + .symbol_flags(symbol_id) + .contains(SymbolFlags::Import) + { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + } + let AstKind::VariableDeclarator(declarator) = + semantic.symbol_declaration(symbol_id).kind() + else { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + }; + if !matches!(declarator.id, BindingPattern::BindingIdentifier(_)) { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + } + let Some(init) = &declarator.init else { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + }; + if !visited.insert(symbol_id) { + for sym in visited_chain { + visited.remove(&sym); + } + return None; + } + visited_chain.push(symbol_id); + + let mut next = super::utils::unwrap_expression(init); + while let Some(unwrapped) = super::utils::unwrap_forward_ref(next, semantic) { + next = super::utils::unwrap_expression(unwrapped); + } + if let Expression::ArrayExpression(arr) = next { + break arr; + } + current = next; + }; + + let mut elements = Vec::with_capacity(arr.elements.len()); + let mut valid = !arr.elements.is_empty(); + if valid { + for elem in &arr.elements { + match elem { + ArrayExpressionElement::SpreadElement(spread) => { + if let Some(spread_elements) = + try_expand_local_const_array(&spread.argument, semantic, eval, visited) + { + elements.extend(spread_elements); + } else { + valid = false; + break; + } + } + ArrayExpressionElement::Elision(_) => { + valid = false; + break; + } + _ => { + let Some(el_expr) = elem.as_expression() else { + valid = false; + break; + }; + let classified = + classify_isolated_element_with_visited(el_expr, semantic, eval, visited); + if matches!(classified, IsolatedTypeElementData::Never { .. }) { + valid = false; + break; + } + elements.push(classified); + } + } + } + } + + for sym in visited_chain { + visited.remove(&sym); + } + + (valid && !elements.is_empty()).then_some(elements) } /// The syntactic fallback (`transformToTypeTupleElement`). @@ -918,7 +1077,7 @@ mod tests { let source = r#" import {NgModule, forwardRef} from '@angular/core'; - import {AveLoggingModule} from '@external/ave-logging'; + import {LoggingModule} from '@external/logging'; import {FooModule, provideFoo} from './foo'; import {isDev} from './config'; @@ -927,7 +1086,7 @@ mod tests { function getLocalImports() { return [LocalModule]; } - const NG_MODULE_IMPORTS = [AveLoggingModule]; + const NG_MODULE_IMPORTS = [LoggingModule]; @NgModule({ imports: [ @@ -958,21 +1117,42 @@ mod tests { other => other.clone(), }; - // 0: `NG_MODULE_IMPORTS` (local const of an import) -> Deferred, fallback `typeof NG_MODULE_IMPORTS` + // 0: `NG_MODULE_IMPORTS` (local const of an import) -> Deferred, fallback Tuple([LoggingModule]) match &isolated_imports.elements[0] { IsolatedTypeElementData::Deferred { resolved, fallback } => { assert!(resolved.contains_incomplete()); match fallback.as_ref() { - IsolatedTypeElementData::Typeof { - span: Some(span), - reference: None, + IsolatedTypeElementData::Tuple { + elements, + fallback: outer_fallback, } => { - assert_eq!( - &source[span.start as usize..span.end as usize], - "NG_MODULE_IMPORTS" - ); + assert_eq!(elements.len(), 1); + match settled(&elements[0]) { + IsolatedTypeElementData::Typeof { + span: Some(span), + reference: None, + } => { + assert_eq!( + &source[span.start as usize..span.end as usize], + "LoggingModule" + ); + } + other => panic!("Expected Typeof LoggingModule, got {other:?}"), + } + match outer_fallback.as_ref() { + IsolatedTypeElementData::Typeof { + span: Some(span), + reference: None, + } => { + assert_eq!( + &source[span.start as usize..span.end as usize], + "NG_MODULE_IMPORTS" + ); + } + other => panic!("Expected outer Typeof fallback, got {other:?}"), + } } - other => panic!("Expected Typeof span fallback, got {other:?}"), + other => panic!("Expected Tuple fallback, got {other:?}"), } } other => panic!("Expected Deferred for NG_MODULE_IMPORTS, got {other:?}"), diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs index dbc3d5294d0c..87d907f7e2bd 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs @@ -1341,10 +1341,22 @@ fn test_isolated_type_tuple_resolves_local_const_of_unresolved_imports() { "optimize={optimize}" ); - // 1: `WITH_CALL` (`[ExternalModuleA.forRoot()]`) -> falls back to `typeof WITH_CALL` - assert_eq!(edge.elements[1].kind, IsolatedTypeElementKind::Typeof); + // 1: `WITH_CALL` (`[ExternalModuleA.forRoot()]`) -> expands to ReferenceTuple([CallReturnType]) + assert_eq!( + edge.elements[1].kind, + IsolatedTypeElementKind::ReferenceTuple + ); assert!(edge.elements[1].references.is_none(), "optimize={optimize}"); - assert_eq!(span_text(&edge.elements[1]), "WITH_CALL"); + let call_elements = edge.elements[1] + .elements + .as_ref() + .expect("WITH_CALL expanded elements"); + assert_eq!(call_elements.len(), 1); + assert_eq!( + call_elements[0].kind, + IsolatedTypeElementKind::CallReturnType + ); + assert_eq!(span_text(&call_elements[0]), "ExternalModuleA.forRoot"); // 2: `WITH_SPREAD` (`[...EXTERNAL_ARRAY]`) -> falls back to `typeof WITH_SPREAD` assert_eq!(edge.elements[2].kind, IsolatedTypeElementKind::Typeof); diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/types/metadata.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/types/metadata.rs index 3cbe02970f66..24a380971f77 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/types/metadata.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/types/metadata.rs @@ -819,6 +819,10 @@ pub struct IsolatedTypeElementMetadata { /// reference for `Typeof`, or the full list for `ReferenceTuple`). #[serde(skip_serializing_if = "Option::is_none")] pub references: Option>, + /// Nested tuple elements when `kind` is `ReferenceTuple` and the tuple was expanded from a + /// same-file `const` array containing non-reference elements (such as `CallReturnType`). + #[serde(skip_serializing_if = "Option::is_none")] + pub elements: Option>, } #[cfg_attr(feature = "napi", napi(object))] diff --git a/packages/compiler-cli/preprocessor/src/processor.ts b/packages/compiler-cli/preprocessor/src/processor.ts index b984a42bbad3..7342828207aa 100644 --- a/packages/compiler-cli/preprocessor/src/processor.ts +++ b/packages/compiler-cli/preprocessor/src/processor.ts @@ -2895,7 +2895,7 @@ function compileNgModuleDef( return null; } - const elementExprs = tuple.elements.map((el): o.Expression => { + const buildIsolatedTypeElementExpr = (el: nga.IsolatedTypeElementMetadata): o.Expression => { switch (el.kind) { case 'typeof': { if (el.references && el.references.length > 0) { @@ -2918,6 +2918,9 @@ function compileNgModuleDef( return new o.WrappedNodeExpr(new RawSource(`ReturnType`)); } case 'referenceTuple': { + if (el.elements && el.elements.length > 0) { + return o.literalArr(el.elements.map(buildIsolatedTypeElementExpr)); + } return o.literalArr( (el.references ?? []).map((ref) => new o.TypeofExpr(refToTypeExpression(ref))), ); @@ -2936,7 +2939,9 @@ function compileNgModuleDef( return new o.WrappedNodeExpr(new RawSource('never')); } } - }); + }; + + const elementExprs = tuple.elements.map(buildIsolatedTypeElementExpr); return tuple.isArrayLiteral ? o.literalArr(elementExprs) : (elementExprs[0] ?? null); }; diff --git a/packages/compiler-cli/preprocessor/src/types.ts b/packages/compiler-cli/preprocessor/src/types.ts index 730269e57b91..887d6f542c40 100644 --- a/packages/compiler-cli/preprocessor/src/types.ts +++ b/packages/compiler-cli/preprocessor/src/types.ts @@ -703,6 +703,11 @@ export interface IsolatedTypeElementMetadata { * reference for `Typeof`, or the full list for `ReferenceTuple`). */ references?: Array; + /** + * Expanded inner elements for a same-file `const` array that mixes references and call + * expressions (e.g. `RouterModule.forChild(...)`) when `kind` is `ReferenceTuple`. + */ + elements?: Array; } export interface IsolatedTypeTupleMetadata { diff --git a/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts b/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts index 6c6c4876b5c0..0acb1e0c4fbe 100644 --- a/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts +++ b/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts @@ -151,4 +151,81 @@ describe('isolated NgModule imports under emitDeclarationOnly', () => { ); } }); + + it('expands same-file unexported const arrays mixing classes and call expressions without triggering TS9017', async () => { + for (const optimize of [false, true]) { + const outputs = await compileFiles( + { + '/test.ts': ` + import {Component, NgModule} from '@angular/core'; + import {RouterModule} from '@angular/router'; + import {FeaturePage} from './feature-page'; + + @Component({ + selector: 'shared-widget', + template: 'widget', + }) + export class SharedWidget {} + + const MODULE_IMPORTS = [ + FeaturePage, + RouterModule.forChild([{path: '', component: FeaturePage}]), + SharedWidget, + ]; + + @NgModule({ + imports: [MODULE_IMPORTS], + exports: [MODULE_IMPORTS], + }) + export class FeatureModule {} + + @NgModule({ + imports: MODULE_IMPORTS, + exports: MODULE_IMPORTS, + }) + export class DirectFeatureModule {} + `, + }, + { + optimize, + compilerOptions: { + declaration: true, + emitDeclarationOnly: true, + isolatedDeclarations: true, + }, + angularCompilerOptions: { + _experimentalAllowEmitDeclarationOnly: true, + }, + }, + ); + + const content = outputs.get('/out/test.ts')!; + expect(content).toContain( + 'static ɵmod: i0.ɵɵNgModuleDeclaration, typeof SharedWidget]], [[typeof FeaturePage, ReturnType, typeof SharedWidget]]>', + ); + expect(content).toContain( + 'static ɵmod: i0.ɵɵNgModuleDeclaration, typeof SharedWidget], [typeof FeaturePage, ReturnType, typeof SharedWidget]>', + ); + expect(content).not.toContain('typeof MODULE_IMPORTS'); + + const dtsResult = ts.transpileDeclaration(content, { + fileName: 'test.ts', + compilerOptions: { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + declaration: true, + emitDeclarationOnly: true, + isolatedDeclarations: true, + }, + }); + expect(dtsResult.diagnostics ?? []).toEqual([]); + expect(dtsResult.outputText).toContain( + 'static ɵmod: i0.ɵɵNgModuleDeclaration, typeof SharedWidget]], [[typeof FeaturePage, ReturnType, typeof SharedWidget]]>;', + ); + expect(dtsResult.outputText).toContain( + 'static ɵmod: i0.ɵɵNgModuleDeclaration, typeof SharedWidget], [typeof FeaturePage, ReturnType, typeof SharedWidget]>;', + ); + } + }); }); + From 1ab73090b50e1fd80febb9413d8fab6b44cfec81 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 2 Nov 2026 11:08:44 +0100 Subject: [PATCH 5/6] fix(compiler-cli): add explicit type to propDecorators Adds an explicit type to `propDecorators` so it doesn't raise errors when isolated declarations are enabled. --- .../preprocessor/src/processor.ts | 2 +- .../golden/jit_signal_transforms/golden.md | 4 +- .../test/isolated_ngmodule_imports_spec.ts | 1 - .../test/jit_downlevel_decorators_spec.ts | 94 ++++++++++++++++++- 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/packages/compiler-cli/preprocessor/src/processor.ts b/packages/compiler-cli/preprocessor/src/processor.ts index 7342828207aa..f001f64d65ef 100644 --- a/packages/compiler-cli/preprocessor/src/processor.ts +++ b/packages/compiler-cli/preprocessor/src/processor.ts @@ -527,7 +527,7 @@ export function processFileSync( s.appendLeft( span.end - 1, // ts-ignore is necessary to suppress errors if the base class has `propDecorators`. - `\n ${nocollapse}// @ts-ignore\n static propDecorators = ${printer.print(propExpr)};\n`, + `\n ${nocollapse}// @ts-ignore\n static propDecorators${printer.emitTypes ? ': any' : ''} = ${printer.print(propExpr)};\n`, ); } } diff --git a/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md b/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md index dbb8f19981a9..ccda7954ea3f 100644 --- a/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md +++ b/packages/compiler-cli/preprocessor/test/golden/jit_signal_transforms/golden.md @@ -81,7 +81,7 @@ export class JitComponent { ); // @ts-ignore - static propDecorators = { + static propDecorators: any = { sigInput: [{ type: i0.Input, args: [{ isSignal: true, alias: 'sigInput', required: false }] }], reqInput: [{ type: i0.Input, args: [{ isSignal: true, alias: 'reqInput', required: true }] }], aliasedInput: [ @@ -126,7 +126,7 @@ export class JitDirective { ); // @ts-ignore - static propDecorators = { + static propDecorators: any = { dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: 'dirInput', required: false }] }], dirOutput: [{ type: i0.Output, args: ['dirOutput'] }], dirModel: [ diff --git a/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts b/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts index 0acb1e0c4fbe..616ccf7a3fb5 100644 --- a/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts +++ b/packages/compiler-cli/preprocessor/test/isolated_ngmodule_imports_spec.ts @@ -228,4 +228,3 @@ describe('isolated NgModule imports under emitDeclarationOnly', () => { } }); }); - diff --git a/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts b/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts index 68ddebcbe532..5a561da8f6e1 100644 --- a/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts +++ b/packages/compiler-cli/preprocessor/test/jit_downlevel_decorators_spec.ts @@ -53,7 +53,7 @@ describe('jit: true Angular member and constructor parameter decorator downlevel expect(content).not.toContain('@Output()'); expect(content).toContain('@CustomProp()'); expect(content).toContain("@Input() 'string-literal-input' = 123;"); - expect(content).toContain('// @ts-ignore\n static propDecorators = {'); + expect(content).toContain('// @ts-ignore\n static propDecorators: any = {'); expect(content).not.toContain('string-literal-input:'); const gsMatches = [...content.matchAll(/\bgs:\s*\[/g)]; expect(gsMatches.length).toBe(1); @@ -111,7 +111,9 @@ describe('jit: true Angular member and constructor parameter decorator downlevel const content = outputs.get('/out/test.ts')!; expect([...content.matchAll(/\/\/ @ts-ignore\n static ctorParameters =/g)].length).toBe(2); - expect([...content.matchAll(/\/\/ @ts-ignore\n static propDecorators =/g)].length).toBe(2); + expect([...content.matchAll(/\/\/ @ts-ignore\n static propDecorators: any =/g)].length).toBe( + 2, + ); const diagnostics = typecheckEmittedTs(content, {noImplicitOverride: true}); expect( @@ -149,7 +151,9 @@ describe('jit: true Angular member and constructor parameter decorator downlevel expect(content).toContain( '/** @nocollapse */\n // @ts-ignore\n static ctorParameters = (): any => [', ); - expect(content).toContain('/** @nocollapse */\n // @ts-ignore\n static propDecorators = {'); + expect(content).toContain( + '/** @nocollapse */\n // @ts-ignore\n static propDecorators: any = {', + ); const transpiled = ts.transpileModule(content, { compilerOptions: { @@ -229,7 +233,7 @@ describe('jit: true Angular member and constructor parameter decorator downlevel expect(content).not.toContain('@i0.Output'); expect(content).not.toContain('@i0.ViewChild'); expect(content).not.toContain('@i0.ContentChild'); - expect(content).toContain('static propDecorators = {'); + expect(content).toContain('static propDecorators: any = {'); expect(content).toContain('legacyInput: [{type: Input}]'); expect(content).toContain( "sigInput: [{type: i0.Input, args: [{isSignal: true, alias: 'sigInput', required: true}]}]", @@ -245,6 +249,87 @@ describe('jit: true Angular member and constructor parameter decorator downlevel "projected: [{type: i0.ContentChild, args: ['proj', {isSignal: true}]}]", ); }); + + it('emits isolatedDeclarations-compatible static fields on exported jit: true classes without TS9017 or TS9007', async () => { + const outputs = await compileFiles( + { + '/test.ts': ` + import {Component, Input, Inject, Optional, ViewChild} from '@angular/core'; + + @Component({ + selector: 'jit-isolated-cmp', + template: '', + jit: true, + }) + export class JitIsolatedCmp { + @ViewChild('container', {static: true}) container: any; + @Input() label = ''; + + constructor(@Optional() @Inject('TOKEN') public token: string) {} + } + `, + }, + { + compilerOptions: { + declaration: true, + isolatedDeclarations: true, + }, + }, + ); + + const content = outputs.get('/out/test.ts')!; + const dtsResult = ts.transpileDeclaration(content, { + fileName: 'test.ts', + compilerOptions: { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + declaration: true, + emitDeclarationOnly: true, + isolatedDeclarations: true, + }, + }); + expect(dtsResult.diagnostics ?? []).toEqual([]); + expect(dtsResult.outputText).toContain('static ctorParameters: () => any;'); + expect(dtsResult.outputText).toContain('static propDecorators: any;'); + }); + + it('does not trigger TS2417 when a jit: true subclass declares different member decorators than its jit: true base class', async () => { + const outputs = await compileFiles({ + '/test.ts': ` + import {Component, Input, ViewChild, Inject, Optional} from '@angular/core'; + + @Component({ + selector: 'base-cmp', + template: '', + jit: true, + }) + export class BaseCmp { + @ViewChild('container', {static: true}) container: any; + constructor(@Optional() @Inject('BASE_TOKEN') public baseToken: string) {} + } + + @Component({ + selector: 'sub-cmp', + template: '', + jit: true, + }) + export class SubCmp extends BaseCmp { + @Input() label = ''; + constructor(@Optional() @Inject('BASE_TOKEN') baseToken: string, public count: number) { + super(baseToken); + } + } + `, + }); + + const content = outputs.get('/out/test.ts')!; + const diagnostics = typecheckEmittedTs(content, {noImplicitOverride: true}); + expect( + diagnostics.map( + (d) => `TS${d.code}: ${ts.flattenDiagnosticMessageText(d.messageText, '\n')}`, + ), + ).toEqual([]); + }); }); function makeTsconfig( @@ -329,6 +414,7 @@ function typecheckEmittedTs( export function Directive(meta: any): ClassDecorator; export function Input(alias?: string): any; export function Output(alias?: string): any; + export function ViewChild(selector: any, opts?: any): any; export function Inject(token: any): ParameterDecorator; export function Optional(): ParameterDecorator; } From 2d12a5e8f457f183545fa14364b6f8982ec21452 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 2 Nov 2026 12:53:40 +0100 Subject: [PATCH 6/6] refactor(compiler-cli): follow up on feedback Addresses the following feedback from #71767: * Avoid expanding mutable (`let`/`var`) local array declarations in isolated NgModule type tuples. * Abort outer `const` array expansion when a nested local variable reference cannot be expanded, preventing `typeof INNER` from leaking into emitted types. * Expand local `const` arrays before partial evaluation to avoid duplicate `Deferred` completion passes and ensure fallback references are always collected. --- .../ng-analyze/src/analyzer/class_data.rs | 40 ++++- .../ng-analyze/src/analyzer/ngmodule.rs | 158 +++++++++++------- .../ng-analyze/src/compiler/tests.rs | 35 +++- .../preprocessor/src/processor.ts | 2 + 4 files changed, 166 insertions(+), 69 deletions(-) diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs index 42baf64962ae..4d0ae09ae702 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/class_data.rs @@ -1666,9 +1666,8 @@ impl IsolatedTypeElementData { ResolvedIsolatedElement::from_value(resolved.raw(), resolved.origin) { out.extend(element.references()); - } else { - fallback.collect_references(out); } + fallback.collect_references(out); } _ => {} } @@ -4665,7 +4664,25 @@ impl ClassData { IsolatedTypeElementData::Tuple { elements, fallback } => { let projected: Vec<_> = elements .iter() - .map(|el| self.to_wire_isolated_element(el, current_file_path, cx)) + .map(|el| { + if let IsolatedTypeElementData::Deferred { resolved, .. } = el { + if let Some(local_name) = + unresolved_local_import_name(resolved.raw(), self.reference_id.file) + { + return IsolatedTypeElementMetadata { + kind: IsolatedTypeElementKind::Typeof, + span: None, + references: Some(vec![ + ReferenceMetadata::for_local_declaration( + local_name.to_string(), + ), + ]), + elements: None, + }; + } + } + self.to_wire_isolated_element(el, current_file_path, cx) + }) .collect(); if projected .iter() @@ -4673,6 +4690,23 @@ impl ClassData { { return self.to_wire_isolated_element(fallback, current_file_path, cx); } + if projected.iter().all(|el| { + matches!(el.kind, IsolatedTypeElementKind::Typeof) + && el.span.is_none() + && el.elements.is_none() + && el.references.as_ref().is_some_and(|r| r.len() == 1) + }) { + let references = projected + .into_iter() + .flat_map(|el| el.references.into_iter().flatten()) + .collect(); + return IsolatedTypeElementMetadata { + kind: IsolatedTypeElementKind::ReferenceTuple, + span: None, + references: Some(references), + elements: None, + }; + } IsolatedTypeElementMetadata { kind: IsolatedTypeElementKind::ReferenceTuple, span: None, diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs index 4e3afa4b29dd..360ba5c23523 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/analyzer/ngmodule.rs @@ -371,7 +371,7 @@ fn classify_isolated_element<'a>( eval: &EvalInput<'a, '_>, ) -> super::class_data::IsolatedTypeElementData { let mut visited = std::collections::HashSet::new(); - classify_isolated_element_with_visited(el, semantic, eval, &mut visited) + classify_isolated_element_with_visited(el, semantic, eval, &mut visited, false) } fn classify_isolated_element_with_visited<'a>( @@ -379,11 +379,32 @@ fn classify_isolated_element_with_visited<'a>( semantic: &Semantic<'a>, eval: &EvalInput<'a, '_>, visited: &mut std::collections::HashSet, + inside_expanded_tuple: bool, ) -> super::class_data::IsolatedTypeElementData { use super::class_data::IsolatedTypeElementData; use crate::evaluator::ResolvedValue; use crate::types::analysis::Reference; + let local_var_const = local_variable_constness(el, semantic); + let fallback = |el: &Expression<'a>| { + if inside_expanded_tuple && local_var_const.is_some() { + IsolatedTypeElementData::Never { span: el.span() } + } else { + syntactic_isolated_element(el, semantic) + } + }; + + if local_var_const == Some(false) { + return fallback(el); + } + + if let Some(elements) = try_expand_local_const_array(el, semantic, eval, visited) { + return IsolatedTypeElementData::Tuple { + elements, + fallback: Box::new(fallback(el)), + }; + } + // `transformToTypeTupleExpression` calls `evaluator.evaluate(el)` without foreign function // resolvers (relying on the syntactic `unwrap_forward_ref` loop below for top-level // `forwardRef`s). @@ -395,7 +416,7 @@ fn classify_isolated_element_with_visited<'a>( if resolved.contains_incomplete() { return IsolatedTypeElementData::Deferred { resolved: Resolved::from_syntax(resolved, eval.file), - fallback: Box::new(fallback_isolated_element(el, semantic, eval, visited)), + fallback: Box::new(fallback(el)), }; } match resolved.unwrap_named() { @@ -426,30 +447,12 @@ fn classify_isolated_element_with_visited<'a>( _ => {} } - fallback_isolated_element(el, semantic, eval, visited) -} - -fn fallback_isolated_element<'a>( - el: &Expression<'a>, - semantic: &Semantic<'a>, - eval: &EvalInput<'a, '_>, - visited: &mut std::collections::HashSet, -) -> super::class_data::IsolatedTypeElementData { - use super::class_data::IsolatedTypeElementData; - - let syntactic = syntactic_isolated_element(el, semantic); - if let Some(elements) = try_expand_local_const_array(el, semantic, eval, visited) { - return IsolatedTypeElementData::Tuple { - elements, - fallback: Box::new(syntactic), - }; - } - syntactic + fallback(el) } -/// If `el` resolves to a same-file variable initialized to a non-empty array literal, classify its -/// elements individually so arrays mixing references and `ModuleWithProviders` calls (e.g. -/// `RouterModule.forChild(...)`) can expand instead of falling back to `typeof CONST_NAME` +/// If `el` resolves to a same-file `const` variable initialized to a non-empty array literal, +/// classify its elements individually so arrays mixing references and `ModuleWithProviders` calls +/// (e.g. `RouterModule.forChild(...)`) can expand instead of falling back to `typeof CONST_NAME` /// (which triggers TS9017 under `--isolatedDeclarations`). fn try_expand_local_const_array<'a>( el: &Expression<'a>, @@ -488,10 +491,8 @@ fn try_expand_local_const_array<'a>( } return None; }; - if scoping - .symbol_flags(symbol_id) - .contains(SymbolFlags::Import) - { + let flags = scoping.symbol_flags(symbol_id); + if flags.contains(SymbolFlags::Import) || !flags.contains(SymbolFlags::ConstVariable) { for sym in visited_chain { visited.remove(&sym); } @@ -559,8 +560,9 @@ fn try_expand_local_const_array<'a>( valid = false; break; }; - let classified = - classify_isolated_element_with_visited(el_expr, semantic, eval, visited); + let classified = classify_isolated_element_with_visited( + el_expr, semantic, eval, visited, true, + ); if matches!(classified, IsolatedTypeElementData::Never { .. }) { valid = false; break; @@ -578,6 +580,40 @@ fn try_expand_local_const_array<'a>( (valid && !elements.is_empty()).then_some(elements) } +/// Returns `Some(is_const)` when `el` (after unwrapping parentheses, type assertions, and +/// `forwardRef`) is an identifier referencing a same-file variable declaration +/// (`const`/`let`/`var`). Inside an expanded `const` array, such an identifier must either +/// evaluate to a reference/array or expand into a nested `Tuple`; falling back to `typeof IDENT` +/// would leak `TS9017` under `--isolatedDeclarations`. +fn local_variable_constness<'a>(el: &Expression<'a>, semantic: &Semantic<'a>) -> Option { + use oxc_ast::AstKind; + use oxc_semantic::SymbolFlags; + + let mut current = super::utils::unwrap_expression(el); + while let Some(unwrapped) = super::utils::unwrap_forward_ref(current, semantic) { + current = super::utils::unwrap_expression(unwrapped); + } + let Expression::Identifier(ident) = current else { + return None; + }; + let Some(ref_id) = ident.reference_id.get() else { + return None; + }; + let scoping = semantic.scoping(); + let Some(symbol_id) = scoping.get_reference(ref_id).symbol_id() else { + return None; + }; + let flags = scoping.symbol_flags(symbol_id); + if flags.contains(SymbolFlags::Import) { + return None; + } + matches!( + semantic.symbol_declaration(symbol_id).kind(), + AstKind::VariableDeclarator(_) + ) + .then(|| flags.contains(SymbolFlags::ConstVariable)) +} + /// The syntactic fallback (`transformToTypeTupleElement`). fn syntactic_isolated_element<'a>( el: &Expression<'a>, @@ -1117,45 +1153,39 @@ mod tests { other => other.clone(), }; - // 0: `NG_MODULE_IMPORTS` (local const of an import) -> Deferred, fallback Tuple([LoggingModule]) + // 0: `NG_MODULE_IMPORTS` (local const of an import) -> Tuple([Deferred(LoggingModule)]) match &isolated_imports.elements[0] { - IsolatedTypeElementData::Deferred { resolved, fallback } => { - assert!(resolved.contains_incomplete()); - match fallback.as_ref() { - IsolatedTypeElementData::Tuple { - elements, - fallback: outer_fallback, + IsolatedTypeElementData::Tuple { + elements, + fallback: outer_fallback, + } => { + assert_eq!(elements.len(), 1); + match settled(&elements[0]) { + IsolatedTypeElementData::Typeof { + span: Some(span), + reference: None, } => { - assert_eq!(elements.len(), 1); - match settled(&elements[0]) { - IsolatedTypeElementData::Typeof { - span: Some(span), - reference: None, - } => { - assert_eq!( - &source[span.start as usize..span.end as usize], - "LoggingModule" - ); - } - other => panic!("Expected Typeof LoggingModule, got {other:?}"), - } - match outer_fallback.as_ref() { - IsolatedTypeElementData::Typeof { - span: Some(span), - reference: None, - } => { - assert_eq!( - &source[span.start as usize..span.end as usize], - "NG_MODULE_IMPORTS" - ); - } - other => panic!("Expected outer Typeof fallback, got {other:?}"), - } + assert_eq!( + &source[span.start as usize..span.end as usize], + "LoggingModule" + ); + } + other => panic!("Expected Typeof LoggingModule, got {other:?}"), + } + match outer_fallback.as_ref() { + IsolatedTypeElementData::Typeof { + span: Some(span), + reference: None, + } => { + assert_eq!( + &source[span.start as usize..span.end as usize], + "NG_MODULE_IMPORTS" + ); } - other => panic!("Expected Tuple fallback, got {other:?}"), + other => panic!("Expected outer Typeof fallback, got {other:?}"), } } - other => panic!("Expected Deferred for NG_MODULE_IMPORTS, got {other:?}"), + other => panic!("Expected Tuple for NG_MODULE_IMPORTS, got {other:?}"), } // 1: `FooModule.forRoot()` -> CallReturnType with `FooModule.forRoot` diff --git a/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs b/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs index 87d907f7e2bd..e734541ae37b 100644 --- a/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs +++ b/packages/compiler-cli/preprocessor/ng-analyze/src/compiler/tests.rs @@ -1233,6 +1233,10 @@ fn test_isolated_type_tuple_resolves_local_const_of_unresolved_imports() { const ALL_IMPORTS = [...MODULE_IMPORTS, ExternalModuleA, ResolvedModule]; const WITH_CALL = [ExternalModuleA.forRoot()]; const WITH_SPREAD = [...EXTERNAL_ARRAY]; + let MUTABLE_IMPORTS = [ExternalModuleA, ExternalModuleA.forRoot()]; + const OUTER_WITH_UNEXPANDABLE = [WITH_SPREAD, ExternalModuleA.forRoot()]; + const CYCLE_A: any = [CYCLE_B, ExternalModuleA.forRoot()]; + const CYCLE_B: any = [CYCLE_A]; @NgModule({ imports: [MODULE_IMPORTS], @@ -1241,7 +1245,16 @@ fn test_isolated_type_tuple_resolves_local_const_of_unresolved_imports() { export class LocalConstModule {} @NgModule({ - imports: [ALL_IMPORTS, WITH_CALL, WITH_SPREAD, EXTERNAL_ARRAY, SHARED], + imports: [ + ALL_IMPORTS, + WITH_CALL, + WITH_SPREAD, + EXTERNAL_ARRAY, + SHARED, + MUTABLE_IMPORTS, + OUTER_WITH_UNEXPANDABLE, + CYCLE_A, + ], }) export class EdgeCasesModule {} "#; @@ -1323,7 +1336,7 @@ fn test_isolated_type_tuple_resolves_local_const_of_unresolved_imports() { .as_ref() .expect("EdgeCasesModule isolated_imports"); assert!(edge.is_array_literal); - assert_eq!(edge.elements.len(), 5); + assert_eq!(edge.elements.len(), 8); // 0: `ALL_IMPORTS` (spread of same-file const + duplicate unresolved import + resolved cross-file class) assert_eq!( @@ -1372,6 +1385,24 @@ fn test_isolated_type_tuple_resolves_local_const_of_unresolved_imports() { assert_eq!(edge.elements[4].kind, IsolatedTypeElementKind::Typeof); assert!(edge.elements[4].references.is_none(), "optimize={optimize}"); assert_eq!(span_text(&edge.elements[4]), "SHARED"); + + // 5: `MUTABLE_IMPORTS` (`let` declaration) -> falls back to `typeof MUTABLE_IMPORTS` + assert_eq!(edge.elements[5].kind, IsolatedTypeElementKind::Typeof); + assert!(edge.elements[5].references.is_none(), "optimize={optimize}"); + assert!(edge.elements[5].elements.is_none(), "optimize={optimize}"); + assert_eq!(span_text(&edge.elements[5]), "MUTABLE_IMPORTS"); + + // 6: `OUTER_WITH_UNEXPANDABLE` (references local const `WITH_SPREAD` that cannot expand) -> falls back to `typeof OUTER_WITH_UNEXPANDABLE` + assert_eq!(edge.elements[6].kind, IsolatedTypeElementKind::Typeof); + assert!(edge.elements[6].references.is_none(), "optimize={optimize}"); + assert!(edge.elements[6].elements.is_none(), "optimize={optimize}"); + assert_eq!(span_text(&edge.elements[6]), "OUTER_WITH_UNEXPANDABLE"); + + // 7: `CYCLE_A` (cyclic local const reference) -> falls back to `typeof CYCLE_A` + assert_eq!(edge.elements[7].kind, IsolatedTypeElementKind::Typeof); + assert!(edge.elements[7].references.is_none(), "optimize={optimize}"); + assert!(edge.elements[7].elements.is_none(), "optimize={optimize}"); + assert_eq!(span_text(&edge.elements[7]), "CYCLE_A"); } } diff --git a/packages/compiler-cli/preprocessor/src/processor.ts b/packages/compiler-cli/preprocessor/src/processor.ts index f001f64d65ef..bf5522167a94 100644 --- a/packages/compiler-cli/preprocessor/src/processor.ts +++ b/packages/compiler-cli/preprocessor/src/processor.ts @@ -504,6 +504,8 @@ export function processFileSync( if (isJit) { if (!ctx.emitDeclarationOnly) { + // TODO: this was added to avoid invalidating the import graph since we're almost + // always guaranteed to need a core import. Figure out if we still need it. printer.getOrCreateNamespace('@angular/core'); if (constructorParams && constructorParams.length > 0) { for (const param of constructorParams) {