mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
333 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 681a63ceda | |||
| 5f2d98a1b1 | |||
| 6c80778576 | |||
| 3aa45a2fa1 | |||
| 21a4c027e2 | |||
| adbc27cac1 | |||
| 9d9abf238a | |||
| a249d0b857 | |||
| 127023f35e | |||
| 582858568a | |||
| 48eac2320d | |||
| 69dce38e77 | |||
| 1f13a5eb3b | |||
| 29660f2df9 | |||
| 7d67bd3067 | |||
| 1feaa9fd25 | |||
| ddda3558ca | |||
| 3b989ac5bd | |||
| e638b292f2 | |||
| fae3c837e2 | |||
| 47270d551b | |||
| 2fb88a56dd | |||
| 713a14fadd | |||
| 7e933d8486 | |||
| 45c24e4aaf | |||
| ac6e977663 | |||
| b63c2edd76 | |||
| d1cfbb1f92 | |||
| b169eb11db | |||
| b0ab653965 | |||
| 98c9ac8f92 | |||
| 37b921dcde | |||
| d330f94e48 | |||
| e60d016f46 | |||
| 532bd11f41 | |||
| 608a1511ca | |||
| 06f45c3d59 | |||
| 4049d7797d | |||
| c1f4a47457 | |||
| 6cd2028d81 | |||
| d9bb74f950 | |||
| 47467949d8 | |||
| 00de13c909 | |||
| 79a89f7742 | |||
| a9e440d96b | |||
| da0e6b1eee | |||
| 45a788b0b7 | |||
| 2532ffa35e | |||
| 21be258be6 | |||
| d325f9b55f | |||
| aea747ab3b | |||
| 11692c8dab | |||
| 48a1437e77 | |||
| 66dcc691f5 | |||
| 7a6fd427d5 | |||
| 31fb9d0d52 | |||
| b40875a2cc | |||
| 59394ee830 | |||
| 46bafb0b0a | |||
| 9ca9db4f74 | |||
| 9f2726bb43 | |||
| 490786af10 | |||
| 7b13d34a24 | |||
| 99b5417d28 | |||
| bf83801b8c | |||
| 02de7fc731 | |||
| f187c3abf8 | |||
| 7eb264a017 | |||
| b35e7f8bb3 | |||
| 10081a9d7a | |||
| 0e225ce16b | |||
| ccf2c7f40e | |||
| 39a851fa55 | |||
| 91874a7c41 | |||
| 3b06a09df2 | |||
| 3fb696df96 | |||
| 69702b17cc | |||
| de03e93262 | |||
| 44cb2f8131 | |||
| 1db1b3d6b8 | |||
| c18ec3dd26 | |||
| 694fe3faab | |||
| ca5bc0c5bd | |||
| 3d1fd3442e | |||
| 8f6560857c | |||
| dc911c635a | |||
| 42eb096b77 | |||
| 46b088a245 | |||
| 46a2ad39f5 | |||
| c48816e123 | |||
| 7c16e70b20 | |||
| 70591e7de0 | |||
| a21867df6a | |||
| 8d8fb44ec1 | |||
| 6b67cb5976 | |||
| 0e62b30989 | |||
| ca637fe6a9 | |||
| b84e2d3338 | |||
| b9d846dad7 | |||
| 8bb21de452 | |||
| bf4215356e | |||
| b3ebbf48a3 | |||
| 7cbec22a85 | |||
| e3adaeaed3 | |||
| 8f2b0ede59 | |||
| add5c256bc | |||
| d0612d6c6d | |||
| 69b7cf54e0 | |||
| 4875bc3073 | |||
| 2f347ef8fc | |||
| 9d5b1eccff | |||
| 2e7cfcb2ef | |||
| e40a4fa3c7 | |||
| 901c1e1a7f | |||
| 249d0260f9 | |||
| 1391928e0a | |||
| dbf15c8f34 | |||
| 03ac3c299d | |||
| 8f6c326fa6 | |||
| 9db9213687 | |||
| 95b523c7e9 | |||
| 11795f222f | |||
| 97e28411fa | |||
| b7b0052d48 | |||
| 997fe96d3b | |||
| 613dd02746 | |||
| 1825460fc3 | |||
| 3cc40f304d | |||
| bc0bc5716d | |||
| 49c6a579d1 | |||
| 609201527f | |||
| c6b86357f6 | |||
| cd6ecdbcf8 | |||
| f3f4548033 | |||
| fc2f936e2f | |||
| a694d3d7b8 | |||
| bca8026385 | |||
| 157c30e3e0 | |||
| 1ee7ec5204 | |||
| e0ead9212f | |||
| e53f753ee1 | |||
| 07a485fdba | |||
| 88457ab9be | |||
| 47d5341fc5 | |||
| 54a0ea785f | |||
| ad9ba9a95f | |||
| 1701c40f08 | |||
| 6f0042e4a1 | |||
| ac3fc9a794 | |||
| 2f96a348be | |||
| 0365f38492 | |||
| b5d70b86b5 | |||
| 5d19174cba | |||
| e685ed883a | |||
| 7fdb47956b | |||
| 76709d5d6e | |||
| dbd697ca2d | |||
| c0b49867f1 | |||
| cd970541fe | |||
| e16b8a3794 | |||
| 218999025a | |||
| b6dc274e6b | |||
| d5a27b8818 | |||
| 5c866942a1 | |||
| 156f0d0c1b | |||
| fab6462517 | |||
| 9bf60b06c9 | |||
| 5846d134e6 | |||
| 7339e08c8a | |||
| 98ffbb3d05 | |||
| fa4107e413 | |||
| c4b19c4d43 | |||
| a919c6cae3 | |||
| d0fe538c8a | |||
| 8d278390b2 | |||
| 9d730ed0b4 | |||
| b619d6987e | |||
| 0a86b49ab0 | |||
| 4cb07e6a56 | |||
| 981255c77e | |||
| 0843f31474 | |||
| 98ad3afa57 | |||
| e4bed4b0db | |||
| bb8e7cea28 | |||
| 9895e4492f | |||
| 09f2ec0a12 | |||
| 1fa59425db | |||
| 82bd547d23 | |||
| af4e39a0ab | |||
| 3bce676996 | |||
| 21f67b95b5 | |||
| 74d3fbf14b | |||
| 5014bb5b1c | |||
| 004110c48d | |||
| 69ca33f4a4 | |||
| 102ed0c131 | |||
| f75429d8a8 | |||
| c91c37728f | |||
| de68e049e4 | |||
| 2324d9b15a | |||
| b494e9b5f7 | |||
| 5d7e446ce2 | |||
| 00c0876626 | |||
| 9c3aeb99bb | |||
| 582aac49a7 | |||
| 5256016695 | |||
| d2d72e369c | |||
| 641808fa72 | |||
| f859d5c156 | |||
| cbb36fc1ab | |||
| 4d8da40574 | |||
| 614c311a01 | |||
| ed91b03e0f | |||
| 5007970c3d | |||
| c068045d69 | |||
| 9814767d34 | |||
| 93bdbbc812 | |||
| 07607716d4 | |||
| d9c1004a35 | |||
| bb29c8bbd7 | |||
| 8e1d6c71fc | |||
| 98c6c05c6f | |||
| 3b7162d03c | |||
| 3bc28678fa | |||
| 97fd311b6a | |||
| 1853bbb061 | |||
| 3ecf620cc5 | |||
| 36aa3af77b | |||
| adf32746c2 | |||
| 5501e9c8ed | |||
| 389933f079 | |||
| 674aed5984 | |||
| b14c864170 | |||
| 10fce207e0 | |||
| eba3a0ade0 | |||
| 3d48a72193 | |||
| 5d2e243c76 | |||
| 5e9661d69c | |||
| df42d2be16 | |||
| 106917af87 | |||
| b74b5f0c06 | |||
| 4231404065 | |||
| adcdfeca16 | |||
| 4f8c406664 | |||
| 206b7be7f7 | |||
| 804925b114 | |||
| 6a953c6ed4 | |||
| 01cae95596 | |||
| 371ad098d5 | |||
| 73ad6ea647 | |||
| d3366815ee | |||
| 76ec60d73c | |||
| 7bfe81700b | |||
| 3067633bc7 | |||
| be82f282a4 | |||
| 03b35270b0 | |||
| fe9005ce8e | |||
| 03ec620e31 | |||
| 6d3a2af146 | |||
| 223b7857ef | |||
| 5bdae0387f | |||
| 034b32c133 | |||
| 2a89e184e0 | |||
| 9b6865ec03 | |||
| 6059ca8f1f | |||
| 800f6c8ca3 | |||
| 87c594b90b | |||
| f133489a16 | |||
| 09deb24d74 | |||
| f1bc6f895b | |||
| 5f95afb331 | |||
| b30bc4531a | |||
| a6225a6cdd | |||
| 69b20d8391 | |||
| 6089772fe8 | |||
| 1a5f5ee5ce | |||
| 76808cc328 | |||
| 980c64abaa | |||
| df3e9c1661 | |||
| cb7d817edd | |||
| 8645152f8c | |||
| 0979ca0354 | |||
| af20b2a161 | |||
| b5719ba5eb | |||
| c4901d4dfe | |||
| 1682c60938 | |||
| c65ab136bf | |||
| c89be37193 | |||
| f723288adf | |||
| 58e00deb34 | |||
| 57db366522 | |||
| 3e6ee7eaff | |||
| 086b754cef | |||
| 4bd9ba714c | |||
| 3b63082384 | |||
| 25bc810f83 | |||
| 9de30a7b1c | |||
| b1ed7e2b5f | |||
| 6b4357fae4 | |||
| 0bb649b8fa | |||
| 286012fb89 | |||
| 9155f75e36 | |||
| 93410eda0a | |||
| d49e083999 | |||
| e50d239e80 | |||
| e30c60e89f | |||
| 8104fc2126 | |||
| 7accd9d885 | |||
| 334c99f968 | |||
| b1a9d0f4de | |||
| 5d76401ff5 | |||
| de85979648 | |||
| bbc970bb0b | |||
| 87d00d26ff | |||
| 84752069f2 | |||
| d3b46ade94 | |||
| 1828d11d84 | |||
| b8c82fa2f7 | |||
| fa77c9e5b4 | |||
| 5d16c286bf | |||
| 604270619a | |||
| 1b546975f0 | |||
| 16ae748257 | |||
| ecb0f8f161 | |||
| cea3e4b594 | |||
| 448279f57e | |||
| 5558e275ee | |||
| 86216792fd | |||
| cab6c23602 | |||
| 296216cbe1 | |||
| 9af760eb51 | |||
| b16dd6d67f | |||
| 02d613ef9c |
@@ -158,9 +158,8 @@ test:saucelabs --flaky_test_attempts=1
|
||||
|
||||
# --ng_perf will ask the Ivy compiler to produce performance results for each build.
|
||||
build --flag_alias=ng_perf=//packages/compiler-cli:ng_perf
|
||||
# --adev_fast will run adev build/serve in a faster mode, skipping things like prerendering
|
||||
# for local development.
|
||||
build --flag_alias=fast_adev=//adev:fast_build_mode
|
||||
# --prerender_adev will run adev build/serve in a full mode, performing prerendering
|
||||
build --flag_alias=prerender_adev=//adev:prerender_adev
|
||||
|
||||
####################################################
|
||||
# User bazel configuration
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: 'Docs or angular.dev Bug Report'
|
||||
description: Report an issue in Angular's documentation or angular.io application
|
||||
description: Report an issue in Angular's documentation or angular.dev application
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security issue disclosure
|
||||
url: https://angular.io/guide/security#report-issues
|
||||
url: https://angular.dev/best-practices/security#report-issues
|
||||
about: Report a security issue in Angular Framework, CDK, Material, or CLI
|
||||
- name: Angular CLI
|
||||
url: https://github.com/angular/angular-cli/issues/new/choose
|
||||
|
||||
@@ -18,7 +18,7 @@ What kind of change does this PR introduce?
|
||||
- [ ] Build related changes
|
||||
- [ ] CI related changes
|
||||
- [ ] Documentation content changes
|
||||
- [ ] angular.io application / infrastructure changes
|
||||
- [ ] angular.dev application / infrastructure changes
|
||||
- [ ] Other... Please describe:
|
||||
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export async function setupRedirect(deployment: Deployment) {
|
||||
}
|
||||
|
||||
function firebase(cmd: string, cwd?: string) {
|
||||
spawnSync('npx', `-y firebase-tools@latest ${cmd}`.split(' '), {
|
||||
spawnSync('npx', `-y firebase-tools@13.15.1 ${cmd}`.split(' '), {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
shell: true,
|
||||
|
||||
+4029
-2895
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
name: 'Saucelabs legacy test job'
|
||||
description: 'Runs tests against Saucelabs (outside of Bazel)'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Starting Saucelabs tunnel service
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
|
||||
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
|
||||
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
|
||||
# test in all supported browsers on Saucelabs anyway until this job can be removed.
|
||||
- name: Preparing Bazel-generated fixtures required in legacy tests
|
||||
shell: bash
|
||||
run: |
|
||||
yarn bazel build //packages/core/test:downleveled_es5_fixture //packages/common/locales
|
||||
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
|
||||
mkdir -p dist/legacy-test-out/core/test/reflection/
|
||||
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
|
||||
dist/legacy-test-out/core/test/reflection/es5_downleveled_inheritance_fixture.js
|
||||
# Locale files are needed for i18n tests running within Saucelabs. These are added
|
||||
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
|
||||
# can succeed. Note that the base locale and currencies files are checked-in, so
|
||||
# we do not need to re-generate those through Bazel.
|
||||
mkdir -p packages/common/locales/extra
|
||||
cp dist/bin/packages/common/locales/*.ts packages/common/locales
|
||||
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
|
||||
- name: Build bundle of tests to run on Saucelabs
|
||||
shell: bash
|
||||
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
|
||||
- name: Wait and confirm Saucelabs tunnel has connected
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh ready-wait
|
||||
- name: Running tests on Saucelabs.
|
||||
shell: bash
|
||||
run: KARMA_WEB_TEST_MODE=SL_REQUIRED yarn karma start ./karma-js.conf.js --single-run
|
||||
- name: Stop Saucelabs tunnel service
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh stop
|
||||
@@ -4,7 +4,7 @@ description: 'Installs the dependencies using Yarn'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4
|
||||
- uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4
|
||||
with:
|
||||
path: |
|
||||
./node_modules/
|
||||
|
||||
@@ -21,16 +21,16 @@ jobs:
|
||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build //adev:build --config=release
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
run: yarn bazel build //adev:build --prerender_adev --config=release
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
token: '${{secrets.GITHUB_TOKEN}}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
|
||||
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
+30
-41
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: CI (push)
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Install node modules
|
||||
@@ -41,13 +41,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -59,13 +59,13 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -76,30 +76,32 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev in fast mode to ensure it continues to work
|
||||
run: yarn bazel build //adev:build --fast_adev --config=release
|
||||
- name: Run tests
|
||||
run: yarn bazel test //adev:test
|
||||
run: yarn bazel build //adev:build --config=release
|
||||
# TODO: re-enable tests once the next release is shipped
|
||||
# Tests are broken because of https://github.com/angular/angular/issues/54858
|
||||
# - name: Run tests
|
||||
# run: yarn bazel test //adev/...
|
||||
|
||||
publish-snapshots:
|
||||
runs-on:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -111,7 +113,7 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -119,9 +121,9 @@ jobs:
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -152,31 +154,18 @@ jobs:
|
||||
- run: yarn --cwd packages/zone.js/test/typings install --frozen-lockfile --non-interactive
|
||||
- run: yarn --cwd packages/zone.js/test/typings test
|
||||
|
||||
bazel-saucelabs:
|
||||
saucelabs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
env:
|
||||
JOBS: 2
|
||||
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
- name: Run all saucelabs bazel tests
|
||||
run: |
|
||||
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
|
||||
yarn bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
|
||||
- uses: ./.github/actions/saucelabs-legacy
|
||||
|
||||
adev-deploy:
|
||||
needs: [adev]
|
||||
@@ -184,15 +173,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev to ensure it continues to work
|
||||
run: yarn bazel build //adev:build --config=release
|
||||
run: yarn bazel build //adev:build --prerender_adev --config=release
|
||||
- name: Deploy to firebase
|
||||
uses: ./.github/actions/deploy-docs-site
|
||||
with:
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@11bd341a82719997a59039363f716bea76b88459
|
||||
- uses: angular/dev-infra/github-actions/commit-message-based-labels@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Manual jobs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
|
||||
jobs:
|
||||
# Bazel saucelabs job resides in `manual.yml` because it's currently unstable, but
|
||||
# kept as "runnable" for debugging/stabilization effort purposes.
|
||||
bazel-saucelabs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
env:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
- name: Run all saucelabs bazel tests
|
||||
run: |
|
||||
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
|
||||
yarn bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
+24
-55
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Install node modules
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Check code format
|
||||
run: yarn ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
|
||||
- name: Check Package Licenses
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@56966c765e97f463c4795fac85d46543d1e6b342
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
|
||||
|
||||
@@ -47,13 +47,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -65,13 +65,13 @@ jobs:
|
||||
runs-on: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -83,13 +83,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile --network-timeout 100000
|
||||
- name: Run CI tests for framework
|
||||
@@ -105,24 +105,26 @@ jobs:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build adev in fast mode to ensure it continues to work
|
||||
run: yarn bazel build //adev:build --fast_adev --config=release
|
||||
- name: Run tests
|
||||
run: yarn bazel test //adev:test
|
||||
run: yarn bazel build //adev:build --config=release
|
||||
# TODO: re-enable tests once the next release is shipped
|
||||
# Tests are broken because of https://github.com/angular/angular/issues/54858
|
||||
# - name: Run tests
|
||||
# run: yarn bazel test //adev/...
|
||||
|
||||
zone-js:
|
||||
runs-on:
|
||||
labels: ubuntu-latest-4core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
node-module-directories: |
|
||||
@@ -130,9 +132,9 @@ jobs:
|
||||
./packages/zone.js/node_modules
|
||||
./packages/zone.js/test/typings/node_modules
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- run: |
|
||||
@@ -169,42 +171,9 @@ jobs:
|
||||
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
cache-node-modules: true
|
||||
- name: Install node modules
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
- name: Starting Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
||||
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
|
||||
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
|
||||
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
|
||||
# test in all supported browsers on Saucelabs anyway until this job can be removed.
|
||||
- name: Preparing Bazel-generated fixtures required in legacy tests
|
||||
run: |
|
||||
yarn bazel build //packages/core/test:downleveled_es5_fixture //packages/common/locales
|
||||
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
|
||||
mkdir -p dist/legacy-test-out/core/test/reflection/
|
||||
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
|
||||
dist/legacy-test-out/core/test/reflection/es5_downleveled_inheritance_fixture.js
|
||||
# Locale files are needed for i18n tests running within Saucelabs. These are added
|
||||
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
|
||||
# can succeed. Note that the base locale and currencies files are checked-in, so
|
||||
# we do not need to re-generate those through Bazel.
|
||||
mkdir -p packages/common/locales/extra
|
||||
cp dist/bin/packages/common/locales/*.ts packages/common/locales
|
||||
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
|
||||
- name: Build bundle of tests to run on Saucelabs
|
||||
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
|
||||
- name: Wait and confirm Saucelabs tunnel has connected
|
||||
run: ./tools/saucelabs/sauce-service.sh ready-wait
|
||||
timeout-minutes: 3
|
||||
- name: Running tests on Saucelabs.
|
||||
run: KARMA_WEB_TEST_MODE=SL_REQUIRED yarn karma start ./karma-js.conf.js --single-run
|
||||
- name: Stop Saucelabs tunnel service
|
||||
run: ./tools/saucelabs/sauce-service.sh stop
|
||||
- uses: ./.github/actions/saucelabs-legacy
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
# Upload the results as artifacts.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
|
||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
env:
|
||||
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
|
||||
- name: Create a PR (if necessary)
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@03b8a7dffd1205e061f0bee949024ebefc2a6592
|
||||
uses: angular/dev-infra/github-actions/create-pr-for-changes@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
|
||||
with:
|
||||
branch-prefix: update-cli-help
|
||||
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
|
||||
|
||||
+4
-4
@@ -57,7 +57,7 @@
|
||||
version: 3
|
||||
|
||||
availability:
|
||||
users_unavailable: []
|
||||
users_unavailable: ['atscott']
|
||||
|
||||
# Meta field that goes unused by PullApprove to allow for defining aliases to be
|
||||
# used throughout the config.
|
||||
@@ -372,7 +372,7 @@ groups:
|
||||
<<: *defaults
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files.exclude('packages/compiler-cli/*').exclude('packages/language-service/*').exclude('packages/service-worker/*'), [
|
||||
contains_any_globs(files.exclude('packages/compiler-cli/*').exclude('packages/language-service/*').exclude('packages/service-worker/*').exclude('packages/core/schematics/*'), [
|
||||
'packages/**/testing/**/{*,.*}',
|
||||
])
|
||||
reviewers:
|
||||
@@ -613,6 +613,7 @@ groups:
|
||||
- devversion
|
||||
- atscott
|
||||
- pkozlowski-opensource
|
||||
- MarkTechson
|
||||
- ~JeanMeche
|
||||
|
||||
# =========================================================
|
||||
@@ -632,7 +633,7 @@ groups:
|
||||
- dgp1130
|
||||
- josephperrott
|
||||
- mgechev
|
||||
- twerske
|
||||
- MarkTechson
|
||||
- ~JeanMeche
|
||||
|
||||
# =========================================================
|
||||
@@ -827,7 +828,6 @@ groups:
|
||||
- eduhmc # Eduardo Huerta-Mercado
|
||||
- ehlemur # Edward Lesmes
|
||||
- ellenyuan # Ellen Yuan
|
||||
- jatraman # Jatin Ramanathan
|
||||
- jesse-good # Jesse Costello-Good
|
||||
- mturco # Matt Turco
|
||||
- neonstalwart # Ben Hockey
|
||||
|
||||
+1430
-910
File diff suppressed because it is too large
Load Diff
@@ -2,4 +2,4 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
yarn-path ".yarn/releases/yarn-1.22.19.cjs"
|
||||
yarn-path ".yarn/releases/yarn-1.22.22.cjs"
|
||||
|
||||
+2
-2
@@ -64,8 +64,8 @@ config_setting(
|
||||
|
||||
nodejs_binary(
|
||||
name = "yarn_vendored",
|
||||
data = [".yarn/releases/yarn-1.22.19.cjs"],
|
||||
entry_point = ".yarn/releases/yarn-1.22.19.cjs",
|
||||
data = [".yarn/releases/yarn-1.22.22.cjs"],
|
||||
entry_point = ".yarn/releases/yarn-1.22.22.cjs",
|
||||
visibility = [
|
||||
"//integration:__subpackages__",
|
||||
],
|
||||
|
||||
+3406
-2138
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -89,7 +89,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
We cannot accept code without a signed CLA.
|
||||
Make sure you author all contributed Git commits with email address associated with your CLA signature.
|
||||
|
||||
4. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the angular/angular repo.
|
||||
4. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the [angular/angular](https://github.com/angular/angular/fork) repo.
|
||||
|
||||
5. In your forked repository, make your changes in a new git branch:
|
||||
|
||||
@@ -207,7 +207,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
|
||||
* All public API methods **must be documented**.
|
||||
* We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at **100 characters**.
|
||||
|
||||
An automated formatter is available, see [DEVELOPER.md](contributing-docs/building-and-testing-angular#formatting-your-source-code).
|
||||
An automated formatter is available, see [DEVELOPER.md](contributing-docs/building-and-testing-angular.md#formatting-your-source-code).
|
||||
|
||||
|
||||
## <a name="commit"></a> Commit Message Format
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2024 Google LLC. https://angular.io/license
|
||||
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -143,10 +143,10 @@ cldr_xml_data_repository(
|
||||
# sass rules
|
||||
http_archive(
|
||||
name = "io_bazel_rules_sass",
|
||||
sha256 = "cd83736ea65d0df064283aea5922dbaf132dd2b3aa54e7151aae7edaa9572c3e",
|
||||
strip_prefix = "rules_sass-83022b98114c07e9588089c7fe8f76bc0262c7e7",
|
||||
sha256 = "b00f13a280fc60ef2a1d142b31c629075bac841c980a0c3462190f338e5d584a",
|
||||
strip_prefix = "rules_sass-6e23fb1351f392f7fe7a07038cd537770fe97742",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_sass/archive/83022b98114c07e9588089c7fe8f76bc0262c7e7.zip",
|
||||
"https://github.com/bazelbuild/rules_sass/archive/6e23fb1351f392f7fe7a07038cd537770fe97742.zip",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -181,3 +181,15 @@ http_archive(
|
||||
strip_prefix = "sc-4.8.2-osx",
|
||||
url = "https://saucelabs.com/downloads/sc-4.8.2-osx.zip",
|
||||
)
|
||||
|
||||
yarn_install(
|
||||
name = "npm_ts_versions",
|
||||
data = [
|
||||
YARN_LABEL,
|
||||
"//:.yarnrc",
|
||||
],
|
||||
exports_directories_only = False,
|
||||
package_json = "//packages/core/schematics/migrations/signal-migration/test/ts-versions:package.json",
|
||||
yarn = YARN_LABEL,
|
||||
yarn_lock = "//packages/core/schematics/migrations/signal-migration/test/ts-versions:yarn.lock",
|
||||
)
|
||||
|
||||
+16
-20
@@ -1,4 +1,5 @@
|
||||
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
|
||||
load("@npm//@angular/build-tooling/bazel/http-server:index.bzl", "http_server")
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect", "architect_test")
|
||||
@@ -113,27 +114,27 @@ copy_to_bin(
|
||||
)
|
||||
|
||||
bool_flag(
|
||||
name = "fast_build_mode",
|
||||
name = "prerender_adev",
|
||||
build_setting_default = False,
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "fast",
|
||||
name = "no_prerender",
|
||||
flag_values = {
|
||||
":fast_build_mode": "true",
|
||||
":prerender_adev": "false",
|
||||
},
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "full",
|
||||
name = "prerender",
|
||||
flag_values = {
|
||||
":fast_build_mode": "false",
|
||||
":prerender_adev": "true",
|
||||
},
|
||||
)
|
||||
|
||||
config_based_architect_flags = select({
|
||||
":fast": ["--no-prerender"],
|
||||
":full": ["--prerender"],
|
||||
":no_prerender": ["--no-prerender"],
|
||||
":prerender": ["--prerender"],
|
||||
})
|
||||
|
||||
architect(
|
||||
@@ -146,6 +147,9 @@ architect(
|
||||
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
env = {
|
||||
"NG_BUILD_OPTIMIZE_CHUNKS": "1",
|
||||
},
|
||||
# Network is required to inline fonts.
|
||||
exec_properties = ENABLE_NETWORK,
|
||||
output_dir = True,
|
||||
@@ -154,21 +158,13 @@ architect(
|
||||
],
|
||||
)
|
||||
|
||||
architect(
|
||||
http_server(
|
||||
name = "serve",
|
||||
args = [
|
||||
"angular-dev:serve",
|
||||
"--poll=1000",
|
||||
"--live-reload",
|
||||
"--watch",
|
||||
],
|
||||
chdir = package_name(),
|
||||
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
tags = [
|
||||
"no-remote-exec",
|
||||
additional_root_paths = [
|
||||
"angular/adev/build/browser",
|
||||
],
|
||||
enable_dev_ui = True,
|
||||
deps = [":build"],
|
||||
)
|
||||
|
||||
architect_test(
|
||||
|
||||
@@ -22,6 +22,7 @@ ts_library(
|
||||
"//adev/shared-docs/components/slide-toggle",
|
||||
"//adev/shared-docs/components/table-of-contents",
|
||||
"//adev/shared-docs/components/text-field",
|
||||
"//adev/shared-docs/components/top-level-banner",
|
||||
"//adev/shared-docs/components/viewers",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
import {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core';
|
||||
import {NavigationState} from '../../services/index';
|
||||
import {NavigationItem} from '../../interfaces/index';
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {RouterLink} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-breadcrumb',
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor, RouterLink],
|
||||
imports: [RouterLink],
|
||||
templateUrl: './breadcrumb.component.html',
|
||||
styleUrls: ['./breadcrumb.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
|
||||
import {NgIf} from '@angular/common';
|
||||
import {LOCAL_STORAGE} from '../../providers/index';
|
||||
import {setCookieConsent} from '../../utils';
|
||||
|
||||
@@ -21,7 +20,6 @@ export const STORAGE_KEY = 'docs-accepts-cookies';
|
||||
@Component({
|
||||
selector: 'docs-cookie-popup',
|
||||
standalone: true,
|
||||
imports: [NgIf],
|
||||
templateUrl: './cookie-popup.component.html',
|
||||
styleUrls: ['./cookie-popup.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
@@ -18,6 +18,7 @@ ng_module(
|
||||
"//adev/shared-docs/components/navigation-list:__pkg__",
|
||||
"//adev/shared-docs/components/table-of-contents:__pkg__",
|
||||
"//adev/shared-docs/components/text-field:__pkg__",
|
||||
"//adev/shared-docs/components/top-level-banner:__pkg__",
|
||||
"//adev/shared-docs/components/viewers:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
|
||||
@@ -15,3 +15,4 @@ export * from './table-of-contents/table-of-contents.component';
|
||||
export * from './text-field/text-field.component';
|
||||
export * from './icon/icon.component';
|
||||
export * from './search-dialog/search-dialog.component';
|
||||
export * from './top-level-banner/top-level-banner.component';
|
||||
|
||||
@@ -22,6 +22,7 @@ ng_module(
|
||||
"//adev/shared-docs/interfaces",
|
||||
"//adev/shared-docs/pipes",
|
||||
"//adev/shared-docs/services",
|
||||
"//packages/common",
|
||||
"//packages/core",
|
||||
"//packages/forms",
|
||||
"//packages/router",
|
||||
@@ -42,6 +43,7 @@ ts_library(
|
||||
deps = [
|
||||
":search-dialog",
|
||||
"//adev/shared-docs/components/algolia-icon",
|
||||
"//adev/shared-docs/interfaces",
|
||||
"//adev/shared-docs/providers",
|
||||
"//adev/shared-docs/services",
|
||||
"//adev/shared-docs/testing",
|
||||
|
||||
@@ -10,65 +10,62 @@
|
||||
></docs-text-field>
|
||||
|
||||
@if (searchResults() && searchResults()!.length > 0) {
|
||||
<ul class="docs-search-results docs-mini-scroll-track">
|
||||
@for (result of searchResults(); track result.objectID) {
|
||||
<li docsSearchItem [item]="result">
|
||||
@if (result.url) {
|
||||
<a [routerLink]="'/' + result.url | relativeLink: 'pathname'" [fragment]="result.url | relativeLink: 'hash'">
|
||||
<div>
|
||||
<div class="docs-result-icon-and-type">
|
||||
<!-- Icon -->
|
||||
<span class="docs-search-result-icon" aria-hidden="true">
|
||||
@if (result.hierarchy?.lvl0 === 'Docs') {
|
||||
<i role="presentation" class="material-symbols-outlined docs-icon-small">
|
||||
description
|
||||
</i>
|
||||
} @else if (result.hierarchy?.lvl0 === 'Tutorials') {
|
||||
<i role="presentation" class="material-symbols-outlined docs-icon-small">code</i>
|
||||
} @else if (result.hierarchy?.lvl0 === 'Reference') {
|
||||
<i role="presentation" class="material-symbols-outlined docs-icon-small">
|
||||
description
|
||||
</i>
|
||||
}
|
||||
</span>
|
||||
<!-- Results type -->
|
||||
<span class="docs-search-results__type">{{ result.hierarchy?.lvl1 }}</span>
|
||||
<ul class="docs-search-results docs-mini-scroll-track">
|
||||
@for (result of searchResults(); track result.objectID) {
|
||||
<li docsSearchItem [item]="result">
|
||||
<a
|
||||
[routerLink]="'/' + result.url | relativeLink: 'pathname'"
|
||||
[fragment]="result.url | relativeLink: 'hash'"
|
||||
>
|
||||
<div>
|
||||
<div class="docs-result-icon-and-type">
|
||||
<!-- Icon -->
|
||||
<span class="docs-search-result-icon" aria-hidden="true">
|
||||
<i role="presentation" class="material-symbols-outlined docs-icon-small">
|
||||
{{ result.hierarchy.lvl0 === 'Tutorials' ? 'code' : 'description'}}
|
||||
</i>
|
||||
</span>
|
||||
<!-- Results type -->
|
||||
<span class="docs-search-results__type">
|
||||
@let snippet = result._snippetResult.hierarchy?.lvl1?.value ?? '';
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="highlightSnippet"
|
||||
[ngTemplateOutletContext]="{snippet}"
|
||||
></ng-container>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@let content = result._snippetResult.content;
|
||||
@let hierarchy = result._snippetResult.hierarchy;
|
||||
@if (content || hierarchy?.lvl2 || hierarchy?.lvl3 || hierarchy?.lvl4) {
|
||||
<span class="docs-search-results__type docs-search-results__lvl2">
|
||||
@let snippet = getBestSnippetForMatch(result);
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="highlightSnippet"
|
||||
[ngTemplateOutletContext]="{snippet}"
|
||||
></ng-container>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Hide level 2 if level 3 exists -->
|
||||
<!-- Level 2 -->
|
||||
@if (result.hierarchy?.lvl2 && !result.hierarchy?.lvl3) {
|
||||
<span class="docs-search-results__type docs-search-results__lvl2">
|
||||
{{ result.hierarchy?.lvl2 }}
|
||||
</span>
|
||||
}
|
||||
<!-- Level 3 -->
|
||||
@if (result.hierarchy?.lvl3) {
|
||||
<span class="docs-search-results__type docs-search-results__lvl3">
|
||||
{{ result.hierarchy?.lvl3 }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Page title -->
|
||||
<span class="docs-result-page-title">{{ result.hierarchy?.lvl0 }}</span>
|
||||
</a>
|
||||
<!-- Page title -->
|
||||
<span class="docs-result-page-title">{{ result.hierarchy?.lvl0 }}</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</ul>
|
||||
} @else {
|
||||
<div class="docs-search-results docs-mini-scroll-track">
|
||||
@if (searchResults() === undefined) {
|
||||
<div class="docs-search-results__start-typing">
|
||||
<span>Start typing to see results</span>
|
||||
<div class="docs-search-results docs-mini-scroll-track">
|
||||
@if (searchResults() === undefined) {
|
||||
<div class="docs-search-results__start-typing">
|
||||
<span>Start typing to see results</span>
|
||||
</div>
|
||||
} @else if (searchResults()?.length === 0) {
|
||||
<div class="docs-search-results__no-results">
|
||||
<span>No results found</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else if (searchResults()?.length === 0) {
|
||||
<div class="docs-search-results__no-results">
|
||||
<span>No results found</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="docs-algolia">
|
||||
@@ -79,3 +76,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<ng-template #highlightSnippet let-snippet="snippet">
|
||||
@let parts = splitMarkedText(snippet);
|
||||
@for (part of parts; track $index) {
|
||||
@if (part.highlight) {
|
||||
<mark>{{part.text}}</mark>
|
||||
} @else {
|
||||
<span>{{part.text}}</span>
|
||||
}
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
@@ -48,6 +48,14 @@ dialog {
|
||||
padding-inline-end: 1rem;
|
||||
padding-block: 0.25rem;
|
||||
|
||||
mark {
|
||||
background: #e62600;
|
||||
background: var(--red-to-orange-horizontal-gradient);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--secondary-contrast);
|
||||
display: flex;
|
||||
|
||||
@@ -17,6 +17,7 @@ import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {Router} from '@angular/router';
|
||||
import {provideExperimentalZonelessChangeDetection} from '@angular/core';
|
||||
import {SearchResult} from '../../interfaces';
|
||||
|
||||
describe('SearchDialog', () => {
|
||||
let fixture: ComponentFixture<SearchDialog>;
|
||||
@@ -135,6 +136,9 @@ const fakeSearchResults = [
|
||||
'lvl6': null,
|
||||
},
|
||||
'objectID': 'fakeObjectId1',
|
||||
_snippetResult: {},
|
||||
type: '',
|
||||
content: null,
|
||||
},
|
||||
{
|
||||
'url': 'https://angular.dev/fakeUrl2#h1',
|
||||
@@ -148,5 +152,8 @@ const fakeSearchResults = [
|
||||
'lvl6': null,
|
||||
},
|
||||
'objectID': 'fakeObjectId2',
|
||||
type: '',
|
||||
content: null,
|
||||
_snippetResult: {},
|
||||
},
|
||||
];
|
||||
] satisfies SearchResult[];
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
viewChild,
|
||||
viewChildren,
|
||||
} from '@angular/core';
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
|
||||
import {WINDOW} from '../../providers/index';
|
||||
import {ClickOutside} from '../../directives/index';
|
||||
@@ -34,6 +35,7 @@ import {Router, RouterLink} from '@angular/router';
|
||||
import {filter, fromEvent} from 'rxjs';
|
||||
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
|
||||
import {RelativeLink} from '../../pipes/relative-link.pipe';
|
||||
import {SearchResult, SnippetResult} from '../../interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-search-dialog',
|
||||
@@ -47,6 +49,7 @@ import {RelativeLink} from '../../pipes/relative-link.pipe';
|
||||
AlgoliaIcon,
|
||||
RelativeLink,
|
||||
RouterLink,
|
||||
NgTemplateOutlet,
|
||||
],
|
||||
templateUrl: './search-dialog.component.html',
|
||||
styleUrls: ['./search-dialog.component.scss'],
|
||||
@@ -104,6 +107,46 @@ export class SearchDialog implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
splitMarkedText(snippet: string): Array<{highlight: boolean; text: string}> {
|
||||
const parts: Array<{highlight: boolean; text: string}> = [];
|
||||
while (snippet.indexOf('<ɵ>') !== -1) {
|
||||
const beforeMatch = snippet.substring(0, snippet.indexOf('<ɵ>'));
|
||||
const match = snippet.substring(snippet.indexOf('<ɵ>') + 3, snippet.indexOf('</ɵ>'));
|
||||
parts.push({highlight: false, text: beforeMatch});
|
||||
parts.push({highlight: true, text: match});
|
||||
snippet = snippet.substring(snippet.indexOf('</ɵ>') + 4);
|
||||
}
|
||||
parts.push({highlight: false, text: snippet});
|
||||
return parts;
|
||||
}
|
||||
|
||||
getBestSnippetForMatch(result: SearchResult): string {
|
||||
// if there is content, return it
|
||||
if (result._snippetResult.content !== undefined) {
|
||||
return result._snippetResult.content.value;
|
||||
}
|
||||
|
||||
const hierarchy = result._snippetResult.hierarchy;
|
||||
if (hierarchy === undefined) {
|
||||
return '';
|
||||
}
|
||||
function matched(snippet: SnippetResult | undefined) {
|
||||
return snippet?.matchLevel !== undefined && snippet.matchLevel !== 'none';
|
||||
}
|
||||
// return the most specific subheader match
|
||||
if (matched(hierarchy.lvl4)) {
|
||||
return hierarchy.lvl4!.value;
|
||||
}
|
||||
if (matched(hierarchy.lvl3)) {
|
||||
return hierarchy.lvl3!.value;
|
||||
}
|
||||
if (matched(hierarchy.lvl2)) {
|
||||
return hierarchy.lvl2!.value;
|
||||
}
|
||||
// if no subheader matched the query, fall back to just returning the most specific one
|
||||
return hierarchy.lvl3?.value ?? hierarchy.lvl2?.value ?? '';
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.keyManager.destroy();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {ChangeDetectionStrategy, Component, Input, computed, inject} from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
Input,
|
||||
computed,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {TableOfContentsLevel} from '../../interfaces/index';
|
||||
import {TableOfContentsLoader} from '../../services/table-of-contents-loader.service';
|
||||
@@ -20,7 +26,7 @@ import {IconComponent} from '../icon/icon.component';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './table-of-contents.component.html',
|
||||
styleUrls: ['./table-of-contents.component.scss'],
|
||||
imports: [NgIf, NgFor, RouterLink, IconComponent],
|
||||
imports: [RouterLink, IconComponent],
|
||||
})
|
||||
export class TableOfContents {
|
||||
// Element that contains the content from which the Table of Contents is built
|
||||
@@ -28,6 +34,8 @@ export class TableOfContents {
|
||||
|
||||
private readonly scrollSpy = inject(TableOfContentsScrollSpy);
|
||||
private readonly tableOfContentsLoader = inject(TableOfContentsLoader);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems;
|
||||
|
||||
activeItemId = this.scrollSpy.activeItemId;
|
||||
@@ -36,7 +44,7 @@ export class TableOfContents {
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement);
|
||||
this.scrollSpy.startListeningToScroll(this.contentSourceElement);
|
||||
this.scrollSpy.startListeningToScroll(this.contentSourceElement, this.destroyRef);
|
||||
}
|
||||
|
||||
scrollToTop(): void {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library")
|
||||
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
|
||||
|
||||
package(default_visibility = ["//visibility:private"])
|
||||
|
||||
ng_module(
|
||||
name = "top-level-banner",
|
||||
srcs = [
|
||||
"top-level-banner.component.ts",
|
||||
],
|
||||
assets = [
|
||||
":top-level-banner.component.css",
|
||||
"top-level-banner.component.html",
|
||||
],
|
||||
visibility = [
|
||||
"//adev/shared-docs/components:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//adev/shared-docs/components/icon",
|
||||
"//adev/shared-docs/directives",
|
||||
"//adev/shared-docs/providers",
|
||||
"//packages/common",
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
||||
sass_binary(
|
||||
name = "style",
|
||||
src = "top-level-banner.component.scss",
|
||||
)
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = glob(
|
||||
["*.spec.ts"],
|
||||
),
|
||||
deps = [
|
||||
":top-level-banner",
|
||||
"//adev/shared-docs/providers",
|
||||
"//packages/core",
|
||||
"//packages/core/testing",
|
||||
],
|
||||
)
|
||||
|
||||
karma_web_test_suite(
|
||||
name = "test",
|
||||
deps = [":test_lib"],
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
@if (!hasClosed()) {
|
||||
@if (link()) {
|
||||
<a [href]="link()" class="docs-top-level-banner">
|
||||
<h1 tabindex="-1" class="docs-top-level-banner-cta background">{{ text() }}</h1>
|
||||
<h1 tabindex="0" class="docs-top-level-banner-cta shimmer">{{ text() }}</h1>
|
||||
</a>
|
||||
} @else {
|
||||
<div class="docs-top-level-banner">
|
||||
<h1 tabindex="-1" class="docs-top-level-banner-cta background">{{ text() }}</h1>
|
||||
<h1 tabindex="0" class="docs-top-level-banner-cta shimmer">{{ text() }}</h1>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="docs-top-level-banner-close" type="button" (click)="close()">
|
||||
<docs-icon class="docs-icon_high-contrast">close</docs-icon>
|
||||
</button>
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
:host {
|
||||
&:not(:empty) {
|
||||
z-index: 50;
|
||||
position: fixed;
|
||||
height: 2rem;
|
||||
width: 100vw;
|
||||
border-bottom: 1px solid var(--septenary-contrast);
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
backdrop-filter: blur(16px);
|
||||
background-color: color-mix(in srgb, var(--page-background) 70%, transparent);
|
||||
}
|
||||
|
||||
a.docs-top-level-banner {
|
||||
width: 100%;
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
h1.docs-top-level-banner-cta {
|
||||
display: inline;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
width: fit-content;
|
||||
font-weight: 500;
|
||||
|
||||
&.background {
|
||||
color: var(--tertiary-contrast);
|
||||
}
|
||||
|
||||
&:not(.background) {
|
||||
color: transparent;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
background: var(--tertiary-contrast);
|
||||
transform: scaleX(0);
|
||||
transform-origin: bottom right;
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
transform: scaleX(1);
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.docs-top-level-banner-close {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.5rem;
|
||||
color: var(--tertiary-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
background: var(--red-to-pink-to-purple-horizontal-gradient);
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
background-repeat: no-repeat;
|
||||
-webkit-background-size: 125px 100%;
|
||||
-moz-background-size: 125px 100%;
|
||||
background-size: 125px 100%;
|
||||
-webkit-background-clip: text;
|
||||
-moz-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-animation-name: shimmer;
|
||||
-moz-animation-name: shimmer;
|
||||
animation-name: shimmer;
|
||||
-webkit-animation-duration: 10s;
|
||||
-moz-animation-duration: 10s;
|
||||
animation-duration: 10s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
-moz-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes shimmer {
|
||||
0% {
|
||||
background-position: top left;
|
||||
background-position-x: -150px;
|
||||
}
|
||||
100% {
|
||||
background-position: top right;
|
||||
background-position-x: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes shimmer {
|
||||
0% {
|
||||
background-position: top left;
|
||||
background-position-x: -150px;
|
||||
}
|
||||
100% {
|
||||
background-position: top right;
|
||||
background-position-x: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes shimmer {
|
||||
0% {
|
||||
background-position: top left;
|
||||
background-position-x: -150px;
|
||||
}
|
||||
100% {
|
||||
background-position: top right;
|
||||
background-position-x: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: top left;
|
||||
background-position-x: -150px;
|
||||
}
|
||||
100% {
|
||||
background-position: top right;
|
||||
background-position-x: 500px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {STORAGE_KEY_PREFIX, TopLevelBannerComponent} from './top-level-banner.component';
|
||||
import {LOCAL_STORAGE, WINDOW} from '../../providers';
|
||||
|
||||
describe('TopLevelBannerComponent', () => {
|
||||
let component: TopLevelBannerComponent;
|
||||
let fixture: ComponentFixture<TopLevelBannerComponent>;
|
||||
let mockLocalStorage: jasmine.SpyObj<Storage>;
|
||||
|
||||
const EXAMPLE_TEXT = 'Click Here';
|
||||
const EXAMPLE_LINK = 'https://example.com';
|
||||
const EXAMPLE_ID = 'banner-id';
|
||||
|
||||
beforeEach(async () => {
|
||||
mockLocalStorage = jasmine.createSpyObj('Storage', ['getItem', 'setItem']);
|
||||
|
||||
fixture = TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{provide: LOCAL_STORAGE, useValue: mockLocalStorage},
|
||||
{provide: WINDOW, useValue: {location: {origin: ''}}},
|
||||
],
|
||||
}).createComponent(TopLevelBannerComponent);
|
||||
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.componentRef.setInput('id', EXAMPLE_ID);
|
||||
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should render an anchor element when link is provided', () => {
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.componentRef.setInput('link', EXAMPLE_LINK);
|
||||
fixture.detectChanges();
|
||||
|
||||
const bannerElement = fixture.nativeElement.querySelector('a.docs-top-level-banner');
|
||||
expect(bannerElement).toBeTruthy();
|
||||
expect(bannerElement.getAttribute('href')).toBe(EXAMPLE_LINK);
|
||||
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT);
|
||||
});
|
||||
|
||||
it('should render a div element when link is not provided', () => {
|
||||
const EXAMPLE_TEXT = 'No Link Available';
|
||||
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.detectChanges();
|
||||
|
||||
const bannerElement = fixture.nativeElement.querySelector('div.docs-top-level-banner');
|
||||
expect(bannerElement).toBeTruthy();
|
||||
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT);
|
||||
});
|
||||
|
||||
it('should correctly render the text input', () => {
|
||||
const EXAMPLE_TEXT = 'Lorem ipsum dolor...';
|
||||
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.detectChanges();
|
||||
|
||||
const bannerElement = fixture.nativeElement.querySelector('.docs-top-level-banner-cta');
|
||||
expect(bannerElement).toBeTruthy();
|
||||
expect(bannerElement.textContent).toBe(EXAMPLE_TEXT);
|
||||
});
|
||||
|
||||
it('should set hasClosed to true if the banner was closed before', () => {
|
||||
mockLocalStorage.getItem.and.returnValue('true');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.hasClosed()).toBeTrue();
|
||||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`);
|
||||
});
|
||||
|
||||
it('should set hasClosed to false if the banner was not closed before', () => {
|
||||
mockLocalStorage.getItem.and.returnValue('false');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.hasClosed()).toBeFalse();
|
||||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`);
|
||||
});
|
||||
|
||||
it('should set hasClosed to false if accessing localStorage throws an error', () => {
|
||||
mockLocalStorage.getItem.and.throwError('Local storage error');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.hasClosed()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should set the banner as closed in localStorage and update hasClosed', () => {
|
||||
component.close();
|
||||
|
||||
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
||||
`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`,
|
||||
'true',
|
||||
);
|
||||
expect(component.hasClosed()).toBeTrue();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import {ChangeDetectionStrategy, Component, inject, input, OnInit, signal} from '@angular/core';
|
||||
import {ExternalLink} from '../../directives';
|
||||
import {LOCAL_STORAGE} from '../../providers';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
export const STORAGE_KEY_PREFIX = 'docs-was-closed-top-banner-';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-top-level-banner',
|
||||
standalone: true,
|
||||
imports: [ExternalLink, IconComponent],
|
||||
templateUrl: './top-level-banner.component.html',
|
||||
styleUrl: './top-level-banner.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TopLevelBannerComponent implements OnInit {
|
||||
private readonly localStorage = inject(LOCAL_STORAGE);
|
||||
|
||||
/**
|
||||
* Unique identifier for the banner. This ID is required to ensure that
|
||||
* the state of the banner (e.g., whether it has been closed) is tracked
|
||||
* separately for different events or instances. Without a unique ID,
|
||||
* closing one banner could inadvertently hide other banners for different events.
|
||||
*/
|
||||
id = input.required<string>();
|
||||
// Optional URL link that the banner should navigate to when clicked.
|
||||
link = input<string>();
|
||||
// Text content to be displayed in the banner.
|
||||
text = input.required<string>();
|
||||
// Optional expiry date. Setting the default expiry as a future date so we
|
||||
// don't have to deal with undefined signal values.
|
||||
expiry = input(new Date('3000-01-01'), {transform: parseDate});
|
||||
// Whether the user has closed the banner or the survey has expired.
|
||||
hasClosed = signal<boolean>(false);
|
||||
|
||||
ngOnInit(): void {
|
||||
const expired = Date.now() > this.expiry().getTime();
|
||||
|
||||
// Needs to be in a try/catch, because some browsers will
|
||||
// throw when using `localStorage` in private mode.
|
||||
try {
|
||||
this.hasClosed.set(
|
||||
this.localStorage?.getItem(this.getBannerStorageKey()) === 'true' || expired,
|
||||
);
|
||||
} catch {
|
||||
this.hasClosed.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.localStorage?.setItem(this.getBannerStorageKey(), 'true');
|
||||
this.hasClosed.set(true);
|
||||
}
|
||||
|
||||
private getBannerStorageKey(): string {
|
||||
return `${STORAGE_KEY_PREFIX}${this.id()}`;
|
||||
}
|
||||
}
|
||||
|
||||
const parseDate = (inputDate: string | Date): Date => {
|
||||
if (inputDate instanceof Date) {
|
||||
return inputDate;
|
||||
}
|
||||
const outputDate = new Date(inputDate);
|
||||
if (isNaN(outputDate.getTime())) {
|
||||
throw new Error(`Invalid date string: ${inputDate}`);
|
||||
}
|
||||
return outputDate;
|
||||
};
|
||||
@@ -36,7 +36,7 @@
|
||||
h5,
|
||||
h6 {
|
||||
.docs-anchor {
|
||||
margin-block-start: 2.5rem;
|
||||
margin-block-start: 0.75rem;
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ export class DocViewer implements OnChanges {
|
||||
private readonly elementRef = inject(ElementRef);
|
||||
private readonly location = inject(Location);
|
||||
private readonly navigationState = inject(NavigationState);
|
||||
private readonly platformId = inject(PLATFORM_ID);
|
||||
private readonly router = inject(Router);
|
||||
private readonly viewContainer = inject(ViewContainerRef);
|
||||
private readonly environmentInjector = inject(EnvironmentInjector);
|
||||
@@ -86,6 +85,8 @@ export class DocViewer implements OnChanges {
|
||||
private animateContent = false;
|
||||
private readonly pendingRenderTasks = inject(PendingTasks);
|
||||
|
||||
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
||||
|
||||
private countOfExamples = 0;
|
||||
|
||||
async ngOnChanges(changes: SimpleChanges): Promise<void> {
|
||||
@@ -97,11 +98,10 @@ export class DocViewer implements OnChanges {
|
||||
}
|
||||
|
||||
async renderContentsAndRunClientSetup(content?: string): Promise<void> {
|
||||
const isBrowser = isPlatformBrowser(this.platformId);
|
||||
const contentContainer = this.elementRef.nativeElement;
|
||||
|
||||
if (content) {
|
||||
if (isBrowser && !(this.document as any).startViewTransition) {
|
||||
if (this.isBrowser && !(this.document as any).startViewTransition) {
|
||||
// Apply a special class to the host node to trigger animation.
|
||||
// Note: when a page is hydrated, the `content` would be empty,
|
||||
// so we don't trigger an animation to avoid a content flickering
|
||||
@@ -112,7 +112,7 @@ export class DocViewer implements OnChanges {
|
||||
contentContainer.innerHTML = content;
|
||||
}
|
||||
|
||||
if (isBrowser) {
|
||||
if (this.isBrowser) {
|
||||
// First we setup event listeners on the HTML we just loaded.
|
||||
// We want to do this before things like the example viewers are loaded.
|
||||
this.setupAnchorListeners(contentContainer);
|
||||
@@ -301,6 +301,14 @@ export class DocViewer implements OnChanges {
|
||||
// during SSG.
|
||||
this.appRef.attachView(componentRef.hostView);
|
||||
|
||||
// This is wrapped with `isBrowser` in for hydration purposes.
|
||||
if (this.isBrowser) {
|
||||
// The `docs-viewer` may be rendered multiple times when navigating
|
||||
// between pages, which will create new components that need to be
|
||||
// destroyed for gradual cleanup.
|
||||
this.destroyRef.onDestroy(() => componentRef.destroy());
|
||||
}
|
||||
|
||||
return componentRef;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,39 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
export interface SnippetResult {
|
||||
value: string;
|
||||
matchLevel: 'none' | 'full' | string;
|
||||
}
|
||||
|
||||
/* The interface represents Algolia search result item. */
|
||||
export interface SearchResult {
|
||||
/* The url link to the search result page */
|
||||
url?: string;
|
||||
url: string;
|
||||
/* The hierarchy of the item */
|
||||
hierarchy?: Hierarchy;
|
||||
hierarchy: Hierarchy;
|
||||
/* The unique id of the search result item */
|
||||
objectID: string;
|
||||
/**
|
||||
* The type of the result. A content result will have
|
||||
* matched the content. A result of type 'lvl#' may have i
|
||||
* matched a lvl above it. For example, a type 'lvl3' may be
|
||||
* included in results because its 'lvl2' header matched the query.
|
||||
*/
|
||||
type: string;
|
||||
/** Documentation content (not headers) */
|
||||
content: string | null;
|
||||
/** Snippets of the matched text */
|
||||
_snippetResult: {
|
||||
hierarchy?: {
|
||||
lvl0?: SnippetResult;
|
||||
lvl1?: SnippetResult;
|
||||
lvl2?: SnippetResult;
|
||||
lvl3?: SnippetResult;
|
||||
lvl4?: SnippetResult;
|
||||
};
|
||||
content?: SnippetResult;
|
||||
};
|
||||
}
|
||||
|
||||
/* The hierarchy of the item */
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
"fast-glob": "~3.3.2",
|
||||
"fflate": "^0.8.2",
|
||||
"html-entities": "~2.5.2",
|
||||
"jsdom": "~24.1.0",
|
||||
"marked": "~12.0.2",
|
||||
"mermaid": "^10.8.0",
|
||||
"jsdom": "~25.0.0",
|
||||
"marked": "~14.0.0",
|
||||
"mermaid": "^11.0.0",
|
||||
"shiki": "^1.10.3"
|
||||
},
|
||||
"exports": {
|
||||
|
||||
@@ -13,6 +13,12 @@ def _extract_api_to_json(ctx):
|
||||
# Pass the module_name for the extracted APIs. This will be something like "@angular/core".
|
||||
args.add(ctx.attr.module_name)
|
||||
|
||||
# Pass the module_label for the extracted APIs, This is something like core for "@angular/core".
|
||||
args.add(ctx.attr.module_label)
|
||||
|
||||
# Pass the set of private modules that should not be included in the API reference.
|
||||
args.add_joined(ctx.attr.private_modules, join_with = ",")
|
||||
|
||||
# Pass the entry_point for from which to extract public symbols.
|
||||
args.add(ctx.file.entry_point)
|
||||
|
||||
@@ -74,6 +80,9 @@ extract_api_to_json = rule(
|
||||
mandatory = True,
|
||||
allow_single_file = True,
|
||||
),
|
||||
"private_modules": attr.string_list(
|
||||
doc = """List of private modules that should not be included in the API symbol linking""",
|
||||
),
|
||||
"import_map": attr.label_keyed_string_dict(
|
||||
doc = """Map of import path to the index.ts file for that import""",
|
||||
allow_files = True,
|
||||
@@ -82,6 +91,9 @@ extract_api_to_json = rule(
|
||||
doc = """JS Module name to be used for the extracted symbols""",
|
||||
mandatory = True,
|
||||
),
|
||||
"module_label": attr.string(
|
||||
doc = """Module label to be used for the extracted symbols. To be used as display name, for example in API docs""",
|
||||
),
|
||||
"extra_entries": attr.label_list(
|
||||
doc = """JSON files that contain extra entries to append to the final collection.""",
|
||||
allow_files = True,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
|
||||
import {NgtscProgram, CompilerOptions, createCompilerHost, DocEntry} from '@angular/compiler-cli';
|
||||
import {
|
||||
NgtscProgram,
|
||||
CompilerOptions,
|
||||
createCompilerHost,
|
||||
DocEntry,
|
||||
EntryCollection,
|
||||
InterfaceEntry,
|
||||
ClassEntry,
|
||||
} from '@angular/compiler-cli';
|
||||
import ts from 'typescript';
|
||||
|
||||
function main() {
|
||||
@@ -10,6 +18,8 @@ function main() {
|
||||
|
||||
const [
|
||||
moduleName,
|
||||
moduleLabel,
|
||||
serializedPrivateModules,
|
||||
entryPointExecRootRelativePath,
|
||||
srcs,
|
||||
outputFilenameExecRootRelativePath,
|
||||
@@ -17,6 +27,8 @@ function main() {
|
||||
extraEntriesSrcs,
|
||||
] = rawParamLines;
|
||||
|
||||
const privateModules = new Set(serializedPrivateModules.split(','));
|
||||
|
||||
// The path map is a serialized JSON map of import path to index.ts file.
|
||||
// For example, {'@angular/core': 'path/to/some/index.ts'}
|
||||
const pathMap = JSON.parse(serializedPathMapWithExecRootRelativePaths) as Record<string, string>;
|
||||
@@ -54,15 +66,45 @@ function main() {
|
||||
return result.concat(JSON.parse(readFileSync(path, {encoding: 'utf8'})) as DocEntry[]);
|
||||
}, []);
|
||||
|
||||
const extractedEntries = program.getApiDocumentation(entryPointExecRootRelativePath);
|
||||
const apiDoc = program.getApiDocumentation(entryPointExecRootRelativePath, privateModules);
|
||||
const extractedEntries = apiDoc.entries;
|
||||
const combinedEntries = extractedEntries.concat(extraEntries);
|
||||
|
||||
const normalized = moduleName.replace('@', '').replace(/[\/]/g, '_');
|
||||
|
||||
const output = JSON.stringify({
|
||||
moduleLabel: moduleLabel || moduleName,
|
||||
moduleName: moduleName,
|
||||
normalizedModuleName: normalized,
|
||||
entries: combinedEntries,
|
||||
});
|
||||
symbols: [
|
||||
// Symbols referenced, originating from other packages
|
||||
...apiDoc.symbols.entries(),
|
||||
|
||||
// Exported symbols from the current package
|
||||
...apiDoc.entries.map((entry) => [entry.name, moduleName]),
|
||||
|
||||
// Also doing it for every member of classes/interfaces
|
||||
...apiDoc.entries.flatMap((entry) => [
|
||||
[entry.name, moduleName],
|
||||
...getEntriesFromMembers(entry).map((member) => [member, moduleName]),
|
||||
]),
|
||||
],
|
||||
} as EntryCollection);
|
||||
|
||||
writeFileSync(outputFilenameExecRootRelativePath, output, {encoding: 'utf8'});
|
||||
}
|
||||
|
||||
function getEntriesFromMembers(entry: DocEntry): string[] {
|
||||
if (!hasMembers(entry)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return entry.members.map((member) => `${entry.name}.${member.name}`);
|
||||
}
|
||||
|
||||
function hasMembers(entry: DocEntry): entry is InterfaceEntry | ClassEntry {
|
||||
return 'members' in entry;
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -14,6 +14,7 @@ extract_api_to_json(
|
||||
},
|
||||
module_name = "@angular/core",
|
||||
output_name = "api.json",
|
||||
private_modules = [""],
|
||||
)
|
||||
|
||||
extract_api_to_json(
|
||||
@@ -31,6 +32,7 @@ extract_api_to_json(
|
||||
},
|
||||
module_name = "@angular/core",
|
||||
output_name = "extra_api.json",
|
||||
private_modules = [""],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
load("//adev/shared-docs/pipeline/api-gen/extraction:extract_api_to_json.bzl", "extract_api_to_json")
|
||||
load("//adev/shared-docs/pipeline/api-gen/rendering:render_api_to_html.bzl", "render_api_to_html")
|
||||
|
||||
def generate_api_docs(name, module_name, entry_point, srcs, import_map = {}, extra_entries = []):
|
||||
def generate_api_docs(name, module_name, entry_point, srcs, private_modules = [""], module_label = None, import_map = {}, extra_entries = []):
|
||||
"""Generates API documentation reference pages for the given sources."""
|
||||
json_outfile = name + "_api.json"
|
||||
|
||||
extract_api_to_json(
|
||||
name = name + "_extraction",
|
||||
module_name = module_name,
|
||||
module_label = module_label,
|
||||
entry_point = entry_point,
|
||||
srcs = srcs,
|
||||
output_name = json_outfile,
|
||||
import_map = import_map,
|
||||
extra_entries = extra_entries,
|
||||
private_modules = private_modules,
|
||||
)
|
||||
|
||||
render_api_to_html(
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
|
||||
import type {DocEntry, JsDocTagEntry} from '@angular/compiler-cli';
|
||||
|
||||
/** The JSON data file format for extracted API reference info. */
|
||||
export interface EntryCollection {
|
||||
moduleName: string;
|
||||
entries: DocEntry[];
|
||||
}
|
||||
import type {DocEntry, EntryCollection, JsDocTagEntry} from '@angular/compiler-cli';
|
||||
|
||||
export interface ManifestEntry {
|
||||
name: string;
|
||||
@@ -16,7 +10,12 @@ export interface ManifestEntry {
|
||||
}
|
||||
|
||||
/** Manifest that maps each module name to a list of API symbols. */
|
||||
export type Manifest = Record<string, ManifestEntry[]>;
|
||||
export type Manifest = {
|
||||
moduleName: string;
|
||||
normalizedModuleName: string;
|
||||
moduleLabel: string;
|
||||
entries: ManifestEntry[];
|
||||
}[];
|
||||
|
||||
/** Gets a unique lookup key for an API, e.g. "@angular/core/ElementRef". */
|
||||
function getApiLookupKey(moduleName: string, name: string) {
|
||||
@@ -114,22 +113,39 @@ export function generateManifest(apiCollections: EntryCollection[]): Manifest {
|
||||
});
|
||||
}
|
||||
|
||||
const manifest: Manifest = {};
|
||||
const manifest: Manifest = [];
|
||||
for (const collection of apiCollections) {
|
||||
if (!manifest[collection.moduleName]) {
|
||||
manifest[collection.moduleName] = [];
|
||||
const entries = collection.entries.map((entry) => ({
|
||||
name: entry.name,
|
||||
type: entry.entryType,
|
||||
isDeprecated: isDeprecated(entryLookup, collection.moduleName, entry),
|
||||
isDeveloperPreview: isDeveloperPreview(entryLookup, collection.moduleName, entry),
|
||||
isExperimental: isExperimental(entryLookup, collection.moduleName, entry),
|
||||
}));
|
||||
|
||||
const existingEntry = manifest.find((entry) => entry.moduleName === collection.moduleName);
|
||||
if (existingEntry) {
|
||||
existingEntry.entries.push(...entries);
|
||||
} else {
|
||||
manifest.push({
|
||||
moduleName: collection.moduleName,
|
||||
normalizedModuleName: collection.normalizedModuleName,
|
||||
moduleLabel: collection.moduleLabel ?? collection.moduleName,
|
||||
entries,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
manifest.sort((entry1, entry2) => {
|
||||
// Ensure that labels that start with a `code` tag like `window.ng` are last
|
||||
if (entry1.moduleLabel.startsWith('<')) {
|
||||
return 1;
|
||||
} else if (entry2.moduleLabel.startsWith('<')) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
manifest[collection.moduleName].push(
|
||||
...collection.entries.map((entry) => ({
|
||||
name: entry.name,
|
||||
type: entry.entryType,
|
||||
isDeprecated: isDeprecated(entryLookup, collection.moduleName, entry),
|
||||
isDeveloperPreview: isDeveloperPreview(entryLookup, collection.moduleName, entry),
|
||||
isExperimental: isExperimental(entryLookup, collection.moduleName, entry),
|
||||
})),
|
||||
);
|
||||
}
|
||||
return entry1.moduleLabel.localeCompare(entry2.moduleLabel);
|
||||
});
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
import {EntryCollection, generateManifest} from './generate_manifest';
|
||||
import {generateManifest} from './generate_manifest';
|
||||
import type {EntryCollection} from '@angular/compiler-cli';
|
||||
|
||||
function main() {
|
||||
const [paramFilePath] = process.argv.slice(2);
|
||||
|
||||
@@ -16,6 +16,7 @@ extract_api_to_json(
|
||||
entry_point = "another-fake-source.ts",
|
||||
module_name = "@angular/router",
|
||||
output_name = "api.json",
|
||||
private_modules = [""],
|
||||
)
|
||||
|
||||
ts_library(
|
||||
|
||||
@@ -1,40 +1,54 @@
|
||||
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
|
||||
import {DocEntry, EntryType, JsDocTagEntry} from '@angular/compiler-cli';
|
||||
import {generateManifest} from '../generate_manifest';
|
||||
import {generateManifest, Manifest} from '../generate_manifest';
|
||||
|
||||
describe('api manifest generation', () => {
|
||||
it('should generate a manifest from multiple collections', () => {
|
||||
const manifest = generateManifest([
|
||||
const manifest: Manifest = generateManifest([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/router',
|
||||
entries: [entry({name: 'Router', entryType: EntryType.UndecoratedClass})],
|
||||
normalizedModuleName: 'angular_router',
|
||||
moduleLabel: 'router',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
'@angular/router': [
|
||||
{
|
||||
name: 'Router',
|
||||
type: EntryType.UndecoratedClass,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/router',
|
||||
moduleLabel: 'router',
|
||||
normalizedModuleName: 'angular_router',
|
||||
entries: [
|
||||
{
|
||||
name: 'Router',
|
||||
type: EntryType.UndecoratedClass,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate a manifest when collections share a symbol with the same name', () => {
|
||||
@@ -42,33 +56,47 @@ describe('api manifest generation', () => {
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/router',
|
||||
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
|
||||
normalizedModuleName: 'angular_router',
|
||||
moduleLabel: 'router',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
'@angular/router': [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/router',
|
||||
moduleLabel: 'router',
|
||||
normalizedModuleName: 'angular_router',
|
||||
entries: [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should union collections for the same module into one manifest', () => {
|
||||
@@ -76,31 +104,40 @@ describe('api manifest generation', () => {
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
entries: [entry({name: 'TAO', entryType: EntryType.Constant})],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
{
|
||||
name: 'TAO',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
{
|
||||
name: 'TAO',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should mark a manifest entry as deprecated', () => {
|
||||
@@ -111,27 +148,34 @@ describe('api manifest generation', () => {
|
||||
entry({name: 'PI', entryType: EntryType.Constant, jsdocTags: jsdocTags('deprecated')}),
|
||||
entry({name: 'XI', entryType: EntryType.Constant, jsdocTags: jsdocTags('experimental')}),
|
||||
],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: true,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
{
|
||||
name: 'XI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: true,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
{
|
||||
name: 'XI',
|
||||
type: EntryType.Constant,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should deduplicate function overloads', () => {
|
||||
@@ -142,20 +186,27 @@ describe('api manifest generation', () => {
|
||||
entry({name: 'save', entryType: EntryType.Function}),
|
||||
entry({name: 'save', entryType: EntryType.Function}),
|
||||
],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not mark a function as deprecated if only one overload is deprecated', () => {
|
||||
@@ -166,20 +217,27 @@ describe('api manifest generation', () => {
|
||||
entry({name: 'save', entryType: EntryType.Function}),
|
||||
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
|
||||
],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should mark a function as deprecated if all overloads are deprecated', () => {
|
||||
@@ -190,20 +248,27 @@ describe('api manifest generation', () => {
|
||||
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
|
||||
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
|
||||
],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: true,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
entries: [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: true,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should mark a fn as deprecated if there's one w/ the same name in another collection", () => {
|
||||
@@ -213,33 +278,47 @@ describe('api manifest generation', () => {
|
||||
entries: [
|
||||
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
|
||||
],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/more',
|
||||
entries: [entry({name: 'save', entryType: EntryType.Function})],
|
||||
normalizedModuleName: 'angular_more',
|
||||
moduleLabel: 'more',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: true,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
'@angular/more': [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: true,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
moduleName: '@angular/more',
|
||||
moduleLabel: 'more',
|
||||
normalizedModuleName: 'angular_more',
|
||||
entries: [
|
||||
{
|
||||
name: 'save',
|
||||
type: EntryType.Function,
|
||||
isDeprecated: false,
|
||||
isDeveloperPreview: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should mark a manifest entry as developerPreview', () => {
|
||||
@@ -254,27 +333,34 @@ describe('api manifest generation', () => {
|
||||
}),
|
||||
entry({name: 'XI', entryType: EntryType.Constant, jsdocTags: jsdocTags('experimental')}),
|
||||
],
|
||||
normalizedModuleName: 'angular_core',
|
||||
moduleLabel: 'core',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(manifest).toEqual({
|
||||
'@angular/core': [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeveloperPreview: true,
|
||||
isDeprecated: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
{
|
||||
name: 'XI',
|
||||
type: EntryType.Constant,
|
||||
isDeveloperPreview: false,
|
||||
isDeprecated: false,
|
||||
isExperimental: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(manifest).toEqual([
|
||||
{
|
||||
moduleName: '@angular/core',
|
||||
moduleLabel: 'core',
|
||||
normalizedModuleName: 'angular_core',
|
||||
entries: [
|
||||
{
|
||||
name: 'PI',
|
||||
type: EntryType.Constant,
|
||||
isDeveloperPreview: true,
|
||||
isDeprecated: false,
|
||||
isExperimental: false,
|
||||
},
|
||||
{
|
||||
name: 'XI',
|
||||
type: EntryType.Constant,
|
||||
isDeveloperPreview: false,
|
||||
isDeprecated: false,
|
||||
isExperimental: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
// This file is temporarily copied from work-in-progress on
|
||||
@@ -101,6 +101,8 @@ export interface ClassEntry extends DocEntry {
|
||||
isAbstract: boolean;
|
||||
members: MemberEntry[];
|
||||
generics: GenericEntry[];
|
||||
extends?: string;
|
||||
implements: string[];
|
||||
}
|
||||
|
||||
// From an API doc perspective, class and interfaces are identical.
|
||||
@@ -132,15 +134,25 @@ export interface PipeEntry extends ClassEntry {
|
||||
// TODO: add `isPure`.
|
||||
}
|
||||
|
||||
export interface FunctionEntry extends DocEntry {
|
||||
export interface FunctionSignatureMetadata extends DocEntry {
|
||||
params: ParameterEntry[];
|
||||
returnType: string;
|
||||
returnDescription?: string;
|
||||
|
||||
generics: GenericEntry[];
|
||||
isNewType: boolean;
|
||||
}
|
||||
|
||||
export interface FunctionWithOverloadsEntry extends FunctionEntry {
|
||||
overloads: FunctionEntry[] | null;
|
||||
export type FunctionEntry = FunctionDefinitionEntry &
|
||||
DocEntry & {
|
||||
implementation: FunctionSignatureMetadata;
|
||||
};
|
||||
|
||||
/** Interface describing a function with overload signatures. */
|
||||
export interface FunctionDefinitionEntry {
|
||||
name: string;
|
||||
signatures: FunctionSignatureMetadata[];
|
||||
implementation: FunctionSignatureMetadata | null;
|
||||
}
|
||||
|
||||
/** Sub-entry for a single class or enum member. */
|
||||
@@ -178,15 +190,9 @@ export interface ParameterEntry {
|
||||
isRestParam: boolean;
|
||||
}
|
||||
|
||||
export interface FunctionWithOverloads {
|
||||
name: string;
|
||||
signatures: FunctionEntry[];
|
||||
implementation: FunctionEntry | null;
|
||||
}
|
||||
|
||||
export interface InitializerApiFunctionEntry extends DocEntry {
|
||||
callFunction: FunctionWithOverloads;
|
||||
subFunctions: FunctionWithOverloads[];
|
||||
callFunction: FunctionDefinitionEntry;
|
||||
subFunctions: FunctionDefinitionEntry[];
|
||||
|
||||
__docsMetadata__?: {
|
||||
/**
|
||||
@@ -204,7 +210,3 @@ export interface InitializerApiFunctionEntry extends DocEntry {
|
||||
export function isDocEntryWithSourceInfo(entry: DocEntry): entry is DocEntryWithSourceInfo {
|
||||
return 'source' in entry;
|
||||
}
|
||||
|
||||
export function isFunctionEntryWithOverloads(entry: DocEntry): entry is FunctionWithOverloadsEntry {
|
||||
return 'overloads' in entry;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ClassEntry,
|
||||
ConstantEntry,
|
||||
DecoratorEntry,
|
||||
DocEntry,
|
||||
EntryType,
|
||||
EnumEntry,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
ClassEntryRenderable,
|
||||
CliCommandRenderable,
|
||||
ConstantEntryRenderable,
|
||||
DecoratorEntryRenderable,
|
||||
DocEntryRenderable,
|
||||
EnumEntryRenderable,
|
||||
FunctionEntryRenderable,
|
||||
@@ -42,11 +44,16 @@ export function isClassEntry(entry: DocEntry): entry is ClassEntry {
|
||||
entry.entryType === EntryType.Component ||
|
||||
entry.entryType === EntryType.Pipe ||
|
||||
entry.entryType === EntryType.NgModule ||
|
||||
entry.entryType === EntryType.Directive ||
|
||||
entry.entryType === EntryType.Decorator
|
||||
entry.entryType === EntryType.Directive
|
||||
);
|
||||
}
|
||||
|
||||
export function isDecoratorEntry(entry: DocEntryRenderable): entry is DecoratorEntryRenderable;
|
||||
export function isDecoratorEntry(entry: DocEntry): entry is DecoratorEntry;
|
||||
export function isDecoratorEntry(entry: DocEntry): entry is DecoratorEntry {
|
||||
return entry.entryType === EntryType.Decorator;
|
||||
}
|
||||
|
||||
/** Gets whether the given entry represents a constant */
|
||||
export function isConstantEntry(entry: DocEntryRenderable): entry is ConstantEntryRenderable;
|
||||
export function isConstantEntry(entry: DocEntry): entry is ConstantEntry;
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
import {
|
||||
ClassEntry,
|
||||
ConstantEntry,
|
||||
DecoratorEntry,
|
||||
DocEntry,
|
||||
EnumEntry,
|
||||
FunctionEntry,
|
||||
FunctionSignatureMetadata,
|
||||
InitializerApiFunctionEntry,
|
||||
JsDocTagEntry,
|
||||
MemberEntry,
|
||||
@@ -55,7 +57,13 @@ export type TypeAliasEntryRenderable = TypeAliasEntry & DocEntryRenderable & Has
|
||||
export type ClassEntryRenderable = ClassEntry &
|
||||
DocEntryRenderable &
|
||||
HasRenderableToc & {
|
||||
membersGroups: Map<string, MemberEntryRenderable[]>;
|
||||
members: MemberEntryRenderable[];
|
||||
};
|
||||
|
||||
export type DecoratorEntryRenderable = DecoratorEntry &
|
||||
DocEntryRenderable &
|
||||
HasRenderableToc & {
|
||||
members: MemberEntryRenderable[];
|
||||
};
|
||||
|
||||
/** Documentation entity for a TypeScript enum augmented transformed content for rendering. */
|
||||
@@ -71,9 +79,12 @@ export type InterfaceEntryRenderable = ClassEntryRenderable;
|
||||
export type FunctionEntryRenderable = FunctionEntry &
|
||||
DocEntryRenderable &
|
||||
HasRenderableToc & {
|
||||
params: ParameterEntryRenderable[];
|
||||
deprecationMessage: string | null;
|
||||
overloads: FunctionEntryRenderable[] | null;
|
||||
};
|
||||
|
||||
export type FunctionSignatureMetadataRenderable = FunctionSignatureMetadata &
|
||||
DocEntryRenderable & {
|
||||
params: ParameterEntryRenderable[];
|
||||
};
|
||||
|
||||
/** Sub-entry for a single class or enum member augmented with transformed content for rendering. */
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {FunctionEntry, JsDocTagEntry, MemberEntry, ParameterEntry} from '../entities';
|
||||
import {JsDocTagEntry, MemberEntry, ParameterEntry} from '../entities';
|
||||
|
||||
import {
|
||||
CodeLineRenderable,
|
||||
FunctionEntryRenderable,
|
||||
JsDocTagRenderable,
|
||||
LinkEntryRenderable,
|
||||
MemberEntryRenderable,
|
||||
@@ -81,14 +80,6 @@ export interface HasRenderableParams {
|
||||
params: ParameterEntryRenderable[];
|
||||
}
|
||||
|
||||
export interface HasOverloads {
|
||||
overloads: FunctionEntry[] | null;
|
||||
}
|
||||
|
||||
export interface HasRenderableOverloads {
|
||||
overloads: FunctionEntryRenderable[] | null;
|
||||
}
|
||||
|
||||
export interface HasDeprecatedFlag {
|
||||
isDeprecated: boolean;
|
||||
deprecationMessage: string | null;
|
||||
|
||||
@@ -7,20 +7,27 @@ import {configureMarkedGlobally} from './marked/configuration';
|
||||
import {getRenderable} from './processing';
|
||||
import {renderEntry} from './rendering';
|
||||
import {initHighlighter} from './shiki/shiki';
|
||||
import {setCurrentSymbol, setSymbols} from './symbol-context';
|
||||
|
||||
/** The JSON data file format for extracted API reference info. */
|
||||
interface EntryCollection {
|
||||
moduleName: string;
|
||||
moduleLabel?: string;
|
||||
normalizedModuleName: string;
|
||||
entries: DocEntry[];
|
||||
symbols: Map<string, string>;
|
||||
}
|
||||
|
||||
/** Parse all JSON data source files into an array of collections. */
|
||||
function parseEntryData(srcs: string[]): EntryCollection[] {
|
||||
return srcs.flatMap((jsonDataFilePath) => {
|
||||
return srcs.flatMap((jsonDataFilePath): EntryCollection | EntryCollection[] => {
|
||||
const fileContent = readFileSync(jsonDataFilePath, {encoding: 'utf8'});
|
||||
const fileContentJson = JSON.parse(fileContent) as unknown;
|
||||
if ((fileContentJson as EntryCollection).entries) {
|
||||
return fileContentJson as EntryCollection;
|
||||
return {
|
||||
...(fileContentJson as EntryCollection),
|
||||
symbols: new Map((fileContentJson as any).symbols ?? []),
|
||||
};
|
||||
}
|
||||
|
||||
// CLI subcommands should generate a separate file for each subcommand.
|
||||
@@ -30,12 +37,16 @@ function parseEntryData(srcs: string[]): EntryCollection[] {
|
||||
return [
|
||||
{
|
||||
moduleName: 'unknown',
|
||||
normalizedModuleName: 'unknown',
|
||||
entries: [fileContentJson as DocEntry],
|
||||
symbols: new Map(),
|
||||
},
|
||||
...command.subcommands!.map((subCommand) => {
|
||||
return {
|
||||
moduleName: 'unknown',
|
||||
normalizedModuleName: 'unknown',
|
||||
entries: [{...subCommand, parentCommand: command} as any],
|
||||
symbols: new Map(),
|
||||
};
|
||||
}),
|
||||
];
|
||||
@@ -43,13 +54,15 @@ function parseEntryData(srcs: string[]): EntryCollection[] {
|
||||
|
||||
return {
|
||||
moduleName: 'unknown',
|
||||
normalizedModuleName: 'unknown',
|
||||
entries: [fileContentJson as DocEntry], // TODO: fix the typing cli entries aren't DocEntry
|
||||
symbols: new Map(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Gets a normalized filename for a doc entry. */
|
||||
function getNormalizedFilename(moduleName: string, entry: DocEntry | CliCommand): string {
|
||||
function getNormalizedFilename(normalizedModuleName: string, entry: DocEntry | CliCommand): string {
|
||||
if (isCliEntry(entry)) {
|
||||
return entry.parentCommand
|
||||
? `${entry.parentCommand.name}/${entry.name}.html`
|
||||
@@ -57,9 +70,6 @@ function getNormalizedFilename(moduleName: string, entry: DocEntry | CliCommand)
|
||||
}
|
||||
|
||||
entry = entry as DocEntry;
|
||||
// Angular entry points all contain an "@" character, which we want to remove
|
||||
// from the filename. We also swap `/` with an underscore.
|
||||
const normalizedModuleName = moduleName.replace('@', '').replace(/\//g, '_');
|
||||
|
||||
// Append entry type as suffix to prevent writing to file that only differs in casing or query string from already written file.
|
||||
// This will lead to a race-condition and corrupted files on case-insensitive file systems.
|
||||
@@ -96,14 +106,22 @@ async function main() {
|
||||
|
||||
for (const collection of entryCollections) {
|
||||
const extractedEntries = collection.entries;
|
||||
const renderableEntries = extractedEntries.map((entry) =>
|
||||
getRenderable(entry, collection.moduleName),
|
||||
);
|
||||
|
||||
// Setting the symbols are a global context for the rendering templates of this entry
|
||||
setSymbols(collection.symbols);
|
||||
|
||||
const renderableEntries = extractedEntries.map((entry) => {
|
||||
setCurrentSymbol(entry.name);
|
||||
return getRenderable(entry, collection.moduleName);
|
||||
});
|
||||
|
||||
const htmlOutputs = renderableEntries.map(renderEntry);
|
||||
|
||||
for (let i = 0; i < htmlOutputs.length; i++) {
|
||||
const filename = getNormalizedFilename(collection.moduleName, collection.entries[i]);
|
||||
const filename = getNormalizedFilename(
|
||||
collection.normalizedModuleName,
|
||||
collection.entries[i],
|
||||
);
|
||||
const outputPath = path.join(outputFilenameExecRootRelativePath, filename);
|
||||
|
||||
// in case the output path is nested, ensure the directory exists
|
||||
|
||||
@@ -6,16 +6,18 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Renderer as MarkedRenderer} from 'marked';
|
||||
import {Renderer, Tokens} from 'marked';
|
||||
import {codeToHtml} from '../shiki/shiki';
|
||||
|
||||
/**
|
||||
* Custom renderer for marked that will be used to transform markdown files to HTML
|
||||
* files that can be used in the Angular docs.
|
||||
*/
|
||||
export const renderer: Partial<MarkedRenderer> = {
|
||||
code(code: string, language: string, isEscaped: boolean): string {
|
||||
const highlightResult = codeToHtml(code, language).replace(/>\s+</g, '><');
|
||||
export const renderer: Partial<Renderer> = {
|
||||
code({lang, text}): string {
|
||||
const highlightResult = codeToHtml(text, lang)
|
||||
// remove spaces/line-breaks between elements to not mess-up `pre` style
|
||||
.replace(/>\s+</g, '><');
|
||||
|
||||
return `
|
||||
<div class="docs-code" role="group">
|
||||
@@ -25,37 +27,46 @@ export const renderer: Partial<MarkedRenderer> = {
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
image(href: string | null, title: string | null, text: string): string {
|
||||
image({href, title, text}): string {
|
||||
return `
|
||||
<img src="${href}" alt="${text}" title="${title}" class="docs-image">
|
||||
`;
|
||||
},
|
||||
link(href: string, title: string, text: string): string {
|
||||
return `<a href="${href}">${text}</a>`;
|
||||
link(this: Renderer, {href, tokens}): string {
|
||||
return `<a href="${href}">${this.parser.parseInline(tokens)}</a>`;
|
||||
},
|
||||
list(body: string, ordered: boolean, start: number) {
|
||||
list(this: Renderer, {items, ordered, start}) {
|
||||
if (ordered) {
|
||||
return `
|
||||
<ol class="docs-ordered-list">
|
||||
${body}
|
||||
${items.map((item) => this.listitem(item)).join('')}
|
||||
</ol>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<ul class="docs-list">
|
||||
${body}
|
||||
${items.map((item) => this.listitem(item)).join('')}
|
||||
</ul>
|
||||
`;
|
||||
},
|
||||
table(header: string, body: string): string {
|
||||
|
||||
table(this: Renderer, {header, rows}: Tokens.Table) {
|
||||
return `
|
||||
<div class="docs-table docs-scroll-track-transparent">
|
||||
<table>
|
||||
<thead>
|
||||
${header}
|
||||
${this.tablerow({
|
||||
text: header.map((cell) => this.tablecell(cell)).join(''),
|
||||
})}
|
||||
</thead>
|
||||
<tbody>
|
||||
${body}
|
||||
${rows
|
||||
.map((row) =>
|
||||
this.tablerow({
|
||||
text: row.map((cell) => this.tablecell(cell)).join(''),
|
||||
}),
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isClassEntry,
|
||||
isCliEntry,
|
||||
isConstantEntry,
|
||||
isDecoratorEntry,
|
||||
isEnumEntry,
|
||||
isFunctionEntry,
|
||||
isInitializerApiFunctionEntry,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
} from './entities/categorization';
|
||||
import {CliCommandRenderable, DocEntryRenderable} from './entities/renderables';
|
||||
import {getClassRenderable} from './transforms/class-transforms';
|
||||
import {getDecoratorRenderable} from './transforms/decorator-transforms';
|
||||
import {getCliRenderable} from './transforms/cli-transforms';
|
||||
import {getConstantRenderable} from './transforms/constant-transforms';
|
||||
import {getEnumRenderable} from './transforms/enum-transforms';
|
||||
@@ -43,6 +45,9 @@ export function getRenderable(
|
||||
if (isClassEntry(entry)) {
|
||||
return getClassRenderable(entry, moduleName);
|
||||
}
|
||||
if (isDecoratorEntry(entry)) {
|
||||
return getDecoratorRenderable(entry, moduleName);
|
||||
}
|
||||
if (isConstantEntry(entry)) {
|
||||
return getConstantRenderable(entry, moduleName);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isClassEntry,
|
||||
isCliEntry,
|
||||
isConstantEntry,
|
||||
isDecoratorEntry,
|
||||
isEnumEntry,
|
||||
isFunctionEntry,
|
||||
isInitializerApiFunctionEntry,
|
||||
@@ -27,14 +28,16 @@ import {EnumReference} from './templates/enum-reference';
|
||||
import {FunctionReference} from './templates/function-reference';
|
||||
import {InitializerApiFunction} from './templates/initializer-api-function';
|
||||
import {TypeAliasReference} from './templates/type-alias-reference';
|
||||
import {setCurrentSymbol} from './symbol-context';
|
||||
|
||||
/** Given a doc entry, get the transformed version of the entry for rendering. */
|
||||
export function renderEntry(renderable: DocEntryRenderable | CliCommandRenderable): string {
|
||||
setCurrentSymbol(renderable.name);
|
||||
if (isCliEntry(renderable)) {
|
||||
return render(CliCommandReference(renderable));
|
||||
}
|
||||
|
||||
if (isClassEntry(renderable) || isInterfaceEntry(renderable)) {
|
||||
if (isClassEntry(renderable) || isInterfaceEntry(renderable) || isDecoratorEntry(renderable)) {
|
||||
return render(ClassReference(renderable));
|
||||
}
|
||||
if (isConstantEntry(renderable)) {
|
||||
|
||||
@@ -21,8 +21,12 @@ export async function initHighlighter() {
|
||||
});
|
||||
}
|
||||
|
||||
export function codeToHtml(code: string, language: string | undefined): string {
|
||||
return highlighter.codeToHtml(code, {
|
||||
export function codeToHtml(
|
||||
code: string,
|
||||
language: string | undefined,
|
||||
options?: {removeFunctionKeyword?: boolean},
|
||||
): string {
|
||||
const html = highlighter.codeToHtml(code, {
|
||||
lang: language ?? 'text',
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
@@ -31,4 +35,24 @@ export function codeToHtml(code: string, language: string | undefined): string {
|
||||
cssVariablePrefix: '--shiki-',
|
||||
defaultColor: false,
|
||||
});
|
||||
|
||||
if (options?.removeFunctionKeyword) {
|
||||
return replaceKeywordFromShikiHtml('function', html);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
export function replaceKeywordFromShikiHtml(
|
||||
keyword: string,
|
||||
shikiHtml: string,
|
||||
replaceWith = '',
|
||||
): string {
|
||||
return (
|
||||
shikiHtml
|
||||
// remove the leading space of the element after the "function" element
|
||||
.replace(new RegExp(`(<[^>]*>${keyword}<\\/\\w+><[^>]*>)(\\s)(\\w+<\\/\\w+>)`, 'g'), '$1$3')
|
||||
// Shiki requires the keyword function for highlighting functions signatures
|
||||
// We don't want to display it so we remove elements with the keyword
|
||||
.replace(new RegExp(`<[^>]*>${keyword}<\\/\\w+>`, 'g'), replaceWith)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
// TODO(jelbourn): all of these CSS classes should use the `docs-` prefix.
|
||||
@@ -16,6 +16,7 @@ export const REFERENCE_MEMBERS = 'docs-reference-members';
|
||||
export const REFERENCE_DEPRECATED = 'docs-reference-deprecated';
|
||||
export const REFERENCE_MEMBERS_CONTAINER = 'docs-reference-members-container';
|
||||
export const REFERENCE_MEMBER_CARD = 'docs-reference-member-card';
|
||||
export const REFERENCE_MEMBER_CARD_HEADER = 'docs-reference-card-header';
|
||||
export const REFERENCE_MEMBER_CARD_BODY = 'docs-reference-card-body';
|
||||
export const REFERENCE_MEMBER_CARD_ITEM = 'docs-reference-card-item';
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* API pages are generated each package at a time.
|
||||
* This allows to use a global context to store the symbols and their corresponding module names.
|
||||
*/
|
||||
|
||||
let symbols = new Map<string, string>();
|
||||
|
||||
// This is used to store the currently processed symbol (usually a class or an interface)
|
||||
let currentSymbol: string | undefined;
|
||||
|
||||
export function setSymbols(newSymbols: Map<string, string>): void {
|
||||
symbols = newSymbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the module name of a symbol.
|
||||
* eg: 'ApplicationRef' => 'core', 'FormControl' => 'forms'
|
||||
* Also supports class.member, 'NgZone.runOutsideAngular => 'core'
|
||||
*/
|
||||
export function getModuleName(symbol: string): string | undefined {
|
||||
const moduleName = symbols.get(symbol);
|
||||
return moduleName?.replace('@angular/', '');
|
||||
}
|
||||
|
||||
export function setCurrentSymbol(symbol: string): void {
|
||||
currentSymbol = symbol;
|
||||
}
|
||||
|
||||
export function getCurrentSymbol(): string | undefined {
|
||||
return currentSymbol;
|
||||
}
|
||||
|
||||
export function logUnknownSymbol(link: string, symbol: string): void {
|
||||
console.warn(
|
||||
`WARNING: {@link ${link}} is invalid, ${symbol} or ${currentSymbol}.${symbol} is unknown in this context`,
|
||||
);
|
||||
}
|
||||
@@ -10,11 +10,11 @@ import {h} from 'preact';
|
||||
import {MemberEntryRenderable} from '../entities/renderables';
|
||||
import {ClassMember} from './class-member';
|
||||
|
||||
export function ClassMemberList(props: {membersGroups: Map<string, MemberEntryRenderable[]>}) {
|
||||
export function ClassMemberList(props: {members: MemberEntryRenderable[]}) {
|
||||
return (
|
||||
<div class="docs-reference-members">
|
||||
{Array.from(props.membersGroups).map(([_, group]) => (
|
||||
<ClassMember members={group} />
|
||||
{props.members.map((member) => (
|
||||
<ClassMember member={member} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,53 +15,51 @@ import {
|
||||
} from '../entities/categorization';
|
||||
import {MemberEntryRenderable} from '../entities/renderables';
|
||||
import {
|
||||
REFERENCE_HEADER,
|
||||
REFERENCE_MEMBER_CARD,
|
||||
REFERENCE_MEMBER_CARD_BODY,
|
||||
REFERENCE_MEMBER_CARD_HEADER,
|
||||
REFERENCE_MEMBER_CARD_ITEM,
|
||||
} from '../styling/css-classes';
|
||||
import {ClassMethodInfo} from './class-method-info';
|
||||
import {DeprecatedLabel} from './deprecated-label';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {getFunctionMetadataRenderable} from '../transforms/function-transforms';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
|
||||
export function ClassMember(props: {members: MemberEntryRenderable[]}) {
|
||||
const memberName = props.members[0].name;
|
||||
const returnType = getMemberType(props.members[0]);
|
||||
|
||||
// Do not create body element when there is no description
|
||||
const body = props.members.every(
|
||||
(member) => !member.htmlDescription && !isClassMethodEntry(member),
|
||||
) ? (
|
||||
<></>
|
||||
) : (
|
||||
export function ClassMember(props: {member: MemberEntryRenderable}) {
|
||||
const body = (
|
||||
<div className={REFERENCE_MEMBER_CARD_BODY}>
|
||||
{props.members.map((member) => {
|
||||
return isClassMethodEntry(member) ? (
|
||||
<ClassMethodInfo entry={member} isOverloaded={props.members.length > 1} />
|
||||
) : (
|
||||
<div className={REFERENCE_MEMBER_CARD_ITEM}>
|
||||
{props.members.every((member) => member.deprecationMessage !== null) ? (
|
||||
<DeprecatedLabel entry={props.members[0]} />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<RawHtml value={member.htmlDescription} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{isClassMethodEntry(props.member) ? (
|
||||
(props.member.signatures.length
|
||||
? props.member.signatures
|
||||
: [props.member.implementation]
|
||||
).map((sig) => {
|
||||
const renderableMember = getFunctionMetadataRenderable(sig);
|
||||
return <ClassMethodInfo entry={renderableMember} options={{showUsageNotes: true}} />;
|
||||
})
|
||||
) : props.member.htmlDescription || props.member.deprecationMessage ? (
|
||||
<div className={REFERENCE_MEMBER_CARD_ITEM}>
|
||||
<DeprecatedLabel entry={props.member} />
|
||||
<RawHtml value={props.member.htmlDescription} />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const memberName = props.member.name;
|
||||
const returnType = getMemberType(props.member);
|
||||
return (
|
||||
<div id={memberName} className={REFERENCE_MEMBER_CARD} tabIndex={-1}>
|
||||
<header>
|
||||
<div className={REFERENCE_HEADER}>
|
||||
<div className={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
<h3>{memberName}</h3>
|
||||
<div>
|
||||
{props.members.length > 1 ? (
|
||||
<span>{props.members.length} overloads</span>
|
||||
{isClassMethodEntry(props.member) && props.member.signatures.length > 1 ? (
|
||||
<span>{props.member.signatures.length} overloads</span>
|
||||
) : returnType ? (
|
||||
<code>{returnType}</code>
|
||||
<CodeSymbol code={returnType} />
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
@@ -75,7 +73,7 @@ export function ClassMember(props: {members: MemberEntryRenderable[]}) {
|
||||
|
||||
function getMemberType(entry: MemberEntryRenderable): string | null {
|
||||
if (isClassMethodEntry(entry)) {
|
||||
return entry.returnType;
|
||||
return entry.implementation.returnType;
|
||||
} else if (isPropertyEntry(entry) || isGetterEntry(entry) || isSetterEntry(entry)) {
|
||||
return entry.type;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import {Fragment, h} from 'preact';
|
||||
import {
|
||||
FunctionEntryRenderable,
|
||||
FunctionSignatureMetadataRenderable,
|
||||
MethodEntryRenderable,
|
||||
ParameterEntryRenderable,
|
||||
} from '../entities/renderables';
|
||||
@@ -16,13 +17,16 @@ import {PARAM_KEYWORD_CLASS_NAME, REFERENCE_MEMBER_CARD_ITEM} from '../styling/c
|
||||
import {DeprecatedLabel} from './deprecated-label';
|
||||
import {Parameter} from './parameter';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
|
||||
/**
|
||||
* Component to render the method-specific parts of a class's API reference.
|
||||
*/
|
||||
export function ClassMethodInfo(props: {
|
||||
entry: MethodEntryRenderable | FunctionEntryRenderable;
|
||||
isOverloaded?: boolean;
|
||||
entry: FunctionSignatureMetadataRenderable;
|
||||
options?: {
|
||||
showUsageNotes?: boolean;
|
||||
};
|
||||
}) {
|
||||
const entry = props.entry;
|
||||
|
||||
@@ -32,21 +36,21 @@ export function ClassMethodInfo(props: {
|
||||
>
|
||||
<RawHtml value={entry.htmlDescription} className={'docs-function-definition'} />
|
||||
{/* In case when method is overloaded we need to indicate which overload is deprecated */}
|
||||
{!props.isOverloaded ? (
|
||||
<></>
|
||||
) : (
|
||||
{entry.isDeprecated ? (
|
||||
<div>
|
||||
<DeprecatedLabel entry={entry} />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{entry.params.map((param: ParameterEntryRenderable) => (
|
||||
<Parameter param={param} />
|
||||
))}
|
||||
<div className={'docs-return-type'}>
|
||||
<span className={PARAM_KEYWORD_CLASS_NAME}>@returns</span>
|
||||
<code>{entry.returnType}</code>
|
||||
<CodeSymbol code={entry.returnType} />
|
||||
</div>
|
||||
{entry.htmlUsageNotes ? (
|
||||
{entry.htmlUsageNotes && props.options?.showUsageNotes ? (
|
||||
<div className={'docs-usage-notes'}>
|
||||
<span className={PARAM_KEYWORD_CLASS_NAME}>Usage notes</span>
|
||||
<RawHtml value={entry.htmlUsageNotes} />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Fragment, h} from 'preact';
|
||||
import {ClassEntryRenderable} from '../entities/renderables';
|
||||
import {ClassEntryRenderable, DecoratorEntryRenderable} from '../entities/renderables';
|
||||
import {ClassMemberList} from './class-member-list';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {REFERENCE_MEMBERS_CONTAINER} from '../styling/css-classes';
|
||||
@@ -16,20 +16,20 @@ import {TabUsageNotes} from './tab-usage-notes';
|
||||
import {TabApi} from './tab-api';
|
||||
|
||||
/** Component to render a class API reference document. */
|
||||
export function ClassReference(entry: ClassEntryRenderable) {
|
||||
export function ClassReference(entry: ClassEntryRenderable | DecoratorEntryRenderable) {
|
||||
return (
|
||||
<div class="api">
|
||||
<HeaderApi entry={entry} />
|
||||
<TabApi entry={entry} />
|
||||
<TabDescription entry={entry} />
|
||||
<TabUsageNotes entry={entry} />
|
||||
{
|
||||
entry.membersGroups.size > 0
|
||||
? (<div class={REFERENCE_MEMBERS_CONTAINER}>
|
||||
<ClassMemberList membersGroups={entry.membersGroups} />
|
||||
</div>)
|
||||
: (<></>)
|
||||
}
|
||||
{entry.members.length > 0 ? (
|
||||
<div class={REFERENCE_MEMBERS_CONTAINER}>
|
||||
<ClassMemberList members={entry.members} />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
import {Fragment, h} from 'preact';
|
||||
import {CliCardRenderable} from '../entities/renderables';
|
||||
import {DeprecatedLabel} from './deprecated-label';
|
||||
import { REFERENCE_MEMBER_CARD, REFERENCE_MEMBER_CARD_HEADER } from '../styling/css-classes';
|
||||
|
||||
export function CliCard(props: {card: CliCardRenderable}) {
|
||||
return (
|
||||
<div id={props.card.type} class="docs-reference-member-card" tabIndex={-1}>
|
||||
<div id={props.card.type} class={REFERENCE_MEMBER_CARD} tabIndex={-1}>
|
||||
<header>
|
||||
<div class="docs-card-ref-header">
|
||||
<div class={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
<h3>{props.card.type}</h3>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function CliCommandReference(entry: CliCommandRenderable) {
|
||||
<div class="docs-code docs-reference-cli-toc">
|
||||
<pre class="docs-mini-scroll-track">
|
||||
<code>
|
||||
<div className={'shiki-ln-line'}>
|
||||
<div className={'shiki line cli'}>
|
||||
ng {commandName(entry, command)}
|
||||
{entry.argumentsLabel ? <button member-id={'Arguments'} className="shiki-ln-line-argument">{entry.argumentsLabel}</button> : <></>}
|
||||
{entry.hasOptions ? <button member-id={'Options'} className="shiki-ln-line-option">[options]</button> : <></>}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import {h} from 'preact';
|
||||
import {getModuleName} from '../symbol-context';
|
||||
import {getLinkToModule} from '../transforms/url-transforms';
|
||||
|
||||
const symbolRegex = /([a-zA-Z_$][a-zA-Z_$0-9\.]*)/;
|
||||
|
||||
/**
|
||||
* Component that generates a code block with a link to a Symbol if it's known,
|
||||
* else generates a string code block
|
||||
*/
|
||||
export function CodeSymbol(props: {code: string}) {
|
||||
return (
|
||||
<code>
|
||||
{props.code.split(symbolRegex).map((rawSymbol, index) => {
|
||||
// Every even index is a non-match when the regex has 1 capturing group
|
||||
if (index % 2 === 0) return rawSymbol;
|
||||
|
||||
let [symbol, subSymbol] = rawSymbol.split('.'); // Also takes care of methods, enum value etc.
|
||||
const moduleName = getModuleName(symbol);
|
||||
|
||||
if (moduleName) {
|
||||
const url = getLinkToModule(moduleName, symbol, subSymbol);
|
||||
return <a href={url}>{rawSymbol}</a>;
|
||||
}
|
||||
|
||||
return rawSymbol;
|
||||
})}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Fragment, h} from 'preact';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {h, Fragment} from 'preact';
|
||||
import {EnumEntryRenderable} from '../entities/renderables';
|
||||
import {EnumEntryRenderable, MemberEntryRenderable} from '../entities/renderables';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {TabDescription} from './tab-description';
|
||||
import {TabApi} from './tab-api';
|
||||
@@ -26,7 +26,7 @@ export function EnumReference(entry: EnumEntryRenderable) {
|
||||
? (
|
||||
<div class={REFERENCE_MEMBERS_CONTAINER}>
|
||||
<div class={REFERENCE_MEMBERS}>
|
||||
{entry.members.map((member: any) => (<ClassMember members={[member]}/>))}
|
||||
{entry.members.map((member: MemberEntryRenderable) => (<ClassMember member={member}/>))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -7,23 +7,66 @@
|
||||
*/
|
||||
|
||||
import {h} from 'preact';
|
||||
import {FunctionEntryRenderable} from '../entities/renderables';
|
||||
import {FunctionEntryRenderable, FunctionSignatureMetadataRenderable} from '../entities/renderables';
|
||||
import {
|
||||
PARAM_KEYWORD_CLASS_NAME,
|
||||
REFERENCE_HEADER,
|
||||
REFERENCE_MEMBERS,
|
||||
REFERENCE_MEMBERS_CONTAINER,
|
||||
REFERENCE_MEMBER_CARD,
|
||||
REFERENCE_MEMBER_CARD_BODY,
|
||||
REFERENCE_MEMBER_CARD_HEADER,
|
||||
} from '../styling/css-classes';
|
||||
import {ClassMethodInfo} from './class-method-info';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {TabApi} from './tab-api';
|
||||
import {TabDescription} from './tab-description';
|
||||
import {TabUsageNotes} from './tab-usage-notes';
|
||||
import {HighlightTypeScript} from './highlight-ts';
|
||||
import {printInitializerFunctionSignatureLine} from '../transforms/code-transforms';
|
||||
import {getFunctionMetadataRenderable} from '../transforms/function-transforms';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
|
||||
export const signatureCard = (
|
||||
name: string,
|
||||
signature: FunctionSignatureMetadataRenderable,
|
||||
opts: {id: string},
|
||||
printSignaturesAsHeader: boolean,
|
||||
) => {
|
||||
return (
|
||||
<div class={REFERENCE_MEMBER_CARD} id={opts.id} tabIndex={-1}>
|
||||
<header>
|
||||
{printSignaturesAsHeader ? (
|
||||
<code>
|
||||
<HighlightTypeScript
|
||||
code={printInitializerFunctionSignatureLine(
|
||||
name,
|
||||
signature,
|
||||
// Always omit types in signature headers, to keep them short.
|
||||
true,
|
||||
)}
|
||||
removeFunctionKeyword={true}
|
||||
/>
|
||||
</code>
|
||||
) : (
|
||||
<div className={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
<h3>{name}</h3>
|
||||
<div>
|
||||
<CodeSymbol code={signature.returnType} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<div class={REFERENCE_MEMBER_CARD_BODY}>
|
||||
<ClassMethodInfo entry={signature} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** Component to render a function API reference document. */
|
||||
export function FunctionReference(entry: FunctionEntryRenderable) {
|
||||
// Use signatures as header if there are multiple signatures.
|
||||
const printSignaturesAsHeader = entry.signatures.length > 1;
|
||||
|
||||
return (
|
||||
<div class="api">
|
||||
<HeaderApi entry={entry} />
|
||||
@@ -32,26 +75,16 @@ export function FunctionReference(entry: FunctionEntryRenderable) {
|
||||
<TabUsageNotes entry={entry} />
|
||||
<div className={REFERENCE_MEMBERS_CONTAINER}>
|
||||
<div className={REFERENCE_MEMBERS}>
|
||||
<div className={REFERENCE_MEMBER_CARD}>
|
||||
<header>
|
||||
<div className={REFERENCE_HEADER}>
|
||||
<h3>{entry.name}</h3>
|
||||
<div>
|
||||
<code>{entry.returnType}</code>
|
||||
</div>
|
||||
</div>
|
||||
{entry.isDeprecated && (
|
||||
<span className={`${PARAM_KEYWORD_CLASS_NAME} docs-deprecated`}>@deprecated</span>
|
||||
)}
|
||||
</header>
|
||||
<div className={REFERENCE_MEMBER_CARD_BODY}>
|
||||
{entry.overloads ? (
|
||||
entry.overloads.map((overload) => <ClassMethodInfo entry={overload} />)
|
||||
) : (
|
||||
<ClassMethodInfo entry={entry} isOverloaded={true} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{entry.signatures.map((s, i) =>
|
||||
signatureCard(
|
||||
s.name,
|
||||
getFunctionMetadataRenderable(s, entry.moduleName),
|
||||
{
|
||||
id: `${s.name}_${i}`,
|
||||
},
|
||||
printSignaturesAsHeader,
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {h} from 'preact';
|
||||
@@ -74,12 +74,12 @@ export function HeaderApi(props: {entry: DocEntryRenderable; showFullDescription
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p
|
||||
<section
|
||||
className={'docs-reference-description'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: props.showFullDescription ? entry.htmlDescription : entry.shortHtmlDescription,
|
||||
}}
|
||||
></p>
|
||||
></section>
|
||||
|
||||
<DocsPillRow links={entry.additionalLinks} />
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {h} from 'preact';
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import { h } from 'preact';
|
||||
import { RawHtml } from './raw-html';
|
||||
import { codeToHtml } from '../shiki/shiki';
|
||||
import {h} from 'preact';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {codeToHtml} from '../shiki/shiki';
|
||||
|
||||
/** Component to render a header of the CLI page. */
|
||||
export function HighlightTypeScript(props: {code: string}) {
|
||||
const result = codeToHtml(props.code, 'typescript');
|
||||
export function HighlightTypeScript(props: {code: string; removeFunctionKeyword?: boolean}) {
|
||||
const result = codeToHtml(props.code, 'typescript', {
|
||||
removeFunctionKeyword: props.removeFunctionKeyword,
|
||||
});
|
||||
|
||||
return (
|
||||
<RawHtml value={result} />
|
||||
);
|
||||
return <RawHtml value={result} />;
|
||||
}
|
||||
|
||||
+14
-41
@@ -7,20 +7,13 @@
|
||||
*/
|
||||
|
||||
import {h, JSX} from 'preact';
|
||||
import {FunctionEntryRenderable, InitializerApiFunctionRenderable} from '../entities/renderables';
|
||||
import {InitializerApiFunctionRenderable} from '../entities/renderables';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {TabApi} from './tab-api';
|
||||
import {TabUsageNotes} from './tab-usage-notes';
|
||||
import {
|
||||
REFERENCE_MEMBERS,
|
||||
REFERENCE_MEMBERS_CONTAINER,
|
||||
REFERENCE_MEMBER_CARD,
|
||||
REFERENCE_MEMBER_CARD_BODY,
|
||||
} from '../styling/css-classes';
|
||||
import {printInitializerFunctionSignatureLine} from '../transforms/code-transforms';
|
||||
import {HighlightTypeScript} from './highlight-ts';
|
||||
import {ClassMethodInfo} from './class-method-info';
|
||||
import {getFunctionRenderable} from '../transforms/function-transforms';
|
||||
import {REFERENCE_MEMBERS, REFERENCE_MEMBERS_CONTAINER} from '../styling/css-classes';
|
||||
import {getFunctionMetadataRenderable} from '../transforms/function-transforms';
|
||||
import {signatureCard} from './function-reference';
|
||||
|
||||
/** Component to render a constant API reference document. */
|
||||
export function InitializerApiFunction(entry: InitializerApiFunctionRenderable) {
|
||||
@@ -40,32 +33,6 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
|
||||
entry.callFunction.signatures[0].description = '';
|
||||
}
|
||||
|
||||
const signatureCard = (name: string, signature: FunctionEntryRenderable, opts: {id: string}) => {
|
||||
return (
|
||||
<div class={REFERENCE_MEMBER_CARD} id={opts.id} tabIndex={-1}>
|
||||
<header>
|
||||
{printSignaturesAsHeader ? (
|
||||
<code>
|
||||
<HighlightTypeScript
|
||||
code={printInitializerFunctionSignatureLine(
|
||||
name,
|
||||
signature,
|
||||
// Always omit types in signature headers, to keep them short.
|
||||
true,
|
||||
)}
|
||||
/>
|
||||
</code>
|
||||
) : (
|
||||
<h3>{`${name}()`}</h3>
|
||||
)}
|
||||
</header>
|
||||
<div class={REFERENCE_MEMBER_CARD_BODY}>
|
||||
<ClassMethodInfo entry={signature} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="api">
|
||||
<HeaderApi entry={entry} showFullDescription={true} />
|
||||
@@ -75,9 +42,14 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
|
||||
<div class={REFERENCE_MEMBERS_CONTAINER}>
|
||||
<div class={REFERENCE_MEMBERS}>
|
||||
{entry.callFunction.signatures.map((s, i) =>
|
||||
signatureCard(s.name, getFunctionRenderable(s, entry.moduleName), {
|
||||
id: `${s.name}_${i}`,
|
||||
}),
|
||||
signatureCard(
|
||||
s.name,
|
||||
getFunctionMetadataRenderable(s, entry.moduleName),
|
||||
{
|
||||
id: `${s.name}_${i}`,
|
||||
},
|
||||
printSignaturesAsHeader,
|
||||
),
|
||||
)}
|
||||
|
||||
{entry.subFunctions.reduce(
|
||||
@@ -86,10 +58,11 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
|
||||
...subFunction.signatures.map((s, i) =>
|
||||
signatureCard(
|
||||
`${entry.name}.${s.name}`,
|
||||
getFunctionRenderable(s, entry.moduleName),
|
||||
getFunctionMetadataRenderable(s, entry.moduleName),
|
||||
{
|
||||
id: `${entry.name}_${s.name}_${i}`,
|
||||
},
|
||||
printSignaturesAsHeader,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -10,7 +10,7 @@ import {h} from 'preact';
|
||||
import {ParameterEntryRenderable} from '../entities/renderables';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {PARAM_GROUP_CLASS_NAME} from '../styling/css-classes';
|
||||
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
|
||||
/** Component to render a function or method parameter reference doc fragment. */
|
||||
export function Parameter(props: {param: ParameterEntryRenderable}) {
|
||||
@@ -21,7 +21,7 @@ export function Parameter(props: {param: ParameterEntryRenderable}) {
|
||||
{/*TODO: isOptional, isRestParam*/}
|
||||
<span class="docs-param-keyword">@param</span>
|
||||
<span class="docs-param-name">{param.name}</span>
|
||||
<code>{param.type}</code>
|
||||
<CodeSymbol code={param.type} />
|
||||
<RawHtml value={param.htmlDescription} className="docs-parameter-description" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,18 +10,41 @@ import {Fragment, h} from 'preact';
|
||||
import {DocEntryRenderable} from '../entities/renderables';
|
||||
import {normalizeTabUrl} from '../transforms/url-transforms';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
|
||||
const DESCRIPTION_TAB_NAME = 'Description';
|
||||
|
||||
/** Component to render the description tab. */
|
||||
export function TabDescription(props: {entry: DocEntryRenderable}) {
|
||||
if (!props.entry.htmlDescription || props.entry.htmlDescription === props.entry.shortHtmlDescription) {
|
||||
return (<></>);
|
||||
const exportedBy = props.entry.jsdocTags.filter((t) => t.name === 'ngModule');
|
||||
if (
|
||||
(!props.entry.htmlDescription ||
|
||||
props.entry.htmlDescription === props.entry.shortHtmlDescription) &&
|
||||
!exportedBy.length
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-tab={DESCRIPTION_TAB_NAME} data-tab-url={normalizeTabUrl(DESCRIPTION_TAB_NAME)}>
|
||||
<RawHtml value={props.entry.htmlDescription} />
|
||||
|
||||
{exportedBy.length ? (
|
||||
<>
|
||||
<hr />
|
||||
<h2>Exported by</h2>
|
||||
|
||||
<ul>
|
||||
{exportedBy.map((tag) => (
|
||||
<li>
|
||||
<CodeSymbol code={tag.comment} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,18 @@ ts_library(
|
||||
),
|
||||
deps = [
|
||||
"//adev/shared-docs/pipeline/api-gen/rendering:render_api_to_html_lib",
|
||||
"@npm//@bazel/runfiles",
|
||||
"@npm//@types/jsdom",
|
||||
"@npm//jsdom",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "unit_tests",
|
||||
data = [
|
||||
"@npm//jsdom",
|
||||
] + glob([
|
||||
"**/*.json",
|
||||
]),
|
||||
deps = [":unit_test_lib"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import {readFile} from 'fs/promises';
|
||||
import {JSDOM} from 'jsdom';
|
||||
import {renderEntry} from '../rendering';
|
||||
import {getRenderable} from '../processing';
|
||||
import {initHighlighter} from '../shiki/shiki';
|
||||
import {configureMarkedGlobally} from '../marked/configuration';
|
||||
|
||||
describe('CLI docs to html', () => {
|
||||
let fragment: DocumentFragment;
|
||||
let entryJson: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
await initHighlighter();
|
||||
await configureMarkedGlobally();
|
||||
|
||||
const entryContent = await readFile(runfiles.resolvePackageRelative('fake-cli-entries.json'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
entryJson = JSON.parse(entryContent) as any;
|
||||
const renderableJson = getRenderable(entryJson, '');
|
||||
fragment = JSDOM.fragment(await renderEntry(renderableJson));
|
||||
});
|
||||
|
||||
it('should subcommands correctly', async () => {
|
||||
const generateComponentSubcommand = entryJson.subcommands.find(
|
||||
(subcommand: any) => subcommand.name === 'component',
|
||||
);
|
||||
const renderableJson = getRenderable(generateComponentSubcommand, '');
|
||||
fragment = JSDOM.fragment(await renderEntry(renderableJson));
|
||||
|
||||
const cliTocs = fragment.querySelectorAll('.docs-reference-cli-toc')!;
|
||||
expect(cliTocs.length).toBe(2);
|
||||
|
||||
expect(cliTocs[0].textContent).toContain('ng component[name][options]');
|
||||
expect(cliTocs[1].textContent).toContain('ng c[name][options]');
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,9 @@
|
||||
"name": "generate",
|
||||
"command": "ng generate <schematic>",
|
||||
"shortDescription": "Generates and/or modifies files based on a schematic.",
|
||||
"aliases": ["g"],
|
||||
"aliases": [
|
||||
"g"
|
||||
],
|
||||
"deprecated": false,
|
||||
"options": [
|
||||
{
|
||||
@@ -14,7 +16,9 @@
|
||||
{
|
||||
"name": "dry-run",
|
||||
"type": "boolean",
|
||||
"aliases": ["d"],
|
||||
"aliases": [
|
||||
"d"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Run through and reports activity without writing out results."
|
||||
},
|
||||
@@ -62,16 +66,26 @@
|
||||
"command": "application [name]",
|
||||
"shortDescription": "Generates a new basic application definition in the \"projects\" subfolder of the workspace.",
|
||||
"options": [
|
||||
{
|
||||
"name": "experimental-zoneless",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Create an application that does not utilize zone.js."
|
||||
},
|
||||
{
|
||||
"name": "inline-style",
|
||||
"type": "boolean",
|
||||
"aliases": ["s"],
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"description": "Include styles inline in the root component.ts file. Only CSS styles can be included inline. Default is false, meaning that an external styles file is created and referenced in the root component.ts file."
|
||||
},
|
||||
{
|
||||
"name": "inline-template",
|
||||
"type": "boolean",
|
||||
"aliases": ["t"],
|
||||
"aliases": [
|
||||
"t"
|
||||
],
|
||||
"description": "Include template inline in the root component.ts file. Default is false, meaning that an external template file is created and referenced in the root component.ts file. "
|
||||
},
|
||||
{
|
||||
@@ -89,7 +103,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"default": "app",
|
||||
"description": "A prefix to apply to generated selectors."
|
||||
},
|
||||
@@ -119,7 +135,9 @@
|
||||
{
|
||||
"name": "skip-tests",
|
||||
"type": "boolean",
|
||||
"aliases": ["S"],
|
||||
"aliases": [
|
||||
"S"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Do not create \"spec.ts\" test files for the application."
|
||||
},
|
||||
@@ -145,17 +163,28 @@
|
||||
"name": "style",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": ["css", "scss", "sass", "less"],
|
||||
"enum": [
|
||||
"css",
|
||||
"scss",
|
||||
"sass",
|
||||
"less"
|
||||
],
|
||||
"description": "The file extension or preprocessor to use for style files."
|
||||
},
|
||||
{
|
||||
"name": "view-encapsulation",
|
||||
"type": "string",
|
||||
"enum": ["Emulated", "None", "ShadowDom"],
|
||||
"enum": [
|
||||
"Emulated",
|
||||
"None",
|
||||
"ShadowDom"
|
||||
],
|
||||
"description": "The view encapsulation strategy to use in the new application."
|
||||
}
|
||||
],
|
||||
"aliases": ["app"],
|
||||
"aliases": [
|
||||
"app"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -186,7 +215,9 @@
|
||||
"description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\"."
|
||||
}
|
||||
],
|
||||
"aliases": ["cl"],
|
||||
"aliases": [
|
||||
"cl"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -197,15 +228,22 @@
|
||||
{
|
||||
"name": "change-detection",
|
||||
"type": "string",
|
||||
"aliases": ["c"],
|
||||
"aliases": [
|
||||
"c"
|
||||
],
|
||||
"default": "Default",
|
||||
"enum": ["Default", "OnPush"],
|
||||
"enum": [
|
||||
"Default",
|
||||
"OnPush"
|
||||
],
|
||||
"description": "The change detection strategy to use in the new component."
|
||||
},
|
||||
{
|
||||
"name": "display-block",
|
||||
"type": "boolean",
|
||||
"aliases": ["b"],
|
||||
"aliases": [
|
||||
"b"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Specifies if the style will contain `:host { display: block; }`."
|
||||
},
|
||||
@@ -215,6 +253,12 @@
|
||||
"default": false,
|
||||
"description": "The declaring NgModule exports this component."
|
||||
},
|
||||
{
|
||||
"name": "export-default",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Use default export for the component instead of a named export."
|
||||
},
|
||||
{
|
||||
"name": "flat",
|
||||
"type": "boolean",
|
||||
@@ -224,21 +268,27 @@
|
||||
{
|
||||
"name": "inline-style",
|
||||
"type": "boolean",
|
||||
"aliases": ["s"],
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Include styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file."
|
||||
},
|
||||
{
|
||||
"name": "inline-template",
|
||||
"type": "boolean",
|
||||
"aliases": ["t"],
|
||||
"aliases": [
|
||||
"t"
|
||||
],
|
||||
"default": false,
|
||||
"description": "Include template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file."
|
||||
},
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -250,7 +300,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"description": "The prefix to apply to the generated component selector."
|
||||
},
|
||||
{
|
||||
@@ -291,7 +343,13 @@
|
||||
"name": "style",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": ["css", "scss", "sass", "less", "none"],
|
||||
"enum": [
|
||||
"css",
|
||||
"scss",
|
||||
"sass",
|
||||
"less",
|
||||
"none"
|
||||
],
|
||||
"description": "The file extension or preprocessor to use for style files, or 'none' to skip generating the style file."
|
||||
},
|
||||
{
|
||||
@@ -303,12 +361,20 @@
|
||||
{
|
||||
"name": "view-encapsulation",
|
||||
"type": "string",
|
||||
"aliases": ["v"],
|
||||
"enum": ["Emulated", "None", "ShadowDom"],
|
||||
"aliases": [
|
||||
"v"
|
||||
],
|
||||
"enum": [
|
||||
"Emulated",
|
||||
"None",
|
||||
"ShadowDom"
|
||||
],
|
||||
"description": "The view encapsulation strategy to use in the new component."
|
||||
}
|
||||
],
|
||||
"aliases": ["c"],
|
||||
"aliases": [
|
||||
"c"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -324,7 +390,10 @@
|
||||
{
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"enum": ["karma", "browserslist"],
|
||||
"enum": [
|
||||
"karma",
|
||||
"browserslist"
|
||||
],
|
||||
"description": "Specifies which type of configuration file to create.",
|
||||
"positional": 0
|
||||
}
|
||||
@@ -352,7 +421,9 @@
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -364,7 +435,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"description": "A prefix to apply to generated selectors."
|
||||
},
|
||||
{
|
||||
@@ -396,7 +469,9 @@
|
||||
"description": "Whether the generated directive is standalone."
|
||||
}
|
||||
],
|
||||
"aliases": ["d"],
|
||||
"aliases": [
|
||||
"d"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -421,7 +496,9 @@
|
||||
"description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\"."
|
||||
}
|
||||
],
|
||||
"aliases": ["e"],
|
||||
"aliases": [
|
||||
"e"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -458,7 +535,9 @@
|
||||
{
|
||||
"name": "implements",
|
||||
"type": "array",
|
||||
"aliases": ["guardType"],
|
||||
"aliases": [
|
||||
"guardType"
|
||||
],
|
||||
"description": "Specifies which type of guard to create."
|
||||
},
|
||||
{
|
||||
@@ -479,7 +558,9 @@
|
||||
"description": "Do not create \"spec.ts\" test files for the new guard."
|
||||
}
|
||||
],
|
||||
"aliases": ["g"],
|
||||
"aliases": [
|
||||
"g"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -548,7 +629,9 @@
|
||||
"positional": 1
|
||||
}
|
||||
],
|
||||
"aliases": ["i"],
|
||||
"aliases": [
|
||||
"i"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -571,7 +654,9 @@
|
||||
{
|
||||
"name": "prefix",
|
||||
"type": "string",
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"default": "lib",
|
||||
"description": "A prefix to apply to generated selectors."
|
||||
},
|
||||
@@ -605,7 +690,9 @@
|
||||
"description": "Creates a library based upon the standalone API, without NgModules."
|
||||
}
|
||||
],
|
||||
"aliases": ["lib"],
|
||||
"aliases": [
|
||||
"lib"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -622,7 +709,9 @@
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -651,11 +740,16 @@
|
||||
"name": "routing-scope",
|
||||
"type": "string",
|
||||
"default": "Child",
|
||||
"enum": ["Child", "Root"],
|
||||
"enum": [
|
||||
"Child",
|
||||
"Root"
|
||||
],
|
||||
"description": "The scope for the new routing module."
|
||||
}
|
||||
],
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -678,7 +772,9 @@
|
||||
{
|
||||
"name": "module",
|
||||
"type": "string",
|
||||
"aliases": ["m"],
|
||||
"aliases": [
|
||||
"m"
|
||||
],
|
||||
"description": "The declaring NgModule."
|
||||
},
|
||||
{
|
||||
@@ -711,7 +807,9 @@
|
||||
"description": "Whether the generated pipe is standalone."
|
||||
}
|
||||
],
|
||||
"aliases": ["p"],
|
||||
"aliases": [
|
||||
"p"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -749,7 +847,9 @@
|
||||
"description": "Do not create \"spec.ts\" test files for the new resolver."
|
||||
}
|
||||
],
|
||||
"aliases": ["r"],
|
||||
"aliases": [
|
||||
"r"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -781,7 +881,9 @@
|
||||
"description": "Do not create \"spec.ts\" test files for the new service."
|
||||
}
|
||||
],
|
||||
"aliases": ["s"],
|
||||
"aliases": [
|
||||
"s"
|
||||
],
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
@@ -828,7 +930,7 @@
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"deprecated": true
|
||||
"deprecated": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -3,81 +3,671 @@
|
||||
"entries": [
|
||||
{
|
||||
"name": "NgTemplateOutlet",
|
||||
"description": "*one* directive",
|
||||
"isAbstract": false,
|
||||
"entryType": "directive",
|
||||
"members": [],
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"source": {
|
||||
"fileName": "/packages/core/src/ng_template_outlet.ts",
|
||||
"line": 7,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "UserProfile",
|
||||
"entryType": "undecorated_class",
|
||||
"members": [
|
||||
{
|
||||
"name": "userId",
|
||||
"type": "number",
|
||||
"name": "ngTemplateOutletContext",
|
||||
"type": "C",
|
||||
"memberType": "property",
|
||||
"memberTags": [],
|
||||
"description": "A user identifier.",
|
||||
"jsdocTags": []
|
||||
"memberTags": ["input"],
|
||||
"description": "A context object to attach to the {@link EmbeddedViewRef}. This should be an\nobject, the object's keys will be available for binding by the local template `let`\ndeclarations.\nUsing the key `$implicit` in the context object will set its value as default.",
|
||||
"jsdocTags": [],
|
||||
"inputAlias": "ngTemplateOutletContext",
|
||||
"isRequiredInput": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"memberType": "getter",
|
||||
"memberTags": [],
|
||||
"description": "Name of the user",
|
||||
"jsdocTags": []
|
||||
"name": "ngTemplateOutlet",
|
||||
"type": "TemplateRef<C>",
|
||||
"memberType": "property",
|
||||
"memberTags": ["input"],
|
||||
"description": "A string defining the template reference and optionally the context object for the template.",
|
||||
"jsdocTags": [],
|
||||
"inputAlias": "ngTemplateOutlet",
|
||||
"isRequiredInput": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"memberType": "setter",
|
||||
"memberTags": [],
|
||||
"description": "Name of the user",
|
||||
"jsdocTags": []
|
||||
"name": "ngTemplateOutletInjector",
|
||||
"type": "Injector",
|
||||
"memberType": "property",
|
||||
"memberTags": ["input"],
|
||||
"description": "Injector to be used within the embedded view.",
|
||||
"jsdocTags": [],
|
||||
"inputAlias": "ngTemplateOutletInjector",
|
||||
"isRequiredInput": false
|
||||
},
|
||||
{
|
||||
"params": [
|
||||
"name": "ngOnChanges",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "config",
|
||||
"description": "Setting for saving.",
|
||||
"type": "object",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
"name": "ngOnChanges",
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [
|
||||
{
|
||||
"name": "changes",
|
||||
"description": "",
|
||||
"type": "SimpleChanges",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"rawComment": "",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"name": "save",
|
||||
"returnType": "boolean",
|
||||
"implementation": {
|
||||
"params": [
|
||||
{
|
||||
"name": "changes",
|
||||
"description": "",
|
||||
"type": "SimpleChanges",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "ngOnChanges",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Save the user.",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "param",
|
||||
"comment": "Setting for saving."
|
||||
},
|
||||
{
|
||||
"name": "returns",
|
||||
"comment": "Whether it succeeded"
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * Save the user.\n * @param config Setting for saving.\n * @returns Whether it succeeded\n */",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"memberType": "method",
|
||||
"memberTags": []
|
||||
}
|
||||
],
|
||||
"generics": [{"name": "C", "default": "unknown"}],
|
||||
"description": "",
|
||||
"jsdocTags": [
|
||||
{"name": "ngModule", "comment": "CommonModule"},
|
||||
{
|
||||
"name": "description",
|
||||
"comment": "Inserts an embedded view from a prepared `TemplateRef`.\n\nYou can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.\n`[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding\nby the local template `let` declarations."
|
||||
},
|
||||
{
|
||||
"name": "usageNotes",
|
||||
"comment": "```\n<ng-container *ngTemplateOutlet=\"templateRefExp; context: contextExp\"></ng-container>\n```\n\nUsing the key `$implicit` in the context object will set its value as default.\n\n### Example\n\n{@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}"
|
||||
},
|
||||
{"name": "publicApi", "comment": ""}
|
||||
],
|
||||
"rawComment": "/**\n * @ngModule CommonModule\n *\n * @description\n *\n * Inserts an embedded view from a prepared `TemplateRef`.\n *\n * You can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.\n * `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding\n * by the local template `let` declarations.\n *\n * @usageNotes\n * ```\n * <ng-container *ngTemplateOutlet=\"templateRefExp; context: contextExp\"></ng-container>\n * ```\n *\n * Using the key `$implicit` in the context object will set its value as default.\n *\n * ### Example\n *\n * {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}\n *\n * @publicApi\n */",
|
||||
"implements": ["OnChanges"],
|
||||
"isStandalone": true,
|
||||
"selector": "[ngTemplateOutlet]",
|
||||
"exportAs": [],
|
||||
"source": {
|
||||
"filePath": "/packages/common/src/directives/ng_template_outlet.ts",
|
||||
"startLine": 45,
|
||||
"endLine": 126
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ViewRef",
|
||||
"isAbstract": true,
|
||||
"entryType": "undecorated_class",
|
||||
"members": [
|
||||
{
|
||||
"name": "destroy",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "destroy",
|
||||
"entryType": "function",
|
||||
"description": "Destroys this view and all of the data structures associated with it.",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Destroys this view and all of the data structures associated with it.\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "destroy",
|
||||
"description": "Destroys this view and all of the data structures associated with it.",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Destroys this view and all of the data structures associated with it.\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Destroys this view and all of the data structures associated with it.",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Destroys this view and all of the data structures associated with it.\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract"]
|
||||
},
|
||||
{
|
||||
"name": "destroyed",
|
||||
"type": "boolean",
|
||||
"memberType": "getter",
|
||||
"memberTags": ["abstract"],
|
||||
"description": "Reports whether this view has been destroyed.",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "returns",
|
||||
"comment": "True after the `destroy()` method has been called, false otherwise."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "onDestroy",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "onDestroy",
|
||||
"entryType": "function",
|
||||
"description": "A lifecycle hook that provides additional developer-defined cleanup\nfunctionality for views.",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "param",
|
||||
"comment": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked."
|
||||
}
|
||||
],
|
||||
"params": [
|
||||
{
|
||||
"name": "callback",
|
||||
"description": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked.",
|
||||
"type": "Function",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * A lifecycle hook that provides additional developer-defined cleanup\n * functionality for views.\n * @param callback A handler function that cleans up developer-defined data\n * associated with a view. Called when the `destroy()` method is invoked.\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [
|
||||
{
|
||||
"name": "callback",
|
||||
"description": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked.",
|
||||
"type": "Function",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "onDestroy",
|
||||
"description": "A lifecycle hook that provides additional developer-defined cleanup\nfunctionality for views.",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "param",
|
||||
"comment": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked."
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * A lifecycle hook that provides additional developer-defined cleanup\n * functionality for views.\n * @param callback A handler function that cleans up developer-defined data\n * associated with a view. Called when the `destroy()` method is invoked.\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "A lifecycle hook that provides additional developer-defined cleanup\nfunctionality for views.",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "param",
|
||||
"comment": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked."
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * A lifecycle hook that provides additional developer-defined cleanup\n * functionality for views.\n * @param callback A handler function that cleans up developer-defined data\n * associated with a view. Called when the `destroy()` method is invoked.\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract"]
|
||||
},
|
||||
{
|
||||
"name": "markForCheck",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "markForCheck",
|
||||
"entryType": "function",
|
||||
"description": "When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\nchange detection strategy, explicitly marks the view as changed so that\nit can be checked again.\n\nComponents are normally marked as dirty (in need of rerendering) when inputs\nhave changed or events have fired in the view. Call this method to ensure that\na component is checked even if these triggers have not occurred.\n\n<!-- TODO: Add a link to a chapter on OnPush components -->",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\n * change detection strategy, explicitly marks the view as changed so that\n * it can be checked again.\n *\n * Components are normally marked as dirty (in need of rerendering) when inputs\n * have changed or events have fired in the view. Call this method to ensure that\n * a component is checked even if these triggers have not occurred.\n *\n * <!-- TODO: Add a link to a chapter on OnPush components -->\n *\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "markForCheck",
|
||||
"description": "When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\nchange detection strategy, explicitly marks the view as changed so that\nit can be checked again.\n\nComponents are normally marked as dirty (in need of rerendering) when inputs\nhave changed or events have fired in the view. Call this method to ensure that\na component is checked even if these triggers have not occurred.\n\n<!-- TODO: Add a link to a chapter on OnPush components -->",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\n * change detection strategy, explicitly marks the view as changed so that\n * it can be checked again.\n *\n * Components are normally marked as dirty (in need of rerendering) when inputs\n * have changed or events have fired in the view. Call this method to ensure that\n * a component is checked even if these triggers have not occurred.\n *\n * <!-- TODO: Add a link to a chapter on OnPush components -->\n *\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\nchange detection strategy, explicitly marks the view as changed so that\nit can be checked again.\n\nComponents are normally marked as dirty (in need of rerendering) when inputs\nhave changed or events have fired in the view. Call this method to ensure that\na component is checked even if these triggers have not occurred.\n\n<!-- TODO: Add a link to a chapter on OnPush components -->",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\n * change detection strategy, explicitly marks the view as changed so that\n * it can be checked again.\n *\n * Components are normally marked as dirty (in need of rerendering) when inputs\n * have changed or events have fired in the view. Call this method to ensure that\n * a component is checked even if these triggers have not occurred.\n *\n * <!-- TODO: Add a link to a chapter on OnPush components -->\n *\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract", "override"]
|
||||
},
|
||||
{
|
||||
"name": "detach",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "detach",
|
||||
"entryType": "function",
|
||||
"description": "Detaches this view from the change-detection tree.\nA detached view is not checked until it is reattached.\nUse in combination with `detectChanges()` to implement local change detection checks.\n\nDetached views are not checked during change detection runs until they are\nre-attached, even if they are marked as dirty.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Detaches this view from the change-detection tree.\n * A detached view is not checked until it is reattached.\n * Use in combination with `detectChanges()` to implement local change detection checks.\n *\n * Detached views are not checked during change detection runs until they are\n * re-attached, even if they are marked as dirty.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "detach",
|
||||
"description": "Detaches this view from the change-detection tree.\nA detached view is not checked until it is reattached.\nUse in combination with `detectChanges()` to implement local change detection checks.\n\nDetached views are not checked during change detection runs until they are\nre-attached, even if they are marked as dirty.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Detaches this view from the change-detection tree.\n * A detached view is not checked until it is reattached.\n * Use in combination with `detectChanges()` to implement local change detection checks.\n *\n * Detached views are not checked during change detection runs until they are\n * re-attached, even if they are marked as dirty.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Detaches this view from the change-detection tree.\nA detached view is not checked until it is reattached.\nUse in combination with `detectChanges()` to implement local change detection checks.\n\nDetached views are not checked during change detection runs until they are\nre-attached, even if they are marked as dirty.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Detaches this view from the change-detection tree.\n * A detached view is not checked until it is reattached.\n * Use in combination with `detectChanges()` to implement local change detection checks.\n *\n * Detached views are not checked during change detection runs until they are\n * re-attached, even if they are marked as dirty.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract", "override"]
|
||||
},
|
||||
{
|
||||
"name": "detectChanges",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "detectChanges",
|
||||
"entryType": "function",
|
||||
"description": "Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\nto implement local change detection checks.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\n * to implement local change detection checks.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "detectChanges",
|
||||
"description": "Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\nto implement local change detection checks.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\n * to implement local change detection checks.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\nto implement local change detection checks.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\n * to implement local change detection checks.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract", "override"]
|
||||
},
|
||||
{
|
||||
"name": "checkNoChanges",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "checkNoChanges",
|
||||
"entryType": "function",
|
||||
"description": "Checks the change detector and its children, and throws if any changes are detected.\n\nUse in development mode to verify that running change detection doesn't introduce\nother changes. Calling it in production mode is a noop.",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "deprecated",
|
||||
"comment": "This is a test-only API that does not have a place in production interface.\n`checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\nmode. For more granular `checkNoChanges` validation, use `ComponentFixture`."
|
||||
}
|
||||
],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Checks the change detector and its children, and throws if any changes are detected.\n *\n * Use in development mode to verify that running change detection doesn't introduce\n * other changes. Calling it in production mode is a noop.\n *\n * @deprecated This is a test-only API that does not have a place in production interface.\n * `checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\n * mode. For more granular `checkNoChanges` validation, use `ComponentFixture`.\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "checkNoChanges",
|
||||
"description": "Checks the change detector and its children, and throws if any changes are detected.\n\nUse in development mode to verify that running change detection doesn't introduce\nother changes. Calling it in production mode is a noop.",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "deprecated",
|
||||
"comment": "This is a test-only API that does not have a place in production interface.\n`checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\nmode. For more granular `checkNoChanges` validation, use `ComponentFixture`."
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * Checks the change detector and its children, and throws if any changes are detected.\n *\n * Use in development mode to verify that running change detection doesn't introduce\n * other changes. Calling it in production mode is a noop.\n *\n * @deprecated This is a test-only API that does not have a place in production interface.\n * `checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\n * mode. For more granular `checkNoChanges` validation, use `ComponentFixture`.\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Checks the change detector and its children, and throws if any changes are detected.\n\nUse in development mode to verify that running change detection doesn't introduce\nother changes. Calling it in production mode is a noop.",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "deprecated",
|
||||
"comment": "This is a test-only API that does not have a place in production interface.\n`checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\nmode. For more granular `checkNoChanges` validation, use `ComponentFixture`."
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * Checks the change detector and its children, and throws if any changes are detected.\n *\n * Use in development mode to verify that running change detection doesn't introduce\n * other changes. Calling it in production mode is a noop.\n *\n * @deprecated This is a test-only API that does not have a place in production interface.\n * `checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\n * mode. For more granular `checkNoChanges` validation, use `ComponentFixture`.\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract", "override"]
|
||||
},
|
||||
{
|
||||
"name": "reattach",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "reattach",
|
||||
"entryType": "function",
|
||||
"description": "Re-attaches the previously detached view to the change detection tree.\nViews are attached to the tree by default.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [],
|
||||
"params": [],
|
||||
"rawComment": "/**\n * Re-attaches the previously detached view to the change detection tree.\n * Views are attached to the tree by default.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n *\n */",
|
||||
"returnType": "void"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [],
|
||||
"isNewType": false,
|
||||
"returnType": "void",
|
||||
"generics": [],
|
||||
"name": "reattach",
|
||||
"description": "Re-attaches the previously detached view to the change detection tree.\nViews are attached to the tree by default.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Re-attaches the previously detached view to the change detection tree.\n * Views are attached to the tree by default.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n *\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Re-attaches the previously detached view to the change detection tree.\nViews are attached to the tree by default.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "/**\n * Re-attaches the previously detached view to the change detection tree.\n * Views are attached to the tree by default.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n *\n */",
|
||||
"memberType": "method",
|
||||
"memberTags": ["abstract", "override"]
|
||||
}
|
||||
],
|
||||
"generics": [],
|
||||
"description": "Represents an Angular view.",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "see",
|
||||
"comment": "[Change detection usage](/api/core/ChangeDetectorRef?tab=usage-notes)"
|
||||
},
|
||||
{"name": "publicApi", "comment": ""}
|
||||
],
|
||||
"rawComment": "/**\n * Represents an Angular view.\n *\n * @see [Change detection usage](/api/core/ChangeDetectorRef?tab=usage-notes)\n *\n * @publicApi\n */",
|
||||
"extends": "ChangeDetectorRef",
|
||||
"implements": [],
|
||||
"source": {
|
||||
"filePath": "/packages/core/src/linker/view_ref.ts",
|
||||
"startLine": 18,
|
||||
"endLine": 37
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterNextRender",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "afterNextRender",
|
||||
"entryType": "function",
|
||||
"description": "Register callbacks to be invoked the next time the application finishes rendering, during the\nspecified phases. The available phases are:\n- `earlyRead`\n Use this phase to **read** from the DOM before a subsequent `write` callback, for example to\n perform custom layout that the browser doesn't natively support. Prefer the `read` phase if\n reading can wait until after the write phase. **Never** write to the DOM in this phase.\n- `write`\n Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.\n- `mixedReadWrite`\n Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if\n it is possible to divide the work among the other phases instead.\n- `read`\n Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.\n\n<div class=\"alert is-critical\">\n\nYou should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`\nphases when possible, to avoid performance degradation.\n\n</div>\n\nNote that:\n- Callbacks run in the following phase order *once, after the next render*:\n 1. `earlyRead`\n 2. `write`\n 3. `mixedReadWrite`\n 4. `read`\n- Callbacks in the same phase run in the order they are registered.\n- Callbacks run on browser platforms only, they will not run on the server.\n\nThe first phase callback to run as part of this spec will receive no parameters. Each\nsubsequent phase callback in this spec will receive the return value of the previously run\nphase callback as a parameter. This can be used to coordinate work across multiple phases.\n\nAngular is unable to verify or enforce that phases are used correctly, and instead\nrelies on each developer to follow the guidelines documented for each value and\ncarefully choose the appropriate one, refactoring their code if necessary. By doing\nso, Angular is better able to minimize the performance degradation associated with\nmanual DOM access, ensuring the best experience for the end users of your application\nor library.\n\n<div class=\"alert is-important\">\n\nComponents are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\nYou must use caution when directly reading or writing the DOM and layout.\n\n</div>",
|
||||
"generics": [
|
||||
{"name": "E", "default": "never"},
|
||||
{"name": "W", "default": "never"},
|
||||
{"name": "M", "default": "never"}
|
||||
],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [
|
||||
{"name": "param", "comment": "The callback functions to register"},
|
||||
{"name": "param", "comment": "Options to control the behavior of the callback"},
|
||||
{
|
||||
"name": "usageNotes",
|
||||
"comment": "Use `afterNextRender` to read or write the DOM once,\nfor example to initialize a non-Angular library.\n\n### Example\n```ts\n@Component({\n selector: 'my-chart-cmp',\n template: `<div #chart>{{ ... }}</div>`,\n})\nexport class MyChartCmp {\n @ViewChild('chart') chartRef: ElementRef;\n chart: MyChart|null;\n\n constructor() {\n afterNextRender({\n write: () => {\n this.chart = new MyChart(this.chartRef.nativeElement);\n }\n });\n }\n}\n```"
|
||||
},
|
||||
{"name": "developerPreview", "comment": ""}
|
||||
],
|
||||
"params": [
|
||||
{
|
||||
"name": "spec",
|
||||
"description": "The callback functions to register",
|
||||
"type": "{ earlyRead?: () => E; write?: (...args: [E] extends [never] ? [] : [E]) => W; mixedReadWrite?: (...args: [W] extends [never] ? [E] extends [never] ? [] : [E] : [W]) => M; read?: (...args: [...] extends [...] ? [...] extends [...] ? [...] extends [...] ? [] : [...] : [...] : [...]) => void; }",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"description": "Options to control the behavior of the callback",
|
||||
"type": "Omit<AfterRenderOptions, \"phase\">",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * Register callbacks to be invoked the next time the application finishes rendering, during the\n * specified phases. The available phases are:\n * - `earlyRead`\n * Use this phase to **read** from the DOM before a subsequent `write` callback, for example to\n * perform custom layout that the browser doesn't natively support. Prefer the `read` phase if\n * reading can wait until after the write phase. **Never** write to the DOM in this phase.\n * - `write`\n * Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.\n * - `mixedReadWrite`\n * Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if\n * it is possible to divide the work among the other phases instead.\n * - `read`\n * Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.\n *\n * <div class=\"alert is-critical\">\n *\n * You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`\n * phases when possible, to avoid performance degradation.\n *\n * </div>\n *\n * Note that:\n * - Callbacks run in the following phase order *once, after the next render*:\n * 1. `earlyRead`\n * 2. `write`\n * 3. `mixedReadWrite`\n * 4. `read`\n * - Callbacks in the same phase run in the order they are registered.\n * - Callbacks run on browser platforms only, they will not run on the server.\n *\n * The first phase callback to run as part of this spec will receive no parameters. Each\n * subsequent phase callback in this spec will receive the return value of the previously run\n * phase callback as a parameter. This can be used to coordinate work across multiple phases.\n *\n * Angular is unable to verify or enforce that phases are used correctly, and instead\n * relies on each developer to follow the guidelines documented for each value and\n * carefully choose the appropriate one, refactoring their code if necessary. By doing\n * so, Angular is better able to minimize the performance degradation associated with\n * manual DOM access, ensuring the best experience for the end users of your application\n * or library.\n *\n * <div class=\"alert is-important\">\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n * </div>\n *\n * @param spec The callback functions to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `<div #chart>{{ ... }}</div>`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @developerPreview\n */",
|
||||
"returnType": "AfterRenderRef"
|
||||
},
|
||||
{
|
||||
"name": "afterNextRender",
|
||||
"entryType": "function",
|
||||
"description": "Register a callback to be invoked the next time the application finishes rendering, during the\n`mixedReadWrite` phase.\n\n<div class=\"alert is-critical\">\n\nYou should prefer specifying an explicit phase for the callback instead, or you risk significant\nperformance degradation.\n\n</div>\n\nNote that the callback will run\n- in the order it was registered\n- on browser platforms only\n- during the `mixedReadWrite` phase\n\n<div class=\"alert is-important\">\n\nComponents are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\nYou must use caution when directly reading or writing the DOM and layout.\n\n</div>",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [
|
||||
{"name": "param", "comment": "A callback function to register"},
|
||||
{"name": "param", "comment": "Options to control the behavior of the callback"},
|
||||
{
|
||||
"name": "usageNotes",
|
||||
"comment": "Use `afterNextRender` to read or write the DOM once,\nfor example to initialize a non-Angular library.\n\n### Example\n```ts\n@Component({\n selector: 'my-chart-cmp',\n template: `<div #chart>{{ ... }}</div>`,\n})\nexport class MyChartCmp {\n @ViewChild('chart') chartRef: ElementRef;\n chart: MyChart|null;\n\n constructor() {\n afterNextRender({\n write: () => {\n this.chart = new MyChart(this.chartRef.nativeElement);\n }\n });\n }\n}\n```"
|
||||
},
|
||||
{"name": "developerPreview", "comment": ""}
|
||||
],
|
||||
"params": [
|
||||
{
|
||||
"name": "callback",
|
||||
"description": "A callback function to register",
|
||||
"type": "VoidFunction",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"description": "Options to control the behavior of the callback",
|
||||
"type": "AfterRenderOptions",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * Register a callback to be invoked the next time the application finishes rendering, during the\n * `mixedReadWrite` phase.\n *\n * <div class=\"alert is-critical\">\n *\n * You should prefer specifying an explicit phase for the callback instead, or you risk significant\n * performance degradation.\n *\n * </div>\n *\n * Note that the callback will run\n * - in the order it was registered\n * - on browser platforms only\n * - during the `mixedReadWrite` phase\n *\n * <div class=\"alert is-important\">\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n * </div>\n *\n * @param callback A callback function to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `<div #chart>{{ ... }}</div>`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @developerPreview\n */",
|
||||
"returnType": "AfterRenderRef"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [
|
||||
{
|
||||
"name": "callbackOrSpec",
|
||||
"description": "",
|
||||
"type": "VoidFunction | { earlyRead?: () => unknown; write?: (r?: unknown) => unknown; mixedReadWrite?: (r?: unknown) => unknown; read?: (r?: unknown) => void; }",
|
||||
"isOptional": false,
|
||||
"isRestParam": false
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"description": "",
|
||||
"type": "AfterRenderOptions",
|
||||
"isOptional": true,
|
||||
"isRestParam": false
|
||||
}
|
||||
],
|
||||
"isNewType": false,
|
||||
"returnType": "AfterRenderRef",
|
||||
"generics": [],
|
||||
"name": "afterNextRender",
|
||||
"description": "",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [],
|
||||
"rawComment": ""
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "",
|
||||
"jsdocTags": [],
|
||||
"rawComment": "",
|
||||
"source": {
|
||||
"fileName": "/packages/core/src/user-profile.ts",
|
||||
"line": 3,
|
||||
"character": 7
|
||||
"filePath": "/packages/core/src/render3/after_render_hooks.ts",
|
||||
"startLine": 442,
|
||||
"endLine": 450
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "provideClientHydration",
|
||||
"signatures": [
|
||||
{
|
||||
"name": "provideClientHydration",
|
||||
"entryType": "function",
|
||||
"description": "Sets up providers necessary to enable hydration functionality for the application.\n\nBy default, the function enables the recommended set of features for the optimal\nperformance for most of the applications. It includes the following features:\n\n* Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\ntransferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n[here](guide/ssr#caching-data-when-using-httpclient).\n\nThese functions allow you to disable some of the default features or enable new ones:\n\n* {@link withNoHttpTransferCache} to disable HTTP transfer cache\n* {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n* {@link withI18nSupport} to enable hydration support for i18n blocks\n* {@link withEventReplay} to enable support for replaying user events",
|
||||
"generics": [],
|
||||
"isNewType": false,
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "usageNotes",
|
||||
"comment": "Basic example of how you can enable hydration in your application when\n`bootstrapApplication` function is used:\n```\nbootstrapApplication(AppComponent, {\n providers: [provideClientHydration()]\n});\n```\n\nAlternatively if you are using NgModules, you would add `provideClientHydration`\nto your root app module's provider list.\n```\n@NgModule({\n declarations: [RootCmp],\n bootstrap: [RootCmp],\n providers: [provideClientHydration()],\n})\nexport class AppModule {}\n```"
|
||||
},
|
||||
{"name": "see", "comment": "{@link withNoHttpTransferCache}"},
|
||||
{"name": "see", "comment": "{@link withHttpTransferCacheOptions}"},
|
||||
{"name": "see", "comment": "{@link withI18nSupport}"},
|
||||
{"name": "see", "comment": "{@link withEventReplay}"},
|
||||
{
|
||||
"name": "param",
|
||||
"comment": "Optional features to configure additional router behaviors."
|
||||
},
|
||||
{"name": "returns", "comment": "A set of providers to enable hydration."},
|
||||
{"name": "publicApi", "comment": ""}
|
||||
],
|
||||
"params": [
|
||||
{
|
||||
"name": "features",
|
||||
"description": "Optional features to configure additional router behaviors.",
|
||||
"type": "HydrationFeature<HydrationFeatureKind>[]",
|
||||
"isOptional": false,
|
||||
"isRestParam": true
|
||||
}
|
||||
],
|
||||
"rawComment": "/**\n * Sets up providers necessary to enable hydration functionality for the application.\n *\n * By default, the function enables the recommended set of features for the optimal\n * performance for most of the applications. It includes the following features:\n *\n * * Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n * * [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\n * transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n * [here](guide/ssr#caching-data-when-using-httpclient).\n *\n * These functions allow you to disable some of the default features or enable new ones:\n *\n * * {@link withNoHttpTransferCache} to disable HTTP transfer cache\n * * {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n * * {@link withI18nSupport} to enable hydration support for i18n blocks\n * * {@link withEventReplay} to enable support for replaying user events\n *\n * @usageNotes\n *\n * Basic example of how you can enable hydration in your application when\n * `bootstrapApplication` function is used:\n * ```\n * bootstrapApplication(AppComponent, {\n * providers: [provideClientHydration()]\n * });\n * ```\n *\n * Alternatively if you are using NgModules, you would add `provideClientHydration`\n * to your root app module's provider list.\n * ```\n * @NgModule({\n * declarations: [RootCmp],\n * bootstrap: [RootCmp],\n * providers: [provideClientHydration()],\n * })\n * export class AppModule {}\n * ```\n *\n * @see {@link withNoHttpTransferCache}\n * @see {@link withHttpTransferCacheOptions}\n * @see {@link withI18nSupport}\n * @see {@link withEventReplay}\n *\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to enable hydration.\n *\n * @publicApi\n */",
|
||||
"returnType": "EnvironmentProviders"
|
||||
}
|
||||
],
|
||||
"implementation": {
|
||||
"params": [
|
||||
{
|
||||
"name": "features",
|
||||
"description": "Optional features to configure additional router behaviors.",
|
||||
"type": "HydrationFeature<HydrationFeatureKind>[]",
|
||||
"isOptional": false,
|
||||
"isRestParam": true
|
||||
}
|
||||
],
|
||||
"isNewType": false,
|
||||
"returnType": "EnvironmentProviders",
|
||||
"returnDescription": "A set of providers to enable hydration.",
|
||||
"generics": [],
|
||||
"name": "provideClientHydration",
|
||||
"description": "Sets up providers necessary to enable hydration functionality for the application.\n\nBy default, the function enables the recommended set of features for the optimal\nperformance for most of the applications. It includes the following features:\n\n* Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\ntransferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n[here](guide/ssr#caching-data-when-using-httpclient).\n\nThese functions allow you to disable some of the default features or enable new ones:\n\n* {@link withNoHttpTransferCache} to disable HTTP transfer cache\n* {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n* {@link withI18nSupport} to enable hydration support for i18n blocks\n* {@link withEventReplay} to enable support for replaying user events",
|
||||
"entryType": "function",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "usageNotes",
|
||||
"comment": "Basic example of how you can enable hydration in your application when\n`bootstrapApplication` function is used:\n```\nbootstrapApplication(AppComponent, {\n providers: [provideClientHydration()]\n});\n```\n\nAlternatively if you are using NgModules, you would add `provideClientHydration`\nto your root app module's provider list.\n```\n@NgModule({\n declarations: [RootCmp],\n bootstrap: [RootCmp],\n providers: [provideClientHydration()],\n})\nexport class AppModule {}\n```"
|
||||
},
|
||||
{"name": "see", "comment": "{@link withNoHttpTransferCache}"},
|
||||
{"name": "see", "comment": "{@link withHttpTransferCacheOptions}"},
|
||||
{"name": "see", "comment": "{@link withI18nSupport}"},
|
||||
{"name": "see", "comment": "{@link withEventReplay}"},
|
||||
{
|
||||
"name": "param",
|
||||
"comment": "Optional features to configure additional router behaviors."
|
||||
},
|
||||
{"name": "returns", "comment": "A set of providers to enable hydration."},
|
||||
{"name": "publicApi", "comment": ""}
|
||||
],
|
||||
"rawComment": "/**\n * Sets up providers necessary to enable hydration functionality for the application.\n *\n * By default, the function enables the recommended set of features for the optimal\n * performance for most of the applications. It includes the following features:\n *\n * * Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n * * [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\n * transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n * [here](guide/ssr#caching-data-when-using-httpclient).\n *\n * These functions allow you to disable some of the default features or enable new ones:\n *\n * * {@link withNoHttpTransferCache} to disable HTTP transfer cache\n * * {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n * * {@link withI18nSupport} to enable hydration support for i18n blocks\n * * {@link withEventReplay} to enable support for replaying user events\n *\n * @usageNotes\n *\n * Basic example of how you can enable hydration in your application when\n * `bootstrapApplication` function is used:\n * ```\n * bootstrapApplication(AppComponent, {\n * providers: [provideClientHydration()]\n * });\n * ```\n *\n * Alternatively if you are using NgModules, you would add `provideClientHydration`\n * to your root app module's provider list.\n * ```\n * @NgModule({\n * declarations: [RootCmp],\n * bootstrap: [RootCmp],\n * providers: [provideClientHydration()],\n * })\n * export class AppModule {}\n * ```\n *\n * @see {@link withNoHttpTransferCache}\n * @see {@link withHttpTransferCacheOptions}\n * @see {@link withI18nSupport}\n * @see {@link withEventReplay}\n *\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to enable hydration.\n *\n * @publicApi\n */"
|
||||
},
|
||||
"entryType": "function",
|
||||
"description": "Sets up providers necessary to enable hydration functionality for the application.\n\nBy default, the function enables the recommended set of features for the optimal\nperformance for most of the applications. It includes the following features:\n\n* Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\ntransferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n[here](guide/ssr#caching-data-when-using-httpclient).\n\nThese functions allow you to disable some of the default features or enable new ones:\n\n* {@link withNoHttpTransferCache} to disable HTTP transfer cache\n* {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n* {@link withI18nSupport} to enable hydration support for i18n blocks\n* {@link withEventReplay} to enable support for replaying user events",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "usageNotes",
|
||||
"comment": "Basic example of how you can enable hydration in your application when\n`bootstrapApplication` function is used:\n```\nbootstrapApplication(AppComponent, {\n providers: [provideClientHydration()]\n});\n```\n\nAlternatively if you are using NgModules, you would add `provideClientHydration`\nto your root app module's provider list.\n```\n@NgModule({\n declarations: [RootCmp],\n bootstrap: [RootCmp],\n providers: [provideClientHydration()],\n})\nexport class AppModule {}\n```"
|
||||
},
|
||||
{"name": "see", "comment": "{@link withNoHttpTransferCache}"},
|
||||
{"name": "see", "comment": "{@link withHttpTransferCacheOptions}"},
|
||||
{"name": "see", "comment": "{@link withI18nSupport}"},
|
||||
{"name": "see", "comment": "{@link withEventReplay}"},
|
||||
{"name": "param", "comment": "Optional features to configure additional router behaviors."},
|
||||
{"name": "returns", "comment": "A set of providers to enable hydration."},
|
||||
{"name": "publicApi", "comment": ""}
|
||||
],
|
||||
"rawComment": "/**\n * Sets up providers necessary to enable hydration functionality for the application.\n *\n * By default, the function enables the recommended set of features for the optimal\n * performance for most of the applications. It includes the following features:\n *\n * * Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n * * [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\n * transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n * [here](guide/ssr#caching-data-when-using-httpclient).\n *\n * These functions allow you to disable some of the default features or enable new ones:\n *\n * * {@link withNoHttpTransferCache} to disable HTTP transfer cache\n * * {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n * * {@link withI18nSupport} to enable hydration support for i18n blocks\n * * {@link withEventReplay} to enable support for replaying user events\n *\n * @usageNotes\n *\n * Basic example of how you can enable hydration in your application when\n * `bootstrapApplication` function is used:\n * ```\n * bootstrapApplication(AppComponent, {\n * providers: [provideClientHydration()]\n * });\n * ```\n *\n * Alternatively if you are using NgModules, you would add `provideClientHydration`\n * to your root app module's provider list.\n * ```\n * @NgModule({\n * declarations: [RootCmp],\n * bootstrap: [RootCmp],\n * providers: [provideClientHydration()],\n * })\n * export class AppModule {}\n * ```\n *\n * @see {@link withNoHttpTransferCache}\n * @see {@link withHttpTransferCacheOptions}\n * @see {@link withI18nSupport}\n * @see {@link withEventReplay}\n *\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to enable hydration.\n *\n * @publicApi\n */",
|
||||
"source": {
|
||||
"filePath": "/packages/platform-browser/src/hydration.ts",
|
||||
"startLine": 201,
|
||||
"endLine": 238
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "AfterRenderOptions",
|
||||
"isAbstract": false,
|
||||
"entryType": "interface",
|
||||
"members": [
|
||||
{
|
||||
"name": "injector",
|
||||
"type": "Injector",
|
||||
"memberType": "property",
|
||||
"memberTags": ["optional"],
|
||||
"description": "The `Injector` to use during creation.\n\nIf this is not provided, the current injection context will be used instead (via `inject`).",
|
||||
"jsdocTags": []
|
||||
},
|
||||
{
|
||||
"name": "phase",
|
||||
"type": "AfterRenderPhase",
|
||||
"memberType": "property",
|
||||
"memberTags": ["optional"],
|
||||
"description": "The phase the callback should be invoked in.\n\n<div class=\"alert is-critical\">\n\nDefaults to `AfterRenderPhase.MixedReadWrite`. You should choose a more specific\nphase instead. See `AfterRenderPhase` for more information.\n\n</div>",
|
||||
"jsdocTags": [
|
||||
{
|
||||
"name": "deprecated",
|
||||
"comment": "Specify the phase for your callback to run in by passing a spec-object as the first\nparameter to `afterRender` or `afterNextRender` instead of a function."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"generics": [],
|
||||
"description": "Options passed to `afterRender` and `afterNextRender`.",
|
||||
"jsdocTags": [{"name": "developerPreview", "comment": ""}],
|
||||
"rawComment": "/**\n * Options passed to `afterRender` and `afterNextRender`.\n *\n * @developerPreview\n */",
|
||||
"implements": [],
|
||||
"source": {
|
||||
"filePath": "/packages/core/src/render3/after_render_hooks.ts",
|
||||
"startLine": 103,
|
||||
"endLine": 125
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import {runfiles} from '@bazel/runfiles';
|
||||
import {readFile} from 'fs/promises';
|
||||
import {JSDOM} from 'jsdom';
|
||||
import {renderEntry} from '../rendering';
|
||||
import {getRenderable} from '../processing';
|
||||
import {initHighlighter} from '../shiki/shiki';
|
||||
import {configureMarkedGlobally} from '../marked/configuration';
|
||||
import {setSymbols} from '../symbol-context';
|
||||
|
||||
// Note: The tests will probably break if the schema of the api extraction changes.
|
||||
// All entries in the fake-entries are extracted from Angular's api.
|
||||
// You can just generate them an copy/replace the items in the fake-entries file.
|
||||
|
||||
describe('markdown to html', () => {
|
||||
const entries = new Map<string, DocumentFragment>();
|
||||
const entries2 = new Map<string, string>();
|
||||
|
||||
beforeAll(async () => {
|
||||
await initHighlighter();
|
||||
await configureMarkedGlobally();
|
||||
|
||||
const entryContent = await readFile(runfiles.resolvePackageRelative('fake-entries.json'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
const entryJson = JSON.parse(entryContent) as any;
|
||||
const symbols = new Map<string, string>([
|
||||
['AfterRenderPhase', 'core'],
|
||||
['afterRender', 'core'],
|
||||
]);
|
||||
setSymbols(symbols);
|
||||
for (const entry of entryJson.entries) {
|
||||
const renderableJson = getRenderable(entry, '@angular/fakeentry');
|
||||
const fragment = JSDOM.fragment(await renderEntry(renderableJson));
|
||||
entries.set(entry['name'], fragment);
|
||||
entries2.set(entry['name'], await renderEntry(renderableJson));
|
||||
}
|
||||
});
|
||||
|
||||
it('should render description correctly', () => {
|
||||
const afterNextRenderEntry = entries.get('afterNextRender')!;
|
||||
const header = afterNextRenderEntry.querySelector('.docs-reference-header')!;
|
||||
expect(header).toBeDefined();
|
||||
expect(header.outerHTML).not.toContain('```');
|
||||
|
||||
console.log(entries2.get('afterNextRender'));
|
||||
const list = afterNextRenderEntry.querySelector('ul')!;
|
||||
expect(list).toBeDefined();
|
||||
|
||||
// List are rendered
|
||||
expect(list.outerHTML).toContain('<li>');
|
||||
|
||||
// Code blocks are rendered
|
||||
expect(list.outerHTML).toContain('<code>mixedReadWrite</code>');
|
||||
});
|
||||
|
||||
it('should render multiple {@link} blocks', () => {
|
||||
const provideClientHydrationEntry = entries.get('provideClientHydration')!;
|
||||
expect(provideClientHydrationEntry).toBeDefined();
|
||||
const cardItem = provideClientHydrationEntry.querySelector('.docs-reference-card-item')!;
|
||||
expect(cardItem.innerHTML).not.toContain('@link');
|
||||
});
|
||||
|
||||
it('should create cross-links', () => {
|
||||
const entry = entries.get('AfterRenderOptions')!;
|
||||
expect(entry).toBeDefined();
|
||||
|
||||
// In the description
|
||||
const descriptionItem = entry.querySelector('.docs-reference-description')!;
|
||||
expect(descriptionItem.innerHTML).toContain('<a href="/api/core/afterRender">afterRender</a>');
|
||||
|
||||
// In the card
|
||||
const cardItem = entry.querySelectorAll('.docs-reference-card-item')[1];
|
||||
expect(cardItem.innerHTML).toContain(
|
||||
'<a href="/api/core/AfterRenderPhase#MixedReadWrite">AfterRenderPhase.MixedReadWrite</a>',
|
||||
);
|
||||
});
|
||||
});
|
||||
+65
-2
@@ -1,8 +1,21 @@
|
||||
import {setCurrentSymbol, setSymbols} from '../../symbol-context';
|
||||
import {addHtmlAdditionalLinks} from '../../transforms/jsdoc-transforms';
|
||||
|
||||
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
|
||||
describe('jsdoc transforms', () => {
|
||||
it('should transform links', () => {
|
||||
setCurrentSymbol('Router');
|
||||
setSymbols(
|
||||
new Map([
|
||||
['Route', 'test'],
|
||||
['Router', 'test'],
|
||||
['Router.someMethod', 'test'],
|
||||
['Router.someMethodWithParenthesis', 'test'],
|
||||
['FormGroup', 'test'],
|
||||
['FormGroup.someMethod', 'test'],
|
||||
]),
|
||||
);
|
||||
|
||||
const entry = addHtmlAdditionalLinks({
|
||||
jsdocTags: [
|
||||
{
|
||||
@@ -21,6 +34,30 @@ describe('jsdoc transforms', () => {
|
||||
name: 'see',
|
||||
comment: '{@link Route Something else}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link #someMethod}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link #someMethodWithParenthesis()}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link someMethod()}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link FormGroup.someMethod()}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link https://angular.dev/api/core/ApplicationRef}',
|
||||
},
|
||||
{
|
||||
name: 'see',
|
||||
comment: '{@link https://angular.dev}',
|
||||
},
|
||||
],
|
||||
moduleName: 'test',
|
||||
});
|
||||
@@ -39,12 +76,38 @@ describe('jsdoc transforms', () => {
|
||||
|
||||
expect(entry.additionalLinks[2]).toEqual({
|
||||
label: 'Route',
|
||||
url: 'api/test/Route',
|
||||
url: '/api/test/Route',
|
||||
});
|
||||
|
||||
expect(entry.additionalLinks[3]).toEqual({
|
||||
label: 'Something else',
|
||||
url: 'api/test/Route',
|
||||
url: '/api/test/Route',
|
||||
});
|
||||
|
||||
expect(entry.additionalLinks[4]).toEqual({
|
||||
label: 'someMethod',
|
||||
url: '/api/test/Router#someMethod',
|
||||
});
|
||||
expect(entry.additionalLinks[5]).toEqual({
|
||||
label: 'someMethodWithParenthesis()',
|
||||
url: '/api/test/Router#someMethodWithParenthesis',
|
||||
});
|
||||
expect(entry.additionalLinks[6]).toEqual({
|
||||
label: 'someMethod()',
|
||||
url: '/api/test/Router#someMethod',
|
||||
});
|
||||
expect(entry.additionalLinks[7]).toEqual({
|
||||
label: 'FormGroup.someMethod()',
|
||||
url: '/api/test/FormGroup#someMethod',
|
||||
});
|
||||
|
||||
expect(entry.additionalLinks[8]).toEqual({
|
||||
label: 'ApplicationRef',
|
||||
url: 'https://angular.dev/api/core/ApplicationRef',
|
||||
});
|
||||
expect(entry.additionalLinks[9]).toEqual({
|
||||
label: 'angular.dev',
|
||||
url: 'https://angular.dev',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
addHtmlUsageNotes,
|
||||
setEntryFlags,
|
||||
} from './jsdoc-transforms';
|
||||
import {addRenderableGroupMembers} from './member-transforms';
|
||||
import {addRenderableMembers} from './member-transforms';
|
||||
import {addModuleName} from './module-name';
|
||||
|
||||
/** Given an unprocessed class entry, get the fully renderable class entry. */
|
||||
@@ -26,7 +26,7 @@ export function getClassRenderable(
|
||||
): ClassEntryRenderable {
|
||||
return setEntryFlags(
|
||||
addRenderableCodeToc(
|
||||
addRenderableGroupMembers(
|
||||
addRenderableMembers(
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(classEntry, moduleName))),
|
||||
|
||||
@@ -62,7 +62,7 @@ function getArgumentsLabel(command: CliCommand): string {
|
||||
if (args.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return command.command.replace(`ng ${command.name} `, '');
|
||||
return command.command.replace(`${command.name} `, '');
|
||||
}
|
||||
|
||||
function getArgs(command: CliCommand): CliOption[] {
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
|
||||
import {
|
||||
DocEntry,
|
||||
FunctionEntry,
|
||||
FunctionSignatureMetadata,
|
||||
MemberEntry,
|
||||
MemberTags,
|
||||
ParameterEntry,
|
||||
PropertyEntry,
|
||||
isFunctionEntryWithOverloads,
|
||||
} from '../entities';
|
||||
|
||||
import {
|
||||
isClassEntry,
|
||||
isClassMethodEntry,
|
||||
isConstantEntry,
|
||||
isDecoratorEntry,
|
||||
isDeprecatedEntry,
|
||||
isEnumEntry,
|
||||
isFunctionEntry,
|
||||
@@ -31,9 +31,11 @@ import {
|
||||
} from '../entities/categorization';
|
||||
import {CodeLineRenderable} from '../entities/renderables';
|
||||
import {HasModuleName, HasRenderableToc} from '../entities/traits';
|
||||
import {codeToHtml} from '../shiki/shiki';
|
||||
import {getModuleName} from '../symbol-context';
|
||||
import {codeToHtml, replaceKeywordFromShikiHtml} from '../shiki/shiki';
|
||||
|
||||
import {filterLifecycleMethods, mergeGettersAndSetters} from './member-transforms';
|
||||
import {getLinkToModule} from './url-transforms';
|
||||
|
||||
// Allows to generate links for code lines.
|
||||
interface CodeTableOfContentsData {
|
||||
@@ -64,7 +66,19 @@ export function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
|
||||
const metadata = mapDocEntryToCode(entry);
|
||||
appendPrefixAndSuffix(entry, metadata);
|
||||
|
||||
const codeWithSyntaxHighlighting = codeToHtml(metadata.contents, 'typescript');
|
||||
let codeWithSyntaxHighlighting = codeToHtml(metadata.contents, 'typescript', {
|
||||
removeFunctionKeyword: true,
|
||||
});
|
||||
|
||||
if (isDecoratorEntry(entry)) {
|
||||
// Shiki requires a keyword for correct formating of Decorators
|
||||
// We use an interface and then replace it with a '@'
|
||||
codeWithSyntaxHighlighting = replaceKeywordFromShikiHtml(
|
||||
'interface',
|
||||
codeWithSyntaxHighlighting,
|
||||
'@',
|
||||
);
|
||||
}
|
||||
|
||||
// shiki returns the lines wrapped by 2 node : 1 pre node, 1 code node.
|
||||
// As leveraging jsdom isn't trivial here, we rely on a regex to extract the line nodes
|
||||
@@ -77,8 +91,13 @@ export function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
|
||||
const insideCode = match[2];
|
||||
const afterCode = match[3];
|
||||
|
||||
const lines = splitLines(insideCode);
|
||||
const groups = groupCodeLines(lines, metadata);
|
||||
// Note: Don't expect enum value in signatures to be linked correctly
|
||||
// as skihi already splits them into separate span blocks.
|
||||
// Only the enum itself will recieve a link
|
||||
const codeWithLinks = addApiLinksToHtml(insideCode);
|
||||
|
||||
const lines = splitLines(codeWithLinks);
|
||||
const groups = groupCodeLines(lines, metadata, entry);
|
||||
|
||||
return {
|
||||
...entry,
|
||||
@@ -89,11 +108,12 @@ export function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
|
||||
}
|
||||
|
||||
/** Group overloaded methods */
|
||||
function groupCodeLines(lines: string[], metadata: CodeTableOfContentsData) {
|
||||
function groupCodeLines(lines: string[], metadata: CodeTableOfContentsData, entry: DocEntry) {
|
||||
const hasSingleSignature = isFunctionEntry(entry) && entry.signatures.length === 1;
|
||||
return lines.reduce((groups, line, index) => {
|
||||
const tocItem = {
|
||||
const tocItem: CodeLineRenderable = {
|
||||
contents: line,
|
||||
id: metadata.codeLineNumbersWithIdentifiers.get(index),
|
||||
id: hasSingleSignature ? undefined : metadata.codeLineNumbersWithIdentifiers.get(index),
|
||||
isDeprecated: metadata.deprecatedLineNumbers.some((lineNumber) => lineNumber === index),
|
||||
};
|
||||
|
||||
@@ -109,46 +129,56 @@ function groupCodeLines(lines: string[], metadata: CodeTableOfContentsData) {
|
||||
}
|
||||
|
||||
export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
|
||||
const isDeprecated = isDeprecatedEntry(entry);
|
||||
const deprecatedLineNumbers = isDeprecated ? [0] : [];
|
||||
|
||||
if (isClassEntry(entry)) {
|
||||
const members = filterLifecycleMethods(mergeGettersAndSetters(entry.members));
|
||||
return getCodeTocData(members, true);
|
||||
return getCodeTocData(members, true, isDeprecated);
|
||||
}
|
||||
|
||||
if (isDecoratorEntry(entry)) {
|
||||
return getCodeTocData(entry.members, true, isDeprecated);
|
||||
}
|
||||
|
||||
if (isConstantEntry(entry)) {
|
||||
const isDeprecated = isDeprecatedEntry(entry);
|
||||
return {
|
||||
contents: `const ${entry.name}: ${entry.type};`,
|
||||
codeLineNumbersWithIdentifiers: new Map(),
|
||||
deprecatedLineNumbers: isDeprecated ? [0] : [],
|
||||
deprecatedLineNumbers,
|
||||
};
|
||||
}
|
||||
|
||||
if (isEnumEntry(entry)) {
|
||||
return getCodeTocData(entry.members, true);
|
||||
return getCodeTocData(entry.members, true, isDeprecated);
|
||||
}
|
||||
|
||||
if (isInterfaceEntry(entry)) {
|
||||
return getCodeTocData(mergeGettersAndSetters(entry.members), true);
|
||||
return getCodeTocData(mergeGettersAndSetters(entry.members), true, isDeprecated);
|
||||
}
|
||||
|
||||
if (isFunctionEntry(entry)) {
|
||||
const isDeprecated = isDeprecatedEntry(entry);
|
||||
const codeLineNumbersWithIdentifiers = new Map<number, string>();
|
||||
const hasSingleSignature = entry.signatures.length === 1;
|
||||
|
||||
if (isFunctionEntryWithOverloads(entry) && entry.overloads) {
|
||||
if (entry.signatures.length > 0) {
|
||||
const initialMetadata: CodeTableOfContentsData = {
|
||||
contents: '',
|
||||
codeLineNumbersWithIdentifiers: new Map<number, string>(),
|
||||
deprecatedLineNumbers: [],
|
||||
deprecatedLineNumbers,
|
||||
};
|
||||
|
||||
return entry.overloads.reduce(
|
||||
(acc: CodeTableOfContentsData, curr: FunctionEntry, index: number) => {
|
||||
return entry.signatures.reduce(
|
||||
(acc: CodeTableOfContentsData, curr: FunctionSignatureMetadata, index: number) => {
|
||||
const lineNumber = index;
|
||||
acc.codeLineNumbersWithIdentifiers.set(lineNumber, curr.name);
|
||||
acc.contents += `${curr.name}(${curr.params
|
||||
.map((param) => mapParamEntry(param))
|
||||
.join(`, `)}): ${curr.returnType}\n`;
|
||||
acc.codeLineNumbersWithIdentifiers.set(lineNumber, `${curr.name}_${index}`);
|
||||
acc.contents += getMethodCodeLine(curr, [], hasSingleSignature, true);
|
||||
|
||||
// We don't want to add line break after the last item
|
||||
if (!hasSingleSignature && index < entry.signatures.length - 1) {
|
||||
acc.contents += '\n';
|
||||
}
|
||||
|
||||
if (isDeprecatedEntry(curr)) {
|
||||
acc.deprecatedLineNumbers.push(lineNumber);
|
||||
}
|
||||
@@ -157,11 +187,12 @@ export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
|
||||
initialMetadata,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
// It is important to add the function keyword as shiki will only highlight valid ts
|
||||
contents: `function ${getMethodCodeLine(entry, [], true)}`,
|
||||
contents: `function ${getMethodCodeLine(entry.implementation, [], true)}`,
|
||||
codeLineNumbersWithIdentifiers,
|
||||
deprecatedLineNumbers: isDeprecated ? [0] : [],
|
||||
deprecatedLineNumbers,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,16 +237,13 @@ export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
|
||||
return {
|
||||
contents: lines.join('\n'),
|
||||
codeLineNumbersWithIdentifiers,
|
||||
deprecatedLineNumbers: [],
|
||||
deprecatedLineNumbers,
|
||||
};
|
||||
}
|
||||
|
||||
if (isTypeAliasEntry(entry)) {
|
||||
const isDeprecated = isDeprecatedEntry(entry);
|
||||
const contents = `type ${entry.name} = ${entry.type}`;
|
||||
|
||||
let deprecatedLineNumbers = [];
|
||||
|
||||
if (isDeprecated) {
|
||||
const numberOfLinesOfCode = getNumberOfLinesOfCode(contents);
|
||||
|
||||
@@ -234,36 +262,54 @@ export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
|
||||
return {
|
||||
contents: '',
|
||||
codeLineNumbersWithIdentifiers: new Map(),
|
||||
deprecatedLineNumbers: [],
|
||||
deprecatedLineNumbers,
|
||||
};
|
||||
}
|
||||
|
||||
/** Generate code ToC data for list of members. */
|
||||
function getCodeTocData(members: MemberEntry[], hasPrefixLine: boolean): CodeTableOfContentsData {
|
||||
function getCodeTocData(
|
||||
members: MemberEntry[],
|
||||
hasPrefixLine: boolean,
|
||||
isDeprecated: boolean,
|
||||
): CodeTableOfContentsData {
|
||||
const initialMetadata: CodeTableOfContentsData = {
|
||||
contents: '',
|
||||
codeLineNumbersWithIdentifiers: new Map<number, string>(),
|
||||
deprecatedLineNumbers: [],
|
||||
deprecatedLineNumbers: isDeprecated ? [0] : [],
|
||||
};
|
||||
// In case when hasPrefixLine is true we should take it into account when we're generating
|
||||
// `codeLineNumbersWithIdentifiers` below.
|
||||
const skip = !!hasPrefixLine ? 1 : 0;
|
||||
let lineNumber = skip;
|
||||
|
||||
return members.reduce((acc: CodeTableOfContentsData, curr: MemberEntry, index: number) => {
|
||||
const lineNumber = index + skip;
|
||||
acc.codeLineNumbersWithIdentifiers.set(lineNumber, curr.name);
|
||||
acc.contents += ` ${getCodeLine(curr).trim()}\n`;
|
||||
if (isDeprecatedEntry(curr)) {
|
||||
acc.deprecatedLineNumbers.push(lineNumber);
|
||||
const setTocData = (entry: DocEntry | MemberEntry, content: string) => {
|
||||
acc.contents += ` ${content.trim()}\n`;
|
||||
acc.codeLineNumbersWithIdentifiers.set(lineNumber, entry.name);
|
||||
if (isDeprecatedEntry(entry)) {
|
||||
acc.deprecatedLineNumbers.push(lineNumber);
|
||||
}
|
||||
|
||||
lineNumber++;
|
||||
};
|
||||
|
||||
if (isClassMethodEntry(curr)) {
|
||||
if (curr.signatures.length > 0) {
|
||||
curr.signatures.forEach((signature) => {
|
||||
setTocData(signature, getMethodCodeLine(signature, curr.memberTags));
|
||||
});
|
||||
} else {
|
||||
setTocData(curr, getMethodCodeLine(curr.implementation, curr.memberTags));
|
||||
}
|
||||
} else {
|
||||
setTocData(curr, getCodeLine(curr));
|
||||
}
|
||||
return acc;
|
||||
}, initialMetadata);
|
||||
}
|
||||
|
||||
function getCodeLine(member: MemberEntry) {
|
||||
if (isClassMethodEntry(member)) {
|
||||
return getMethodCodeLine(member, member.memberTags);
|
||||
} else if (isGetterEntry(member)) {
|
||||
function getCodeLine(member: MemberEntry): string {
|
||||
if (isGetterEntry(member)) {
|
||||
return getGetterCodeLine(member);
|
||||
} else if (isSetterEntry(member)) {
|
||||
return getSetterCodeLine(member);
|
||||
@@ -281,11 +327,13 @@ function getPropertyCodeLine(member: PropertyEntry): string {
|
||||
|
||||
/** Map method entry to text */
|
||||
function getMethodCodeLine(
|
||||
member: FunctionEntry,
|
||||
member: FunctionSignatureMetadata,
|
||||
memberTags: MemberTags[] = [],
|
||||
displayParamsInNewLines: boolean = false,
|
||||
isFunction: boolean = false,
|
||||
): string {
|
||||
return `${memberTags.join(' ')} ${member.name}(${displayParamsInNewLines ? '\n ' : ''}${member.params
|
||||
displayParamsInNewLines &&= member.params.length > 0;
|
||||
return `${isFunction ? 'function' : ''}${memberTags.join(' ')} ${member.name}(${displayParamsInNewLines ? '\n ' : ''}${member.params
|
||||
.map((param) => mapParamEntry(param))
|
||||
.join(`,${displayParamsInNewLines ? '\n ' : ' '}`)}${
|
||||
displayParamsInNewLines ? '\n' : ''
|
||||
@@ -344,7 +392,7 @@ function getNumberOfLinesOfCode(contents: string): number {
|
||||
/** Prints an initializer function signature into a single line. */
|
||||
export function printInitializerFunctionSignatureLine(
|
||||
name: string,
|
||||
signature: FunctionEntry,
|
||||
signature: FunctionSignatureMetadata,
|
||||
showTypesInSignaturePreview: boolean,
|
||||
): string {
|
||||
let res = name;
|
||||
@@ -381,7 +429,7 @@ export function printInitializerFunctionSignatureLine(
|
||||
res += `: ${signature.returnType}`;
|
||||
}
|
||||
res += ';';
|
||||
return res;
|
||||
return `function ${res}`;
|
||||
}
|
||||
|
||||
function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContentsData): void {
|
||||
@@ -393,16 +441,63 @@ function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContents
|
||||
data.contents = `${firstLine}\n${data.contents}${lastLine}`;
|
||||
};
|
||||
|
||||
if (isClassEntry(entry)) {
|
||||
const abstractPrefix = entry.isAbstract ? 'abstract ' : '';
|
||||
appendFirstAndLastLines(codeTocData, `${abstractPrefix}class ${entry.name} {`, `}`);
|
||||
if (isClassEntry(entry) || isInterfaceEntry(entry)) {
|
||||
const generics =
|
||||
entry.generics?.length > 0
|
||||
? `<${entry.generics
|
||||
.map((g) => (g.constraint ? `${g.name} extends ${g.constraint}` : g.name))
|
||||
.join(', ')}>`
|
||||
: '';
|
||||
|
||||
const extendsStr = entry.extends ? ` extends ${entry.extends}` : '';
|
||||
// TODO: remove the ? when we distinguish Class & Decorator entries
|
||||
const implementsStr =
|
||||
entry.implements?.length > 0 ? ` implements ${entry.implements.join(' ,')}` : '';
|
||||
|
||||
const signature = `${entry.name}${generics}${extendsStr}${implementsStr}`;
|
||||
if (isClassEntry(entry)) {
|
||||
const abstractPrefix = entry.isAbstract ? 'abstract ' : '';
|
||||
appendFirstAndLastLines(codeTocData, `${abstractPrefix}class ${signature} {`, `}`);
|
||||
}
|
||||
|
||||
if (isInterfaceEntry(entry)) {
|
||||
appendFirstAndLastLines(codeTocData, `interface ${signature} {`, `}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnumEntry(entry)) {
|
||||
appendFirstAndLastLines(codeTocData, `enum ${entry.name} {`, `}`);
|
||||
}
|
||||
|
||||
if (isInterfaceEntry(entry)) {
|
||||
appendFirstAndLastLines(codeTocData, `interface ${entry.name} {`, `}`);
|
||||
if (isDecoratorEntry(entry)) {
|
||||
appendFirstAndLastLines(codeTocData, `interface ${entry.name} ({`, `})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces any code block that isn't already wrapped by an anchor element
|
||||
* by a link if the symbol is known
|
||||
*/
|
||||
export function addApiLinksToHtml(htmlString: string): string {
|
||||
const result = htmlString.replace(
|
||||
// This regex looks for span/code blocks not wrapped by an anchor block.
|
||||
// Their content are then replaced with a link if the symbol is known
|
||||
// The captured content ==> vvvvvvvv
|
||||
/(?<!<a[^>]*>)(<(?:(?:span)|(?:code))[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g,
|
||||
(type: string, span1: string, potentialSymbolName: string, span2: string) => {
|
||||
let [symbol, subSymbol] = potentialSymbolName.split(/(?:#|\.)/) as [string, string?];
|
||||
|
||||
// mySymbol() => mySymbol
|
||||
const symbolWithoutInvocation = symbol.replace(/\([^)]*\);?/g, '');
|
||||
const moduleName = getModuleName(symbolWithoutInvocation)!;
|
||||
|
||||
if (moduleName) {
|
||||
return `${span1}<a href="${getLinkToModule(moduleName, symbol, subSymbol)}">${potentialSymbolName}</a>${span2}`;
|
||||
}
|
||||
|
||||
return type;
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DecoratorEntry} from '../entities';
|
||||
import {DecoratorEntryRenderable} from '../entities/renderables';
|
||||
import {addRenderableCodeToc} from './code-transforms';
|
||||
import {
|
||||
addHtmlAdditionalLinks,
|
||||
addHtmlDescription,
|
||||
addHtmlJsDocTagComments,
|
||||
addHtmlUsageNotes,
|
||||
setEntryFlags,
|
||||
} from './jsdoc-transforms';
|
||||
import {addRenderableMembers} from './member-transforms';
|
||||
import {addModuleName} from './module-name';
|
||||
|
||||
/** Given an unprocessed class entry, get the fully renderable class entry. */
|
||||
export function getDecoratorRenderable(
|
||||
classEntry: DecoratorEntry,
|
||||
moduleName: string,
|
||||
): DecoratorEntryRenderable {
|
||||
return setEntryFlags(
|
||||
addRenderableCodeToc(
|
||||
addRenderableMembers(
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(classEntry, moduleName))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
) as DecoratorEntryRenderable;
|
||||
}
|
||||
@@ -6,9 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {FunctionEntry, isFunctionEntryWithOverloads} from '../entities';
|
||||
import {FunctionEntryRenderable} from '../entities/renderables';
|
||||
import {HasRenderableOverloads} from '../entities/traits';
|
||||
import {FunctionEntry, FunctionSignatureMetadata} from '../entities';
|
||||
import {
|
||||
FunctionEntryRenderable,
|
||||
FunctionSignatureMetadataRenderable,
|
||||
} from '../entities/renderables';
|
||||
import {addRenderableCodeToc} from './code-transforms';
|
||||
import {
|
||||
addHtmlAdditionalLinks,
|
||||
@@ -27,15 +29,10 @@ export function getFunctionRenderable(
|
||||
): FunctionEntryRenderable {
|
||||
return setEntryFlags(
|
||||
addRenderableCodeToc(
|
||||
addRenderableFunctionParams(
|
||||
addOverloads(
|
||||
moduleName,
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
setEntryFlags(
|
||||
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
|
||||
),
|
||||
),
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
setEntryFlags(
|
||||
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -43,15 +40,17 @@ export function getFunctionRenderable(
|
||||
);
|
||||
}
|
||||
|
||||
function addOverloads<T extends FunctionEntry>(
|
||||
moduleName: string,
|
||||
entry: T,
|
||||
): T & HasRenderableOverloads {
|
||||
return {
|
||||
...entry,
|
||||
overloads:
|
||||
isFunctionEntryWithOverloads(entry) && entry.overloads
|
||||
? entry.overloads.map((overload) => getFunctionRenderable(overload, moduleName))
|
||||
: null,
|
||||
};
|
||||
export function getFunctionMetadataRenderable(
|
||||
entry: FunctionSignatureMetadata,
|
||||
moduleName: string = '',
|
||||
): FunctionSignatureMetadataRenderable {
|
||||
return addHtmlAdditionalLinks(
|
||||
addRenderableFunctionParams(
|
||||
addHtmlUsageNotes(
|
||||
setEntryFlags(
|
||||
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
addHtmlUsageNotes,
|
||||
setEntryFlags,
|
||||
} from './jsdoc-transforms';
|
||||
import {addRenderableGroupMembers} from './member-transforms';
|
||||
import {addRenderableMembers} from './member-transforms';
|
||||
import {addModuleName} from './module-name';
|
||||
|
||||
/** Given an unprocessed interface entry, get the fully renderable interface entry. */
|
||||
@@ -26,7 +26,7 @@ export function getInterfaceRenderable(
|
||||
): InterfaceEntryRenderable {
|
||||
return setEntryFlags(
|
||||
addRenderableCodeToc(
|
||||
addRenderableGroupMembers(
|
||||
addRenderableMembers(
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
} from '../entities/traits';
|
||||
|
||||
import {getLinkToModule} from './url-transforms';
|
||||
import {addApiLinksToHtml} from './code-transforms';
|
||||
import {getCurrentSymbol, getModuleName, logUnknownSymbol} from '../symbol-context';
|
||||
|
||||
export const JS_DOC_USAGE_NOTES_TAG = 'usageNotes';
|
||||
export const JS_DOC_SEE_TAG = 'see';
|
||||
@@ -37,6 +39,7 @@ export const JS_DOC_DESCRIPTION_TAG = 'description';
|
||||
|
||||
// Some links are written in the following format: {@link Route}
|
||||
const jsDoclinkRegex = /\{\s*@link\s+([^}]+)\s*\}/;
|
||||
const jsDoclinkRegexGlobal = new RegExp(jsDoclinkRegex.source, 'g');
|
||||
|
||||
/** Given an entity with a description, gets the entity augmented with an `htmlDescription`. */
|
||||
export function addHtmlDescription<T extends HasDescription & HasModuleName>(
|
||||
@@ -100,15 +103,18 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
|
||||
) as string)
|
||||
: '';
|
||||
|
||||
const transformedHtml = addApiLinksToHtml(htmlUsageNotes);
|
||||
|
||||
return {
|
||||
...entry,
|
||||
htmlUsageNotes,
|
||||
htmlUsageNotes: transformedHtml,
|
||||
};
|
||||
}
|
||||
|
||||
/** Given a markdown JsDoc text, gets the rendered HTML. */
|
||||
function getHtmlForJsDocText<T extends HasModuleName>(text: string, entry: T): string {
|
||||
return marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text), entry)) as string;
|
||||
const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string;
|
||||
return addApiLinksToHtml(parsed);
|
||||
}
|
||||
|
||||
export function setEntryFlags<T extends HasJsDocTags & HasModuleName>(
|
||||
@@ -126,9 +132,7 @@ export function setEntryFlags<T extends HasJsDocTags & HasModuleName>(
|
||||
};
|
||||
}
|
||||
|
||||
function getHtmlAdditionalLinks<T extends HasJsDocTags & HasModuleName>(
|
||||
entry: T,
|
||||
): LinkEntryRenderable[] {
|
||||
function getHtmlAdditionalLinks<T extends HasJsDocTags>(entry: T): LinkEntryRenderable[] {
|
||||
const markdownLinkRule = /\[(.*?)\]\((.*?)(?: "(.*?)")?\)/;
|
||||
|
||||
const seeAlsoLinks = entry.jsdocTags
|
||||
@@ -149,21 +153,8 @@ function getHtmlAdditionalLinks<T extends HasJsDocTags & HasModuleName>(
|
||||
|
||||
if (linkMatch) {
|
||||
const link = linkMatch[1];
|
||||
|
||||
// handling links like {@link Route Some route with description}
|
||||
const [symbol, description] = link.split(/\s(.+)/);
|
||||
if (entry && description) {
|
||||
return {
|
||||
label: description.trim(),
|
||||
url: `${getLinkToModule(entry.moduleName)}/${symbol}`,
|
||||
};
|
||||
}
|
||||
|
||||
// handling links like {@link Route}
|
||||
return {
|
||||
label: linkMatch[1].trim(),
|
||||
url: `${getLinkToModule(entry.moduleName)}/${linkMatch[1].trim()}`,
|
||||
};
|
||||
const {url, label} = parseAtLink(link);
|
||||
return {label, url};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -195,15 +186,52 @@ function convertJsDocExampleToHtmlExample(text: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
function convertLinks(text: string, entry: HasModuleName) {
|
||||
return text.replace(jsDoclinkRegex, (_, link) => {
|
||||
const [symbol, description] = link.split(/\s(.+)/);
|
||||
if (symbol && description) {
|
||||
// {@link Route Some route with description}
|
||||
return `<a href="${getLinkToModule(entry.moduleName)}/${symbol}"><code>${description}</code></a>`;
|
||||
} else {
|
||||
// {@link Route}
|
||||
return `<a href="${getLinkToModule(entry.moduleName)}/${symbol}"><code>${symbol}</code></a>`;
|
||||
}
|
||||
/**
|
||||
* Converts {@link } tags into html anchor elements
|
||||
*/
|
||||
function convertLinks(text: string) {
|
||||
return text.replace(jsDoclinkRegexGlobal, (_, link) => {
|
||||
const {label, url} = parseAtLink(link);
|
||||
|
||||
return `<a href="${url}"><code>${label}</code></a>`;
|
||||
});
|
||||
}
|
||||
|
||||
function parseAtLink(link: string): {label: string; url: string} {
|
||||
// Because of microsoft/TypeScript/issues/59679
|
||||
// getTextOfJSDocComment introduces an extra space between the symbol and a trailing ()
|
||||
link = link.replace(/ \(\)$/, '');
|
||||
|
||||
let [rawSymbol, description] = link.split(/\s(.+)/);
|
||||
if (rawSymbol.startsWith('#')) {
|
||||
rawSymbol = rawSymbol.substring(1);
|
||||
} else if (rawSymbol.startsWith('http://') || rawSymbol.startsWith('https://')) {
|
||||
return {
|
||||
url: rawSymbol,
|
||||
label: rawSymbol.split('/').pop()!,
|
||||
};
|
||||
}
|
||||
|
||||
let [symbol, subSymbol] = rawSymbol.replace(/\(\)$/, '').split(/(?:#|\.)/);
|
||||
|
||||
let moduleName = getModuleName(symbol);
|
||||
const label = description ?? rawSymbol;
|
||||
|
||||
const currentSymbol = getCurrentSymbol();
|
||||
|
||||
if (!moduleName) {
|
||||
// 2nd attemp, try to get the module name in the context of the current symbol
|
||||
moduleName = getModuleName(`${currentSymbol}.${symbol}`);
|
||||
|
||||
if (!moduleName || !currentSymbol) {
|
||||
// TODO: remove the links that generate this error
|
||||
// TODO: throw an error when there are no more warning generated
|
||||
logUnknownSymbol(link, symbol);
|
||||
return {label, url: '#'};
|
||||
}
|
||||
subSymbol = symbol;
|
||||
symbol = currentSymbol;
|
||||
}
|
||||
|
||||
return {label, url: getLinkToModule(moduleName, symbol, subSymbol)};
|
||||
}
|
||||
|
||||
@@ -8,14 +8,7 @@
|
||||
|
||||
import {MemberEntry, MemberTags, MemberType} from '../entities';
|
||||
|
||||
import {isClassMethodEntry} from '../entities/categorization';
|
||||
import {MemberEntryRenderable} from '../entities/renderables';
|
||||
import {
|
||||
HasMembers,
|
||||
HasModuleName,
|
||||
HasRenderableMembers,
|
||||
HasRenderableMembersGroups,
|
||||
} from '../entities/traits';
|
||||
import {HasMembers, HasModuleName, HasRenderableMembers} from '../entities/traits';
|
||||
|
||||
import {
|
||||
addHtmlDescription,
|
||||
@@ -69,44 +62,13 @@ export function mergeGettersAndSetters(members: MemberEntry[]): MemberEntry[] {
|
||||
);
|
||||
}
|
||||
|
||||
/** Given an entity with members, gets the entity augmented with renderable members. */
|
||||
export function addRenderableGroupMembers<T extends HasMembers & HasModuleName>(
|
||||
entry: T,
|
||||
): T & HasRenderableMembersGroups {
|
||||
const members = filterLifecycleMethods(entry.members);
|
||||
|
||||
const membersGroups = members.reduce((groups, item) => {
|
||||
const member = setEntryFlags(
|
||||
addMethodParamsDescription(
|
||||
addHtmlDescription(
|
||||
addHtmlUsageNotes(addHtmlJsDocTagComments(addModuleName(item, entry.moduleName))),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (groups.has(member.name)) {
|
||||
const group = groups.get(member.name);
|
||||
group?.push(member);
|
||||
} else {
|
||||
groups.set(member.name, [member]);
|
||||
}
|
||||
return groups;
|
||||
}, new Map<string, MemberEntryRenderable[]>());
|
||||
|
||||
return {
|
||||
...entry,
|
||||
membersGroups,
|
||||
};
|
||||
}
|
||||
|
||||
export function addRenderableMembers<T extends HasMembers & HasModuleName>(
|
||||
entry: T,
|
||||
): T & HasRenderableMembers {
|
||||
const members = entry.members.map((member) =>
|
||||
setEntryFlags(
|
||||
addMethodParamsDescription(
|
||||
addHtmlDescription(
|
||||
addHtmlUsageNotes(addHtmlJsDocTagComments(addModuleName(member, entry.moduleName))),
|
||||
),
|
||||
addHtmlDescription(
|
||||
addHtmlUsageNotes(addHtmlJsDocTagComments(addModuleName(member, entry.moduleName))),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -116,15 +78,3 @@ export function addRenderableMembers<T extends HasMembers & HasModuleName>(
|
||||
members,
|
||||
};
|
||||
}
|
||||
|
||||
function addMethodParamsDescription<T extends MemberEntry & HasModuleName>(entry: T): T {
|
||||
if (isClassMethodEntry(entry)) {
|
||||
return {
|
||||
...entry,
|
||||
params: entry.params.map((param) =>
|
||||
addHtmlDescription(addModuleName(param, entry.moduleName)),
|
||||
),
|
||||
};
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,14 @@
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
export const API_PREFIX = 'api';
|
||||
export const MODULE_NAME_PREFIX = '@angular/';
|
||||
|
||||
export function removeAngularPrefixFromModule(moduleName: string): string {
|
||||
return moduleName.replace(MODULE_NAME_PREFIX, '');
|
||||
}
|
||||
|
||||
export function getLinkToModule(moduleName: string) {
|
||||
return `${API_PREFIX}/${removeAngularPrefixFromModule(moduleName)}`;
|
||||
export function getLinkToModule(moduleName: string, symbol: string, subSymbol?: string) {
|
||||
return `/${API_PREFIX}/${moduleName}/${symbol}${subSymbol ? `#${subSymbol}` : ''}`;
|
||||
}
|
||||
|
||||
export const normalizePath = (path: string): string => {
|
||||
|
||||
@@ -18,8 +18,8 @@ export interface DocsCodeToken extends CodeToken {
|
||||
|
||||
// Capture group 1: all attributes on the opening tag
|
||||
// Capture group 2: all content between the open and close tags
|
||||
const singleFileSelfClosingCodeRule = /^\s*<docs-code\s([^>]*)((?:.(?!\/>))*)\/>/s;
|
||||
const singleFileCodeRule = /^\s*<docs-code\s([^>]*)>((?:.(?!\/docs-code))*)<\/docs-code>/s;
|
||||
const singleFileCodeRule =
|
||||
/^\s*<docs-code((?:\s+[\w-]+(?:="[^"]*"|='[^']*'|=[^\s>]*)?)*)\s*(?:\/>|>(.*?)<\/docs-code>)/s;
|
||||
|
||||
const pathRule = /path="([^"]*)"/;
|
||||
const headerRule = /header="([^"]*)"/;
|
||||
@@ -38,10 +38,7 @@ export const docsCodeExtension = {
|
||||
return src.match(/^<docs-code\s/)?.index;
|
||||
},
|
||||
tokenizer(this: TokenizerThis, src: string): DocsCodeToken | undefined {
|
||||
const code = singleFileCodeRule.exec(src);
|
||||
const selfClosingCode = singleFileSelfClosingCodeRule.exec(src);
|
||||
const match = selfClosingCode ?? code;
|
||||
|
||||
const match = singleFileCodeRule.exec(src);
|
||||
if (match) {
|
||||
const attr = match[1].trim();
|
||||
|
||||
@@ -55,7 +52,7 @@ export const docsCodeExtension = {
|
||||
const visibleRegion = visibleRegionRule.exec(attr);
|
||||
const preview = previewRule.exec(attr) ? true : false;
|
||||
|
||||
let code = match[2].trim();
|
||||
let code = match[2]?.trim() ?? '';
|
||||
if (path && path[1]) {
|
||||
code = loadWorkspaceRelativeFile(path[1]);
|
||||
// Remove ESLint Comments
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user