Compare commits
20 Commits
11.0.0-nex
...
10.1.0
Author | SHA1 | Date | |
---|---|---|---|
c01bd0fe8e | |||
5588324802 | |||
437ecc8090 | |||
0dda97ea66 | |||
5e4aeaa348 | |||
cbbf8b542f | |||
91dfb18840 | |||
e44ddf5baa | |||
6b1a505566 | |||
659705ad78 | |||
8864b0ed69 | |||
4e596b672f | |||
83866827c3 | |||
7006cac50a | |||
dd82f2fefd | |||
bf003340ab | |||
5e35edd724 | |||
c132dcd0ae | |||
bbe331569b | |||
21e9a0032c |
@ -653,10 +653,8 @@ jobs:
|
|||||||
name: Starting Saucelabs tunnel service
|
name: Starting Saucelabs tunnel service
|
||||||
command: ./tools/saucelabs/sauce-service.sh run
|
command: ./tools/saucelabs/sauce-service.sh run
|
||||||
background: true
|
background: true
|
||||||
# add module umd tsc compile option so the test can work
|
- run: yarn tsc -p packages
|
||||||
# properly in the legacy browsers
|
- run: yarn tsc -p modules
|
||||||
- run: yarn tsc -p packages --module UMD
|
|
||||||
- run: yarn tsc -p modules --module UMD
|
|
||||||
- run: yarn bazel build //packages/zone.js:npm_package
|
- run: yarn bazel build //packages/zone.js:npm_package
|
||||||
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
|
# 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
|
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
|
||||||
|
2
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
2
.github/ISSUE_TEMPLATE/1-bug-report.md
vendored
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: "\U0001F41E Bug report"
|
name: "\U0001F41EBug report"
|
||||||
about: Report a bug in the Angular Framework
|
about: Report a bug in the Angular Framework
|
||||||
---
|
---
|
||||||
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
|
||||||
|
2
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
2
.github/ISSUE_TEMPLATE/2-feature-request.md
vendored
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: "\U0001F680 Feature request"
|
name: "\U0001F680Feature request"
|
||||||
about: Suggest a feature for Angular Framework
|
about: Suggest a feature for Angular Framework
|
||||||
|
|
||||||
---
|
---
|
||||||
|
2
.github/ISSUE_TEMPLATE/5-support-request.md
vendored
2
.github/ISSUE_TEMPLATE/5-support-request.md
vendored
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: "❓ Support request"
|
name: "❓Support request"
|
||||||
about: Questions and requests for support
|
about: Questions and requests for support
|
||||||
|
|
||||||
---
|
---
|
||||||
|
2
.github/ISSUE_TEMPLATE/6-angular-cli.md
vendored
2
.github/ISSUE_TEMPLATE/6-angular-cli.md
vendored
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: "\U0001F6E0️ Angular CLI"
|
name: "\U0001F6E0️Angular CLI"
|
||||||
about: Issues and feature requests for Angular CLI
|
about: Issues and feature requests for Angular CLI
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: "\U0001F48E Angular Components"
|
name: "\U0001F48EAngular Components"
|
||||||
about: Issues and feature requests for Angular Components
|
about: Issues and feature requests for Angular Components
|
||||||
|
|
||||||
---
|
---
|
||||||
|
2
.github/workflows/lock-closed.yml
vendored
2
.github/workflows/lock-closed.yml
vendored
@ -10,6 +10,6 @@ jobs:
|
|||||||
if: github.repository == 'angular/angular'
|
if: github.repository == 'angular/angular'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: angular/dev-infra/github-actions/lock-closed@414834b2b24dd2df37c6ed00808387ee6fd91b66
|
- uses: angular/dev-infra/github-actions/lock-closed@66462f6
|
||||||
with:
|
with:
|
||||||
lock-bot-key: ${{ secrets.LOCK_BOT_PRIVATE_KEY }}
|
lock-bot-key: ${{ secrets.LOCK_BOT_PRIVATE_KEY }}
|
||||||
|
69
CHANGELOG.md
69
CHANGELOG.md
@ -1,71 +1,3 @@
|
|||||||
<a name="11.0.0-next.1"></a>
|
|
||||||
# 11.0.0-next.1 (2020-09-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-cli:** compute source-mappings for localized strings ([#38645](https://github.com/angular/angular/issues/38645)) ([7e0b3fd](https://github.com/angular/angular/commit/7e0b3fd)), closes [#38588](https://github.com/angular/angular/issues/38588)
|
|
||||||
* **core:** remove CollectionChangeRecord symbol ([#38668](https://github.com/angular/angular/issues/38668)) ([fdea180](https://github.com/angular/angular/commit/fdea180))
|
|
||||||
* **router:** support lazy loading for empty path named outlets ([#38379](https://github.com/angular/angular/issues/38379)) ([926ffcd](https://github.com/angular/angular/commit/926ffcd)), closes [#12842](https://github.com/angular/angular/issues/12842)
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* **core:** CollectionChangeRecord has been removed, use IterableChangeRecord
|
|
||||||
instead
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="10.1.1"></a>
|
|
||||||
## 10.1.1 (2020-09-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler:** correct confusion between field and property names ([#38685](https://github.com/angular/angular/issues/38685)) ([a1c34c6](https://github.com/angular/angular/commit/a1c34c6))
|
|
||||||
* **compiler-cli:** compute source-mappings for localized strings ([#38747](https://github.com/angular/angular/issues/38747)) ([b4eb016](https://github.com/angular/angular/commit/b4eb016)), closes [#38588](https://github.com/angular/angular/issues/38588)
|
|
||||||
* **compiler-cli:** ensure that a declaration is available in type-to-value conversion ([#38684](https://github.com/angular/angular/issues/38684)) ([56d5ff2](https://github.com/angular/angular/commit/56d5ff2)), closes [#38670](https://github.com/angular/angular/issues/38670)
|
|
||||||
* **core:** reset `tView` between tests in Ivy TestBed ([#38659](https://github.com/angular/angular/issues/38659)) ([efc7606](https://github.com/angular/angular/commit/efc7606)), closes [#38600](https://github.com/angular/angular/issues/38600)
|
|
||||||
* **localize:** do not expose NodeJS typings in $localize runtime code ([#38700](https://github.com/angular/angular/issues/38700)) ([4de8dc3](https://github.com/angular/angular/commit/4de8dc3)), closes [#38692](https://github.com/angular/angular/issues/38692)
|
|
||||||
* **localize:** enable whitespace preservation marker in XLIFF files ([#38737](https://github.com/angular/angular/issues/38737)) ([190dca0](https://github.com/angular/angular/commit/190dca0)), closes [#38679](https://github.com/angular/angular/issues/38679)
|
|
||||||
* **localize:** install `[@angular](https://github.com/angular)/localize` in `devDependencies` by default ([#38680](https://github.com/angular/angular/issues/38680)) ([dbab744](https://github.com/angular/angular/commit/dbab744)), closes [#38329](https://github.com/angular/angular/issues/38329)
|
|
||||||
* **localize:** render context of translation file parse errors ([#38673](https://github.com/angular/angular/issues/38673)) ([32f33f0](https://github.com/angular/angular/commit/32f33f0)), closes [#38377](https://github.com/angular/angular/issues/38377)
|
|
||||||
* **localize:** render location in XLIFF 2 even if there is no metadata ([#38713](https://github.com/angular/angular/issues/38713)) ([ab4f953](https://github.com/angular/angular/commit/ab4f953)), closes [#38705](https://github.com/angular/angular/issues/38705)
|
|
||||||
* **ngcc:** use aliased exported types correctly ([#38666](https://github.com/angular/angular/issues/38666)) ([6a28675](https://github.com/angular/angular/commit/6a28675)), closes [#38238](https://github.com/angular/angular/issues/38238)
|
|
||||||
* **router:** If users are using the Alt key when clicking the router links, prioritize browser’s default behavior ([#38375](https://github.com/angular/angular/issues/38375)) ([309709d](https://github.com/angular/angular/commit/309709d))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* **core:** use `ngDevMode` to tree-shake error messages ([#38612](https://github.com/angular/angular/issues/38612)) ([b084bff](https://github.com/angular/angular/commit/b084bff))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="11.0.0-next.0"></a>
|
|
||||||
# 11.0.0-next.0 (2020-09-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **forms:** ensure to emit `statusChanges` on subsequent value update/validations ([#38354](https://github.com/angular/angular/issues/38354)) ([d9fea85](https://github.com/angular/angular/commit/d9fea85)), closes [#20424](https://github.com/angular/angular/issues/20424) [#14542](https://github.com/angular/angular/issues/14542)
|
|
||||||
* **service-worker:** fix condition to check for a cache-busted request ([#36847](https://github.com/angular/angular/issues/36847)) ([5be4edf](https://github.com/angular/angular/commit/5be4edf))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **service-worker:** add `UnrecoverableStateError` ([#36847](https://github.com/angular/angular/issues/36847)) ([036a2fa](https://github.com/angular/angular/commit/036a2fa)), closes [#36539](https://github.com/angular/angular/issues/36539)
|
|
||||||
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* **forms:** Previously if FormControl, FormGroup and FormArray class instances had async validators
|
|
||||||
defined at initialization time, the status change event was not emitted once async validator
|
|
||||||
completed. After this change the status event is emitted into the `statusChanges` observable.
|
|
||||||
If your code relies on the old behavior, you can filter/ignore this additional status change
|
|
||||||
event.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="10.1.0"></a>
|
<a name="10.1.0"></a>
|
||||||
# 10.1.0 (2020-09-02)
|
# 10.1.0 (2020-09-02)
|
||||||
|
|
||||||
@ -120,6 +52,7 @@ event.
|
|||||||
* **router:** properly compare array queryParams for equality ([#37709](https://github.com/angular/angular/issues/37709)) ([#37860](https://github.com/angular/angular/issues/37860)) ([1801d0c](https://github.com/angular/angular/commit/1801d0c))
|
* **router:** properly compare array queryParams for equality ([#37709](https://github.com/angular/angular/issues/37709)) ([#37860](https://github.com/angular/angular/issues/37860)) ([1801d0c](https://github.com/angular/angular/commit/1801d0c))
|
||||||
* **router:** remove parenthesis for primary outlet segment after removing auxiliary outlet segment ([#24656](https://github.com/angular/angular/issues/24656)) ([#37163](https://github.com/angular/angular/issues/37163)) ([71f008f](https://github.com/angular/angular/commit/71f008f))
|
* **router:** remove parenthesis for primary outlet segment after removing auxiliary outlet segment ([#24656](https://github.com/angular/angular/issues/24656)) ([#37163](https://github.com/angular/angular/issues/37163)) ([71f008f](https://github.com/angular/angular/commit/71f008f))
|
||||||
* **router:** restore 'history.state' object for navigations coming from Angular router ([#28108](https://github.com/angular/angular/issues/28108)) ([#28176](https://github.com/angular/angular/issues/28176)) ([df76a20](https://github.com/angular/angular/commit/df76a20))
|
* **router:** restore 'history.state' object for navigations coming from Angular router ([#28108](https://github.com/angular/angular/issues/28108)) ([#28176](https://github.com/angular/angular/issues/28176)) ([df76a20](https://github.com/angular/angular/commit/df76a20))
|
||||||
|
* **router:** support lazy loading for empty path named outlets ([#38379](https://github.com/angular/angular/issues/38379)) ([7ad3264](https://github.com/angular/angular/commit/7ad3264)), closes [#12842](https://github.com/angular/angular/issues/12842)
|
||||||
|
|
||||||
### Code Refactoring
|
### Code Refactoring
|
||||||
* **router:** export DefaultRouteReuseStrategy to Router public_api ([#31575](https://github.com/angular/angular/issues/31575)) ([ca79880](https://github.com/angular/angular/commit/ca79880))
|
* **router:** export DefaultRouteReuseStrategy to Router public_api ([#31575](https://github.com/angular/angular/issues/31575)) ([ca79880](https://github.com/angular/angular/commit/ca79880))
|
||||||
|
@ -16,6 +16,13 @@ import {BuildNums, PrNums, SHA} from './constants';
|
|||||||
|
|
||||||
const logger = new Logger('mock-external-apis');
|
const logger = new Logger('mock-external-apis');
|
||||||
|
|
||||||
|
const log = (...args: any[]) => {
|
||||||
|
// Filter out non-matching URL checks
|
||||||
|
if (!/^matching.+: false$/.test(args[0])) {
|
||||||
|
logger.log(...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const AIO_CIRCLE_CI_TOKEN = getEnvVar('AIO_CIRCLE_CI_TOKEN');
|
const AIO_CIRCLE_CI_TOKEN = getEnvVar('AIO_CIRCLE_CI_TOKEN');
|
||||||
const AIO_GITHUB_TOKEN = getEnvVar('AIO_GITHUB_TOKEN');
|
const AIO_GITHUB_TOKEN = getEnvVar('AIO_GITHUB_TOKEN');
|
||||||
|
|
||||||
@ -84,8 +91,8 @@ const createArchive = (buildNum: number, prNum: number, sha: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create request scopes
|
// Create request scopes
|
||||||
const circleCiApi = nock(CIRCLE_CI_API_HOST).persist();
|
const circleCiApi = nock(CIRCLE_CI_API_HOST).log(log).persist();
|
||||||
const githubApi = nock(GITHUB_API_HOST).persist().matchHeader('Authorization', `token ${AIO_GITHUB_TOKEN}`);
|
const githubApi = nock(GITHUB_API_HOST).log(log).persist().matchHeader('Authorization', `token ${AIO_GITHUB_TOKEN}`);
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
|
@ -27,28 +27,28 @@
|
|||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"delete-empty": "^3.0.0",
|
"delete-empty": "^3.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"jasmine": "^3.6.1",
|
"jasmine": "^3.5.0",
|
||||||
"nock": "^13.0.4",
|
"nock": "^12.0.3",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.0",
|
||||||
"shelljs": "^0.8.4",
|
"shelljs": "^0.8.4",
|
||||||
"source-map-support": "^0.5.19",
|
"source-map-support": "^0.5.19",
|
||||||
"tar-stream": "^2.1.3",
|
"tar-stream": "^2.1.2",
|
||||||
"tslib": "^2.0.1"
|
"tslib": "^1.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/body-parser": "^1.19.0",
|
"@types/body-parser": "^1.19.0",
|
||||||
"@types/express": "^4.17.8",
|
"@types/express": "^4.17.6",
|
||||||
"@types/jasmine": "^3.5.14",
|
"@types/jasmine": "^3.5.10",
|
||||||
"@types/nock": "^11.1.0",
|
"@types/nock": "^11.1.0",
|
||||||
"@types/node": "^14.6.4",
|
"@types/node": "^13.13.2",
|
||||||
"@types/node-fetch": "^2.5.7",
|
"@types/node-fetch": "^2.5.7",
|
||||||
"@types/shelljs": "^0.8.8",
|
"@types/shelljs": "^0.8.7",
|
||||||
"@types/supertest": "^2.0.10",
|
"@types/supertest": "^2.0.8",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.3",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.1",
|
||||||
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
|
||||||
"typescript": "^4.0.2"
|
"typescript": "^3.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,24 +214,23 @@ describe('GithubApi', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should call \'https.request()\' with the correct options', async () => {
|
it('should call \'https.request()\' with the correct options', () => {
|
||||||
const requestHandler = nock('https://api.github.com')
|
const requestHandler = nock('https://api.github.com')
|
||||||
.intercept('/path', 'method')
|
.intercept('/path', 'method')
|
||||||
.reply(200);
|
.reply(200);
|
||||||
|
|
||||||
await (api as any).request('method', '/path');
|
(api as any).request('method', '/path');
|
||||||
requestHandler.done();
|
requestHandler.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should add the \'Authorization\' header containing the \'githubToken\'', async () => {
|
it('should add the \'Authorization\' header containing the \'githubToken\'', () => {
|
||||||
const requestHandler = nock('https://api.github.com')
|
const requestHandler = nock('https://api.github.com')
|
||||||
.intercept('/path', 'method', undefined, {
|
.intercept('/path', 'method', undefined, {
|
||||||
reqheaders: {Authorization: 'token 12345'},
|
reqheaders: {Authorization: 'token 12345'},
|
||||||
})
|
})
|
||||||
.reply(200);
|
.reply(200);
|
||||||
|
(api as any).request('method', '/path');
|
||||||
await (api as any).request('method', '/path');
|
|
||||||
requestHandler.done();
|
requestHandler.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -245,13 +244,12 @@ describe('GithubApi', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should \'JSON.stringify\' and send the data along with the request', async () => {
|
it('should \'JSON.stringify\' and send the data along with the request', () => {
|
||||||
const data = {key: 'value'};
|
const data = {key: 'value'};
|
||||||
const requestHandler = nock('https://api.github.com')
|
const requestHandler = nock('https://api.github.com')
|
||||||
.intercept('/path', 'method', JSON.stringify(data))
|
.intercept('/path', 'method', JSON.stringify(data))
|
||||||
.reply(200);
|
.reply(200);
|
||||||
|
(api as any).request('method', '/path', data);
|
||||||
await (api as any).request('method', '/path', data);
|
|
||||||
requestHandler.done();
|
requestHandler.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
* This example project is special in that it is not a cli app. To run tests appropriate for this
|
|
||||||
* project, the test command is overwritten in `aio/content/examples/observables/example-config.json`.
|
|
||||||
*
|
|
||||||
* This is an empty placeholder file to ensure that `aio/tools/examples/run-example-e2e.js` runs
|
|
||||||
* tests for this project.
|
|
||||||
*
|
|
||||||
* TODO: Fix our infrastructure/tooling, so that this hack is not necessary.
|
|
||||||
*/
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"cmd": "yarn",
|
|
||||||
"args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cmd": "yarn",
|
|
||||||
"args": ["jasmine", "out-tsc/**/*.spec.js"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
import { docRegionChain, docRegionObservable, docRegionUnsubscribe } from './observables';
|
|
||||||
|
|
||||||
describe('observables', () => {
|
|
||||||
it('should print 2', (doneFn: DoneFn) => {
|
|
||||||
const consoleLogSpy = spyOn(console, 'log');
|
|
||||||
const observable = docRegionObservable(console);
|
|
||||||
observable.subscribe(() => {
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledWith(2);
|
|
||||||
doneFn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should close the subscription', () => {
|
|
||||||
const subscription = docRegionUnsubscribe();
|
|
||||||
expect(subscription.closed).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should chain an observable', (doneFn: DoneFn) => {
|
|
||||||
const observable = docRegionChain();
|
|
||||||
observable.subscribe(value => {
|
|
||||||
expect(value).toBe(4);
|
|
||||||
doneFn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,72 +1,40 @@
|
|||||||
// #docplaster
|
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
export function docRegionObservable(console: Console) {
|
// #docregion observable
|
||||||
// #docregion observable
|
|
||||||
|
|
||||||
// declare a publishing operation
|
// declare a publishing operation
|
||||||
const observable = new Observable<number>(observer => {
|
const observable = new Observable<number>(observer => {
|
||||||
// Subscriber fn...
|
// Subscriber fn...
|
||||||
// #enddocregion observable
|
});
|
||||||
// The below code is used for unit testing only
|
|
||||||
observer.next(2);
|
|
||||||
// #docregion observable
|
|
||||||
});
|
|
||||||
|
|
||||||
// initiate execution
|
// initiate execution
|
||||||
observable.subscribe(value => {
|
observable.subscribe(() => {
|
||||||
// observer handles notifications
|
// observer handles notifications
|
||||||
// #enddocregion observable
|
});
|
||||||
// The below code is used for unit testing only
|
|
||||||
console.log(value);
|
|
||||||
// #docregion observable
|
|
||||||
});
|
|
||||||
|
|
||||||
// #enddocregion observable
|
// #enddocregion observable
|
||||||
return observable;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function docRegionUnsubscribe() {
|
// #docregion unsubscribe
|
||||||
const observable = new Observable<number>(() => {
|
|
||||||
// Subscriber fn...
|
|
||||||
});
|
|
||||||
// #docregion unsubscribe
|
|
||||||
|
|
||||||
const subscription = observable.subscribe(() => {
|
const subscription = observable.subscribe(() => {
|
||||||
// observer handles notifications
|
// observer handles notifications
|
||||||
});
|
});
|
||||||
|
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
|
|
||||||
// #enddocregion unsubscribe
|
// #enddocregion unsubscribe
|
||||||
return subscription;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function docRegionError() {
|
// #docregion error
|
||||||
const observable = new Observable<number>(() => {
|
|
||||||
// Subscriber fn...
|
|
||||||
});
|
|
||||||
|
|
||||||
// #docregion error
|
observable.subscribe(() => {
|
||||||
observable.subscribe(() => {
|
throw Error('my error');
|
||||||
throw new Error('my error');
|
});
|
||||||
});
|
|
||||||
// #enddocregion error
|
|
||||||
}
|
|
||||||
|
|
||||||
export function docRegionChain() {
|
// #enddocregion error
|
||||||
let observable = new Observable<number>(observer => {
|
|
||||||
// Subscriber fn...
|
|
||||||
observer.next(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
observable =
|
// #docregion chain
|
||||||
// #docregion chain
|
|
||||||
|
|
||||||
observable.pipe(map(v => 2 * v));
|
observable.pipe(map(v => 2 * v));
|
||||||
|
|
||||||
// #enddocregion chain
|
// #enddocregion chain
|
||||||
return observable;
|
|
||||||
}
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import { docRegionError, docRegionPromise } from './promises';
|
|
||||||
|
|
||||||
describe('promises', () => {
|
|
||||||
it('should print 2', (doneFn: DoneFn) => {
|
|
||||||
const consoleLogSpy = spyOn(console, 'log');
|
|
||||||
const pr = docRegionPromise(console, 2);
|
|
||||||
pr.then((value) => {
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(consoleLogSpy).toHaveBeenCalledWith(2);
|
|
||||||
expect(value).toBe(4);
|
|
||||||
doneFn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error', (doneFn: DoneFn) => {
|
|
||||||
const promise = docRegionError();
|
|
||||||
promise
|
|
||||||
.then(() => {
|
|
||||||
throw new Error('Promise should be rejected.');
|
|
||||||
},
|
|
||||||
() => doneFn());
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,44 +1,25 @@
|
|||||||
// #docplaster
|
// #docregion promise
|
||||||
|
// initiate execution
|
||||||
export function docRegionPromise(console: Console, inputValue: number) {
|
const promise = new Promise<number>((resolve, reject) => {
|
||||||
// #docregion promise
|
|
||||||
// initiate execution
|
|
||||||
let promise = new Promise<number>((resolve, reject) => {
|
|
||||||
// Executer fn...
|
// Executer fn...
|
||||||
// #enddocregion promise
|
});
|
||||||
// The below is used in the unit tests.
|
|
||||||
resolve(inputValue);
|
promise.then(value => {
|
||||||
// #docregion promise
|
|
||||||
});
|
|
||||||
// #enddocregion promise
|
|
||||||
promise =
|
|
||||||
// #docregion promise
|
|
||||||
promise.then(value => {
|
|
||||||
// handle result here
|
// handle result here
|
||||||
// #enddocregion promise
|
});
|
||||||
// The below is used in the unit tests.
|
|
||||||
console.log(value);
|
|
||||||
return value;
|
|
||||||
// #docregion promise
|
|
||||||
});
|
|
||||||
// #enddocregion promise
|
|
||||||
promise =
|
|
||||||
// #docregion chain
|
|
||||||
promise.then(v => 2 * v);
|
|
||||||
// #enddocregion chain
|
|
||||||
|
|
||||||
return promise;
|
// #enddocregion promise
|
||||||
}
|
|
||||||
|
|
||||||
export function docRegionError() {
|
// #docregion chain
|
||||||
let promise = Promise.resolve();
|
|
||||||
promise =
|
|
||||||
// #docregion error
|
|
||||||
|
|
||||||
promise.then(() => {
|
promise.then(v => 2 * v);
|
||||||
throw new Error('my error');
|
|
||||||
});
|
|
||||||
|
|
||||||
// #enddocregion error
|
// #enddocregion chain
|
||||||
return promise;
|
|
||||||
}
|
// #docregion error
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
throw Error('my error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// #enddocregion error
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<!-- #enddocregion translated-plural -->
|
<!-- #enddocregion translated-plural -->
|
||||||
<!-- #docregion translated-select -->
|
<!-- #docregion translated-select -->
|
||||||
<!-- #docregion translate-select-1 -->
|
<!-- #docregion translate-select-1 -->
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
|
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
|
||||||
<source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
|
<source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
|
||||||
<target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
|
<target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { SwUpdate } from '@angular/service-worker';
|
|
||||||
|
|
||||||
function notifyUser(message: string): void { }
|
|
||||||
|
|
||||||
// #docregion sw-unrecoverable-state
|
|
||||||
@Injectable()
|
|
||||||
export class HandleUnrecoverableStateService {
|
|
||||||
constructor(updates: SwUpdate) {
|
|
||||||
updates.unrecoverable.subscribe(event => {
|
|
||||||
notifyUser(
|
|
||||||
`An error occurred that we cannot recover from:\n${event.reason}\n\n` +
|
|
||||||
'Please reload the page.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #enddocregion sw-unrecoverable-state
|
|
@ -62,7 +62,7 @@ In the following example, the `@Component()` metadata object and the class const
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-typical',
|
selector: 'app-typical',
|
||||||
template: '<div>A typical component for {{data.name}}</div>'
|
template: '<div>A typical component for {{data.name}}</div>'
|
||||||
})
|
)}
|
||||||
export class TypicalComponent {
|
export class TypicalComponent {
|
||||||
@Input() data: TypicalData;
|
@Input() data: TypicalData;
|
||||||
constructor(private someService: SomeService) { ... }
|
constructor(private someService: SomeService) { ... }
|
||||||
|
@ -125,7 +125,7 @@ Emulated is the default and most commonly used view encapsulation. For more info
|
|||||||
|
|
||||||
<div class="alert is-important">
|
<div class="alert is-important">
|
||||||
|
|
||||||
The shadow-piercing descendant combinator is deprecated and [support is being removed from major browsers](https://www.chromestatus.com/feature/6750456638341120) and tools.
|
The shadow-piercing descendant combinator is deprecated and [support is being removed from major browsers](https://www.chromestatus.com/features/6750456638341120) and tools.
|
||||||
As such we plan to drop support in Angular (for all 3 of `/deep/`, `>>>` and `::ng-deep`).
|
As such we plan to drop support in Angular (for all 3 of `/deep/`, `>>>` and `::ng-deep`).
|
||||||
Until then `::ng-deep` should be preferred for a broader compatibility with the tools.
|
Until then `::ng-deep` should be preferred for a broader compatibility with the tools.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ The `ng generate` command creates the `projects/my-lib` folder in your workspace
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
When you generate a new library, the workspace configuration file, `angular.json`, is updated with a project of type `library`.
|
When you generate a new library, the workspace configuration file, `angular.json`, is updated with a project of type 'library'.
|
||||||
|
|
||||||
<code-example format="json">
|
<code-example format="json">
|
||||||
"projects": {
|
"projects": {
|
||||||
@ -109,7 +109,7 @@ If you want a dropdown that would contain different passed-in values each time,
|
|||||||
|
|
||||||
Suppose you want to read a configuration file and then generate a form based on that configuration.
|
Suppose you want to read a configuration file and then generate a form based on that configuration.
|
||||||
If that form will need additional customization by the developer who is using your library, it might work best as a schematic.
|
If that form will need additional customization by the developer who is using your library, it might work best as a schematic.
|
||||||
However, if the form will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form.
|
However, if the forms will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form.
|
||||||
In general, the more complex the customization, the more useful the schematic approach.
|
In general, the more complex the customization, the more useful the schematic approach.
|
||||||
|
|
||||||
To learn more, see [Schematics Overview](guide/schematics) and [Schematics for Libraries](guide/schematics-for-libraries).
|
To learn more, see [Schematics Overview](guide/schematics) and [Schematics for Libraries](guide/schematics-for-libraries).
|
||||||
|
@ -511,9 +511,9 @@ Each script tag has a `type="module"` or `nomodule` attribute. Browsers with nat
|
|||||||
|
|
||||||
To include differential loading in your application builds, you must configure the Browserslist and TypeScript configuration files in your application project.
|
To include differential loading in your application builds, you must configure the Browserslist and TypeScript configuration files in your application project.
|
||||||
|
|
||||||
The following examples show a `.browserslistrc` and `tsconfig.json` file for a newly created Angular application. In this configuration, legacy browsers such as IE 9-11 are ignored, and the compilation target is ES2015.
|
The following examples show a `browserlistrc` and `tsconfig.json` file for a newly created Angular application. In this configuration, legacy browsers such as IE 9-11 are ignored, and the compilation target is ES2015.
|
||||||
|
|
||||||
<code-example language="none" header=".browserslistrc">
|
<code-example language="none" header="browserslistrc">
|
||||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||||
# For additional information regarding the format and rule options, please see:
|
# For additional information regarding the format and rule options, please see:
|
||||||
# https://github.com/browserslist/browserslist#queries
|
# https://github.com/browserslist/browserslist#queries
|
||||||
@ -527,7 +527,7 @@ The following examples show a `.browserslistrc` and `tsconfig.json` file for a n
|
|||||||
last 1 Chrome version
|
last 1 Chrome version
|
||||||
last 1 Firefox version
|
last 1 Firefox version
|
||||||
last 2 Edge major versions
|
last 2 Edge major versions
|
||||||
last 2 Safari major versions
|
last 2 Safari major version
|
||||||
last 2 iOS major versions
|
last 2 iOS major versions
|
||||||
Firefox ESR
|
Firefox ESR
|
||||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||||
|
@ -38,6 +38,7 @@ v9 - v12
|
|||||||
| `@angular/bazel` | [`Bazel builder and schematics`](#bazelbuilder) | v10 |
|
| `@angular/bazel` | [`Bazel builder and schematics`](#bazelbuilder) | v10 |
|
||||||
| `@angular/common` | [`ReflectiveInjector`](#reflectiveinjector) | <!--v8--> v11 |
|
| `@angular/common` | [`ReflectiveInjector`](#reflectiveinjector) | <!--v8--> v11 |
|
||||||
| `@angular/common` | [`CurrencyPipe` - `DEFAULT_CURRENCY_CODE`](api/common/CurrencyPipe#currency-code-deprecation) | <!--v9--> v11 |
|
| `@angular/common` | [`CurrencyPipe` - `DEFAULT_CURRENCY_CODE`](api/common/CurrencyPipe#currency-code-deprecation) | <!--v9--> v11 |
|
||||||
|
| `@angular/core` | [`CollectionChangeRecord`](#core) | <!--v7--> v11 |
|
||||||
| `@angular/core` | [`DefaultIterableDiffer`](#core) | <!--v7--> v11 |
|
| `@angular/core` | [`DefaultIterableDiffer`](#core) | <!--v7--> v11 |
|
||||||
| `@angular/core` | [`ReflectiveKey`](#core) | <!--v8--> v11 |
|
| `@angular/core` | [`ReflectiveKey`](#core) | <!--v8--> v11 |
|
||||||
| `@angular/core` | [`RenderComponentType`](#core) | <!--v7--> v11 |
|
| `@angular/core` | [`RenderComponentType`](#core) | <!--v7--> v11 |
|
||||||
@ -88,6 +89,7 @@ Tip: In the [API reference section](api) of this doc site, deprecated APIs are i
|
|||||||
|
|
||||||
| API | Replacement | Deprecation announced | Notes |
|
| API | Replacement | Deprecation announced | Notes |
|
||||||
| --- | ----------- | --------------------- | ----- |
|
| --- | ----------- | --------------------- | ----- |
|
||||||
|
| [`CollectionChangeRecord`](api/core/CollectionChangeRecord) | [`IterableChangeRecord`](api/core/IterableChangeRecord) | v4 | none |
|
||||||
| [`DefaultIterableDiffer`](api/core/DefaultIterableDiffer) | n/a | v4 | Not part of public API. |
|
| [`DefaultIterableDiffer`](api/core/DefaultIterableDiffer) | n/a | v4 | Not part of public API. |
|
||||||
| [`ReflectiveInjector`](api/core/ReflectiveInjector) | [`Injector.create`](api/core/Injector#create) | v5 | See [`ReflectiveInjector`](#reflectiveinjector) |
|
| [`ReflectiveInjector`](api/core/ReflectiveInjector) | [`Injector.create`](api/core/Injector#create) | v5 | See [`ReflectiveInjector`](#reflectiveinjector) |
|
||||||
| [`ReflectiveKey`](api/core/ReflectiveKey) | none | v5 | none |
|
| [`ReflectiveKey`](api/core/ReflectiveKey) | none | v5 | none |
|
||||||
|
@ -76,12 +76,6 @@ All router components must be entry components. Because this would require you t
|
|||||||
|
|
||||||
## The `entryComponents` array
|
## The `entryComponents` array
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
|
||||||
|
|
||||||
Since 9.0.0 with Ivy, the `entryComponents` property is no longer necessary. See [deprecations guide](guide/deprecations#entryComponents).
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
Though the `@NgModule` decorator has an `entryComponents` array, most of the time
|
Though the `@NgModule` decorator has an `entryComponents` array, most of the time
|
||||||
you won't have to explicitly set any entry components because Angular adds components listed in `@NgModule.bootstrap` and those in route definitions to entry components automatically. Though these two mechanisms account for most entry components, if your app happens to bootstrap or dynamically load a component by type imperatively,
|
you won't have to explicitly set any entry components because Angular adds components listed in `@NgModule.bootstrap` and those in route definitions to entry components automatically. Though these two mechanisms account for most entry components, if your app happens to bootstrap or dynamically load a component by type imperatively,
|
||||||
you must add it to `entryComponents` explicitly.
|
you must add it to `entryComponents` explicitly.
|
||||||
|
@ -62,8 +62,6 @@ Angular executes hook methods in the following sequence. You can use them to per
|
|||||||
|
|
||||||
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
Called before `ngOnInit()` and whenever one or more data-bound input properties change.
|
||||||
|
|
||||||
Note that if your component has no inputs or you use it without providing any inputs, the framework will not call `ngOnChanges()`.
|
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style='vertical-align:top'>
|
<tr style='vertical-align:top'>
|
||||||
|
@ -141,7 +141,7 @@ Because the token is now an abstract class, and the injectable component impleme
|
|||||||
The implementation of the method (with all of its code overhead) resides in the injectable component that can be tree-shaken.
|
The implementation of the method (with all of its code overhead) resides in the injectable component that can be tree-shaken.
|
||||||
This allows the parent to communicate with the child (if it is present) in a type-safe manner.
|
This allows the parent to communicate with the child (if it is present) in a type-safe manner.
|
||||||
|
|
||||||
For example, the `LibCardComponent` now queries `LibHeaderToken` rather than `LibHeaderComponent`.
|
For example, the `LibCardComponent` now queries`LibHeaderToken` rather than `LibHeaderComponent`.
|
||||||
The following example shows how the pattern allows `LibCardComponent` to communicate with the `LibHeaderComponent` without actually referring to `LibHeaderComponent`.
|
The following example shows how the pattern allows `LibCardComponent` to communicate with the `LibHeaderComponent` without actually referring to `LibHeaderComponent`.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -223,6 +223,6 @@ content harmlessly. The following is the browser output
|
|||||||
of the `evilTitle` examples.
|
of the `evilTitle` examples.
|
||||||
|
|
||||||
<code-example language="bash">
|
<code-example language="bash">
|
||||||
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
|
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.
|
||||||
"Template Syntax" is the property bound evil title.
|
"Template alert("evil never sleeps")Syntax" is the property bound evil title.
|
||||||
</code-example>
|
</code-example>
|
||||||
|
@ -102,7 +102,7 @@ The following table provides the status for Angular versions under support.
|
|||||||
Version | Status | Released | Active Ends | LTS Ends
|
Version | Status | Released | Active Ends | LTS Ends
|
||||||
------- | ------ | ------------ | ------------ | ------------
|
------- | ------ | ------------ | ------------ | ------------
|
||||||
^10.0.0 | Active | Jun 24, 2020 | Dec 24, 2020 | Dec 24, 2021
|
^10.0.0 | Active | Jun 24, 2020 | Dec 24, 2020 | Dec 24, 2021
|
||||||
^9.0.0 | LTS | Feb 06, 2020 | Aug 06, 2020 | Aug 06, 2021
|
^9.0.0 | Active | Feb 06, 2020 | Aug 06, 2020 | Aug 06, 2021
|
||||||
^8.0.0 | LTS | May 28, 2019 | Nov 28, 2019 | Nov 28, 2020
|
^8.0.0 | LTS | May 28, 2019 | Nov 28, 2019 | Nov 28, 2020
|
||||||
|
|
||||||
Angular versions ^4.0.0, ^5.0.0, ^6.0.0 and ^7.0.0 are no longer under support.
|
Angular versions ^4.0.0, ^5.0.0, ^6.0.0 and ^7.0.0 are no longer under support.
|
||||||
|
@ -53,7 +53,7 @@ RxJS provides many operators, but only a handful are used frequently. For a list
|
|||||||
|
|
||||||
| Area | Operators |
|
| Area | Operators |
|
||||||
| :------------| :----------|
|
| :------------| :----------|
|
||||||
| Creation | `from`, `fromEvent`, `of` |
|
| Creation | `from`,`fromEvent`, `of` |
|
||||||
| Combination | `combineLatest`, `concat`, `merge`, `startWith` , `withLatestFrom`, `zip` |
|
| Combination | `combineLatest`, `concat`, `merge`, `startWith` , `withLatestFrom`, `zip` |
|
||||||
| Filtering | `debounceTime`, `distinctUntilChanged`, `filter`, `take`, `takeUntil` |
|
| Filtering | `debounceTime`, `distinctUntilChanged`, `filter`, `take`, `takeUntil` |
|
||||||
| Transformation | `bufferTime`, `concatMap`, `map`, `mergeMap`, `scan`, `switchMap` |
|
| Transformation | `bufferTime`, `concatMap`, `map`, `mergeMap`, `scan`, `switchMap` |
|
||||||
|
@ -67,33 +67,6 @@ Therefore, it is recommended to reload the page once the promise returned by `ac
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Handling an unrecoverable state
|
|
||||||
|
|
||||||
In some cases, the version of the app used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload.
|
|
||||||
|
|
||||||
For example, imagine the following scenario:
|
|
||||||
- A user opens the app for the first time and the service worker caches the latest version of the app.
|
|
||||||
Let's assume the app's cached assets include `index.html`, `main.<main-hash-1>.js` and `lazy-chunk.<lazy-hash-1>.js`.
|
|
||||||
- The user closes the app and does not open it for a while.
|
|
||||||
- After some time, a new version of the app is deployed to the server.
|
|
||||||
This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js` (note that the hashes are different now, because the content of the files has changed).
|
|
||||||
The old version is no longer available on the server.
|
|
||||||
- In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache.
|
|
||||||
Browsers may decide to evict specific (or all) resources from a cache in order to reclaim disk space.
|
|
||||||
- The user opens the app again.
|
|
||||||
The service worker serves the latest version known to it at this point, namely the old version (`index.html` and `main.<main-hash-1>.js`).
|
|
||||||
- At some later point, the app requests the lazy bundle, `lazy-chunk.<lazy-hash-1>.js`.
|
|
||||||
- The service worker is unable to find the asset in the cache (remember that the browser evicted it).
|
|
||||||
Nor is it able to retrieve it from the server (since the server now only has `lazy-chunk.<lazy-hash-2>.js` from the newer version).
|
|
||||||
|
|
||||||
In the above scenario, the service worker is not able to serve an asset that would normally be cached.
|
|
||||||
That particular app version is broken and there is no way to fix the state of the client without reloading the page.
|
|
||||||
In such cases, the service worker notifies the client by sending an `UnrecoverableStateEvent` event.
|
|
||||||
You can subscribe to `SwUpdate#unrecoverable` to be notified and handle these errors.
|
|
||||||
|
|
||||||
<code-example path="service-worker-getting-started/src/app/handle-unrecoverable-state.service.ts" header="handle-unrecoverable-state.service.ts" region="sw-unrecoverable-state"></code-example>
|
|
||||||
|
|
||||||
|
|
||||||
## More on Angular service workers
|
## More on Angular service workers
|
||||||
|
|
||||||
You may also be interested in the following:
|
You may also be interested in the following:
|
||||||
|
@ -107,7 +107,7 @@ Notice that all of the files the browser needs to render this application are ca
|
|||||||
<div class="alert is-helpful">
|
<div class="alert is-helpful">
|
||||||
Pay attention to two key points:
|
Pay attention to two key points:
|
||||||
|
|
||||||
1. The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extensions. In some cases, you might want to modify the glob pattern to suit your needs.
|
1. The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extentions. In some cases, you might want to modify the glob pattern to suit your needs.
|
||||||
|
|
||||||
1. If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`.
|
1. If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`.
|
||||||
</div>
|
</div>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
@ -53,9 +53,6 @@
|
|||||||
},
|
},
|
||||||
"kyliau": {
|
"kyliau": {
|
||||||
"name": "Keen Yee Liau",
|
"name": "Keen Yee Liau",
|
||||||
"twitter": "liauky",
|
|
||||||
"website": "https://github.com/kyliau",
|
|
||||||
"bio": "Keen works on language service and CLI. He also maintains Karma and Protractor.",
|
|
||||||
"groups": ["Angular"],
|
"groups": ["Angular"],
|
||||||
"lead": "igorminar",
|
"lead": "igorminar",
|
||||||
"picture": "kyliau.jpg"
|
"picture": "kyliau.jpg"
|
||||||
@ -813,21 +810,5 @@
|
|||||||
"website": "kreuzercode.com",
|
"website": "kreuzercode.com",
|
||||||
"bio": "Kevin is a passionate freelance front-end engineer and Google Developer Expert based in Switzerland. He is a JavaScript enthusiast and fascinated by Angular. Kevin always tries to learn new things, expand his knowledge, and share it with others in the form of blog posts, workshops, podcasts, or presentations. He is a writer for various publications and the most active writer on Angular in-depth in 2019. Contributing to multiple projects and maintaining 7 npm packages, Kevin is also a big believer in open source. Furthermore, Kevin is a big football fan. Since his childhood, he has supported Real Madrid, which you might notice in a lot of his blog posts and tutorials.",
|
"bio": "Kevin is a passionate freelance front-end engineer and Google Developer Expert based in Switzerland. He is a JavaScript enthusiast and fascinated by Angular. Kevin always tries to learn new things, expand his knowledge, and share it with others in the form of blog posts, workshops, podcasts, or presentations. He is a writer for various publications and the most active writer on Angular in-depth in 2019. Contributing to multiple projects and maintaining 7 npm packages, Kevin is also a big believer in open source. Furthermore, Kevin is a big football fan. Since his childhood, he has supported Real Madrid, which you might notice in a lot of his blog posts and tutorials.",
|
||||||
"groups": ["GDE"]
|
"groups": ["GDE"]
|
||||||
},
|
|
||||||
"samvloeberghs": {
|
|
||||||
"name": "Sam Vloeberghs",
|
|
||||||
"picture": "samvloeberghs.jpg",
|
|
||||||
"groups": ["GDE"],
|
|
||||||
"twitter": "samvloeberghs",
|
|
||||||
"website": "https://samvloeberghs.be",
|
|
||||||
"bio": "Sam is a freelance software architect and Internet entrepreneur, currently focusing on frontend technologies. Co-organiser of the Belgian Angular conference NG-BE and Angular Belgium Meetup group."
|
|
||||||
},
|
|
||||||
"thekiba": {
|
|
||||||
"name": "Andrew Grekov",
|
|
||||||
"picture": "thekiba.jpg",
|
|
||||||
"twitter": "thekiba_io",
|
|
||||||
"website": "https://thekiba.io",
|
|
||||||
"bio": "Andrew is a software engineer using Angular and .NET. He spends most of his spare time staying up-to-date, helping other people, and experimenting with web tech.",
|
|
||||||
"groups": ["GDE"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,10 @@
|
|||||||
"url": "https://dev.to/t/angular",
|
"url": "https://dev.to/t/angular",
|
||||||
"title": "DEV Community"
|
"title": "DEV Community"
|
||||||
},
|
},
|
||||||
"indepth-dev": {
|
"angular-in-depth": {
|
||||||
"desc": "Peer-reviewed Angular articles and tutorials.",
|
"desc": "The place where advanced Angular concepts are explained",
|
||||||
"url": "https://indepth.dev/angular/",
|
"url": "https://blog.angularindepth.com",
|
||||||
"title": "Angular inDepth"
|
"title": "Angular In Depth"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -63,12 +63,6 @@
|
|||||||
"logo": "",
|
"logo": "",
|
||||||
"title": "NgRuAir",
|
"title": "NgRuAir",
|
||||||
"url": "https://github.com/ngRuAir/ngruair"
|
"url": "https://github.com/ngRuAir/ngruair"
|
||||||
},
|
|
||||||
"the-deep-dive": {
|
|
||||||
"desc": "The advanced web development podcast about Angular, RxJS, TypeScript and other technologies. English, audio only.",
|
|
||||||
"logo": "https://i.imgur.com/mmE5Feq.jpg",
|
|
||||||
"title": "The Deep Dive",
|
|
||||||
"url": "https://thedeepdive.simplecast.com"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,12 +429,6 @@
|
|||||||
"desc": "Jigsaw provides a set of web components based on Angular. It is supporting the development of all applications of Big Data Product of ZTE (https://www.zte.com.cn).",
|
"desc": "Jigsaw provides a set of web components based on Angular. It is supporting the development of all applications of Big Data Product of ZTE (https://www.zte.com.cn).",
|
||||||
"title": "Awade Jigsaw (Chinese)",
|
"title": "Awade Jigsaw (Chinese)",
|
||||||
"url": "https://jigsaw-zte.gitee.io"
|
"url": "https://jigsaw-zte.gitee.io"
|
||||||
},
|
|
||||||
"material-dayjs-adapter": {
|
|
||||||
"desc": "A DayJS implementation of @angular/material's DateAdapter that results in smaller bundle sizes than its MomentJS counterpart.",
|
|
||||||
"rev": true,
|
|
||||||
"title": "material-dayjs-adapter",
|
|
||||||
"url": "https://www.npmjs.com/package/@tabuckner/material-dayjs-adapter"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,13 +48,54 @@
|
|||||||
{
|
{
|
||||||
"url": "docs",
|
"url": "docs",
|
||||||
"title": "Introduction",
|
"title": "Introduction",
|
||||||
"tooltip": "Welcome to the Angular documentation set.",
|
"tooltip": "Introduction to the Angular documentation",
|
||||||
"hidden": false
|
"hidden": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Getting Started",
|
"title": "Getting Started",
|
||||||
"tooltip": "Set up your environment and learn basic concepts",
|
"tooltip": "Set up your environment and learn basic concepts",
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/setup-local",
|
||||||
|
"title": "Setup",
|
||||||
|
"tooltip": "Setting up for local development with the Angular CLI."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Angular Concepts",
|
||||||
|
"tooltip": "Introduction to basic concepts for Angular applications.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/architecture",
|
||||||
|
"title": "Intro to Basic Concepts",
|
||||||
|
"tooltip": "Basic building blocks of Angular applications."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/architecture-modules",
|
||||||
|
"title": "Intro to Modules",
|
||||||
|
"tooltip": "About NgModules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/architecture-components",
|
||||||
|
"title": "Intro to Components",
|
||||||
|
"tooltip": "About Components, Templates, and Views."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/architecture-services",
|
||||||
|
"title": "Intro to Services and DI",
|
||||||
|
"tooltip": "About services and dependency injection."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/architecture-next-steps",
|
||||||
|
"title": "Next Steps",
|
||||||
|
"tooltip": "Beyond the basics."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/glossary",
|
||||||
|
"title": "Angular Glossary",
|
||||||
|
"tooltip": "Brief definitions of the most important words in the Angular vocabulary."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Try it",
|
"title": "Try it",
|
||||||
"tooltip": "Examine and work with a ready-made sample app, with no setup.",
|
"tooltip": "Examine and work with a ready-made sample app, with no setup.",
|
||||||
@ -87,64 +128,68 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/setup-local",
|
"title": "Tutorial: Tour of Heroes",
|
||||||
"title": "Setup",
|
"tooltip": "The Tour of Heroes app is used as a reference point in many Angular examples.",
|
||||||
"tooltip": "Setting up for local development with the Angular CLI."
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "tutorial",
|
||||||
|
"title": "Introduction",
|
||||||
|
"tooltip": "Introduction to the Tour of Heroes app and tutorial"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt0",
|
||||||
|
"title": "Create a Project",
|
||||||
|
"tooltip": "Creating the application shell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt1",
|
||||||
|
"title": "1. The Hero Editor",
|
||||||
|
"tooltip": "Part 1: Build a simple editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt2",
|
||||||
|
"title": "2. Display a List",
|
||||||
|
"tooltip": "Part 2: Build a master/detail page with a list of heroes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt3",
|
||||||
|
"title": "3. Create a Feature Component",
|
||||||
|
"tooltip": "Part 3: Refactor the master/detail views into separate components."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt4",
|
||||||
|
"title": "4. Add Services",
|
||||||
|
"tooltip": "Part 4: Create a reusable service to manage hero data."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt5",
|
||||||
|
"title": "5. Add In-app Navigation",
|
||||||
|
"tooltip": "Part 5: Add the Angular router and navigate among the views."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "tutorial/toh-pt6",
|
||||||
|
"title": "6. Get Data from a Server",
|
||||||
|
"tooltip": "Part 6: Use HTTP to retrieve and save hero data."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Main Concepts",
|
"title": "Fundamentals",
|
||||||
"tooltip": "Learn the concepts essential to becoming a proficient Angular developer.",
|
"tooltip": "The fundamentals of Angular",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"title": "Components",
|
"title": "Components & Templates",
|
||||||
"tooltip": "Building dynamic views with data binding",
|
"tooltip": "Building dynamic views with data binding",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"url": "guide/displaying-data",
|
"url": "guide/displaying-data",
|
||||||
"title": "Data binding",
|
"title": "Displaying Data",
|
||||||
"tooltip": "Property binding helps show app data in the UI."
|
"tooltip": "Property binding helps show app data in the UI."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/user-input",
|
"title": "Template Syntax",
|
||||||
"title": "User Input",
|
|
||||||
"tooltip": "User input triggers DOM events. Angular listens to those events with event bindings that funnel updated values back into your app's components and models."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/pipes",
|
|
||||||
"title": "Pipes",
|
|
||||||
"tooltip": "Pipes transform displayed values within a template."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/lifecycle-hooks",
|
|
||||||
"title": "Component Lifecycle",
|
|
||||||
"tooltip": "Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/component-interaction",
|
|
||||||
"title": "Component Interaction",
|
|
||||||
"tooltip": "Share information between different directives and components."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/component-styles",
|
|
||||||
"title": "Component Styles",
|
|
||||||
"tooltip": "Add CSS styles that are specific to a component."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/dynamic-component-loader",
|
|
||||||
"title": "Dynamic Components",
|
|
||||||
"tooltip": "Load components dynamically."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/elements",
|
|
||||||
"title": "Angular Elements",
|
|
||||||
"tooltip": "Convert components to Custom Elements."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Templates",
|
|
||||||
"tooltip": "Syntax to use in templates for binding, expressions, and directives.",
|
"tooltip": "Syntax to use in templates for binding, expressions, and directives.",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
@ -187,6 +232,11 @@
|
|||||||
"title": "Two-way binding",
|
"title": "Two-way binding",
|
||||||
"tooltip": "Introductory guide to sharing data between a class and a template."
|
"tooltip": "Introductory guide to sharing data between a class and a template."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/built-in-directives",
|
||||||
|
"title": "Built-in directives",
|
||||||
|
"tooltip": "Introductory guide to some of the most popular built-in directives."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/template-reference-variables",
|
"url": "guide/template-reference-variables",
|
||||||
"title": "Template reference variables",
|
"title": "Template reference variables",
|
||||||
@ -210,13 +260,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Directives",
|
"url": "guide/user-input",
|
||||||
"tooltip": "Control the behavior of elements and the layout of your pages with directives.",
|
"title": "User Input",
|
||||||
"children": [
|
"tooltip": "User input triggers DOM events. Angular listens to those events with event bindings that funnel updated values back into your app's components and models."
|
||||||
{
|
|
||||||
"url": "guide/built-in-directives",
|
|
||||||
"title": "Built-in directives",
|
|
||||||
"tooltip": "Introductory guide to some of the most popular built-in directives."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/attribute-directives",
|
"url": "guide/attribute-directives",
|
||||||
@ -227,6 +273,164 @@
|
|||||||
"url": "guide/structural-directives",
|
"url": "guide/structural-directives",
|
||||||
"title": "Structural Directives",
|
"title": "Structural Directives",
|
||||||
"tooltip": "Structural directives manipulate the layout of the page."
|
"tooltip": "Structural directives manipulate the layout of the page."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/pipes",
|
||||||
|
"title": "Pipes",
|
||||||
|
"tooltip": "Pipes transform displayed values within a template."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/lifecycle-hooks",
|
||||||
|
"title": "Hook into the Component Lifecycle",
|
||||||
|
"tooltip": "Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/component-interaction",
|
||||||
|
"title": "Component Interaction",
|
||||||
|
"tooltip": "Share information between different directives and components."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/component-styles",
|
||||||
|
"title": "Component Styles",
|
||||||
|
"tooltip": "Add CSS styles that are specific to a component."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/dynamic-component-loader",
|
||||||
|
"title": "Dynamic Components",
|
||||||
|
"tooltip": "Load components dynamically."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/elements",
|
||||||
|
"title": "Angular Elements",
|
||||||
|
"tooltip": "Convert components to Custom Elements."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Forms for User Input",
|
||||||
|
"tooltip": "Forms creates a cohesive, effective, and compelling data entry experience.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/forms-overview",
|
||||||
|
"title": "Introduction",
|
||||||
|
"tooltip": "An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/reactive-forms",
|
||||||
|
"title": "Reactive Forms",
|
||||||
|
"tooltip": "Create a reactive form using FormBuilder, groups, and arrays."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/form-validation",
|
||||||
|
"title": "Validate form input",
|
||||||
|
"tooltip": "Validate user's form entries."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/dynamic-form",
|
||||||
|
"title": "Building Dynamic Forms",
|
||||||
|
"tooltip": "Create dynamic form templates using FormGroup."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Observables & RxJS",
|
||||||
|
"tooltip": "Using observables for message passing in Angular.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/observables",
|
||||||
|
"title": "Observables Overview",
|
||||||
|
"tooltip": "Using observables to pass values synchronously or asynchronously."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/rx-library",
|
||||||
|
"title": "The RxJS Library",
|
||||||
|
"tooltip": "A library for reactive programming using observables to compose asynchronous or callback-based code."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/observables-in-angular",
|
||||||
|
"title": "Observables in Angular",
|
||||||
|
"tooltip": "How Angular subsystems use and expect observables."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/practical-observable-usage",
|
||||||
|
"title": "Practical Usage",
|
||||||
|
"tooltip": "Domains in which observables are particularly useful."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/comparing-observables",
|
||||||
|
"title": "Compare to Other Techniques",
|
||||||
|
"tooltip": "How observables compare to promises and other message passing techniques."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "NgModules",
|
||||||
|
"tooltip": "NgModules.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/ngmodules",
|
||||||
|
"title": "NgModules Introduction",
|
||||||
|
"tooltip": "Use NgModules to make your apps efficient."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/ngmodule-vs-jsmodule",
|
||||||
|
"title": "JS Modules vs NgModules",
|
||||||
|
"tooltip": "Differentiate between JavaScript modules and NgModules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/bootstrapping",
|
||||||
|
"title": "Launching Apps with a Root Module",
|
||||||
|
"tooltip": "Tell Angular how to construct and bootstrap the app in the root \"AppModule\"."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/frequent-ngmodules",
|
||||||
|
"title": "Frequently Used NgModules",
|
||||||
|
"tooltip": "Introduction to the most frequently used NgModules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/module-types",
|
||||||
|
"title": "Types of Feature Modules",
|
||||||
|
"tooltip": "Description of the different types of feature modules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/entry-components",
|
||||||
|
"title": "Entry Components",
|
||||||
|
"tooltip": "All about entry components in Angular."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/feature-modules",
|
||||||
|
"title": "Feature Modules",
|
||||||
|
"tooltip": "Create feature modules to organize your code."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/providers",
|
||||||
|
"title": "Providing Dependencies",
|
||||||
|
"tooltip": "Providing dependencies to NgModules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/singleton-services",
|
||||||
|
"title": "Singleton Services",
|
||||||
|
"tooltip": "Creating singleton services."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/lazy-loading-ngmodules",
|
||||||
|
"title": "Lazy Loading Feature Modules",
|
||||||
|
"tooltip": "Lazy load modules to speed up your apps."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/sharing-ngmodules",
|
||||||
|
"title": "Sharing NgModules",
|
||||||
|
"tooltip": "Share NgModules to streamline your apps."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/ngmodule-api",
|
||||||
|
"title": "NgModule API",
|
||||||
|
"tooltip": "Understand the details of NgModules."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/ngmodule-faq",
|
||||||
|
"title": "NgModule FAQs",
|
||||||
|
"tooltip": "Answers to frequently asked questions about NgModules."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -260,54 +464,28 @@
|
|||||||
"tooltip": "Use the injection tree to find parent components."
|
"tooltip": "Use the injection tree to find parent components."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Built-in Features",
|
"url": "guide/http",
|
||||||
"tooltip": "Learn how to add Angular's built-in features to add functionality to your applications.",
|
"title": "Access Servers over HTTP",
|
||||||
"children": [
|
"tooltip": "Use HTTP to talk to a remote server."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/router",
|
"url": "guide/router",
|
||||||
"title": "Routing & Navigation",
|
"title": "Routing & Navigation",
|
||||||
"tooltip": "Build in-app navigation among views using the Angular Router."
|
"tooltip": "Build in-app navigation among views using the Angular Router."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Forms",
|
"url": "guide/security",
|
||||||
"tooltip": "Forms creates a cohesive, effective, and compelling data entry experience.",
|
"title": "Security",
|
||||||
"children": [
|
"tooltip": "Developing for content security in Angular applications."
|
||||||
{
|
|
||||||
"url": "guide/forms-overview",
|
|
||||||
"title": "Introduction",
|
|
||||||
"tooltip": "An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/reactive-forms",
|
|
||||||
"title": "Reactive Forms",
|
|
||||||
"tooltip": "Create a reactive form using FormBuilder, groups, and arrays."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/form-validation",
|
|
||||||
"title": "Validate form input",
|
|
||||||
"tooltip": "Validate user's form entries."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/dynamic-form",
|
|
||||||
"title": "Building Dynamic Forms",
|
|
||||||
"tooltip": "Create dynamic form templates using FormGroup."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/http",
|
"title": "Techniques",
|
||||||
"title": "HTTP Client",
|
"tooltip": "Techniques for putting Angular to work in your environment",
|
||||||
"tooltip": "Use HTTP to talk to a remote server."
|
"children": [
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/i18n",
|
|
||||||
"title": "Internationalization (i18n)",
|
|
||||||
"tooltip": "Translate the app's template text into multiple languages."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"title": "Animations",
|
"title": "Animations",
|
||||||
"tooltip": "Enhance the user experience with animation.",
|
"tooltip": "Enhance the user experience with animation.",
|
||||||
@ -340,25 +518,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Schematics",
|
"url": "guide/i18n",
|
||||||
"tooltip": "Understanding schematics.",
|
"title": "Internationalization (i18n)",
|
||||||
"children": [
|
"tooltip": "Translate the app's template text into multiple languages."
|
||||||
{
|
|
||||||
"url": "guide/schematics",
|
|
||||||
"title": "Schematics Overview",
|
|
||||||
"tooltip": "Extending CLI generation capabilities."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/schematics-authoring",
|
"url": "guide/accessibility",
|
||||||
"title": "Authoring Schematics",
|
"title": "Accessibility",
|
||||||
"tooltip": "Understand the structure of a schematic."
|
"tooltip": "Design apps to be accessible to all users."
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/schematics-for-libraries",
|
|
||||||
"title": "Schematics for Libraries",
|
|
||||||
"tooltip": "Use schematics to integrate your library with the Angular CLI."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Service Workers & PWA",
|
"title": "Service Workers & PWA",
|
||||||
@ -395,27 +562,53 @@
|
|||||||
"tooltip": "Configuring service worker caching behavior."
|
"tooltip": "Configuring service worker caching behavior."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/web-worker",
|
||||||
|
"title": "Web Workers",
|
||||||
|
"tooltip": "Using web workers for background processing."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/universal",
|
||||||
|
"title": "Server-side Rendering",
|
||||||
|
"tooltip": "Render HTML server-side with Angular Universal."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Best Practices",
|
"title": "Dev Workflow",
|
||||||
"tooltip": "Learn how to build robust, scalable applications.",
|
"tooltip": "Build, testing, and deployment information.",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"url": "guide/security",
|
"title": "AOT Compiler",
|
||||||
"title": "Security",
|
"tooltip": "Understanding ahead-of-time compilation.",
|
||||||
"tooltip": "Developing for content security in Angular applications."
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/aot-compiler",
|
||||||
|
"title": "Ahead-of-Time Compilation",
|
||||||
|
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/accessibility",
|
"url": "guide/angular-compiler-options",
|
||||||
"title": "Accessibility",
|
"title": "Angular Compiler Options",
|
||||||
"tooltip": "Design apps to be accessible to all users."
|
"tooltip": "Configuring AOT compilation."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/updating",
|
"url": "guide/aot-metadata-errors",
|
||||||
"title": "Keeping Up-to-Date",
|
"title": "AOT Metadata Errors",
|
||||||
"tooltip": "Information about updating Angular applications and libraries to the latest version."
|
"tooltip": "Troubleshooting AOT compilation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/template-typecheck",
|
||||||
|
"title": "Template Type-checking",
|
||||||
|
"tooltip": "Template type-checking in Angular."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/build",
|
||||||
|
"title": "Building & Serving",
|
||||||
|
"tooltip": "Building and serving Angular apps."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Testing",
|
"title": "Testing",
|
||||||
@ -468,48 +661,89 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"title": "Dev Workflow",
|
|
||||||
"tooltip": "Build, and deployment information.",
|
|
||||||
"children": [
|
|
||||||
{
|
{
|
||||||
"url": "guide/deployment",
|
"url": "guide/deployment",
|
||||||
"title": "Deploying applications",
|
"title": "Deployment",
|
||||||
"tooltip": "Learn how to deploy your Angular app."
|
"tooltip": "Learn how to deploy your Angular app."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "AOT Compiler",
|
"title": "Dev Tool Integration",
|
||||||
"tooltip": "Understanding ahead-of-time compilation.",
|
"tooltip": "Integrate with your development environment and tools.",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"url": "guide/aot-compiler",
|
"url": "guide/language-service",
|
||||||
"title": "Ahead-of-Time Compilation",
|
"title": "Language Service",
|
||||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
"tooltip": "Use Angular Language Service to speed up dev time."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/angular-compiler-options",
|
"url": "guide/visual-studio-2015",
|
||||||
"title": "Angular Compiler Options",
|
"title": "Visual Studio 2015",
|
||||||
"tooltip": "Configuring AOT compilation."
|
"tooltip": "Using Angular with Visual Studio 2015.",
|
||||||
},
|
"hidden": true
|
||||||
{
|
}
|
||||||
"url": "guide/aot-metadata-errors",
|
]
|
||||||
"title": "AOT Metadata Errors",
|
|
||||||
"tooltip": "Troubleshooting AOT compilation."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/template-typecheck",
|
|
||||||
"title": "Template Type-checking",
|
|
||||||
"tooltip": "Template type-checking in Angular."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/build",
|
"title": "Configuration",
|
||||||
"title": "Building & Serving",
|
"tooltip": "Workspace and project file structure and configuration.",
|
||||||
"tooltip": "Building and serving Angular apps."
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/file-structure",
|
||||||
|
"title": "Project File Structure",
|
||||||
|
"tooltip": "How your Angular workspace looks on your filesystem."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/workspace-config",
|
||||||
|
"title": "Workspace Configuration",
|
||||||
|
"tooltip": "The \"angular.json\" file contains workspace and project configuration defaults for Angular CLI commands."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/npm-packages",
|
||||||
|
"title": "npm Dependencies",
|
||||||
|
"tooltip": "Description of npm packages required at development time and at runtime."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/typescript-configuration",
|
||||||
|
"title": "TypeScript Configuration",
|
||||||
|
"tooltip": "TypeScript configuration for Angular developers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/browser-support",
|
||||||
|
"title": "Browser Support",
|
||||||
|
"tooltip": "Browser support and polyfills guide."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/strict-mode",
|
||||||
|
"title": "Strict mode",
|
||||||
|
"tooltip": "Reference documentation for Angular's strict mode."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Extending Angular",
|
||||||
|
"tooltip": "Working with libraries and extending the CLI.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"title": "Angular Libraries",
|
||||||
|
"tooltip": "Extending Angular with shared libraries.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/libraries",
|
||||||
|
"title": "Libraries Overview",
|
||||||
|
"tooltip": "Understand how and when to use or create libraries."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/using-libraries",
|
||||||
|
"title": "Using Published Libraries",
|
||||||
|
"tooltip": "Integrate published libraries into an app."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/creating-libraries",
|
||||||
|
"title": "Creating Libraries",
|
||||||
|
"tooltip": "Extend Angular by creating, publishing, and using your own libraries."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/lightweight-injection-tokens",
|
"url": "guide/lightweight-injection-tokens",
|
||||||
"title": "Lightweight Injection Tokens for Libraries",
|
"title": "Lightweight Injection Tokens for Libraries",
|
||||||
@ -518,23 +752,30 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Angular Tools",
|
"title": "Schematics",
|
||||||
"tooltip": "Tools to help you build your Angular applications.",
|
"tooltip": "Understanding schematics.",
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/schematics",
|
||||||
|
"title": "Schematics Overview",
|
||||||
|
"tooltip": "Extending CLI generation capabilities."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/schematics-authoring",
|
||||||
|
"title": "Authoring Schematics",
|
||||||
|
"tooltip": "Understand the structure of a schematic."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/schematics-for-libraries",
|
||||||
|
"title": "Schematics for Libraries",
|
||||||
|
"tooltip": "Use schematics to integrate your library with the Angular CLI."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/cli-builder",
|
"url": "guide/cli-builder",
|
||||||
"title": "CLI Builders",
|
"title": "CLI Builders",
|
||||||
"tooltip": "Using builders to customize Angular CLI."
|
"tooltip": "Using builders to customize Angular CLI."
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/universal",
|
|
||||||
"title": "Server-side Rendering",
|
|
||||||
"tooltip": "Render HTML server-side with Angular Universal."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/language-service",
|
|
||||||
"title": "Language Service",
|
|
||||||
"tooltip": "Use Angular Language Service to speed up dev time."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -562,78 +803,6 @@
|
|||||||
"url": "guide/forms",
|
"url": "guide/forms",
|
||||||
"title": "Building a Template-driven Form",
|
"title": "Building a Template-driven Form",
|
||||||
"tooltip": "Create a template-driven form using directives and Angular template syntax."
|
"tooltip": "Create a template-driven form using directives and Angular template syntax."
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Tutorial: Tour of Heroes",
|
|
||||||
"tooltip": "The Tour of Heroes app is used as a reference point in many Angular examples.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "tutorial",
|
|
||||||
"title": "Introduction",
|
|
||||||
"tooltip": "Introduction to the Tour of Heroes app and tutorial"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt0",
|
|
||||||
"title": "Create a Project",
|
|
||||||
"tooltip": "Creating the application shell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt1",
|
|
||||||
"title": "1. The Hero Editor",
|
|
||||||
"tooltip": "Part 1: Build a simple editor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt2",
|
|
||||||
"title": "2. Display a List",
|
|
||||||
"tooltip": "Part 2: Build a master/detail page with a list of heroes."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt3",
|
|
||||||
"title": "3. Create a Feature Component",
|
|
||||||
"tooltip": "Part 3: Refactor the master/detail views into separate components."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt4",
|
|
||||||
"title": "4. Add Services",
|
|
||||||
"tooltip": "Part 4: Create a reusable service to manage hero data."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt5",
|
|
||||||
"title": "5. Add In-app Navigation",
|
|
||||||
"tooltip": "Part 5: Add the Angular router and navigate among the views."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "tutorial/toh-pt6",
|
|
||||||
"title": "6. Get Data from a Server",
|
|
||||||
"tooltip": "Part 6: Use HTTP to retrieve and save hero data."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/web-worker",
|
|
||||||
"title": "Web Workers",
|
|
||||||
"tooltip": "Using web workers for background processing."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Angular Libraries",
|
|
||||||
"tooltip": "Extending Angular with shared libraries.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/libraries",
|
|
||||||
"title": "Libraries Overview",
|
|
||||||
"tooltip": "Understand how and when to use or create libraries."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/using-libraries",
|
|
||||||
"title": "Using Published Libraries",
|
|
||||||
"tooltip": "Integrate published libraries into an app."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/creating-libraries",
|
|
||||||
"title": "Creating Libraries",
|
|
||||||
"tooltip": "Extend Angular by creating, publishing, and using your own libraries."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -641,6 +810,11 @@
|
|||||||
"title": "Release Information",
|
"title": "Release Information",
|
||||||
"tooltip": "Angular release practices, updating, and upgrading.",
|
"tooltip": "Angular release practices, updating, and upgrading.",
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/updating",
|
||||||
|
"title": "Keeping Up-to-Date",
|
||||||
|
"tooltip": "Information about updating Angular applications and libraries to the latest version."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/releases",
|
"url": "guide/releases",
|
||||||
"title": "Release Practices",
|
"title": "Release Practices",
|
||||||
@ -737,181 +911,23 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Reference",
|
"title": "Angular Style and Usage",
|
||||||
"tooltip": "Reference guides for Angular features and tools.",
|
"tooltip": "Summaries of Angular syntax, coding, and doc styles.",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"title": "Conceptual Reference",
|
"url": "guide/cheatsheet",
|
||||||
"tooltip": "Reference documentation that explains how Angular features work.",
|
"title": "Quick Reference",
|
||||||
"children": [
|
"tooltip": "A quick guide to common Angular coding techniques."
|
||||||
{
|
|
||||||
"title": "Angular Concepts",
|
|
||||||
"tooltip": "Introduction to basic concepts for Angular applications.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/architecture",
|
|
||||||
"title": "Intro to Basic Concepts",
|
|
||||||
"tooltip": "Basic building blocks of Angular applications."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/architecture-modules",
|
"url": "guide/styleguide",
|
||||||
"title": "Intro to Modules",
|
"title": "Coding Style Guide",
|
||||||
"tooltip": "About NgModules."
|
"tooltip": "Guidelines for writing Angular code."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/architecture-components",
|
"url": "guide/docs-style-guide",
|
||||||
"title": "Intro to Components",
|
"title": "Documentation Style Guide",
|
||||||
"tooltip": "About Components, Templates, and Views."
|
"tooltip": "Style guide for documentation authors."
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/architecture-services",
|
|
||||||
"title": "Intro to Services and DI",
|
|
||||||
"tooltip": "About services and dependency injection."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/architecture-next-steps",
|
|
||||||
"title": "Next Steps",
|
|
||||||
"tooltip": "Beyond the basics."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Workspace and project structure",
|
|
||||||
"tooltip": "Workspace and project file structure and configuration.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/file-structure",
|
|
||||||
"title": "Project File Structure",
|
|
||||||
"tooltip": "How your Angular workspace looks on your filesystem."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/workspace-config",
|
|
||||||
"title": "Workspace Configuration",
|
|
||||||
"tooltip": "The \"angular.json\" file contains workspace and project configuration defaults for Angular CLI commands."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/npm-packages",
|
|
||||||
"title": "npm Dependencies",
|
|
||||||
"tooltip": "Description of npm packages required at development time and at runtime."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/typescript-configuration",
|
|
||||||
"title": "TypeScript Configuration",
|
|
||||||
"tooltip": "TypeScript configuration for Angular developers."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/browser-support",
|
|
||||||
"title": "Browser Support",
|
|
||||||
"tooltip": "Browser support and polyfills guide."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/strict-mode",
|
|
||||||
"title": "Strict mode",
|
|
||||||
"tooltip": "Reference documentation for Angular's strict mode."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "NgModules",
|
|
||||||
"tooltip": "NgModules.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/ngmodules",
|
|
||||||
"title": "NgModules Introduction",
|
|
||||||
"tooltip": "Use NgModules to make your apps efficient."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/ngmodule-vs-jsmodule",
|
|
||||||
"title": "JS Modules vs NgModules",
|
|
||||||
"tooltip": "Differentiate between JavaScript modules and NgModules."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/bootstrapping",
|
|
||||||
"title": "Launching Apps with a Root Module",
|
|
||||||
"tooltip": "Tell Angular how to construct and bootstrap the app in the root \"AppModule\"."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/frequent-ngmodules",
|
|
||||||
"title": "Frequently Used NgModules",
|
|
||||||
"tooltip": "Introduction to the most frequently used NgModules."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/module-types",
|
|
||||||
"title": "Types of Feature Modules",
|
|
||||||
"tooltip": "Description of the different types of feature modules."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/entry-components",
|
|
||||||
"title": "Entry Components",
|
|
||||||
"tooltip": "All about entry components in Angular."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/feature-modules",
|
|
||||||
"title": "Feature Modules",
|
|
||||||
"tooltip": "Create feature modules to organize your code."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/providers",
|
|
||||||
"title": "Providing Dependencies",
|
|
||||||
"tooltip": "Providing dependencies to NgModules."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/singleton-services",
|
|
||||||
"title": "Singleton Services",
|
|
||||||
"tooltip": "Creating singleton services."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/lazy-loading-ngmodules",
|
|
||||||
"title": "Lazy Loading Feature Modules",
|
|
||||||
"tooltip": "Lazy load modules to speed up your apps."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/sharing-ngmodules",
|
|
||||||
"title": "Sharing NgModules",
|
|
||||||
"tooltip": "Share NgModules to streamline your apps."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/ngmodule-api",
|
|
||||||
"title": "NgModule API",
|
|
||||||
"tooltip": "Understand the details of NgModules."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/ngmodule-faq",
|
|
||||||
"title": "NgModule FAQs",
|
|
||||||
"tooltip": "Answers to frequently asked questions about NgModules."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Observables & RxJS",
|
|
||||||
"tooltip": "Using observables for message passing in Angular.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/observables",
|
|
||||||
"title": "Observables Overview",
|
|
||||||
"tooltip": "Using observables to pass values synchronously or asynchronously."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/rx-library",
|
|
||||||
"title": "The RxJS Library",
|
|
||||||
"tooltip": "A library for reactive programming using observables to compose asynchronous or callback-based code."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/observables-in-angular",
|
|
||||||
"title": "Observables in Angular",
|
|
||||||
"tooltip": "How Angular subsystems use and expect observables."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/practical-observable-usage",
|
|
||||||
"title": "Practical Usage",
|
|
||||||
"tooltip": "Domains in which observables are particularly useful."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/comparing-observables",
|
|
||||||
"title": "Compare to Other Techniques",
|
|
||||||
"tooltip": "How observables compare to promises and other message passing techniques."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -935,29 +951,6 @@
|
|||||||
"title": "API Reference",
|
"title": "API Reference",
|
||||||
"tooltip": "Details of the Angular packages, classes, interfaces, and other types.",
|
"tooltip": "Details of the Angular packages, classes, interfaces, and other types.",
|
||||||
"url": "api"
|
"url": "api"
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/glossary",
|
|
||||||
"title": "Angular Glossary",
|
|
||||||
"tooltip": "Brief definitions of the most important words in the Angular vocabulary."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Angular Style and Usage",
|
|
||||||
"tooltip": "Summaries of Angular syntax, coding, and doc styles.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/cheatsheet",
|
|
||||||
"title": "Quick Reference",
|
|
||||||
"tooltip": "A quick guide to common Angular coding techniques."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/styleguide",
|
|
||||||
"title": "Coding Style Guide",
|
|
||||||
"tooltip": "Guidelines for writing Angular code."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Footer": [
|
"Footer": [
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"build-local-with-viewengine": "yarn ~~build",
|
"build-local-with-viewengine": "yarn ~~build",
|
||||||
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
|
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
|
||||||
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
|
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
|
||||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 32391604b",
|
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js ef770f1cb",
|
||||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||||
"test": "yarn check-env && ng test",
|
"test": "yarn check-env && ng test",
|
||||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||||
|
@ -99,11 +99,10 @@ describe('site App', function() {
|
|||||||
describe('scrolling to the top', () => {
|
describe('scrolling to the top', () => {
|
||||||
it('should scroll to the top when navigating to another page', () => {
|
it('should scroll to the top when navigating to another page', () => {
|
||||||
page.navigateTo('guide/security');
|
page.navigateTo('guide/security');
|
||||||
|
|
||||||
page.scrollTo('bottom');
|
page.scrollTo('bottom');
|
||||||
expect(page.getScrollTop()).toBeGreaterThan(0);
|
expect(page.getScrollTop()).toBeGreaterThan(0);
|
||||||
// Navigate to Reference section, then check
|
|
||||||
// Find the navigation item that has the text "api"
|
|
||||||
page.click(page.getNavItem(/reference/i));
|
|
||||||
page.click(page.getNavItem(/api/i));
|
page.click(page.getNavItem(/api/i));
|
||||||
expect(page.locationPath()).toBe('/api');
|
expect(page.locationPath()).toBe('/api');
|
||||||
expect(page.getScrollTop()).toBe(0);
|
expect(page.getScrollTop()).toBe(0);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
{%- macro renderHeritage(exportDoc) -%}
|
{%- macro renderHeritage(exportDoc) -%}
|
||||||
{%- if exportDoc.extendsClauses.length %} extends {% for clause in exportDoc.extendsClauses -%}
|
{%- if exportDoc.extendsClauses.length %} extends {% for clause in exportDoc.extendsClauses -%}
|
||||||
{% if clause.doc.path %}<a class="code-anchor" href="{$ clause.doc.path $}">{$ clause.text $}</a>{% else %}{$ clause.text $}{% endif %}{% if not loop.last %}, {% endif -%}
|
<a class="code-anchor" href="{$ clause.doc.path $}">{$ clause.text $}</a>{% if not loop.last %}, {% endif -%}
|
||||||
{% endfor %}{% endif %}
|
{% endfor %}{% endif %}
|
||||||
{%- if exportDoc.implementsClauses.length %} implements {% for clause in exportDoc.implementsClauses -%}
|
{%- if exportDoc.implementsClauses.length %} implements {% for clause in exportDoc.implementsClauses -%}
|
||||||
<a class="code-anchor" href="{$ clause.doc.path $}">{$ clause.text $}</a>{% if not loop.last %}, {% endif -%}
|
<a class="code-anchor" href="{$ clause.doc.path $}">{$ clause.text $}</a>{% if not loop.last %}, {% endif -%}
|
||||||
|
@ -49,7 +49,7 @@ export const COMMIT_TYPES: {[key: string]: CommitType} = {
|
|||||||
build: {
|
build: {
|
||||||
name: 'build',
|
name: 'build',
|
||||||
description: 'Changes to local repository build system and tooling',
|
description: 'Changes to local repository build system and tooling',
|
||||||
scope: ScopeRequirement.Optional,
|
scope: ScopeRequirement.Forbidden,
|
||||||
},
|
},
|
||||||
ci: {
|
ci: {
|
||||||
name: 'ci',
|
name: 'ci',
|
||||||
|
@ -6,10 +6,9 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {info} from 'console';
|
||||||
import {writeFileSync} from 'fs';
|
import {writeFileSync} from 'fs';
|
||||||
|
|
||||||
import {debug, log} from '../utils/console';
|
|
||||||
|
|
||||||
import {loadCommitMessageDraft} from './commit-message-draft';
|
import {loadCommitMessageDraft} from './commit-message-draft';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,19 +20,19 @@ import {loadCommitMessageDraft} from './commit-message-draft';
|
|||||||
export function restoreCommitMessage(
|
export function restoreCommitMessage(
|
||||||
filePath: string, source?: 'message'|'template'|'squash'|'commit') {
|
filePath: string, source?: 'message'|'template'|'squash'|'commit') {
|
||||||
if (!!source) {
|
if (!!source) {
|
||||||
log('Skipping commit message restoration attempt');
|
info('Skipping commit message restoration attempt');
|
||||||
if (source === 'message') {
|
if (source === 'message') {
|
||||||
debug('A commit message was already provided via the command with a -m or -F flag');
|
info('A commit message was already provided via the command with a -m or -F flag');
|
||||||
}
|
}
|
||||||
if (source === 'template') {
|
if (source === 'template') {
|
||||||
debug('A commit message was already provided via the -t flag or config.template setting');
|
info('A commit message was already provided via the -t flag or config.template setting');
|
||||||
}
|
}
|
||||||
if (source === 'squash') {
|
if (source === 'squash') {
|
||||||
debug('A commit message was already provided as a merge action or via .git/MERGE_MSG');
|
info('A commit message was already provided as a merge action or via .git/MERGE_MSG');
|
||||||
}
|
}
|
||||||
if (source === 'commit') {
|
if (source === 'commit') {
|
||||||
debug('A commit message was already provided through a revision specified via --fixup, -c,');
|
info('A commit message was already provided through a revision specified via --fixup, -c,');
|
||||||
debug('-C or --amend flag');
|
info('-C or --amend flag');
|
||||||
}
|
}
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export class GitCommandError extends Error {
|
|||||||
/**
|
/**
|
||||||
* Common client for performing Git interactions.
|
* Common client for performing Git interactions.
|
||||||
*
|
*
|
||||||
* Takes in two optional arguments:
|
* Takes in two optional arguements:
|
||||||
* _githubToken: the token used for authentifation in github interactions, by default empty
|
* _githubToken: the token used for authentifation in github interactions, by default empty
|
||||||
* allowing readonly actions.
|
* allowing readonly actions.
|
||||||
* _config: The dev-infra configuration containing GitClientConfig information, by default
|
* _config: The dev-infra configuration containing GitClientConfig information, by default
|
||||||
|
@ -4,23 +4,71 @@ Caretaker is responsible for merging PRs into the individual branches and intern
|
|||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
|
|
||||||
- Draining the queue of PRs ready to be merged. (PRs with [`action: merge`](https://github.com/angular/angular/pulls?q=is%3Aopen+is%3Apr+label%3A%22action%3A+merge%22) label)
|
- Draining the queue of PRs ready to be merged. (PRs with [`PR action: merge`](https://github.com/angular/angular/pulls?q=is%3Aopen+is%3Apr+label%3A%22PR+action%3A+merge%22) label)
|
||||||
- Assigning [new issues](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) to individual component authors.
|
- Assigning [new issues](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) to individual component authors.
|
||||||
|
|
||||||
## Merging the PR
|
## Merging the PR
|
||||||
|
|
||||||
A PR needs to have `action: merge` and `target: *` labels to be considered
|
A PR needs to have `PR action: merge` and `PR target: *` labels to be considered
|
||||||
ready to merge. Merging is performed by running `ng-dev pr merge` with a PR number to merge.
|
ready to merge. Merging is performed by running `merge-pr` with a PR number to merge.
|
||||||
|
|
||||||
The tooling automatically verifies the given PR is ready for merge. If the PR passes the tests, the
|
|
||||||
tool will automatically merge it based on the applied target label.
|
|
||||||
|
|
||||||
To merge a PR run:
|
To merge a PR run:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ yarn ng-dev pr merge <pr number>
|
$ ./scripts/github/merge-pr 1234
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `merge-pr` script will:
|
||||||
|
- Ensure that all appropriate labels are on the PR.
|
||||||
|
- Fetches the latest PR code from the `angular/angular` repo.
|
||||||
|
- It will `cherry-pick` all of the SHAs from the PR into the current corresponding branches `master` and or `?.?.x` (patch).
|
||||||
|
- It will rewrite commit history by automatically adding `Close #1234` and `(#1234)` into the commit message.
|
||||||
|
|
||||||
|
NOTE: The `merge-pr` will land the PR on `master` and or `?.?.x` (patch) as described by `PR target: *` label.
|
||||||
|
|
||||||
### Recovering from failed `merge-pr` due to conflicts
|
### Recovering from failed `merge-pr` due to conflicts
|
||||||
|
|
||||||
The `ng-dev pr merge` tool will automatically restore to the previous git state when a merge fails.
|
When running `merge-pr` the script will output the commands which it is about to run.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./scripts/github/merge-pr 1234
|
||||||
|
======================
|
||||||
|
GitHub Merge PR Steps
|
||||||
|
======================
|
||||||
|
git cherry-pick angular/pr/1234~1..angular/pr/1234
|
||||||
|
git filter-branch -f --msg-filter "/home/misko/angular/scripts/github/utils/github.closes 1234" HEAD~1..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
If the `cherry-pick` command fails than resolve conflicts and use `git cherry-pick --continue` once ready. After the `cherry-pick` is done cut&paste and run the `filter-branch` command to properly rewrite the messages
|
||||||
|
|
||||||
|
## Cherry-picking PRs into patch branch
|
||||||
|
|
||||||
|
In addition to merging PRs into the master branch, many PRs need to be also merged into a patch branch.
|
||||||
|
Follow these steps to get patch branch up to date.
|
||||||
|
|
||||||
|
1. Check out the most recent patch branch: `git checkout 4.3.x`
|
||||||
|
2. Get a list of PRs merged into master: `git log master --oneline -n10`
|
||||||
|
3. For each PR number in the commit message run: `./scripts/github/merge-pr 1234`
|
||||||
|
- The PR will only merge if the `PR target:` matches the branch.
|
||||||
|
|
||||||
|
Once all of the PRs are in patch branch, push the all branches and tags to github using `push-upstream` script.
|
||||||
|
|
||||||
|
|
||||||
|
## Pushing merged PRs into github
|
||||||
|
|
||||||
|
Use `push-upstream` script to push all of the branch and tags to github.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./scripts/github/push-upstream
|
||||||
|
git push git@github.com:angular/angular.git master:master 4.3.x:4.3.x
|
||||||
|
Counting objects: 25, done.
|
||||||
|
Delta compression using up to 6 threads.
|
||||||
|
Compressing objects: 100% (17/17), done.
|
||||||
|
Writing objects: 100% (25/25), 2.22 KiB | 284.00 KiB/s, done.
|
||||||
|
Total 25 (delta 22), reused 8 (delta 7)
|
||||||
|
remote: Resolving deltas: 100% (22/22), completed with 18 local objects.
|
||||||
|
To github.com:angular/angular.git
|
||||||
|
079d884b6..d1c4a94bb master -> master
|
||||||
|
git push --tags -f git@github.com:angular/angular.git patch_sync:patch_sync
|
||||||
|
Everything up-to-date
|
||||||
|
```
|
||||||
|
@ -12,7 +12,7 @@ Change approvals in our monorepo are managed via [PullApprove](https://docs.pull
|
|||||||
# Merging
|
# Merging
|
||||||
|
|
||||||
Once a change has all of the required approvals, either the last approver or the PR author (if PR author has the project collaborator status)
|
Once a change has all of the required approvals, either the last approver or the PR author (if PR author has the project collaborator status)
|
||||||
should mark the PR with the `action: merge` label and the correct [target label](https://github.com/angular/angular/blob/master/docs/TRIAGE_AND_LABELS.md#pr-target).
|
should mark the PR with the `PR action: merge` label and the correct [target label](https://github.com/angular/angular/blob/master/docs/TRIAGE_AND_LABELS.md#pr-target).
|
||||||
This signals to the caretaker that the PR should be merged. See [merge instructions](CARETAKER.md).
|
This signals to the caretaker that the PR should be merged. See [merge instructions](CARETAKER.md).
|
||||||
|
|
||||||
# Who is the Caretaker?
|
# Who is the Caretaker?
|
||||||
|
@ -154,7 +154,9 @@ available as a long-term distribution mechanism, but they are guaranteed to be a
|
|||||||
time of the build.
|
time of the build.
|
||||||
|
|
||||||
You can access the artifacts for a specific CI run by going to the workflow page, clicking on the
|
You can access the artifacts for a specific CI run by going to the workflow page, clicking on the
|
||||||
`publish_packages_as_artifacts` job and then switching to the "ARTIFACTS" tab.
|
`publish_packages_as_artifacts` job and then switching to the "Artifacts" tab.
|
||||||
|
(If you happen to know the build number of the job, the URL will be something like:
|
||||||
|
`https://circleci.com/gh/angular/angular/<build-number>#artifacts`)
|
||||||
|
|
||||||
#### Archives for each Package
|
#### Archives for each Package
|
||||||
On the "Artifacts" tab, there is a list of links to compressed archives for Angular packages. The
|
On the "Artifacts" tab, there is a list of links to compressed archives for Angular packages. The
|
||||||
|
@ -125,28 +125,28 @@ Triaging PRs is the same as triaging issues, except that the labels `frequency:
|
|||||||
|
|
||||||
PRs also have additional label categories that should be used to signal their state.
|
PRs also have additional label categories that should be used to signal their state.
|
||||||
|
|
||||||
Every triaged PR must have a `action: *` label assigned to it:
|
Every triaged PR must have a `PR action` label assigned to it:
|
||||||
|
|
||||||
* `action: discuss`: Discussion is needed, to be led by the author.
|
* `PR action: discuss`: Discussion is needed, to be led by the author.
|
||||||
* _**Who adds it:** Typically the PR author._
|
* _**Who adds it:** Typically the PR author._
|
||||||
* _**Who removes it:** Whoever added it._
|
* _**Who removes it:** Whoever added it._
|
||||||
* `action: review` (optional): One or more reviews are pending. The label is optional, since the review status can be derived from GitHub's Reviewers interface.
|
* `PR action: review` (optional): One or more reviews are pending. The label is optional, since the review status can be derived from GitHub's Reviewers interface.
|
||||||
* _**Who adds it:** Any team member. The caretaker can use it to differentiate PRs pending review from merge-ready PRs._
|
* _**Who adds it:** Any team member. The caretaker can use it to differentiate PRs pending review from merge-ready PRs._
|
||||||
* _**Who removes it:** Whoever added it or the reviewer adding the last missing review._
|
* _**Who removes it:** Whoever added it or the reviewer adding the last missing review._
|
||||||
* `action: cleanup`: More work is needed from the author.
|
* `PR action: cleanup`: More work is needed from the author.
|
||||||
* _**Who adds it:** The reviewer requesting changes to the PR._
|
* _**Who adds it:** The reviewer requesting changes to the PR._
|
||||||
* _**Who removes it:** Either the author (after implementing the requested changes) or the reviewer (after confirming the requested changes have been implemented)._
|
* _**Who removes it:** Either the author (after implementing the requested changes) or the reviewer (after confirming the requested changes have been implemented)._
|
||||||
* `action: merge`: The PR author is ready for the changes to be merged by the caretaker as soon as the PR is green (or merge-assistance label is applied and caretaker has deemed it acceptable manually). In other words, this label indicates to "auto submit when ready".
|
* `PR action: merge`: The PR author is ready for the changes to be merged by the caretaker as soon as the PR is green (or merge-assistance label is applied and caretaker has deemed it acceptable manually). In other words, this label indicates to "auto submit when ready".
|
||||||
* _**Who adds it:** Typically the PR author._
|
* _**Who adds it:** Typically the PR author._
|
||||||
* _**Who removes it:** Whoever added it._
|
* _**Who removes it:** Whoever added it._
|
||||||
|
|
||||||
|
|
||||||
In addition, PRs can have the following states:
|
In addition, PRs can have the following states:
|
||||||
|
|
||||||
* `state: WIP`: PR is experimental or rapidly changing. Not ready for review or triage.
|
* `PR state: WIP`: PR is experimental or rapidly changing. Not ready for review or triage.
|
||||||
* _**Who adds it:** The PR author._
|
* _**Who adds it:** The PR author._
|
||||||
* _**Who removes it:** Whoever added it._
|
* _**Who removes it:** Whoever added it._
|
||||||
* `state: blocked`: PR is blocked on an issue or other PR. Not ready for merge.
|
* `PR state: blocked`: PR is blocked on an issue or other PR. Not ready for merge.
|
||||||
* _**Who adds it:** Any team member._
|
* _**Who adds it:** Any team member._
|
||||||
* _**Who removes it:** Any team member._
|
* _**Who removes it:** Any team member._
|
||||||
|
|
||||||
@ -162,27 +162,13 @@ This decision is then honored when the PR is being merged by the caretaker.
|
|||||||
|
|
||||||
To communicate the target we use the following labels:
|
To communicate the target we use the following labels:
|
||||||
|
|
||||||
Targeting an active release train:
|
* `PR target: master & patch`: the PR should me merged into the master branch and cherry-picked into the most recent patch branch. All PRs with fixes, docs and refactorings should use this target.
|
||||||
|
* `PR target: master-only`: the PR should be merged only into the `master` branch. All PRs with new features, API changes or high-risk changes should use this target.
|
||||||
|
* `PR target: patch-only`: the PR should be merged only into the most recent patch branch (e.g. 5.0.x). This target is useful if a `master & patch` PR can't be cleanly cherry-picked into the stable branch and a new PR is needed.
|
||||||
|
* `PR target: LTS-only`: the PR should be merged only into the active LTS branch(es). Only security and critical fixes are allowed in these branches. Always send a new PR targeting just the LTS branch and request review approval from @IgorMinar.
|
||||||
|
* `PR target: TBD`: the target is yet to be determined.
|
||||||
|
|
||||||
* `target: major`: Any breaking change
|
If a PR is missing the `PR target: *` label, or if the label is set to "TBD" when the PR is sent to the caretaker, the caretaker should reject the PR and request the appropriate target label to be applied before the PR is merged.
|
||||||
* `target: minor`: Any new feature
|
|
||||||
* `target: patch`: Bug fixes, refactorings, documentation changes, etc. that pose no or very low risk of adversely
|
|
||||||
affecting existing applications.
|
|
||||||
|
|
||||||
Special Cases:
|
|
||||||
* `target: rc`: A critical fix for an active release-train while it is in a feature freeze or RC phase
|
|
||||||
* `target: lts`: A criticial fix for a specific release-train that is still within the long term support phase
|
|
||||||
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- To land a change only in a patch/RC branch, without landing it in any other active release-train branch (such
|
|
||||||
as `master`), the patch/RC branch can be targeted in the Github UI with the appropriate
|
|
||||||
`target: patch`/`target: rc` label.
|
|
||||||
- `target: lts` PRs must target the specific LTS branch they would need to merge into in the Github UI, in
|
|
||||||
cases which a change is desired in multiple LTS branches, individual PRs for each LTS branch must be created
|
|
||||||
|
|
||||||
|
|
||||||
If a PR is missing the `target:*` label, it will be marked as pending by the angular robot status checks.
|
|
||||||
|
|
||||||
|
|
||||||
## PR Approvals
|
## PR Approvals
|
||||||
@ -196,7 +182,7 @@ In any case, the reviewer should actually look through the code and provide feed
|
|||||||
|
|
||||||
Note that approved state does not mean a PR is ready to be merged.
|
Note that approved state does not mean a PR is ready to be merged.
|
||||||
For example, a reviewer might approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small uncontroversial change.
|
For example, a reviewer might approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small uncontroversial change.
|
||||||
Only the `action: merge` label means that the PR is ready for merging.
|
Only the `PR action: merge` label means that the PR is ready for merging.
|
||||||
|
|
||||||
|
|
||||||
## Special Labels
|
## Special Labels
|
||||||
@ -215,7 +201,7 @@ Only issues with `cla:yes` should be merged into master.
|
|||||||
|
|
||||||
Applying this label to a PR makes the angular.io preview available regardless of the author. [More info](../aio/aio-builds-setup/docs/overview--security-model.md)
|
Applying this label to a PR makes the angular.io preview available regardless of the author. [More info](../aio/aio-builds-setup/docs/overview--security-model.md)
|
||||||
|
|
||||||
### `action: merge-assistance`
|
### `PR action: merge-assistance`
|
||||||
* _**Who adds it:** Any team member._
|
* _**Who adds it:** Any team member._
|
||||||
* _**Who removes it:** Any team member._
|
* _**Who removes it:** Any team member._
|
||||||
|
|
||||||
@ -225,7 +211,7 @@ The comment should be formatted like this: `merge-assistance: <explain what kind
|
|||||||
|
|
||||||
For example, the PR owner might not be a Googler and needs help to run g3sync; or one of the checks is failing due to external causes and the PR should still be merged.
|
For example, the PR owner might not be a Googler and needs help to run g3sync; or one of the checks is failing due to external causes and the PR should still be merged.
|
||||||
|
|
||||||
### `action: rerun CI at HEAD`
|
### `PR action: rerun CI at HEAD`
|
||||||
* _**Who adds it:** Any team member._
|
* _**Who adds it:** Any team member._
|
||||||
* _**Who removes it:** The Angular Bot, once it triggers the CI rerun._
|
* _**Who removes it:** The Angular Bot, once it triggers the CI rerun._
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ export declare enum ErrorCode {
|
|||||||
CONFIG_FLAT_MODULE_NO_INDEX = 4001,
|
CONFIG_FLAT_MODULE_NO_INDEX = 4001,
|
||||||
CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK = 4002,
|
CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK = 4002,
|
||||||
HOST_BINDING_PARSE_ERROR = 5001,
|
HOST_BINDING_PARSE_ERROR = 5001,
|
||||||
TEMPLATE_PARSE_ERROR = 5002,
|
|
||||||
NGMODULE_INVALID_DECLARATION = 6001,
|
NGMODULE_INVALID_DECLARATION = 6001,
|
||||||
NGMODULE_INVALID_IMPORT = 6002,
|
NGMODULE_INVALID_IMPORT = 6002,
|
||||||
NGMODULE_INVALID_EXPORT = 6003,
|
NGMODULE_INVALID_EXPORT = 6003,
|
||||||
|
4
goldens/public-api/core/core.d.ts
vendored
4
goldens/public-api/core/core.d.ts
vendored
@ -85,6 +85,10 @@ export declare interface ClassSansProvider {
|
|||||||
useClass: Type<any>;
|
useClass: Type<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
|
export declare interface CollectionChangeRecord<V> extends IterableChangeRecord<V> {
|
||||||
|
}
|
||||||
|
|
||||||
export declare class Compiler {
|
export declare class Compiler {
|
||||||
compileModuleAndAllComponentsAsync: <T>(moduleType: Type<T>) => Promise<ModuleWithComponentFactories<T>>;
|
compileModuleAndAllComponentsAsync: <T>(moduleType: Type<T>) => Promise<ModuleWithComponentFactories<T>>;
|
||||||
compileModuleAndAllComponentsSync: <T>(moduleType: Type<T>) => ModuleWithComponentFactories<T>;
|
compileModuleAndAllComponentsSync: <T>(moduleType: Type<T>) => ModuleWithComponentFactories<T>;
|
||||||
|
2
goldens/public-api/router/router.d.ts
vendored
2
goldens/public-api/router/router.d.ts
vendored
@ -432,7 +432,7 @@ export declare class RouterLinkWithHref implements OnChanges, OnDestroy {
|
|||||||
constructor(router: Router, route: ActivatedRoute, locationStrategy: LocationStrategy);
|
constructor(router: Router, route: ActivatedRoute, locationStrategy: LocationStrategy);
|
||||||
ngOnChanges(changes: SimpleChanges): any;
|
ngOnChanges(changes: SimpleChanges): any;
|
||||||
ngOnDestroy(): any;
|
ngOnDestroy(): any;
|
||||||
onClick(button: number, ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean): boolean;
|
onClick(button: number, ctrlKey: boolean, metaKey: boolean, shiftKey: boolean): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class RouterModule {
|
export declare class RouterModule {
|
||||||
|
@ -29,17 +29,11 @@ export declare class SwUpdate {
|
|||||||
readonly activated: Observable<UpdateActivatedEvent>;
|
readonly activated: Observable<UpdateActivatedEvent>;
|
||||||
readonly available: Observable<UpdateAvailableEvent>;
|
readonly available: Observable<UpdateAvailableEvent>;
|
||||||
get isEnabled(): boolean;
|
get isEnabled(): boolean;
|
||||||
readonly unrecoverable: Observable<UnrecoverableStateEvent>;
|
|
||||||
constructor(sw: ɵangular_packages_service_worker_service_worker_a);
|
constructor(sw: ɵangular_packages_service_worker_service_worker_a);
|
||||||
activateUpdate(): Promise<void>;
|
activateUpdate(): Promise<void>;
|
||||||
checkForUpdate(): Promise<void>;
|
checkForUpdate(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare interface UnrecoverableStateEvent {
|
|
||||||
reason: string;
|
|
||||||
type: 'UNRECOVERABLE_STATE';
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare interface UpdateActivatedEvent {
|
export declare interface UpdateActivatedEvent {
|
||||||
current: {
|
current: {
|
||||||
hash: string;
|
hash: string;
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 1485,
|
"runtime-es2015": 1485,
|
||||||
"main-es2015": 146989,
|
"main-es2015": 147573,
|
||||||
"polyfills-es2015": 36571
|
"polyfills-es2015": 36571
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 2289,
|
"runtime-es2015": 2289,
|
||||||
"main-es2015": 245303,
|
"main-es2015": 245351,
|
||||||
"polyfills-es2015": 36938,
|
"polyfills-es2015": 36938,
|
||||||
"5-es2015": 751
|
"5-es2015": 751
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 2289,
|
"runtime-es2015": 2289,
|
||||||
"main-es2015": 221887,
|
"main-es2015": 221939,
|
||||||
"polyfills-es2015": 36723,
|
"polyfills-es2015": 36723,
|
||||||
"5-es2015": 781
|
"5-es2015": 781
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "11.0.0-next.1",
|
"version": "10.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Angular - a web framework for modern web apps",
|
"description": "Angular - a web framework for modern web apps",
|
||||||
"homepage": "https://github.com/angular/angular",
|
"homepage": "https://github.com/angular/angular",
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {absoluteFromSourceFile} from '../../../src/ngtsc/file_system';
|
import {absoluteFromSourceFile} from '../../../src/ngtsc/file_system';
|
||||||
|
|
||||||
import {Logger} from '../../../src/ngtsc/logging';
|
import {Logger} from '../../../src/ngtsc/logging';
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference, TypeValueReferenceKind, ValueUnavailableKind} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, EnumMember, isDecoratorIdentifier, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference, TypeValueReferenceKind, ValueUnavailableKind} from '../../../src/ngtsc/reflection';
|
||||||
import {isWithinPackage} from '../analysis/util';
|
import {isWithinPackage} from '../analysis/util';
|
||||||
@ -1593,7 +1593,35 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||||||
constructorParamInfo[index] :
|
constructorParamInfo[index] :
|
||||||
{decorators: null, typeExpression: null};
|
{decorators: null, typeExpression: null};
|
||||||
const nameNode = node.name;
|
const nameNode = node.name;
|
||||||
const typeValueReference = this.typeToValue(typeExpression);
|
|
||||||
|
let typeValueReference: TypeValueReference;
|
||||||
|
if (typeExpression !== null) {
|
||||||
|
// `typeExpression` is an expression in a "type" context. Resolve it to a declared value.
|
||||||
|
// Either it's a reference to an imported type, or a type declared locally. Distinguish the
|
||||||
|
// two cases with `getDeclarationOfExpression`.
|
||||||
|
const decl = this.getDeclarationOfExpression(typeExpression);
|
||||||
|
if (decl !== null && decl.node !== null && decl.viaModule !== null &&
|
||||||
|
isNamedDeclaration(decl.node)) {
|
||||||
|
typeValueReference = {
|
||||||
|
kind: TypeValueReferenceKind.IMPORTED,
|
||||||
|
valueDeclaration: decl.node,
|
||||||
|
moduleName: decl.viaModule,
|
||||||
|
importedName: decl.node.name.text,
|
||||||
|
nestedPath: null,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
typeValueReference = {
|
||||||
|
kind: TypeValueReferenceKind.LOCAL,
|
||||||
|
expression: typeExpression,
|
||||||
|
defaultImportStatement: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typeValueReference = {
|
||||||
|
kind: TypeValueReferenceKind.UNAVAILABLE,
|
||||||
|
reason: {kind: ValueUnavailableKind.MISSING_TYPE},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: getNameText(nameNode),
|
name: getNameText(nameNode),
|
||||||
@ -1605,29 +1633,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the `TypeValueReference` for the given `typeExpression`.
|
|
||||||
*
|
|
||||||
* In ngcc, all the `typeExpression` are guaranteed to be "values" because it is working in JS and
|
|
||||||
* not TS. This means that the TS compiler is not going to remove the "type" import and so we can
|
|
||||||
* always use a LOCAL `TypeValueReference` kind, rather than trying to force an additional import
|
|
||||||
* for non-local expressions.
|
|
||||||
*/
|
|
||||||
private typeToValue(typeExpression: ts.Expression|null): TypeValueReference {
|
|
||||||
if (typeExpression === null) {
|
|
||||||
return {
|
|
||||||
kind: TypeValueReferenceKind.UNAVAILABLE,
|
|
||||||
reason: {kind: ValueUnavailableKind.MISSING_TYPE},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
kind: TypeValueReferenceKind.LOCAL,
|
|
||||||
expression: typeExpression,
|
|
||||||
defaultImportStatement: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parameter type and decorators for the constructor of a class,
|
* Get the parameter type and decorators for the constructor of a class,
|
||||||
* where the information is stored on a static property of the class.
|
* where the information is stored on a static property of the class.
|
||||||
|
@ -1211,69 +1211,6 @@ exports.MissingClass2 = MissingClass2;
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getConstructorParameters', () => {
|
describe('getConstructorParameters', () => {
|
||||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
|
||||||
() => {
|
|
||||||
const files = [
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
|
||||||
contents: `
|
|
||||||
declare class Foo {}
|
|
||||||
export {Foo as Bar};
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
|
||||||
contents: `
|
|
||||||
export {Bar as Baz} from './foo';
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/local.js'),
|
|
||||||
contents: `
|
|
||||||
var Internal = (function() {
|
|
||||||
function Internal() {
|
|
||||||
}
|
|
||||||
return Internal;
|
|
||||||
}());
|
|
||||||
exports.External = Internal;
|
|
||||||
`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/main.js'),
|
|
||||||
contents: `
|
|
||||||
var shared = require('shared-lib');
|
|
||||||
var local = require('./local');
|
|
||||||
var SameFile = (function() {
|
|
||||||
function SameFile() {
|
|
||||||
}
|
|
||||||
return SameFile;
|
|
||||||
}());
|
|
||||||
exports.SameFile = SameFile;
|
|
||||||
|
|
||||||
var SomeClass = (function() {
|
|
||||||
function SomeClass(arg1, arg2, arg3) {}
|
|
||||||
return SomeClass;
|
|
||||||
}());
|
|
||||||
SomeClass.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; };
|
|
||||||
exports.SomeClass = SomeClass;
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
loadTestFiles(files);
|
|
||||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
|
||||||
const host =
|
|
||||||
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
||||||
const classNode = getDeclaration(
|
|
||||||
bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration);
|
|
||||||
|
|
||||||
const parameters = host.getConstructorParameters(classNode)!;
|
|
||||||
|
|
||||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
|
||||||
expectTypeValueReferencesForParameters(
|
|
||||||
parameters, ['shared.Baz', 'local.External', 'SameFile']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find the decorated constructor parameters', () => {
|
it('should find the decorated constructor parameters', () => {
|
||||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||||
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||||
|
@ -1140,57 +1140,6 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getConstructorParameters()', () => {
|
describe('getConstructorParameters()', () => {
|
||||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
|
||||||
() => {
|
|
||||||
const files = [
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
|
||||||
contents: `
|
|
||||||
declare class Foo {}
|
|
||||||
export {Foo as Bar};
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
|
||||||
contents: `
|
|
||||||
export {Bar as Baz} from './foo';
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/local.js'),
|
|
||||||
contents: `
|
|
||||||
class Internal {}
|
|
||||||
export {Internal as External};
|
|
||||||
`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/main.js'),
|
|
||||||
contents: `
|
|
||||||
import {Baz} from 'shared-lib';
|
|
||||||
import {External} from './local';
|
|
||||||
export class SameFile {}
|
|
||||||
|
|
||||||
export class SomeClass {
|
|
||||||
constructor(arg1, arg2, arg3) {}
|
|
||||||
}
|
|
||||||
SomeClass.ctorParameters = [{ type: Baz }, { type: External }, { type: SameFile }];
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
loadTestFiles(files);
|
|
||||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
|
||||||
const host =
|
|
||||||
createHost(bundle, new Esm2015ReflectionHost(new MockLogger(), false, bundle));
|
|
||||||
const classNode =
|
|
||||||
getDeclaration(bundle.program, _('/main.js'), 'SomeClass', isNamedClassDeclaration);
|
|
||||||
|
|
||||||
const parameters = host.getConstructorParameters(classNode)!;
|
|
||||||
|
|
||||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
|
||||||
expectTypeValueReferencesForParameters(parameters, ['Baz', 'External', 'SameFile']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find the decorated constructor parameters', () => {
|
it('should find the decorated constructor parameters', () => {
|
||||||
loadFakeCore(getFileSystem());
|
loadFakeCore(getFileSystem());
|
||||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||||
@ -1205,7 +1154,7 @@ runInEachFileSystem(() => {
|
|||||||
'_viewContainer', '_template', 'injected'
|
'_viewContainer', '_template', 'injected'
|
||||||
]);
|
]);
|
||||||
expectTypeValueReferencesForParameters(
|
expectTypeValueReferencesForParameters(
|
||||||
parameters, ['ViewContainerRef', 'TemplateRef', null]);
|
parameters, ['ViewContainerRef', 'TemplateRef', null], '@angular/core');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept `ctorParameters` as an array', () => {
|
it('should accept `ctorParameters` as an array', () => {
|
||||||
|
@ -1252,67 +1252,6 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getConstructorParameters()', () => {
|
describe('getConstructorParameters()', () => {
|
||||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
|
||||||
() => {
|
|
||||||
const files = [
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
|
||||||
contents: `
|
|
||||||
declare class Foo {}
|
|
||||||
export {Foo as Bar};
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
|
||||||
contents: `
|
|
||||||
export {Bar as Baz} from './foo';
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/local.js'),
|
|
||||||
contents: `
|
|
||||||
var Internal = (function() {
|
|
||||||
function Internal() {
|
|
||||||
}
|
|
||||||
return Internal;
|
|
||||||
}());
|
|
||||||
export {Internal as External};
|
|
||||||
`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/main.js'),
|
|
||||||
contents: `
|
|
||||||
import {Baz} from 'shared-lib';
|
|
||||||
import {External} from './local';
|
|
||||||
var SameFile = (function() {
|
|
||||||
function SameFile() {
|
|
||||||
}
|
|
||||||
return SameFile;
|
|
||||||
}());
|
|
||||||
export SameFile;
|
|
||||||
|
|
||||||
var SomeClass = (function() {
|
|
||||||
function SomeClass(arg1, arg2, arg3) {}
|
|
||||||
return SomeClass;
|
|
||||||
}());
|
|
||||||
SomeClass.ctorParameters = function() { return [{ type: Baz }, { type: External }, { type: SameFile }]; };
|
|
||||||
export SomeClass;
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
loadTestFiles(files);
|
|
||||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
|
||||||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
|
||||||
const classNode = getDeclaration(
|
|
||||||
bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration);
|
|
||||||
|
|
||||||
const parameters = host.getConstructorParameters(classNode)!;
|
|
||||||
|
|
||||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
|
||||||
expectTypeValueReferencesForParameters(parameters, ['Baz', 'External', 'SameFile']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find the decorated constructor parameters', () => {
|
it('should find the decorated constructor parameters', () => {
|
||||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||||
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||||
|
@ -1332,78 +1332,6 @@ runInEachFileSystem(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getConstructorParameters', () => {
|
describe('getConstructorParameters', () => {
|
||||||
it('should always specify LOCAL type value references for decorated constructor parameter types',
|
|
||||||
() => {
|
|
||||||
const files = [
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/foo.d.ts'),
|
|
||||||
contents: `
|
|
||||||
declare class Foo {}
|
|
||||||
export {Foo as Bar};
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/node_modules/shared-lib/index.d.ts'),
|
|
||||||
contents: `
|
|
||||||
export {Bar as Baz} from './foo';
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/local.js'),
|
|
||||||
contents: `
|
|
||||||
(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
||||||
typeof define === 'function' && define.amd ? define('local', ['exports'], factory) :
|
|
||||||
(factory(global.local));
|
|
||||||
}(this, (function (exports) { 'use strict';
|
|
||||||
var Internal = (function() {
|
|
||||||
function Internal() {
|
|
||||||
}
|
|
||||||
return Internal;
|
|
||||||
}());
|
|
||||||
exports.External = Internal;
|
|
||||||
})));
|
|
||||||
`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/main.js'),
|
|
||||||
contents: `
|
|
||||||
(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('shared-lib), require('./local')) :
|
|
||||||
typeof define === 'function' && define.amd ? define('main', ['exports', 'shared-lib', './local'], factory) :
|
|
||||||
(factory(global.main, global.shared, global.local));
|
|
||||||
}(this, (function (exports, shared, local) { 'use strict';
|
|
||||||
var SameFile = (function() {
|
|
||||||
function SameFile() {
|
|
||||||
}
|
|
||||||
return SameFile;
|
|
||||||
}());
|
|
||||||
exports.SameFile = SameFile;
|
|
||||||
|
|
||||||
var SomeClass = (function() {
|
|
||||||
function SomeClass(arg1, arg2, arg3) {}
|
|
||||||
return SomeClass;
|
|
||||||
}());
|
|
||||||
SomeClass.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; };
|
|
||||||
exports.SomeClass = SomeClass;
|
|
||||||
})));
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
loadTestFiles(files);
|
|
||||||
const bundle = makeTestBundleProgram(_('/main.js'));
|
|
||||||
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
|
||||||
const classNode = getDeclaration(
|
|
||||||
bundle.program, _('/main.js'), 'SomeClass', isNamedVariableDeclaration);
|
|
||||||
|
|
||||||
const parameters = host.getConstructorParameters(classNode)!;
|
|
||||||
|
|
||||||
expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']);
|
|
||||||
expectTypeValueReferencesForParameters(
|
|
||||||
parameters, ['shared.Baz', 'local.External', 'SameFile']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find the decorated constructor parameters', () => {
|
it('should find the decorated constructor parameters', () => {
|
||||||
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
||||||
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
||||||
|
@ -13,36 +13,26 @@ import {CtorParameter, TypeValueReferenceKind} from '../../../src/ngtsc/reflecti
|
|||||||
* names.
|
* names.
|
||||||
*/
|
*/
|
||||||
export function expectTypeValueReferencesForParameters(
|
export function expectTypeValueReferencesForParameters(
|
||||||
parameters: CtorParameter[], expectedParams: (string|null)[],
|
parameters: CtorParameter[], expectedParams: (string|null)[], fromModule: string|null = null) {
|
||||||
fromModule: (string|null)[] = []) {
|
|
||||||
parameters!.forEach((param, idx) => {
|
parameters!.forEach((param, idx) => {
|
||||||
const expected = expectedParams[idx];
|
const expected = expectedParams[idx];
|
||||||
if (expected !== null) {
|
if (expected !== null) {
|
||||||
if (param.typeValueReference.kind === TypeValueReferenceKind.UNAVAILABLE) {
|
if (param.typeValueReference.kind === TypeValueReferenceKind.UNAVAILABLE) {
|
||||||
fail(`Incorrect typeValueReference generated for ${param.name}, expected "${
|
fail(`Incorrect typeValueReference generated, expected ${expected}`);
|
||||||
expected}" because "${param.typeValueReference.reason}"`);
|
|
||||||
} else if (
|
} else if (
|
||||||
param.typeValueReference.kind === TypeValueReferenceKind.LOCAL &&
|
param.typeValueReference.kind === TypeValueReferenceKind.LOCAL && fromModule !== null) {
|
||||||
fromModule[idx] != null) {
|
fail(`Incorrect typeValueReference generated, expected non-local`);
|
||||||
fail(`Incorrect typeValueReference generated for ${param.name}, expected non-LOCAL (from ${
|
|
||||||
fromModule[idx]}) but was marked LOCAL`);
|
|
||||||
} else if (
|
} else if (
|
||||||
param.typeValueReference.kind !== TypeValueReferenceKind.LOCAL &&
|
param.typeValueReference.kind !== TypeValueReferenceKind.LOCAL && fromModule === null) {
|
||||||
fromModule[idx] == null) {
|
fail(`Incorrect typeValueReference generated, expected local`);
|
||||||
fail(`Incorrect typeValueReference generated for ${
|
|
||||||
param.name}, expected LOCAL but was imported from ${
|
|
||||||
param.typeValueReference.moduleName}`);
|
|
||||||
} else if (param.typeValueReference.kind === TypeValueReferenceKind.LOCAL) {
|
} else if (param.typeValueReference.kind === TypeValueReferenceKind.LOCAL) {
|
||||||
if (!ts.isIdentifier(param.typeValueReference.expression) &&
|
if (!ts.isIdentifier(param.typeValueReference.expression)) {
|
||||||
!ts.isPropertyAccessExpression(param.typeValueReference.expression)) {
|
fail(`Incorrect typeValueReference generated, expected identifier`);
|
||||||
fail(`Incorrect typeValueReference generated for ${
|
|
||||||
param.name}, expected an identifier but got "${
|
|
||||||
param.typeValueReference.expression.getText()}"`);
|
|
||||||
} else {
|
} else {
|
||||||
expect(param.typeValueReference.expression.getText()).toEqual(expected);
|
expect(param.typeValueReference.expression.text).toEqual(expected);
|
||||||
}
|
}
|
||||||
} else if (param.typeValueReference.kind === TypeValueReferenceKind.IMPORTED) {
|
} else if (param.typeValueReference.kind === TypeValueReferenceKind.IMPORTED) {
|
||||||
expect(param.typeValueReference.moduleName).toBe(fromModule[idx]!);
|
expect(param.typeValueReference.moduleName).toBe(fromModule!);
|
||||||
expect(param.typeValueReference.importedName).toBe(expected);
|
expect(param.typeValueReference.importedName).toBe(expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/shims:api",
|
"//packages/compiler-cli/src/ngtsc/shims:api",
|
||||||
"//packages/compiler-cli/src/ngtsc/transform",
|
"//packages/compiler-cli/src/ngtsc/transform",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/util",
|
"//packages/compiler-cli/src/ngtsc/util",
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
|
@ -6,23 +6,22 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {compileComponentFromMetadata, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ComponentMetadata, R3FactoryTarget, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
|
import {compileComponentFromMetadata, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, makeBindingParser, ParseError, ParseSourceFile, parseTemplate, ParseTemplateOptions, R3ComponentMetadata, R3FactoryTarget, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CycleAnalyzer} from '../../cycles';
|
import {CycleAnalyzer} from '../../cycles';
|
||||||
import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {absoluteFrom, relative} from '../../file_system';
|
import {absoluteFrom, relative} from '../../file_system';
|
||||||
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {DependencyTracker} from '../../incremental/api';
|
import {DependencyTracker} from '../../incremental/api';
|
||||||
import {IndexingContext} from '../../indexer';
|
import {IndexingContext} from '../../indexer';
|
||||||
import {ClassPropertyMapping, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
import {DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||||
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
||||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
||||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||||
import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck/api';
|
import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck/api';
|
||||||
import {getTemplateId, makeTemplateDiagnostic} from '../../typecheck/diagnostics';
|
|
||||||
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
|
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
|
||||||
import {SubsetOfKeys} from '../../util/src/typescript';
|
import {SubsetOfKeys} from '../../util/src/typescript';
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagno
|
|||||||
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
||||||
import {compileNgFactoryDefField} from './factory';
|
import {compileNgFactoryDefField} from './factory';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, makeDuplicateDeclarationError, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
||||||
|
|
||||||
const EMPTY_MAP = new Map<string, Expression>();
|
const EMPTY_MAP = new Map<string, Expression>();
|
||||||
const EMPTY_ARRAY: any[] = [];
|
const EMPTY_ARRAY: any[] = [];
|
||||||
@ -56,9 +55,6 @@ export interface ComponentAnalysisData {
|
|||||||
template: ParsedTemplateWithSource;
|
template: ParsedTemplateWithSource;
|
||||||
metadataStmt: Statement|null;
|
metadataStmt: Statement|null;
|
||||||
|
|
||||||
inputs: ClassPropertyMapping;
|
|
||||||
outputs: ClassPropertyMapping;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Providers extracted from the `providers` field of the component annotation which will require
|
* Providers extracted from the `providers` field of the component annotation which will require
|
||||||
* an Angular factory definition at runtime.
|
* an Angular factory definition at runtime.
|
||||||
@ -194,7 +190,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Next, read the `@Component`-specific fields.
|
// Next, read the `@Component`-specific fields.
|
||||||
const {decorator: component, metadata, inputs, outputs} = directiveResult;
|
const {decorator: component, metadata} = directiveResult;
|
||||||
|
|
||||||
// Go through the root directories for this project, and select the one with the smallest
|
// Go through the root directories for this project, and select the one with the smallest
|
||||||
// relative path representation.
|
// relative path representation.
|
||||||
@ -258,26 +254,9 @@ export class ComponentDecoratorHandler implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let diagnostics: ts.Diagnostic[]|undefined = undefined;
|
if (template.errors !== undefined) {
|
||||||
|
throw new Error(
|
||||||
if (template.errors !== null) {
|
`Errors parsing template: ${template.errors.map(e => e.toString()).join(', ')}`);
|
||||||
// If there are any template parsing errors, convert them to `ts.Diagnostic`s for display.
|
|
||||||
const id = getTemplateId(node);
|
|
||||||
diagnostics = template.errors.map(error => {
|
|
||||||
const span = error.span;
|
|
||||||
|
|
||||||
if (span.start.offset === span.end.offset) {
|
|
||||||
// Template errors can contain zero-length spans, if the error occurs at a single point.
|
|
||||||
// However, TypeScript does not handle displaying a zero-length diagnostic very well, so
|
|
||||||
// increase the ending offset by 1 for such errors, to ensure the position is shown in the
|
|
||||||
// diagnostic.
|
|
||||||
span.end.offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeTemplateDiagnostic(
|
|
||||||
id, template.sourceMapping, span, ts.DiagnosticCategory.Error,
|
|
||||||
ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR), error.msg);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Figure out the set of styles. The ordering here is important: external resources (styleUrls)
|
// Figure out the set of styles. The ordering here is important: external resources (styleUrls)
|
||||||
@ -331,16 +310,14 @@ export class ComponentDecoratorHandler implements
|
|||||||
const output: AnalysisOutput<ComponentAnalysisData> = {
|
const output: AnalysisOutput<ComponentAnalysisData> = {
|
||||||
analysis: {
|
analysis: {
|
||||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
meta: {
|
meta: {
|
||||||
...metadata,
|
...metadata,
|
||||||
template: {
|
template: {
|
||||||
nodes: template.nodes,
|
nodes: template.emitNodes,
|
||||||
ngContentSelectors: template.ngContentSelectors,
|
ngContentSelectors: template.ngContentSelectors,
|
||||||
},
|
},
|
||||||
encapsulation,
|
encapsulation,
|
||||||
interpolation: template.interpolationConfig ?? DEFAULT_INTERPOLATION_CONFIG,
|
interpolation: template.interpolation,
|
||||||
styles: styles || [],
|
styles: styles || [],
|
||||||
|
|
||||||
// These will be replaced during the compilation step, after all `NgModule`s have been
|
// These will be replaced during the compilation step, after all `NgModule`s have been
|
||||||
@ -350,7 +327,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
i18nUseExternalIds: this.i18nUseExternalIds,
|
i18nUseExternalIds: this.i18nUseExternalIds,
|
||||||
relativeContextFilePath,
|
relativeContextFilePath,
|
||||||
},
|
},
|
||||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
|
typeCheckMeta: extractDirectiveTypeCheckMeta(node, metadata.inputs, this.reflector),
|
||||||
metadataStmt: generateSetClassMetadataCall(
|
metadataStmt: generateSetClassMetadataCall(
|
||||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||||
this.annotateForClosureCompiler),
|
this.annotateForClosureCompiler),
|
||||||
@ -358,7 +335,6 @@ export class ComponentDecoratorHandler implements
|
|||||||
providersRequiringFactory,
|
providersRequiringFactory,
|
||||||
viewProvidersRequiringFactory,
|
viewProvidersRequiringFactory,
|
||||||
},
|
},
|
||||||
diagnostics,
|
|
||||||
};
|
};
|
||||||
if (changeDetection !== null) {
|
if (changeDetection !== null) {
|
||||||
output.analysis!.meta.changeDetection = changeDetection;
|
output.analysis!.meta.changeDetection = changeDetection;
|
||||||
@ -375,8 +351,8 @@ export class ComponentDecoratorHandler implements
|
|||||||
name: node.name.text,
|
name: node.name.text,
|
||||||
selector: analysis.meta.selector,
|
selector: analysis.meta.selector,
|
||||||
exportAs: analysis.meta.exportAs,
|
exportAs: analysis.meta.exportAs,
|
||||||
inputs: analysis.inputs,
|
inputs: analysis.meta.inputs,
|
||||||
outputs: analysis.outputs,
|
outputs: analysis.meta.outputs,
|
||||||
queries: analysis.meta.queries.map(query => query.propertyName),
|
queries: analysis.meta.queries.map(query => query.propertyName),
|
||||||
isComponent: true,
|
isComponent: true,
|
||||||
baseClass: analysis.baseClass,
|
baseClass: analysis.baseClass,
|
||||||
@ -772,7 +748,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
|
|
||||||
private _parseTemplate(
|
private _parseTemplate(
|
||||||
component: Map<string, ts.Expression>, templateStr: string, templateUrl: string,
|
component: Map<string, ts.Expression>, templateStr: string, templateUrl: string,
|
||||||
templateRange: LexerRange|undefined, escapedString: boolean): ParsedComponentTemplate {
|
templateRange: LexerRange|undefined, escapedString: boolean): ParsedTemplate {
|
||||||
let preserveWhitespaces: boolean = this.defaultPreserveWhitespaces;
|
let preserveWhitespaces: boolean = this.defaultPreserveWhitespaces;
|
||||||
if (component.has('preserveWhitespaces')) {
|
if (component.has('preserveWhitespaces')) {
|
||||||
const expr = component.get('preserveWhitespaces')!;
|
const expr = component.get('preserveWhitespaces')!;
|
||||||
@ -783,7 +759,7 @@ export class ComponentDecoratorHandler implements
|
|||||||
preserveWhitespaces = value;
|
preserveWhitespaces = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
let interpolationConfig = DEFAULT_INTERPOLATION_CONFIG;
|
let interpolation: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG;
|
||||||
if (component.has('interpolation')) {
|
if (component.has('interpolation')) {
|
||||||
const expr = component.get('interpolation')!;
|
const expr = component.get('interpolation')!;
|
||||||
const value = this.evaluator.evaluate(expr);
|
const value = this.evaluator.evaluate(expr);
|
||||||
@ -792,19 +768,17 @@ export class ComponentDecoratorHandler implements
|
|||||||
throw createValueHasWrongTypeError(
|
throw createValueHasWrongTypeError(
|
||||||
expr, value, 'interpolation must be an array with 2 elements of string type');
|
expr, value, 'interpolation must be an array with 2 elements of string type');
|
||||||
}
|
}
|
||||||
interpolationConfig = InterpolationConfig.fromArray(value as [string, string]);
|
interpolation = InterpolationConfig.fromArray(value as [string, string]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always normalize line endings if the template has been escaped (i.e. is inline).
|
const {errors, nodes: emitNodes, styleUrls, styles, ngContentSelectors} =
|
||||||
const i18nNormalizeLineEndingsInICUs = escapedString || this.i18nNormalizeLineEndingsInICUs;
|
parseTemplate(templateStr, templateUrl, {
|
||||||
|
|
||||||
const parsedTemplate = parseTemplate(templateStr, templateUrl, {
|
|
||||||
preserveWhitespaces,
|
preserveWhitespaces,
|
||||||
interpolationConfig,
|
interpolationConfig: interpolation,
|
||||||
range: templateRange,
|
range: templateRange,
|
||||||
escapedString,
|
escapedString,
|
||||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||||
i18nNormalizeLineEndingsInICUs,
|
i18nNormalizeLineEndingsInICUs: this.i18nNormalizeLineEndingsInICUs,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unfortunately, the primary parse of the template above may not contain accurate source map
|
// Unfortunately, the primary parse of the template above may not contain accurate source map
|
||||||
@ -822,17 +796,22 @@ export class ComponentDecoratorHandler implements
|
|||||||
|
|
||||||
const {nodes: diagNodes} = parseTemplate(templateStr, templateUrl, {
|
const {nodes: diagNodes} = parseTemplate(templateStr, templateUrl, {
|
||||||
preserveWhitespaces: true,
|
preserveWhitespaces: true,
|
||||||
interpolationConfig,
|
interpolationConfig: interpolation,
|
||||||
range: templateRange,
|
range: templateRange,
|
||||||
escapedString,
|
escapedString,
|
||||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||||
i18nNormalizeLineEndingsInICUs,
|
i18nNormalizeLineEndingsInICUs: this.i18nNormalizeLineEndingsInICUs,
|
||||||
leadingTriviaChars: [],
|
leadingTriviaChars: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...parsedTemplate,
|
interpolation,
|
||||||
|
emitNodes,
|
||||||
diagNodes,
|
diagNodes,
|
||||||
|
styleUrls,
|
||||||
|
styles,
|
||||||
|
ngContentSelectors,
|
||||||
|
errors,
|
||||||
template: templateStr,
|
template: templateStr,
|
||||||
templateUrl,
|
templateUrl,
|
||||||
isInline: component.has('template'),
|
isInline: component.has('template'),
|
||||||
@ -899,7 +878,12 @@ function sourceMapUrl(resourceUrl: string): string {
|
|||||||
* This contains the actual parsed template as well as any metadata collected during its parsing,
|
* This contains the actual parsed template as well as any metadata collected during its parsing,
|
||||||
* some of which might be useful for re-parsing the template with different options.
|
* some of which might be useful for re-parsing the template with different options.
|
||||||
*/
|
*/
|
||||||
export interface ParsedComponentTemplate extends ParsedTemplate {
|
export interface ParsedTemplate {
|
||||||
|
/**
|
||||||
|
* The `InterpolationConfig` specified by the user.
|
||||||
|
*/
|
||||||
|
interpolation: InterpolationConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A full path to the file which contains the template.
|
* A full path to the file which contains the template.
|
||||||
*
|
*
|
||||||
@ -909,10 +893,22 @@ export interface ParsedComponentTemplate extends ParsedTemplate {
|
|||||||
templateUrl: string;
|
templateUrl: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the original template was stored inline;
|
* The string contents of the template.
|
||||||
* False if the template was in an external file.
|
*
|
||||||
|
* This is the "logical" template string, after expansion of any escaped characters (for inline
|
||||||
|
* templates). This may differ from the actual template bytes as they appear in the .ts file.
|
||||||
*/
|
*/
|
||||||
isInline: boolean;
|
template: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any errors from parsing the template the first time.
|
||||||
|
*/
|
||||||
|
errors?: ParseError[]|undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template AST, parsed according to the user's specifications.
|
||||||
|
*/
|
||||||
|
emitNodes: TmplAstNode[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The template AST, parsed in a manner which preserves source map information for diagnostics.
|
* The template AST, parsed in a manner which preserves source map information for diagnostics.
|
||||||
@ -921,12 +917,36 @@ export interface ParsedComponentTemplate extends ParsedTemplate {
|
|||||||
*/
|
*/
|
||||||
diagNodes: TmplAstNode[];
|
diagNodes: TmplAstNode[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any styleUrls extracted from the metadata.
|
||||||
|
*/
|
||||||
|
styleUrls: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any inline styles extracted from the metadata.
|
||||||
|
*/
|
||||||
|
styles: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any ng-content selectors extracted from the template.
|
||||||
|
*/
|
||||||
|
ngContentSelectors: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the template was inline.
|
||||||
|
*/
|
||||||
|
isInline: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ParseSourceFile` for the template.
|
* The `ParseSourceFile` for the template.
|
||||||
*/
|
*/
|
||||||
file: ParseSourceFile;
|
file: ParseSourceFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParsedTemplateWithSource extends ParsedComponentTemplate {
|
export interface ParsedTemplateWithSource extends ParsedTemplate {
|
||||||
sourceMapping: TemplateSourceMapping;
|
sourceMapping: TemplateSourceMapping;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||||
import {ClassPropertyMapping, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
import {DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||||
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
||||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
@ -39,8 +39,6 @@ export interface DirectiveHandlerData {
|
|||||||
meta: R3DirectiveMetadata;
|
meta: R3DirectiveMetadata;
|
||||||
metadataStmt: Statement|null;
|
metadataStmt: Statement|null;
|
||||||
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||||
inputs: ClassPropertyMapping;
|
|
||||||
outputs: ClassPropertyMapping;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectiveDecoratorHandler implements
|
export class DirectiveDecoratorHandler implements
|
||||||
@ -85,10 +83,11 @@ export class DirectiveDecoratorHandler implements
|
|||||||
const directiveResult = extractDirectiveMetadata(
|
const directiveResult = extractDirectiveMetadata(
|
||||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
||||||
flags, this.annotateForClosureCompiler);
|
flags, this.annotateForClosureCompiler);
|
||||||
if (directiveResult === undefined) {
|
const analysis = directiveResult && directiveResult.metadata;
|
||||||
|
|
||||||
|
if (analysis === undefined) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const analysis = directiveResult.metadata;
|
|
||||||
|
|
||||||
let providersRequiringFactory: Set<Reference<ClassDeclaration>>|null = null;
|
let providersRequiringFactory: Set<Reference<ClassDeclaration>>|null = null;
|
||||||
if (directiveResult !== undefined && directiveResult.decorator.has('providers')) {
|
if (directiveResult !== undefined && directiveResult.decorator.has('providers')) {
|
||||||
@ -98,14 +97,12 @@ export class DirectiveDecoratorHandler implements
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
analysis: {
|
analysis: {
|
||||||
inputs: directiveResult.inputs,
|
|
||||||
outputs: directiveResult.outputs,
|
|
||||||
meta: analysis,
|
meta: analysis,
|
||||||
metadataStmt: generateSetClassMetadataCall(
|
metadataStmt: generateSetClassMetadataCall(
|
||||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||||
this.annotateForClosureCompiler),
|
this.annotateForClosureCompiler),
|
||||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
|
typeCheckMeta: extractDirectiveTypeCheckMeta(node, analysis.inputs, this.reflector),
|
||||||
providersRequiringFactory
|
providersRequiringFactory
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -120,8 +117,8 @@ export class DirectiveDecoratorHandler implements
|
|||||||
name: node.name.text,
|
name: node.name.text,
|
||||||
selector: analysis.meta.selector,
|
selector: analysis.meta.selector,
|
||||||
exportAs: analysis.meta.exportAs,
|
exportAs: analysis.meta.exportAs,
|
||||||
inputs: analysis.inputs,
|
inputs: analysis.meta.inputs,
|
||||||
outputs: analysis.outputs,
|
outputs: analysis.meta.outputs,
|
||||||
queries: analysis.meta.queries.map(query => query.propertyName),
|
queries: analysis.meta.queries.map(query => query.propertyName),
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
baseClass: analysis.baseClass,
|
baseClass: analysis.baseClass,
|
||||||
@ -202,13 +199,8 @@ export class DirectiveDecoratorHandler implements
|
|||||||
export function extractDirectiveMetadata(
|
export function extractDirectiveMetadata(
|
||||||
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
|
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
|
||||||
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||||
flags: HandlerFlags, annotateForClosureCompiler: boolean,
|
flags: HandlerFlags, annotateForClosureCompiler: boolean, defaultSelector: string|null = null):
|
||||||
defaultSelector: string|null = null): {
|
{decorator: Map<string, ts.Expression>, metadata: R3DirectiveMetadata}|undefined {
|
||||||
decorator: Map<string, ts.Expression>,
|
|
||||||
metadata: R3DirectiveMetadata,
|
|
||||||
inputs: ClassPropertyMapping,
|
|
||||||
outputs: ClassPropertyMapping,
|
|
||||||
}|undefined {
|
|
||||||
let directive: Map<string, ts.Expression>;
|
let directive: Map<string, ts.Expression>;
|
||||||
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
||||||
directive = new Map<string, ts.Expression>();
|
directive = new Map<string, ts.Expression>();
|
||||||
@ -339,9 +331,6 @@ export function extractDirectiveMetadata(
|
|||||||
const type = wrapTypeReference(reflector, clazz);
|
const type = wrapTypeReference(reflector, clazz);
|
||||||
const internalType = new WrappedNodeExpr(reflector.getInternalNameOfClass(clazz));
|
const internalType = new WrappedNodeExpr(reflector.getInternalNameOfClass(clazz));
|
||||||
|
|
||||||
const inputs = ClassPropertyMapping.fromMappedObject({...inputsFromMeta, ...inputsFromFields});
|
|
||||||
const outputs = ClassPropertyMapping.fromMappedObject({...outputsFromMeta, ...outputsFromFields});
|
|
||||||
|
|
||||||
const metadata: R3DirectiveMetadata = {
|
const metadata: R3DirectiveMetadata = {
|
||||||
name: clazz.name.text,
|
name: clazz.name.text,
|
||||||
deps: ctorDeps,
|
deps: ctorDeps,
|
||||||
@ -349,8 +338,8 @@ export function extractDirectiveMetadata(
|
|||||||
lifecycle: {
|
lifecycle: {
|
||||||
usesOnChanges,
|
usesOnChanges,
|
||||||
},
|
},
|
||||||
inputs: inputs.toJointMappedObject(),
|
inputs: {...inputsFromMeta, ...inputsFromFields},
|
||||||
outputs: outputs.toDirectMappedObject(),
|
outputs: {...outputsFromMeta, ...outputsFromFields},
|
||||||
queries,
|
queries,
|
||||||
viewQueries,
|
viewQueries,
|
||||||
selector,
|
selector,
|
||||||
@ -363,12 +352,7 @@ export function extractDirectiveMetadata(
|
|||||||
exportAs,
|
exportAs,
|
||||||
providers
|
providers
|
||||||
};
|
};
|
||||||
return {
|
return {decorator: directive, metadata};
|
||||||
decorator: directive,
|
|
||||||
metadata,
|
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractQueryMetadata(
|
export function extractQueryMetadata(
|
||||||
|
@ -215,10 +215,8 @@ function createUnsuitableInjectionTokenError(
|
|||||||
makeRelatedInformation(
|
makeRelatedInformation(
|
||||||
reason.typeNode,
|
reason.typeNode,
|
||||||
'This type does not have a value, so it cannot be used as injection token.'),
|
'This type does not have a value, so it cannot be used as injection token.'),
|
||||||
|
makeRelatedInformation(reason.decl, 'The type is declared here.'),
|
||||||
];
|
];
|
||||||
if (reason.decl !== null) {
|
|
||||||
hints.push(makeRelatedInformation(reason.decl, 'The type is declared here.'));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ValueUnavailableKind.TYPE_ONLY_IMPORT:
|
case ValueUnavailableKind.TYPE_ONLY_IMPORT:
|
||||||
chainMessage =
|
chainMessage =
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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.io/license
|
||||||
*/
|
*/
|
||||||
import {CssSelector, DirectiveMeta as T2DirectiveMeta, parseTemplate, R3TargetBinder, SelectorMatcher, TmplAstElement} from '@angular/compiler';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {absoluteFrom} from '../../file_system';
|
import {absoluteFrom} from '../../file_system';
|
||||||
@ -74,49 +73,6 @@ runInEachFileSystem(() => {
|
|||||||
expect(span.start.toString()).toContain('/entry.ts@5:22');
|
expect(span.start.toString()).toContain('/entry.ts@5:22');
|
||||||
expect(span.end.toString()).toContain('/entry.ts@5:29');
|
expect(span.end.toString()).toContain('/entry.ts@5:29');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should produce metadata compatible with template binding', () => {
|
|
||||||
const src = `
|
|
||||||
import {Directive, Input} from '@angular/core';
|
|
||||||
|
|
||||||
@Directive({selector: '[dir]'})
|
|
||||||
export class TestDir {
|
|
||||||
@Input('propName')
|
|
||||||
fieldName: string;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const {program} = makeProgram([
|
|
||||||
{
|
|
||||||
name: _('/node_modules/@angular/core/index.d.ts'),
|
|
||||||
contents: 'export const Directive: any; export const Input: any;',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: _('/entry.ts'),
|
|
||||||
contents: src,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const analysis = analyzeDirective(program, 'TestDir');
|
|
||||||
const matcher = new SelectorMatcher<T2DirectiveMeta>();
|
|
||||||
const dirMeta: T2DirectiveMeta = {
|
|
||||||
exportAs: null,
|
|
||||||
inputs: analysis.inputs,
|
|
||||||
outputs: analysis.outputs,
|
|
||||||
isComponent: false,
|
|
||||||
name: 'Dir',
|
|
||||||
};
|
|
||||||
matcher.addSelectables(CssSelector.parse('[dir]'), dirMeta);
|
|
||||||
|
|
||||||
const {nodes} = parseTemplate('<div dir [propName]="expr"></div>', 'unimportant.html');
|
|
||||||
const binder = new R3TargetBinder(matcher).bind({template: nodes});
|
|
||||||
const propBinding = (nodes[0] as TmplAstElement).inputs[0];
|
|
||||||
const propBindingConsumer = binder.getConsumerOfBinding(propBinding);
|
|
||||||
|
|
||||||
// Assert that the consumer of the binding is the directive, which means that the metadata
|
|
||||||
// fed into the SelectorMatcher was compatible with the binder, and did not confuse property
|
|
||||||
// and field names.
|
|
||||||
expect(propBindingConsumer).toBe(dirMeta);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
@ -34,7 +34,6 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/transform",
|
"//packages/compiler-cli/src/ngtsc/transform",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/util",
|
"//packages/compiler-cli/src/ngtsc/util",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
|
@ -28,9 +28,8 @@ import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeRe
|
|||||||
import {generatedFactoryTransform} from '../../shims';
|
import {generatedFactoryTransform} from '../../shims';
|
||||||
import {ivySwitchTransform} from '../../switch';
|
import {ivySwitchTransform} from '../../switch';
|
||||||
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
||||||
import {TemplateTypeCheckerImpl} from '../../typecheck';
|
import {isTemplateDiagnostic, TemplateTypeCheckerImpl} from '../../typecheck';
|
||||||
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api';
|
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api';
|
||||||
import {isTemplateDiagnostic} from '../../typecheck/diagnostics';
|
|
||||||
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
|
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
|
||||||
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
|
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
|
||||||
|
|
||||||
|
@ -56,11 +56,6 @@ export enum ErrorCode {
|
|||||||
*/
|
*/
|
||||||
HOST_BINDING_PARSE_ERROR = 5001,
|
HOST_BINDING_PARSE_ERROR = 5001,
|
||||||
|
|
||||||
/**
|
|
||||||
* Raised when the compiler cannot parse a component's template.
|
|
||||||
*/
|
|
||||||
TEMPLATE_PARSE_ERROR = 5002,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raised when an NgModule contains an invalid reference in `declarations`.
|
* Raised when an NgModule contains an invalid reference in `declarations`.
|
||||||
*/
|
*/
|
||||||
|
@ -22,10 +22,10 @@ export class InvalidFileSystem implements FileSystem {
|
|||||||
readFile(path: AbsoluteFsPath): string {
|
readFile(path: AbsoluteFsPath): string {
|
||||||
throw makeError();
|
throw makeError();
|
||||||
}
|
}
|
||||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array {
|
readFileBuffer(path: AbsoluteFsPath): Buffer {
|
||||||
throw makeError();
|
throw makeError();
|
||||||
}
|
}
|
||||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive?: boolean): void {
|
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive?: boolean): void {
|
||||||
throw makeError();
|
throw makeError();
|
||||||
}
|
}
|
||||||
removeFile(path: AbsoluteFsPath): void {
|
removeFile(path: AbsoluteFsPath): void {
|
||||||
|
@ -23,10 +23,10 @@ export class NodeJSFileSystem implements FileSystem {
|
|||||||
readFile(path: AbsoluteFsPath): string {
|
readFile(path: AbsoluteFsPath): string {
|
||||||
return fs.readFileSync(path, 'utf8');
|
return fs.readFileSync(path, 'utf8');
|
||||||
}
|
}
|
||||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array {
|
readFileBuffer(path: AbsoluteFsPath): Buffer {
|
||||||
return fs.readFileSync(path);
|
return fs.readFileSync(path);
|
||||||
}
|
}
|
||||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive: boolean = false): void {
|
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive: boolean = false): void {
|
||||||
fs.writeFileSync(path, data, exclusive ? {flag: 'wx'} : undefined);
|
fs.writeFileSync(path, data, exclusive ? {flag: 'wx'} : undefined);
|
||||||
}
|
}
|
||||||
removeFile(path: AbsoluteFsPath): void {
|
removeFile(path: AbsoluteFsPath): void {
|
||||||
|
@ -37,8 +37,8 @@ export type PathSegment = BrandedPath<'PathSegment'>;
|
|||||||
export interface FileSystem {
|
export interface FileSystem {
|
||||||
exists(path: AbsoluteFsPath): boolean;
|
exists(path: AbsoluteFsPath): boolean;
|
||||||
readFile(path: AbsoluteFsPath): string;
|
readFile(path: AbsoluteFsPath): string;
|
||||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array;
|
readFileBuffer(path: AbsoluteFsPath): Buffer;
|
||||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive?: boolean): void;
|
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive?: boolean): void;
|
||||||
removeFile(path: AbsoluteFsPath): void;
|
removeFile(path: AbsoluteFsPath): void;
|
||||||
symlink(target: AbsoluteFsPath, path: AbsoluteFsPath): void;
|
symlink(target: AbsoluteFsPath, path: AbsoluteFsPath): void;
|
||||||
readdir(path: AbsoluteFsPath): PathSegment[];
|
readdir(path: AbsoluteFsPath): PathSegment[];
|
||||||
|
@ -38,16 +38,16 @@ export abstract class MockFileSystem implements FileSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readFileBuffer(path: AbsoluteFsPath): Uint8Array {
|
readFileBuffer(path: AbsoluteFsPath): Buffer {
|
||||||
const {entity} = this.findFromPath(path);
|
const {entity} = this.findFromPath(path);
|
||||||
if (isFile(entity)) {
|
if (isFile(entity)) {
|
||||||
return entity instanceof Uint8Array ? entity : new Buffer(entity);
|
return Buffer.isBuffer(entity) ? entity : new Buffer(entity);
|
||||||
} else {
|
} else {
|
||||||
throw new MockFileSystemError('ENOENT', path, `File "${path}" does not exist.`);
|
throw new MockFileSystemError('ENOENT', path, `File "${path}" does not exist.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile(path: AbsoluteFsPath, data: string|Uint8Array, exclusive: boolean = false): void {
|
writeFile(path: AbsoluteFsPath, data: string|Buffer, exclusive: boolean = false): void {
|
||||||
const [folderPath, basename] = this.splitIntoFolderAndFile(path);
|
const [folderPath, basename] = this.splitIntoFolderAndFile(path);
|
||||||
const {entity} = this.findFromPath(folderPath);
|
const {entity} = this.findFromPath(folderPath);
|
||||||
if (entity === null || !isFolder(entity)) {
|
if (entity === null || !isFolder(entity)) {
|
||||||
@ -295,7 +295,7 @@ export type Entity = Folder|File|SymLink;
|
|||||||
export interface Folder {
|
export interface Folder {
|
||||||
[pathSegments: string]: Entity;
|
[pathSegments: string]: Entity;
|
||||||
}
|
}
|
||||||
export type File = string|Uint8Array;
|
export type File = string|Buffer;
|
||||||
export class SymLink {
|
export class SymLink {
|
||||||
constructor(public path: AbsoluteFsPath) {}
|
constructor(public path: AbsoluteFsPath) {}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +246,7 @@ class TemplateVisitor extends TmplAstRecursiveVisitor {
|
|||||||
name = node.name;
|
name = node.name;
|
||||||
kind = IdentifierKind.Element;
|
kind = IdentifierKind.Element;
|
||||||
}
|
}
|
||||||
const sourceSpan = node.startSourceSpan;
|
const {sourceSpan} = node;
|
||||||
// An element's or template's source span can be of the form `<element>`, `<element />`, or
|
// An element's or template's source span can be of the form `<element>`, `<element />`, or
|
||||||
// `<element></element>`. Only the selector is interesting to the indexer, so the source is
|
// `<element></element>`. Only the selector is interesting to the indexer, so the source is
|
||||||
// searched for the first occurrence of the element (selector) name.
|
// searched for the first occurrence of the element (selector) name.
|
||||||
|
@ -11,7 +11,6 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {absoluteFrom, AbsoluteFsPath} from '../../file_system';
|
import {absoluteFrom, AbsoluteFsPath} from '../../file_system';
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassPropertyMapping} from '../../metadata';
|
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
import {getDeclaration, makeProgram} from '../../testing';
|
import {getDeclaration, makeProgram} from '../../testing';
|
||||||
import {ComponentMeta} from '../src/context';
|
import {ComponentMeta} from '../src/context';
|
||||||
@ -51,8 +50,8 @@ export function getBoundTemplate(
|
|||||||
selector,
|
selector,
|
||||||
name: declaration.name.getText(),
|
name: declaration.name.getText(),
|
||||||
isComponent: true,
|
isComponent: true,
|
||||||
inputs: ClassPropertyMapping.fromMappedObject({}),
|
inputs: {},
|
||||||
outputs: ClassPropertyMapping.fromMappedObject({}),
|
outputs: {},
|
||||||
exportAs: null,
|
exportAs: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,4 +10,3 @@ export * from './src/api';
|
|||||||
export {DtsMetadataReader} from './src/dts';
|
export {DtsMetadataReader} from './src/dts';
|
||||||
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
||||||
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
||||||
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
|
|
||||||
|
@ -12,8 +12,6 @@ import * as ts from 'typescript';
|
|||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
|
||||||
import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata collected for an `NgModule`.
|
* Metadata collected for an `NgModule`.
|
||||||
@ -54,25 +52,25 @@ export interface DirectiveTypeCheckMeta {
|
|||||||
* Directive's class. This allows inputs to accept a wider range of types and coerce the input to
|
* Directive's class. This allows inputs to accept a wider range of types and coerce the input to
|
||||||
* a narrower type with a getter/setter. See https://angular.io/guide/template-typecheck.
|
* a narrower type with a getter/setter. See https://angular.io/guide/template-typecheck.
|
||||||
*/
|
*/
|
||||||
coercedInputFields: Set<ClassPropertyName>;
|
coercedInputFields: Set<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of input fields which map to `readonly`, `private`, or `protected` members in the
|
* The set of input fields which map to `readonly`, `private`, or `protected` members in the
|
||||||
* Directive's class.
|
* Directive's class.
|
||||||
*/
|
*/
|
||||||
restrictedInputFields: Set<ClassPropertyName>;
|
restrictedInputFields: Set<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of input fields which are declared as string literal members in the Directive's class.
|
* The set of input fields which are declared as string literal members in the Directive's class.
|
||||||
* We need to track these separately because these fields may not be valid JS identifiers so
|
* We need to track these separately because these fields may not be valid JS identifiers so
|
||||||
* we cannot use them with property access expressions when assigning inputs.
|
* we cannot use them with property access expressions when assigning inputs.
|
||||||
*/
|
*/
|
||||||
stringLiteralInputFields: Set<ClassPropertyName>;
|
stringLiteralInputFields: Set<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of input fields which do not have corresponding members in the Directive's class.
|
* The set of input fields which do not have corresponding members in the Directive's class.
|
||||||
*/
|
*/
|
||||||
undeclaredInputFields: Set<ClassPropertyName>;
|
undeclaredInputFields: Set<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the Directive's class is generic, i.e. `class MyDir<T> {...}`.
|
* Whether the Directive's class is generic, i.e. `class MyDir<T> {...}`.
|
||||||
@ -91,16 +89,6 @@ export interface DirectiveMeta extends T2DirectiveMeta, DirectiveTypeCheckMeta {
|
|||||||
selector: string|null;
|
selector: string|null;
|
||||||
queries: string[];
|
queries: string[];
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapping of input field names to the property names.
|
|
||||||
*/
|
|
||||||
inputs: ClassPropertyMapping;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapping of output field names to the property names.
|
|
||||||
*/
|
|
||||||
outputs: ClassPropertyMapping;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `Reference` to the base class for the directive, if one was detected.
|
* A `Reference` to the base class for the directive, if one was detected.
|
||||||
*
|
*
|
||||||
|
@ -12,7 +12,6 @@ import {Reference} from '../../imports';
|
|||||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
||||||
|
|
||||||
import {DirectiveMeta, MetadataReader, NgModuleMeta, PipeMeta} from './api';
|
import {DirectiveMeta, MetadataReader, NgModuleMeta, PipeMeta} from './api';
|
||||||
import {ClassPropertyMapping} from './property_mapping';
|
|
||||||
import {extractDirectiveTypeCheckMeta, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
|
import {extractDirectiveTypeCheckMeta, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,10 +76,7 @@ export class DtsMetadataReader implements MetadataReader {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputs =
|
const inputs = readStringMapType(def.type.typeArguments[3]);
|
||||||
ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[3]));
|
|
||||||
const outputs =
|
|
||||||
ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[4]));
|
|
||||||
return {
|
return {
|
||||||
ref,
|
ref,
|
||||||
name: clazz.name.text,
|
name: clazz.name.text,
|
||||||
@ -88,7 +84,7 @@ export class DtsMetadataReader implements MetadataReader {
|
|||||||
selector: readStringType(def.type.typeArguments[1]),
|
selector: readStringType(def.type.typeArguments[1]),
|
||||||
exportAs: readStringArrayType(def.type.typeArguments[2]),
|
exportAs: readStringArrayType(def.type.typeArguments[2]),
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs: readStringMapType(def.type.typeArguments[4]),
|
||||||
queries: readStringArrayType(def.type.typeArguments[5]),
|
queries: readStringArrayType(def.type.typeArguments[5]),
|
||||||
...extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector),
|
...extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector),
|
||||||
baseClass: readBaseClass(clazz, this.checker, this.reflector),
|
baseClass: readBaseClass(clazz, this.checker, this.reflector),
|
||||||
|
@ -7,11 +7,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
|
import {DirectiveMeta, MetadataReader} from '../../metadata';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
|
||||||
import {DirectiveMeta, MetadataReader} from './api';
|
|
||||||
import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a reference to a directive, return a flattened version of its `DirectiveMeta` metadata
|
* Given a reference to a directive, return a flattened version of its `DirectiveMeta` metadata
|
||||||
* which includes metadata from its entire inheritance chain.
|
* which includes metadata from its entire inheritance chain.
|
||||||
@ -27,13 +25,13 @@ export function flattenInheritedDirectiveMetadata(
|
|||||||
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
|
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const coercedInputFields = new Set<ClassPropertyName>();
|
let inputs: {[key: string]: string|[string, string]} = {};
|
||||||
const undeclaredInputFields = new Set<ClassPropertyName>();
|
let outputs: {[key: string]: string} = {};
|
||||||
const restrictedInputFields = new Set<ClassPropertyName>();
|
const coercedInputFields = new Set<string>();
|
||||||
const stringLiteralInputFields = new Set<ClassPropertyName>();
|
const undeclaredInputFields = new Set<string>();
|
||||||
|
const restrictedInputFields = new Set<string>();
|
||||||
|
const stringLiteralInputFields = new Set<string>();
|
||||||
let isDynamic = false;
|
let isDynamic = false;
|
||||||
let inputs = ClassPropertyMapping.empty();
|
|
||||||
let outputs = ClassPropertyMapping.empty();
|
|
||||||
|
|
||||||
const addMetadata = (meta: DirectiveMeta): void => {
|
const addMetadata = (meta: DirectiveMeta): void => {
|
||||||
if (meta.baseClass === 'dynamic') {
|
if (meta.baseClass === 'dynamic') {
|
||||||
@ -47,9 +45,8 @@ export function flattenInheritedDirectiveMetadata(
|
|||||||
isDynamic = true;
|
isDynamic = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inputs = {...inputs, ...meta.inputs};
|
||||||
inputs = ClassPropertyMapping.merge(inputs, meta.inputs);
|
outputs = {...outputs, ...meta.outputs};
|
||||||
outputs = ClassPropertyMapping.merge(outputs, meta.outputs);
|
|
||||||
|
|
||||||
for (const coercedInputField of meta.coercedInputFields) {
|
for (const coercedInputField of meta.coercedInputFields) {
|
||||||
coercedInputFields.add(coercedInputField);
|
coercedInputFields.add(coercedInputField);
|
||||||
|
@ -1,200 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {InputOutputPropertySet} from '@angular/compiler';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of a class property that backs an input or output declared by a directive or component.
|
|
||||||
*
|
|
||||||
* This type exists for documentation only.
|
|
||||||
*/
|
|
||||||
export type ClassPropertyName = string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name by which an input or output of a directive or component is bound in an Angular template.
|
|
||||||
*
|
|
||||||
* This type exists for documentation only.
|
|
||||||
*/
|
|
||||||
export type BindingPropertyName = string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An input or output of a directive that has both a named JavaScript class property on a component
|
|
||||||
* or directive class, as well as an Angular template property name used for binding.
|
|
||||||
*/
|
|
||||||
export interface InputOrOutput {
|
|
||||||
/**
|
|
||||||
* The name of the JavaScript property on the component or directive instance for this input or
|
|
||||||
* output.
|
|
||||||
*/
|
|
||||||
readonly classPropertyName: ClassPropertyName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The property name used to bind this input or output in an Angular template.
|
|
||||||
*/
|
|
||||||
readonly bindingPropertyName: BindingPropertyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapping of component property and template binding property names, for example containing the
|
|
||||||
* inputs of a particular directive or component.
|
|
||||||
*
|
|
||||||
* A single component property has exactly one input/output annotation (and therefore one binding
|
|
||||||
* property name) associated with it, but the same binding property name may be shared across many
|
|
||||||
* component property names.
|
|
||||||
*
|
|
||||||
* Allows bidirectional querying of the mapping - looking up all inputs/outputs with a given
|
|
||||||
* property name, or mapping from a specific class property to its binding property name.
|
|
||||||
*/
|
|
||||||
export class ClassPropertyMapping implements InputOutputPropertySet {
|
|
||||||
/**
|
|
||||||
* Mapping from class property names to the single `InputOrOutput` for that class property.
|
|
||||||
*/
|
|
||||||
private forwardMap: Map<ClassPropertyName, InputOrOutput>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping from property names to one or more `InputOrOutput`s which share that name.
|
|
||||||
*/
|
|
||||||
private reverseMap: Map<BindingPropertyName, InputOrOutput[]>;
|
|
||||||
|
|
||||||
private constructor(forwardMap: Map<ClassPropertyName, InputOrOutput>) {
|
|
||||||
this.forwardMap = forwardMap;
|
|
||||||
this.reverseMap = reverseMapFromForwardMap(forwardMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a `ClassPropertyMapping` with no entries.
|
|
||||||
*/
|
|
||||||
static empty(): ClassPropertyMapping {
|
|
||||||
return new ClassPropertyMapping(new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a `ClassPropertyMapping` from a primitive JS object which maps class property names
|
|
||||||
* to either binding property names or an array that contains both names, which is used in on-disk
|
|
||||||
* metadata formats (e.g. in .d.ts files).
|
|
||||||
*/
|
|
||||||
static fromMappedObject(obj: {
|
|
||||||
[classPropertyName: string]: BindingPropertyName|[ClassPropertyName, BindingPropertyName]
|
|
||||||
}): ClassPropertyMapping {
|
|
||||||
const forwardMap = new Map<ClassPropertyName, InputOrOutput>();
|
|
||||||
|
|
||||||
for (const classPropertyName of Object.keys(obj)) {
|
|
||||||
const value = obj[classPropertyName];
|
|
||||||
const bindingPropertyName = Array.isArray(value) ? value[0] : value;
|
|
||||||
const inputOrOutput: InputOrOutput = {classPropertyName, bindingPropertyName};
|
|
||||||
forwardMap.set(classPropertyName, inputOrOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClassPropertyMapping(forwardMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge two mappings into one, with class properties from `b` taking precedence over class
|
|
||||||
* properties from `a`.
|
|
||||||
*/
|
|
||||||
static merge(a: ClassPropertyMapping, b: ClassPropertyMapping): ClassPropertyMapping {
|
|
||||||
const forwardMap = new Map<ClassPropertyName, InputOrOutput>(a.forwardMap.entries());
|
|
||||||
for (const [classPropertyName, inputOrOutput] of b.forwardMap) {
|
|
||||||
forwardMap.set(classPropertyName, inputOrOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClassPropertyMapping(forwardMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All class property names mapped in this mapping.
|
|
||||||
*/
|
|
||||||
get classPropertyNames(): ClassPropertyName[] {
|
|
||||||
return Array.from(this.forwardMap.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All binding property names mapped in this mapping.
|
|
||||||
*/
|
|
||||||
get propertyNames(): BindingPropertyName[] {
|
|
||||||
return Array.from(this.reverseMap.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether a mapping for the given property name exists.
|
|
||||||
*/
|
|
||||||
hasBindingPropertyName(propertyName: BindingPropertyName): boolean {
|
|
||||||
return this.reverseMap.has(propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup all `InputOrOutput`s that use this `propertyName`.
|
|
||||||
*/
|
|
||||||
getByBindingPropertyName(propertyName: string): ReadonlyArray<InputOrOutput>|null {
|
|
||||||
return this.reverseMap.has(propertyName) ? this.reverseMap.get(propertyName)! : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup the `InputOrOutput` associated with a `classPropertyName`.
|
|
||||||
*/
|
|
||||||
getByClassPropertyName(classPropertyName: string): InputOrOutput|null {
|
|
||||||
return this.forwardMap.has(classPropertyName) ? this.forwardMap.get(classPropertyName)! : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert this mapping to a primitive JS object which maps each class property directly to the
|
|
||||||
* binding property name associated with it.
|
|
||||||
*/
|
|
||||||
toDirectMappedObject(): {[classPropertyName: string]: BindingPropertyName} {
|
|
||||||
const obj: {[classPropertyName: string]: BindingPropertyName} = {};
|
|
||||||
for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
|
|
||||||
obj[classPropertyName] = inputOrOutput.bindingPropertyName;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert this mapping to a primitive JS object which maps each class property either to itself
|
|
||||||
* (for cases where the binding property name is the same) or to an array which contains both
|
|
||||||
* names if they differ.
|
|
||||||
*
|
|
||||||
* This object format is used when mappings are serialized (for example into .d.ts files).
|
|
||||||
*/
|
|
||||||
toJointMappedObject():
|
|
||||||
{[classPropertyName: string]: BindingPropertyName|[BindingPropertyName, ClassPropertyName]} {
|
|
||||||
const obj: {
|
|
||||||
[classPropertyName: string]: BindingPropertyName|[BindingPropertyName, ClassPropertyName]
|
|
||||||
} = {};
|
|
||||||
for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
|
|
||||||
if (inputOrOutput.bindingPropertyName as string === classPropertyName as string) {
|
|
||||||
obj[classPropertyName] = inputOrOutput.bindingPropertyName;
|
|
||||||
} else {
|
|
||||||
obj[classPropertyName] = [inputOrOutput.bindingPropertyName, classPropertyName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implement the iterator protocol and return entry objects which contain the class and binding
|
|
||||||
* property names (and are useful for destructuring).
|
|
||||||
*/
|
|
||||||
* [Symbol.iterator](): IterableIterator<[ClassPropertyName, BindingPropertyName]> {
|
|
||||||
for (const [classPropertyName, inputOrOutput] of this.forwardMap.entries()) {
|
|
||||||
yield [classPropertyName, inputOrOutput.bindingPropertyName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reverseMapFromForwardMap(forwardMap: Map<ClassPropertyName, InputOrOutput>):
|
|
||||||
Map<BindingPropertyName, InputOrOutput[]> {
|
|
||||||
const reverseMap = new Map<BindingPropertyName, InputOrOutput[]>();
|
|
||||||
for (const [_, inputOrOutput] of forwardMap) {
|
|
||||||
if (!reverseMap.has(inputOrOutput.bindingPropertyName)) {
|
|
||||||
reverseMap.set(inputOrOutput.bindingPropertyName, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
reverseMap.get(inputOrOutput.bindingPropertyName)!.push(inputOrOutput);
|
|
||||||
}
|
|
||||||
return reverseMap;
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ import {ClassDeclaration, ClassMember, ClassMemberKind, isNamedClassDeclaration,
|
|||||||
import {nodeDebugInfo} from '../../util/src/typescript';
|
import {nodeDebugInfo} from '../../util/src/typescript';
|
||||||
|
|
||||||
import {DirectiveMeta, DirectiveTypeCheckMeta, MetadataReader, NgModuleMeta, PipeMeta, TemplateGuardMeta} from './api';
|
import {DirectiveMeta, DirectiveTypeCheckMeta, MetadataReader, NgModuleMeta, PipeMeta, TemplateGuardMeta} from './api';
|
||||||
import {ClassPropertyMapping, ClassPropertyName} from './property_mapping';
|
|
||||||
|
|
||||||
export function extractReferencesFromType(
|
export function extractReferencesFromType(
|
||||||
checker: ts.TypeChecker, def: ts.TypeNode, ngModuleImportedFrom: string|null,
|
checker: ts.TypeChecker, def: ts.TypeNode, ngModuleImportedFrom: string|null,
|
||||||
@ -92,7 +91,7 @@ export function readStringArrayType(type: ts.TypeNode): string[] {
|
|||||||
* making this metadata invariant to changes of inherited classes.
|
* making this metadata invariant to changes of inherited classes.
|
||||||
*/
|
*/
|
||||||
export function extractDirectiveTypeCheckMeta(
|
export function extractDirectiveTypeCheckMeta(
|
||||||
node: ClassDeclaration, inputs: ClassPropertyMapping,
|
node: ClassDeclaration, inputs: {[fieldName: string]: string|[string, string]},
|
||||||
reflector: ReflectionHost): DirectiveTypeCheckMeta {
|
reflector: ReflectionHost): DirectiveTypeCheckMeta {
|
||||||
const members = reflector.getMembersOfClass(node);
|
const members = reflector.getMembersOfClass(node);
|
||||||
const staticMembers = members.filter(member => member.isStatic);
|
const staticMembers = members.filter(member => member.isStatic);
|
||||||
@ -103,23 +102,23 @@ export function extractDirectiveTypeCheckMeta(
|
|||||||
|
|
||||||
const coercedInputFields =
|
const coercedInputFields =
|
||||||
new Set(staticMembers.map(extractCoercedInput)
|
new Set(staticMembers.map(extractCoercedInput)
|
||||||
.filter((inputName): inputName is ClassPropertyName => inputName !== null));
|
.filter((inputName): inputName is string => inputName !== null));
|
||||||
|
|
||||||
const restrictedInputFields = new Set<ClassPropertyName>();
|
const restrictedInputFields = new Set<string>();
|
||||||
const stringLiteralInputFields = new Set<ClassPropertyName>();
|
const stringLiteralInputFields = new Set<string>();
|
||||||
const undeclaredInputFields = new Set<ClassPropertyName>();
|
const undeclaredInputFields = new Set<string>();
|
||||||
|
|
||||||
for (const classPropertyName of inputs.classPropertyNames) {
|
for (const fieldName of Object.keys(inputs)) {
|
||||||
const field = members.find(member => member.name === classPropertyName);
|
const field = members.find(member => member.name === fieldName);
|
||||||
if (field === undefined || field.node === null) {
|
if (field === undefined || field.node === null) {
|
||||||
undeclaredInputFields.add(classPropertyName);
|
undeclaredInputFields.add(fieldName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isRestricted(field.node)) {
|
if (isRestricted(field.node)) {
|
||||||
restrictedInputFields.add(classPropertyName);
|
restrictedInputFields.add(fieldName);
|
||||||
}
|
}
|
||||||
if (field.nameNode !== null && ts.isStringLiteral(field.nameNode)) {
|
if (field.nameNode !== null && ts.isStringLiteral(field.nameNode)) {
|
||||||
stringLiteralInputFields.add(classPropertyName);
|
stringLiteralInputFields.add(fieldName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +336,7 @@ export interface UnsupportedType {
|
|||||||
export interface NoValueDeclaration {
|
export interface NoValueDeclaration {
|
||||||
kind: ValueUnavailableKind.NO_VALUE_DECLARATION;
|
kind: ValueUnavailableKind.NO_VALUE_DECLARATION;
|
||||||
typeNode: ts.TypeNode;
|
typeNode: ts.TypeNode;
|
||||||
decl: ts.Declaration|null;
|
decl: ts.Declaration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TypeOnlyImport {
|
export interface TypeOnlyImport {
|
||||||
|
@ -38,11 +38,7 @@ export function typeToValue(
|
|||||||
// has a value declaration associated with it. Note that const enums are an exception,
|
// has a value declaration associated with it. Note that const enums are an exception,
|
||||||
// because while they do have a value declaration, they don't exist at runtime.
|
// because while they do have a value declaration, they don't exist at runtime.
|
||||||
if (decl.valueDeclaration === undefined || decl.flags & ts.SymbolFlags.ConstEnum) {
|
if (decl.valueDeclaration === undefined || decl.flags & ts.SymbolFlags.ConstEnum) {
|
||||||
let typeOnlyDecl: ts.Declaration|null = null;
|
return noValueDeclaration(typeNode, decl.declarations[0]);
|
||||||
if (decl.declarations !== undefined && decl.declarations.length > 0) {
|
|
||||||
typeOnlyDecl = decl.declarations[0];
|
|
||||||
}
|
|
||||||
return noValueDeclaration(typeNode, typeOnlyDecl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type points to a valid value declaration. Rewrite the TypeReference into an
|
// The type points to a valid value declaration. Rewrite the TypeReference into an
|
||||||
@ -144,7 +140,7 @@ function unsupportedType(typeNode: ts.TypeNode): UnavailableTypeValueReference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function noValueDeclaration(
|
function noValueDeclaration(
|
||||||
typeNode: ts.TypeNode, decl: ts.Declaration|null): UnavailableTypeValueReference {
|
typeNode: ts.TypeNode, decl: ts.Declaration): UnavailableTypeValueReference {
|
||||||
return {
|
return {
|
||||||
kind: TypeValueReferenceKind.UNAVAILABLE,
|
kind: TypeValueReferenceKind.UNAVAILABLE,
|
||||||
reason: {kind: ValueUnavailableKind.NO_VALUE_DECLARATION, typeNode, decl},
|
reason: {kind: ValueUnavailableKind.NO_VALUE_DECLARATION, typeNode, decl},
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Reference, ReferenceEmitter} from '../../imports';
|
import {Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {ClassPropertyMapping, CompoundMetadataRegistry, DirectiveMeta, LocalMetadataRegistry, MetadataRegistry, PipeMeta} from '../../metadata';
|
import {CompoundMetadataRegistry, DirectiveMeta, LocalMetadataRegistry, MetadataRegistry, PipeMeta} from '../../metadata';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
import {ScopeData} from '../src/api';
|
import {ScopeData} from '../src/api';
|
||||||
import {DtsModuleScopeResolver} from '../src/dependency';
|
import {DtsModuleScopeResolver} from '../src/dependency';
|
||||||
@ -236,8 +236,8 @@ function fakeDirective(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
|||||||
name,
|
name,
|
||||||
selector: `[${ref.debugName}]`,
|
selector: `[${ref.debugName}]`,
|
||||||
isComponent: name.startsWith('Cmp'),
|
isComponent: name.startsWith('Cmp'),
|
||||||
inputs: ClassPropertyMapping.fromMappedObject({}),
|
inputs: {},
|
||||||
outputs: ClassPropertyMapping.fromMappedObject({}),
|
outputs: {},
|
||||||
exportAs: null,
|
exportAs: null,
|
||||||
queries: [],
|
queries: [],
|
||||||
hasNgTemplateContextGuard: false,
|
hasNgTemplateContextGuard: false,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||||
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
|
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
|
|
||||||
visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.Identifier {
|
visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.Identifier {
|
||||||
const identifier = ts.createIdentifier(ast.name!);
|
const identifier = ts.createIdentifier(ast.name!);
|
||||||
this.setSourceMapRange(identifier, ast.sourceSpan);
|
this.setSourceMapRange(identifier, ast);
|
||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
const call = ts.createCall(
|
const call = ts.createCall(
|
||||||
ast.name !== null ? ts.createPropertyAccess(target, ast.name) : target, undefined,
|
ast.name !== null ? ts.createPropertyAccess(target, ast.name) : target, undefined,
|
||||||
ast.args.map(arg => arg.visitExpression(this, context)));
|
ast.args.map(arg => arg.visitExpression(this, context)));
|
||||||
this.setSourceMapRange(call, ast.sourceSpan);
|
this.setSourceMapRange(call, ast);
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +255,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
if (ast.pure) {
|
if (ast.pure) {
|
||||||
ts.addSyntheticLeadingComment(expr, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false);
|
ts.addSyntheticLeadingComment(expr, ts.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', false);
|
||||||
}
|
}
|
||||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
this.setSourceMapRange(expr, ast);
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,15 +274,15 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
} else {
|
} else {
|
||||||
expr = ts.createLiteral(ast.value);
|
expr = ts.createLiteral(ast.value);
|
||||||
}
|
}
|
||||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
this.setSourceMapRange(expr, ast);
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression {
|
visitLocalizedString(ast: LocalizedString, context: Context): ts.Expression {
|
||||||
const localizedString = this.scriptTarget >= ts.ScriptTarget.ES2015 ?
|
const localizedString = this.scriptTarget >= ts.ScriptTarget.ES2015 ?
|
||||||
this.createLocalizedStringTaggedTemplate(ast, context) :
|
createLocalizedStringTaggedTemplate(ast, context, this) :
|
||||||
this.createLocalizedStringFunctionCall(ast, context);
|
createLocalizedStringFunctionCall(ast, context, this, this.imports);
|
||||||
this.setSourceMapRange(localizedString, ast.sourceSpan);
|
this.setSourceMapRange(localizedString, ast);
|
||||||
return localizedString;
|
return localizedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +395,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.ArrayLiteralExpression {
|
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.ArrayLiteralExpression {
|
||||||
const expr =
|
const expr =
|
||||||
ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context)));
|
ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context)));
|
||||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
this.setSourceMapRange(expr, ast);
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +405,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
entry.quoted ? ts.createLiteral(entry.key) : ts.createIdentifier(entry.key),
|
entry.quoted ? ts.createLiteral(entry.key) : ts.createIdentifier(entry.key),
|
||||||
entry.value.visitExpression(this, context)));
|
entry.value.visitExpression(this, context)));
|
||||||
const expr = ts.createObjectLiteral(entries);
|
const expr = ts.createObjectLiteral(entries);
|
||||||
this.setSourceMapRange(expr, ast.sourceSpan);
|
this.setSourceMapRange(expr, ast);
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,111 +424,9 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
return ts.createTypeOf(ast.expr.visitExpression(this, context));
|
return ts.createTypeOf(ast.expr.visitExpression(this, context));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private setSourceMapRange(expr: ts.Expression, ast: Expression) {
|
||||||
* Translate the `LocalizedString` node into a `TaggedTemplateExpression` for ES2015 formatted
|
if (ast.sourceSpan) {
|
||||||
* output.
|
const {start, end} = ast.sourceSpan;
|
||||||
*/
|
|
||||||
private createLocalizedStringTaggedTemplate(ast: LocalizedString, context: Context):
|
|
||||||
ts.TaggedTemplateExpression {
|
|
||||||
let template: ts.TemplateLiteral;
|
|
||||||
const length = ast.messageParts.length;
|
|
||||||
const metaBlock = ast.serializeI18nHead();
|
|
||||||
if (length === 1) {
|
|
||||||
template = ts.createNoSubstitutionTemplateLiteral(metaBlock.cooked, metaBlock.raw);
|
|
||||||
this.setSourceMapRange(template, ast.getMessagePartSourceSpan(0));
|
|
||||||
} else {
|
|
||||||
// Create the head part
|
|
||||||
const head = ts.createTemplateHead(metaBlock.cooked, metaBlock.raw);
|
|
||||||
this.setSourceMapRange(head, ast.getMessagePartSourceSpan(0));
|
|
||||||
const spans: ts.TemplateSpan[] = [];
|
|
||||||
// Create the middle parts
|
|
||||||
for (let i = 1; i < length - 1; i++) {
|
|
||||||
const resolvedExpression = ast.expressions[i - 1].visitExpression(this, context);
|
|
||||||
this.setSourceMapRange(resolvedExpression, ast.getPlaceholderSourceSpan(i - 1));
|
|
||||||
const templatePart = ast.serializeI18nTemplatePart(i);
|
|
||||||
const templateMiddle = createTemplateMiddle(templatePart.cooked, templatePart.raw);
|
|
||||||
this.setSourceMapRange(templateMiddle, ast.getMessagePartSourceSpan(i));
|
|
||||||
const templateSpan = ts.createTemplateSpan(resolvedExpression, templateMiddle);
|
|
||||||
spans.push(templateSpan);
|
|
||||||
}
|
|
||||||
// Create the tail part
|
|
||||||
const resolvedExpression = ast.expressions[length - 2].visitExpression(this, context);
|
|
||||||
this.setSourceMapRange(resolvedExpression, ast.getPlaceholderSourceSpan(length - 2));
|
|
||||||
const templatePart = ast.serializeI18nTemplatePart(length - 1);
|
|
||||||
const templateTail = createTemplateTail(templatePart.cooked, templatePart.raw);
|
|
||||||
this.setSourceMapRange(templateTail, ast.getMessagePartSourceSpan(length - 1));
|
|
||||||
spans.push(ts.createTemplateSpan(resolvedExpression, templateTail));
|
|
||||||
// Put it all together
|
|
||||||
template = ts.createTemplateExpression(head, spans);
|
|
||||||
}
|
|
||||||
const expression = ts.createTaggedTemplate(ts.createIdentifier('$localize'), template);
|
|
||||||
this.setSourceMapRange(expression, ast.sourceSpan);
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translate the `LocalizedString` node into a `$localize` call using the imported
|
|
||||||
* `__makeTemplateObject` helper for ES5 formatted output.
|
|
||||||
*/
|
|
||||||
private createLocalizedStringFunctionCall(ast: LocalizedString, context: Context) {
|
|
||||||
// A `$localize` message consists `messageParts` and `expressions`, which get interleaved
|
|
||||||
// together. The interleaved pieces look like:
|
|
||||||
// `[messagePart0, expression0, messagePart1, expression1, messagePart2]`
|
|
||||||
//
|
|
||||||
// Note that there is always a message part at the start and end, and so therefore
|
|
||||||
// `messageParts.length === expressions.length + 1`.
|
|
||||||
//
|
|
||||||
// Each message part may be prefixed with "metadata", which is wrapped in colons (:) delimiters.
|
|
||||||
// The metadata is attached to the first and subsequent message parts by calls to
|
|
||||||
// `serializeI18nHead()` and `serializeI18nTemplatePart()` respectively.
|
|
||||||
|
|
||||||
// The first message part (i.e. `ast.messageParts[0]`) is used to initialize `messageParts`
|
|
||||||
// array.
|
|
||||||
const messageParts = [ast.serializeI18nHead()];
|
|
||||||
const expressions: any[] = [];
|
|
||||||
|
|
||||||
// The rest of the `ast.messageParts` and each of the expressions are `ast.expressions` pushed
|
|
||||||
// into the arrays. Note that `ast.messagePart[i]` corresponds to `expressions[i-1]`
|
|
||||||
for (let i = 1; i < ast.messageParts.length; i++) {
|
|
||||||
expressions.push(ast.expressions[i - 1].visitExpression(this, context));
|
|
||||||
messageParts.push(ast.serializeI18nTemplatePart(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The resulting downlevelled tagged template string uses a call to the `__makeTemplateObject()`
|
|
||||||
// helper, so we must ensure it has been imported.
|
|
||||||
const {moduleImport, symbol} =
|
|
||||||
this.imports.generateNamedImport('tslib', '__makeTemplateObject');
|
|
||||||
const __makeTemplateObjectHelper = (moduleImport === null) ?
|
|
||||||
ts.createIdentifier(symbol) :
|
|
||||||
ts.createPropertyAccess(ts.createIdentifier(moduleImport), ts.createIdentifier(symbol));
|
|
||||||
|
|
||||||
// Generate the call in the form:
|
|
||||||
// `$localize(__makeTemplateObject(cookedMessageParts, rawMessageParts), ...expressions);`
|
|
||||||
const cookedLiterals = messageParts.map(
|
|
||||||
(messagePart, i) =>
|
|
||||||
this.createLiteral(messagePart.cooked, ast.getMessagePartSourceSpan(i)));
|
|
||||||
const rawLiterals = messageParts.map(
|
|
||||||
(messagePart, i) => this.createLiteral(messagePart.raw, ast.getMessagePartSourceSpan(i)));
|
|
||||||
return ts.createCall(
|
|
||||||
/* expression */ ts.createIdentifier('$localize'),
|
|
||||||
/* typeArguments */ undefined,
|
|
||||||
/* argumentsArray */[
|
|
||||||
ts.createCall(
|
|
||||||
/* expression */ __makeTemplateObjectHelper,
|
|
||||||
/* typeArguments */ undefined,
|
|
||||||
/* argumentsArray */
|
|
||||||
[
|
|
||||||
ts.createArrayLiteral(cookedLiterals),
|
|
||||||
ts.createArrayLiteral(rawLiterals),
|
|
||||||
]),
|
|
||||||
...expressions,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private setSourceMapRange(expr: ts.Node, sourceSpan: ParseSourceSpan|null) {
|
|
||||||
if (sourceSpan) {
|
|
||||||
const {start, end} = sourceSpan;
|
|
||||||
const {url, content} = start.file;
|
const {url, content} = start.file;
|
||||||
if (url) {
|
if (url) {
|
||||||
if (!this.externalSourceFiles.has(url)) {
|
if (!this.externalSourceFiles.has(url)) {
|
||||||
@ -539,12 +437,6 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createLiteral(text: string, span: ParseSourceSpan|null) {
|
|
||||||
const literal = ts.createStringLiteral(text);
|
|
||||||
this.setSourceMapRange(literal, span);
|
|
||||||
return literal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||||
@ -770,6 +662,40 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate the `LocalizedString` node into a `TaggedTemplateExpression` for ES2015 formatted
|
||||||
|
* output.
|
||||||
|
*/
|
||||||
|
function createLocalizedStringTaggedTemplate(
|
||||||
|
ast: LocalizedString, context: Context, visitor: ExpressionVisitor) {
|
||||||
|
let template: ts.TemplateLiteral;
|
||||||
|
const length = ast.messageParts.length;
|
||||||
|
const metaBlock = ast.serializeI18nHead();
|
||||||
|
if (length === 1) {
|
||||||
|
template = ts.createNoSubstitutionTemplateLiteral(metaBlock.cooked, metaBlock.raw);
|
||||||
|
} else {
|
||||||
|
// Create the head part
|
||||||
|
const head = ts.createTemplateHead(metaBlock.cooked, metaBlock.raw);
|
||||||
|
const spans: ts.TemplateSpan[] = [];
|
||||||
|
// Create the middle parts
|
||||||
|
for (let i = 1; i < length - 1; i++) {
|
||||||
|
const resolvedExpression = ast.expressions[i - 1].visitExpression(visitor, context);
|
||||||
|
const templatePart = ast.serializeI18nTemplatePart(i);
|
||||||
|
const templateMiddle = createTemplateMiddle(templatePart.cooked, templatePart.raw);
|
||||||
|
spans.push(ts.createTemplateSpan(resolvedExpression, templateMiddle));
|
||||||
|
}
|
||||||
|
// Create the tail part
|
||||||
|
const resolvedExpression = ast.expressions[length - 2].visitExpression(visitor, context);
|
||||||
|
const templatePart = ast.serializeI18nTemplatePart(length - 1);
|
||||||
|
const templateTail = createTemplateTail(templatePart.cooked, templatePart.raw);
|
||||||
|
spans.push(ts.createTemplateSpan(resolvedExpression, templateTail));
|
||||||
|
// Put it all together
|
||||||
|
template = ts.createTemplateExpression(head, spans);
|
||||||
|
}
|
||||||
|
return ts.createTaggedTemplate(ts.createIdentifier('$localize'), template);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// HACK: Use this in place of `ts.createTemplateMiddle()`.
|
// HACK: Use this in place of `ts.createTemplateMiddle()`.
|
||||||
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
||||||
function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
||||||
@ -785,3 +711,58 @@ function createTemplateTail(cooked: string, raw: string): ts.TemplateTail {
|
|||||||
(node.kind as ts.SyntaxKind) = ts.SyntaxKind.TemplateTail;
|
(node.kind as ts.SyntaxKind) = ts.SyntaxKind.TemplateTail;
|
||||||
return node as ts.TemplateTail;
|
return node as ts.TemplateTail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate the `LocalizedString` node into a `$localize` call using the imported
|
||||||
|
* `__makeTemplateObject` helper for ES5 formatted output.
|
||||||
|
*/
|
||||||
|
function createLocalizedStringFunctionCall(
|
||||||
|
ast: LocalizedString, context: Context, visitor: ExpressionVisitor, imports: ImportManager) {
|
||||||
|
// A `$localize` message consists `messageParts` and `expressions`, which get interleaved
|
||||||
|
// together. The interleaved pieces look like:
|
||||||
|
// `[messagePart0, expression0, messagePart1, expression1, messagePart2]`
|
||||||
|
//
|
||||||
|
// Note that there is always a message part at the start and end, and so therefore
|
||||||
|
// `messageParts.length === expressions.length + 1`.
|
||||||
|
//
|
||||||
|
// Each message part may be prefixed with "metadata", which is wrapped in colons (:) delimiters.
|
||||||
|
// The metadata is attached to the first and subsequent message parts by calls to
|
||||||
|
// `serializeI18nHead()` and `serializeI18nTemplatePart()` respectively.
|
||||||
|
|
||||||
|
// The first message part (i.e. `ast.messageParts[0]`) is used to initialize `messageParts` array.
|
||||||
|
const messageParts = [ast.serializeI18nHead()];
|
||||||
|
const expressions: any[] = [];
|
||||||
|
|
||||||
|
// The rest of the `ast.messageParts` and each of the expressions are `ast.expressions` pushed
|
||||||
|
// into the arrays. Note that `ast.messagePart[i]` corresponds to `expressions[i-1]`
|
||||||
|
for (let i = 1; i < ast.messageParts.length; i++) {
|
||||||
|
expressions.push(ast.expressions[i - 1].visitExpression(visitor, context));
|
||||||
|
messageParts.push(ast.serializeI18nTemplatePart(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resulting downlevelled tagged template string uses a call to the `__makeTemplateObject()`
|
||||||
|
// helper, so we must ensure it has been imported.
|
||||||
|
const {moduleImport, symbol} = imports.generateNamedImport('tslib', '__makeTemplateObject');
|
||||||
|
const __makeTemplateObjectHelper = (moduleImport === null) ?
|
||||||
|
ts.createIdentifier(symbol) :
|
||||||
|
ts.createPropertyAccess(ts.createIdentifier(moduleImport), ts.createIdentifier(symbol));
|
||||||
|
|
||||||
|
// Generate the call in the form:
|
||||||
|
// `$localize(__makeTemplateObject(cookedMessageParts, rawMessageParts), ...expressions);`
|
||||||
|
return ts.createCall(
|
||||||
|
/* expression */ ts.createIdentifier('$localize'),
|
||||||
|
/* typeArguments */ undefined,
|
||||||
|
/* argumentsArray */[
|
||||||
|
ts.createCall(
|
||||||
|
/* expression */ __makeTemplateObjectHelper,
|
||||||
|
/* typeArguments */ undefined,
|
||||||
|
/* argumentsArray */
|
||||||
|
[
|
||||||
|
ts.createArrayLiteral(
|
||||||
|
messageParts.map(messagePart => ts.createStringLiteral(messagePart.cooked))),
|
||||||
|
ts.createArrayLiteral(
|
||||||
|
messageParts.map(messagePart => ts.createStringLiteral(messagePart.raw))),
|
||||||
|
]),
|
||||||
|
...expressions,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
@ -20,7 +20,6 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/shims:api",
|
"//packages/compiler-cli/src/ngtsc/shims:api",
|
||||||
"//packages/compiler-cli/src/ngtsc/translator",
|
"//packages/compiler-cli/src/ngtsc/translator",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/util",
|
"//packages/compiler-cli/src/ngtsc/util",
|
||||||
"@npm//@types/node",
|
"@npm//@types/node",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {AbsoluteFsPath} from '../../file_system';
|
import {AbsoluteFsPath} from '../../file_system';
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassPropertyMapping, DirectiveTypeCheckMeta} from '../../metadata';
|
import {DirectiveTypeCheckMeta} from '../../metadata';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
|
||||||
|
|
||||||
@ -22,8 +22,6 @@ import {ClassDeclaration} from '../../reflection';
|
|||||||
export interface TypeCheckableDirectiveMeta extends DirectiveMeta, DirectiveTypeCheckMeta {
|
export interface TypeCheckableDirectiveMeta extends DirectiveMeta, DirectiveTypeCheckMeta {
|
||||||
ref: Reference<ClassDeclaration>;
|
ref: Reference<ClassDeclaration>;
|
||||||
queries: string[];
|
queries: string[];
|
||||||
inputs: ClassPropertyMapping;
|
|
||||||
outputs: ClassPropertyMapping;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TemplateId = string&{__brand: 'TemplateId'};
|
export type TemplateId = string&{__brand: 'TemplateId'};
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
load("//tools:defaults.bzl", "ts_library")
|
|
||||||
|
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
ts_library(
|
|
||||||
name = "diagnostics",
|
|
||||||
srcs = glob(["**/*.ts"]),
|
|
||||||
module_name = "@angular/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
|
||||||
deps = [
|
|
||||||
"//packages:types",
|
|
||||||
"//packages/compiler",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
|
||||||
"@npm//typescript",
|
|
||||||
],
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './src/diagnostic';
|
|
||||||
export * from './src/id';
|
|
@ -1,128 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {ParseSourceSpan} from '@angular/compiler';
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from '../../api';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A `ts.Diagnostic` with additional information about the diagnostic related to template
|
|
||||||
* type-checking.
|
|
||||||
*/
|
|
||||||
export interface TemplateDiagnostic extends ts.Diagnostic {
|
|
||||||
/**
|
|
||||||
* The component with the template that resulted in this diagnostic.
|
|
||||||
*/
|
|
||||||
componentFile: ts.SourceFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The template id of the component that resulted in this diagnostic.
|
|
||||||
*/
|
|
||||||
templateId: TemplateId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
|
|
||||||
*/
|
|
||||||
export function makeTemplateDiagnostic(
|
|
||||||
templateId: TemplateId, mapping: TemplateSourceMapping, span: ParseSourceSpan,
|
|
||||||
category: ts.DiagnosticCategory, code: number, messageText: string|ts.DiagnosticMessageChain,
|
|
||||||
relatedMessage?: {
|
|
||||||
text: string,
|
|
||||||
span: ParseSourceSpan,
|
|
||||||
}): TemplateDiagnostic {
|
|
||||||
if (mapping.type === 'direct') {
|
|
||||||
let relatedInformation: ts.DiagnosticRelatedInformation[]|undefined = undefined;
|
|
||||||
if (relatedMessage !== undefined) {
|
|
||||||
relatedInformation = [{
|
|
||||||
category: ts.DiagnosticCategory.Message,
|
|
||||||
code: 0,
|
|
||||||
file: mapping.node.getSourceFile(),
|
|
||||||
start: relatedMessage.span.start.offset,
|
|
||||||
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
|
||||||
messageText: relatedMessage.text,
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
// For direct mappings, the error is shown inline as ngtsc was able to pinpoint a string
|
|
||||||
// constant within the `@Component` decorator for the template. This allows us to map the error
|
|
||||||
// directly into the bytes of the source file.
|
|
||||||
return {
|
|
||||||
source: 'ngtsc',
|
|
||||||
code,
|
|
||||||
category,
|
|
||||||
messageText,
|
|
||||||
file: mapping.node.getSourceFile(),
|
|
||||||
componentFile: mapping.node.getSourceFile(),
|
|
||||||
templateId,
|
|
||||||
start: span.start.offset,
|
|
||||||
length: span.end.offset - span.start.offset,
|
|
||||||
relatedInformation,
|
|
||||||
};
|
|
||||||
} else if (mapping.type === 'indirect' || mapping.type === 'external') {
|
|
||||||
// For indirect mappings (template was declared inline, but ngtsc couldn't map it directly
|
|
||||||
// to a string constant in the decorator), the component's file name is given with a suffix
|
|
||||||
// indicating it's not the TS file being displayed, but a template.
|
|
||||||
// For external temoplates, the HTML filename is used.
|
|
||||||
const componentSf = mapping.componentClass.getSourceFile();
|
|
||||||
const componentName = mapping.componentClass.name.text;
|
|
||||||
// TODO(alxhub): remove cast when TS in g3 supports this narrowing.
|
|
||||||
const fileName = mapping.type === 'indirect' ?
|
|
||||||
`${componentSf.fileName} (${componentName} template)` :
|
|
||||||
(mapping as ExternalTemplateSourceMapping).templateUrl;
|
|
||||||
// TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS
|
|
||||||
// parser against the template (HTML is just really syntactically invalid TypeScript code ;).
|
|
||||||
// Also investigate caching the file to avoid running the parser multiple times.
|
|
||||||
const sf = ts.createSourceFile(
|
|
||||||
fileName, mapping.template, ts.ScriptTarget.Latest, false, ts.ScriptKind.JSX);
|
|
||||||
|
|
||||||
let relatedInformation: ts.DiagnosticRelatedInformation[] = [];
|
|
||||||
if (relatedMessage !== undefined) {
|
|
||||||
relatedInformation.push({
|
|
||||||
category: ts.DiagnosticCategory.Message,
|
|
||||||
code: 0,
|
|
||||||
file: sf,
|
|
||||||
start: relatedMessage.span.start.offset,
|
|
||||||
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
|
||||||
messageText: relatedMessage.text,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
relatedInformation.push({
|
|
||||||
category: ts.DiagnosticCategory.Message,
|
|
||||||
code: 0,
|
|
||||||
file: componentSf,
|
|
||||||
// mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
|
|
||||||
// and getEnd() are used because they don't include surrounding whitespace.
|
|
||||||
start: mapping.node.getStart(),
|
|
||||||
length: mapping.node.getEnd() - mapping.node.getStart(),
|
|
||||||
messageText: `Error occurs in the template of component ${componentName}.`,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
source: 'ngtsc',
|
|
||||||
category,
|
|
||||||
code,
|
|
||||||
messageText,
|
|
||||||
file: sf,
|
|
||||||
componentFile: componentSf,
|
|
||||||
templateId,
|
|
||||||
start: span.start.offset,
|
|
||||||
length: span.end.offset - span.start.offset,
|
|
||||||
// Show a secondary message indicating the component whose template contains the error.
|
|
||||||
relatedInformation,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unexpected source mapping type: ${(mapping as {type: string}).type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isTemplateDiagnostic(diagnostic: ts.Diagnostic): diagnostic is TemplateDiagnostic {
|
|
||||||
return diagnostic.hasOwnProperty('componentFile') &&
|
|
||||||
ts.isSourceFile((diagnostic as any).componentFile);
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
import {TemplateId} from '../../api';
|
|
||||||
|
|
||||||
|
|
||||||
const TEMPLATE_ID = Symbol('ngTemplateId');
|
|
||||||
const NEXT_TEMPLATE_ID = Symbol('ngNextTemplateId');
|
|
||||||
|
|
||||||
interface HasTemplateId {
|
|
||||||
[TEMPLATE_ID]: TemplateId;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HasNextTemplateId {
|
|
||||||
[NEXT_TEMPLATE_ID]: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTemplateId(clazz: ts.Declaration): TemplateId {
|
|
||||||
const node = clazz as ts.Declaration & Partial<HasTemplateId>;
|
|
||||||
if (node[TEMPLATE_ID] === undefined) {
|
|
||||||
node[TEMPLATE_ID] = allocateTemplateId(node.getSourceFile());
|
|
||||||
}
|
|
||||||
return node[TEMPLATE_ID]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
function allocateTemplateId(sf: ts.SourceFile&Partial<HasNextTemplateId>): TemplateId {
|
|
||||||
if (sf[NEXT_TEMPLATE_ID] === undefined) {
|
|
||||||
sf[NEXT_TEMPLATE_ID] = 1;
|
|
||||||
}
|
|
||||||
return (`tcb${sf[NEXT_TEMPLATE_ID]!++}`) as TemplateId;
|
|
||||||
}
|
|
@ -9,6 +9,7 @@
|
|||||||
export {ReusedProgramStrategy} from './src/augmented_program';
|
export {ReusedProgramStrategy} from './src/augmented_program';
|
||||||
export {FileTypeCheckingData, TemplateTypeCheckerImpl} from './src/checker';
|
export {FileTypeCheckingData, TemplateTypeCheckerImpl} from './src/checker';
|
||||||
export {TypeCheckContextImpl} from './src/context';
|
export {TypeCheckContextImpl} from './src/context';
|
||||||
|
export {isTemplateDiagnostic, TemplateDiagnostic} from './src/diagnostics';
|
||||||
export {TypeCheckProgramHost} from './src/host';
|
export {TypeCheckProgramHost} from './src/host';
|
||||||
export {TypeCheckShimGenerator} from './src/shim';
|
export {TypeCheckShimGenerator} from './src/shim';
|
||||||
export {typeCheckFilePath} from './src/type_check_file';
|
export {typeCheckFilePath} from './src/type_check_file';
|
||||||
|
@ -16,10 +16,9 @@ import {ReflectionHost} from '../../reflection';
|
|||||||
import {isShim} from '../../shims';
|
import {isShim} from '../../shims';
|
||||||
import {getSourceFileOrNull} from '../../util/src/typescript';
|
import {getSourceFileOrNull} from '../../util/src/typescript';
|
||||||
import {OptimizeFor, ProgramTypeCheckAdapter, TemplateId, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
|
import {OptimizeFor, ProgramTypeCheckAdapter, TemplateId, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
|
||||||
import {TemplateDiagnostic} from '../diagnostics';
|
|
||||||
|
|
||||||
import {InliningMode, ShimTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from './context';
|
import {InliningMode, ShimTypeCheckingData, TypeCheckContextImpl, TypeCheckingHost} from './context';
|
||||||
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
|
import {findTypeCheckBlock, shouldReportDiagnostic, TemplateDiagnostic, TemplateSourceResolver, translateDiagnostic} from './diagnostics';
|
||||||
import {TemplateSourceManager} from './source';
|
import {TemplateSourceManager} from './source';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,7 +78,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
|||||||
leadingTriviaChars: [],
|
leadingTriviaChars: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (errors !== null) {
|
if (errors !== undefined) {
|
||||||
return {nodes, errors};
|
return {nodes, errors};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ import {NoopImportRewriter, Reference, ReferenceEmitter} from '../../imports';
|
|||||||
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, ReflectionHost} from '../../reflection';
|
||||||
import {ImportManager} from '../../translator';
|
import {ImportManager} from '../../translator';
|
||||||
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
import {ComponentToShimMappingStrategy, TemplateId, TemplateSourceMapping, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata, TypeCheckContext, TypeCheckingConfig, TypeCtorMetadata} from '../api';
|
||||||
import {TemplateDiagnostic} from '../diagnostics';
|
|
||||||
|
|
||||||
|
import {TemplateDiagnostic} from './diagnostics';
|
||||||
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
|
import {DomSchemaChecker, RegistryDomSchemaChecker} from './dom';
|
||||||
import {Environment} from './environment';
|
import {Environment} from './environment';
|
||||||
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
|
import {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticRecorderImpl} from './oob';
|
||||||
@ -241,8 +241,8 @@ export class TypeCheckContextImpl implements TypeCheckContext {
|
|||||||
// it comes from a .d.ts file. .d.ts declarations don't have bodies.
|
// it comes from a .d.ts file. .d.ts declarations don't have bodies.
|
||||||
body: !dirNode.getSourceFile().isDeclarationFile,
|
body: !dirNode.getSourceFile().isDeclarationFile,
|
||||||
fields: {
|
fields: {
|
||||||
inputs: dir.inputs.classPropertyNames,
|
inputs: Object.keys(dir.inputs),
|
||||||
outputs: dir.outputs.classPropertyNames,
|
outputs: Object.keys(dir.outputs),
|
||||||
// TODO(alxhub): support queries
|
// TODO(alxhub): support queries
|
||||||
queries: dir.queries,
|
queries: dir.queries,
|
||||||
},
|
},
|
||||||
|
@ -10,7 +10,23 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {getTokenAtPosition} from '../../util/src/typescript';
|
import {getTokenAtPosition} from '../../util/src/typescript';
|
||||||
import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from '../api';
|
import {ExternalTemplateSourceMapping, TemplateId, TemplateSourceMapping} from '../api';
|
||||||
import {makeTemplateDiagnostic, TemplateDiagnostic} from '../diagnostics';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `ts.Diagnostic` with additional information about the diagnostic related to template
|
||||||
|
* type-checking.
|
||||||
|
*/
|
||||||
|
export interface TemplateDiagnostic extends ts.Diagnostic {
|
||||||
|
/**
|
||||||
|
* The component with the template that resulted in this diagnostic.
|
||||||
|
*/
|
||||||
|
componentFile: ts.SourceFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template id of the component that resulted in this diagnostic.
|
||||||
|
*/
|
||||||
|
templateId: TemplateId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter interface which allows the template type-checking diagnostics code to interpret offsets
|
* Adapter interface which allows the template type-checking diagnostics code to interpret offsets
|
||||||
@ -141,6 +157,101 @@ export function findTypeCheckBlock(file: ts.SourceFile, id: TemplateId): ts.Node
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
|
||||||
|
*/
|
||||||
|
export function makeTemplateDiagnostic(
|
||||||
|
templateId: TemplateId, mapping: TemplateSourceMapping, span: ParseSourceSpan,
|
||||||
|
category: ts.DiagnosticCategory, code: number, messageText: string|ts.DiagnosticMessageChain,
|
||||||
|
relatedMessage?: {
|
||||||
|
text: string,
|
||||||
|
span: ParseSourceSpan,
|
||||||
|
}): TemplateDiagnostic {
|
||||||
|
if (mapping.type === 'direct') {
|
||||||
|
let relatedInformation: ts.DiagnosticRelatedInformation[]|undefined = undefined;
|
||||||
|
if (relatedMessage !== undefined) {
|
||||||
|
relatedInformation = [{
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
file: mapping.node.getSourceFile(),
|
||||||
|
start: relatedMessage.span.start.offset,
|
||||||
|
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
||||||
|
messageText: relatedMessage.text,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
// For direct mappings, the error is shown inline as ngtsc was able to pinpoint a string
|
||||||
|
// constant within the `@Component` decorator for the template. This allows us to map the error
|
||||||
|
// directly into the bytes of the source file.
|
||||||
|
return {
|
||||||
|
source: 'ngtsc',
|
||||||
|
code,
|
||||||
|
category,
|
||||||
|
messageText,
|
||||||
|
file: mapping.node.getSourceFile(),
|
||||||
|
componentFile: mapping.node.getSourceFile(),
|
||||||
|
templateId,
|
||||||
|
start: span.start.offset,
|
||||||
|
length: span.end.offset - span.start.offset,
|
||||||
|
relatedInformation,
|
||||||
|
};
|
||||||
|
} else if (mapping.type === 'indirect' || mapping.type === 'external') {
|
||||||
|
// For indirect mappings (template was declared inline, but ngtsc couldn't map it directly
|
||||||
|
// to a string constant in the decorator), the component's file name is given with a suffix
|
||||||
|
// indicating it's not the TS file being displayed, but a template.
|
||||||
|
// For external temoplates, the HTML filename is used.
|
||||||
|
const componentSf = mapping.componentClass.getSourceFile();
|
||||||
|
const componentName = mapping.componentClass.name.text;
|
||||||
|
// TODO(alxhub): remove cast when TS in g3 supports this narrowing.
|
||||||
|
const fileName = mapping.type === 'indirect' ?
|
||||||
|
`${componentSf.fileName} (${componentName} template)` :
|
||||||
|
(mapping as ExternalTemplateSourceMapping).templateUrl;
|
||||||
|
// TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS
|
||||||
|
// parser against the template (HTML is just really syntactically invalid TypeScript code ;).
|
||||||
|
// Also investigate caching the file to avoid running the parser multiple times.
|
||||||
|
const sf = ts.createSourceFile(
|
||||||
|
fileName, mapping.template, ts.ScriptTarget.Latest, false, ts.ScriptKind.JSX);
|
||||||
|
|
||||||
|
let relatedInformation: ts.DiagnosticRelatedInformation[] = [];
|
||||||
|
if (relatedMessage !== undefined) {
|
||||||
|
relatedInformation.push({
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
file: sf,
|
||||||
|
start: relatedMessage.span.start.offset,
|
||||||
|
length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
||||||
|
messageText: relatedMessage.text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
relatedInformation.push({
|
||||||
|
category: ts.DiagnosticCategory.Message,
|
||||||
|
code: 0,
|
||||||
|
file: componentSf,
|
||||||
|
// mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
|
||||||
|
// and getEnd() are used because they don't include surrounding whitespace.
|
||||||
|
start: mapping.node.getStart(),
|
||||||
|
length: mapping.node.getEnd() - mapping.node.getStart(),
|
||||||
|
messageText: `Error occurs in the template of component ${componentName}.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: 'ngtsc',
|
||||||
|
category,
|
||||||
|
code,
|
||||||
|
messageText,
|
||||||
|
file: sf,
|
||||||
|
componentFile: componentSf,
|
||||||
|
templateId,
|
||||||
|
start: span.start.offset,
|
||||||
|
length: span.end.offset - span.start.offset,
|
||||||
|
// Show a secondary message indicating the component whose template contains the error.
|
||||||
|
relatedInformation,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected source mapping type: ${(mapping as {type: string}).type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SourceLocation {
|
interface SourceLocation {
|
||||||
id: TemplateId;
|
id: TemplateId;
|
||||||
span: AbsoluteSourceSpan;
|
span: AbsoluteSourceSpan;
|
||||||
@ -227,3 +338,8 @@ function hasIgnoreMarker(node: ts.Node, sourceFile: ts.SourceFile): boolean {
|
|||||||
return commentText === IGNORE_MARKER;
|
return commentText === IGNORE_MARKER;
|
||||||
}) === true;
|
}) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTemplateDiagnostic(diagnostic: ts.Diagnostic): diagnostic is TemplateDiagnostic {
|
||||||
|
return diagnostic.hasOwnProperty('componentFile') &&
|
||||||
|
ts.isSourceFile((diagnostic as any).componentFile);
|
||||||
|
}
|
||||||
|
@ -11,9 +11,8 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {ErrorCode, ngErrorCode} from '../../diagnostics';
|
import {ErrorCode, ngErrorCode} from '../../diagnostics';
|
||||||
import {TemplateId} from '../api';
|
import {TemplateId} from '../api';
|
||||||
import {makeTemplateDiagnostic, TemplateDiagnostic} from '../diagnostics';
|
|
||||||
|
|
||||||
import {TemplateSourceResolver} from './diagnostics';
|
import {makeTemplateDiagnostic, TemplateDiagnostic, TemplateSourceResolver} from './diagnostics';
|
||||||
|
|
||||||
const REGISTRY = new DomElementSchemaRegistry();
|
const REGISTRY = new DomElementSchemaRegistry();
|
||||||
const REMOVE_XHTML_REGEX = /^:xhtml:/;
|
const REMOVE_XHTML_REGEX = /^:xhtml:/;
|
||||||
@ -94,7 +93,7 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const diag = makeTemplateDiagnostic(
|
const diag = makeTemplateDiagnostic(
|
||||||
id, mapping, element.startSourceSpan, ts.DiagnosticCategory.Error,
|
id, mapping, element.sourceSpan, ts.DiagnosticCategory.Error,
|
||||||
ngErrorCode(ErrorCode.SCHEMA_INVALID_ELEMENT), errorMsg);
|
ngErrorCode(ErrorCode.SCHEMA_INVALID_ELEMENT), errorMsg);
|
||||||
this._diagnostics.push(diag);
|
this._diagnostics.push(diag);
|
||||||
}
|
}
|
||||||
|
@ -79,8 +79,8 @@ export class Environment {
|
|||||||
fnName,
|
fnName,
|
||||||
body: true,
|
body: true,
|
||||||
fields: {
|
fields: {
|
||||||
inputs: dir.inputs.classPropertyNames,
|
inputs: Object.keys(dir.inputs),
|
||||||
outputs: dir.outputs.classPropertyNames,
|
outputs: Object.keys(dir.outputs),
|
||||||
// TODO: support queries
|
// TODO: support queries
|
||||||
queries: dir.queries,
|
queries: dir.queries,
|
||||||
},
|
},
|
||||||
|
@ -12,9 +12,8 @@ import * as ts from 'typescript';
|
|||||||
import {ErrorCode, makeDiagnostic, makeRelatedInformation, ngErrorCode} from '../../diagnostics';
|
import {ErrorCode, makeDiagnostic, makeRelatedInformation, ngErrorCode} from '../../diagnostics';
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
import {TemplateId} from '../api';
|
import {TemplateId} from '../api';
|
||||||
import {makeTemplateDiagnostic, TemplateDiagnostic} from '../diagnostics';
|
|
||||||
|
|
||||||
import {TemplateSourceResolver} from './diagnostics';
|
import {makeTemplateDiagnostic, TemplateDiagnostic, TemplateSourceResolver} from './diagnostics';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,9 +8,7 @@
|
|||||||
|
|
||||||
import {AbsoluteSourceSpan, ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
|
import {AbsoluteSourceSpan, ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {TemplateId, TemplateSourceMapping} from '../api';
|
import {TemplateId, TemplateSourceMapping} from '../api';
|
||||||
import {getTemplateId} from '../diagnostics';
|
|
||||||
|
|
||||||
import {TemplateSourceResolver} from './diagnostics';
|
import {TemplateSourceResolver} from './diagnostics';
|
||||||
import {computeLineStartsMap, getLineAndCharacterFromPosition} from './line_mappings';
|
import {computeLineStartsMap, getLineAndCharacterFromPosition} from './line_mappings';
|
||||||
@ -83,3 +81,28 @@ export class TemplateSourceManager implements TemplateSourceResolver {
|
|||||||
return templateSource.toParseSourceSpan(span.start, span.end);
|
return templateSource.toParseSourceSpan(span.start, span.end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TEMPLATE_ID = Symbol('ngTemplateId');
|
||||||
|
const NEXT_TEMPLATE_ID = Symbol('ngNextTemplateId');
|
||||||
|
|
||||||
|
interface HasTemplateId {
|
||||||
|
[TEMPLATE_ID]: TemplateId;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HasNextTemplateId {
|
||||||
|
[NEXT_TEMPLATE_ID]: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplateId(node: ts.ClassDeclaration&Partial<HasTemplateId>): TemplateId {
|
||||||
|
if (node[TEMPLATE_ID] === undefined) {
|
||||||
|
node[TEMPLATE_ID] = allocateTemplateId(node.getSourceFile());
|
||||||
|
}
|
||||||
|
return node[TEMPLATE_ID]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allocateTemplateId(sf: ts.SourceFile&Partial<HasNextTemplateId>): TemplateId {
|
||||||
|
if (sf[NEXT_TEMPLATE_ID] === undefined) {
|
||||||
|
sf[NEXT_TEMPLATE_ID] = 1;
|
||||||
|
}
|
||||||
|
return (`tcb${sf[NEXT_TEMPLATE_ID]!++}`) as TemplateId;
|
||||||
|
}
|
||||||
|
@ -10,7 +10,6 @@ import {AST, BindingPipe, BindingType, BoundTarget, DYNAMIC_TYPE, ImplicitReceiv
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassPropertyName} from '../../metadata';
|
|
||||||
import {ClassDeclaration} from '../../reflection';
|
import {ClassDeclaration} from '../../reflection';
|
||||||
import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata} from '../api';
|
import {TemplateId, TypeCheckableDirectiveMeta, TypeCheckBlockMetadata} from '../api';
|
||||||
|
|
||||||
@ -432,7 +431,7 @@ class TcbDirectiveCtorOp extends TcbOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add unset directive inputs for each of the remaining unset fields.
|
// Add unset directive inputs for each of the remaining unset fields.
|
||||||
for (const [fieldName] of this.dir.inputs) {
|
for (const fieldName of Object.keys(this.dir.inputs)) {
|
||||||
if (!genericInputs.has(fieldName)) {
|
if (!genericInputs.has(fieldName)) {
|
||||||
genericInputs.set(fieldName, {type: 'unset', field: fieldName});
|
genericInputs.set(fieldName, {type: 'unset', field: fieldName});
|
||||||
}
|
}
|
||||||
@ -744,14 +743,22 @@ class TcbDirectiveOutputsOp extends TcbOp {
|
|||||||
|
|
||||||
execute(): null {
|
execute(): null {
|
||||||
let dirId: ts.Expression|null = null;
|
let dirId: ts.Expression|null = null;
|
||||||
|
|
||||||
|
|
||||||
|
// `dir.outputs` is an object map of field names on the directive class to event names.
|
||||||
|
// This is backwards from what's needed to match event handlers - a map of event names to field
|
||||||
|
// names is desired. Invert `dir.outputs` into `fieldByEventName` to create this map.
|
||||||
|
const fieldByEventName = new Map<string, string>();
|
||||||
const outputs = this.dir.outputs;
|
const outputs = this.dir.outputs;
|
||||||
|
for (const key of Object.keys(outputs)) {
|
||||||
|
fieldByEventName.set(outputs[key], key);
|
||||||
|
}
|
||||||
|
|
||||||
for (const output of this.node.outputs) {
|
for (const output of this.node.outputs) {
|
||||||
if (output.type !== ParsedEventType.Regular || !outputs.hasBindingPropertyName(output.name)) {
|
if (output.type !== ParsedEventType.Regular || !fieldByEventName.has(output.name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// TODO(alxhub): consider supporting multiple fields with the same property name for outputs.
|
const field = fieldByEventName.get(output.name)!;
|
||||||
const field = outputs.getByBindingPropertyName(output.name)![0].classPropertyName;
|
|
||||||
|
|
||||||
if (this.tcb.env.config.checkTypeOfOutputEvents) {
|
if (this.tcb.env.config.checkTypeOfOutputEvents) {
|
||||||
// For strict checking of directive events, generate a call to the `subscribe` method
|
// For strict checking of directive events, generate a call to the `subscribe` method
|
||||||
@ -1218,8 +1225,9 @@ class Scope {
|
|||||||
if (node instanceof TmplAstElement) {
|
if (node instanceof TmplAstElement) {
|
||||||
// Go through the directives and remove any inputs that it claims from `elementInputs`.
|
// Go through the directives and remove any inputs that it claims from `elementInputs`.
|
||||||
for (const dir of directives) {
|
for (const dir of directives) {
|
||||||
for (const propertyName of dir.inputs.propertyNames) {
|
for (const fieldName of Object.keys(dir.inputs)) {
|
||||||
claimedInputs.add(propertyName);
|
const value = dir.inputs[fieldName];
|
||||||
|
claimedInputs.add(Array.isArray(value) ? value[0] : value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1256,8 +1264,8 @@ class Scope {
|
|||||||
if (node instanceof TmplAstElement) {
|
if (node instanceof TmplAstElement) {
|
||||||
// Go through the directives and register any outputs that it claims in `claimedOutputs`.
|
// Go through the directives and register any outputs that it claims in `claimedOutputs`.
|
||||||
for (const dir of directives) {
|
for (const dir of directives) {
|
||||||
for (const outputProperty of dir.outputs.propertyNames) {
|
for (const outputField of Object.keys(dir.outputs)) {
|
||||||
claimedOutputs.add(outputProperty);
|
claimedOutputs.add(dir.outputs[outputField]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,7 +1276,7 @@ class Scope {
|
|||||||
|
|
||||||
interface TcbBoundInput {
|
interface TcbBoundInput {
|
||||||
attribute: TmplAstBoundAttribute|TmplAstTextAttribute;
|
attribute: TmplAstBoundAttribute|TmplAstTextAttribute;
|
||||||
fieldNames: ClassPropertyName[];
|
fieldNames: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1529,6 +1537,7 @@ function getBoundInputs(
|
|||||||
tcb: Context): TcbBoundInput[] {
|
tcb: Context): TcbBoundInput[] {
|
||||||
const boundInputs: TcbBoundInput[] = [];
|
const boundInputs: TcbBoundInput[] = [];
|
||||||
|
|
||||||
|
const propertyToFieldNames = invertInputs(directive.inputs);
|
||||||
const processAttribute = (attr: TmplAstBoundAttribute|TmplAstTextAttribute) => {
|
const processAttribute = (attr: TmplAstBoundAttribute|TmplAstTextAttribute) => {
|
||||||
// Skip non-property bindings.
|
// Skip non-property bindings.
|
||||||
if (attr instanceof TmplAstBoundAttribute && attr.type !== BindingType.Property) {
|
if (attr instanceof TmplAstBoundAttribute && attr.type !== BindingType.Property) {
|
||||||
@ -1541,11 +1550,10 @@ function getBoundInputs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip the attribute if the directive does not have an input for it.
|
// Skip the attribute if the directive does not have an input for it.
|
||||||
const inputs = directive.inputs.getByBindingPropertyName(attr.name);
|
if (!propertyToFieldNames.has(attr.name)) {
|
||||||
if (inputs === null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fieldNames = inputs.map(input => input.classPropertyName);
|
const fieldNames = propertyToFieldNames.get(attr.name)!;
|
||||||
boundInputs.push({attribute: attr, fieldNames});
|
boundInputs.push({attribute: attr, fieldNames});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1572,6 +1580,26 @@ function translateInput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inverts the input-mapping from field-to-property name into property-to-field name, to be able
|
||||||
|
* to match a property in a template with the corresponding field on a directive.
|
||||||
|
*/
|
||||||
|
function invertInputs(inputs: {[fieldName: string]: string|[string, string]}):
|
||||||
|
Map<string, string[]> {
|
||||||
|
const propertyToFieldNames = new Map<string, string[]>();
|
||||||
|
for (const fieldName of Object.keys(inputs)) {
|
||||||
|
const propertyNames = inputs[fieldName];
|
||||||
|
const propertyName = Array.isArray(propertyNames) ? propertyNames[0] : propertyNames;
|
||||||
|
|
||||||
|
if (propertyToFieldNames.has(propertyName)) {
|
||||||
|
propertyToFieldNames.get(propertyName)!.push(fieldName);
|
||||||
|
} else {
|
||||||
|
propertyToFieldNames.set(propertyName, [fieldName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return propertyToFieldNames;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An input binding that corresponds with a field of a directive.
|
* An input binding that corresponds with a field of a directive.
|
||||||
*/
|
*/
|
||||||
|
@ -16,13 +16,11 @@ ts_library(
|
|||||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||||
"//packages/compiler-cli/src/ngtsc/imports",
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
"//packages/compiler-cli/src/ngtsc/shims",
|
"//packages/compiler-cli/src/ngtsc/shims",
|
||||||
"//packages/compiler-cli/src/ngtsc/testing",
|
"//packages/compiler-cli/src/ngtsc/testing",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
|
||||||
"//packages/compiler-cli/src/ngtsc/util",
|
"//packages/compiler-cli/src/ngtsc/util",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user