Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
ecac8c032b | |||
ce728dd323 | |||
20dd0ac4f2 | |||
0f4d424da9 | |||
6f2fd6e4b4 | |||
8a68a7c490 | |||
800e31b6d3 | |||
0d784e0b00 | |||
8b0e26f786 | |||
6ff6159e32 | |||
db9704a433 | |||
add3e18e9d | |||
b71cdf99fc | |||
f882ff01f3 | |||
2184ad5436 | |||
83d7819bfc | |||
4cc519eb10 | |||
8bcb0784bc | |||
9f68ff9e5b | |||
689964b20f | |||
677d666a20 | |||
4d7b176329 | |||
6b0e247412 | |||
9e23a69d1b | |||
63bde80ca7 | |||
5ea9a61a87 | |||
5a09ea328f | |||
000c834554 | |||
d24ce21c45 | |||
68ca32fe51 | |||
4fe3f37b3c | |||
e0cf3ad4b3 | |||
5abf068b23 | |||
28d2bf7d7c | |||
51c1911c56 | |||
eaf5b5856d | |||
9a9cc01be1 | |||
6330200004 | |||
645fa40571 | |||
2cccf59415 | |||
2a9b254ca5 | |||
78f968b7dd | |||
2eaf420bdf | |||
07efe2a6aa | |||
773d7b86aa |
@ -29,7 +29,7 @@ var_4_win: &cache_key_win_fallback v5-angular-win-node-12-
|
||||
|
||||
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
|
||||
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
|
||||
var_5: &components_repo_unit_tests_cache_key v5-angular-components-2ec7254f88c4865e0de251f74c27e64c9d00d40a
|
||||
var_5: &components_repo_unit_tests_cache_key v5-angular-components-598db096e668aa7e9debd56eedfd127b7a55e371
|
||||
var_6: &components_repo_unit_tests_cache_key_fallback v5-angular-components-
|
||||
|
||||
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
|
||||
@ -465,12 +465,14 @@ jobs:
|
||||
- when:
|
||||
condition: << parameters.ivy >>
|
||||
steps:
|
||||
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
|
||||
# package installer picks up the locally built packages from that location.
|
||||
# Rename the "dist/*-dist-ivy-aot" packages directories (persisted to the workspace by
|
||||
# the `build-ivy-npm-packages` job) to "dist/*-dist" as the AIO package installer
|
||||
# picks up the locally built packages from that location.
|
||||
# *Note*: We could also adjust the packages installer, but given we won't have
|
||||
# two different folders of Angular distributions in the future, we should keep
|
||||
# the packages installer unchanged.
|
||||
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
|
||||
- run: mv dist/zone.js-dist-ivy-aot dist/zone.js-dist
|
||||
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
|
||||
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
|
||||
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
|
||||
@ -552,6 +554,7 @@ jobs:
|
||||
root: *workspace_location
|
||||
paths:
|
||||
- ng/dist/packages-dist-ivy-aot
|
||||
- ng/dist/zone.js-dist-ivy-aot
|
||||
|
||||
# We run a subset of the integration tests outside of Bazel that track
|
||||
# payload size.
|
||||
|
@ -68,7 +68,7 @@ setPublicVar COMPONENTS_REPO_TMP_DIR "/tmp/angular-components-repo"
|
||||
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
|
||||
setPublicVar COMPONENTS_REPO_BRANCH "master"
|
||||
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
|
||||
setPublicVar COMPONENTS_REPO_COMMIT "2ec7254f88c4865e0de251f74c27e64c9d00d40a"
|
||||
setPublicVar COMPONENTS_REPO_COMMIT "598db096e668aa7e9debd56eedfd127b7a55e371"
|
||||
|
||||
|
||||
####################################################################################################
|
||||
|
@ -546,7 +546,6 @@ groups:
|
||||
conditions:
|
||||
- >
|
||||
contains_any_globs(files, [
|
||||
'modules/benchmarks_external/**',
|
||||
'modules/benchmarks/**'
|
||||
])
|
||||
reviewers:
|
||||
@ -928,6 +927,7 @@ groups:
|
||||
'.github/**',
|
||||
'.vscode/**',
|
||||
'.yarn/**',
|
||||
'dev-infra/**',
|
||||
'docs/BAZEL.md',
|
||||
'docs/CARETAKER.md',
|
||||
'docs/COMMITTER.md',
|
||||
@ -952,6 +952,7 @@ groups:
|
||||
'tools/browsers/**',
|
||||
'tools/build/**',
|
||||
'tools/circular_dependency_test/**',
|
||||
'tools/contributing-stats/**',
|
||||
'tools/gulp-tasks/**',
|
||||
'tools/ng_rollup_bundle/**',
|
||||
'tools/ngcontainer/**',
|
||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,3 +1,31 @@
|
||||
<a name="9.0.6"></a>
|
||||
## [9.0.6](https://github.com/angular/angular/compare/9.0.5...9.0.6) (2020-03-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** do not use manifest paths for generated imports within compilation unit ([#35841](https://github.com/angular/angular/issues/35841)) ([5ea9a61](https://github.com/angular/angular/commit/5ea9a61))
|
||||
* **compiler:** process `imports` first and `declarations` second while calculating scopes ([#35850](https://github.com/angular/angular/issues/35850)) ([6f2fd6e](https://github.com/angular/angular/commit/6f2fd6e)), closes [#35502](https://github.com/angular/angular/issues/35502)
|
||||
* **core:** add `noSideEffects()` to `make*Decorator()` functions ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([4fe3f37](https://github.com/angular/angular/commit/4fe3f37))
|
||||
* **core:** add `noSideEffects()` to `ɵɵdefineComponent()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([68ca32f](https://github.com/angular/angular/commit/68ca32f))
|
||||
* **core:** remove side effects from `ɵɵgetInheritedFactory()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([000c834](https://github.com/angular/angular/commit/000c834))
|
||||
* **core:** remove side effects from `ɵɵNgOnChangesFeature()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([d24ce21](https://github.com/angular/angular/commit/d24ce21))
|
||||
* **core:** undecorated-classes-with-di migration should handle libraries generated with CLI versions past v6.2.0 ([#35824](https://github.com/angular/angular/issues/35824)) ([eaf5b58](https://github.com/angular/angular/commit/eaf5b58)), closes [#34985](https://github.com/angular/angular/issues/34985)
|
||||
* **language-service:** resolve the variable from the template context first ([#35982](https://github.com/angular/angular/issues/35982)) ([f882ff0](https://github.com/angular/angular/commit/f882ff0))
|
||||
* **localize:** improve matching and parsing of XLIFF 1.2 translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([677d666](https://github.com/angular/angular/commit/677d666))
|
||||
* **localize:** improve matching and parsing of XLIFF 2.0 translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([689964b](https://github.com/angular/angular/commit/689964b))
|
||||
* **localize:** improve matching and parsing of XTB translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([9f68ff9](https://github.com/angular/angular/commit/9f68ff9))
|
||||
* **localize:** merge translation from all XLIFF `<file>` elements ([#35936](https://github.com/angular/angular/issues/35936)) ([83d7819](https://github.com/angular/angular/commit/83d7819)), closes [#35839](https://github.com/angular/angular/issues/35839)
|
||||
* **platform-browser:** add missing peerDependency on `[@angular](https://github.com/angular)/animations` ([#35949](https://github.com/angular/angular/issues/35949)) ([db9704a](https://github.com/angular/angular/commit/db9704a)), closes [#35888](https://github.com/angular/angular/issues/35888)
|
||||
* **router:** state data missing in routerLink ([#33203](https://github.com/angular/angular/issues/33203)) ([773d7b8](https://github.com/angular/angular/commit/773d7b8))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ngcc:** reduce directory traversing ([#35756](https://github.com/angular/angular/issues/35756)) ([2eaf420](https://github.com/angular/angular/commit/2eaf420)), closes [#35717](https://github.com/angular/angular/issues/35717)
|
||||
|
||||
|
||||
|
||||
<a name="9.0.5"></a>
|
||||
## [9.0.5](https://github.com/angular/angular/compare/9.0.4...9.0.5) (2020-03-04)
|
||||
|
||||
|
@ -158,11 +158,11 @@ export class Provider6bComponent {
|
||||
|
||||
// #docregion silent-logger
|
||||
// An object in the shape of the logger service
|
||||
export function SilentLoggerFn() {}
|
||||
function silentLoggerFn() {}
|
||||
|
||||
const silentLogger = {
|
||||
export const SilentLogger = {
|
||||
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
|
||||
log: SilentLoggerFn
|
||||
log: silentLoggerFn
|
||||
};
|
||||
// #enddocregion silent-logger
|
||||
|
||||
@ -171,7 +171,7 @@ const silentLogger = {
|
||||
template: template,
|
||||
providers:
|
||||
// #docregion providers-7
|
||||
[{ provide: Logger, useValue: silentLogger }]
|
||||
[{ provide: Logger, useValue: SilentLogger }]
|
||||
// #enddocregion providers-7
|
||||
})
|
||||
export class Provider7Component {
|
||||
|
@ -185,7 +185,7 @@ searchHeroes(term: string): Observable {
|
||||
|
||||
let heroesURL = `${this.heroesURL}?${term}`;
|
||||
return this.http.jsonp(heroesUrl, 'callback').pipe(
|
||||
catchError(this.handleError('searchHeroes', []) // then handle the error
|
||||
catchError(this.handleError('searchHeroes', [])) // then handle the error
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -218,7 +218,7 @@ zone.run(() => {
|
||||
This new context, `zoneThis`, can be retrieved from the `setTimeout()` callback function, and this context is the same when the `setTimeout()` is scheduled.
|
||||
To get the context, you can call [`Zone.current`](https://github.com/angular/angular/blob/master/packages/zone.js/lib/zone.ts).
|
||||
|
||||
### Zones and async lifecycle hooks
|
||||
## Zones and async lifecycle hooks
|
||||
|
||||
Zone.js can create contexts that persist across asynchronous operations as well as provide lifecycle hooks for asynchronous operations.
|
||||
|
||||
@ -303,7 +303,7 @@ This service creates a zone named `angular` to automatically trigger change dete
|
||||
|
||||
### NgZone `run()`/`runOutsideOfAngular()`
|
||||
|
||||
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then(),and `addEventListener()`.
|
||||
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then()`, and `addEventListener()`.
|
||||
For the full list, see the [Zone Module document](https://github.com/angular/angular/blob/master/packages/zone.js/MODULE.md).
|
||||
Therefore in those asynchronous APIs, you don't need to trigger change detection manually.
|
||||
|
||||
@ -315,12 +315,12 @@ This function, and all asynchronous operations in that function, trigger change
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(private ngZone: NgZone) {}
|
||||
ngOnInit() {
|
||||
// new async API is not handled by Zone, so you need to
|
||||
// use ngZone.run to make the asynchronous operation in angular zone
|
||||
// and trigger change detection automatically
|
||||
// New async API is not handled by Zone, so you need to
|
||||
// use ngZone.run() to make the asynchronous operation in the angular zone
|
||||
// and trigger change detection automatically.
|
||||
this.ngZone.run(() => {
|
||||
someNewAsyncAPI(() => {
|
||||
// update data of component
|
||||
// update the data of the component
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -335,19 +335,20 @@ In that situation, you can use another NgZone method: [runOutsideAngular()](api/
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(private ngZone: NgZone) {}
|
||||
ngOnInit() {
|
||||
// you know no data will be updated
|
||||
// you don't want to do change detection in this
|
||||
// specified operation, you can call runOutsideAngular
|
||||
// You know no data will be updated,
|
||||
// so you don't want to trigger change detection in this
|
||||
// specified operation. Instead, call ngZone.runOutsideAngular()
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
setTimeout(() => {
|
||||
// do something will not update component data
|
||||
// update component data
|
||||
// but don't trigger change detection.
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Seting up Zone.js
|
||||
### Setting up Zone.js
|
||||
|
||||
To make Zone.js available in Angular, you need to import the zone.js package.
|
||||
If you are using the Angular CLI, this step is done automatically, and you will see the following line in the `src/polyfills.ts`:
|
||||
|
@ -13,12 +13,6 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngIndia 2020 -->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
|
||||
<td>Delhi, India</td>
|
||||
<td>Feb 29, 2020</td>
|
||||
</tr>
|
||||
<!-- ng-conf 2020 -->
|
||||
<tr>
|
||||
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
@ -38,6 +32,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngIndia 2020 -->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
|
||||
<td>Delhi, India</td>
|
||||
<td>Feb 29, 2020</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf 2019 -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
|
@ -106,7 +106,7 @@ aio-search-results {
|
||||
}
|
||||
}
|
||||
|
||||
.not-found {
|
||||
.no-results {
|
||||
color: $darkgray;
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,12 @@ const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
|
||||
|
||||
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
|
||||
const ANGULAR_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
|
||||
const ZONEJS_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/zone.js-dist');
|
||||
const DIST_PACKAGES_BUILD_SCRIPT = path.join(ANGULAR_ROOT_DIR, 'scripts/build/build-packages-dist.js');
|
||||
const DIST_PACKAGES_BUILD_CMD = `"${process.execPath}" "${DIST_PACKAGES_BUILD_SCRIPT}"`;
|
||||
|
||||
/**
|
||||
* A tool that can install Angular dependencies for a project from NPM or from the
|
||||
* A tool that can install Angular/Zone.js dependencies for a project from NPM or from the
|
||||
* locally built distributables.
|
||||
*
|
||||
* This tool is used to change dependencies of the `aio` application and the example
|
||||
@ -33,7 +34,7 @@ class NgPackagesInstaller {
|
||||
* @param {object} options - a hash of options for the install:
|
||||
* * `debug` (`boolean`) - whether to display debug messages.
|
||||
* * `force` (`boolean`) - whether to force a local installation even if there is a local marker file.
|
||||
* * `buildPackages` (`boolean`) - whether to build the local Angular packages before using them.
|
||||
* * `buildPackages` (`boolean`) - whether to build the local Angular/Zone.js packages before using them.
|
||||
* (NOTE: Building the packages is currently not supported on Windows, so a message is printed instead.)
|
||||
* * `ignorePackages` (`string[]`) - a collection of names of packages that should not be copied over.
|
||||
*/
|
||||
@ -52,7 +53,7 @@ class NgPackagesInstaller {
|
||||
|
||||
/**
|
||||
* Check whether the dependencies have been overridden with locally built
|
||||
* Angular packages. This is done by checking for the `_local_.json` marker file.
|
||||
* Angular/Zone.js packages. This is done by checking for the `_local_.json` marker file.
|
||||
* This will emit a warning to the console if the dependencies have been overridden.
|
||||
*/
|
||||
checkDependencies() {
|
||||
@ -62,8 +63,8 @@ class NgPackagesInstaller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Install locally built Angular dependencies, overriding the dependencies in the package.json
|
||||
* This will also write a "marker" file (`_local_.json`), which contains the overridden package.json
|
||||
* Install locally built Angular/Zone.js dependencies, overriding the dependencies in the `package.json`.
|
||||
* This will also write a "marker" file (`_local_.json`), which contains the overridden `package.json`
|
||||
* contents and acts as an indicator that dependencies have been overridden.
|
||||
*/
|
||||
installLocalDependencies() {
|
||||
@ -86,13 +87,13 @@ class NgPackagesInstaller {
|
||||
// Prevent accidental publishing of the package, if something goes wrong.
|
||||
tmpConfig.private = true;
|
||||
|
||||
// Overwrite project dependencies/devDependencies to Angular packages with local files.
|
||||
// Overwrite project dependencies/devDependencies to Angular/Zone.js packages with local files.
|
||||
['dependencies', 'devDependencies'].forEach(prop => {
|
||||
const deps = tmpConfig[prop] || {};
|
||||
Object.keys(deps).forEach(key2 => {
|
||||
const pkg2 = packages[key2];
|
||||
if (pkg2) {
|
||||
// point the core Angular packages at the distributable folder
|
||||
// point the local packages at the distributable folder
|
||||
deps[key2] = `file:${pkg2.packageDir}`;
|
||||
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
|
||||
}
|
||||
@ -125,8 +126,8 @@ class NgPackagesInstaller {
|
||||
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
|
||||
}
|
||||
} finally {
|
||||
// Restore local Angular packages dependencies to other Angular packages.
|
||||
this._log(`Restoring original ${PACKAGE_JSON} for local Angular packages.`);
|
||||
// Restore local Angular/Zone.js packages dependencies to other Angular packages.
|
||||
this._log(`Restoring original ${PACKAGE_JSON} for local packages.`);
|
||||
Object.keys(packages).forEach(key => {
|
||||
const pkg = packages[key];
|
||||
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config, null, 2));
|
||||
@ -167,7 +168,7 @@ class NgPackagesInstaller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the local Angular packages.
|
||||
* Build the local Angular/Zone.js packages.
|
||||
*
|
||||
* NOTE:
|
||||
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
|
||||
@ -177,14 +178,14 @@ class NgPackagesInstaller {
|
||||
const canBuild = process.platform !== 'win32';
|
||||
|
||||
if (canBuild) {
|
||||
this._log(`Building the Angular packages with: ${DIST_PACKAGES_BUILD_SCRIPT}`);
|
||||
this._log(`Building the local packages with: ${DIST_PACKAGES_BUILD_SCRIPT}`);
|
||||
shelljs.exec(DIST_PACKAGES_BUILD_CMD);
|
||||
} else {
|
||||
this._warn([
|
||||
'Automatically building the local Angular packages is currently not supported on Windows.',
|
||||
`Please, ensure '${ANGULAR_DIST_PACKAGES_DIR}' exists and is up-to-date (e.g. by running ` +
|
||||
`'${DIST_PACKAGES_BUILD_SCRIPT}' in Git Bash for Windows, Windows Subsystem for Linux or a Linux docker ` +
|
||||
'container or VM).',
|
||||
'Automatically building the local Angular/Zone.js packages is currently not supported on Windows.',
|
||||
`Please, ensure '${ANGULAR_DIST_PACKAGES_DIR}' and '${ZONEJS_DIST_PACKAGES_DIR}' exist and are up-to-date ` +
|
||||
`(e.g. by running '${DIST_PACKAGES_BUILD_SCRIPT}' in Git Bash for Windows, Windows Subsystem for Linux or ` +
|
||||
'a Linux docker container or VM).',
|
||||
'',
|
||||
'Proceeding anyway...',
|
||||
].join('\n'));
|
||||
@ -204,7 +205,7 @@ class NgPackagesInstaller {
|
||||
// grab peer dependencies
|
||||
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
|
||||
Object.keys(sourcePackagePeerDeps)
|
||||
// ignore peerDependencies which are already core Angular packages
|
||||
// ignore peerDependencies which are already core Angular/Zone.js packages
|
||||
.filter(key => !packages[key])
|
||||
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
|
||||
}
|
||||
@ -214,11 +215,13 @@ class NgPackagesInstaller {
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash of Angular package configs.
|
||||
* (Detected as directories in '/dist/packages-dist/' that contain a top-level 'package.json' file.)
|
||||
* A hash of Angular/Zone.js package configs.
|
||||
* (Detected as directories in '/dist/packages-dist/' and '/dist/zone.js-dist/' that contain a top-level
|
||||
* 'package.json' file.)
|
||||
*/
|
||||
_getDistPackages() {
|
||||
this._log(`Angular distributable directory: ${ANGULAR_DIST_PACKAGES_DIR}.`);
|
||||
this._log(`Zone.js distributable directory: ${ZONEJS_DIST_PACKAGES_DIR}.`);
|
||||
|
||||
if (this.buildPackages) {
|
||||
this._buildDistPackages();
|
||||
@ -254,7 +257,10 @@ class NgPackagesInstaller {
|
||||
return packages;
|
||||
};
|
||||
|
||||
const packageConfigs = collectPackages(ANGULAR_DIST_PACKAGES_DIR);
|
||||
const packageConfigs = {
|
||||
...collectPackages(ANGULAR_DIST_PACKAGES_DIR),
|
||||
...collectPackages(ZONEJS_DIST_PACKAGES_DIR),
|
||||
};
|
||||
|
||||
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
|
||||
return packageConfigs;
|
||||
@ -343,7 +349,7 @@ class NgPackagesInstaller {
|
||||
|
||||
// Log a warning.
|
||||
this._warn([
|
||||
`The project at "${absoluteProjectDir}" is running against the local Angular build.`,
|
||||
`The project at "${absoluteProjectDir}" is running against the local Angular/Zone.js build.`,
|
||||
'',
|
||||
'To restore the npm packages run:',
|
||||
'',
|
||||
@ -396,10 +402,10 @@ function main() {
|
||||
|
||||
.option('debug', { describe: 'Print additional debug information.', default: false })
|
||||
.option('force', { describe: 'Force the command to execute even if not needed.', default: false })
|
||||
.option('build-packages', { describe: 'Build the local Angular packages, before using them.', default: false })
|
||||
.option('ignore-packages', { describe: 'List of Angular packages that should not be used in local mode.', default: [], array: true })
|
||||
.option('build-packages', { describe: 'Build the local Angular/Zone.js packages, before using them.', default: false })
|
||||
.option('ignore-packages', { describe: 'List of Angular/Zone.js packages that should not be used in local mode.', default: [], array: true })
|
||||
|
||||
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular distributables.', () => {}, argv => {
|
||||
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular/Zone.js distributables.', () => {}, argv => {
|
||||
createInstaller(argv).installLocalDependencies();
|
||||
})
|
||||
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {
|
||||
|
@ -15,6 +15,7 @@ describe('NgPackagesInstaller', () => {
|
||||
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
|
||||
const ngRootDir = path.resolve(__dirname, '../../..');
|
||||
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
|
||||
const zoneJsDir = path.join(ngRootDir, 'dist/zone.js-dist');
|
||||
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
|
||||
let installer;
|
||||
|
||||
@ -51,7 +52,7 @@ describe('NgPackagesInstaller', () => {
|
||||
|
||||
describe('installLocalDependencies()', () => {
|
||||
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
|
||||
let dummyNgPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
|
||||
let dummyLocalPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(installer, '_checkLocalMarker');
|
||||
@ -60,17 +61,18 @@ describe('NgPackagesInstaller', () => {
|
||||
|
||||
spyOn(installer, '_parseLockfile').and.returnValue({
|
||||
'rxjs@^6.3.0': {version: '6.3.3'},
|
||||
'zone.js@^0.8.26': {version: '0.8.27'}
|
||||
'rxjs-dev@^6.3.0': {version: '6.4.2'}
|
||||
});
|
||||
|
||||
// These are the packages that are "found" in the dist directory
|
||||
dummyNgPackages = {
|
||||
dummyLocalPackages = {
|
||||
'@angular/core': {
|
||||
packageDir: `${packagesDir}/core`,
|
||||
packageJsonPath: `${packagesDir}/core/package.json`,
|
||||
config: {
|
||||
peerDependencies: {
|
||||
'rxjs': '^6.4.0',
|
||||
'rxjs-dev': '^6.4.0',
|
||||
'some-package': '5.0.1',
|
||||
'zone.js': '~0.8.26'
|
||||
}
|
||||
@ -101,32 +103,40 @@ describe('NgPackagesInstaller', () => {
|
||||
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
|
||||
peerDependencies: { tsickle: '^1.4.0' }
|
||||
}
|
||||
}
|
||||
},
|
||||
'zone.js': {
|
||||
packageDir: `${zoneJsDir}/zone.js`,
|
||||
packageJsonPath: `${zoneJsDir}/zone.js/package.json`,
|
||||
config: {
|
||||
devDependencies: { typescript: '^2.4.2' }
|
||||
}
|
||||
},
|
||||
};
|
||||
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyNgPackages));
|
||||
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyLocalPackages));
|
||||
|
||||
// This is the package.json in the "test" folder
|
||||
dummyPackage = {
|
||||
dependencies: {
|
||||
'@angular/core': '4.4.1',
|
||||
'@angular/common': '4.4.1',
|
||||
rxjs: '^6.3.0'
|
||||
rxjs: '^6.3.0',
|
||||
'zone.js': '^0.8.26'
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': '4.4.1',
|
||||
'zone.js': '^0.8.26'
|
||||
'rxjs-dev': '^6.3.0'
|
||||
}
|
||||
};
|
||||
dummyPackageJson = JSON.stringify(dummyPackage);
|
||||
fs.readFileSync.and.returnValue(dummyPackageJson);
|
||||
|
||||
// This is the package.json that is temporarily written to the "test" folder
|
||||
// Note that the Angular (dev)dependencies have been modified to use a "file:" path
|
||||
// And that the peerDependencies from `dummyNgPackages` have been updated or added as
|
||||
// Note that the Angular/Zone.js (dev)dependencies have been modified to use a "file:" path
|
||||
// and that the peerDependencies from `dummyLocalPackages` have been updated or added as
|
||||
// (dev)dependencies (unless the current version in lockfile satisfies semver).
|
||||
//
|
||||
// For example, `zone.js@0.8.27` (from lockfile) satisfies `zone.js@~0.8.26` (from
|
||||
// `@angular/core`), thus `zone.js: ^0.8.26` (from original `package.json`) is retained.
|
||||
// For example, `rxjs-dev@6.4.2` (from lockfile) satisfies `rxjs-dev@^6.4.0` (from
|
||||
// `@angular/core`), thus `rxjs-dev: ^6.3.0` (from original `package.json`) is retained.
|
||||
// In contrast, `rxjs@6.3.3` (from lockfile) does not satisfy `rxjs@^6.4.0 (from
|
||||
// `@angular/core`), thus `rxjs: ^6.3.0` (from original `package.json`) is replaced with
|
||||
// `rxjs: ^6.4.0` (from `@angular/core`).
|
||||
@ -134,11 +144,12 @@ describe('NgPackagesInstaller', () => {
|
||||
dependencies: {
|
||||
'@angular/core': `file:${packagesDir}/core`,
|
||||
'@angular/common': `file:${packagesDir}/common`,
|
||||
'rxjs': '^6.4.0'
|
||||
'rxjs': '^6.4.0',
|
||||
'zone.js': `file:${zoneJsDir}/zone.js`,
|
||||
},
|
||||
devDependencies: {
|
||||
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
|
||||
'zone.js': '^0.8.26',
|
||||
'rxjs-dev': '^6.3.0',
|
||||
'some-package': '5.0.1',
|
||||
typescript: '^2.4.2'
|
||||
},
|
||||
@ -182,31 +193,56 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
|
||||
it('should temporarily overwrite the package.json files of local Angular packages', () => {
|
||||
const pkgJsonFor = pkgName => dummyNgPackages[`@angular/${pkgName}`].packageJsonPath;
|
||||
const pkgConfigFor = pkgName => copyJsonObj(dummyNgPackages[`@angular/${pkgName}`].config);
|
||||
const pkgJsonPathFor = pkgName => dummyLocalPackages[pkgName].packageJsonPath;
|
||||
const pkgConfigFor = pkgName => copyJsonObj(dummyLocalPackages[pkgName].config);
|
||||
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
|
||||
const stringifyConfig = config => JSON.stringify(config, null, 2);
|
||||
|
||||
const allArgs = fs.writeFileSync.calls.allArgs();
|
||||
const firstFiveArgs = allArgs.slice(0, 5);
|
||||
const lastFiveArgs = allArgs.slice(-5);
|
||||
const firstSixArgs = allArgs.slice(0, 6);
|
||||
const lastSixArgs = allArgs.slice(-6);
|
||||
|
||||
expect(firstFiveArgs).toEqual([
|
||||
[pkgJsonFor('core'), stringifyConfig(overwriteConfigFor('core', {private: true}))],
|
||||
[pkgJsonFor('common'), stringifyConfig(overwriteConfigFor('common', {private: true}))],
|
||||
[pkgJsonFor('compiler'), stringifyConfig(overwriteConfigFor('compiler', {private: true}))],
|
||||
[pkgJsonFor('compiler-cli'), stringifyConfig(overwriteConfigFor('compiler-cli', {
|
||||
private: true,
|
||||
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` }
|
||||
}))],
|
||||
[pkgJsonFor('tsc-wrapped'), stringifyConfig(overwriteConfigFor('tsc-wrapped', {
|
||||
private: true,
|
||||
devDependencies: { '@angular/common': `file:${packagesDir}/common` }
|
||||
}))],
|
||||
expect(firstSixArgs).toEqual([
|
||||
[
|
||||
pkgJsonPathFor('@angular/core'),
|
||||
stringifyConfig(overwriteConfigFor('@angular/core', {private: true})),
|
||||
],
|
||||
[
|
||||
pkgJsonPathFor('@angular/common'),
|
||||
stringifyConfig(overwriteConfigFor('@angular/common', {private: true})),
|
||||
],
|
||||
[
|
||||
pkgJsonPathFor('@angular/compiler'),
|
||||
stringifyConfig(overwriteConfigFor('@angular/compiler', {private: true})),
|
||||
],
|
||||
[
|
||||
pkgJsonPathFor('@angular/compiler-cli'),
|
||||
stringifyConfig(overwriteConfigFor('@angular/compiler-cli', {
|
||||
private: true,
|
||||
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` },
|
||||
})),
|
||||
],
|
||||
[
|
||||
pkgJsonPathFor('@angular/tsc-wrapped'),
|
||||
stringifyConfig(overwriteConfigFor('@angular/tsc-wrapped', {
|
||||
private: true,
|
||||
devDependencies: { '@angular/common': `file:${packagesDir}/common` },
|
||||
})),
|
||||
],
|
||||
[
|
||||
pkgJsonPathFor('zone.js'),
|
||||
stringifyConfig(overwriteConfigFor('zone.js', {private: true})),
|
||||
],
|
||||
]);
|
||||
|
||||
expect(lastFiveArgs).toEqual(['core', 'common', 'compiler', 'compiler-cli', 'tsc-wrapped']
|
||||
.map(pkgName => [pkgJsonFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
|
||||
expect(lastSixArgs).toEqual([
|
||||
'@angular/core',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/compiler-cli',
|
||||
'@angular/tsc-wrapped',
|
||||
'zone.js',
|
||||
].map(pkgName => [pkgJsonPathFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
|
||||
});
|
||||
|
||||
it('should load the package.json', () => {
|
||||
@ -280,7 +316,7 @@ describe('NgPackagesInstaller', () => {
|
||||
|
||||
expect(shelljs.exec).not.toHaveBeenCalled();
|
||||
expect(warning).toContain(
|
||||
'Automatically building the local Angular packages is currently not supported on Windows.');
|
||||
'Automatically building the local Angular/Zone.js packages is currently not supported on Windows.');
|
||||
expect(warning).toContain('Git Bash for Windows');
|
||||
expect(warning).toContain('Windows Subsystem for Linux');
|
||||
expect(warning).toContain('Linux docker container or VM');
|
||||
@ -309,8 +345,8 @@ describe('NgPackagesInstaller', () => {
|
||||
expect(installer._buildDistPackages).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should include top level Angular packages', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
it('should include top level Angular and Zone.js packages', () => {
|
||||
const localPackages = installer._getDistPackages();
|
||||
const expectedValue = jasmine.objectContaining({
|
||||
packageDir: jasmine.any(String),
|
||||
packageJsonPath: jasmine.any(String),
|
||||
@ -318,28 +354,30 @@ describe('NgPackagesInstaller', () => {
|
||||
});
|
||||
|
||||
// For example...
|
||||
expect(ngPackages['@angular/common']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/core']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/router']).toEqual(expectedValue);
|
||||
expect(ngPackages['@angular/upgrade']).toEqual(expectedValue);
|
||||
expect(localPackages['@angular/common']).toEqual(expectedValue);
|
||||
expect(localPackages['@angular/core']).toEqual(expectedValue);
|
||||
expect(localPackages['@angular/router']).toEqual(expectedValue);
|
||||
expect(localPackages['@angular/upgrade']).toEqual(expectedValue);
|
||||
expect(localPackages['zone.js']).toEqual(expectedValue);
|
||||
|
||||
expect(ngPackages['@angular/upgrade/static']).not.toBeDefined();
|
||||
expect(localPackages['@angular/upgrade/static']).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should store each package\'s directory', () => {
|
||||
const ngPackages = installer._getDistPackages();
|
||||
const localPackages = installer._getDistPackages();
|
||||
|
||||
// For example...
|
||||
expect(ngPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
|
||||
expect(ngPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
|
||||
expect(localPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
|
||||
expect(localPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
|
||||
expect(localPackages['zone.js'].packageDir).toBe(path.join(zoneJsDir, 'zone.js'));
|
||||
});
|
||||
|
||||
it('should not include packages that have been ignored', () => {
|
||||
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
|
||||
const ngPackages = installer._getDistPackages();
|
||||
const localPackages = installer._getDistPackages();
|
||||
|
||||
expect(ngPackages['@angular/common']).toBeDefined();
|
||||
expect(ngPackages['@angular/router']).toBeUndefined();
|
||||
expect(localPackages['@angular/common']).toBeDefined();
|
||||
expect(localPackages['@angular/router']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@ -480,7 +518,7 @@ describe('NgPackagesInstaller', () => {
|
||||
describe('_printWarning()', () => {
|
||||
it('should mention the message passed in the warning', () => {
|
||||
installer._printWarning();
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular build');
|
||||
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular/Zone.js build');
|
||||
});
|
||||
|
||||
it('should mention the command to restore the Angular packages in any warning', () => {
|
||||
|
38
dev-infra/BUILD.bazel
Normal file
38
dev-infra/BUILD.bazel
Normal file
@ -0,0 +1,38 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
|
||||
|
||||
ts_library(
|
||||
name = "cli",
|
||||
srcs = [
|
||||
"cli.ts",
|
||||
],
|
||||
deps = [
|
||||
"//dev-infra/pullapprove",
|
||||
"@npm//@types/node",
|
||||
],
|
||||
)
|
||||
|
||||
rollup_bundle(
|
||||
name = "bundle",
|
||||
config_file = "rollup.config.js",
|
||||
entry_point = ":cli.ts",
|
||||
format = "umd",
|
||||
sourcemap = "hidden",
|
||||
deps = [
|
||||
":cli",
|
||||
"@npm//rollup-plugin-commonjs",
|
||||
"@npm//rollup-plugin-node-resolve",
|
||||
],
|
||||
)
|
||||
|
||||
pkg_npm(
|
||||
name = "npm_package",
|
||||
srcs = [
|
||||
"package.json",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":bundle",
|
||||
],
|
||||
)
|
20
dev-infra/cli.ts
Normal file
20
dev-infra/cli.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {verify} from './pullapprove/verify';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
|
||||
// TODO(josephperrott): Set up proper cli flag/command handling
|
||||
switch (args[0]) {
|
||||
case 'pullapprove:verify':
|
||||
verify();
|
||||
break;
|
||||
default:
|
||||
console.info('No commands were matched');
|
||||
}
|
10
dev-infra/package.json
Normal file
10
dev-infra/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@angular/dev-infra-private",
|
||||
"version": "0.0.0",
|
||||
"description": "INTERNAL USE ONLY - Angular internal DevInfra tooling/scripts - INTERNAL USE ONLY",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"bin": {
|
||||
"ng-dev": "./bundle.js"
|
||||
}
|
||||
}
|
19
dev-infra/pullapprove/BUILD.bazel
Normal file
19
dev-infra/pullapprove/BUILD.bazel
Normal file
@ -0,0 +1,19 @@
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "pullapprove",
|
||||
srcs = [
|
||||
"verify.ts",
|
||||
],
|
||||
visibility = ["//dev-infra:__subpackages__"],
|
||||
deps = [
|
||||
"@npm//@types/minimatch",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/shelljs",
|
||||
"@npm//@types/yaml",
|
||||
"@npm//minimatch",
|
||||
"@npm//shelljs",
|
||||
"@npm//tslib",
|
||||
"@npm//yaml",
|
||||
],
|
||||
)
|
215
dev-infra/pullapprove/verify.ts
Normal file
215
dev-infra/pullapprove/verify.ts
Normal file
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {readFileSync} from 'fs';
|
||||
import {IMinimatch, Minimatch} from 'minimatch';
|
||||
import * as path from 'path';
|
||||
import {cd, exec, set} from 'shelljs';
|
||||
import {parse as parseYaml} from 'yaml';
|
||||
|
||||
interface GlobMatcher {
|
||||
group: string;
|
||||
glob: string;
|
||||
matcher: IMinimatch;
|
||||
matchCount: number;
|
||||
}
|
||||
|
||||
export function verify() {
|
||||
// Exit early on shelljs errors
|
||||
set('-e');
|
||||
|
||||
// Regex Matcher for contains_any_globs conditions
|
||||
const CONTAINS_ANY_GLOBS_REGEX = /^'([^']+)',?$/;
|
||||
// Full path of the angular project directory
|
||||
const ANGULAR_PROJECT_DIR = process.cwd();
|
||||
// Change to the Angular project directory
|
||||
cd(ANGULAR_PROJECT_DIR);
|
||||
|
||||
// Whether to log verbosely
|
||||
const VERBOSE_MODE = process.argv.includes('-v');
|
||||
// Full path to PullApprove config file
|
||||
const PULL_APPROVE_YAML_PATH = path.resolve(ANGULAR_PROJECT_DIR, '.pullapprove.yml');
|
||||
// All relative path file names in the git repo, this is retrieved using git rather
|
||||
// that a glob so that we only get files that are checked in, ignoring things like
|
||||
// node_modules, .bazelrc.user, etc
|
||||
const ALL_FILES = exec('git ls-tree --full-tree -r --name-only HEAD', {silent: true})
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((_: string) => !!_);
|
||||
if (!ALL_FILES.length) {
|
||||
console.error(
|
||||
`No files were found to be in the git tree, did you run this command from \n` +
|
||||
`inside the angular repository?`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/** Gets the glob matching information from each group's condition. */
|
||||
function getGlobMatchersFromCondition(
|
||||
groupName: string, condition: string): [GlobMatcher[], string[]] {
|
||||
const trimmedCondition = condition.trim();
|
||||
const globMatchers: GlobMatcher[] = [];
|
||||
const badConditionLines: string[] = [];
|
||||
|
||||
// If the condition starts with contains_any_globs, evaluate all of the globs
|
||||
if (trimmedCondition.startsWith('contains_any_globs')) {
|
||||
trimmedCondition.split('\n')
|
||||
.slice(1, -1)
|
||||
.map(glob => {
|
||||
const trimmedGlob = glob.trim();
|
||||
const match = trimmedGlob.match(CONTAINS_ANY_GLOBS_REGEX);
|
||||
if (!match) {
|
||||
badConditionLines.push(trimmedGlob);
|
||||
return '';
|
||||
}
|
||||
return match[1];
|
||||
})
|
||||
.filter(globString => !!globString)
|
||||
.forEach(globString => globMatchers.push({
|
||||
group: groupName,
|
||||
glob: globString,
|
||||
matcher: new Minimatch(globString, {dot: true}),
|
||||
matchCount: 0,
|
||||
}));
|
||||
}
|
||||
return [globMatchers, badConditionLines];
|
||||
}
|
||||
|
||||
/** Create logs for each review group. */
|
||||
function logGroups(groups: Map<string, Map<string, GlobMatcher>>) {
|
||||
Array.from(groups.entries()).sort().forEach(([groupName, globs]) => {
|
||||
console.groupCollapsed(groupName);
|
||||
Array.from(globs.values())
|
||||
.sort((a, b) => b.matchCount - a.matchCount)
|
||||
.forEach(glob => console.info(`${glob.glob} - ${glob.matchCount}`));
|
||||
console.groupEnd();
|
||||
});
|
||||
}
|
||||
|
||||
/** Logs a header within a text drawn box. */
|
||||
function logHeader(...params: string[]) {
|
||||
const totalWidth = 80;
|
||||
const fillWidth = totalWidth - 2;
|
||||
const headerText = params.join(' ').substr(0, fillWidth);
|
||||
const leftSpace = Math.ceil((fillWidth - headerText.length) / 2);
|
||||
const rightSpace = fillWidth - leftSpace - headerText.length;
|
||||
const fill = (count: number, content: string) => content.repeat(count);
|
||||
|
||||
console.info(`┌${fill(fillWidth, '─')}┐`);
|
||||
console.info(`│${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}│`);
|
||||
console.info(`└${fill(fillWidth, '─')}┘`);
|
||||
}
|
||||
|
||||
/** Runs the pull approve verification check on provided files. */
|
||||
function runVerification(files: string[]) {
|
||||
// All of the globs created for each group's conditions.
|
||||
const allGlobs: GlobMatcher[] = [];
|
||||
// The pull approve config file.
|
||||
const pullApprove = readFileSync(PULL_APPROVE_YAML_PATH, {encoding: 'utf8'});
|
||||
// All of the PullApprove groups, parsed from the PullApprove yaml file.
|
||||
const parsedPullApproveGroups =
|
||||
parseYaml(pullApprove).groups as{[key: string]: {conditions: string}};
|
||||
// All files which were found to match a condition in PullApprove.
|
||||
const matchedFiles = new Set<string>();
|
||||
// All files which were not found to match a condition in PullApprove.
|
||||
const unmatchedFiles = new Set<string>();
|
||||
// All PullApprove groups which matched at least one file.
|
||||
const matchedGroups = new Map<string, Map<string, GlobMatcher>>();
|
||||
// All PullApprove groups which did not match at least one file.
|
||||
const unmatchedGroups = new Map<string, Map<string, GlobMatcher>>();
|
||||
// All condition lines which were not able to be correctly parsed, by group.
|
||||
const badConditionLinesByGroup = new Map<string, string[]>();
|
||||
// Total number of condition lines which were not able to be correctly parsed.
|
||||
let badConditionLineCount = 0;
|
||||
|
||||
// Get all of the globs from the PullApprove group conditions.
|
||||
Object.entries(parsedPullApproveGroups).forEach(([groupName, group]) => {
|
||||
for (const condition of group.conditions) {
|
||||
const [matchers, badConditions] = getGlobMatchersFromCondition(groupName, condition);
|
||||
if (badConditions.length) {
|
||||
badConditionLinesByGroup.set(groupName, badConditions);
|
||||
badConditionLineCount += badConditions.length;
|
||||
}
|
||||
allGlobs.push(...matchers);
|
||||
}
|
||||
});
|
||||
|
||||
if (badConditionLineCount) {
|
||||
console.info(`Discovered ${badConditionLineCount} parsing errors in PullApprove conditions`);
|
||||
console.info(`Attempted parsing using: ${CONTAINS_ANY_GLOBS_REGEX}`);
|
||||
console.info();
|
||||
console.info(`Unable to properly parse the following line(s) by group:`);
|
||||
badConditionLinesByGroup.forEach((badConditionLines, groupName) => {
|
||||
console.info(`- ${groupName}:`);
|
||||
badConditionLines.forEach(line => console.info(` ${line}`));
|
||||
});
|
||||
console.info();
|
||||
console.info(
|
||||
`Please correct the invalid conditions, before PullApprove verification can be completed`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check each file for if it is matched by a PullApprove condition.
|
||||
for (let file of files) {
|
||||
const matched = allGlobs.filter(glob => glob.matcher.match(file));
|
||||
matched.length ? matchedFiles.add(file) : unmatchedFiles.add(file);
|
||||
matched.forEach(glob => glob.matchCount++);
|
||||
}
|
||||
|
||||
// Add each glob for each group to a map either matched or unmatched.
|
||||
allGlobs.forEach(glob => {
|
||||
const groups = glob.matchCount ? matchedGroups : unmatchedGroups;
|
||||
const globs = groups.get(glob.group) || new Map<string, GlobMatcher>();
|
||||
// Set the globs map in the groups map
|
||||
groups.set(glob.group, globs);
|
||||
// Set the glob in the globs map
|
||||
globs.set(glob.glob, glob);
|
||||
});
|
||||
|
||||
// PullApprove is considered verified if no files or groups are found to be unsed.
|
||||
const verificationSucceeded = !(unmatchedFiles.size || unmatchedGroups.size);
|
||||
|
||||
/**
|
||||
* Overall result
|
||||
*/
|
||||
logHeader('Result');
|
||||
if (verificationSucceeded) {
|
||||
console.info('PullApprove verification succeeded!');
|
||||
} else {
|
||||
console.info(`PullApprove verification failed.\n`);
|
||||
console.info(`Please update '.pullapprove.yml' to ensure that all necessary`);
|
||||
console.info(`files/directories have owners and all patterns that appear in`);
|
||||
console.info(`the file correspond to actual files/directories in the repo.`);
|
||||
}
|
||||
/**
|
||||
* File by file Summary
|
||||
*/
|
||||
logHeader('PullApprove file match results');
|
||||
console.groupCollapsed(`Matched Files (${matchedFiles.size} files)`);
|
||||
VERBOSE_MODE && matchedFiles.forEach(file => console.info(file));
|
||||
console.groupEnd();
|
||||
console.groupCollapsed(`Unmatched Files (${unmatchedFiles.size} files)`);
|
||||
unmatchedFiles.forEach(file => console.info(file));
|
||||
console.groupEnd();
|
||||
|
||||
/**
|
||||
* Group by group Summary
|
||||
*/
|
||||
logHeader('PullApprove group matches');
|
||||
console.groupCollapsed(`Matched Groups (${matchedGroups.size} groups)`);
|
||||
VERBOSE_MODE && logGroups(matchedGroups);
|
||||
console.groupEnd();
|
||||
console.groupCollapsed(`Unmatched Groups (${unmatchedGroups.size} groups)`);
|
||||
logGroups(unmatchedGroups);
|
||||
console.groupEnd();
|
||||
|
||||
// Provide correct exit code based on verification success.
|
||||
process.exit(verificationSucceeded ? 0 : 1);
|
||||
}
|
||||
|
||||
|
||||
runVerification(ALL_FILES);
|
||||
}
|
16
dev-infra/rollup.config.js
Normal file
16
dev-infra/rollup.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
const node = require('rollup-plugin-node-resolve');
|
||||
const commonjs = require('rollup-plugin-commonjs');
|
||||
|
||||
module.exports = {
|
||||
external: ['shelljs', 'minimatch', 'yaml'],
|
||||
preferBuiltins: true,
|
||||
output: {
|
||||
banner: "#!/usr/bin/env node",
|
||||
},
|
||||
plugins: [
|
||||
node({
|
||||
mainFields: ['browser', 'es2015', 'module', 'jsnext:main', 'main'],
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
};
|
@ -60,7 +60,7 @@
|
||||
"uncompressed": {
|
||||
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
|
||||
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
||||
"bundle": 175498
|
||||
"bundle": 170618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ function installLocalPackages() {
|
||||
local_packages+=("puppeteer@file:${pwd}/../node_modules/puppeteer")
|
||||
local_packages+=("webdriver-manager@file:${pwd}/../node_modules/webdriver-manager")
|
||||
|
||||
yarn add --ignore-scripts --silent "${local_packages[@]}"
|
||||
yarn add --ignore-scripts --silent "${local_packages[@]}" --cache-folder ./.yarn_local_cache
|
||||
}
|
||||
|
||||
function patchKarmaConf() {
|
||||
@ -58,6 +58,8 @@ function testBazel() {
|
||||
# Create project
|
||||
ng new demo --collection=@angular/bazel --routing --skip-git --skip-install --style=scss
|
||||
cd demo
|
||||
# Use a local yarn cache folder so we don't access the global yarn cache
|
||||
mkdir .yarn_local_cache
|
||||
patchKarmaConf
|
||||
patchProtractorConf
|
||||
installLocalPackages
|
||||
@ -79,7 +81,7 @@ function testNonBazel() {
|
||||
# disable CLI's version check (if version is 0.0.0, then no version check happens)
|
||||
yarn --cwd node_modules/@angular/cli version --new-version 0.0.0 --no-git-tag-version
|
||||
# re-add build-angular
|
||||
yarn add --dev file:../node_modules/@angular-devkit/build-angular
|
||||
yarn add --dev file:../node_modules/@angular-devkit/build-angular --cache-folder ./.yarn_local_cache
|
||||
ng build --progress=false
|
||||
ng test --progress=false --watch=false
|
||||
ng e2e --port 0 --configuration=production --webdriver-update=false
|
||||
|
@ -53,11 +53,13 @@ rm('-rf', `demo`);
|
||||
exec('ng version');
|
||||
exec('ng new demo --skip-git --skip-install --style=css --no-interactive');
|
||||
cd('demo');
|
||||
// Use a local yarn cache folder so we don't access the global yarn cache
|
||||
exec('mkdir .yarn_local_cache');
|
||||
|
||||
// Install Angular packages that are built locally from HEAD and npm packages
|
||||
// from root node modules that are to be kept in sync
|
||||
const packageList = Object.keys(packages).map(p => `${p}@${packages[p]}`).join(' ');
|
||||
exec(`yarn add --ignore-scripts --silent ${packageList}`);
|
||||
exec(`yarn add --ignore-scripts --silent ${packageList} --cache-folder ./.yarn_local_cache`);
|
||||
|
||||
// Add @angular/elements
|
||||
exec(bazelMappings ? `ng add "${bazelMappings['@angular/elements']}"` : `ng add "${__dirname}/../../dist/packages-dist/elements"`);
|
||||
|
@ -1,6 +0,0 @@
|
||||
# How to run the benchmarks_external locally
|
||||
|
||||
$ cp -r ./modules/benchmarks_external ./dist/all/
|
||||
$ ./node_modules/.bin/tsc -p modules --emitDecoratorMetadata -w
|
||||
$ gulp serve
|
||||
$ open http://localhost:8000/all/benchmarks_external/src/tree/index.html?bundles=false
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
||||
|
||||
describe('ng1.x compiler benchmark', function() {
|
||||
|
||||
const URL = 'benchmarks_external/src/compiler/compiler_benchmark.html';
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should log withBinding stats', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#compileWithBindings'],
|
||||
id: 'ng1.compile.withBindings',
|
||||
params: [{name: 'elements', value: 150, scale: 'linear'}],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log noBindings stats', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#compileNoBindings'],
|
||||
id: 'ng1.compile.noBindings',
|
||||
params: [{name: 'elements', value: 150, scale: 'linear'}],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
||||
|
||||
describe('ng1.x largetable benchmark', function() {
|
||||
const URL = 'benchmarks_external/src/largetable/largetable_benchmark.html';
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
['baselineBinding', 'baselineInterpolation', 'ngBind', 'ngBindOnce', 'interpolation',
|
||||
'interpolationAttr', 'ngBindFn', 'interpolationFn', 'ngBindFilter', 'interpolationFilter']
|
||||
.forEach(function(benchmarkType) {
|
||||
it('should log the stats with: ' + benchmarkType, function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#destroyDom', '#createDom'],
|
||||
id: 'ng1.largetable.' + benchmarkType,
|
||||
params: [
|
||||
{name: 'columns', value: 100, scale: 'sqrt'},
|
||||
{name: 'rows', value: 20, scale: 'sqrt'},
|
||||
{name: 'benchmarkType', value: benchmarkType}
|
||||
],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {runBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
||||
|
||||
describe('ng-dart1.x naive infinite scroll benchmark', function() {
|
||||
|
||||
const URL = 'benchmarks_external/src/naive_infinite_scroll/index.html';
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
[1, 2, 4].forEach(function(appSize) {
|
||||
it('should run scroll benchmark and collect stats for appSize = ' + appSize, function(done) {
|
||||
runBenchmark({
|
||||
url: URL,
|
||||
id: 'ng1-dart1.x.naive_infinite_scroll',
|
||||
work: function() {
|
||||
$('#reset-btn').click();
|
||||
$('#run-btn').click();
|
||||
let s = 1000;
|
||||
if (appSize > 4) {
|
||||
s = s + appSize * 100;
|
||||
}
|
||||
browser.sleep(s);
|
||||
},
|
||||
params: [
|
||||
{name: 'appSize', value: appSize},
|
||||
{name: 'iterationCount', value: 20, scale: 'linear'},
|
||||
{name: 'scrollIncrement', value: 40}
|
||||
],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
||||
|
||||
describe('react tree benchmark', function() {
|
||||
|
||||
const URL = 'benchmarks_external/src/tree/react/index.html';
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should log the stats (create)', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#destroyDom', '#createDom'],
|
||||
id: 'react.tree.create',
|
||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log the stats (update)', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#createDom'],
|
||||
id: 'react.tree.update',
|
||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
||||
|
||||
describe('ng1.x tree benchmark', function() {
|
||||
|
||||
const URL = 'benchmarks_external/src/static_tree/tree_benchmark.html';
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should log the stats (create)', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#destroyDom', '#createDom'],
|
||||
id: 'ng1.static.tree.create',
|
||||
params: [],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log the stats (update)', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#createDom'],
|
||||
id: 'ng1.static.tree.update',
|
||||
params: [],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
|
||||
|
||||
describe('ng1.x tree benchmark', function() {
|
||||
|
||||
const URL = 'benchmarks_external/src/tree/tree_benchmark.html';
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should log the stats (create)', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#destroyDom', '#createDom'],
|
||||
id: 'ng1.tree.create',
|
||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log the stats (update)', function(done) {
|
||||
runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#createDom'],
|
||||
id: 'ng1.tree.update',
|
||||
params: [{name: 'depth', value: 9, scale: 'log2'}],
|
||||
waitForAngular2: false
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
});
|
@ -1,91 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
|
||||
(function(global: any /** TODO #9100 */) {
|
||||
|
||||
writeScriptTag('/all/benchmarks/vendor/core.js');
|
||||
writeScriptTag('/all/benchmarks/vendor/zone.js');
|
||||
writeScriptTag('/all/benchmarks/vendor/long-stack-trace-zone.js');
|
||||
writeScriptTag('/all/benchmarks/vendor/system.src.js');
|
||||
writeScriptTag('/all/benchmarks/vendor/Reflect.js', 'benchmarksBootstrap()');
|
||||
|
||||
(<any>global).benchmarksBootstrap = benchmarksBootstrap;
|
||||
|
||||
function benchmarksBootstrap() {
|
||||
// check query param
|
||||
const useBundles = location.search.indexOf('bundles=false') == -1;
|
||||
if (useBundles) {
|
||||
System.config({
|
||||
map: {
|
||||
'index': 'index.js',
|
||||
'@angular/core': '/packages-dist/core/bundles/core.umd.js',
|
||||
'@angular/common': '/packages-dist/common/bundles/common.umd.js',
|
||||
'@angular/forms': '/packages-dist/forms/bundles/forms.umd.js',
|
||||
'@angular/compiler': '/packages-dist/compiler/bundles/compiler.umd.js',
|
||||
'@angular/platform-browser':
|
||||
'/packages-dist/platform-browser/bundles/platform-browser.umd.js',
|
||||
'@angular/platform-browser-dynamic':
|
||||
'/packages-dist/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/http': '/packages-dist/http/bundles/http.umd.js',
|
||||
'@angular/upgrade': '/packages-dist/upgrade/bundles/upgrade.umd.js',
|
||||
'@angular/router': '/packages-dist/router/bundles/router.umd.js',
|
||||
'@angular/core/src/facade': '/all/@angular/core/src/facade',
|
||||
'rxjs': 'node_modules/rxjs',
|
||||
},
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'},
|
||||
'@angular/core/src/facade': {defaultExtension: 'js'},
|
||||
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs': { main: 'index.js', defaultExtension: 'js' },
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
'Not using the Angular bundles. Don\'t use this configuration for e2e/performance tests!');
|
||||
|
||||
System.config({
|
||||
map: {
|
||||
'index': 'index.js',
|
||||
'@angular': '/all/@angular',
|
||||
'rxjs': 'node_modules/rxjs',
|
||||
},
|
||||
packages: {
|
||||
'app': {defaultExtension: 'js'},
|
||||
'@angular/core': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/router': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/forms': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/upgrade': {main: 'index.js', defaultExtension: 'js'},
|
||||
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
|
||||
'rxjs': { main: 'index.js', defaultExtension: 'js' },
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// BOOTSTRAP the app!
|
||||
System.import('index').then(function(m: any /** TODO #9100 */) {
|
||||
m.main();
|
||||
}, console.error.bind(console));
|
||||
}
|
||||
|
||||
|
||||
function writeScriptTag(scriptUrl: any /** TODO #9100 */, onload?: any /** TODO #9100 */) {
|
||||
document.write(`<script src="${scriptUrl}" onload="${onload}"></script>`);
|
||||
}
|
||||
}(window));
|
@ -1,53 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Elements:
|
||||
<input type="number" name="elements" placeholder="elements" value="150">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Actions</h2>
|
||||
<p>
|
||||
<button id="compileWithBindings">CompileWithBindings</button>
|
||||
<button id="compileNoBindings">CompileNoBindings</button>
|
||||
</p>
|
||||
|
||||
<template id="templateNoBindings">
|
||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
||||
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="templateWithBindings">
|
||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
||||
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
|
||||
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
$SCRIPTS$
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
// compiler benchmark in AngularJS 1.x
|
||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
||||
declare var angular: any;
|
||||
|
||||
export function main() {
|
||||
const ngEl = document.createElement('div');
|
||||
angular.bootstrap(ngEl, ['app']);
|
||||
}
|
||||
|
||||
function loadTemplate(templateId, repeatCount) {
|
||||
const template = document.querySelectorAll(`#${templateId}`)[0];
|
||||
const content = (<HTMLElement>template).innerHTML;
|
||||
let result = '';
|
||||
for (let i = 0; i < repeatCount; i++) {
|
||||
result += content;
|
||||
}
|
||||
// replace [] binding syntax
|
||||
result = result.replace(/[\[\]]/g, '');
|
||||
|
||||
// Use a DIV as container as Angular 1.3 does not know <template> elements...
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = result;
|
||||
return div;
|
||||
}
|
||||
|
||||
angular.module('app', [])
|
||||
.directive('dir0',
|
||||
[
|
||||
'$parse',
|
||||
function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
const expr = $parse($attrs.attr0);
|
||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.directive('dir1',
|
||||
[
|
||||
'$parse',
|
||||
function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
const expr = $parse($attrs.attr1);
|
||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.directive('dir2',
|
||||
[
|
||||
'$parse',
|
||||
function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
const expr = $parse($attrs.attr2);
|
||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.directive('dir3',
|
||||
[
|
||||
'$parse',
|
||||
function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
const expr = $parse($attrs.attr3);
|
||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.directive('dir4',
|
||||
[
|
||||
'$parse',
|
||||
function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
const expr = $parse($attrs.attr4);
|
||||
return ($scope) => $scope.$watch(expr, angular.noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.run([
|
||||
'$compile',
|
||||
function($compile) {
|
||||
const count = getIntParameter('elements');
|
||||
const templateNoBindings = loadTemplate('templateNoBindings', count);
|
||||
const templateWithBindings = loadTemplate('templateWithBindings', count);
|
||||
|
||||
bindAction('#compileWithBindings', compileWithBindings);
|
||||
bindAction('#compileNoBindings', compileNoBindings);
|
||||
|
||||
function compileNoBindings() {
|
||||
// Need to clone every time as the compiler might modify the template!
|
||||
const cloned = templateNoBindings.cloneNode(true);
|
||||
$compile(cloned);
|
||||
}
|
||||
|
||||
function compileWithBindings() {
|
||||
// Need to clone every time as the compiler might modify the template!
|
||||
const cloned = templateWithBindings.cloneNode(true);
|
||||
$compile(cloned);
|
||||
}
|
||||
}
|
||||
]);
|
@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="compiler/compiler_benchmark.html">Compiler benchmark</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="tree/tree_benchmark.html">Tree benchmark</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="static_tree/tree_benchmark.html">Static tree benchmark</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="tree/react/index.html">React Tree benchmark</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="largetable/largetable_benchmark.html">Largetable benchmark</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="naive_infinite_scroll/index.html">Naive infinite scroll benchmark</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,58 +0,0 @@
|
||||
<ng-switch on="benchmarkType">
|
||||
<baseline-binding-table ng-switch-when="baselineBinding">
|
||||
</baseline-binding-table>
|
||||
<baseline-interpolation-table ng-switch-when="baselineInterpolation">
|
||||
</baseline-interpolation-table>
|
||||
<div ng-switch-when="ngBind">
|
||||
<h2>baseline binding</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">
|
||||
<span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngBindOnce">
|
||||
<h2>baseline binding once</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in ::row">
|
||||
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolation">
|
||||
<h2>baseline interpolation</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolationAttr">
|
||||
<h2>attribute interpolation</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row" i="{{column.i}}" j="{{column.j}}">i,j attrs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngBindFn">
|
||||
<h2>bindings with functions</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row"><span ng-bind="column.iFn()"></span>:<span ng-bind="column.jFn()"></span>|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolationFn">
|
||||
<h2>interpolation with functions</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">{{column.iFn()}}:{{column.jFn()}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngBindFilter">
|
||||
<h2>bindings with filter</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row"><span ng-bind="column.i | noop"></span>:<span ng-bind="column.j | noop"></span>|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolationFilter">
|
||||
<h2>interpolation with filter</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row">{{column.i | noop}}:{{column.j | noop}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-switch>
|
@ -1,98 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>AngularJS/Dart 1.x largetable benchmark</h2>
|
||||
<form>
|
||||
<div>
|
||||
rows:
|
||||
<input type="number" name="rows" value="100">
|
||||
columns:
|
||||
<input type="number" name="columns" value="20">
|
||||
</div>
|
||||
<div>
|
||||
baseline binding:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="baselineBinding"
|
||||
id="baselineBinding"
|
||||
checked>
|
||||
</div>
|
||||
<div>
|
||||
baseline interpolation:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="baselineInterpolation"
|
||||
id="baselineInterpolation">
|
||||
</div>
|
||||
<div>
|
||||
ngBind:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="ngBind"
|
||||
id="ngBind">
|
||||
</div>
|
||||
<div>
|
||||
ngBindOnce:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="ngBindOnce"
|
||||
id="ngBindOnce">
|
||||
</div>
|
||||
<div>
|
||||
interpolation:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="interpolation"
|
||||
id="interpolation">
|
||||
</div>
|
||||
<div>
|
||||
attribute interpolation:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="interpolationAttr"
|
||||
id="interpolationAttr">
|
||||
</div>
|
||||
<div>
|
||||
ngBind + fnInvocation:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="ngBindFn"
|
||||
id="ngBindFn">
|
||||
</div>
|
||||
<div>
|
||||
interpolation + fnInvocation:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="interpolationFn"
|
||||
id="interpolationFn">
|
||||
</div>
|
||||
<div>
|
||||
ngBind + filter:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="ngBindFilter"
|
||||
id="ngBindFilter">
|
||||
</div>
|
||||
<div>
|
||||
interpolation + filter:
|
||||
<input type="radio"
|
||||
name="benchmarkType"
|
||||
value="interpolationFilter"
|
||||
id="interpolationFilter">
|
||||
</div>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<largetable></largetable>
|
||||
</div>
|
||||
|
||||
$SCRIPTS$
|
||||
</body>
|
||||
</html>
|
@ -1,119 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {getIntParameter, getStringParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
||||
declare var angular: any;
|
||||
|
||||
const totalRows = getIntParameter('rows');
|
||||
const totalColumns = getIntParameter('columns');
|
||||
const benchmarkType = getStringParameter('benchmarkType');
|
||||
|
||||
export function main() {
|
||||
angular.bootstrap(document.querySelector('largetable'), ['app']);
|
||||
}
|
||||
|
||||
angular.module('app', [])
|
||||
.config(function($compileProvider) {
|
||||
if ($compileProvider.debugInfoEnabled) {
|
||||
$compileProvider.debugInfoEnabled(false);
|
||||
}
|
||||
})
|
||||
.filter('noop', function() { return function(input) { return input; }; })
|
||||
.directive('largetable',
|
||||
function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'largetable-js-template.html',
|
||||
controller: 'DataController'
|
||||
};
|
||||
})
|
||||
.controller('DataController',
|
||||
function($scope) {
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
|
||||
function destroyDom() {
|
||||
$scope.$apply(function() { $scope.benchmarkType = 'none'; });
|
||||
}
|
||||
|
||||
function createDom() {
|
||||
$scope.$apply(function() { $scope.benchmarkType = benchmarkType; });
|
||||
}
|
||||
|
||||
const data = $scope.data = [];
|
||||
|
||||
function iGetter() { return this.i; }
|
||||
function jGetter() { return this.j; }
|
||||
|
||||
for (let i = 0; i < totalRows; i++) {
|
||||
data[i] = [];
|
||||
for (let j = 0; j < totalColumns; j++) {
|
||||
data[i][j] = {i: i, j: j, iFn: iGetter, jFn: jGetter};
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('baselineBindingTable',
|
||||
function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $element) {
|
||||
let i, j, row, cell, comment;
|
||||
const template = document.createElement('span');
|
||||
template.setAttribute('ng-repeat', 'foo in foos');
|
||||
template.classList.add('ng-scope');
|
||||
template.appendChild(document.createElement('span'));
|
||||
template.appendChild(document.createTextNode(':'));
|
||||
template.appendChild(document.createElement('span'));
|
||||
template.appendChild(document.createTextNode('|'));
|
||||
|
||||
for (i = 0; i < totalRows; i++) {
|
||||
row = document.createElement('div');
|
||||
$element[0].appendChild(row);
|
||||
for (j = 0; j < totalColumns; j++) {
|
||||
cell = template.cloneNode(true);
|
||||
row.appendChild(cell);
|
||||
cell.childNodes[0].textContent = i;
|
||||
cell.childNodes[2].textContent = j;
|
||||
cell.ng3992 = 'xxx';
|
||||
comment = document.createComment('ngRepeat end: bar in foo');
|
||||
row.appendChild(comment);
|
||||
}
|
||||
|
||||
comment = document.createComment('ngRepeat end: foo in foos');
|
||||
$element[0].appendChild(comment);
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('baselineInterpolationTable', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, $element) {
|
||||
let i, j, row, cell, comment;
|
||||
const template = document.createElement('span');
|
||||
template.setAttribute('ng-repeat', 'foo in foos');
|
||||
template.classList.add('ng-scope');
|
||||
|
||||
for (i = 0; i < totalRows; i++) {
|
||||
row = document.createElement('div');
|
||||
$element[0].appendChild(row);
|
||||
for (j = 0; j < totalColumns; j++) {
|
||||
cell = template.cloneNode(true);
|
||||
row.appendChild(cell);
|
||||
cell.textContent = '' + i + ':' + j + '|';
|
||||
cell.ng3992 = 'xxx';
|
||||
comment = document.createComment('ngRepeat end: bar in foo');
|
||||
row.appendChild(comment);
|
||||
}
|
||||
|
||||
comment = document.createComment('ngRepeat end: foo in foos');
|
||||
$element[0].appendChild(comment);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>AngularDart Scrolling Benchmark</title>
|
||||
</head>
|
||||
<body>
|
||||
<form>
|
||||
App size: <input type="text" name="appSize" value="1"><br>
|
||||
Iteration count: <input type="text" name="iterationCount" value="1"><br>
|
||||
Scroll increment: <input type="text" name="scrollIncrement" value="1"><br>
|
||||
</form>
|
||||
<div>
|
||||
<button id="run-btn">Run</button>
|
||||
<button id="reset-btn">Reset</button>
|
||||
</div>
|
||||
<scroll-app></scroll-app>
|
||||
|
||||
$SCRIPTS$
|
||||
</body>
|
||||
</html>
|
@ -1,13 +0,0 @@
|
||||
<div>
|
||||
<div id="scrollDiv"
|
||||
ng-style="scrollDivStyle"
|
||||
ng-scroll="onScroll()">
|
||||
<div ng-style="paddingStyle"></div>
|
||||
<div ng-style="innerStyle">
|
||||
<scroll-item
|
||||
ng-repeat="item in visibleItems"
|
||||
offering="item">
|
||||
</scroll-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,45 +0,0 @@
|
||||
<div ng-style="itemStyle">
|
||||
<company-name company="offering.company"
|
||||
cell-width="companyNameWidth">
|
||||
</company-name>
|
||||
|
||||
<opportunity-name opportunity="offering.opportunity"
|
||||
cell-width="opportunityNameWidth">
|
||||
</opportunity-name>
|
||||
|
||||
<offering-name offering="offering"
|
||||
cell-width="offeringNameWidth">
|
||||
</offering-name>
|
||||
|
||||
<account-cell account="offering.account"
|
||||
cell-width="accountCellWidth">
|
||||
</account-cell>
|
||||
|
||||
<formatted-cell value="offering.basePoints"
|
||||
cell-width="basePointsWidth">
|
||||
</formatted-cell>
|
||||
|
||||
<formatted-cell value="offering.kickerPoints"
|
||||
cell-width="kickerPointsWidth">
|
||||
</formatted-cell>
|
||||
|
||||
<stage-buttons offering="offering"
|
||||
cell-width="stageButtonsWidth">
|
||||
</stage-buttons>
|
||||
|
||||
<formatted-cell value="offering.bundles"
|
||||
cell-width="bundlesWidth">
|
||||
</formatted-cell>
|
||||
|
||||
<formatted-cell value="offering.dueDate"
|
||||
cell-width="dueDateWidth">
|
||||
</formatted-cell>
|
||||
|
||||
<formatted-cell value="offering.endDate"
|
||||
cell-width="endDateWidth">
|
||||
</formatted-cell>
|
||||
|
||||
<formatted-cell value="offering.aatStatus"
|
||||
cell-width="aatStatusWidth">
|
||||
</formatted-cell>
|
||||
</div>
|
@ -1,17 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>AngularJS/Dart 1.x static tree benchmark (depth 10)</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<tree9 data="initData" ng-if="initData != null" class="app"></tree9>
|
||||
</div>
|
||||
|
||||
$SCRIPTS$
|
||||
</body>
|
||||
</html>
|
@ -1,77 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
// static tree benchmark in AngularJS 1.x
|
||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
||||
declare var angular: any;
|
||||
|
||||
const MAX_DEPTH = 10;
|
||||
|
||||
export function main() {
|
||||
angular.bootstrap(document.querySelector('.app'), ['app']);
|
||||
}
|
||||
|
||||
function addTreeDirective(module, level: number) {
|
||||
let template;
|
||||
if (level <= 0) {
|
||||
template = `<span> {{data.value}}</span>`;
|
||||
} else {
|
||||
template = `<span> {{data.value}} <tree${level-1} data='data.right'></tree${level-1}><tree${level-1} data='data.left'></tree${level-1}></span>`;
|
||||
}
|
||||
module.directive(`tree${level}`, function() { return {scope: {data: '='}, template: template}; });
|
||||
}
|
||||
|
||||
const module = angular.module('app', []);
|
||||
for (let depth = 0; depth < MAX_DEPTH; depth++) {
|
||||
addTreeDirective(module, depth);
|
||||
}
|
||||
module.config([
|
||||
'$compileProvider',
|
||||
function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
|
||||
])
|
||||
.run([
|
||||
'$rootScope',
|
||||
function($rootScope) {
|
||||
let count = 0;
|
||||
$rootScope.initData = null;
|
||||
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
|
||||
function createData(): TreeNode {
|
||||
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
|
||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
|
||||
return buildTree(MAX_DEPTH, values, 0);
|
||||
}
|
||||
|
||||
function destroyDom() {
|
||||
$rootScope.$apply(function() { $rootScope.initData = null; });
|
||||
}
|
||||
|
||||
function createDom() {
|
||||
$rootScope.$apply(function() { $rootScope.initData = createData(); });
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
class TreeNode {
|
||||
value: string;
|
||||
left: TreeNode;
|
||||
right: TreeNode;
|
||||
constructor(value, left, right) {
|
||||
this.value = value;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
}
|
||||
|
||||
function buildTree(maxDepth, values, curDepth) {
|
||||
if (maxDepth === curDepth) return new TreeNode('', null, null);
|
||||
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
|
||||
buildTree(maxDepth, values, curDepth + 1));
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
|
||||
<h2>React Tree Benchmark</h2>
|
||||
<root-tree id="rootTree"></root-tree>
|
||||
|
||||
$SCRIPTS$
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,73 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
// tree benchmark in React
|
||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
||||
import * as React from './react.min';
|
||||
|
||||
const TreeComponent = React.createClass({
|
||||
displayName: 'TreeComponent',
|
||||
|
||||
render: function() {
|
||||
const treeNode = this.props.treeNode;
|
||||
|
||||
let left = null;
|
||||
if (treeNode.left) {
|
||||
left = React.createElement(
|
||||
"span", {}, [React.createElement(TreeComponent, {treeNode: treeNode.left}, "")]);
|
||||
}
|
||||
|
||||
let right = null;
|
||||
if (treeNode.right) {
|
||||
right = React.createElement(
|
||||
"span", {}, [React.createElement(TreeComponent, {treeNode: treeNode.right}, "")]);
|
||||
}
|
||||
|
||||
const span = React.createElement("span", {}, [" " + treeNode.value, left, right]);
|
||||
|
||||
return (React.createElement("tree", {}, [span]));
|
||||
}
|
||||
});
|
||||
|
||||
export function main() {
|
||||
let count = 0;
|
||||
const maxDepth = getIntParameter('depth');
|
||||
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
|
||||
const empty = new TreeNode(0, null, null);
|
||||
const rootComponent = React.render(React.createElement(TreeComponent, {treeNode: empty}, ""),
|
||||
document.getElementById('rootTree'));
|
||||
|
||||
function destroyDom() { rootComponent.setProps({treeNode: empty}); }
|
||||
|
||||
function createDom() {
|
||||
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
|
||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
|
||||
rootComponent.setProps({treeNode: buildTree(maxDepth, values, 0)});
|
||||
}
|
||||
}
|
||||
|
||||
class TreeNode {
|
||||
value: string;
|
||||
left: TreeNode;
|
||||
right: TreeNode;
|
||||
|
||||
constructor(value, left, right) {
|
||||
this.value = value;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
}
|
||||
|
||||
function buildTree(maxDepth, values, curDepth) {
|
||||
if (maxDepth === curDepth) return new TreeNode('', null, null);
|
||||
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
|
||||
buildTree(maxDepth, values, curDepth + 1));
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export var createElement: Function;
|
||||
export var render: Function;
|
||||
export var createClass: Function;
|
@ -1,25 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>AngularJS/Dart 1.x tree benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<tree data="initData"></tree>
|
||||
</div>
|
||||
|
||||
$SCRIPTS$
|
||||
</body>
|
||||
</html>
|
@ -1,107 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
// tree benchmark in AngularJS 1.x
|
||||
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
|
||||
declare var angular: any;
|
||||
|
||||
export function main() {
|
||||
angular.bootstrap(document.querySelector('tree'), ['app']);
|
||||
}
|
||||
|
||||
angular.module('app', [])
|
||||
.directive('tree',
|
||||
function() {
|
||||
return {
|
||||
scope: {data: '='},
|
||||
template: '<span> {{data.value}}' +
|
||||
' <span tree-if="data.left"></span>' +
|
||||
' <span tree-if="data.right"></span>' +
|
||||
'</span>'
|
||||
};
|
||||
})
|
||||
// special directive for "if" as angular 1.3 does not support
|
||||
// recursive components.
|
||||
.directive('treeIf',
|
||||
[
|
||||
'$compile',
|
||||
'$parse',
|
||||
function($compile, $parse) {
|
||||
const transcludeFn;
|
||||
return {
|
||||
compile: function(element, attrs) {
|
||||
const expr = $parse('!!' + attrs.treeIf);
|
||||
const template = '<tree data="' + attrs.treeIf + '"></tree>';
|
||||
let transclude;
|
||||
return function($scope, $element, $attrs) {
|
||||
if (!transclude) {
|
||||
transclude = $compile(template);
|
||||
}
|
||||
let childScope;
|
||||
let childElement;
|
||||
$scope.$watch(expr, function(newValue) {
|
||||
if (childScope) {
|
||||
childScope.$destroy();
|
||||
childElement.remove();
|
||||
childScope = null;
|
||||
childElement = null;
|
||||
}
|
||||
if (newValue) {
|
||||
childScope = $scope.$new();
|
||||
childElement = transclude(childScope,
|
||||
function(clone) { $element.append(clone); });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
.config([
|
||||
'$compileProvider',
|
||||
function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
|
||||
])
|
||||
.run([
|
||||
'$rootScope',
|
||||
function($rootScope) {
|
||||
let count = 0;
|
||||
const maxDepth = getIntParameter('depth');
|
||||
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
|
||||
function destroyDom() {
|
||||
$rootScope.$apply(function() { $rootScope.initData = new TreeNode('', null, null); });
|
||||
}
|
||||
|
||||
function createDom() {
|
||||
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
|
||||
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
|
||||
|
||||
$rootScope.$apply(function() { $rootScope.initData = buildTree(maxDepth, values, 0); });
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
class TreeNode {
|
||||
value: string;
|
||||
left: TreeNode;
|
||||
right: TreeNode;
|
||||
constructor(value, left, right) {
|
||||
this.value = value;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
}
|
||||
|
||||
function buildTree(maxDepth, values, curDepth) {
|
||||
if (maxDepth === curDepth) return new TreeNode('', null, null);
|
||||
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
|
||||
buildTree(maxDepth, values, curDepth + 1));
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "9.0.5",
|
||||
"version": "9.0.6",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
@ -78,7 +78,7 @@
|
||||
"@types/systemjs": "0.19.32",
|
||||
"@types/yaml": "^1.2.0",
|
||||
"@types/yargs": "^11.1.1",
|
||||
"@webcomponents/custom-elements": "^1.0.4",
|
||||
"@webcomponents/custom-elements": "^1.1.0",
|
||||
"angular": "npm:angular@1.7",
|
||||
"angular-1.5": "npm:angular@1.5",
|
||||
"angular-1.6": "npm:angular@1.6",
|
||||
@ -147,6 +147,7 @@
|
||||
"@bazel/bazel": "2.1.0",
|
||||
"@bazel/buildifier": "^0.29.0",
|
||||
"@bazel/ibazel": "^0.11.1",
|
||||
"@octokit/graphql": "^4.3.1",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"browserstacktunnel-wrapper": "2.0.1",
|
||||
@ -175,9 +176,11 @@
|
||||
"rewire": "2.5.2",
|
||||
"sauce-connect": "https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz",
|
||||
"semver": "^6.3.0",
|
||||
"ts-node": "^8.6.2",
|
||||
"tslint-eslint-rules": "5.4.0",
|
||||
"tslint-no-toplevel-property-access": "0.0.2",
|
||||
"tsutils": "2.27.2",
|
||||
"typed-graphqlify": "^2.3.0",
|
||||
"universal-analytics": "0.4.15",
|
||||
"vlq": "0.2.2",
|
||||
"vrsource-tslint-rules": "5.1.1"
|
||||
|
@ -318,7 +318,12 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
"createExternalSymbolFactoryReexports": (not _is_bazel()),
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
|
||||
# We instruct the compiler to use the host for import generation in Blaze. By default,
|
||||
# module names between source files of the same compilation unit are relative paths. This
|
||||
# is not desired in google3 where the generated module names are used as qualified names
|
||||
# for aliased exports. We disable relative paths and always use manifest paths in google3.
|
||||
"_useHostForImportGeneration": (not _is_bazel()),
|
||||
"_useManifestPathsAsModuleName": (not _is_bazel()),
|
||||
}
|
||||
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
|
@ -113,19 +113,17 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
|
||||
}
|
||||
}
|
||||
|
||||
const expectedOuts = config['angularCompilerOptions']['expectedOut'];
|
||||
// These are options passed through from the `ng_module` rule which aren't supported
|
||||
// by the `@angular/compiler-cli` and are only intended for `ngc-wrapped`.
|
||||
const {expectedOut, _useManifestPathsAsModuleName} = config['angularCompilerOptions'];
|
||||
|
||||
const {basePath} = ng.calcProjectFileAndBasePath(project);
|
||||
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
|
||||
const tsHost = ts.createCompilerHost(compilerOpts, true);
|
||||
const {diagnostics} = compile({
|
||||
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
|
||||
compilerOpts,
|
||||
tsHost,
|
||||
bazelOpts,
|
||||
files,
|
||||
inputs,
|
||||
expectedOuts
|
||||
useManifestPathsAsModuleName: _useManifestPathsAsModuleName,
|
||||
expectedOuts: expectedOut, compilerOpts, tsHost, bazelOpts, files, inputs,
|
||||
});
|
||||
if (diagnostics.length) {
|
||||
console.error(ng.formatDiagnostics(diagnostics));
|
||||
@ -144,9 +142,11 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files,
|
||||
inputs, expectedOuts, gatherDiagnostics, bazelHost}: {
|
||||
export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModuleName,
|
||||
compilerOpts, tsHost, bazelOpts, files, inputs, expectedOuts,
|
||||
gatherDiagnostics, bazelHost}: {
|
||||
allDepsCompiledWithBazel?: boolean,
|
||||
useManifestPathsAsModuleName?: boolean,
|
||||
compilerOpts: ng.CompilerOptions,
|
||||
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
|
||||
bazelOpts: BazelOptions,
|
||||
@ -199,13 +199,14 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
|
||||
throw new Error(`Couldn't find bazel bin in the rootDirs: ${compilerOpts.rootDirs}`);
|
||||
}
|
||||
|
||||
const expectedOutsSet = new Set(expectedOuts.map(p => p.replace(/\\/g, '/')));
|
||||
const expectedOutsSet = new Set(expectedOuts.map(p => convertToForwardSlashPath(p)));
|
||||
|
||||
const originalWriteFile = tsHost.writeFile.bind(tsHost);
|
||||
tsHost.writeFile =
|
||||
(fileName: string, content: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
const relative = relativeToRootDirs(fileName.replace(/\\/g, '/'), [compilerOpts.rootDir]);
|
||||
const relative =
|
||||
relativeToRootDirs(convertToForwardSlashPath(fileName), [compilerOpts.rootDir]);
|
||||
if (expectedOutsSet.has(relative)) {
|
||||
expectedOutsSet.delete(relative);
|
||||
originalWriteFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
|
||||
@ -290,20 +291,32 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
|
||||
|
||||
const ngHost = ng.createCompilerHost({options: compilerOpts, tsHost: bazelHost});
|
||||
const fileNameToModuleNameCache = new Map<string, string>();
|
||||
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath: string) => {
|
||||
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath?: string) => {
|
||||
const cacheKey = `${importedFilePath}:${containingFilePath}`;
|
||||
// Memoize this lookup to avoid expensive re-parses of the same file
|
||||
// When run as a worker, the actual ts.SourceFile is cached
|
||||
// but when we don't run as a worker, there is no cache.
|
||||
// For one example target in g3, we saw a cache hit rate of 7590/7695
|
||||
if (fileNameToModuleNameCache.has(importedFilePath)) {
|
||||
return fileNameToModuleNameCache.get(importedFilePath);
|
||||
if (fileNameToModuleNameCache.has(cacheKey)) {
|
||||
return fileNameToModuleNameCache.get(cacheKey);
|
||||
}
|
||||
const result = doFileNameToModuleName(importedFilePath);
|
||||
fileNameToModuleNameCache.set(importedFilePath, result);
|
||||
const result = doFileNameToModuleName(importedFilePath, containingFilePath);
|
||||
fileNameToModuleNameCache.set(cacheKey, result);
|
||||
return result;
|
||||
};
|
||||
|
||||
function doFileNameToModuleName(importedFilePath: string): string {
|
||||
function doFileNameToModuleName(importedFilePath: string, containingFilePath?: string): string {
|
||||
const relativeTargetPath =
|
||||
relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
|
||||
const manifestTargetPath = `${bazelOpts.workspaceName}/${relativeTargetPath}`;
|
||||
if (useManifestPathsAsModuleName === true) {
|
||||
return manifestTargetPath;
|
||||
}
|
||||
|
||||
// Unless manifest paths are explicitly enforced, we initially check if a module name is
|
||||
// set for the given source file. The compiler host from `@bazel/typescript` sets source
|
||||
// file module names if the compilation targets either UMD or AMD. To ensure that the AMD
|
||||
// module names match, we first consider those.
|
||||
try {
|
||||
const sourceFile = ngHost.getSourceFile(importedFilePath, ts.ScriptTarget.Latest);
|
||||
if (sourceFile && sourceFile.moduleName) {
|
||||
@ -342,11 +355,31 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
|
||||
ngHost.amdModuleName) {
|
||||
return ngHost.amdModuleName({ fileName: importedFilePath } as ts.SourceFile);
|
||||
}
|
||||
const result = relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
|
||||
if (result.startsWith(NODE_MODULES)) {
|
||||
return result.substr(NODE_MODULES.length);
|
||||
|
||||
// If no AMD module name has been set for the source file by the `@bazel/typescript` compiler
|
||||
// host, and the target file is not part of a flat module node module package, we use the
|
||||
// following rules (in order):
|
||||
// 1. If target file is part of `node_modules/`, we use the package module name.
|
||||
// 2. If no containing file is specified, or the target file is part of a different
|
||||
// compilation unit, we use a Bazel manifest path. Relative paths are not possible
|
||||
// since we don't have a containing file, and the target file could be located in the
|
||||
// output directory, or in an external Bazel repository.
|
||||
// 3. If both rules above didn't match, we compute a relative path between the source files
|
||||
// since they are part of the same compilation unit.
|
||||
// Note that we don't want to always use (2) because it could mean that compilation outputs
|
||||
// are always leaking Bazel-specific paths, and the output is not self-contained. This could
|
||||
// break `esm2015` or `esm5` output for Angular package release output
|
||||
// Omit the `node_modules` prefix if the module name of an NPM package is requested.
|
||||
if (relativeTargetPath.startsWith(NODE_MODULES)) {
|
||||
return relativeTargetPath.substr(NODE_MODULES.length);
|
||||
} else if (
|
||||
containingFilePath == null || !bazelOpts.compilationTargetSrc.includes(importedFilePath)) {
|
||||
return manifestTargetPath;
|
||||
}
|
||||
return bazelOpts.workspaceName + '/' + result;
|
||||
const containingFileDir =
|
||||
path.dirname(relativeToRootDirs(containingFilePath, compilerOpts.rootDirs));
|
||||
const relativeImportPath = path.posix.relative(containingFileDir, relativeTargetPath);
|
||||
return relativeImportPath.startsWith('.') ? relativeImportPath : `./${relativeImportPath}`;
|
||||
}
|
||||
|
||||
ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.posix.join(
|
||||
@ -464,6 +497,10 @@ function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolea
|
||||
(bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
|
||||
}
|
||||
|
||||
function convertToForwardSlashPath(filePath: string): string {
|
||||
return filePath.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
function gatherDiagnosticsForInputsOnly(
|
||||
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
||||
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {
|
||||
|
@ -32,6 +32,7 @@ ng_package(
|
||||
deps = [
|
||||
":example",
|
||||
"//packages/bazel/test/ng_package/example/a11y",
|
||||
"//packages/bazel/test/ng_package/example/imports",
|
||||
"//packages/bazel/test/ng_package/example/secondary",
|
||||
],
|
||||
)
|
||||
|
12
packages/bazel/test/ng_package/example/imports/BUILD.bazel
Normal file
12
packages/bazel/test/ng_package/example/imports/BUILD.bazel
Normal file
@ -0,0 +1,12 @@
|
||||
load("//tools:defaults.bzl", "ng_module")
|
||||
|
||||
package(default_visibility = ["//packages/bazel/test:__subpackages__"])
|
||||
|
||||
ng_module(
|
||||
name = "imports",
|
||||
srcs = glob(["*.ts"]),
|
||||
module_name = "example/imports",
|
||||
deps = [
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
9
packages/bazel/test/ng_package/example/imports/index.ts
Normal file
9
packages/bazel/test/ng_package/example/imports/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 './public-api';
|
15
packages/bazel/test/ng_package/example/imports/public-api.ts
Normal file
15
packages/bazel/test/ng_package/example/imports/public-api.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {Injectable} from '@angular/core';
|
||||
import {MySecondService} from './second';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class MyService {
|
||||
constructor(public secondService: MySecondService) {}
|
||||
}
|
13
packages/bazel/test/ng_package/example/imports/second.ts
Normal file
13
packages/bazel/test/ng_package/example/imports/second.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class MySecondService {
|
||||
}
|
@ -13,6 +13,10 @@ bundles
|
||||
bundles/waffels-a11y.umd.js.map
|
||||
bundles/waffels-a11y.umd.min.js
|
||||
bundles/waffels-a11y.umd.min.js.map
|
||||
bundles/waffels-imports.umd.js
|
||||
bundles/waffels-imports.umd.js.map
|
||||
bundles/waffels-imports.umd.min.js
|
||||
bundles/waffels-imports.umd.min.js.map
|
||||
bundles/waffels-secondary.umd.js
|
||||
bundles/waffels-secondary.umd.js.map
|
||||
bundles/waffels-secondary.umd.min.js
|
||||
@ -29,6 +33,12 @@ esm2015
|
||||
esm2015/a11y/public-api.js
|
||||
esm2015/example.externs.js
|
||||
esm2015/example.js
|
||||
esm2015/imports
|
||||
esm2015/imports/imports.externs.js
|
||||
esm2015/imports/imports.js
|
||||
esm2015/imports/index.js
|
||||
esm2015/imports/public-api.js
|
||||
esm2015/imports/second.js
|
||||
esm2015/index.js
|
||||
esm2015/mymodule.js
|
||||
esm2015/secondary
|
||||
@ -42,6 +52,11 @@ esm5
|
||||
esm5/a11y/index.js
|
||||
esm5/a11y/public-api.js
|
||||
esm5/example.js
|
||||
esm5/imports
|
||||
esm5/imports/imports.js
|
||||
esm5/imports/index.js
|
||||
esm5/imports/public-api.js
|
||||
esm5/imports/second.js
|
||||
esm5/index.js
|
||||
esm5/mymodule.js
|
||||
esm5/secondary
|
||||
@ -54,6 +69,8 @@ extra-styles.css
|
||||
fesm2015
|
||||
fesm2015/a11y.js
|
||||
fesm2015/a11y.js.map
|
||||
fesm2015/imports.js
|
||||
fesm2015/imports.js.map
|
||||
fesm2015/secondary.js
|
||||
fesm2015/secondary.js.map
|
||||
fesm2015/waffels.js
|
||||
@ -61,10 +78,18 @@ fesm2015
|
||||
fesm5
|
||||
fesm5/a11y.js
|
||||
fesm5/a11y.js.map
|
||||
fesm5/imports.js
|
||||
fesm5/imports.js.map
|
||||
fesm5/secondary.js
|
||||
fesm5/secondary.js.map
|
||||
fesm5/waffels.js
|
||||
fesm5/waffels.js.map
|
||||
imports
|
||||
imports/imports.d.ts
|
||||
imports/imports.metadata.json
|
||||
imports/package.json
|
||||
imports.d.ts
|
||||
imports.metadata.json
|
||||
logo.png
|
||||
package.json
|
||||
secondary
|
||||
@ -444,6 +469,291 @@ var o=function n(e,t,o,r){var f,c=arguments.length,l=c<3?t:null===r?r=Object.get
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/e.A11yModule=o,Object.defineProperty(e,"__esModule",{value:!0})}));
|
||||
|
||||
--- bundles/waffels-imports.umd.js ---
|
||||
|
||||
/**
|
||||
* @license Angular v0.0.0
|
||||
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
|
||||
typeof define === 'function' && define.amd ? define('example/imports', ['exports', '@angular/core'], factory) :
|
||||
(global = global || self, factory((global.example = global.example || {}, global.example.imports = {}), global.ng.core));
|
||||
}(this, (function (exports, i0) { 'use strict';
|
||||
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
/* global Reflect, Promise */
|
||||
|
||||
var extendStatics = function(d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
|
||||
function __extends(d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
}
|
||||
|
||||
var __assign = function() {
|
||||
__assign = Object.assign || function __assign(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
|
||||
function __rest(s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function __decorate(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
}
|
||||
|
||||
function __param(paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
}
|
||||
|
||||
function __metadata(metadataKey, metadataValue) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
||||
}
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
}
|
||||
|
||||
function __generator(thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (_) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
}
|
||||
|
||||
function __exportStar(m, exports) {
|
||||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
||||
}
|
||||
|
||||
function __values(o) {
|
||||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
|
||||
if (m) return m.call(o);
|
||||
return {
|
||||
next: function () {
|
||||
if (o && i >= o.length) o = void 0;
|
||||
return { value: o && o[i++], done: !o };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function __read(o, n) {
|
||||
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
||||
if (!m) return o;
|
||||
var i = m.call(o), r, ar = [], e;
|
||||
try {
|
||||
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
||||
}
|
||||
catch (error) { e = { error: error }; }
|
||||
finally {
|
||||
try {
|
||||
if (r && !r.done && (m = i["return"])) m.call(i);
|
||||
}
|
||||
finally { if (e) throw e.error; }
|
||||
}
|
||||
return ar;
|
||||
}
|
||||
|
||||
function __spread() {
|
||||
for (var ar = [], i = 0; i < arguments.length; i++)
|
||||
ar = ar.concat(__read(arguments[i]));
|
||||
return ar;
|
||||
}
|
||||
|
||||
function __spreadArrays() {
|
||||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
|
||||
for (var r = Array(s), k = 0, i = 0; i < il; i++)
|
||||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
|
||||
r[k] = a[j];
|
||||
return r;
|
||||
};
|
||||
|
||||
function __await(v) {
|
||||
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
||||
}
|
||||
|
||||
function __asyncGenerator(thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
}
|
||||
|
||||
function __asyncDelegator(o) {
|
||||
var i, p;
|
||||
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
|
||||
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
|
||||
}
|
||||
|
||||
function __asyncValues(o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
}
|
||||
|
||||
function __makeTemplateObject(cooked, raw) {
|
||||
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
|
||||
return cooked;
|
||||
};
|
||||
|
||||
function __importStar(mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result.default = mod;
|
||||
return result;
|
||||
}
|
||||
|
||||
function __importDefault(mod) {
|
||||
return (mod && mod.__esModule) ? mod : { default: mod };
|
||||
}
|
||||
|
||||
var MySecondService = /** @class */ (function () {
|
||||
function MySecondService() {
|
||||
}
|
||||
MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||
MySecondService = __decorate([
|
||||
i0.Injectable({ providedIn: 'root' })
|
||||
], MySecondService);
|
||||
return MySecondService;
|
||||
}());
|
||||
|
||||
var MyService = /** @class */ (function () {
|
||||
function MyService(secondService) {
|
||||
this.secondService = secondService;
|
||||
}
|
||||
MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
|
||||
MyService = __decorate([
|
||||
i0.Injectable({ providedIn: 'root' }),
|
||||
__metadata("design:paramtypes", [MySecondService])
|
||||
], MyService);
|
||||
return MyService;
|
||||
}());
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generated bundle index. Do not edit.
|
||||
*/
|
||||
|
||||
exports.MyService = MyService;
|
||||
exports.ɵangular_packages_bazel_test_ng_package_example_imports_imports_a = MySecondService;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
})));
|
||||
//# sourceMappingURL=waffels-imports.umd.js.map
|
||||
|
||||
|
||||
--- bundles/waffels-imports.umd.min.js ---
|
||||
|
||||
/**
|
||||
* @license Angular v0.0.0
|
||||
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/imports",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.imports={}),e.ng.core)}(this,(function(e,t){"use strict";
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */function n(e,t,n,o){var r,c=arguments.length,f=c<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,n):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)f=Reflect.decorate(e,t,n,o);else for(var i=e.length-1;i>=0;i--)(r=e[i])&&(f=(c<3?r(f):c>3?r(t,n,f):r(t,n))||f);return c>3&&f&&Object.defineProperty(t,n,f),f}function o(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}var r=function(){function e(){}return e.ɵprov=t.ɵɵdefineInjectable({factory:function t(){return new e},token:e,providedIn:"root"}),e=n([t.Injectable({providedIn:"root"})],e)}(),c=function(){function e(e){this.secondService=e}return e.ɵprov=t.ɵɵdefineInjectable({factory:function n(){return new e(t.ɵɵinject(r))},token:e,providedIn:"root"}),e=n([t.Injectable({providedIn:"root"}),o("design:paramtypes",[r])],e)}();
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
e.MyService=c,e.ɵangular_packages_bazel_test_ng_package_example_imports_imports_a=r,Object.defineProperty(e,"__esModule",{value:!0})}));
|
||||
|
||||
--- bundles/waffels-secondary.umd.js ---
|
||||
|
||||
/**
|
||||
@ -1092,6 +1402,106 @@ A11yModule.decorators = [
|
||||
export * from './index';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
|
||||
|
||||
--- esm2015/imports/imports.externs.js ---
|
||||
|
||||
/** @externs */
|
||||
/**
|
||||
* @externs
|
||||
* @suppress {duplicate,checkTypes}
|
||||
*/
|
||||
// NOTE: generated by tsickle, do not edit.
|
||||
|
||||
|
||||
--- esm2015/imports/imports.js ---
|
||||
|
||||
/**
|
||||
* Generated bundle index. Do not edit.
|
||||
*/
|
||||
export * from './index';
|
||||
export { MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a } from './second';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDO0FBRXhCLE9BQU8sRUFBQyxlQUFlLElBQUksaUVBQWlFLEVBQUMsTUFBTSxVQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vaW5kZXgnO1xuXG5leHBvcnQge015U2Vjb25kU2VydmljZSBhcyDJtWFuZ3VsYXJfcGFja2FnZXNfYmF6ZWxfdGVzdF9uZ19wYWNrYWdlX2V4YW1wbGVfaW1wb3J0c19pbXBvcnRzX2F9IGZyb20gJy4vc2Vjb25kJzsiXX0=
|
||||
|
||||
--- esm2015/imports/index.js ---
|
||||
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: packages/bazel/test/ng_package/example/imports/index.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 { MyService } from './public-api';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iYXplbC90ZXN0L25nX3BhY2thZ2UvZXhhbXBsZS9pbXBvcnRzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQVFBLDBCQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
|
||||
|
||||
--- esm2015/imports/public-api.js ---
|
||||
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: packages/bazel/test/ng_package/example/imports/public-api.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 { Injectable } from '@angular/core';
|
||||
import { MySecondService } from './second';
|
||||
import * as i0 from "@angular/core";
|
||||
import * as i1 from "./second";
|
||||
export class MyService {
|
||||
/**
|
||||
* @param {?} secondService
|
||||
*/
|
||||
constructor(secondService) {
|
||||
this.secondService = secondService;
|
||||
}
|
||||
}
|
||||
MyService.decorators = [
|
||||
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||
];
|
||||
/** @nocollapse */
|
||||
MyService.ctorParameters = () => [
|
||||
{ type: MySecondService }
|
||||
];
|
||||
/** @nocollapse */ MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(i1.MySecondService)); }, token: MyService, providedIn: "root" });
|
||||
if (false) {
|
||||
/** @type {?} */
|
||||
MyService.prototype.secondService;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFRQSxPQUFPLEVBQUMsVUFBVSxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBQyxlQUFlLEVBQUMsTUFBTSxVQUFVLENBQUM7OztBQUd6QyxNQUFNLE9BQU8sU0FBUzs7OztJQUNwQixZQUFtQixhQUE4QjtRQUE5QixrQkFBYSxHQUFiLGFBQWEsQ0FBaUI7SUFBRyxDQUFDOzs7WUFGdEQsVUFBVSxTQUFDLEVBQUMsVUFBVSxFQUFFLE1BQU0sRUFBQzs7OztZQUZ4QixlQUFlOzs7OztJQUlULGtDQUFxQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7TXlTZWNvbmRTZXJ2aWNlfSBmcm9tICcuL3NlY29uZCc7XG5cbkBJbmplY3RhYmxlKHtwcm92aWRlZEluOiAncm9vdCd9KVxuZXhwb3J0IGNsYXNzIE15U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBzZWNvbmRTZXJ2aWNlOiBNeVNlY29uZFNlcnZpY2UpIHt9XG59XG4iXX0=
|
||||
|
||||
--- esm2015/imports/second.js ---
|
||||
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: packages/bazel/test/ng_package/example/imports/second.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 { Injectable } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MySecondService {
|
||||
}
|
||||
MySecondService.decorators = [
|
||||
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||
];
|
||||
/** @nocollapse */ MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vjb25kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvYmF6ZWwvdGVzdC9uZ19wYWNrYWdlL2V4YW1wbGUvaW1wb3J0cy9zZWNvbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBUUEsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLGVBQWUsQ0FBQzs7QUFHekMsTUFBTSxPQUFPLGVBQWU7OztZQUQzQixVQUFVLFNBQUMsRUFBQyxVQUFVLEVBQUUsTUFBTSxFQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5pbXBvcnQge0luamVjdGFibGV9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5ASW5qZWN0YWJsZSh7cHJvdmlkZWRJbjogJ3Jvb3QnfSlcbmV4cG9ydCBjbGFzcyBNeVNlY29uZFNlcnZpY2Uge1xufVxuIl19
|
||||
|
||||
--- esm2015/index.js ---
|
||||
|
||||
/**
|
||||
@ -1240,6 +1650,79 @@ export { A11yModule };
|
||||
export * from './index';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
|
||||
|
||||
--- esm5/imports/imports.js ---
|
||||
|
||||
/**
|
||||
* Generated bundle index. Do not edit.
|
||||
*/
|
||||
export * from './index';
|
||||
export { MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a } from './second';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDO0FBRXhCLE9BQU8sRUFBQyxlQUFlLElBQUksaUVBQWlFLEVBQUMsTUFBTSxVQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vaW5kZXgnO1xuXG5leHBvcnQge015U2Vjb25kU2VydmljZSBhcyDJtWFuZ3VsYXJfcGFja2FnZXNfYmF6ZWxfdGVzdF9uZ19wYWNrYWdlX2V4YW1wbGVfaW1wb3J0c19pbXBvcnRzX2F9IGZyb20gJy4vc2Vjb25kJzsiXX0=
|
||||
|
||||
--- esm5/imports/index.js ---
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 './public-api';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iYXplbC90ZXN0L25nX3BhY2thZ2UvZXhhbXBsZS9pbXBvcnRzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUVILGNBQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpYy1hcGknO1xuIl19
|
||||
|
||||
--- esm5/imports/public-api.js ---
|
||||
|
||||
import { __decorate, __metadata } from "tslib";
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 { Injectable } from '@angular/core';
|
||||
import { MySecondService } from './second';
|
||||
import * as i0 from "@angular/core";
|
||||
import * as i1 from "./second";
|
||||
var MyService = /** @class */ (function () {
|
||||
function MyService(secondService) {
|
||||
this.secondService = secondService;
|
||||
}
|
||||
MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(i1.MySecondService)); }, token: MyService, providedIn: "root" });
|
||||
MyService = __decorate([
|
||||
Injectable({ providedIn: 'root' }),
|
||||
__metadata("design:paramtypes", [MySecondService])
|
||||
], MyService);
|
||||
return MyService;
|
||||
}());
|
||||
export { MyService };
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUMsZUFBZSxFQUFDLE1BQU0sVUFBVSxDQUFDOzs7QUFHekM7SUFDRSxtQkFBbUIsYUFBOEI7UUFBOUIsa0JBQWEsR0FBYixhQUFhLENBQWlCO0lBQUcsQ0FBQzs7SUFEMUMsU0FBUztRQURyQixVQUFVLENBQUMsRUFBQyxVQUFVLEVBQUUsTUFBTSxFQUFDLENBQUM7eUNBRUcsZUFBZTtPQUR0QyxTQUFTLENBRXJCO29CQWREO0NBY0MsQUFGRCxJQUVDO1NBRlksU0FBUyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7TXlTZWNvbmRTZXJ2aWNlfSBmcm9tICcuL3NlY29uZCc7XG5cbkBJbmplY3RhYmxlKHtwcm92aWRlZEluOiAncm9vdCd9KVxuZXhwb3J0IGNsYXNzIE15U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBzZWNvbmRTZXJ2aWNlOiBNeVNlY29uZFNlcnZpY2UpIHt9XG59XG4iXX0=
|
||||
|
||||
--- esm5/imports/second.js ---
|
||||
|
||||
import { __decorate } from "tslib";
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 { Injectable } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
var MySecondService = /** @class */ (function () {
|
||||
function MySecondService() {
|
||||
}
|
||||
MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||
MySecondService = __decorate([
|
||||
Injectable({ providedIn: 'root' })
|
||||
], MySecondService);
|
||||
return MySecondService;
|
||||
}());
|
||||
export { MySecondService };
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vjb25kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvYmF6ZWwvdGVzdC9uZ19wYWNrYWdlL2V4YW1wbGUvaW1wb3J0cy9zZWNvbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7R0FNRztBQUVILE9BQU8sRUFBQyxVQUFVLEVBQUMsTUFBTSxlQUFlLENBQUM7O0FBR3pDO0lBQUE7S0FDQzs7SUFEWSxlQUFlO1FBRDNCLFVBQVUsQ0FBQyxFQUFDLFVBQVUsRUFBRSxNQUFNLEVBQUMsQ0FBQztPQUNwQixlQUFlLENBQzNCOzBCQVpEO0NBWUMsQUFERCxJQUNDO1NBRFksZUFBZSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuQEluamVjdGFibGUoe3Byb3ZpZGVkSW46ICdyb290J30pXG5leHBvcnQgY2xhc3MgTXlTZWNvbmRTZXJ2aWNlIHtcbn1cbiJdfQ==
|
||||
|
||||
--- esm5/index.js ---
|
||||
|
||||
/**
|
||||
@ -1379,6 +1862,68 @@ export { A11yModule };
|
||||
//# sourceMappingURL=a11y.js.map
|
||||
|
||||
|
||||
--- fesm2015/imports.js ---
|
||||
|
||||
/**
|
||||
* @license Angular v0.0.0
|
||||
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
import { Injectable, ɵɵdefineInjectable, ɵɵinject } from '@angular/core';
|
||||
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: packages/bazel/test/ng_package/example/imports/second.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
class MySecondService {
|
||||
}
|
||||
MySecondService.decorators = [
|
||||
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||
];
|
||||
/** @nocollapse */ MySecondService.ɵprov = ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: packages/bazel/test/ng_package/example/imports/public-api.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
class MyService {
|
||||
/**
|
||||
* @param {?} secondService
|
||||
*/
|
||||
constructor(secondService) {
|
||||
this.secondService = secondService;
|
||||
}
|
||||
}
|
||||
MyService.decorators = [
|
||||
{ type: Injectable, args: [{ providedIn: 'root' },] }
|
||||
];
|
||||
/** @nocollapse */
|
||||
MyService.ctorParameters = () => [
|
||||
{ type: MySecondService }
|
||||
];
|
||||
/** @nocollapse */ MyService.ɵprov = ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
|
||||
if (false) {
|
||||
/** @type {?} */
|
||||
MyService.prototype.secondService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @fileoverview added by tsickle
|
||||
* Generated from: packages/bazel/test/ng_package/example/imports/index.ts
|
||||
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generated bundle index. Do not edit.
|
||||
*/
|
||||
|
||||
export { MyService, MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a };
|
||||
//# sourceMappingURL=imports.js.map
|
||||
|
||||
|
||||
--- fesm2015/secondary.js ---
|
||||
|
||||
/**
|
||||
@ -1494,6 +2039,55 @@ export { A11yModule };
|
||||
//# sourceMappingURL=a11y.js.map
|
||||
|
||||
|
||||
--- fesm5/imports.js ---
|
||||
|
||||
/**
|
||||
* @license Angular v0.0.0
|
||||
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
import { __decorate, __metadata } from 'tslib';
|
||||
import { ɵɵdefineInjectable, Injectable, ɵɵinject } from '@angular/core';
|
||||
|
||||
var MySecondService = /** @class */ (function () {
|
||||
function MySecondService() {
|
||||
}
|
||||
MySecondService.ɵprov = ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
|
||||
MySecondService = __decorate([
|
||||
Injectable({ providedIn: 'root' })
|
||||
], MySecondService);
|
||||
return MySecondService;
|
||||
}());
|
||||
|
||||
var MyService = /** @class */ (function () {
|
||||
function MyService(secondService) {
|
||||
this.secondService = secondService;
|
||||
}
|
||||
MyService.ɵprov = ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
|
||||
MyService = __decorate([
|
||||
Injectable({ providedIn: 'root' }),
|
||||
__metadata("design:paramtypes", [MySecondService])
|
||||
], MyService);
|
||||
return MyService;
|
||||
}());
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generated bundle index. Do not edit.
|
||||
*/
|
||||
|
||||
export { MyService, MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a };
|
||||
//# sourceMappingURL=imports.js.map
|
||||
|
||||
|
||||
--- fesm5/secondary.js ---
|
||||
|
||||
/**
|
||||
@ -1581,6 +2175,61 @@ export { MyModule };
|
||||
//# sourceMappingURL=waffels.js.map
|
||||
|
||||
|
||||
--- imports/imports.d.ts ---
|
||||
|
||||
/**
|
||||
* @license Angular v0.0.0
|
||||
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
|
||||
export declare class MyService {
|
||||
secondService: ɵangular_packages_bazel_test_ng_package_example_imports_imports_a;
|
||||
constructor(secondService: ɵangular_packages_bazel_test_ng_package_example_imports_imports_a);
|
||||
}
|
||||
|
||||
|
||||
export declare class ɵangular_packages_bazel_test_ng_package_example_imports_imports_a {
|
||||
}
|
||||
|
||||
export { }
|
||||
|
||||
|
||||
--- imports/imports.metadata.json ---
|
||||
|
||||
{"__symbolic":"module","version":4,"metadata":{"MyService":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable","line":11,"character":1},"arguments":[{"providedIn":"root"}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","name":"ɵangular_packages_bazel_test_ng_package_example_imports_imports_a"}]}]},"statics":{"ɵprov":{}}},"ɵangular_packages_bazel_test_ng_package_example_imports_imports_a":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable","line":10,"character":1},"arguments":[{"providedIn":"root"}]}],"members":{},"statics":{"ɵprov":{}}}},"origins":{"MyService":"./imports","ɵangular_packages_bazel_test_ng_package_example_imports_imports_a":"./imports"},"importAs":"example/imports"}
|
||||
|
||||
--- imports/package.json ---
|
||||
|
||||
{
|
||||
"name": "example/imports",
|
||||
"main": "../bundles/example-imports.umd.js",
|
||||
"fesm5": "../fesm5/imports.js",
|
||||
"fesm2015": "../fesm2015/imports.js",
|
||||
"esm5": "../esm5/imports/imports.js",
|
||||
"esm2015": "../esm2015/imports/imports.js",
|
||||
"typings": "./imports.d.ts",
|
||||
"module": "../fesm5/imports.js",
|
||||
"es2015": "../fesm2015/imports.js"
|
||||
}
|
||||
|
||||
--- imports.d.ts ---
|
||||
|
||||
/**
|
||||
* @license Angular v0.0.0
|
||||
* (c) 2010-2020 Google LLC. https://angular.io/
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
export * from './imports/imports';
|
||||
|
||||
|
||||
--- imports.metadata.json ---
|
||||
|
||||
{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./imports/imports"}],"flatModuleIndexRedirect":true,"importAs":"example/imports"}
|
||||
|
||||
|
||||
--- logo.png ---
|
||||
|
||||
9db278d630f5fabd8e7ba16c2e329a3a
|
||||
|
@ -9,8 +9,9 @@ import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file
|
||||
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
|
||||
import {Logger} from '../logging/logger';
|
||||
import {NgccConfiguration} from '../packages/configuration';
|
||||
import {EntryPoint, getEntryPointInfo} from '../packages/entry_point';
|
||||
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
|
||||
import {PathMappings} from '../utils';
|
||||
import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer';
|
||||
import {EntryPointFinder} from './interface';
|
||||
import {getBasePaths} from './utils';
|
||||
|
||||
@ -40,10 +41,24 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
||||
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
|
||||
* @param sourceDirectory An absolute path to the root directory where searching begins.
|
||||
*/
|
||||
private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
|
||||
walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
|
||||
const entryPoints = this.getEntryPointsForPackage(sourceDirectory);
|
||||
if (entryPoints === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (entryPoints.length > 0) {
|
||||
// The `sourceDirectory` is an entry-point itself so no need to search its sub-directories.
|
||||
// The `sourceDirectory` is an entry point itself so no need to search its sub-directories.
|
||||
// Also check for any nested node_modules in this package but only if it was compiled by
|
||||
// Angular.
|
||||
// It is unlikely that a non Angular entry point has a dependency on an Angular library.
|
||||
if (entryPoints.some(e => e.compiledByAngular)) {
|
||||
const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules');
|
||||
if (this.fs.exists(nestedNodeModulesPath)) {
|
||||
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
|
||||
}
|
||||
}
|
||||
|
||||
return entryPoints;
|
||||
}
|
||||
|
||||
@ -52,23 +67,16 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
||||
// Not interested in hidden files
|
||||
.filter(p => !p.startsWith('.'))
|
||||
// Ignore node_modules
|
||||
.filter(p => p !== 'node_modules')
|
||||
.filter(p => p !== 'node_modules' && p !== NGCC_DIRECTORY)
|
||||
// Only interested in directories (and only those that are not symlinks)
|
||||
.filter(p => {
|
||||
const stat = this.fs.lstat(resolve(sourceDirectory, p));
|
||||
return stat.isDirectory() && !stat.isSymbolicLink();
|
||||
})
|
||||
.forEach(p => {
|
||||
// Either the directory is a potential package or a namespace containing packages (e.g
|
||||
// `@angular`).
|
||||
// Package is a potential namespace containing packages (e.g `@angular`).
|
||||
const packagePath = join(sourceDirectory, p);
|
||||
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
|
||||
|
||||
// Also check for any nested node_modules in this package
|
||||
const nestedNodeModulesPath = join(packagePath, 'node_modules');
|
||||
if (this.fs.exists(nestedNodeModulesPath)) {
|
||||
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
|
||||
}
|
||||
});
|
||||
return entryPoints;
|
||||
}
|
||||
@ -76,9 +84,9 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
||||
/**
|
||||
* Recurse the folder structure looking for all the entry points
|
||||
* @param packagePath The absolute path to an npm package that may contain entry points
|
||||
* @returns An array of entry points that were discovered.
|
||||
* @returns An array of entry points that were discovered or null when it's not a valid entrypoint
|
||||
*/
|
||||
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] {
|
||||
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[]|null {
|
||||
const entryPoints: EntryPoint[] = [];
|
||||
|
||||
// Try to get an entry point from the top level package directory
|
||||
@ -86,20 +94,30 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
||||
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, packagePath);
|
||||
|
||||
// If there is no primary entry-point then exit
|
||||
if (topLevelEntryPoint === null) {
|
||||
if (topLevelEntryPoint === NO_ENTRY_POINT) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (topLevelEntryPoint === INVALID_ENTRY_POINT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise store it and search for secondary entry-points
|
||||
entryPoints.push(topLevelEntryPoint);
|
||||
this.walkDirectory(packagePath, packagePath, (path, isDirectory) => {
|
||||
if (!path.endsWith('.js') && !isDirectory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the path is a JS file then strip its extension and see if we can match an entry-point.
|
||||
const possibleEntryPointPath = isDirectory ? path : stripJsExtension(path);
|
||||
const subEntryPoint =
|
||||
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath);
|
||||
if (subEntryPoint !== null) {
|
||||
entryPoints.push(subEntryPoint);
|
||||
if (subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INVALID_ENTRY_POINT) {
|
||||
return false;
|
||||
}
|
||||
entryPoints.push(subEntryPoint);
|
||||
return true;
|
||||
});
|
||||
|
||||
return entryPoints;
|
||||
@ -113,26 +131,25 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
|
||||
*/
|
||||
private walkDirectory(
|
||||
packagePath: AbsoluteFsPath, dir: AbsoluteFsPath,
|
||||
fn: (path: AbsoluteFsPath, isDirectory: boolean) => void) {
|
||||
fn: (path: AbsoluteFsPath, isDirectory: boolean) => boolean) {
|
||||
return this.fs
|
||||
.readdir(dir)
|
||||
// Not interested in hidden files
|
||||
.filter(path => !path.startsWith('.'))
|
||||
// Ignore node_modules
|
||||
.filter(path => path !== 'node_modules')
|
||||
.map(path => resolve(dir, path))
|
||||
.filter(path => path !== 'node_modules' && path !== NGCC_DIRECTORY)
|
||||
.forEach(path => {
|
||||
const stat = this.fs.lstat(path);
|
||||
const absolutePath = resolve(dir, path);
|
||||
const stat = this.fs.lstat(absolutePath);
|
||||
|
||||
if (stat.isSymbolicLink()) {
|
||||
// We are not interested in symbolic links
|
||||
return;
|
||||
}
|
||||
|
||||
fn(path, stat.isDirectory());
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
this.walkDirectory(packagePath, path, fn);
|
||||
const containsEntryPoint = fn(absolutePath, stat.isDirectory());
|
||||
if (containsEntryPoint) {
|
||||
this.walkDirectory(packagePath, absolutePath, fn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/depende
|
||||
import {Logger} from '../logging/logger';
|
||||
import {hasBeenProcessed} from '../packages/build_marker';
|
||||
import {NgccConfiguration} from '../packages/configuration';
|
||||
import {EntryPoint, EntryPointJsonProperty, getEntryPointInfo} from '../packages/entry_point';
|
||||
import {EntryPoint, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
|
||||
import {PathMappings} from '../utils';
|
||||
import {EntryPointFinder} from './interface';
|
||||
import {getBasePaths} from './utils';
|
||||
@ -78,20 +78,26 @@ export class TargetedEntryPointFinder implements EntryPointFinder {
|
||||
private processNextPath(): void {
|
||||
const path = this.unprocessedPaths.shift() !;
|
||||
const entryPoint = this.getEntryPoint(path);
|
||||
if (entryPoint !== null && entryPoint.compiledByAngular) {
|
||||
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
|
||||
const deps = this.resolver.getEntryPointDependencies(entryPoint);
|
||||
deps.dependencies.forEach(dep => {
|
||||
if (!this.unsortedEntryPoints.has(dep)) {
|
||||
this.unprocessedPaths.push(dep);
|
||||
}
|
||||
});
|
||||
if (entryPoint === null || !entryPoint.compiledByAngular) {
|
||||
return;
|
||||
}
|
||||
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
|
||||
const deps = this.resolver.getEntryPointDependencies(entryPoint);
|
||||
deps.dependencies.forEach(dep => {
|
||||
if (!this.unsortedEntryPoints.has(dep)) {
|
||||
this.unprocessedPaths.push(dep);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
||||
const packagePath = this.computePackagePath(entryPointPath);
|
||||
return getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
|
||||
const entryPoint =
|
||||
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
|
||||
if (entryPoint === NO_ENTRY_POINT || entryPoint === INVALID_ENTRY_POINT) {
|
||||
return null;
|
||||
}
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,41 +75,77 @@ export type EntryPointJsonProperty = Exclude<PackageJsonFormatProperties, 'types
|
||||
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
|
||||
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
|
||||
|
||||
|
||||
/**
|
||||
* The path does not represent an entry-point:
|
||||
* * there is no package.json at the path and there is no config to force an entry-point
|
||||
* * or the entrypoint is `ignored` by a config.
|
||||
*/
|
||||
export const NO_ENTRY_POINT = 'no-entry-point';
|
||||
|
||||
/**
|
||||
* The path has a package.json, but it is not a valid entry-point for ngcc processing.
|
||||
*/
|
||||
export const INVALID_ENTRY_POINT = 'invalid-entry-point';
|
||||
|
||||
/**
|
||||
* The result of calling `getEntryPointInfo()`.
|
||||
*
|
||||
* This will be an `EntryPoint` object if an Angular entry-point was identified;
|
||||
* Otherwise it will be a flag indicating one of:
|
||||
* * NO_ENTRY_POINT - the path is not an entry-point or ngcc is configured to ignore it
|
||||
* * INVALID_ENTRY_POINT - the path was a non-processable entry-point that should be searched
|
||||
* for sub-entry-points
|
||||
*/
|
||||
export type GetEntryPointResult = EntryPoint | typeof INVALID_ENTRY_POINT | typeof NO_ENTRY_POINT;
|
||||
|
||||
|
||||
/**
|
||||
* Try to create an entry-point from the given paths and properties.
|
||||
*
|
||||
* @param packagePath the absolute path to the containing npm package
|
||||
* @param entryPointPath the absolute path to the potential entry-point.
|
||||
* @returns An entry-point if it is valid, `null` otherwise.
|
||||
* @returns
|
||||
* - An entry-point if it is valid.
|
||||
* - `undefined` when there is no package.json at the path and there is no config to force an
|
||||
* entry-point or the entrypoint is `ignored`.
|
||||
* - `null` there is a package.json but it is not a valid Angular compiled entry-point.
|
||||
*/
|
||||
export function getEntryPointInfo(
|
||||
fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath,
|
||||
entryPointPath: AbsoluteFsPath): EntryPoint|null {
|
||||
entryPointPath: AbsoluteFsPath): GetEntryPointResult {
|
||||
const packageJsonPath = resolve(entryPointPath, 'package.json');
|
||||
const packageVersion = getPackageVersion(fs, packageJsonPath);
|
||||
const entryPointConfig =
|
||||
config.getConfig(packagePath, packageVersion).entryPoints[entryPointPath];
|
||||
if (entryPointConfig === undefined && !fs.exists(packageJsonPath)) {
|
||||
return null;
|
||||
const hasConfig = entryPointConfig !== undefined;
|
||||
|
||||
if (!hasConfig && !fs.exists(packageJsonPath)) {
|
||||
// No package.json and no config
|
||||
return NO_ENTRY_POINT;
|
||||
}
|
||||
|
||||
if (entryPointConfig !== undefined && entryPointConfig.ignore === true) {
|
||||
return null;
|
||||
if (hasConfig && entryPointConfig.ignore === true) {
|
||||
// Explicitly ignored
|
||||
return NO_ENTRY_POINT;
|
||||
}
|
||||
|
||||
const loadedEntryPointPackageJson =
|
||||
loadEntryPointPackage(fs, logger, packageJsonPath, entryPointConfig !== undefined);
|
||||
const entryPointPackageJson = mergeConfigAndPackageJson(
|
||||
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath);
|
||||
const loadedEntryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath, hasConfig);
|
||||
const entryPointPackageJson = hasConfig ?
|
||||
mergeConfigAndPackageJson(
|
||||
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath) :
|
||||
loadedEntryPointPackageJson;
|
||||
|
||||
if (entryPointPackageJson === null) {
|
||||
return null;
|
||||
// package.json exists but could not be parsed and there was no redeeming config
|
||||
return INVALID_ENTRY_POINT;
|
||||
}
|
||||
|
||||
// We must have a typings property
|
||||
const typings = entryPointPackageJson.typings || entryPointPackageJson.types ||
|
||||
guessTypingsFromPackageJson(fs, entryPointPath, entryPointPackageJson);
|
||||
if (typeof typings !== 'string') {
|
||||
return null;
|
||||
// Missing the required `typings` property
|
||||
return INVALID_ENTRY_POINT;
|
||||
}
|
||||
|
||||
// An entry-point is assumed to be compiled by Angular if there is either:
|
||||
@ -198,22 +234,13 @@ function isUmdModule(fs: FileSystem, sourceFilePath: AbsoluteFsPath): boolean {
|
||||
}
|
||||
|
||||
function mergeConfigAndPackageJson(
|
||||
entryPointPackageJson: EntryPointPackageJson | null,
|
||||
entryPointConfig: NgccEntryPointConfig | undefined, packagePath: AbsoluteFsPath,
|
||||
entryPointPath: AbsoluteFsPath): EntryPointPackageJson|null {
|
||||
entryPointPackageJson: EntryPointPackageJson | null, entryPointConfig: NgccEntryPointConfig,
|
||||
packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPointPackageJson {
|
||||
if (entryPointPackageJson !== null) {
|
||||
if (entryPointConfig === undefined) {
|
||||
return entryPointPackageJson;
|
||||
} else {
|
||||
return {...entryPointPackageJson, ...entryPointConfig.override};
|
||||
}
|
||||
return {...entryPointPackageJson, ...entryPointConfig.override};
|
||||
} else {
|
||||
if (entryPointConfig === undefined) {
|
||||
return null;
|
||||
} else {
|
||||
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
|
||||
return {name, ...entryPointConfig.override};
|
||||
}
|
||||
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
|
||||
return {name, ...entryPointConfig.override};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +136,71 @@ runInEachFileSystem(() => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle try to process nested node_modules of non Angular packages', () => {
|
||||
const basePath = _Abs('/nested_node_modules/node_modules');
|
||||
loadTestFiles([
|
||||
...createPackage(basePath, 'outer', ['inner'], false),
|
||||
...createPackage(_Abs(`${basePath}/outer/node_modules`), 'inner', undefined, false),
|
||||
]);
|
||||
|
||||
const finder = new DirectoryWalkerEntryPointFinder(
|
||||
fs, config, logger, resolver, _Abs('/nested_node_modules/node_modules'), undefined);
|
||||
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
|
||||
const {entryPoints} = finder.findEntryPoints();
|
||||
expect(spy.calls.allArgs()).toEqual([
|
||||
[_Abs(basePath)],
|
||||
[_Abs(`${basePath}/outer`)],
|
||||
]);
|
||||
|
||||
expect(entryPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not try to process deeply nested folders of non TypeScript packages', () => {
|
||||
const basePath = _Abs('/namespaced/node_modules');
|
||||
loadTestFiles([
|
||||
...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'),
|
||||
{
|
||||
name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`),
|
||||
contents: 'index',
|
||||
},
|
||||
]);
|
||||
|
||||
const finder =
|
||||
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
|
||||
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
|
||||
const {entryPoints} = finder.findEntryPoints();
|
||||
expect(spy.calls.allArgs()).toEqual([
|
||||
[_Abs(basePath)],
|
||||
[_Abs(`${basePath}/@schematics`)],
|
||||
[_Abs(`${basePath}/@schematics/angular`)],
|
||||
]);
|
||||
|
||||
expect(entryPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not try to process nested node_modules of non TypeScript packages', () => {
|
||||
const basePath = _Abs('/namespaced/node_modules');
|
||||
loadTestFiles([
|
||||
...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'),
|
||||
...createNonTsPackage(_Abs(`${basePath}/@schematics/angular/node_modules`), 'test'),
|
||||
{
|
||||
name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`),
|
||||
contents: 'index',
|
||||
},
|
||||
]);
|
||||
|
||||
const finder =
|
||||
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
|
||||
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
|
||||
const {entryPoints} = finder.findEntryPoints();
|
||||
expect(spy.calls.allArgs()).toEqual([
|
||||
[_Abs(basePath)],
|
||||
[_Abs(`${basePath}/@schematics`)],
|
||||
[_Abs(`${basePath}/@schematics/angular`)],
|
||||
]);
|
||||
|
||||
expect(entryPoints).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle dependencies via pathMappings', () => {
|
||||
const basePath = _Abs('/path_mapped/node_modules');
|
||||
@ -195,8 +260,9 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
function createPackage(
|
||||
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
|
||||
return [
|
||||
basePath: AbsoluteFsPath, packageName: string, deps: string[] = [],
|
||||
isCompiledByAngular = true): TestFile[] {
|
||||
const files: TestFile[] = [
|
||||
{
|
||||
name: _Abs(`${basePath}/${packageName}/package.json`),
|
||||
contents: JSON.stringify({
|
||||
@ -205,8 +271,29 @@ runInEachFileSystem(() => {
|
||||
})
|
||||
},
|
||||
{
|
||||
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
|
||||
contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'),
|
||||
},
|
||||
];
|
||||
|
||||
if (isCompiledByAngular) {
|
||||
files.push({
|
||||
name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`),
|
||||
contents: 'metadata info'
|
||||
});
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function createNonTsPackage(
|
||||
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
|
||||
return [
|
||||
{
|
||||
name: _Abs(`${basePath}/${packageName}/package.json`),
|
||||
contents: JSON.stringify({
|
||||
fesm2015: `./fesm2015/${packageName}.js`,
|
||||
})
|
||||
},
|
||||
{
|
||||
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
|
||||
|
@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem} from '../../../
|
||||
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {loadTestFiles} from '../../../test/helpers';
|
||||
import {NgccConfiguration} from '../../src/packages/configuration';
|
||||
import {EntryPoint, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point';
|
||||
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
@ -55,7 +55,7 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if configured to ignore the specified entry-point', () => {
|
||||
it('should return `NO_ENTRY_POINT` if configured to ignore the specified entry-point', () => {
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
|
||||
@ -75,7 +75,7 @@ runInEachFileSystem(() => {
|
||||
const entryPoint = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/valid_entry_point'));
|
||||
expect(entryPoint).toBe(null);
|
||||
expect(entryPoint).toBe(NO_ENTRY_POINT);
|
||||
});
|
||||
|
||||
it('should override the properties on package.json if the entry-point is configured', () => {
|
||||
@ -116,7 +116,7 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if there is no package.json at the entry-point path', () => {
|
||||
it('should return `NO_ENTRY_POINT` if there is no package.json at the entry-point path', () => {
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _(
|
||||
@ -128,7 +128,7 @@ runInEachFileSystem(() => {
|
||||
const entryPoint = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/missing_package_json'));
|
||||
expect(entryPoint).toBe(null);
|
||||
expect(entryPoint).toBe(NO_ENTRY_POINT);
|
||||
});
|
||||
|
||||
it('should return a configured entry-point if there is no package.json at the entry-point path',
|
||||
@ -165,26 +165,27 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
|
||||
it('should return null if there is no typings or types field in the package.json', () => {
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _('/project/node_modules/some_package/missing_typings/package.json'),
|
||||
contents: createPackageJson('missing_typings', {excludes: ['typings']})
|
||||
},
|
||||
{
|
||||
name:
|
||||
_('/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
|
||||
contents: 'some meta data'
|
||||
},
|
||||
]);
|
||||
const config = new NgccConfiguration(fs, _('/project'));
|
||||
const entryPoint = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/missing_typings'));
|
||||
expect(entryPoint).toBe(null);
|
||||
});
|
||||
it('should return `INVALID_ENTRY_POINT` if there is no typings or types field in the package.json',
|
||||
() => {
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _('/project/node_modules/some_package/missing_typings/package.json'),
|
||||
contents: createPackageJson('missing_typings', {excludes: ['typings']})
|
||||
},
|
||||
{
|
||||
name: _(
|
||||
'/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
|
||||
contents: 'some meta data'
|
||||
},
|
||||
]);
|
||||
const config = new NgccConfiguration(fs, _('/project'));
|
||||
const entryPoint = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/missing_typings'));
|
||||
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
|
||||
});
|
||||
|
||||
it('should return null if the typings or types field is not a string in the package.json',
|
||||
it('should return `INVALID_ENTRY_POINT` if the typings or types field is not a string in the package.json',
|
||||
() => {
|
||||
loadTestFiles([
|
||||
{
|
||||
@ -201,7 +202,7 @@ runInEachFileSystem(() => {
|
||||
const entryPoint = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/typings_array'));
|
||||
expect(entryPoint).toBe(null);
|
||||
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
|
||||
});
|
||||
|
||||
for (let prop of SUPPORTED_FORMAT_PROPERTIES) {
|
||||
@ -358,7 +359,7 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null if the package.json is not valid JSON', () => {
|
||||
it('should return `INVALID_ENTRY_POINT` if the package.json is not valid JSON', () => {
|
||||
loadTestFiles([
|
||||
// package.json might not be a valid JSON
|
||||
// for example, @schematics/angular contains a package.json blueprint
|
||||
@ -372,7 +373,7 @@ runInEachFileSystem(() => {
|
||||
const entryPoint = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/unexpected_symbols'));
|
||||
expect(entryPoint).toBe(null);
|
||||
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
|
||||
});
|
||||
});
|
||||
|
||||
@ -391,9 +392,13 @@ runInEachFileSystem(() => {
|
||||
contents: createPackageJson('valid_entry_point')
|
||||
}]);
|
||||
const config = new NgccConfiguration(fs, _('/project'));
|
||||
entryPoint = getEntryPointInfo(
|
||||
const result = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), SOME_PACKAGE,
|
||||
_('/project/node_modules/some_package/valid_entry_point')) !;
|
||||
_('/project/node_modules/some_package/valid_entry_point'));
|
||||
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||
return fail(`Expected an entry point but got ${result}`);
|
||||
}
|
||||
entryPoint = result;
|
||||
});
|
||||
|
||||
it('should return `esm2015` format for `fesm2015` property',
|
||||
|
@ -9,7 +9,7 @@ import {FileSystem, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_s
|
||||
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
||||
import {loadTestFiles} from '../../../test/helpers';
|
||||
import {NgccConfiguration} from '../../src/packages/configuration';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point';
|
||||
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../../src/packages/entry_point';
|
||||
import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||
import {FileWriter} from '../../src/writing/file_writer';
|
||||
import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer';
|
||||
@ -103,8 +103,12 @@ runInEachFileSystem(() => {
|
||||
fs = getFileSystem();
|
||||
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
||||
const config = new NgccConfiguration(fs, _('/'));
|
||||
entryPoint = getEntryPointInfo(
|
||||
const result = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
|
||||
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||
return fail(`Expected an entry point but got ${result}`);
|
||||
}
|
||||
entryPoint = result;
|
||||
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
||||
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
||||
});
|
||||
@ -239,8 +243,12 @@ runInEachFileSystem(() => {
|
||||
fs = getFileSystem();
|
||||
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
||||
const config = new NgccConfiguration(fs, _('/'));
|
||||
entryPoint = getEntryPointInfo(
|
||||
const result = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
|
||||
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||
return fail(`Expected an entry point but got ${result}`);
|
||||
}
|
||||
entryPoint = result;
|
||||
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
||||
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
||||
});
|
||||
@ -364,8 +372,12 @@ runInEachFileSystem(() => {
|
||||
fs = getFileSystem();
|
||||
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
|
||||
const config = new NgccConfiguration(fs, _('/'));
|
||||
entryPoint = getEntryPointInfo(
|
||||
const result = getEntryPointInfo(
|
||||
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;
|
||||
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
|
||||
return fail(`Expected an entry point but got ${result}`);
|
||||
}
|
||||
entryPoint = result;
|
||||
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
|
||||
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ ts_library(
|
||||
|
||||
ts_library(
|
||||
name = "api",
|
||||
srcs = ["api.ts"],
|
||||
srcs = glob(["api/**/*.ts"]),
|
||||
deps = [
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
11
packages/compiler-cli/src/ngtsc/core/api/index.ts
Normal file
11
packages/compiler-cli/src/ngtsc/core/api/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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/interfaces';
|
||||
export * from './src/options';
|
||||
export * from './src/public_options';
|
65
packages/compiler-cli/src/ngtsc/core/api/src/interfaces.ts
Normal file
65
packages/compiler-cli/src/ngtsc/core/api/src/interfaces.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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';
|
||||
|
||||
/**
|
||||
* A host backed by a build system which has a unified view of the module namespace.
|
||||
*
|
||||
* Such a build system supports the `fileNameToModuleName` method provided by certain build system
|
||||
* integrations (such as the integration with Bazel). See the docs on `fileNameToModuleName` for
|
||||
* more details.
|
||||
*/
|
||||
export interface UnifiedModulesHost {
|
||||
/**
|
||||
* Converts a file path to a module name that can be used as an `import ...`.
|
||||
*
|
||||
* For example, such a host might determine that `/absolute/path/to/monorepo/lib/importedFile.ts`
|
||||
* should be imported using a module specifier of `monorepo/lib/importedFile`.
|
||||
*/
|
||||
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A host which additionally tracks and produces "resources" (HTML templates, CSS
|
||||
* files, etc).
|
||||
*/
|
||||
export interface ResourceHost {
|
||||
/**
|
||||
* Converts a file path for a resource that is used in a source file or another resource
|
||||
* into a filepath.
|
||||
*/
|
||||
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
|
||||
|
||||
/**
|
||||
* Load a referenced resource either statically or asynchronously. If the host returns a
|
||||
* `Promise<string>` it is assumed the user of the corresponding `Program` will call
|
||||
* `loadNgStructureAsync()`. Returning `Promise<string>` outside `loadNgStructureAsync()` will
|
||||
* cause a diagnostics diagnostic error or an exception to be thrown.
|
||||
*/
|
||||
readResource(fileName: string): Promise<string>|string;
|
||||
|
||||
/**
|
||||
* Get the absolute paths to the changed files that triggered the current compilation
|
||||
* or `undefined` if this is not an incremental build.
|
||||
*/
|
||||
getModifiedResourceFiles?(): Set<string>|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ts.CompilerHost` interface which supports some number of optional methods in addition to the
|
||||
* core interface.
|
||||
*/
|
||||
export interface ExtendedTsCompilerHost extends ts.CompilerHost, Partial<ResourceHost>,
|
||||
Partial<UnifiedModulesHost> {}
|
||||
|
||||
export interface LazyRoute {
|
||||
route: string;
|
||||
module: {name: string, filePath: string};
|
||||
referencedModule: {name: string, filePath: string};
|
||||
}
|
57
packages/compiler-cli/src/ngtsc/core/api/src/options.ts
Normal file
57
packages/compiler-cli/src/ngtsc/core/api/src/options.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. 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 {BazelAndG3Options, I18nOptions, LegacyNgcOptions, MiscOptions, NgcCompatibilityOptions, StrictTemplateOptions} from './public_options';
|
||||
|
||||
|
||||
/**
|
||||
* Non-public options which are useful during testing of the compiler.
|
||||
*/
|
||||
export interface TestOnlyOptions {
|
||||
/**
|
||||
* Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
|
||||
* import module specifiers. This is false by default, and exists to support running ngtsc
|
||||
* within Google. This option is internal and is used by the ng_module.bzl rule to switch
|
||||
* behavior between Bazel and Blaze.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_useHostForImportGeneration?: boolean;
|
||||
|
||||
/**
|
||||
* Turn on template type-checking in the Ivy compiler.
|
||||
*
|
||||
* This is an internal flag being used to roll out template type-checking in ngtsc. Turning it on
|
||||
* by default before it's ready might break other users attempting to test the new compiler's
|
||||
* behavior.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
ivyTemplateTypeCheck?: boolean;
|
||||
|
||||
/** An option to enable ngtsc's internal performance tracing.
|
||||
*
|
||||
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
|
||||
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
|
||||
* (not all hosts support this mode of operation).
|
||||
*
|
||||
* This is currently not exposed to users as the trace format is still unstable.
|
||||
*/
|
||||
tracePerformance?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A merged interface of all of the various Angular compiler options, as well as the standard
|
||||
* `ts.CompilerOptions`.
|
||||
*
|
||||
* Also includes a few miscellaneous options.
|
||||
*/
|
||||
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
|
||||
NgcCompatibilityOptions, StrictTemplateOptions, TestOnlyOptions, I18nOptions, MiscOptions {}
|
@ -6,63 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
* A host backed by a build system which has a unified view of the module namespace.
|
||||
*
|
||||
* Such a build system supports the `fileNameToModuleName` method provided by certain build system
|
||||
* integrations (such as the integration with Bazel). See the docs on `fileNameToModuleName` for
|
||||
* more details.
|
||||
*/
|
||||
export interface UnifiedModulesHost {
|
||||
/**
|
||||
* Converts a file path to a module name that can be used as an `import ...`.
|
||||
*
|
||||
* For example, such a host might determine that `/absolute/path/to/monorepo/lib/importedFile.ts`
|
||||
* should be imported using a module specifier of `monorepo/lib/importedFile`.
|
||||
*/
|
||||
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A host which additionally tracks and produces "resources" (HTML templates, CSS
|
||||
* files, etc).
|
||||
*/
|
||||
export interface ResourceHost {
|
||||
/**
|
||||
* Converts a file path for a resource that is used in a source file or another resource
|
||||
* into a filepath.
|
||||
*/
|
||||
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
|
||||
|
||||
/**
|
||||
* Load a referenced resource either statically or asynchronously. If the host returns a
|
||||
* `Promise<string>` it is assumed the user of the corresponding `Program` will call
|
||||
* `loadNgStructureAsync()`. Returning `Promise<string>` outside `loadNgStructureAsync()` will
|
||||
* cause a diagnostics diagnostic error or an exception to be thrown.
|
||||
*/
|
||||
readResource(fileName: string): Promise<string>|string;
|
||||
|
||||
/**
|
||||
* Get the absolute paths to the changed files that triggered the current compilation
|
||||
* or `undefined` if this is not an incremental build.
|
||||
*/
|
||||
getModifiedResourceFiles?(): Set<string>|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ts.CompilerHost` interface which supports some number of optional methods in addition to the
|
||||
* core interface.
|
||||
*/
|
||||
export interface ExtendedTsCompilerHost extends ts.CompilerHost, Partial<ResourceHost>,
|
||||
Partial<UnifiedModulesHost> {}
|
||||
|
||||
/**
|
||||
* Options supported by the legacy View Engine compiler, which are still consumed by the Angular Ivy
|
||||
* compiler for backwards compatibility.
|
||||
*
|
||||
* These are expected to be removed at some point in the future.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface LegacyNgcOptions {
|
||||
/** generate all possible generated files */
|
||||
@ -134,6 +84,8 @@ export interface LegacyNgcOptions {
|
||||
* existing View Engine applications.
|
||||
*
|
||||
* These are expected to be removed at some point in the future.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface NgcCompatibilityOptions {
|
||||
/**
|
||||
@ -168,6 +120,8 @@ export interface NgcCompatibilityOptions {
|
||||
|
||||
/**
|
||||
* Options related to template type-checking and its strictness.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface StrictTemplateOptions {
|
||||
/**
|
||||
@ -291,6 +245,8 @@ export interface StrictTemplateOptions {
|
||||
/**
|
||||
* Options which control behavior useful for "monorepo" build cases using Bazel (such as the
|
||||
* internal Google monorepo, g3).
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface BazelAndG3Options {
|
||||
/**
|
||||
@ -332,6 +288,8 @@ export interface BazelAndG3Options {
|
||||
|
||||
/**
|
||||
* Options related to i18n compilation support.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface I18nOptions {
|
||||
/**
|
||||
@ -358,69 +316,19 @@ export interface I18nOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-public options which are useful during testing of the compiler.
|
||||
*/
|
||||
export interface TestOnlyOptions {
|
||||
/**
|
||||
* Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
|
||||
* import module specifiers. This is false by default, and exists to support running ngtsc
|
||||
* within Google. This option is internal and is used by the ng_module.bzl rule to switch
|
||||
* behavior between Bazel and Blaze.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_useHostForImportGeneration?: boolean;
|
||||
|
||||
/**
|
||||
* Turn on template type-checking in the Ivy compiler.
|
||||
*
|
||||
* This is an internal flag being used to roll out template type-checking in ngtsc. Turning it on
|
||||
* by default before it's ready might break other users attempting to test the new compiler's
|
||||
* behavior.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
ivyTemplateTypeCheck?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A merged interface of all of the various Angular compiler options, as well as the standard
|
||||
* `ts.CompilerOptions`.
|
||||
* Miscellaneous options that don't fall into any other category
|
||||
*
|
||||
* Also includes a few miscellaneous options.
|
||||
* @publicApi
|
||||
*/
|
||||
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
|
||||
NgcCompatibilityOptions, StrictTemplateOptions, TestOnlyOptions, I18nOptions {
|
||||
export interface MiscOptions {
|
||||
/**
|
||||
* Whether the compiler should avoid generating code for classes that haven't been exported.
|
||||
* This is only active when building with `enableIvy: true`. Defaults to `true`.
|
||||
*/
|
||||
compileNonExportedClasses?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to remove blank text nodes from compiled templates. It is `false` by default starting
|
||||
* from Angular 6.
|
||||
*/
|
||||
preserveWhitespaces?: boolean;
|
||||
|
||||
/**
|
||||
* Disable TypeScript Version Check.
|
||||
*/
|
||||
disableTypeScriptVersionCheck?: boolean;
|
||||
|
||||
/** An option to enable ngtsc's internal performance tracing.
|
||||
*
|
||||
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
|
||||
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
|
||||
* (not all hosts support this mode of operation).
|
||||
*
|
||||
* This is currently not exposed to users as the trace format is still unstable.
|
||||
*/
|
||||
tracePerformance?: string;
|
||||
}
|
||||
|
||||
export interface LazyRoute {
|
||||
route: string;
|
||||
module: {name: string, filePath: string};
|
||||
referencedModule: {name: string, filePath: string};
|
||||
}
|
||||
}
|
@ -296,9 +296,10 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
|
||||
const exportPipes = new Map<ts.Declaration, PipeMeta>();
|
||||
|
||||
// The algorithm is as follows:
|
||||
// 1) Add directives/pipes declared in the NgModule to the compilation scope.
|
||||
// 2) Add all of the directives/pipes from each NgModule imported into the current one to the
|
||||
// compilation scope. At this point, the compilation scope is complete.
|
||||
// 1) Add all of the directives/pipes from each NgModule imported into the current one to the
|
||||
// compilation scope.
|
||||
// 2) Add directives/pipes declared in the NgModule to the compilation scope. At this point, the
|
||||
// compilation scope is complete.
|
||||
// 3) For each entry in the NgModule's exports:
|
||||
// a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
|
||||
// one, add them to the export scope of this NgModule.
|
||||
@ -307,31 +308,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
|
||||
// c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
|
||||
// is an error.
|
||||
|
||||
// 1) add declarations.
|
||||
for (const decl of ngModule.declarations) {
|
||||
const directive = this.localReader.getDirectiveMetadata(decl);
|
||||
const pipe = this.localReader.getPipeMetadata(decl);
|
||||
if (directive !== null) {
|
||||
compilationDirectives.set(decl.node, {...directive, ref: decl});
|
||||
} else if (pipe !== null) {
|
||||
compilationPipes.set(decl.node, {...pipe, ref: decl});
|
||||
} else {
|
||||
this.taintedModules.add(ngModule.ref.node);
|
||||
|
||||
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations !);
|
||||
diagnostics.push(makeDiagnostic(
|
||||
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
|
||||
`The class '${decl.node.name.text}' is listed in the declarations of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe.
|
||||
|
||||
Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
|
||||
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}]));
|
||||
continue;
|
||||
}
|
||||
|
||||
declared.add(decl.node);
|
||||
}
|
||||
|
||||
// 2) process imports.
|
||||
// 1) process imports.
|
||||
for (const decl of ngModule.imports) {
|
||||
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
|
||||
if (importScope === null) {
|
||||
@ -353,6 +330,30 @@ Either remove it from the NgModule's declarations, or add an appropriate Angular
|
||||
}
|
||||
}
|
||||
|
||||
// 2) add declarations.
|
||||
for (const decl of ngModule.declarations) {
|
||||
const directive = this.localReader.getDirectiveMetadata(decl);
|
||||
const pipe = this.localReader.getPipeMetadata(decl);
|
||||
if (directive !== null) {
|
||||
compilationDirectives.set(decl.node, {...directive, ref: decl});
|
||||
} else if (pipe !== null) {
|
||||
compilationPipes.set(decl.node, {...pipe, ref: decl});
|
||||
} else {
|
||||
this.taintedModules.add(ngModule.ref.node);
|
||||
|
||||
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations !);
|
||||
diagnostics.push(makeDiagnostic(
|
||||
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
|
||||
`The class '${decl.node.name.text}' is listed in the declarations ` +
|
||||
`of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe. ` +
|
||||
`Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
|
||||
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}]));
|
||||
continue;
|
||||
}
|
||||
|
||||
declared.add(decl.node);
|
||||
}
|
||||
|
||||
// 3) process exports.
|
||||
// Exports can contain modules, components, or directives. They're processed differently.
|
||||
// Modules are straightforward. Directives and pipes from exported modules are added to the
|
||||
|
@ -2542,7 +2542,7 @@ describe('compiler compliance', () => {
|
||||
type: LifecycleComp,
|
||||
selectors: [["lifecycle-comp"]],
|
||||
inputs: {nameMin: ["name", "nameMin"]},
|
||||
features: [$r3$.ɵɵNgOnChangesFeature()],
|
||||
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: function LifecycleComp_Template(rf, ctx) {},
|
||||
@ -2662,7 +2662,7 @@ describe('compiler compliance', () => {
|
||||
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
|
||||
type: ForOfDirective,
|
||||
selectors: [["", "forOf", ""]],
|
||||
features: [$r3$.ɵɵNgOnChangesFeature()],
|
||||
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||
inputs: {forOf: "forOf"}
|
||||
});
|
||||
`;
|
||||
@ -2742,7 +2742,7 @@ describe('compiler compliance', () => {
|
||||
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
|
||||
type: ForOfDirective,
|
||||
selectors: [["", "forOf", ""]],
|
||||
features: [$r3$.ɵɵNgOnChangesFeature()],
|
||||
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||
inputs: {forOf: "forOf"}
|
||||
});
|
||||
`;
|
||||
@ -3767,7 +3767,7 @@ describe('compiler compliance', () => {
|
||||
// ...
|
||||
BaseClass.ɵdir = $r3$.ɵɵdefineDirective({
|
||||
type: BaseClass,
|
||||
features: [$r3$.ɵɵNgOnChangesFeature()]
|
||||
features: [$r3$.ɵɵNgOnChangesFeature]
|
||||
});
|
||||
// ...
|
||||
`;
|
||||
|
@ -598,6 +598,213 @@ runInEachFileSystem(os => {
|
||||
expect(jsContents).toContain('outputs: { output: "output" }');
|
||||
});
|
||||
|
||||
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
|
||||
env.tsconfig({});
|
||||
env.write('test.ts', `
|
||||
import {Component, Pipe, NgModule} from '@angular/core';
|
||||
|
||||
// ModuleA classes
|
||||
|
||||
@Pipe({name: 'number'})
|
||||
class PipeA {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [PipeA],
|
||||
exports: [PipeA]
|
||||
})
|
||||
class ModuleA {}
|
||||
|
||||
// ModuleB classes
|
||||
|
||||
@Pipe({name: 'number'})
|
||||
class PipeB {}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '{{ count | number }}'
|
||||
})
|
||||
export class App {}
|
||||
|
||||
@NgModule({
|
||||
imports: [ModuleA],
|
||||
declarations: [PipeB, App],
|
||||
})
|
||||
class ModuleB {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = trim(env.getContents('test.js'));
|
||||
expect(jsContents).toContain('pipes: [PipeB]');
|
||||
});
|
||||
|
||||
it('should respect imported module order when selecting Pipe (last imported Pipe is used)',
|
||||
() => {
|
||||
env.tsconfig({});
|
||||
env.write('test.ts', `
|
||||
import {Component, Pipe, NgModule} from '@angular/core';
|
||||
|
||||
// ModuleA classes
|
||||
|
||||
@Pipe({name: 'number'})
|
||||
class PipeA {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [PipeA],
|
||||
exports: [PipeA]
|
||||
})
|
||||
class ModuleA {}
|
||||
|
||||
// ModuleB classes
|
||||
|
||||
@Pipe({name: 'number'})
|
||||
class PipeB {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [PipeB],
|
||||
exports: [PipeB]
|
||||
})
|
||||
class ModuleB {}
|
||||
|
||||
// ModuleC classes
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '{{ count | number }}'
|
||||
})
|
||||
export class App {}
|
||||
|
||||
@NgModule({
|
||||
imports: [ModuleA, ModuleB],
|
||||
declarations: [App],
|
||||
})
|
||||
class ModuleC {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = trim(env.getContents('test.js'));
|
||||
expect(jsContents).toContain('pipes: [PipeB]');
|
||||
});
|
||||
|
||||
it('should add Directives and Components from `declarations` at the end of the list', () => {
|
||||
env.tsconfig({});
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
|
||||
// ModuleA classes
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveA {}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '...'
|
||||
})
|
||||
class ComponentA {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DirectiveA, ComponentA],
|
||||
exports: [DirectiveA, ComponentA]
|
||||
})
|
||||
class ModuleA {}
|
||||
|
||||
// ModuleB classes
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveB {}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '...',
|
||||
})
|
||||
export class ComponentB {}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: \`
|
||||
<div dir></div>
|
||||
<comp></comp>
|
||||
\`,
|
||||
})
|
||||
export class App {}
|
||||
|
||||
@NgModule({
|
||||
imports: [ModuleA],
|
||||
declarations: [DirectiveB, ComponentB, App],
|
||||
})
|
||||
class ModuleB {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = trim(env.getContents('test.js'));
|
||||
expect(jsContents).toContain('directives: [DirectiveA, DirectiveB, ComponentA, ComponentB]');
|
||||
});
|
||||
|
||||
it('should respect imported module order while processing Directives and Components', () => {
|
||||
env.tsconfig({});
|
||||
env.write('test.ts', `
|
||||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
|
||||
// ModuleA classes
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveA {}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '...'
|
||||
})
|
||||
class ComponentA {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DirectiveA, ComponentA],
|
||||
exports: [DirectiveA, ComponentA]
|
||||
})
|
||||
class ModuleA {}
|
||||
|
||||
// ModuleB classes
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveB {}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '...'
|
||||
})
|
||||
class ComponentB {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DirectiveB, ComponentB],
|
||||
exports: [DirectiveB, ComponentB]
|
||||
})
|
||||
class ModuleB {}
|
||||
|
||||
// ModuleC classes
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: \`
|
||||
<div dir></div>
|
||||
<comp></comp>
|
||||
\`,
|
||||
})
|
||||
export class App {}
|
||||
|
||||
@NgModule({
|
||||
imports: [ModuleA, ModuleB],
|
||||
declarations: [App],
|
||||
})
|
||||
class ModuleC {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = trim(env.getContents('test.js'));
|
||||
expect(jsContents).toContain('directives: [DirectiveA, DirectiveB, ComponentA, ComponentB]');
|
||||
});
|
||||
|
||||
it('should compile Components with a templateUrl in a different rootDir', () => {
|
||||
env.tsconfig({}, ['./extraRootDir']);
|
||||
env.write('extraRootDir/test.html', '<p>Hello World</p>');
|
||||
|
@ -286,7 +286,7 @@ export class ASTWithSource extends AST {
|
||||
export class TemplateBinding {
|
||||
constructor(
|
||||
public span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public key: string,
|
||||
public keyIsVar: boolean, public name: string, public expression: ASTWithSource|null) {}
|
||||
public keyIsVar: boolean, public name: string, public value: ASTWithSource|null) {}
|
||||
}
|
||||
|
||||
export interface AstVisitor {
|
||||
|
@ -118,12 +118,38 @@ export class Parser {
|
||||
span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location);
|
||||
}
|
||||
|
||||
parseTemplateBindings(tplKey: string, tplValue: string, location: any, absoluteOffset: number):
|
||||
TemplateBindingParseResult {
|
||||
const tokens = this._lexer.tokenize(tplValue);
|
||||
/**
|
||||
* Parse microsyntax template expression and return a list of bindings or
|
||||
* parsing errors in case the given expression is invalid.
|
||||
*
|
||||
* For example,
|
||||
* ```
|
||||
* <div *ngFor="let item of items">
|
||||
* ^ `absoluteOffset` for `tplValue`
|
||||
* ```
|
||||
* contains three bindings:
|
||||
* 1. ngFor -> null
|
||||
* 2. item -> NgForOfContext.$implicit
|
||||
* 3. ngForOf -> items
|
||||
*
|
||||
* This is apparent from the de-sugared template:
|
||||
* ```
|
||||
* <ng-template ngFor let-item [ngForOf]="items">
|
||||
* ```
|
||||
*
|
||||
* @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
|
||||
* @param templateValue RHS of the microsyntax attribute
|
||||
* @param templateUrl template filename if it's external, component filename if it's inline
|
||||
* @param absoluteOffset absolute offset of the `tplValue`
|
||||
*/
|
||||
parseTemplateBindings(
|
||||
templateKey: string, templateValue: string, templateUrl: string,
|
||||
absoluteOffset: number): TemplateBindingParseResult {
|
||||
const tokens = this._lexer.tokenize(templateValue);
|
||||
return new _ParseAST(
|
||||
tplValue, location, absoluteOffset, tokens, tplValue.length, false, this.errors, 0)
|
||||
.parseTemplateBindings(tplKey);
|
||||
templateValue, templateUrl, absoluteOffset, tokens, templateValue.length,
|
||||
false /* parseAction */, this.errors, 0 /* relative offset */)
|
||||
.parseTemplateBindings(templateKey);
|
||||
}
|
||||
|
||||
parseInterpolation(
|
||||
@ -288,7 +314,7 @@ export class _ParseAST {
|
||||
|
||||
advance() { this.index++; }
|
||||
|
||||
optionalCharacter(code: number): boolean {
|
||||
consumeOptionalCharacter(code: number): boolean {
|
||||
if (this.next.isCharacter(code)) {
|
||||
this.advance();
|
||||
return true;
|
||||
@ -301,11 +327,11 @@ export class _ParseAST {
|
||||
peekKeywordAs(): boolean { return this.next.isKeywordAs(); }
|
||||
|
||||
expectCharacter(code: number) {
|
||||
if (this.optionalCharacter(code)) return;
|
||||
if (this.consumeOptionalCharacter(code)) return;
|
||||
this.error(`Missing expected ${String.fromCharCode(code)}`);
|
||||
}
|
||||
|
||||
optionalOperator(op: string): boolean {
|
||||
consumeOptionalOperator(op: string): boolean {
|
||||
if (this.next.isOperator(op)) {
|
||||
this.advance();
|
||||
return true;
|
||||
@ -315,7 +341,7 @@ export class _ParseAST {
|
||||
}
|
||||
|
||||
expectOperator(operator: string) {
|
||||
if (this.optionalOperator(operator)) return;
|
||||
if (this.consumeOptionalOperator(operator)) return;
|
||||
this.error(`Missing expected operator ${operator}`);
|
||||
}
|
||||
|
||||
@ -346,11 +372,11 @@ export class _ParseAST {
|
||||
const expr = this.parsePipe();
|
||||
exprs.push(expr);
|
||||
|
||||
if (this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
if (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
|
||||
if (!this.parseAction) {
|
||||
this.error('Binding expression cannot contain chained expression');
|
||||
}
|
||||
while (this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
while (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
|
||||
} // read all semicolons
|
||||
} else if (this.index < this.tokens.length) {
|
||||
this.error(`Unexpected token '${this.next}'`);
|
||||
@ -363,7 +389,7 @@ export class _ParseAST {
|
||||
|
||||
parsePipe(): AST {
|
||||
let result = this.parseExpression();
|
||||
if (this.optionalOperator('|')) {
|
||||
if (this.consumeOptionalOperator('|')) {
|
||||
if (this.parseAction) {
|
||||
this.error('Cannot have a pipe in an action expression');
|
||||
}
|
||||
@ -373,13 +399,13 @@ export class _ParseAST {
|
||||
const name = this.expectIdentifierOrKeyword();
|
||||
const nameSpan = this.sourceSpan(nameStart);
|
||||
const args: AST[] = [];
|
||||
while (this.optionalCharacter(chars.$COLON)) {
|
||||
while (this.consumeOptionalCharacter(chars.$COLON)) {
|
||||
args.push(this.parseExpression());
|
||||
}
|
||||
const {start} = result.span;
|
||||
result =
|
||||
new BindingPipe(this.span(start), this.sourceSpan(start), result, name, args, nameSpan);
|
||||
} while (this.optionalOperator('|'));
|
||||
} while (this.consumeOptionalOperator('|'));
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -391,10 +417,10 @@ export class _ParseAST {
|
||||
const start = this.inputIndex;
|
||||
const result = this.parseLogicalOr();
|
||||
|
||||
if (this.optionalOperator('?')) {
|
||||
if (this.consumeOptionalOperator('?')) {
|
||||
const yes = this.parsePipe();
|
||||
let no: AST;
|
||||
if (!this.optionalCharacter(chars.$COLON)) {
|
||||
if (!this.consumeOptionalCharacter(chars.$COLON)) {
|
||||
const end = this.inputIndex;
|
||||
const expression = this.input.substring(start, end);
|
||||
this.error(`Conditional expression ${expression} requires all 3 expressions`);
|
||||
@ -411,7 +437,7 @@ export class _ParseAST {
|
||||
parseLogicalOr(): AST {
|
||||
// '||'
|
||||
let result = this.parseLogicalAnd();
|
||||
while (this.optionalOperator('||')) {
|
||||
while (this.consumeOptionalOperator('||')) {
|
||||
const right = this.parseLogicalAnd();
|
||||
const {start} = result.span;
|
||||
result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
|
||||
@ -422,7 +448,7 @@ export class _ParseAST {
|
||||
parseLogicalAnd(): AST {
|
||||
// '&&'
|
||||
let result = this.parseEquality();
|
||||
while (this.optionalOperator('&&')) {
|
||||
while (this.consumeOptionalOperator('&&')) {
|
||||
const right = this.parseEquality();
|
||||
const {start} = result.span;
|
||||
result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
|
||||
@ -544,18 +570,18 @@ export class _ParseAST {
|
||||
let result = this.parsePrimary();
|
||||
const resultStart = result.span.start;
|
||||
while (true) {
|
||||
if (this.optionalCharacter(chars.$PERIOD)) {
|
||||
if (this.consumeOptionalCharacter(chars.$PERIOD)) {
|
||||
result = this.parseAccessMemberOrMethodCall(result, false);
|
||||
|
||||
} else if (this.optionalOperator('?.')) {
|
||||
} else if (this.consumeOptionalOperator('?.')) {
|
||||
result = this.parseAccessMemberOrMethodCall(result, true);
|
||||
|
||||
} else if (this.optionalCharacter(chars.$LBRACKET)) {
|
||||
} else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
|
||||
this.rbracketsExpected++;
|
||||
const key = this.parsePipe();
|
||||
this.rbracketsExpected--;
|
||||
this.expectCharacter(chars.$RBRACKET);
|
||||
if (this.optionalOperator('=')) {
|
||||
if (this.consumeOptionalOperator('=')) {
|
||||
const value = this.parseConditional();
|
||||
result = new KeyedWrite(
|
||||
this.span(resultStart), this.sourceSpan(resultStart), result, key, value);
|
||||
@ -563,7 +589,7 @@ export class _ParseAST {
|
||||
result = new KeyedRead(this.span(resultStart), this.sourceSpan(resultStart), result, key);
|
||||
}
|
||||
|
||||
} else if (this.optionalCharacter(chars.$LPAREN)) {
|
||||
} else if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||
this.rparensExpected++;
|
||||
const args = this.parseCallArguments();
|
||||
this.rparensExpected--;
|
||||
@ -571,7 +597,7 @@ export class _ParseAST {
|
||||
result =
|
||||
new FunctionCall(this.span(resultStart), this.sourceSpan(resultStart), result, args);
|
||||
|
||||
} else if (this.optionalOperator('!')) {
|
||||
} else if (this.consumeOptionalOperator('!')) {
|
||||
result = new NonNullAssert(this.span(resultStart), this.sourceSpan(resultStart), result);
|
||||
|
||||
} else {
|
||||
@ -582,7 +608,7 @@ export class _ParseAST {
|
||||
|
||||
parsePrimary(): AST {
|
||||
const start = this.inputIndex;
|
||||
if (this.optionalCharacter(chars.$LPAREN)) {
|
||||
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||
this.rparensExpected++;
|
||||
const result = this.parsePipe();
|
||||
this.rparensExpected--;
|
||||
@ -609,7 +635,7 @@ export class _ParseAST {
|
||||
this.advance();
|
||||
return new ImplicitReceiver(this.span(start), this.sourceSpan(start));
|
||||
|
||||
} else if (this.optionalCharacter(chars.$LBRACKET)) {
|
||||
} else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
|
||||
this.rbracketsExpected++;
|
||||
const elements = this.parseExpressionList(chars.$RBRACKET);
|
||||
this.rbracketsExpected--;
|
||||
@ -647,7 +673,7 @@ export class _ParseAST {
|
||||
if (!this.next.isCharacter(terminator)) {
|
||||
do {
|
||||
result.push(this.parsePipe());
|
||||
} while (this.optionalCharacter(chars.$COMMA));
|
||||
} while (this.consumeOptionalCharacter(chars.$COMMA));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -657,7 +683,7 @@ export class _ParseAST {
|
||||
const values: AST[] = [];
|
||||
const start = this.inputIndex;
|
||||
this.expectCharacter(chars.$LBRACE);
|
||||
if (!this.optionalCharacter(chars.$RBRACE)) {
|
||||
if (!this.consumeOptionalCharacter(chars.$RBRACE)) {
|
||||
this.rbracesExpected++;
|
||||
do {
|
||||
const quoted = this.next.isString();
|
||||
@ -665,7 +691,7 @@ export class _ParseAST {
|
||||
keys.push({key, quoted});
|
||||
this.expectCharacter(chars.$COLON);
|
||||
values.push(this.parsePipe());
|
||||
} while (this.optionalCharacter(chars.$COMMA));
|
||||
} while (this.consumeOptionalCharacter(chars.$COMMA));
|
||||
this.rbracesExpected--;
|
||||
this.expectCharacter(chars.$RBRACE);
|
||||
}
|
||||
@ -676,7 +702,7 @@ export class _ParseAST {
|
||||
const start = receiver.span.start;
|
||||
const id = this.expectIdentifierOrKeyword();
|
||||
|
||||
if (this.optionalCharacter(chars.$LPAREN)) {
|
||||
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||
this.rparensExpected++;
|
||||
const args = this.parseCallArguments();
|
||||
this.expectCharacter(chars.$RPAREN);
|
||||
@ -688,14 +714,14 @@ export class _ParseAST {
|
||||
|
||||
} else {
|
||||
if (isSafe) {
|
||||
if (this.optionalOperator('=')) {
|
||||
if (this.consumeOptionalOperator('=')) {
|
||||
this.error('The \'?.\' operator cannot be used in the assignment');
|
||||
return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
||||
} else {
|
||||
return new SafePropertyRead(this.span(start), this.sourceSpan(start), receiver, id);
|
||||
}
|
||||
} else {
|
||||
if (this.optionalOperator('=')) {
|
||||
if (this.consumeOptionalOperator('=')) {
|
||||
if (!this.parseAction) {
|
||||
this.error('Bindings cannot contain assignments');
|
||||
return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
||||
@ -716,84 +742,208 @@ export class _ParseAST {
|
||||
const positionals: AST[] = [];
|
||||
do {
|
||||
positionals.push(this.parsePipe());
|
||||
} while (this.optionalCharacter(chars.$COMMA));
|
||||
} while (this.consumeOptionalCharacter(chars.$COMMA));
|
||||
return positionals as BindingPipe[];
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier, a keyword, a string with an optional `-` in between.
|
||||
* Parses an identifier, a keyword, a string with an optional `-` in between.
|
||||
*/
|
||||
expectTemplateBindingKey(): string {
|
||||
expectTemplateBindingKey(): {key: string, keySpan: ParseSpan} {
|
||||
let result = '';
|
||||
let operatorFound = false;
|
||||
const start = this.inputIndex;
|
||||
do {
|
||||
result += this.expectIdentifierOrKeywordOrString();
|
||||
operatorFound = this.optionalOperator('-');
|
||||
operatorFound = this.consumeOptionalOperator('-');
|
||||
if (operatorFound) {
|
||||
result += '-';
|
||||
}
|
||||
} while (operatorFound);
|
||||
|
||||
return result.toString();
|
||||
return {
|
||||
key: result,
|
||||
keySpan: new ParseSpan(start, start + result.length),
|
||||
};
|
||||
}
|
||||
|
||||
// Parses the AST for `<some-tag *tplKey=AST>`
|
||||
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
|
||||
let firstBinding = true;
|
||||
/**
|
||||
* Parse microsyntax template expression and return a list of bindings or
|
||||
* parsing errors in case the given expression is invalid.
|
||||
*
|
||||
* For example,
|
||||
* ```
|
||||
* <div *ngFor="let item of items; index as i; trackBy: func">
|
||||
* ```
|
||||
* contains five bindings:
|
||||
* 1. ngFor -> null
|
||||
* 2. item -> NgForOfContext.$implicit
|
||||
* 3. ngForOf -> items
|
||||
* 4. i -> NgForOfContext.index
|
||||
* 5. ngForTrackBy -> func
|
||||
*
|
||||
* For a full description of the microsyntax grammar, see
|
||||
* https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
|
||||
*
|
||||
* @param templateKey name of the microsyntax directive, like ngIf, ngFor, without the *
|
||||
*/
|
||||
parseTemplateBindings(templateKey: string): TemplateBindingParseResult {
|
||||
const bindings: TemplateBinding[] = [];
|
||||
const warnings: string[] = [];
|
||||
do {
|
||||
const start = this.inputIndex;
|
||||
let rawKey: string;
|
||||
let key: string;
|
||||
let isVar: boolean = false;
|
||||
if (firstBinding) {
|
||||
rawKey = key = tplKey;
|
||||
firstBinding = false;
|
||||
|
||||
// The first binding is for the template key itself
|
||||
// In *ngFor="let item of items", key = "ngFor", value = null
|
||||
// In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
|
||||
bindings.push(...this.parseDirectiveKeywordBindings(
|
||||
templateKey, new ParseSpan(0, templateKey.length), this.absoluteOffset));
|
||||
|
||||
while (this.index < this.tokens.length) {
|
||||
// If it starts with 'let', then this must be variable declaration
|
||||
const letBinding = this.parseLetBinding();
|
||||
if (letBinding) {
|
||||
bindings.push(letBinding);
|
||||
} else {
|
||||
isVar = this.peekKeywordLet();
|
||||
if (isVar) this.advance();
|
||||
rawKey = this.expectTemplateBindingKey();
|
||||
key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
|
||||
this.optionalCharacter(chars.$COLON);
|
||||
}
|
||||
|
||||
let name: string = null !;
|
||||
let expression: ASTWithSource|null = null;
|
||||
if (isVar) {
|
||||
if (this.optionalOperator('=')) {
|
||||
name = this.expectTemplateBindingKey();
|
||||
// Two possible cases here, either `value "as" key` or
|
||||
// "directive-keyword expression". We don't know which case, but both
|
||||
// "value" and "directive-keyword" are template binding key, so consume
|
||||
// the key first.
|
||||
const {key, keySpan} = this.expectTemplateBindingKey();
|
||||
// Peek at the next token, if it is "as" then this must be variable
|
||||
// declaration.
|
||||
const binding = this.parseAsBinding(key, keySpan, this.absoluteOffset);
|
||||
if (binding) {
|
||||
bindings.push(binding);
|
||||
} else {
|
||||
name = '\$implicit';
|
||||
// Otherwise the key must be a directive keyword, like "of". Transform
|
||||
// the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
|
||||
const actualKey = templateKey + key[0].toUpperCase() + key.substring(1);
|
||||
bindings.push(
|
||||
...this.parseDirectiveKeywordBindings(actualKey, keySpan, this.absoluteOffset));
|
||||
}
|
||||
} else if (this.peekKeywordAs()) {
|
||||
this.advance(); // consume `as`
|
||||
name = rawKey;
|
||||
key = this.expectTemplateBindingKey(); // read local var name
|
||||
isVar = true;
|
||||
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
||||
const start = this.inputIndex;
|
||||
const ast = this.parsePipe();
|
||||
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
||||
expression =
|
||||
new ASTWithSource(ast, source, this.location, this.absoluteOffset + start, this.errors);
|
||||
}
|
||||
this.consumeStatementTerminator();
|
||||
}
|
||||
|
||||
bindings.push(new TemplateBinding(
|
||||
this.span(start), this.sourceSpan(start), key, isVar, name, expression));
|
||||
if (this.peekKeywordAs() && !isVar) {
|
||||
const letStart = this.inputIndex;
|
||||
this.advance(); // consume `as`
|
||||
const letName = this.expectTemplateBindingKey(); // read local var name
|
||||
bindings.push(new TemplateBinding(
|
||||
this.span(letStart), this.sourceSpan(letStart), letName, true, key, null !));
|
||||
}
|
||||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
this.optionalCharacter(chars.$COMMA);
|
||||
}
|
||||
} while (this.index < this.tokens.length);
|
||||
return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
|
||||
}
|
||||
|
||||
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
||||
/**
|
||||
* Parse a directive keyword, followed by a mandatory expression.
|
||||
* For example, "of items", "trackBy: func".
|
||||
* The bindings are: ngForOf -> items, ngForTrackBy -> func
|
||||
* There could be an optional "as" binding that follows the expression.
|
||||
* For example,
|
||||
* ```
|
||||
* *ngFor="let item of items | slice:0:1 as collection".`
|
||||
* ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
|
||||
* keyword bound target optional 'as' binding
|
||||
* ```
|
||||
*
|
||||
* @param key binding key, for example, ngFor, ngIf, ngForOf
|
||||
* @param keySpan span of the key in the expression. keySpan might be different
|
||||
* from `key.length`. For example, the span for key "ngForOf" is "of".
|
||||
* @param absoluteOffset absolute offset of the attribute value
|
||||
*/
|
||||
private parseDirectiveKeywordBindings(key: string, keySpan: ParseSpan, absoluteOffset: number):
|
||||
TemplateBinding[] {
|
||||
const bindings: TemplateBinding[] = [];
|
||||
this.consumeOptionalCharacter(chars.$COLON); // trackBy: trackByFunction
|
||||
const valueExpr = this.getDirectiveBoundTarget();
|
||||
const span = new ParseSpan(keySpan.start, this.inputIndex);
|
||||
bindings.push(new TemplateBinding(
|
||||
span, span.toAbsolute(absoluteOffset), key, false /* keyIsVar */, valueExpr?.source || '', valueExpr));
|
||||
// The binding could optionally be followed by "as". For example,
|
||||
// *ngIf="cond | pipe as x". In this case, the key in the "as" binding
|
||||
// is "x" and the value is the template key itself ("ngIf"). Note that the
|
||||
// 'key' in the current context now becomes the "value" in the next binding.
|
||||
const asBinding = this.parseAsBinding(key, keySpan, absoluteOffset);
|
||||
if (asBinding) {
|
||||
bindings.push(asBinding);
|
||||
}
|
||||
this.consumeStatementTerminator();
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the expression AST for the bound target of a directive keyword
|
||||
* binding. For example,
|
||||
* ```
|
||||
* *ngIf="condition | pipe".
|
||||
* ^^^^^^^^^^^^^^^^ bound target for "ngIf"
|
||||
* *ngFor="let item of items"
|
||||
* ^^^^^ bound target for "ngForOf"
|
||||
* ```
|
||||
*/
|
||||
private getDirectiveBoundTarget(): ASTWithSource|null {
|
||||
if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
|
||||
return null;
|
||||
}
|
||||
const ast = this.parsePipe(); // example: "condition | async"
|
||||
const {start, end} = ast.span;
|
||||
const value = this.input.substring(start, end);
|
||||
return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the binding for a variable declared using `as`. Note that the order
|
||||
* of the key-value pair in this declaration is reversed. For example,
|
||||
* ```
|
||||
* *ngFor="let item of items; index as i"
|
||||
* ^^^^^ ^
|
||||
* value key
|
||||
* ```
|
||||
*
|
||||
* @param value name of the value in the declaration, "ngIf" in the example above
|
||||
* @param valueSpan span of the value in the declaration
|
||||
* @param absoluteOffset absolute offset of `value`
|
||||
*/
|
||||
private parseAsBinding(value: string, valueSpan: ParseSpan, absoluteOffset: number):
|
||||
TemplateBinding|null {
|
||||
if (!this.peekKeywordAs()) {
|
||||
return null;
|
||||
}
|
||||
this.advance(); // consume the 'as' keyword
|
||||
const {key} = this.expectTemplateBindingKey();
|
||||
const valueAst = new AST(valueSpan, valueSpan.toAbsolute(absoluteOffset));
|
||||
const valueExpr = new ASTWithSource(
|
||||
valueAst, value, this.location, absoluteOffset + valueSpan.start, this.errors);
|
||||
const span = new ParseSpan(valueSpan.start, this.inputIndex);
|
||||
return new TemplateBinding(
|
||||
span, span.toAbsolute(absoluteOffset), key, true /* keyIsVar */, value, valueExpr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the binding for a variable declared using `let`. For example,
|
||||
* ```
|
||||
* *ngFor="let item of items; let i=index;"
|
||||
* ^^^^^^^^ ^^^^^^^^^^^
|
||||
* ```
|
||||
* In the first binding, `item` is bound to `NgForOfContext.$implicit`.
|
||||
* In the second binding, `i` is bound to `NgForOfContext.index`.
|
||||
*/
|
||||
private parseLetBinding(): TemplateBinding|null {
|
||||
if (!this.peekKeywordLet()) {
|
||||
return null;
|
||||
}
|
||||
const spanStart = this.inputIndex;
|
||||
this.advance(); // consume the 'let' keyword
|
||||
const {key} = this.expectTemplateBindingKey();
|
||||
let valueExpr: ASTWithSource|null = null;
|
||||
if (this.consumeOptionalOperator('=')) {
|
||||
const {key: value, keySpan: valueSpan} = this.expectTemplateBindingKey();
|
||||
const ast = new AST(valueSpan, valueSpan.toAbsolute(this.absoluteOffset));
|
||||
valueExpr = new ASTWithSource(
|
||||
ast, value, this.location, this.absoluteOffset + valueSpan.start, this.errors);
|
||||
}
|
||||
const spanEnd = this.inputIndex;
|
||||
const span = new ParseSpan(spanStart, spanEnd);
|
||||
return new TemplateBinding(
|
||||
span, span.toAbsolute(this.absoluteOffset), key, true /* keyIsVar */, valueExpr?.source || '$implicit', valueExpr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the optional statement terminator: semicolon or comma.
|
||||
*/
|
||||
private consumeStatementTerminator() {
|
||||
this.consumeOptionalCharacter(chars.$SEMICOLON) || this.consumeOptionalCharacter(chars.$COMMA);
|
||||
}
|
||||
|
||||
error(message: string, index: number|null = null) {
|
||||
@ -896,4 +1046,4 @@ class IvySimpleExpressionChecker extends SimpleExpressionChecker {
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot, context: any) { ast.expression.visit(this); }
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ function baseDirectiveFields(
|
||||
*/
|
||||
function addFeatures(
|
||||
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
|
||||
// e.g. `features: [NgOnChangesFeature()]`
|
||||
// e.g. `features: [NgOnChangesFeature]`
|
||||
const features: o.Expression[] = [];
|
||||
|
||||
const providers = meta.providers;
|
||||
@ -107,7 +107,7 @@ function addFeatures(
|
||||
features.push(o.importExpr(R3.CopyDefinitionFeature));
|
||||
}
|
||||
if (meta.lifecycle.usesOnChanges) {
|
||||
features.push(o.importExpr(R3.NgOnChangesFeature).callFn(EMPTY_ARRAY));
|
||||
features.push(o.importExpr(R3.NgOnChangesFeature));
|
||||
}
|
||||
if (features.length) {
|
||||
definitionMap.set('features', o.literalArr(features));
|
||||
|
@ -134,10 +134,9 @@ export class BindingParser {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
|
||||
} else if (binding.expression) {
|
||||
} else if (binding.value) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, sourceSpan, undefined, targetMatchableAttrs,
|
||||
targetProps);
|
||||
binding.key, binding.value, sourceSpan, undefined, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
targetMatchableAttrs.push([binding.key, '']);
|
||||
this.parseLiteralAttr(
|
||||
@ -165,8 +164,8 @@ export class BindingParser {
|
||||
this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteValueOffset);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (binding.expression) {
|
||||
this._checkPipes(binding.expression, sourceSpan);
|
||||
if (binding.value) {
|
||||
this._checkPipes(binding.value, sourceSpan);
|
||||
}
|
||||
});
|
||||
bindingsResult.warnings.forEach(
|
||||
|
@ -248,14 +248,16 @@ describe('parser', () => {
|
||||
|
||||
describe('parseTemplateBindings', () => {
|
||||
|
||||
function keys(templateBindings: any[]) { return templateBindings.map(binding => binding.key); }
|
||||
function keys(templateBindings: TemplateBinding[]) {
|
||||
return templateBindings.map(binding => binding.key);
|
||||
}
|
||||
|
||||
function keyValues(templateBindings: any[]) {
|
||||
function keyValues(templateBindings: TemplateBinding[]) {
|
||||
return templateBindings.map(binding => {
|
||||
if (binding.keyIsVar) {
|
||||
return 'let ' + binding.key + (binding.name == null ? '=null' : '=' + binding.name);
|
||||
} else {
|
||||
return binding.key + (binding.expression == null ? '' : `=${binding.expression}`);
|
||||
return binding.key + (binding.value == null ? '' : `=${binding.value}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -265,9 +267,16 @@ describe('parser', () => {
|
||||
binding => source.substring(binding.span.start, binding.span.end));
|
||||
}
|
||||
|
||||
function exprSources(templateBindings: any[]) {
|
||||
return templateBindings.map(
|
||||
binding => binding.expression != null ? binding.expression.source : null);
|
||||
function exprSources(templateBindings: TemplateBinding[]) {
|
||||
return templateBindings.map(binding => binding.value != null ? binding.value.source : null);
|
||||
}
|
||||
|
||||
function humanize(bindings: TemplateBinding[]): Array<[string, string | null, boolean]> {
|
||||
return bindings.map(binding => {
|
||||
const {key, value: expression, name, keyIsVar} = binding;
|
||||
const value = keyIsVar ? name : (expression ? expression.source : expression);
|
||||
return [key, value, keyIsVar];
|
||||
});
|
||||
}
|
||||
|
||||
it('should parse a key without a value',
|
||||
@ -308,13 +317,51 @@ describe('parser', () => {
|
||||
|
||||
it('should store the sources in the result', () => {
|
||||
const bindings = parseTemplateBindings('a', '1,b 2');
|
||||
expect(bindings[0].expression !.source).toEqual('1');
|
||||
expect(bindings[1].expression !.source).toEqual('2');
|
||||
expect(bindings[0].value !.source).toEqual('1');
|
||||
expect(bindings[1].value !.source).toEqual('2');
|
||||
});
|
||||
|
||||
it('should store the passed-in location', () => {
|
||||
const bindings = parseTemplateBindings('a', '1,b 2', 'location');
|
||||
expect(bindings[0].expression !.location).toEqual('location');
|
||||
expect(bindings[0].value !.location).toEqual('location');
|
||||
});
|
||||
|
||||
it('should support common usage of ngIf', () => {
|
||||
const bindings = parseTemplateBindings('ngIf', 'cond | pipe as foo, let x; ngIf as y');
|
||||
expect(humanize(bindings)).toEqual([
|
||||
// [ key, value, keyIsVar ]
|
||||
['ngIf', 'cond | pipe ', false],
|
||||
['foo', 'ngIf', true],
|
||||
['x', '$implicit', true],
|
||||
['y', 'ngIf', true],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support common usage of ngFor', () => {
|
||||
let bindings: TemplateBinding[];
|
||||
bindings = parseTemplateBindings(
|
||||
'ngFor', 'let item; of items | slice:0:1 as collection, trackBy: func; index as i');
|
||||
expect(humanize(bindings)).toEqual([
|
||||
// [ key, value, keyIsVar ]
|
||||
['ngFor', null, false],
|
||||
['item', '$implicit', true],
|
||||
['ngForOf', 'items | slice:0:1 ', false],
|
||||
['collection', 'ngForOf', true],
|
||||
['ngForTrackBy', 'func', false],
|
||||
['i', 'index', true],
|
||||
]);
|
||||
|
||||
bindings = parseTemplateBindings(
|
||||
'ngFor', 'let item, of: [1,2,3] | pipe as items; let i=index, count as len');
|
||||
expect(humanize(bindings)).toEqual([
|
||||
// [ key, value, keyIsVar ]
|
||||
['ngFor', null, false],
|
||||
['item', '$implicit', true],
|
||||
['ngForOf', '[1,2,3] | pipe ', false],
|
||||
['items', 'ngForOf', true],
|
||||
['i', 'index', true],
|
||||
['len', 'count', true],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support let notation', () => {
|
||||
@ -369,7 +416,7 @@ describe('parser', () => {
|
||||
|
||||
it('should parse pipes', () => {
|
||||
const bindings = parseTemplateBindings('key', 'value|pipe');
|
||||
const ast = bindings[0].expression !.ast;
|
||||
const ast = bindings[0].value !.ast;
|
||||
expect(ast).toBeAnInstanceOf(BindingPipe);
|
||||
});
|
||||
|
||||
|
@ -20,6 +20,19 @@ export function createNgcProgram(
|
||||
// NGC program. In order to ensure that the migration runs properly, we set "enableIvy" to false.
|
||||
options.enableIvy = false;
|
||||
|
||||
// Libraries which have been generated with CLI versions past v6.2.0, explicitly set the
|
||||
// flat-module options in their tsconfig files. This is problematic because by default,
|
||||
// those tsconfig files do not specify explicit source files which can be considered as
|
||||
// entry point for the flat-module bundle. Therefore the `@angular/compiler-cli` is unable
|
||||
// to determine the flat module entry point and throws a compile error. This is not an issue
|
||||
// for the libraries built with `ng-packagr`, because the tsconfig files are modified in-memory
|
||||
// to specify an explicit flat module entry-point. Our migrations don't distinguish between
|
||||
// libraries and applications, and also don't run `ng-packagr`. To ensure that such libraries
|
||||
// can be successfully migrated, we remove the flat-module options to eliminate the flat module
|
||||
// entry-point requirement. More context: https://github.com/angular/angular/issues/34985.
|
||||
options.flatModuleId = undefined;
|
||||
options.flatModuleOutFile = undefined;
|
||||
|
||||
const host = createHost(options);
|
||||
|
||||
// For this migration, we never need to read resources and can just return
|
||||
|
@ -1473,6 +1473,40 @@ describe('Undecorated classes with DI migration', () => {
|
||||
'TypeScript program failures');
|
||||
});
|
||||
|
||||
// Regression test for: https://github.com/angular/angular/issues/34985.
|
||||
it('should be able to migrate libraries with multiple source files and flat-module ' +
|
||||
'options set',
|
||||
async() => {
|
||||
writeFile('/tsconfig.json', JSON.stringify({
|
||||
compilerOptions: {
|
||||
lib: ['es2015'],
|
||||
},
|
||||
angularCompilerOptions:
|
||||
{flatModuleId: 'AUTOGENERATED', flatModuleOutFile: 'AUTOGENERATED'}
|
||||
}));
|
||||
|
||||
writeFile('/second.ts', ``);
|
||||
writeFile('/test.ts', `
|
||||
import {Injectable, NgModule, NgZone} from '@angular/core';
|
||||
|
||||
export class BaseClass {
|
||||
constructor(zone: NgZone) {}
|
||||
}
|
||||
|
||||
@Injectable({template: ''})
|
||||
export class MyService extends BaseClass {}
|
||||
|
||||
@NgModule({providers: [MyService]})
|
||||
export class AppModule {}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(errorOutput.length).toBe(0);
|
||||
expect(warnOutput.length).toBe(0);
|
||||
expect(tree.readContent('/test.ts')).toMatch(/@Injectable\(\)\nexport class BaseClass {/);
|
||||
});
|
||||
|
||||
it('should not throw if resources could not be read', async() => {
|
||||
writeFile('/index.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
@ -289,56 +289,56 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
||||
*/
|
||||
schemas?: SchemaMetadata[] | null;
|
||||
}): never {
|
||||
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
|
||||
// See the `initNgDevMode` docstring for more information.
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
|
||||
return noSideEffects(() => {
|
||||
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
|
||||
// See the `initNgDevMode` docstring for more information.
|
||||
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
|
||||
|
||||
const type = componentDefinition.type;
|
||||
const typePrototype = type.prototype;
|
||||
const declaredInputs: {[key: string]: string} = {} as any;
|
||||
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
|
||||
type: type,
|
||||
providersResolver: null,
|
||||
decls: componentDefinition.decls,
|
||||
vars: componentDefinition.vars,
|
||||
factory: null,
|
||||
template: componentDefinition.template || null !,
|
||||
consts: componentDefinition.consts || null,
|
||||
ngContentSelectors: componentDefinition.ngContentSelectors,
|
||||
hostBindings: componentDefinition.hostBindings || null,
|
||||
hostVars: componentDefinition.hostVars || 0,
|
||||
hostAttrs: componentDefinition.hostAttrs || null,
|
||||
contentQueries: componentDefinition.contentQueries || null,
|
||||
declaredInputs: declaredInputs,
|
||||
inputs: null !, // assigned in noSideEffects
|
||||
outputs: null !, // assigned in noSideEffects
|
||||
exportAs: componentDefinition.exportAs || null,
|
||||
onChanges: null,
|
||||
onInit: typePrototype.ngOnInit || null,
|
||||
doCheck: typePrototype.ngDoCheck || null,
|
||||
afterContentInit: typePrototype.ngAfterContentInit || null,
|
||||
afterContentChecked: typePrototype.ngAfterContentChecked || null,
|
||||
afterViewInit: typePrototype.ngAfterViewInit || null,
|
||||
afterViewChecked: typePrototype.ngAfterViewChecked || null,
|
||||
onDestroy: typePrototype.ngOnDestroy || null,
|
||||
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||
directiveDefs: null !, // assigned in noSideEffects
|
||||
pipeDefs: null !, // assigned in noSideEffects
|
||||
selectors: componentDefinition.selectors || EMPTY_ARRAY,
|
||||
viewQuery: componentDefinition.viewQuery || null,
|
||||
features: componentDefinition.features as DirectiveDefFeature[] || null,
|
||||
data: componentDefinition.data || {},
|
||||
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the
|
||||
// next line. Also `None` should be 0 not 2.
|
||||
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
|
||||
id: 'c',
|
||||
styles: componentDefinition.styles || EMPTY_ARRAY,
|
||||
_: null as never,
|
||||
setInput: null,
|
||||
schemas: componentDefinition.schemas || null,
|
||||
tView: null,
|
||||
};
|
||||
def._ = noSideEffects(() => {
|
||||
const type = componentDefinition.type;
|
||||
const typePrototype = type.prototype;
|
||||
const declaredInputs: {[key: string]: string} = {} as any;
|
||||
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
|
||||
type: type,
|
||||
providersResolver: null,
|
||||
decls: componentDefinition.decls,
|
||||
vars: componentDefinition.vars,
|
||||
factory: null,
|
||||
template: componentDefinition.template || null !,
|
||||
consts: componentDefinition.consts || null,
|
||||
ngContentSelectors: componentDefinition.ngContentSelectors,
|
||||
hostBindings: componentDefinition.hostBindings || null,
|
||||
hostVars: componentDefinition.hostVars || 0,
|
||||
hostAttrs: componentDefinition.hostAttrs || null,
|
||||
contentQueries: componentDefinition.contentQueries || null,
|
||||
declaredInputs: declaredInputs,
|
||||
inputs: null !, // assigned in noSideEffects
|
||||
outputs: null !, // assigned in noSideEffects
|
||||
exportAs: componentDefinition.exportAs || null,
|
||||
onChanges: null,
|
||||
onInit: typePrototype.ngOnInit || null,
|
||||
doCheck: typePrototype.ngDoCheck || null,
|
||||
afterContentInit: typePrototype.ngAfterContentInit || null,
|
||||
afterContentChecked: typePrototype.ngAfterContentChecked || null,
|
||||
afterViewInit: typePrototype.ngAfterViewInit || null,
|
||||
afterViewChecked: typePrototype.ngAfterViewChecked || null,
|
||||
onDestroy: typePrototype.ngOnDestroy || null,
|
||||
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||
directiveDefs: null !, // assigned in noSideEffects
|
||||
pipeDefs: null !, // assigned in noSideEffects
|
||||
selectors: componentDefinition.selectors || EMPTY_ARRAY,
|
||||
viewQuery: componentDefinition.viewQuery || null,
|
||||
features: componentDefinition.features as DirectiveDefFeature[] || null,
|
||||
data: componentDefinition.data || {},
|
||||
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in
|
||||
// the next line. Also `None` should be 0 not 2.
|
||||
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
|
||||
id: 'c',
|
||||
styles: componentDefinition.styles || EMPTY_ARRAY,
|
||||
_: null as never,
|
||||
setInput: null,
|
||||
schemas: componentDefinition.schemas || null,
|
||||
tView: null,
|
||||
};
|
||||
const directiveTypes = componentDefinition.directives !;
|
||||
const feature = componentDefinition.features;
|
||||
const pipeTypes = componentDefinition.pipes !;
|
||||
@ -353,9 +353,9 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
||||
def.pipeDefs = pipeTypes ?
|
||||
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
|
||||
null;
|
||||
}) as never;
|
||||
|
||||
return def as never;
|
||||
return def as never;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,6 +14,7 @@ import {getInjectorDef} from '../di/interface/defs';
|
||||
import {InjectFlags} from '../di/interface/injector';
|
||||
import {Type} from '../interface/type';
|
||||
import {assertDefined, assertEqual} from '../util/assert';
|
||||
import {noSideEffects} from '../util/closure';
|
||||
|
||||
import {assertDirectiveDef} from './assert';
|
||||
import {getFactoryDef} from './definition';
|
||||
@ -655,15 +656,17 @@ export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
|
||||
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
|
||||
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
|
||||
if (factory !== null) {
|
||||
return factory;
|
||||
} else {
|
||||
// There is no factory defined. Either this was improper usage of inheritance
|
||||
// (no Angular decorator on the superclass) or there is no constructor at all
|
||||
// in the inheritance chain. Since the two cases cannot be distinguished, the
|
||||
// latter has to be assumed.
|
||||
return (t) => new t();
|
||||
}
|
||||
return noSideEffects(() => {
|
||||
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
|
||||
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
|
||||
if (factory !== null) {
|
||||
return factory;
|
||||
} else {
|
||||
// There is no factory defined. Either this was improper usage of inheritance
|
||||
// (no Angular decorator on the superclass) or there is no constructor at all
|
||||
// in the inheritance chain. Since the two cases cannot be distinguished, the
|
||||
// latter has to be assumed.
|
||||
return (t) => new t();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -35,26 +35,26 @@ type OnChangesExpando = OnChanges & {
|
||||
* static ɵcmp = defineComponent({
|
||||
* ...
|
||||
* inputs: {name: 'publicName'},
|
||||
* features: [NgOnChangesFeature()]
|
||||
* features: [NgOnChangesFeature]
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵNgOnChangesFeature<T>(): DirectiveDefFeature {
|
||||
// This option ensures that the ngOnChanges lifecycle hook will be inherited
|
||||
// from superclasses (in InheritDefinitionFeature).
|
||||
(NgOnChangesFeatureImpl as DirectiveDefFeature).ngInherit = true;
|
||||
return NgOnChangesFeatureImpl;
|
||||
}
|
||||
|
||||
function NgOnChangesFeatureImpl<T>(definition: DirectiveDef<T>): void {
|
||||
export function ɵɵNgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
|
||||
if (definition.type.prototype.ngOnChanges) {
|
||||
definition.setInput = ngOnChangesSetInput;
|
||||
(definition as{onChanges: Function}).onChanges = wrapOnChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// This option ensures that the ngOnChanges lifecycle hook will be inherited
|
||||
// from superclasses (in InheritDefinitionFeature).
|
||||
/** @nocollapse */
|
||||
// tslint:disable-next-line:no-toplevel-property-access
|
||||
(ɵɵNgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
|
||||
|
||||
function wrapOnChanges() {
|
||||
return function wrapOnChangesHook_inPreviousChangesStorage(this: OnChanges) {
|
||||
const simpleChangesStore = getSimpleChangesStore(this);
|
||||
|
@ -455,19 +455,6 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
|
||||
},
|
||||
};
|
||||
|
||||
maybeUnwrapFn(def.declarations).forEach(declared => {
|
||||
const declaredWithDefs = declared as Type<any>& { ɵpipe?: any; };
|
||||
|
||||
if (getPipeDef(declaredWithDefs)) {
|
||||
scopes.compilation.pipes.add(declared);
|
||||
} else {
|
||||
// Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
|
||||
// had its template compiled yet. In either case, it gets added to the compilation's
|
||||
// directives.
|
||||
scopes.compilation.directives.add(declared);
|
||||
}
|
||||
});
|
||||
|
||||
maybeUnwrapFn(def.imports).forEach(<I>(imported: Type<I>) => {
|
||||
const importedType = imported as Type<I>& {
|
||||
// If imported is an @NgModule:
|
||||
@ -485,6 +472,19 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
|
||||
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
|
||||
});
|
||||
|
||||
maybeUnwrapFn(def.declarations).forEach(declared => {
|
||||
const declaredWithDefs = declared as Type<any>& { ɵpipe?: any; };
|
||||
|
||||
if (getPipeDef(declaredWithDefs)) {
|
||||
scopes.compilation.pipes.add(declared);
|
||||
} else {
|
||||
// Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
|
||||
// had its template compiled yet. In either case, it gets added to the compilation's
|
||||
// directives.
|
||||
scopes.compilation.directives.add(declared);
|
||||
}
|
||||
});
|
||||
|
||||
maybeUnwrapFn(def.exports).forEach(<E>(exported: Type<E>) => {
|
||||
const exportedType = exported as Type<E>& {
|
||||
// Components, Directives, NgModules, and Pipes can all be exported.
|
||||
|
@ -15,6 +15,6 @@
|
||||
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure
|
||||
* compiler.
|
||||
*/
|
||||
export function noSideEffects(fn: () => void): string {
|
||||
return '' + {toString: fn};
|
||||
}
|
||||
export function noSideEffects<T>(fn: () => T): T {
|
||||
return {toString: fn}.toString() as unknown as T;
|
||||
}
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
import {Type} from '../interface/type';
|
||||
|
||||
import {noSideEffects} from './closure';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface implemented by all Angular type decorators, which allows them to be used as
|
||||
* decorators as well as Angular syntax.
|
||||
@ -44,39 +48,41 @@ export function makeDecorator<T>(
|
||||
additionalProcessing?: (type: Type<T>) => void,
|
||||
typeFn?: (type: Type<T>, ...args: any[]) => void):
|
||||
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
return noSideEffects(() => {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
|
||||
function DecoratorFactory(
|
||||
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
|
||||
if (this instanceof DecoratorFactory) {
|
||||
metaCtor.call(this, ...args);
|
||||
return this as typeof DecoratorFactory;
|
||||
function DecoratorFactory(
|
||||
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
|
||||
if (this instanceof DecoratorFactory) {
|
||||
metaCtor.call(this, ...args);
|
||||
return this as typeof DecoratorFactory;
|
||||
}
|
||||
|
||||
const annotationInstance = new (DecoratorFactory as any)(...args);
|
||||
return function TypeDecorator(cls: Type<T>) {
|
||||
if (typeFn) typeFn(cls, ...args);
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
|
||||
(cls as any)[ANNOTATIONS] :
|
||||
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
|
||||
annotations.push(annotationInstance);
|
||||
|
||||
|
||||
if (additionalProcessing) additionalProcessing(cls);
|
||||
|
||||
return cls;
|
||||
};
|
||||
}
|
||||
|
||||
const annotationInstance = new (DecoratorFactory as any)(...args);
|
||||
return function TypeDecorator(cls: Type<T>) {
|
||||
if (typeFn) typeFn(cls, ...args);
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
|
||||
(cls as any)[ANNOTATIONS] :
|
||||
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
|
||||
annotations.push(annotationInstance);
|
||||
if (parentClass) {
|
||||
DecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
|
||||
|
||||
if (additionalProcessing) additionalProcessing(cls);
|
||||
|
||||
return cls;
|
||||
};
|
||||
}
|
||||
|
||||
if (parentClass) {
|
||||
DecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
|
||||
DecoratorFactory.prototype.ngMetadataName = name;
|
||||
(DecoratorFactory as any).annotationCls = DecoratorFactory;
|
||||
return DecoratorFactory as any;
|
||||
DecoratorFactory.prototype.ngMetadataName = name;
|
||||
(DecoratorFactory as any).annotationCls = DecoratorFactory;
|
||||
return DecoratorFactory as any;
|
||||
});
|
||||
}
|
||||
|
||||
function makeMetadataCtor(props?: (...args: any[]) => any): any {
|
||||
@ -92,77 +98,82 @@ function makeMetadataCtor(props?: (...args: any[]) => any): any {
|
||||
|
||||
export function makeParamDecorator(
|
||||
name: string, props?: (...args: any[]) => any, parentClass?: any): any {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
function ParamDecoratorFactory(
|
||||
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
|
||||
if (this instanceof ParamDecoratorFactory) {
|
||||
metaCtor.apply(this, args);
|
||||
return this;
|
||||
}
|
||||
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
|
||||
|
||||
(<any>ParamDecorator).annotation = annotationInstance;
|
||||
return ParamDecorator;
|
||||
|
||||
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const parameters = cls.hasOwnProperty(PARAMETERS) ?
|
||||
(cls as any)[PARAMETERS] :
|
||||
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
|
||||
|
||||
// there might be gaps if some in between parameters do not have annotations.
|
||||
// we pad with nulls.
|
||||
while (parameters.length <= index) {
|
||||
parameters.push(null);
|
||||
return noSideEffects(() => {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
function ParamDecoratorFactory(
|
||||
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
|
||||
if (this instanceof ParamDecoratorFactory) {
|
||||
metaCtor.apply(this, args);
|
||||
return this;
|
||||
}
|
||||
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
|
||||
|
||||
(parameters[index] = parameters[index] || []).push(annotationInstance);
|
||||
return cls;
|
||||
(<any>ParamDecorator).annotation = annotationInstance;
|
||||
return ParamDecorator;
|
||||
|
||||
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const parameters = cls.hasOwnProperty(PARAMETERS) ?
|
||||
(cls as any)[PARAMETERS] :
|
||||
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
|
||||
|
||||
// there might be gaps if some in between parameters do not have annotations.
|
||||
// we pad with nulls.
|
||||
while (parameters.length <= index) {
|
||||
parameters.push(null);
|
||||
}
|
||||
|
||||
(parameters[index] = parameters[index] || []).push(annotationInstance);
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parentClass) {
|
||||
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
ParamDecoratorFactory.prototype.ngMetadataName = name;
|
||||
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
|
||||
return ParamDecoratorFactory;
|
||||
if (parentClass) {
|
||||
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
ParamDecoratorFactory.prototype.ngMetadataName = name;
|
||||
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
|
||||
return ParamDecoratorFactory;
|
||||
});
|
||||
}
|
||||
|
||||
export function makePropDecorator(
|
||||
name: string, props?: (...args: any[]) => any, parentClass?: any,
|
||||
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
return noSideEffects(() => {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
|
||||
function PropDecoratorFactory(this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
|
||||
if (this instanceof PropDecoratorFactory) {
|
||||
metaCtor.apply(this, args);
|
||||
return this;
|
||||
function PropDecoratorFactory(
|
||||
this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
|
||||
if (this instanceof PropDecoratorFactory) {
|
||||
metaCtor.apply(this, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
||||
|
||||
function PropDecorator(target: any, name: string) {
|
||||
const constructor = target.constructor;
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
|
||||
(constructor as any)[PROP_METADATA] :
|
||||
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
|
||||
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
|
||||
meta[name].unshift(decoratorInstance);
|
||||
|
||||
if (additionalProcessing) additionalProcessing(target, name, ...args);
|
||||
}
|
||||
|
||||
return PropDecorator;
|
||||
}
|
||||
|
||||
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
||||
|
||||
function PropDecorator(target: any, name: string) {
|
||||
const constructor = target.constructor;
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
|
||||
(constructor as any)[PROP_METADATA] :
|
||||
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
|
||||
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
|
||||
meta[name].unshift(decoratorInstance);
|
||||
|
||||
if (additionalProcessing) additionalProcessing(target, name, ...args);
|
||||
if (parentClass) {
|
||||
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
|
||||
return PropDecorator;
|
||||
}
|
||||
|
||||
if (parentClass) {
|
||||
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
|
||||
PropDecoratorFactory.prototype.ngMetadataName = name;
|
||||
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
|
||||
return PropDecoratorFactory;
|
||||
PropDecoratorFactory.prototype.ngMetadataName = name;
|
||||
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
|
||||
return PropDecoratorFactory;
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, Directive, ElementRef, EventEmitter, Output, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {Component, Directive, ElementRef, EventEmitter, NgModule, Output, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {Input} from '@angular/core/src/metadata';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
@ -633,4 +633,96 @@ describe('directives', () => {
|
||||
expect(div.getAttribute('title')).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('directives with the same selector', () => {
|
||||
it('should process Directives from `declarations` list after imported ones', () => {
|
||||
const log: string[] = [];
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveA {
|
||||
constructor() { log.push('DirectiveA.constructor'); }
|
||||
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DirectiveA],
|
||||
exports: [DirectiveA],
|
||||
})
|
||||
class ModuleA {
|
||||
}
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveB {
|
||||
constructor() { log.push('DirectiveB.constructor'); }
|
||||
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '<div dir></div>',
|
||||
})
|
||||
class App {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ModuleA],
|
||||
declarations: [DirectiveB, App],
|
||||
});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(log).toEqual([
|
||||
'DirectiveA.constructor', 'DirectiveB.constructor', 'DirectiveA.ngOnInit',
|
||||
'DirectiveB.ngOnInit'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should respect imported module order', () => {
|
||||
const log: string[] = [];
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveA {
|
||||
constructor() { log.push('DirectiveA.constructor'); }
|
||||
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DirectiveA],
|
||||
exports: [DirectiveA],
|
||||
})
|
||||
class ModuleA {
|
||||
}
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class DirectiveB {
|
||||
constructor() { log.push('DirectiveB.constructor'); }
|
||||
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DirectiveB],
|
||||
exports: [DirectiveB],
|
||||
})
|
||||
class ModuleB {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '<div dir></div>',
|
||||
})
|
||||
class App {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ModuleA, ModuleB],
|
||||
declarations: [App],
|
||||
});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(log).toEqual([
|
||||
'DirectiveA.constructor', 'DirectiveB.constructor', 'DirectiveA.ngOnInit',
|
||||
'DirectiveB.ngOnInit'
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -110,6 +110,86 @@ describe('pipe', () => {
|
||||
expect(fixture.nativeElement.textContent).toEqual('value a b default 0 1 2 3');
|
||||
});
|
||||
|
||||
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
|
||||
@Pipe({name: 'number'})
|
||||
class PipeA implements PipeTransform {
|
||||
transform(value: any) { return `PipeA: ${value}`; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [PipeA],
|
||||
exports: [PipeA],
|
||||
})
|
||||
class ModuleA {
|
||||
}
|
||||
|
||||
@Pipe({name: 'number'})
|
||||
class PipeB implements PipeTransform {
|
||||
transform(value: any) { return `PipeB: ${value}`; }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '{{ count | number }}',
|
||||
})
|
||||
class App {
|
||||
count = 10;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ModuleA],
|
||||
declarations: [PipeB, App],
|
||||
});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('PipeB: 10');
|
||||
});
|
||||
|
||||
it('should respect imported module order when selecting Pipe (last imported Pipe is used)',
|
||||
() => {
|
||||
@Pipe({name: 'number'})
|
||||
class PipeA implements PipeTransform {
|
||||
transform(value: any) { return `PipeA: ${value}`; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [PipeA],
|
||||
exports: [PipeA],
|
||||
})
|
||||
class ModuleA {
|
||||
}
|
||||
|
||||
@Pipe({name: 'number'})
|
||||
class PipeB implements PipeTransform {
|
||||
transform(value: any) { return `PipeB: ${value}`; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [PipeB],
|
||||
exports: [PipeB],
|
||||
})
|
||||
class ModuleB {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: '{{ count | number }}',
|
||||
})
|
||||
class App {
|
||||
count = 10;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ModuleA, ModuleB],
|
||||
declarations: [App],
|
||||
});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('PipeB: 10');
|
||||
});
|
||||
|
||||
it('should do nothing when no change', () => {
|
||||
let calls: any[] = [];
|
||||
|
||||
|
@ -167,6 +167,9 @@
|
||||
{
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
{
|
||||
"name": "autoRegisterModuleById"
|
||||
},
|
||||
{
|
||||
"name": "baseResolveDirective"
|
||||
},
|
||||
|
@ -223,5 +223,8 @@
|
||||
},
|
||||
{
|
||||
"name": "ɵɵinject"
|
||||
},
|
||||
{
|
||||
"name": "noSideEffects"
|
||||
}
|
||||
]
|
||||
|
@ -41,7 +41,7 @@ NgIf.ɵfac = () =>
|
||||
NgTemplateOutlet.ɵdir = ɵɵdefineDirective({
|
||||
type: NgTemplateOutletDef,
|
||||
selectors: [['', 'ngTemplateOutlet', '']],
|
||||
features: [ɵɵNgOnChangesFeature()],
|
||||
features: [ɵɵNgOnChangesFeature],
|
||||
inputs:
|
||||
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
|
||||
});
|
||||
|
@ -30,6 +30,7 @@ filegroup(
|
||||
testonly = True,
|
||||
# do not sort
|
||||
srcs = [
|
||||
"@npm//:node_modules/core-js/client/core.js",
|
||||
"@npm//:node_modules/@webcomponents/custom-elements/src/native-shim.js",
|
||||
"@npm//:node_modules/reflect-metadata/Reflect.js",
|
||||
"//packages/zone.js/dist:zone.js",
|
||||
@ -42,11 +43,6 @@ karma_web_test_suite(
|
||||
bootstrap = [
|
||||
":elements_test_bootstrap_scripts",
|
||||
],
|
||||
tags = [
|
||||
# FIXME: timed out in CI
|
||||
"fixme-saucelabs-ivy",
|
||||
"fixme-saucelabs-ve",
|
||||
],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
@ -567,8 +567,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
if (binding.expression && inSpan(valueRelativePosition, binding.expression.ast.span)) {
|
||||
this.processExpressionCompletions(binding.expression.ast);
|
||||
if (binding.value && inSpan(valueRelativePosition, binding.value.ast.span)) {
|
||||
this.processExpressionCompletions(binding.value.ast);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -91,10 +91,8 @@ function getVarDeclarations(
|
||||
continue;
|
||||
}
|
||||
for (const variable of current.variables) {
|
||||
let symbol = info.members.get(variable.value);
|
||||
if (!symbol) {
|
||||
symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current);
|
||||
}
|
||||
let symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current);
|
||||
|
||||
const kind = info.query.getTypeKind(symbol);
|
||||
if (kind === BuiltinType.Any || kind === BuiltinType.Unbound) {
|
||||
// For special cases such as ngFor and ngIf, the any type is not very useful.
|
||||
|
@ -209,10 +209,10 @@ function getSymbolInMicrosyntax(info: AstResult, path: TemplateAstPath, attribut
|
||||
|
||||
// Find the symbol that contains the position.
|
||||
templateBindings.filter(tb => !tb.keyIsVar).forEach(tb => {
|
||||
if (inSpan(valueRelativePosition, tb.expression?.ast.span)) {
|
||||
if (inSpan(valueRelativePosition, tb.value?.ast.span)) {
|
||||
const dinfo = diagnosticInfoFromTemplateInfo(info);
|
||||
const scope = getExpressionScope(dinfo, path);
|
||||
result = getExpressionSymbol(scope, tb.expression !, path.position, info.template.query);
|
||||
result = getExpressionSymbol(scope, tb.value !, path.position, info.template.query);
|
||||
} else if (inSpan(valueRelativePosition, tb.span)) {
|
||||
const template = path.first(EmbeddedTemplateAst);
|
||||
if (template) {
|
||||
|
@ -170,6 +170,8 @@ export class TemplateReference {
|
||||
primitiveIndexType: {[name: string]: string} = {};
|
||||
anyValue: any;
|
||||
optional?: string;
|
||||
// Use to test the `index` variable conflict between the `ngFor` and component context.
|
||||
index = null;
|
||||
myClick(event: any) {}
|
||||
}
|
||||
|
||||
|
@ -15,4 +15,15 @@ export class Diagnostics {
|
||||
get hasErrors() { return this.messages.some(m => m.type === 'error'); }
|
||||
warn(message: string) { this.messages.push({type: 'warning', message}); }
|
||||
error(message: string) { this.messages.push({type: 'error', message}); }
|
||||
formatDiagnostics(message: string): string {
|
||||
const errors = this.messages !.filter(d => d.type === 'error').map(d => ' - ' + d.message);
|
||||
const warnings = this.messages !.filter(d => d.type === 'warning').map(d => ' - ' + d.message);
|
||||
if (errors.length) {
|
||||
message += '\nERRORS:\n' + errors.join('\n');
|
||||
}
|
||||
if (warnings.length) {
|
||||
message += '\nWARNINGS:\n' + warnings.join('\n');
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ export function translateFiles({sourceRootPath, sourceFilePaths, translationFile
|
||||
[
|
||||
new Xliff2TranslationParser(),
|
||||
new Xliff1TranslationParser(),
|
||||
new XtbTranslationParser(diagnostics),
|
||||
new XtbTranslationParser(),
|
||||
new SimpleJsonTranslationParser(),
|
||||
],
|
||||
diagnostics);
|
||||
|
@ -14,7 +14,9 @@ import {TranslationParser} from './translation_parsers/translation_parser';
|
||||
* Use this class to load a collection of translation files from disk.
|
||||
*/
|
||||
export class TranslationLoader {
|
||||
constructor(private translationParsers: TranslationParser[], private diagnostics: Diagnostics) {}
|
||||
constructor(
|
||||
private translationParsers: TranslationParser<any>[],
|
||||
/** @deprecated */ private diagnostics?: Diagnostics) {}
|
||||
|
||||
/**
|
||||
* Load and parse the translation files into a collection of `TranslationBundles`.
|
||||
@ -34,22 +36,37 @@ export class TranslationLoader {
|
||||
return translationFilePaths.map((filePath, index) => {
|
||||
const fileContents = FileUtils.readFile(filePath);
|
||||
for (const translationParser of this.translationParsers) {
|
||||
if (translationParser.canParse(filePath, fileContents)) {
|
||||
const providedLocale = translationFileLocales[index];
|
||||
const {locale: parsedLocale, translations} =
|
||||
translationParser.parse(filePath, fileContents);
|
||||
const locale = providedLocale || parsedLocale;
|
||||
if (locale === undefined) {
|
||||
throw new Error(
|
||||
`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`);
|
||||
}
|
||||
if (parsedLocale !== undefined && providedLocale !== undefined &&
|
||||
parsedLocale !== providedLocale) {
|
||||
this.diagnostics.warn(
|
||||
`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`);
|
||||
}
|
||||
return {locale, translations};
|
||||
const result = translationParser.canParse(filePath, fileContents);
|
||||
if (!result) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const {locale: parsedLocale, translations, diagnostics} =
|
||||
translationParser.parse(filePath, fileContents, result);
|
||||
if (diagnostics.hasErrors) {
|
||||
throw new Error(diagnostics.formatDiagnostics(
|
||||
`The translation file "${filePath}" could not be parsed.`));
|
||||
}
|
||||
|
||||
const providedLocale = translationFileLocales[index];
|
||||
const locale = providedLocale || parsedLocale;
|
||||
if (locale === undefined) {
|
||||
throw new Error(
|
||||
`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`);
|
||||
}
|
||||
|
||||
if (parsedLocale !== undefined && providedLocale !== undefined &&
|
||||
parsedLocale !== providedLocale) {
|
||||
diagnostics.warn(
|
||||
`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`);
|
||||
}
|
||||
|
||||
// If we were passed a diagnostics object then copy the messages over to it.
|
||||
if (this.diagnostics) {
|
||||
this.diagnostics.messages.push(...diagnostics.messages);
|
||||
}
|
||||
|
||||
return {locale, translations, diagnostics};
|
||||
}
|
||||
throw new Error(
|
||||
`There is no "TranslationParser" that can parse this translation file: ${filePath}.`);
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
import {ɵMessageId, ɵParsedTranslation, ɵparseTranslation} from '@angular/localize';
|
||||
import {extname} from 'path';
|
||||
import {Diagnostics} from '../../../diagnostics';
|
||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||
|
||||
/**
|
||||
@ -32,6 +33,6 @@ export class SimpleJsonTranslationParser implements TranslationParser {
|
||||
const targetMessage = translations[messageId];
|
||||
parsedTranslations[messageId] = ɵparseTranslation(targetMessage);
|
||||
}
|
||||
return {locale: parsedLocale, translations: parsedTranslations};
|
||||
return {locale: parsedLocale, translations: parsedTranslations, diagnostics: new Diagnostics()};
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
|
||||
import {Diagnostics} from '../../../diagnostics';
|
||||
|
||||
/**
|
||||
* An object that holds translations that have been parsed from a translation file.
|
||||
@ -13,25 +14,62 @@ import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
|
||||
export interface ParsedTranslationBundle {
|
||||
locale: string|undefined;
|
||||
translations: Record<ɵMessageId, ɵParsedTranslation>;
|
||||
diagnostics: Diagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to provide a class that can parse the contents of a translation file.
|
||||
*
|
||||
* The `canParse()` method can return a hint that can be used by the `parse()` method to speed up
|
||||
* parsing. This allows the parser to do significant work to determine if the file can be parsed
|
||||
* without duplicating the work when it comes to actually parsing the file.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```
|
||||
* const parser: TranslationParser = getParser();
|
||||
* const result = parser.canParse(filePath, content);
|
||||
* if (result) {
|
||||
* return parser.parse(filePath, content, result);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface TranslationParser {
|
||||
export interface TranslationParser<Hint = true> {
|
||||
/**
|
||||
* Returns true if this parser can parse the given file.
|
||||
* Can this parser parse the given file?
|
||||
*
|
||||
* @param filePath The absolute path to the translation file.
|
||||
* @param contents The contents of the translation file.
|
||||
* @returns A hint, which can be used in doing the actual parsing, if the file can be parsed by
|
||||
* this parser; false otherwise.
|
||||
*/
|
||||
canParse(filePath: string, contents: string): boolean;
|
||||
canParse(filePath: string, contents: string): Hint|false;
|
||||
|
||||
/**
|
||||
* Parses the given file, extracting the target locale and translations.
|
||||
*
|
||||
* Note that this method should not throw an error. Check the `bundle.diagnostics` property for
|
||||
* potential parsing errors and warnings.
|
||||
*
|
||||
* @param filePath The absolute path to the translation file.
|
||||
* @param contents The contents of the translation file.
|
||||
* @param hint A value that can be used by the parser to speed up parsing of the file. This will
|
||||
* have been provided as the return result from calling `canParse()`.
|
||||
* @returns The translation bundle parsed from the file.
|
||||
* @throws No errors. If there was a problem with parsing the bundle will contain errors
|
||||
* in the `diagnostics` property.
|
||||
*/
|
||||
parse(filePath: string, contents: string, hint: Hint): ParsedTranslationBundle;
|
||||
/**
|
||||
* Parses the given file, extracting the target locale and translations.
|
||||
*
|
||||
* @deprecated This overload is kept for backward compatibility. Going forward use the Hint
|
||||
* returned from `canParse()` so that this method can avoid duplicating effort.
|
||||
*
|
||||
* @param filePath The absolute path to the translation file.
|
||||
* @param contents The contents of the translation file.
|
||||
* @returns The translation bundle parsed from the file.
|
||||
* @throws An error if there was a problem parsing this file.
|
||||
*/
|
||||
parse(filePath: string, contents: string): ParsedTranslationBundle;
|
||||
}
|
||||
|
@ -5,7 +5,8 @@
|
||||
* 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 {Element, LexerRange, Node, XmlParser} from '@angular/compiler';
|
||||
import {Element, LexerRange, Node, ParseError, ParseErrorLevel, ParseSourceSpan, XmlParser} from '@angular/compiler';
|
||||
import {Diagnostics} from '../../../diagnostics';
|
||||
import {TranslationParseError} from './translation_parse_error';
|
||||
|
||||
export function getAttrOrThrow(element: Element, attrName: string): string {
|
||||
@ -22,6 +23,14 @@ export function getAttribute(element: Element, attrName: string): string|undefin
|
||||
return attr !== undefined ? attr.value : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the "contents" of an XML element.
|
||||
*
|
||||
* This would be equivalent to parsing the `innerHTML` string of an HTML document.
|
||||
*
|
||||
* @param element The element whose inner range we want to parse.
|
||||
* @returns a collection of XML `Node` objects that were parsed from the element's contents.
|
||||
*/
|
||||
export function parseInnerRange(element: Element): Node[] {
|
||||
const xmlParser = new XmlParser();
|
||||
const xml = xmlParser.parse(
|
||||
@ -33,6 +42,10 @@ export function parseInnerRange(element: Element): Node[] {
|
||||
return xml.rootNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a `LexerRange` that contains all the children of the given `element`.
|
||||
* @param element The element whose inner range we want to compute.
|
||||
*/
|
||||
function getInnerRange(element: Element): LexerRange {
|
||||
const start = element.startSourceSpan !.end;
|
||||
const end = element.endSourceSpan !.start;
|
||||
@ -42,4 +55,94 @@ function getInnerRange(element: Element): LexerRange {
|
||||
startCol: start.col,
|
||||
endPos: end.offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This "hint" object is used to pass information from `canParse()` to `parse()` for
|
||||
* `TranslationParser`s that expect XML contents.
|
||||
*
|
||||
* This saves the `parse()` method from having to re-parse the XML.
|
||||
*/
|
||||
export interface XmlTranslationParserHint {
|
||||
element: Element;
|
||||
errors: ParseError[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this XML be parsed for translations, given the expected `rootNodeName` and expected root node
|
||||
* `attributes` that should appear in the file.
|
||||
*
|
||||
* @param filePath The path to the file being checked.
|
||||
* @param contents The contents of the file being checked.
|
||||
* @param rootNodeName The expected name of an XML root node that should exist.
|
||||
* @param attributes The attributes (and their values) that should appear on the root node.
|
||||
* @returns The `XmlTranslationParserHint` object for use by `TranslationParser.parse()` if the XML
|
||||
* document has the expected format.
|
||||
*/
|
||||
export function canParseXml(
|
||||
filePath: string, contents: string, rootNodeName: string,
|
||||
attributes: Record<string, string>): XmlTranslationParserHint|false {
|
||||
const xmlParser = new XmlParser();
|
||||
const xml = xmlParser.parse(contents, filePath);
|
||||
|
||||
if (xml.rootNodes.length === 0 ||
|
||||
xml.errors.some(error => error.level === ParseErrorLevel.ERROR)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rootElements = xml.rootNodes.filter(isNamedElement(rootNodeName));
|
||||
const rootElement = rootElements[0];
|
||||
if (rootElement === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const attrKey of Object.keys(attributes)) {
|
||||
const attr = rootElement.attrs.find(attr => attr.name === attrKey);
|
||||
if (attr === undefined || attr.value !== attributes[attrKey]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rootElements.length > 1) {
|
||||
xml.errors.push(new ParseError(
|
||||
xml.rootNodes[1].sourceSpan,
|
||||
'Unexpected root node. XLIFF 1.2 files should only have a single <xliff> root node.',
|
||||
ParseErrorLevel.WARNING));
|
||||
}
|
||||
|
||||
return {element: rootElement, errors: xml.errors};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a predicate, which can be used by things like `Array.filter()`, that will match a named
|
||||
* XML Element from a collection of XML Nodes.
|
||||
*
|
||||
* @param name The expected name of the element to match.
|
||||
*/
|
||||
export function isNamedElement(name: string): (node: Node) => node is Element {
|
||||
function predicate(node: Node): node is Element {
|
||||
return node instanceof Element && node.name === name;
|
||||
}
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an XML parser related message to the given `diagnostics` object.
|
||||
*/
|
||||
export function addParseDiagnostic(
|
||||
diagnostics: Diagnostics, sourceSpan: ParseSourceSpan, message: string,
|
||||
level: ParseErrorLevel): void {
|
||||
addParseError(diagnostics, new ParseError(sourceSpan, message, level));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the formatted error message from the given `parseError` object into the given `diagnostics`
|
||||
* object.
|
||||
*/
|
||||
export function addParseError(diagnostics: Diagnostics, parseError: ParseError): void {
|
||||
if (parseError.level === ParseErrorLevel.ERROR) {
|
||||
diagnostics.error(parseError.toString());
|
||||
} else {
|
||||
diagnostics.warn(parseError.toString());
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,16 @@
|
||||
* 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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
|
||||
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
|
||||
import {extname} from 'path';
|
||||
import {Element, ParseErrorLevel, visitAll} from '@angular/compiler';
|
||||
import {ɵParsedTranslation} from '@angular/localize';
|
||||
|
||||
import {Diagnostics} from '../../../diagnostics';
|
||||
import {BaseVisitor} from '../base_visitor';
|
||||
import {MessageSerializer} from '../message_serialization/message_serializer';
|
||||
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
||||
|
||||
import {TranslationParseError} from './translation_parse_error';
|
||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
|
||||
|
||||
const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
|
||||
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, isNamedElement, parseInnerRange} from './translation_utils';
|
||||
|
||||
/**
|
||||
* A translation parser that can load XLIFF 1.2 files.
|
||||
@ -26,67 +23,132 @@ const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
|
||||
* http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||
*
|
||||
*/
|
||||
export class Xliff1TranslationParser implements TranslationParser {
|
||||
canParse(filePath: string, contents: string): boolean {
|
||||
return (extname(filePath) === '.xlf') && XLIFF_1_2_NS_REGEX.test(contents);
|
||||
export class Xliff1TranslationParser implements TranslationParser<XmlTranslationParserHint> {
|
||||
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
|
||||
return canParseXml(filePath, contents, 'xliff', {version: '1.2'});
|
||||
}
|
||||
|
||||
parse(filePath: string, contents: string): ParsedTranslationBundle {
|
||||
const xmlParser = new XmlParser();
|
||||
const xml = xmlParser.parse(contents, filePath);
|
||||
const bundle = XliffFileElementVisitor.extractBundle(xml.rootNodes);
|
||||
if (bundle === undefined) {
|
||||
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
|
||||
ParsedTranslationBundle {
|
||||
if (hint) {
|
||||
return this.extractBundle(hint);
|
||||
} else {
|
||||
return this.extractBundleDeprecated(filePath, contents);
|
||||
}
|
||||
}
|
||||
|
||||
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
|
||||
const diagnostics = new Diagnostics();
|
||||
errors.forEach(e => addParseError(diagnostics, e));
|
||||
|
||||
if (element.children.length === 0) {
|
||||
addParseDiagnostic(
|
||||
diagnostics, element.sourceSpan, 'Missing expected <file> element',
|
||||
ParseErrorLevel.WARNING);
|
||||
return {locale: undefined, translations: {}, diagnostics};
|
||||
}
|
||||
|
||||
const files = element.children.filter(isNamedElement('file'));
|
||||
if (files.length === 0) {
|
||||
addParseDiagnostic(
|
||||
diagnostics, element.sourceSpan, 'No <file> elements found in <xliff>',
|
||||
ParseErrorLevel.WARNING);
|
||||
} else if (files.length > 1) {
|
||||
addParseDiagnostic(
|
||||
diagnostics, files[1].sourceSpan, 'More than one <file> element found in <xliff>',
|
||||
ParseErrorLevel.WARNING);
|
||||
}
|
||||
|
||||
const bundle: ParsedTranslationBundle = {locale: undefined, translations: {}, diagnostics};
|
||||
const translationVisitor = new XliffTranslationVisitor();
|
||||
const localesFound = new Set<string>();
|
||||
for (const file of files) {
|
||||
const locale = getAttribute(file, 'target-language');
|
||||
if (locale !== undefined) {
|
||||
localesFound.add(locale);
|
||||
bundle.locale = locale;
|
||||
}
|
||||
visitAll(translationVisitor, file.children, bundle);
|
||||
}
|
||||
|
||||
if (localesFound.size > 1) {
|
||||
addParseDiagnostic(
|
||||
diagnostics, element.sourceSpan,
|
||||
`More than one locale found in translation file: ${JSON.stringify(Array.from(localesFound))}. Using "${bundle.locale}"`,
|
||||
ParseErrorLevel.WARNING);
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private extractBundleDeprecated(filePath: string, contents: string) {
|
||||
const hint = this.canParse(filePath, contents);
|
||||
if (!hint) {
|
||||
throw new Error(`Unable to parse "${filePath}" as XLIFF 1.2 format.`);
|
||||
}
|
||||
const bundle = this.extractBundle(hint);
|
||||
if (bundle.diagnostics.hasErrors) {
|
||||
const message =
|
||||
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XLIFF 1.2 format`);
|
||||
throw new Error(message);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
class XliffFileElementVisitor extends BaseVisitor {
|
||||
private bundle: ParsedTranslationBundle|undefined;
|
||||
|
||||
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
|
||||
const visitor = new this();
|
||||
visitAll(visitor, xliff);
|
||||
return visitor.bundle;
|
||||
}
|
||||
|
||||
visitElement(element: Element): any {
|
||||
if (element.name === 'file') {
|
||||
this.bundle = {
|
||||
locale: getAttribute(element, 'target-language'),
|
||||
translations: XliffTranslationVisitor.extractTranslations(element)
|
||||
};
|
||||
} else {
|
||||
return visitAll(this, element.children);
|
||||
visitElement(fileElement: Element): any {
|
||||
if (fileElement.name === 'file') {
|
||||
return {fileElement, locale: getAttribute(fileElement, 'target-language')};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class XliffTranslationVisitor extends BaseVisitor {
|
||||
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
|
||||
|
||||
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
|
||||
const visitor = new this();
|
||||
visitAll(visitor, file.children);
|
||||
return visitor.translations;
|
||||
visitElement(element: Element, bundle: ParsedTranslationBundle): void {
|
||||
if (element.name === 'trans-unit') {
|
||||
this.visitTransUnitElement(element, bundle);
|
||||
} else {
|
||||
visitAll(this, element.children, bundle);
|
||||
}
|
||||
}
|
||||
|
||||
visitElement(element: Element): any {
|
||||
if (element.name === 'trans-unit') {
|
||||
const id = getAttrOrThrow(element, 'id');
|
||||
if (this.translations[id] !== undefined) {
|
||||
throw new TranslationParseError(
|
||||
element.sourceSpan, `Duplicated translations for message "${id}"`);
|
||||
}
|
||||
private visitTransUnitElement(element: Element, bundle: ParsedTranslationBundle): void {
|
||||
// Error if no `id` attribute
|
||||
const id = getAttribute(element, 'id');
|
||||
if (id === undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan,
|
||||
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMessage = element.children.find(isTargetElement);
|
||||
if (targetMessage === undefined) {
|
||||
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
|
||||
// Error if there is already a translation with the same id
|
||||
if (bundle.translations[id] !== undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan, `Duplicated translations for message "${id}"`,
|
||||
ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Error if there is no `<target>` child element
|
||||
const targetMessage = element.children.find(isNamedElement('target'));
|
||||
if (targetMessage === undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan, 'Missing required <target> element',
|
||||
ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
bundle.translations[id] = serializeTargetMessage(targetMessage);
|
||||
} catch (e) {
|
||||
// Capture any errors from serialize the target message
|
||||
if (e.span && e.msg && e.level) {
|
||||
addParseDiagnostic(bundle.diagnostics, e.span, e.msg, e.level);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
this.translations[id] = serializeTargetMessage(targetMessage);
|
||||
} else {
|
||||
return visitAll(this, element.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,7 +160,3 @@ function serializeTargetMessage(source: Element): ɵParsedTranslation {
|
||||
});
|
||||
return serializer.serialize(parseInnerRange(source));
|
||||
}
|
||||
|
||||
function isTargetElement(node: Node): node is Element {
|
||||
return node instanceof Element && node.name === 'target';
|
||||
}
|
||||
|
@ -5,19 +5,16 @@
|
||||
* 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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
|
||||
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
|
||||
import {extname} from 'path';
|
||||
import {Element, Node, ParseErrorLevel, visitAll} from '@angular/compiler';
|
||||
import {ɵParsedTranslation} from '@angular/localize';
|
||||
|
||||
import {Diagnostics} from '../../../diagnostics';
|
||||
import {BaseVisitor} from '../base_visitor';
|
||||
import {MessageSerializer} from '../message_serialization/message_serializer';
|
||||
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
||||
|
||||
import {TranslationParseError} from './translation_parse_error';
|
||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
|
||||
|
||||
const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
|
||||
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, isNamedElement, parseInnerRange} from './translation_utils';
|
||||
|
||||
/**
|
||||
* A translation parser that can load translations from XLIFF 2 files.
|
||||
@ -25,84 +22,126 @@ const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
|
||||
* http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
|
||||
*
|
||||
*/
|
||||
export class Xliff2TranslationParser implements TranslationParser {
|
||||
canParse(filePath: string, contents: string): boolean {
|
||||
return (extname(filePath) === '.xlf') && XLIFF_2_0_NS_REGEX.test(contents);
|
||||
export class Xliff2TranslationParser implements TranslationParser<XmlTranslationParserHint> {
|
||||
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
|
||||
return canParseXml(filePath, contents, 'xliff', {version: '2.0'});
|
||||
}
|
||||
|
||||
parse(filePath: string, contents: string): ParsedTranslationBundle {
|
||||
const xmlParser = new XmlParser();
|
||||
const xml = xmlParser.parse(contents, filePath);
|
||||
const bundle = Xliff2TranslationBundleVisitor.extractBundle(xml.rootNodes);
|
||||
if (bundle === undefined) {
|
||||
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
|
||||
ParsedTranslationBundle {
|
||||
if (hint) {
|
||||
return this.extractBundle(hint);
|
||||
} else {
|
||||
return this.extractBundleDeprecated(filePath, contents);
|
||||
}
|
||||
}
|
||||
|
||||
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
|
||||
const diagnostics = new Diagnostics();
|
||||
errors.forEach(e => addParseError(diagnostics, e));
|
||||
|
||||
const locale = getAttribute(element, 'trgLang');
|
||||
const files = element.children.filter(isFileElement);
|
||||
if (files.length === 0) {
|
||||
addParseDiagnostic(
|
||||
diagnostics, element.sourceSpan, 'No <file> elements found in <xliff>',
|
||||
ParseErrorLevel.WARNING);
|
||||
} else if (files.length > 1) {
|
||||
addParseDiagnostic(
|
||||
diagnostics, files[1].sourceSpan, 'More than one <file> element found in <xliff>',
|
||||
ParseErrorLevel.WARNING);
|
||||
}
|
||||
|
||||
const bundle = {locale, translations: {}, diagnostics};
|
||||
const translationVisitor = new Xliff2TranslationVisitor();
|
||||
for (const file of files) {
|
||||
visitAll(translationVisitor, file.children, {bundle});
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private extractBundleDeprecated(filePath: string, contents: string) {
|
||||
const hint = this.canParse(filePath, contents);
|
||||
if (!hint) {
|
||||
throw new Error(`Unable to parse "${filePath}" as XLIFF 2.0 format.`);
|
||||
}
|
||||
const bundle = this.extractBundle(hint);
|
||||
if (bundle.diagnostics.hasErrors) {
|
||||
const message =
|
||||
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XLIFF 2.0 format`);
|
||||
throw new Error(message);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
interface BundleVisitorContext {
|
||||
parsedLocale?: string;
|
||||
}
|
||||
|
||||
class Xliff2TranslationBundleVisitor extends BaseVisitor {
|
||||
private bundle: ParsedTranslationBundle|undefined;
|
||||
|
||||
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
|
||||
const visitor = new this();
|
||||
visitAll(visitor, xliff, {});
|
||||
return visitor.bundle;
|
||||
}
|
||||
|
||||
visitElement(element: Element, {parsedLocale}: BundleVisitorContext): any {
|
||||
if (element.name === 'xliff') {
|
||||
parsedLocale = getAttribute(element, 'trgLang');
|
||||
return visitAll(this, element.children, {parsedLocale});
|
||||
} else if (element.name === 'file') {
|
||||
this.bundle = {
|
||||
locale: parsedLocale,
|
||||
translations: Xliff2TranslationVisitor.extractTranslations(element)
|
||||
};
|
||||
} else {
|
||||
return visitAll(this, element.children, {parsedLocale});
|
||||
}
|
||||
}
|
||||
interface TranslationVisitorContext {
|
||||
unit?: string;
|
||||
bundle: ParsedTranslationBundle;
|
||||
}
|
||||
|
||||
class Xliff2TranslationVisitor extends BaseVisitor {
|
||||
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
|
||||
|
||||
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
|
||||
const visitor = new this();
|
||||
visitAll(visitor, file.children);
|
||||
return visitor.translations;
|
||||
}
|
||||
|
||||
visitElement(element: Element, context: any): any {
|
||||
visitElement(element: Element, {bundle, unit}: TranslationVisitorContext): any {
|
||||
if (element.name === 'unit') {
|
||||
const externalId = getAttrOrThrow(element, 'id');
|
||||
if (this.translations[externalId] !== undefined) {
|
||||
throw new TranslationParseError(
|
||||
element.sourceSpan, `Duplicated translations for message "${externalId}"`);
|
||||
}
|
||||
visitAll(this, element.children, {unit: externalId});
|
||||
this.visitUnitElement(element, bundle);
|
||||
} else if (element.name === 'segment') {
|
||||
assertTranslationUnit(element, context);
|
||||
const targetMessage = element.children.find(isTargetElement);
|
||||
if (targetMessage === undefined) {
|
||||
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
|
||||
}
|
||||
this.translations[context.unit] = serializeTargetMessage(targetMessage);
|
||||
this.visitSegmentElement(element, bundle, unit);
|
||||
} else {
|
||||
return visitAll(this, element.children);
|
||||
visitAll(this, element.children, {bundle, unit});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertTranslationUnit(segment: Element, context: any) {
|
||||
if (context === undefined || context.unit === undefined) {
|
||||
throw new TranslationParseError(
|
||||
segment.sourceSpan, 'Invalid <segment> element: should be a child of a <unit> element.');
|
||||
private visitUnitElement(element: Element, bundle: ParsedTranslationBundle): void {
|
||||
// Error if no `id` attribute
|
||||
const externalId = getAttribute(element, 'id');
|
||||
if (externalId === undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan,
|
||||
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Error if there is already a translation with the same id
|
||||
if (bundle.translations[externalId] !== undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan,
|
||||
`Duplicated translations for message "${externalId}"`, ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
visitAll(this, element.children, {bundle, unit: externalId});
|
||||
}
|
||||
|
||||
private visitSegmentElement(
|
||||
element: Element, bundle: ParsedTranslationBundle, unit: string|undefined): void {
|
||||
// A `<segment>` element must be below a `<unit>` element
|
||||
if (unit === undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan,
|
||||
'Invalid <segment> element: should be a child of a <unit> element.',
|
||||
ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetMessage = element.children.find(isNamedElement('target'));
|
||||
if (targetMessage === undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan, 'Missing required <target> element',
|
||||
ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
bundle.translations[unit] = serializeTargetMessage(targetMessage);
|
||||
} catch (e) {
|
||||
// Capture any errors from serialize the target message
|
||||
if (e.span && e.msg && e.level) {
|
||||
addParseDiagnostic(bundle.diagnostics, e.span, e.msg, e.level);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +155,6 @@ function serializeTargetMessage(source: Element): ɵParsedTranslation {
|
||||
return serializer.serialize(parseInnerRange(source));
|
||||
}
|
||||
|
||||
function isTargetElement(node: Node): node is Element {
|
||||
return node instanceof Element && node.name === 'target';
|
||||
function isFileElement(node: Node): node is Element {
|
||||
return node instanceof Element && node.name === 'file';
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* 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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
|
||||
import {Element, ParseErrorLevel, visitAll} from '@angular/compiler';
|
||||
import {ɵParsedTranslation} from '@angular/localize';
|
||||
import {extname} from 'path';
|
||||
|
||||
@ -14,83 +14,100 @@ import {BaseVisitor} from '../base_visitor';
|
||||
import {MessageSerializer} from '../message_serialization/message_serializer';
|
||||
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
|
||||
|
||||
import {TranslationParseError} from './translation_parse_error';
|
||||
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
|
||||
import {getAttrOrThrow, parseInnerRange} from './translation_utils';
|
||||
|
||||
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, parseInnerRange} from './translation_utils';
|
||||
|
||||
|
||||
/**
|
||||
* A translation parser that can load XB files.
|
||||
*/
|
||||
export class XtbTranslationParser implements TranslationParser {
|
||||
constructor(private diagnostics: Diagnostics) {}
|
||||
|
||||
canParse(filePath: string, contents: string): boolean {
|
||||
export class XtbTranslationParser implements TranslationParser<XmlTranslationParserHint> {
|
||||
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
|
||||
const extension = extname(filePath);
|
||||
return (extension === '.xtb' || extension === '.xmb') &&
|
||||
contents.includes('<translationbundle');
|
||||
if (extension !== '.xtb' && extension !== '.xmb') {
|
||||
return false;
|
||||
}
|
||||
return canParseXml(filePath, contents, 'translationbundle', {});
|
||||
}
|
||||
|
||||
parse(filePath: string, contents: string): ParsedTranslationBundle {
|
||||
const xmlParser = new XmlParser();
|
||||
const xml = xmlParser.parse(contents, filePath);
|
||||
const bundle = XtbVisitor.extractBundle(this.diagnostics, xml.rootNodes);
|
||||
if (bundle === undefined) {
|
||||
throw new Error(`Unable to parse "${filePath}" as XTB/XMB format.`);
|
||||
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
|
||||
ParsedTranslationBundle {
|
||||
if (hint) {
|
||||
return this.extractBundle(hint);
|
||||
} else {
|
||||
return this.extractBundleDeprecated(filePath, contents);
|
||||
}
|
||||
}
|
||||
|
||||
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
|
||||
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
|
||||
const bundle: ParsedTranslationBundle = {
|
||||
locale: langAttr && langAttr.value,
|
||||
translations: {},
|
||||
diagnostics: new Diagnostics()
|
||||
};
|
||||
errors.forEach(e => addParseError(bundle.diagnostics, e));
|
||||
|
||||
const bundleVisitor = new XtbVisitor();
|
||||
visitAll(bundleVisitor, element.children, bundle);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private extractBundleDeprecated(filePath: string, contents: string) {
|
||||
const hint = this.canParse(filePath, contents);
|
||||
if (!hint) {
|
||||
throw new Error(`Unable to parse "${filePath}" as XMB/XTB format.`);
|
||||
}
|
||||
const bundle = this.extractBundle(hint);
|
||||
if (bundle.diagnostics.hasErrors) {
|
||||
const message =
|
||||
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XMB/XTB format`);
|
||||
throw new Error(message);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
class XtbVisitor extends BaseVisitor {
|
||||
static extractBundle(diagnostics: Diagnostics, messageBundles: Node[]): ParsedTranslationBundle
|
||||
|undefined {
|
||||
const visitor = new this(diagnostics);
|
||||
const bundles: ParsedTranslationBundle[] = visitAll(visitor, messageBundles, undefined);
|
||||
return bundles[0];
|
||||
}
|
||||
|
||||
constructor(private diagnostics: Diagnostics) { super(); }
|
||||
|
||||
visitElement(element: Element, bundle: ParsedTranslationBundle|undefined): any {
|
||||
visitElement(element: Element, bundle: ParsedTranslationBundle): any {
|
||||
switch (element.name) {
|
||||
case 'translationbundle':
|
||||
if (bundle) {
|
||||
throw new TranslationParseError(
|
||||
element.sourceSpan, '<translationbundle> elements can not be nested');
|
||||
}
|
||||
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
|
||||
bundle = {locale: langAttr && langAttr.value, translations: {}};
|
||||
visitAll(this, element.children, bundle);
|
||||
return bundle;
|
||||
|
||||
case 'translation':
|
||||
if (!bundle) {
|
||||
throw new TranslationParseError(
|
||||
element.sourceSpan, '<translation> must be inside a <translationbundle>');
|
||||
// Error if no `id` attribute
|
||||
const id = getAttribute(element, 'id');
|
||||
if (id === undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan,
|
||||
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
const id = getAttrOrThrow(element, 'id');
|
||||
if (bundle.translations.hasOwnProperty(id)) {
|
||||
throw new TranslationParseError(
|
||||
element.sourceSpan, `Duplicated translations for message "${id}"`);
|
||||
} else {
|
||||
try {
|
||||
bundle.translations[id] = serializeTargetMessage(element);
|
||||
} catch (error) {
|
||||
if (typeof error === 'string') {
|
||||
this.diagnostics.warn(
|
||||
`Could not parse message with id "${id}" - perhaps it has an unrecognised ICU format?\n` +
|
||||
error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Error if there is already a translation with the same id
|
||||
if (bundle.translations[id] !== undefined) {
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan, `Duplicated translations for message "${id}"`,
|
||||
ParseErrorLevel.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
bundle.translations[id] = serializeTargetMessage(element);
|
||||
} catch (error) {
|
||||
if (typeof error === 'string') {
|
||||
bundle.diagnostics.warn(
|
||||
`Could not parse message with id "${id}" - perhaps it has an unrecognised ICU format?\n` +
|
||||
error);
|
||||
} else if (error.span && error.msg && error.level) {
|
||||
addParseDiagnostic(bundle.diagnostics, error.span, error.msg, error.level);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new TranslationParseError(element.sourceSpan, 'Unexpected tag');
|
||||
addParseDiagnostic(
|
||||
bundle.diagnostics, element.sourceSpan, `Unexpected <${element.name}> tag.`,
|
||||
ParseErrorLevel.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import {OutputPathFn} from './output_path';
|
||||
export interface TranslationBundle {
|
||||
locale: string;
|
||||
translations: Record<ɵMessageId, ɵParsedTranslation>;
|
||||
diagnostics?: Diagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user