upstream: Merge remote-tracking branch 'upstream/master' into merge-10.1.3
# Conflicts: # .circleci/config.yml # .github/ISSUE_TEMPLATE/1-bug-report.md # .github/ISSUE_TEMPLATE/2-feature-request.md # .github/ISSUE_TEMPLATE/5-support-request.md # .github/ISSUE_TEMPLATE/6-angular-cli.md # .github/ISSUE_TEMPLATE/7-angular-components.md # .ng-dev/commit-message.ts # CODE_OF_CONDUCT.md # CONTRIBUTING.md # README.md # aio/README.md # aio/content/guide/architecture-modules.md # aio/content/guide/architecture-next-steps.md # aio/content/guide/architecture-services.md # aio/content/guide/architecture.md # aio/content/guide/attribute-binding.md # aio/content/guide/bootstrapping.md # aio/content/guide/glossary.md # aio/content/guide/ngmodules.md # aio/content/guide/template-statements.md # aio/content/marketing/analytics.md # aio/content/marketing/docs.md # aio/content/marketing/events.html # aio/content/navigation.json # aio/content/tutorial/toh-pt4.md # aio/content/tutorial/toh-pt6.md # aio/package.json # aio/src/app/shared/ga.service.spec.ts # aio/src/app/shared/ga.service.ts # aio/src/app/shared/location.service.spec.ts # aio/tests/e2e/src/onerror.e2e-spec.ts # aio/yarn.lock
This commit is contained in:
@ -5,8 +5,7 @@ exports_files([
|
||||
"tsconfig.json",
|
||||
])
|
||||
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_config")
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("//tools:defaults.bzl", "ts_config", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "types",
|
||||
|
@ -72,7 +72,7 @@ export class CssKeyframesDriver implements AnimationDriver {
|
||||
keyframeStr += `}\n`;
|
||||
|
||||
const kfElm = document.createElement('style');
|
||||
kfElm.innerHTML = keyframeStr;
|
||||
kfElm.textContent = keyframeStr;
|
||||
return kfElm;
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ describe('CssKeyframesDriver tests', () => {
|
||||
|
||||
it('should animate until the `animationend` method is emitted, but stil retain the <style> method and the element animation details',
|
||||
fakeAsync(() => {
|
||||
// IE10 and IE11 cannot create an instanceof AnimationEvent
|
||||
// IE11 cannot create an instanceof AnimationEvent
|
||||
if (!supportsAnimationEventCreation()) return;
|
||||
|
||||
const elm = createElement();
|
||||
|
@ -195,7 +195,7 @@ const EMPTY_FN = () => {};
|
||||
});
|
||||
|
||||
it('should fire the onDone method when the matching animationend event is emitted', () => {
|
||||
// IE10 and IE11 cannot create an instanceof AnimationEvent
|
||||
// IE11 cannot create an instanceof AnimationEvent
|
||||
if (!supportsAnimationEventCreation()) return;
|
||||
|
||||
const element = createElement();
|
||||
|
@ -1,3 +1,4 @@
|
||||
# BEGIN-DEV-ONLY
|
||||
load("//tools:defaults.bzl", "pkg_npm")
|
||||
|
||||
pkg_npm(
|
||||
@ -17,9 +18,8 @@ pkg_npm(
|
||||
],
|
||||
substitutions = {
|
||||
"(#|\/\/)\\s+BEGIN-DEV-ONLY[\\w\W]+?(#|\/\/)\\s+END-DEV-ONLY": "",
|
||||
"//packages/bazel/src/ngc-wrapped": "@npm//@angular/bazel/bin:ngc-wrapped",
|
||||
"//packages/bazel/": "//",
|
||||
"angular/packages/bazel/": "npm_angular_bazel/",
|
||||
"//packages/bazel/": "//@angular/bazel/",
|
||||
"@npm//@bazel/typescript/internal:": "//@bazel/typescript/internal:",
|
||||
},
|
||||
tags = ["release-with-framework"],
|
||||
# Do not add more to this list.
|
||||
@ -31,3 +31,4 @@ pkg_npm(
|
||||
"//packages/bazel/src/ngc-wrapped:ngc_lib",
|
||||
],
|
||||
)
|
||||
# END-DEV-ONLY
|
||||
|
@ -29,11 +29,11 @@ def rules_angular_dev_dependencies():
|
||||
_maybe(
|
||||
http_archive,
|
||||
name = "bazel_toolchains",
|
||||
sha256 = "db48eed61552e25d36fe051a65d2a329cc0fb08442627e8f13960c5ab087a44e",
|
||||
strip_prefix = "bazel-toolchains-3.2.0",
|
||||
sha256 = "4fb3ceea08101ec41208e3df9e56ec72b69f3d11c56629d6477c0ff88d711cf7",
|
||||
strip_prefix = "bazel-toolchains-3.6.0",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/3.2.0/bazel-toolchains-3.2.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-toolchains/releases/download/3.2.0/bazel-toolchains-3.2.0.tar.gz",
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/3.6.0/bazel-toolchains-3.6.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-toolchains/releases/download/3.6.0/bazel-toolchains-3.6.0.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -19,12 +19,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bazelWorkspaces": {
|
||||
"npm_angular_bazel": {
|
||||
"version": "0.0.0-PLACEHOLDER",
|
||||
"rootPath": "."
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/api-extractor": "^7.7.13",
|
||||
"shelljs": "0.8.2",
|
||||
@ -34,7 +28,7 @@
|
||||
"@angular/compiler-cli": "0.0.0-PLACEHOLDER",
|
||||
"@bazel/typescript": ">=1.0.0",
|
||||
"terser": "^4.3.1",
|
||||
"typescript": ">=3.9 <4.0",
|
||||
"typescript": ">=4.0 <4.1",
|
||||
"rollup": ">=1.20.0",
|
||||
"rollup-plugin-commonjs": ">=9.0.0",
|
||||
"rollup-plugin-node-resolve": ">=4.2.0",
|
||||
|
@ -1,3 +1,4 @@
|
||||
# BEGIN-DEV-ONLY
|
||||
package(default_visibility = ["//packages/bazel:__subpackages__"])
|
||||
|
||||
filegroup(
|
||||
@ -22,3 +23,4 @@ nodejs_binary(
|
||||
node_modules = ":empty_node_modules",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
# END-DEV-ONLY
|
||||
|
@ -1,9 +1,8 @@
|
||||
# BEGIN-DEV-ONLY
|
||||
package(default_visibility = ["//packages:__subpackages__"])
|
||||
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
|
||||
# BEGIN-DEV-ONLY
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
|
||||
ts_library(
|
||||
name = "lib",
|
||||
@ -17,7 +16,6 @@ ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
# END-DEV-ONLY
|
||||
nodejs_binary(
|
||||
name = "api_extractor",
|
||||
data = [
|
||||
@ -33,3 +31,4 @@ filegroup(
|
||||
name = "package_assets",
|
||||
srcs = ["BUILD.bazel"],
|
||||
)
|
||||
# END-DEV-ONLY
|
||||
|
@ -2,11 +2,13 @@
|
||||
"""
|
||||
|
||||
load(
|
||||
"@npm_bazel_typescript//internal:build_defs.bzl",
|
||||
# Replaced with "//@bazel/typescript/internal:..." in published package
|
||||
"@npm//@bazel/typescript/internal:build_defs.bzl",
|
||||
_tsc_wrapped_tsconfig = "tsc_wrapped_tsconfig",
|
||||
)
|
||||
load(
|
||||
"@npm_bazel_typescript//internal:common/compilation.bzl",
|
||||
# Replaced with "//@bazel/typescript/internal:..." in published package
|
||||
"@npm//@bazel/typescript/internal:common/compilation.bzl",
|
||||
_COMMON_ATTRIBUTES = "COMMON_ATTRIBUTES",
|
||||
_COMMON_OUTPUTS = "COMMON_OUTPUTS",
|
||||
_DEPS_ASPECTS = "DEPS_ASPECTS",
|
||||
@ -14,7 +16,8 @@ load(
|
||||
_ts_providers_dict_to_struct = "ts_providers_dict_to_struct",
|
||||
)
|
||||
load(
|
||||
"@npm_bazel_typescript//internal:ts_config.bzl",
|
||||
# Replaced with "//@bazel/typescript/internal:..." in published package
|
||||
"@npm//@bazel/typescript/internal:ts_config.bzl",
|
||||
_TsConfigInfo = "TsConfigInfo",
|
||||
)
|
||||
load(
|
||||
@ -22,6 +25,7 @@ load(
|
||||
_LinkablePackageInfo = "LinkablePackageInfo",
|
||||
_NpmPackageInfo = "NpmPackageInfo",
|
||||
_js_ecma_script_module_info = "js_ecma_script_module_info",
|
||||
_js_module_info = "js_module_info",
|
||||
_js_named_module_info = "js_named_module_info",
|
||||
_node_modules_aspect = "node_modules_aspect",
|
||||
)
|
||||
@ -42,10 +46,26 @@ ts_providers_dict_to_struct = _ts_providers_dict_to_struct
|
||||
# is loaded differently anyways where this file is overridden.
|
||||
BuildSettingInfo = provider(doc = "Not used outside google3.")
|
||||
|
||||
DEFAULT_API_EXTRACTOR = "@npm//@angular/bazel/bin:api-extractor"
|
||||
DEFAULT_NG_COMPILER = "@npm//@angular/bazel/bin:ngc-wrapped"
|
||||
DEFAULT_NG_XI18N = "@npm//@angular/bazel/bin:xi18n"
|
||||
DEFAULT_API_EXTRACTOR = (
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//@angular/bazel/bin:api-extractor"
|
||||
)
|
||||
DEFAULT_NG_COMPILER = (
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//@angular/bazel/bin:ngc-wrapped"
|
||||
)
|
||||
DEFAULT_NG_XI18N = (
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//@angular/bazel/bin:xi18n"
|
||||
)
|
||||
FLAT_DTS_FILE_SUFFIX = ".bundle.d.ts"
|
||||
TsConfigInfo = _TsConfigInfo
|
||||
js_ecma_script_module_info = _js_ecma_script_module_info
|
||||
js_module_info = _js_module_info
|
||||
js_named_module_info = _js_named_module_info
|
||||
|
@ -19,6 +19,7 @@ load(
|
||||
"TsConfigInfo",
|
||||
"compile_ts",
|
||||
"js_ecma_script_module_info",
|
||||
"js_module_info",
|
||||
"js_named_module_info",
|
||||
"node_modules_aspect",
|
||||
"ts_providers_dict_to_struct",
|
||||
@ -47,14 +48,6 @@ def is_ivy_enabled(ctx):
|
||||
ctx.attr._renderer[BuildSettingInfo].value == "ivy")):
|
||||
return True
|
||||
|
||||
# TODO(josephperott): Remove after ~Feb 2020, to allow local script migrations
|
||||
if "compile" in ctx.var and ctx.workspace_name == "angular":
|
||||
fail(
|
||||
msg = "Setting ViewEngine/Ivy using --define=compile is deprecated, please use " +
|
||||
"--config=ivy or --config=view-engine instead.",
|
||||
attr = "ng_module",
|
||||
)
|
||||
|
||||
# This attribute is only defined in google's private ng_module rule and not
|
||||
# available externally. For external users, this is effectively a no-op.
|
||||
if hasattr(ctx.attr, "ivy") and ctx.attr.ivy == True:
|
||||
@ -644,6 +637,10 @@ def _ng_module_impl(ctx):
|
||||
# See design doc https://docs.google.com/document/d/1ggkY5RqUkVL4aQLYm7esRW978LgX3GUCnQirrk5E1C0/edit#
|
||||
# and issue https://github.com/bazelbuild/rules_nodejs/issues/57 for more details.
|
||||
ts_providers["providers"].extend([
|
||||
js_module_info(
|
||||
sources = ts_providers["typescript"]["es5_sources"],
|
||||
deps = ctx.attr.deps,
|
||||
),
|
||||
js_named_module_info(
|
||||
sources = ts_providers["typescript"]["es5_sources"],
|
||||
deps = ctx.attr.deps,
|
||||
@ -701,12 +698,9 @@ NG_MODULE_ATTRIBUTES = {
|
||||
"compiler": attr.label(
|
||||
doc = """Sets a different ngc compiler binary to use for this library.
|
||||
|
||||
The default ngc compiler depends on the `@npm//@angular/bazel`
|
||||
The default ngc compiler depends on the `//@angular/bazel`
|
||||
target which is setup for projects that use bazel managed npm deps that
|
||||
fetch the @angular/bazel npm package. It is recommended that you use
|
||||
the workspace name `@npm` for bazel managed deps so the default
|
||||
compiler works out of the box. Otherwise, you'll have to override
|
||||
the compiler attribute manually.
|
||||
fetch the @angular/bazel npm package.
|
||||
""",
|
||||
default = Label(DEFAULT_NG_COMPILER),
|
||||
executable = True,
|
||||
@ -725,14 +719,11 @@ NG_MODULE_RULE_ATTRS = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
|
||||
"node_modules": attr.label(
|
||||
doc = """The npm packages which should be available during the compile.
|
||||
|
||||
The default value of `@npm//typescript:typescript__typings` is
|
||||
for projects that use bazel managed npm deps. It is recommended
|
||||
that you use the workspace name `@npm` for bazel managed deps so the
|
||||
default value works out of the box. Otherwise, you'll have to
|
||||
override the node_modules attribute manually. This default is in place
|
||||
The default value of `//typescript:typescript__typings` is
|
||||
for projects that use bazel managed npm deps. This default is in place
|
||||
since code compiled by ng_module will always depend on at least the
|
||||
typescript default libs which are provided by
|
||||
`@npm//typescript:typescript__typings`.
|
||||
`//typescript:typescript__typings`.
|
||||
|
||||
This attribute is DEPRECATED. As of version 0.18.0 the recommended
|
||||
approach to npm dependencies is to use fine grained npm dependencies
|
||||
@ -784,7 +775,12 @@ NG_MODULE_RULE_ATTRS = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
|
||||
yarn_lock = "//:yarn.lock",
|
||||
)
|
||||
""",
|
||||
default = Label("@npm//typescript:typescript__typings"),
|
||||
default = Label(
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//typescript:typescript__typings",
|
||||
),
|
||||
),
|
||||
"entry_point": attr.label(allow_single_file = True),
|
||||
|
||||
|
@ -2,8 +2,46 @@ package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
|
||||
nodejs_binary(
|
||||
name = "rollup_for_ng_package",
|
||||
data = [
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//rollup",
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//rollup-plugin-commonjs",
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//rollup-plugin-node-resolve",
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//rollup-plugin-sourcemaps",
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//typescript",
|
||||
],
|
||||
entry_point = (
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//:node_modules/rollup/dist/bin/rollup"
|
||||
),
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"ng_package.bzl",
|
||||
"rollup.config.js",
|
||||
"terser_config.default.json",
|
||||
])
|
||||
|
||||
# BEGIN-DEV-ONLY
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "lib",
|
||||
@ -16,34 +54,6 @@ ts_library(
|
||||
],
|
||||
)
|
||||
|
||||
# END-DEV-ONLY
|
||||
nodejs_binary(
|
||||
name = "packager",
|
||||
data = [
|
||||
"lib",
|
||||
"@npm//shelljs",
|
||||
],
|
||||
entry_point = ":packager.ts",
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "rollup_for_ng_package",
|
||||
data = [
|
||||
"@npm//rollup",
|
||||
"@npm//rollup-plugin-commonjs",
|
||||
"@npm//rollup-plugin-node-resolve",
|
||||
"@npm//rollup-plugin-sourcemaps",
|
||||
"@npm//typescript",
|
||||
],
|
||||
entry_point = "@npm//:node_modules/rollup/dist/bin/rollup",
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"ng_package.bzl",
|
||||
"rollup.config.js",
|
||||
"terser_config.default.json",
|
||||
])
|
||||
|
||||
filegroup(
|
||||
name = "package_assets",
|
||||
srcs = glob(["*.bzl"]) + [
|
||||
@ -52,3 +62,13 @@ filegroup(
|
||||
"terser_config.default.json",
|
||||
],
|
||||
)
|
||||
|
||||
nodejs_binary(
|
||||
name = "packager",
|
||||
data = [
|
||||
"lib",
|
||||
"@npm//shelljs",
|
||||
],
|
||||
entry_point = ":packager.ts",
|
||||
)
|
||||
# END-DEV-ONLY
|
||||
|
@ -28,11 +28,16 @@ def _debug(vars, *args):
|
||||
if "VERBOSE_LOGS" in vars.keys():
|
||||
print("[ng_package.bzl]", args)
|
||||
|
||||
_DEFAULT_NG_PACKAGER = "@npm//@angular/bazel/bin:packager"
|
||||
_DEFAULT_ROLLUP_CONFIG_TMPL = "@npm_angular_bazel//src/ng_package:rollup.config.js"
|
||||
_DEFALUT_TERSER_CONFIG_FILE = "@npm_angular_bazel//src/ng_package:terser_config.default.json"
|
||||
_DEFAULT_ROLLUP = "@npm_angular_bazel//src/ng_package:rollup_for_ng_package"
|
||||
_DEFAULT_TERSER = "@npm//terser/bin:terser"
|
||||
_DEFAULT_NG_PACKAGER = "//@angular/bazel/bin:packager"
|
||||
_DEFAULT_ROLLUP_CONFIG_TMPL = "//:node_modules/@angular/bazel/src/ng_package/rollup.config.js"
|
||||
_DEFALUT_TERSER_CONFIG_FILE = "//:node_modules/@angular/bazel/src/ng_package/terser_config.default.json"
|
||||
_DEFAULT_ROLLUP = "//@angular/bazel/src/ng_package:rollup_for_ng_package"
|
||||
_DEFAULT_TERSER = (
|
||||
# BEGIN-DEV-ONLY
|
||||
"@npm" +
|
||||
# END-DEV-ONLY
|
||||
"//terser/bin:terser"
|
||||
)
|
||||
|
||||
_NG_PACKAGE_MODULE_MAPPINGS_ATTR = "ng_package_module_mappings"
|
||||
|
||||
@ -102,8 +107,6 @@ WELL_KNOWN_GLOBALS = {p: _global_name(p) for p in [
|
||||
"@angular/core",
|
||||
"@angular/platform-server/testing",
|
||||
"@angular/platform-server",
|
||||
"@angular/platform-webworker-dynamic",
|
||||
"@angular/platform-webworker",
|
||||
"@angular/common/testing",
|
||||
"@angular/common",
|
||||
"@angular/common/http/testing",
|
||||
|
@ -1,5 +1,6 @@
|
||||
# BEGIN-DEV-ONLY
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_library")
|
||||
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "ngc_lib",
|
||||
@ -15,11 +16,7 @@ ts_library(
|
||||
"//packages/bazel/test/ngc-wrapped:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
# BEGIN-INTERNAL
|
||||
# Only needed when compiling within the Angular repo.
|
||||
# Users will get this dependency from node_modules.
|
||||
"//packages/compiler-cli",
|
||||
# END-INTERNAL
|
||||
"@npm//@bazel/typescript",
|
||||
"@npm//@types/node",
|
||||
"@npm//tsickle",
|
||||
@ -55,3 +52,4 @@ filegroup(
|
||||
srcs = ["BUILD.bazel"],
|
||||
visibility = ["//packages/bazel:__subpackages__"],
|
||||
)
|
||||
# END-DEV-ONLY
|
||||
|
@ -107,7 +107,6 @@ export declare enum Style {
|
||||
*/
|
||||
export declare enum ViewEncapsulation {
|
||||
Emulated = 'Emulated',
|
||||
Native = 'Native',
|
||||
None = 'None',
|
||||
ShadowDom = 'ShadowDom'
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ jasmine_node_test(
|
||||
"//packages/bazel/test/ngc-wrapped/empty:empty_tsconfig.json",
|
||||
"//packages/bazel/test/ngc-wrapped/empty:tsconfig.json",
|
||||
"//packages/private/testing",
|
||||
"@npm_bazel_typescript//third_party/github.com/bazelbuild/bazel/src/main/protobuf:worker_protocol.proto",
|
||||
"@npm//@bazel/typescript/third_party/github.com/bazelbuild/bazel/src/main/protobuf:worker_protocol.proto",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -3,9 +3,11 @@ licenses(["notice"])
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# BEGIN-DEV-ONLY
|
||||
filegroup(
|
||||
name = "package_assets",
|
||||
srcs = glob(["*"]),
|
||||
)
|
||||
# END-DEV-ONLY
|
||||
|
||||
exports_files(["worker_protocol.proto"])
|
||||
|
@ -11,8 +11,7 @@ const path = require('path');
|
||||
module.exports = {
|
||||
baseDir: '../',
|
||||
goldenFile: '../goldens/circular-deps/packages.json',
|
||||
// The test should not capture deprecated packages such as `http`, or the `webworker` platform.
|
||||
glob: `./!(http|platform-webworker|platform-webworker-dynamic)/**/*.ts`,
|
||||
glob: `./**/*.ts`,
|
||||
// Command that will be displayed if the golden needs to be updated.
|
||||
approveCommand: 'yarn ts-circular-deps:approve',
|
||||
resolveModule: resolveModule
|
||||
|
@ -2448,6 +2448,8 @@ export class HttpClient {
|
||||
*/
|
||||
put<T>(url: string, body: any|null, options: {
|
||||
headers?: HttpHeaders|{[header: string]: string | string[]}, observe: 'events',
|
||||
params?: HttpParams|{[param: string]: string | string[]},
|
||||
reportProgress?: boolean,
|
||||
responseType?: 'json',
|
||||
withCredentials?: boolean,
|
||||
}): Observable<HttpEvent<T>>;
|
||||
@ -2526,8 +2528,8 @@ export class HttpClient {
|
||||
}): Observable<HttpResponse<Object>>;
|
||||
|
||||
/**
|
||||
* Constructs a `PUT` request that interprets the body as a JSON object and returns the full HTTP
|
||||
* response.
|
||||
* Constructs a `PUT` request that interprets the body as an instance of the requested type and
|
||||
* returns the full HTTP response.
|
||||
*
|
||||
* @param url The endpoint URL.
|
||||
* @param body The resources to add/update.
|
||||
@ -2545,14 +2547,14 @@ export class HttpClient {
|
||||
}): Observable<HttpResponse<T>>;
|
||||
|
||||
/**
|
||||
* Constructs a `PUT` request that interprets the body as a JSON object and returns the response
|
||||
* body as a JSON object.
|
||||
* Constructs a `PUT` request that interprets the body as a JSON object
|
||||
* and returns an observable of JSON object.
|
||||
*
|
||||
* @param url The endpoint URL.
|
||||
* @param body The resources to add/update.
|
||||
* @param options HTTP options
|
||||
*
|
||||
* @return An `Observable` of the response, with the response body as a JSON object.
|
||||
* @return An `Observable` of the response as a JSON object.
|
||||
*/
|
||||
put(url: string, body: any|null, options?: {
|
||||
headers?: HttpHeaders|{[header: string]: string | string[]},
|
||||
@ -2564,15 +2566,14 @@ export class HttpClient {
|
||||
}): Observable<Object>;
|
||||
|
||||
/**
|
||||
* Constructs a `PUT` request that interprets the body as a JSON object
|
||||
* and returns an observable of the response.
|
||||
* Constructs a `PUT` request that interprets the body as an instance of the requested type
|
||||
* and returns an observable of the requested type.
|
||||
*
|
||||
* @param url The endpoint URL.
|
||||
* @param body The resources to add/update.
|
||||
* @param options HTTP options
|
||||
*
|
||||
* @return An `Observable` of the `HTTPResponse` for the request, with a response body in the
|
||||
* requested type.
|
||||
* @return An `Observable` of the requested type.
|
||||
*/
|
||||
put<T>(url: string, body: any|null, options?: {
|
||||
headers?: HttpHeaders|{[header: string]: string | string[]},
|
||||
|
@ -79,9 +79,10 @@ export class HttpXhrBackend implements HttpBackend {
|
||||
*/
|
||||
handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
|
||||
// Quick check to give a better error message when a user attempts to use
|
||||
// HttpClient.jsonp() without installing the JsonpClientModule
|
||||
// HttpClient.jsonp() without installing the HttpClientJsonpModule
|
||||
if (req.method === 'JSONP') {
|
||||
throw new Error(`Attempted to construct Jsonp request without JsonpClientModule installed.`);
|
||||
throw new Error(
|
||||
`Attempted to construct Jsonp request without HttpClientJsonpModule installed.`);
|
||||
}
|
||||
|
||||
// Everything happens on Observable subscription.
|
||||
|
@ -31,6 +31,13 @@ import {toArray} from 'rxjs/operators';
|
||||
});
|
||||
backend.expectOne('/test').flush({'data': 'hello world'});
|
||||
});
|
||||
it('should allow flushing requests with a boolean value', (done: DoneFn) => {
|
||||
client.get('/test').subscribe(res => {
|
||||
expect((res as any)).toEqual(true);
|
||||
done();
|
||||
});
|
||||
backend.expectOne('/test').flush(true);
|
||||
});
|
||||
it('for text data', done => {
|
||||
client.get('/test', {responseType: 'text'}).subscribe(res => {
|
||||
expect(res).toEqual('hello world');
|
||||
|
@ -40,11 +40,14 @@ export class TestRequest {
|
||||
*
|
||||
* Both successful and unsuccessful responses can be delivered via `flush()`.
|
||||
*/
|
||||
flush(body: ArrayBuffer|Blob|string|number|Object|(string|number|Object|null)[]|null, opts: {
|
||||
headers?: HttpHeaders|{[name: string]: string | string[]},
|
||||
status?: number,
|
||||
statusText?: string,
|
||||
} = {}): void {
|
||||
flush(
|
||||
body: ArrayBuffer|Blob|boolean|string|number|Object|(boolean|string|number|Object|null)[]|
|
||||
null,
|
||||
opts: {
|
||||
headers?: HttpHeaders|{[name: string]: string | string[]},
|
||||
status?: number,
|
||||
statusText?: string,
|
||||
} = {}): void {
|
||||
if (this.cancelled) {
|
||||
throw new Error(`Cannot flush a cancelled request.`);
|
||||
}
|
||||
@ -146,7 +149,8 @@ function _toBlob(body: ArrayBuffer|Blob|string|number|Object|
|
||||
* Helper function to convert a response body to JSON data.
|
||||
*/
|
||||
function _toJsonBody(
|
||||
body: ArrayBuffer|Blob|string|number|Object|(string | number | Object | null)[],
|
||||
body: ArrayBuffer|Blob|boolean|string|number|Object|
|
||||
(boolean | string | number | Object | null)[],
|
||||
format: string = 'JSON'): Object|string|number|(Object | string | number)[] {
|
||||
if (typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer) {
|
||||
throw new Error(`Automatic conversion to ${format} is not supported for ArrayBuffers.`);
|
||||
@ -155,7 +159,7 @@ function _toJsonBody(
|
||||
throw new Error(`Automatic conversion to ${format} is not supported for Blobs.`);
|
||||
}
|
||||
if (typeof body === 'string' || typeof body === 'number' || typeof body === 'object' ||
|
||||
Array.isArray(body)) {
|
||||
typeof body === 'boolean' || Array.isArray(body)) {
|
||||
return body;
|
||||
}
|
||||
throw new Error(`Automatic conversion to ${format} is not supported for response type.`);
|
||||
|
@ -20,7 +20,7 @@ export default [
|
||||
[['a', 'p'], ['AM', 'PM'], u],
|
||||
[['AM', 'PM'], u, u],
|
||||
[
|
||||
['D', 'L', 'M', 'M', 'H', 'B', 'S'], ['Dom', 'Lun', 'Mar', 'Mks', 'Hu', 'Bi', 'Sa'],
|
||||
['D', 'L', 'M', 'M', 'H', 'B', 'S'], ['Dom', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'],
|
||||
['Domingo', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado'],
|
||||
['Dom', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab']
|
||||
],
|
||||
@ -33,8 +33,15 @@ export default [
|
||||
'Oktubre', 'Nobyembre', 'Disyembre'
|
||||
]
|
||||
],
|
||||
u,
|
||||
[['WK', 'KP'], u, u],
|
||||
[
|
||||
['E', 'P', 'M', 'A', 'M', 'H', 'H', 'A', 'S', 'O', 'N', 'D'],
|
||||
['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'],
|
||||
[
|
||||
'Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre',
|
||||
'Oktubre', 'Nobyembre', 'Disyembre'
|
||||
]
|
||||
],
|
||||
[['BC', 'KP'], u, ['Sa Wala Pa Si Kristo', 'Anno Domini']],
|
||||
0,
|
||||
[6, 0],
|
||||
['M/d/yy', 'MMM d, y', 'MMMM d, y', 'EEEE, MMMM d, y'],
|
||||
|
@ -1917,6 +1917,16 @@ export const locale_eu = [
|
||||
['ig.', 'al.', 'ar.', 'az.', 'og.', 'or.', 'lr.']
|
||||
],
|
||||
u,
|
||||
[
|
||||
['U', 'O', 'M', 'A', 'M', 'E', 'U', 'A', 'I', 'U', 'A', 'A'],
|
||||
[
|
||||
'urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'
|
||||
],
|
||||
[
|
||||
'urtarrilak', 'otsailak', 'martxoak', 'apirilak', 'maiatzak', 'ekainak', 'uztailak',
|
||||
'abuztuak', 'irailak', 'urriak', 'azaroak', 'abenduak'
|
||||
]
|
||||
],
|
||||
[
|
||||
['U', 'O', 'M', 'A', 'M', 'E', 'U', 'A', 'I', 'U', 'A', 'A'],
|
||||
[
|
||||
@ -1927,7 +1937,6 @@ export const locale_eu = [
|
||||
'iraila', 'urria', 'azaroa', 'abendua'
|
||||
]
|
||||
],
|
||||
u,
|
||||
[['K.a.', 'K.o.'], u, ['K.a.', 'Kristo ondoren']],
|
||||
1,
|
||||
[6, 0],
|
||||
@ -4031,7 +4040,7 @@ export const locale_ne = [
|
||||
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'],
|
||||
['{1}, {0}', u, '{1} {0}', u],
|
||||
['.', ',', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'],
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
['#,##,##0.###', '#,##,##0%', '¤ #,##,##0.00', '#E0'],
|
||||
'NPR',
|
||||
'नेरू',
|
||||
'नेपाली रूपैयाँ',
|
||||
|
@ -26,6 +26,16 @@ export default [
|
||||
['ig.', 'al.', 'ar.', 'az.', 'og.', 'or.', 'lr.']
|
||||
],
|
||||
u,
|
||||
[
|
||||
['U', 'O', 'M', 'A', 'M', 'E', 'U', 'A', 'I', 'U', 'A', 'A'],
|
||||
[
|
||||
'urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'
|
||||
],
|
||||
[
|
||||
'urtarrilak', 'otsailak', 'martxoak', 'apirilak', 'maiatzak', 'ekainak', 'uztailak',
|
||||
'abuztuak', 'irailak', 'urriak', 'azaroak', 'abenduak'
|
||||
]
|
||||
],
|
||||
[
|
||||
['U', 'O', 'M', 'A', 'M', 'E', 'U', 'A', 'I', 'U', 'A', 'A'],
|
||||
[
|
||||
@ -36,7 +46,6 @@ export default [
|
||||
'iraila', 'urria', 'azaroa', 'abendua'
|
||||
]
|
||||
],
|
||||
u,
|
||||
[['K.a.', 'K.o.'], u, ['K.a.', 'Kristo ondoren']],
|
||||
1,
|
||||
[6, 0],
|
||||
|
@ -22,7 +22,7 @@ global.ng.common.locales['ceb'] = [
|
||||
[['a', 'p'], ['AM', 'PM'], u],
|
||||
[['AM', 'PM'], u, u],
|
||||
[
|
||||
['D', 'L', 'M', 'M', 'H', 'B', 'S'], ['Dom', 'Lun', 'Mar', 'Mks', 'Hu', 'Bi', 'Sa'],
|
||||
['D', 'L', 'M', 'M', 'H', 'B', 'S'], ['Dom', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab'],
|
||||
['Domingo', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado'],
|
||||
['Dom', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab']
|
||||
],
|
||||
@ -35,8 +35,15 @@ global.ng.common.locales['ceb'] = [
|
||||
'Oktubre', 'Nobyembre', 'Disyembre'
|
||||
]
|
||||
],
|
||||
u,
|
||||
[['WK', 'KP'], u, u],
|
||||
[
|
||||
['E', 'P', 'M', 'A', 'M', 'H', 'H', 'A', 'S', 'O', 'N', 'D'],
|
||||
['Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago', 'Set', 'Okt', 'Nob', 'Dis'],
|
||||
[
|
||||
'Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo', 'Agosto', 'Setyembre',
|
||||
'Oktubre', 'Nobyembre', 'Disyembre'
|
||||
]
|
||||
],
|
||||
[['BC', 'KP'], u, ['Sa Wala Pa Si Kristo', 'Anno Domini']],
|
||||
0,
|
||||
[6, 0],
|
||||
['M/d/yy', 'MMM d, y', 'MMMM d, y', 'EEEE, MMMM d, y'],
|
||||
|
@ -28,6 +28,16 @@ global.ng.common.locales['eu'] = [
|
||||
['ig.', 'al.', 'ar.', 'az.', 'og.', 'or.', 'lr.']
|
||||
],
|
||||
u,
|
||||
[
|
||||
['U', 'O', 'M', 'A', 'M', 'E', 'U', 'A', 'I', 'U', 'A', 'A'],
|
||||
[
|
||||
'urt.', 'ots.', 'mar.', 'api.', 'mai.', 'eka.', 'uzt.', 'abu.', 'ira.', 'urr.', 'aza.', 'abe.'
|
||||
],
|
||||
[
|
||||
'urtarrilak', 'otsailak', 'martxoak', 'apirilak', 'maiatzak', 'ekainak', 'uztailak',
|
||||
'abuztuak', 'irailak', 'urriak', 'azaroak', 'abenduak'
|
||||
]
|
||||
],
|
||||
[
|
||||
['U', 'O', 'M', 'A', 'M', 'E', 'U', 'A', 'I', 'U', 'A', 'A'],
|
||||
[
|
||||
@ -38,7 +48,6 @@ global.ng.common.locales['eu'] = [
|
||||
'iraila', 'urria', 'azaroa', 'abendua'
|
||||
]
|
||||
],
|
||||
u,
|
||||
[['K.a.', 'K.o.'], u, ['K.a.', 'Kristo ondoren']],
|
||||
1,
|
||||
[6, 0],
|
||||
|
@ -47,7 +47,7 @@ global.ng.common.locales['ha-gh'] = [
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
'GHS',
|
||||
'GH₵',
|
||||
'GHS',
|
||||
'Kudin Ghana',
|
||||
{'GHS': ['GH₵'], 'NGN': ['₦']},
|
||||
'ltr',
|
||||
plural,
|
||||
|
@ -35,7 +35,7 @@ global.ng.common.locales['kkj'] = [
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
[
|
||||
'pamba', 'wanja', 'mbiyɔ mɛndoŋgɔ', 'Nyɔlɔmbɔŋgɔ', 'Mɔnɔ ŋgbanja', 'Nyaŋgwɛ ŋgbanja',
|
||||
'kuŋgwɛ', 'fɛ', 'njapi', 'nyukul', '11', 'ɓulɓusɛ'
|
||||
'kuŋgwɛ', 'fɛ', 'njapi', 'nyukul', 'M11', 'ɓulɓusɛ'
|
||||
],
|
||||
u
|
||||
],
|
||||
|
@ -22,20 +22,31 @@ global.ng.common.locales['kok'] = [
|
||||
[['a', 'p'], ['AM', 'PM'], u],
|
||||
[['AM', 'PM'], u, u],
|
||||
[
|
||||
['आ', 'सो', 'मं', 'बु', 'गु', 'शु', 'शे'],
|
||||
['आयतार', 'सोमार', 'मंगळार', 'बुधवार', 'गुरुवार', 'शुक्रार', 'शेनवार'], u,
|
||||
['आय', 'सोम', 'मंगळ', 'बुध', 'गुरु', 'शुक्र', 'शेन']
|
||||
['आ', 'सो', 'मं', 'बु', 'बि', 'शु', 'शे'],
|
||||
['आयतार', 'सोमार', 'मंगळार', 'बुधवार', 'बिरेस्तार', 'शुक्रार', 'शेनवार'], u,
|
||||
['आय', 'सोम', 'मंगळ', 'बुध', 'बिरे', 'शुक्र', 'शेन']
|
||||
],
|
||||
[
|
||||
['आ', 'सो', 'मं', 'बु', 'ब', 'शु', 'शे'],
|
||||
['आयतार', 'सोमार', 'मंगळार', 'बुधवार', 'बिरेस्तार', 'शुक्रार', 'शेनवार'], u,
|
||||
['आय', 'सोम', 'मंगळ', 'बुध', 'बिरे', 'शुक्र', 'शेन']
|
||||
],
|
||||
u,
|
||||
[
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
[
|
||||
'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलाय', 'आगोस्त', 'सप्टेंबर', 'ऑक्टोबर',
|
||||
'नोव्हेंबर', 'डिसेंबर'
|
||||
'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलय', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर',
|
||||
'डिसेंबर'
|
||||
],
|
||||
u
|
||||
],
|
||||
u,
|
||||
[
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
['जाने', 'फेब्रु', 'मार्च', 'एप्री', 'मे', 'जून', 'जुल', 'ऑग', 'सप्टें', 'ऑक्टो', 'नो', 'डिसे'],
|
||||
[
|
||||
'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलय', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर',
|
||||
'डिसेंबर'
|
||||
]
|
||||
],
|
||||
[['क्रिस्तपूर्व', 'क्रिस्तशखा'], u, u],
|
||||
0,
|
||||
[0, 0],
|
||||
|
@ -51,7 +51,7 @@ global.ng.common.locales['ne-in'] = [
|
||||
['h:mm a', 'h:mm:ss a', 'h:mm:ss a z', 'h:mm:ss a zzzz'],
|
||||
['{1}, {0}', u, '{1} {0}', u],
|
||||
['.', ',', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'],
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
['#,##,##0.###', '#,##,##0%', '¤ #,##,##0.00', '#E0'],
|
||||
'INR',
|
||||
'₹',
|
||||
'भारतीय रूपिँया',
|
||||
|
@ -51,7 +51,7 @@ global.ng.common.locales['ne'] = [
|
||||
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'],
|
||||
['{1}, {0}', u, '{1} {0}', u],
|
||||
['.', ',', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'],
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
['#,##,##0.###', '#,##,##0%', '¤ #,##,##0.00', '#E0'],
|
||||
'NPR',
|
||||
'नेरू',
|
||||
'नेपाली रूपैयाँ',
|
||||
|
@ -45,7 +45,7 @@ global.ng.common.locales['so-dj'] = [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
6,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -45,7 +45,7 @@ global.ng.common.locales['so-et'] = [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
0,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -45,7 +45,7 @@ global.ng.common.locales['so-ke'] = [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
0,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -45,7 +45,7 @@ global.ng.common.locales['so'] = [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
1,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -28,11 +28,7 @@ global.ng.common.locales['vai-latn'] = [
|
||||
u,
|
||||
[
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
[
|
||||
'luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo',
|
||||
'kenpkato ɓololɔ', 'luukao lɔma'
|
||||
],
|
||||
u
|
||||
['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], u
|
||||
],
|
||||
u,
|
||||
[['BCE', 'CE'], u, u],
|
||||
|
@ -45,7 +45,7 @@ export default [
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
'GHS',
|
||||
'GH₵',
|
||||
'GHS',
|
||||
'Kudin Ghana',
|
||||
{'GHS': ['GH₵'], 'NGN': ['₦']},
|
||||
'ltr',
|
||||
plural
|
||||
|
@ -33,7 +33,7 @@ export default [
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
[
|
||||
'pamba', 'wanja', 'mbiyɔ mɛndoŋgɔ', 'Nyɔlɔmbɔŋgɔ', 'Mɔnɔ ŋgbanja', 'Nyaŋgwɛ ŋgbanja',
|
||||
'kuŋgwɛ', 'fɛ', 'njapi', 'nyukul', '11', 'ɓulɓusɛ'
|
||||
'kuŋgwɛ', 'fɛ', 'njapi', 'nyukul', 'M11', 'ɓulɓusɛ'
|
||||
],
|
||||
u
|
||||
],
|
||||
|
@ -20,20 +20,31 @@ export default [
|
||||
[['a', 'p'], ['AM', 'PM'], u],
|
||||
[['AM', 'PM'], u, u],
|
||||
[
|
||||
['आ', 'सो', 'मं', 'बु', 'गु', 'शु', 'शे'],
|
||||
['आयतार', 'सोमार', 'मंगळार', 'बुधवार', 'गुरुवार', 'शुक्रार', 'शेनवार'], u,
|
||||
['आय', 'सोम', 'मंगळ', 'बुध', 'गुरु', 'शुक्र', 'शेन']
|
||||
['आ', 'सो', 'मं', 'बु', 'बि', 'शु', 'शे'],
|
||||
['आयतार', 'सोमार', 'मंगळार', 'बुधवार', 'बिरेस्तार', 'शुक्रार', 'शेनवार'], u,
|
||||
['आय', 'सोम', 'मंगळ', 'बुध', 'बिरे', 'शुक्र', 'शेन']
|
||||
],
|
||||
[
|
||||
['आ', 'सो', 'मं', 'बु', 'ब', 'शु', 'शे'],
|
||||
['आयतार', 'सोमार', 'मंगळार', 'बुधवार', 'बिरेस्तार', 'शुक्रार', 'शेनवार'], u,
|
||||
['आय', 'सोम', 'मंगळ', 'बुध', 'बिरे', 'शुक्र', 'शेन']
|
||||
],
|
||||
u,
|
||||
[
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
[
|
||||
'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलाय', 'आगोस्त', 'सप्टेंबर', 'ऑक्टोबर',
|
||||
'नोव्हेंबर', 'डिसेंबर'
|
||||
'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलय', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर',
|
||||
'डिसेंबर'
|
||||
],
|
||||
u
|
||||
],
|
||||
u,
|
||||
[
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
['जाने', 'फेब्रु', 'मार्च', 'एप्री', 'मे', 'जून', 'जुल', 'ऑग', 'सप्टें', 'ऑक्टो', 'नो', 'डिसे'],
|
||||
[
|
||||
'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलय', 'ऑगस्ट', 'सप्टेंबर', 'ऑक्टोबर', 'नोव्हेंबर',
|
||||
'डिसेंबर'
|
||||
]
|
||||
],
|
||||
[['क्रिस्तपूर्व', 'क्रिस्तशखा'], u, u],
|
||||
0,
|
||||
[0, 0],
|
||||
|
@ -49,7 +49,7 @@ export default [
|
||||
['h:mm a', 'h:mm:ss a', 'h:mm:ss a z', 'h:mm:ss a zzzz'],
|
||||
['{1}, {0}', u, '{1} {0}', u],
|
||||
['.', ',', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'],
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
['#,##,##0.###', '#,##,##0%', '¤ #,##,##0.00', '#E0'],
|
||||
'INR',
|
||||
'₹',
|
||||
'भारतीय रूपिँया',
|
||||
|
@ -49,7 +49,7 @@ export default [
|
||||
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'],
|
||||
['{1}, {0}', u, '{1} {0}', u],
|
||||
['.', ',', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'],
|
||||
['#,##0.###', '#,##0%', '¤ #,##0.00', '#E0'],
|
||||
['#,##,##0.###', '#,##,##0%', '¤ #,##,##0.00', '#E0'],
|
||||
'NPR',
|
||||
'नेरू',
|
||||
'नेपाली रूपैयाँ',
|
||||
|
@ -43,7 +43,7 @@ export default [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
6,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -43,7 +43,7 @@ export default [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
0,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -43,7 +43,7 @@ export default [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
0,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -43,7 +43,7 @@ export default [
|
||||
'Oktoobar', 'Nofembar', 'Desembar'
|
||||
]
|
||||
],
|
||||
[['CH', 'CD'], u, ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
[['B', 'A'], ['CH', 'CD'], ['Ciise Hortii', 'Ciise Dabadii']],
|
||||
1,
|
||||
[6, 0],
|
||||
['dd/MM/yy', 'dd-MMM-y', 'dd MMMM y', 'EEEE, MMMM dd, y'],
|
||||
|
@ -26,11 +26,7 @@ export default [
|
||||
u,
|
||||
[
|
||||
['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
|
||||
[
|
||||
'luukao kemã', 'ɓandaɓu', 'vɔɔ', 'fulu', 'goo', '6', '7', 'kɔnde', 'saah', 'galo',
|
||||
'kenpkato ɓololɔ', 'luukao lɔma'
|
||||
],
|
||||
u
|
||||
['M01', 'M02', 'M03', 'M04', 'M05', 'M06', 'M07', 'M08', 'M09', 'M10', 'M11', 'M12'], u
|
||||
],
|
||||
u,
|
||||
[['BCE', 'CE'], u, u],
|
||||
|
@ -155,7 +155,7 @@ export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCh
|
||||
* rather than the identity of the object itself.
|
||||
*
|
||||
* The function receives two inputs,
|
||||
* the iteration index and the node object ID.
|
||||
* the iteration index and the associated node data.
|
||||
*/
|
||||
@Input()
|
||||
set ngForTrackBy(fn: TrackByFunction<T>) {
|
||||
|
@ -13,7 +13,7 @@ export const ISO8601_DATE_REGEX =
|
||||
// 1 2 3 4 5 6 7 8 9 10 11
|
||||
const NAMED_FORMATS: {[localeId: string]: {[format: string]: string}} = {};
|
||||
const DATE_FORMATS_SPLIT =
|
||||
/((?:[^GyMLwWdEabBhHmsSzZO']+)|(?:'(?:[^']|'')*')|(?:G{1,5}|y{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|a{1,5}|b{1,5}|B{1,5}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|O{1,4}))([\s\S]*)/;
|
||||
/((?:[^GyrMLwWdEabBhHmsSzZO']+)|(?:'(?:[^']|'')*')|(?:G{1,5}|y{1,4}|r{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|a{1,5}|b{1,5}|B{1,5}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|O{1,4}))([\s\S]*)/;
|
||||
|
||||
enum ZoneWidth {
|
||||
Short,
|
||||
@ -394,6 +394,18 @@ function weekGetter(size: number, monthBased = false): DateFormatter {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date formatter that provides the week-numbering year for the input date.
|
||||
*/
|
||||
function weekNumberingYearGetter(size: number, trim = false): DateFormatter {
|
||||
return function(date: Date, locale: string) {
|
||||
const thisThurs = getThursdayThisWeek(date);
|
||||
const weekNumberingYear = thisThurs.getFullYear();
|
||||
return padNumber(
|
||||
weekNumberingYear, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign), trim);
|
||||
};
|
||||
}
|
||||
|
||||
type DateFormatter = (date: Date, locale: string, offset: number) => string;
|
||||
|
||||
const DATE_FORMATS: {[format: string]: DateFormatter} = {};
|
||||
@ -438,6 +450,25 @@ function getDateFormatter(format: string): DateFormatter|null {
|
||||
formatter = dateGetter(DateType.FullYear, 4, 0, false, true);
|
||||
break;
|
||||
|
||||
// 1 digit representation of the week-numbering year, e.g. (AD 1 => 1, AD 199 => 199)
|
||||
case 'r':
|
||||
formatter = weekNumberingYearGetter(1);
|
||||
break;
|
||||
// 2 digit representation of the week-numbering year, padded (00-99). (e.g. AD 2001 => 01, AD
|
||||
// 2010 => 10)
|
||||
case 'rr':
|
||||
formatter = weekNumberingYearGetter(2, true);
|
||||
break;
|
||||
// 3 digit representation of the week-numbering year, padded (000-999). (e.g. AD 1 => 001, AD
|
||||
// 2010 => 2010)
|
||||
case 'rrr':
|
||||
formatter = weekNumberingYearGetter(3);
|
||||
break;
|
||||
// 4 digit representation of the week-numbering year (e.g. AD 1 => 0001, AD 2010 => 2010)
|
||||
case 'rrrr':
|
||||
formatter = weekNumberingYearGetter(4);
|
||||
break;
|
||||
|
||||
// Month of the year (1-12), numeric
|
||||
case 'M':
|
||||
case 'L':
|
||||
@ -636,7 +667,7 @@ function getDateFormatter(format: string): DateFormatter|null {
|
||||
}
|
||||
|
||||
function timezoneToOffset(timezone: string, fallback: number): number {
|
||||
// Support: IE 9-11 only, Edge 13-15+
|
||||
// Support: IE 11 only, Edge 13-15+
|
||||
// IE/Edge do not "understand" colon (`:`) in timezone
|
||||
timezone = timezone.replace(/:/g, '');
|
||||
const requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
|
||||
@ -734,7 +765,10 @@ export function isoStringToDate(match: RegExpMatchArray): Date {
|
||||
const h = Number(match[4] || 0) - tzHour;
|
||||
const m = Number(match[5] || 0) - tzMin;
|
||||
const s = Number(match[6] || 0);
|
||||
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
// The ECMAScript specification (https://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.11)
|
||||
// defines that `DateTime` milliseconds should always be rounded down, so that `999.9ms`
|
||||
// becomes `999ms`.
|
||||
const ms = Math.floor(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ export function getLocaleId(locale: string): string {
|
||||
* @publicApi
|
||||
*/
|
||||
export function getLocaleDayPeriods(
|
||||
locale: string, formStyle: FormStyle, width: TranslationWidth): [string, string] {
|
||||
locale: string, formStyle: FormStyle, width: TranslationWidth): Readonly<[string, string]> {
|
||||
const data = ɵfindLocaleData(locale);
|
||||
const amPmData = <[string, string][][]>[
|
||||
data[ɵLocaleDataIndex.DayPeriodsFormat], data[ɵLocaleDataIndex.DayPeriodsStandalone]
|
||||
@ -255,7 +255,7 @@ export function getLocaleDayPeriods(
|
||||
* @publicApi
|
||||
*/
|
||||
export function getLocaleDayNames(
|
||||
locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
|
||||
locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string> {
|
||||
const data = ɵfindLocaleData(locale);
|
||||
const daysData =
|
||||
<string[][][]>[data[ɵLocaleDataIndex.DaysFormat], data[ɵLocaleDataIndex.DaysStandalone]];
|
||||
@ -276,7 +276,7 @@ export function getLocaleDayNames(
|
||||
* @publicApi
|
||||
*/
|
||||
export function getLocaleMonthNames(
|
||||
locale: string, formStyle: FormStyle, width: TranslationWidth): string[] {
|
||||
locale: string, formStyle: FormStyle, width: TranslationWidth): ReadonlyArray<string> {
|
||||
const data = ɵfindLocaleData(locale);
|
||||
const monthsData =
|
||||
<string[][][]>[data[ɵLocaleDataIndex.MonthsFormat], data[ɵLocaleDataIndex.MonthsStandalone]];
|
||||
@ -287,7 +287,6 @@ export function getLocaleMonthNames(
|
||||
/**
|
||||
* Retrieves Gregorian-calendar eras for the given locale.
|
||||
* @param locale A locale code for the locale format rules to use.
|
||||
* @param formStyle The required grammatical form.
|
||||
* @param width The required character width.
|
||||
|
||||
* @returns An array of localized era strings.
|
||||
@ -296,7 +295,8 @@ export function getLocaleMonthNames(
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function getLocaleEraNames(locale: string, width: TranslationWidth): [string, string] {
|
||||
export function getLocaleEraNames(
|
||||
locale: string, width: TranslationWidth): Readonly<[string, string]> {
|
||||
const data = ɵfindLocaleData(locale);
|
||||
const erasData = <[string, string][]>data[ɵLocaleDataIndex.Eras];
|
||||
return getLastDefinedValue(erasData, width);
|
||||
|
@ -94,11 +94,10 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||
}
|
||||
}
|
||||
|
||||
transform<T>(obj: null): null;
|
||||
transform<T>(obj: undefined): undefined;
|
||||
transform<T>(obj: Observable<T>|null|undefined): T|null;
|
||||
transform<T>(obj: Promise<T>|null|undefined): T|null;
|
||||
transform(obj: Observable<any>|Promise<any>|null|undefined): any {
|
||||
transform<T>(obj: Observable<T>|Promise<T>): T|null;
|
||||
transform<T>(obj: null|undefined): null;
|
||||
transform<T>(obj: Observable<T>|Promise<T>|null|undefined): T|null;
|
||||
transform<T>(obj: Observable<T>|Promise<T>|null|undefined): T|null {
|
||||
if (!this._obj) {
|
||||
if (obj) {
|
||||
this._subscribe(obj);
|
||||
@ -108,7 +107,7 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||
|
||||
if (obj !== this._obj) {
|
||||
this._dispose();
|
||||
return this.transform(obj as any);
|
||||
return this.transform(obj);
|
||||
}
|
||||
|
||||
return this._latestValue;
|
||||
|
@ -29,8 +29,11 @@ export class LowerCasePipe implements PipeTransform {
|
||||
/**
|
||||
* @param value The string to transform to lower case.
|
||||
*/
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
transform(value: string): string;
|
||||
transform(value: null|undefined): null;
|
||||
transform(value: string|null|undefined): string|null;
|
||||
transform(value: string|null|undefined): string|null {
|
||||
if (value == null) return null;
|
||||
if (typeof value !== 'string') {
|
||||
throw invalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
@ -72,8 +75,11 @@ export class TitleCasePipe implements PipeTransform {
|
||||
/**
|
||||
* @param value The string to transform to title case.
|
||||
*/
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
transform(value: string): string;
|
||||
transform(value: null|undefined): null;
|
||||
transform(value: string|null|undefined): string|null;
|
||||
transform(value: string|null|undefined): string|null {
|
||||
if (value == null) return null;
|
||||
if (typeof value !== 'string') {
|
||||
throw invalidPipeArgumentError(TitleCasePipe, value);
|
||||
}
|
||||
@ -96,8 +102,11 @@ export class UpperCasePipe implements PipeTransform {
|
||||
/**
|
||||
* @param value The string to transform to upper case.
|
||||
*/
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
transform(value: string): string;
|
||||
transform(value: null|undefined): null;
|
||||
transform(value: string|null|undefined): string|null;
|
||||
transform(value: string|null|undefined): string|null {
|
||||
if (value == null) return null;
|
||||
if (typeof value !== 'string') {
|
||||
throw invalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
|
@ -65,6 +65,10 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* | | yy | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 |
|
||||
* | | yyy | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 |
|
||||
* | | yyyy | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 |
|
||||
* | Week-numbering year| r | Numeric: minimum digits | 2, 20, 201, 2017, 20173 |
|
||||
* | | rr | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 |
|
||||
* | | rrr | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 |
|
||||
* | | rrrr | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 |
|
||||
* | Month | M | Numeric: 1 digit | 9, 12 |
|
||||
* | | MM | Numeric: 2 digits + zero padded | 09, 12 |
|
||||
* | | MMM | Abbreviated | Sep |
|
||||
@ -167,7 +171,15 @@ export class DatePipe implements PipeTransform {
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
* @returns A date string in the desired format.
|
||||
*/
|
||||
transform(value: any, format = 'mediumDate', timezone?: string, locale?: string): string|null {
|
||||
transform(value: Date|string|number, format?: string, timezone?: string, locale?: string): string
|
||||
|null;
|
||||
transform(value: null|undefined, format?: string, timezone?: string, locale?: string): null;
|
||||
transform(
|
||||
value: Date|string|number|null|undefined, format?: string, timezone?: string,
|
||||
locale?: string): string|null;
|
||||
transform(
|
||||
value: Date|string|number|null|undefined, format = 'mediumDate', timezone?: string,
|
||||
locale?: string): string|null {
|
||||
if (value == null || value === '' || value !== value) return null;
|
||||
|
||||
try {
|
||||
|
@ -39,7 +39,8 @@ export class I18nPluralPipe implements PipeTransform {
|
||||
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
|
||||
* default).
|
||||
*/
|
||||
transform(value: number, pluralMap: {[count: string]: string}, locale?: string): string {
|
||||
transform(value: number|null|undefined, pluralMap: {[count: string]: string}, locale?: string):
|
||||
string {
|
||||
if (value == null) return '';
|
||||
|
||||
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
||||
|
@ -50,31 +50,35 @@ export class KeyValuePipe implements PipeTransform {
|
||||
private differ!: KeyValueDiffer<any, any>;
|
||||
private keyValues: Array<KeyValue<any, any>> = [];
|
||||
|
||||
transform<K, V>(input: null, compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): null;
|
||||
transform<V>(
|
||||
input: {[key: string]: V}|ReadonlyMap<string, V>,
|
||||
compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number):
|
||||
Array<KeyValue<string, V>>;
|
||||
transform<V>(
|
||||
input: {[key: string]: V}|ReadonlyMap<string, V>|null,
|
||||
compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number):
|
||||
Array<KeyValue<string, V>>|null;
|
||||
transform<V>(
|
||||
input: {[key: number]: V}|ReadonlyMap<number, V>,
|
||||
compareFn?: (a: KeyValue<number, V>, b: KeyValue<number, V>) => number):
|
||||
Array<KeyValue<number, V>>;
|
||||
transform<V>(
|
||||
input: {[key: number]: V}|ReadonlyMap<number, V>|null,
|
||||
compareFn?: (a: KeyValue<number, V>, b: KeyValue<number, V>) => number):
|
||||
Array<KeyValue<number, V>>|null;
|
||||
/*
|
||||
* NOTE: when the `input` value is a simple Record<K, V> object, the keys are extracted with
|
||||
* Object.keys(). This means that even if the `input` type is Record<number, V> the keys are
|
||||
* compared/returned as `string`s.
|
||||
*/
|
||||
transform<K, V>(
|
||||
input: ReadonlyMap<K, V>,
|
||||
compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): Array<KeyValue<K, V>>;
|
||||
transform<K extends number, V>(
|
||||
input: Record<K, V>, compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number):
|
||||
Array<KeyValue<string, V>>;
|
||||
transform<K extends string, V>(
|
||||
input: Record<K, V>|ReadonlyMap<K, V>,
|
||||
compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): Array<KeyValue<K, V>>;
|
||||
transform(
|
||||
input: null|undefined,
|
||||
compareFn?: (a: KeyValue<unknown, unknown>, b: KeyValue<unknown, unknown>) => number): null;
|
||||
transform<K, V>(
|
||||
input: ReadonlyMap<K, V>|null,
|
||||
input: ReadonlyMap<K, V>|null|undefined,
|
||||
compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): Array<KeyValue<K, V>>|null;
|
||||
transform<K extends number, V>(
|
||||
input: Record<K, V>|null|undefined,
|
||||
compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number):
|
||||
Array<KeyValue<string, V>>|null;
|
||||
transform<K extends string, V>(
|
||||
input: Record<K, V>|ReadonlyMap<K, V>|null|undefined,
|
||||
compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): Array<KeyValue<K, V>>|null;
|
||||
transform<K, V>(
|
||||
input: null|{[key: string]: V, [key: number]: V}|ReadonlyMap<K, V>,
|
||||
input: undefined|null|{[key: string]: V, [key: number]: V}|ReadonlyMap<K, V>,
|
||||
compareFn: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number = defaultComparator):
|
||||
Array<KeyValue<K, V>>|null {
|
||||
if (!input || (!(input instanceof Map) && typeof input !== 'object')) {
|
||||
|
@ -67,8 +67,12 @@ export class DecimalPipe implements PipeTransform {
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
*/
|
||||
transform(value: any, digitsInfo?: string, locale?: string): string|null {
|
||||
if (isEmpty(value)) return null;
|
||||
transform(value: number|string, digitsInfo?: string, locale?: string): string|null;
|
||||
transform(value: null|undefined, digitsInfo?: string, locale?: string): null;
|
||||
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string|null;
|
||||
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string
|
||||
|null {
|
||||
if (!isValue(value)) return null;
|
||||
|
||||
locale = locale || this._locale;
|
||||
|
||||
@ -121,8 +125,12 @@ export class PercentPipe implements PipeTransform {
|
||||
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
*/
|
||||
transform(value: any, digitsInfo?: string, locale?: string): string|null {
|
||||
if (isEmpty(value)) return null;
|
||||
transform(value: number|string, digitsInfo?: string, locale?: string): string|null;
|
||||
transform(value: null|undefined, digitsInfo?: string, locale?: string): null;
|
||||
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string|null;
|
||||
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string
|
||||
|null {
|
||||
if (!isValue(value)) return null;
|
||||
locale = locale || this._locale;
|
||||
try {
|
||||
const num = strToNumber(value);
|
||||
@ -213,10 +221,22 @@ export class CurrencyPipe implements PipeTransform {
|
||||
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
|
||||
*/
|
||||
transform(
|
||||
value: any, currencyCode?: string,
|
||||
value: number|string, currencyCode?: string,
|
||||
display?: 'code'|'symbol'|'symbol-narrow'|string|boolean, digitsInfo?: string,
|
||||
locale?: string): string|null;
|
||||
transform(
|
||||
value: null|undefined, currencyCode?: string,
|
||||
display?: 'code'|'symbol'|'symbol-narrow'|string|boolean, digitsInfo?: string,
|
||||
locale?: string): null;
|
||||
transform(
|
||||
value: number|string|null|undefined, currencyCode?: string,
|
||||
display?: 'code'|'symbol'|'symbol-narrow'|string|boolean, digitsInfo?: string,
|
||||
locale?: string): string|null;
|
||||
transform(
|
||||
value: number|string|null|undefined, currencyCode?: string,
|
||||
display: 'code'|'symbol'|'symbol-narrow'|string|boolean = 'symbol', digitsInfo?: string,
|
||||
locale?: string): string|null {
|
||||
if (isEmpty(value)) return null;
|
||||
if (!isValue(value)) return null;
|
||||
|
||||
locale = locale || this._locale;
|
||||
|
||||
@ -246,8 +266,8 @@ export class CurrencyPipe implements PipeTransform {
|
||||
}
|
||||
}
|
||||
|
||||
function isEmpty(value: any): boolean {
|
||||
return value == null || value === '' || value !== value;
|
||||
function isValue(value: number|string|null|undefined): value is number|string {
|
||||
return !(value == null || value === '' || value !== value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,11 +62,13 @@ export class SlicePipe implements PipeTransform {
|
||||
* - **if negative**: return all items before `end` index from the end of the list or string.
|
||||
*/
|
||||
transform<T>(value: ReadonlyArray<T>, start: number, end?: number): Array<T>;
|
||||
transform(value: null|undefined, start: number, end?: number): null;
|
||||
transform<T>(value: ReadonlyArray<T>|null|undefined, start: number, end?: number): Array<T>|null;
|
||||
transform(value: string, start: number, end?: number): string;
|
||||
transform(value: null, start: number, end?: number): null;
|
||||
transform(value: undefined, start: number, end?: number): undefined;
|
||||
transform(value: any, start: number, end?: number): any {
|
||||
if (value == null) return value;
|
||||
transform(value: string|null|undefined, start: number, end?: number): string|null;
|
||||
transform<T>(value: ReadonlyArray<T>|string|null|undefined, start: number, end?: number):
|
||||
Array<T>|string|null {
|
||||
if (value == null) return null;
|
||||
|
||||
if (!this.supports(value)) {
|
||||
throw invalidPipeArgumentError(SlicePipe, value);
|
||||
|
@ -88,7 +88,7 @@ export class BrowserViewportScroller implements ViewportScroller {
|
||||
* @returns The position in screen coordinates.
|
||||
*/
|
||||
getScrollPosition(): [number, number] {
|
||||
if (this.supportScrollRestoration()) {
|
||||
if (this.supportsScrolling()) {
|
||||
return [this.window.scrollX, this.window.scrollY];
|
||||
} else {
|
||||
return [0, 0];
|
||||
@ -100,7 +100,7 @@ export class BrowserViewportScroller implements ViewportScroller {
|
||||
* @param position The new position in screen coordinates.
|
||||
*/
|
||||
scrollToPosition(position: [number, number]): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
if (this.supportsScrolling()) {
|
||||
this.window.scrollTo(position[0], position[1]);
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ export class BrowserViewportScroller implements ViewportScroller {
|
||||
* @param anchor The ID of the anchor element.
|
||||
*/
|
||||
scrollToAnchor(anchor: string): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
if (this.supportsScrolling()) {
|
||||
const elSelected =
|
||||
this.document.getElementById(anchor) || this.document.getElementsByName(anchor)[0];
|
||||
if (elSelected) {
|
||||
@ -163,6 +163,14 @@ export class BrowserViewportScroller implements ViewportScroller {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private supportsScrolling(): boolean {
|
||||
try {
|
||||
return !!this.window.scrollTo;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getScrollRestorationProperty(obj: any): PropertyDescriptor|undefined {
|
||||
@ -170,8 +178,7 @@ function getScrollRestorationProperty(obj: any): PropertyDescriptor|undefined {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an empty implementation of the viewport scroller. This will
|
||||
* live in @angular/common as it will be used by both platform-server and platform-webworker.
|
||||
* Provides an empty implementation of the viewport scroller.
|
||||
*/
|
||||
export class NullViewportScroller implements ViewportScroller {
|
||||
/**
|
||||
|
@ -95,6 +95,10 @@ describe('Format date', () => {
|
||||
yy: '15',
|
||||
yyy: '2015',
|
||||
yyyy: '2015',
|
||||
r: '2015',
|
||||
rr: '15',
|
||||
rrr: '2015',
|
||||
rrrr: '2015',
|
||||
M: '6',
|
||||
MM: '06',
|
||||
MMM: 'Jun',
|
||||
@ -153,6 +157,10 @@ describe('Format date', () => {
|
||||
yy: '15',
|
||||
yyy: '2015',
|
||||
yyyy: '2015',
|
||||
r: '2015',
|
||||
rr: '15',
|
||||
rrr: '2015',
|
||||
rrrr: '2015',
|
||||
M: '1',
|
||||
MM: '01',
|
||||
MMM: 'Jan',
|
||||
@ -361,5 +369,14 @@ describe('Format date', () => {
|
||||
expect(formatDate(3001, 'm:ss.SS', 'en')).toEqual('0:03.00');
|
||||
expect(formatDate(3001, 'm:ss.SSS', 'en')).toEqual('0:03.001');
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/38739
|
||||
it('should return correct ISO 8601 week-numbering year for dates close to year end/beginning',
|
||||
() => {
|
||||
expect(formatDate('2013-12-27', 'rrrr', 'en')).toEqual('2013');
|
||||
expect(formatDate('2013-12-29', 'rrrr', 'en')).toEqual('2014');
|
||||
expect(formatDate('2010-01-02', 'rrrr', 'en')).toEqual('2009');
|
||||
expect(formatDate('2010-01-04', 'rrrr', 'en')).toEqual('2010');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ import localeHe from '@angular/common/locales/he';
|
||||
import localeZh from '@angular/common/locales/zh';
|
||||
import {ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core';
|
||||
|
||||
import {FormatWidth, getCurrencySymbol, getLocaleDateFormat, getLocaleDirection, getNumberOfCurrencyDigits} from '../../src/i18n/locale_data_api';
|
||||
import {FormatWidth, FormStyle, getCurrencySymbol, getLocaleDateFormat, getLocaleDayNames, getLocaleDirection, getLocaleMonthNames, getNumberOfCurrencyDigits, TranslationWidth} from '../../src/i18n/locale_data_api';
|
||||
|
||||
{
|
||||
describe('locale data api', () => {
|
||||
@ -71,5 +71,96 @@ import {FormatWidth, getCurrencySymbol, getLocaleDateFormat, getLocaleDirection,
|
||||
expect(getLocaleDirection('en')).toEqual('ltr');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocaleDayNames', () => {
|
||||
it('should return english short list of days', () => {
|
||||
expect(
|
||||
getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Short),
|
||||
)
|
||||
.toEqual(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']);
|
||||
});
|
||||
|
||||
it('should return french short list of days', () => {
|
||||
expect(
|
||||
getLocaleDayNames('fr-CA', FormStyle.Format, TranslationWidth.Short),
|
||||
)
|
||||
.toEqual(['di', 'lu', 'ma', 'me', 'je', 've', 'sa']);
|
||||
});
|
||||
|
||||
it('should return english wide list of days', () => {
|
||||
expect(
|
||||
getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Wide),
|
||||
)
|
||||
.toEqual(
|
||||
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
|
||||
});
|
||||
|
||||
it('should return french wide list of days', () => {
|
||||
expect(
|
||||
getLocaleDayNames('fr-CA', FormStyle.Format, TranslationWidth.Wide),
|
||||
)
|
||||
.toEqual(['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi']);
|
||||
});
|
||||
|
||||
it('should return the full short list of days after manipulations', () => {
|
||||
const days =
|
||||
Array.from(getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Short));
|
||||
|
||||
days.splice(2);
|
||||
days.push('unexisting_day');
|
||||
|
||||
const newDays = getLocaleDayNames('en-US', FormStyle.Format, TranslationWidth.Short);
|
||||
|
||||
expect(newDays.length).toBe(7);
|
||||
|
||||
expect(newDays).toEqual(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocaleMonthNames', () => {
|
||||
it('should return english abbreviated list of month', () => {
|
||||
expect(getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Abbreviated))
|
||||
.toEqual([
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return french abbreviated list of month', () => {
|
||||
expect(getLocaleMonthNames('fr-CA', FormStyle.Format, TranslationWidth.Abbreviated))
|
||||
.toEqual([
|
||||
'janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return english wide list of month', () => {
|
||||
expect(getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Wide)).toEqual([
|
||||
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September',
|
||||
'October', 'November', 'December'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return french wide list of month', () => {
|
||||
expect(getLocaleMonthNames('fr-CA', FormStyle.Format, TranslationWidth.Wide)).toEqual([
|
||||
'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the full abbreviated list of month after manipulations', () => {
|
||||
const month = Array.from(
|
||||
getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Abbreviated));
|
||||
month.splice(2);
|
||||
month.push('unexisting_month');
|
||||
|
||||
const newMonth =
|
||||
getLocaleMonthNames('en-US', FormStyle.Format, TranslationWidth.Abbreviated);
|
||||
|
||||
expect(newMonth.length).toBe(12);
|
||||
|
||||
expect(newMonth).toEqual(
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ import {SpyChangeDetectorRef} from '../spies';
|
||||
reject = rej;
|
||||
});
|
||||
ref = new SpyChangeDetectorRef();
|
||||
pipe = new AsyncPipe(<any>ref);
|
||||
pipe = new AsyncPipe(ref as any);
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
@ -218,10 +218,17 @@ import {SpyChangeDetectorRef} from '../spies';
|
||||
});
|
||||
});
|
||||
|
||||
describe('undefined', () => {
|
||||
it('should return null when given undefined', () => {
|
||||
const pipe = new AsyncPipe(null as any);
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('other types', () => {
|
||||
it('should throw when given an invalid object', () => {
|
||||
const pipe = new AsyncPipe(null as any);
|
||||
expect(() => pipe.transform(<any>'some bogus object')).toThrowError();
|
||||
expect(() => pipe.transform('some bogus object' as any)).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -25,8 +25,18 @@ import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||
expect(pipe.transform('BAr')).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should map null to null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
it('should map undefined to null', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not support numbers', () => {
|
||||
expect(() => pipe.transform(0 as any)).toThrowError();
|
||||
});
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform(<any>{})).toThrowError();
|
||||
expect(() => pipe.transform({} as any)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@ -80,8 +90,18 @@ import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||
expect(pipe.transform('éric')).toEqual('Éric');
|
||||
});
|
||||
|
||||
it('should map null to null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
it('should map undefined to null', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not support numbers', () => {
|
||||
expect(() => pipe.transform(0 as any)).toThrowError();
|
||||
});
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform(<any>{})).toThrowError();
|
||||
expect(() => pipe.transform({} as any)).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
@ -101,8 +121,18 @@ import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||
expect(pipe.transform('bar')).toEqual('BAR');
|
||||
});
|
||||
|
||||
it('should map null to null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
it('should map undefined to null', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not support numbers', () => {
|
||||
expect(() => pipe.transform(0 as any)).toThrowError();
|
||||
});
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform(<any>{})).toThrowError();
|
||||
expect(() => pipe.transform({} as any)).toThrowError();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -59,12 +59,20 @@ import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_refle
|
||||
expect(pipe.transform(Number.NaN)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for undefined', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should support ISO string without time', () => {
|
||||
expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform({})).toThrowError(/InvalidPipeArgument/);
|
||||
expect(() => pipe.transform({} as any)).toThrowError(/InvalidPipeArgument/);
|
||||
});
|
||||
});
|
||||
|
||||
@ -85,6 +93,13 @@ import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_refle
|
||||
expect(pipe.transform('2012-12-30T00:00:00', 'w')).toEqual('1');
|
||||
expect(pipe.transform('2012-12-31T00:00:00', 'w')).toEqual('1');
|
||||
});
|
||||
|
||||
|
||||
it('should round milliseconds down to the nearest millisecond', () => {
|
||||
expect(pipe.transform('2020-08-01T23:59:59.999', 'yyyy-MM-dd')).toEqual('2020-08-01');
|
||||
expect(pipe.transform('2020-08-01T23:59:59.9999', 'yyyy-MM-dd, h:mm:ss SSS'))
|
||||
.toEqual('2020-08-01, 11:59:59 999');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -54,12 +54,17 @@ import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_refle
|
||||
});
|
||||
|
||||
it('should use "" if value is undefined', () => {
|
||||
const val = pipe.transform(void (0) as any, mapping);
|
||||
const val = pipe.transform(undefined, mapping);
|
||||
expect(val).toEqual('');
|
||||
});
|
||||
|
||||
it('should use "" if value is null', () => {
|
||||
const val = pipe.transform(null, mapping);
|
||||
expect(val).toEqual('');
|
||||
});
|
||||
|
||||
it('should not support bad arguments', () => {
|
||||
expect(() => pipe.transform(0, <any>'hey')).toThrowError();
|
||||
expect(() => pipe.transform(0, 'hey' as any)).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -40,7 +40,7 @@ import {JitReflector} from '@angular/platform-browser-dynamic/src/compiler_refle
|
||||
});
|
||||
|
||||
it('should throw on bad arguments', () => {
|
||||
expect(() => pipe.transform('male', <any>'hey')).toThrowError();
|
||||
expect(() => pipe.transform('male', 'hey' as any)).toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -17,12 +17,12 @@ describe('KeyValuePipe', () => {
|
||||
});
|
||||
it('should return null when given undefined', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(undefined as any)).toEqual(null);
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
it('should return null for an unsupported type', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const fn = () => {};
|
||||
expect(pipe.transform(fn as any)).toEqual(null);
|
||||
expect(pipe.transform(fn as any as null)).toEqual(null);
|
||||
});
|
||||
describe('object dictionary', () => {
|
||||
it('should return empty array of an empty dictionary', () => {
|
||||
@ -98,8 +98,9 @@ describe('KeyValuePipe', () => {
|
||||
});
|
||||
it('should order by numerical and alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = [[2, 1], [1, 1], ['b', 1], [0, 1], [3, 1], ['a', 1]];
|
||||
expect(pipe.transform(new Map(input as any))).toEqual([
|
||||
const input =
|
||||
[[2, 1], [1, 1], ['b', 1], [0, 1], [3, 1], ['a', 1]] as Array<[number | string, number]>;
|
||||
expect(pipe.transform(new Map(input))).toEqual([
|
||||
{key: 0, value: 1}, {key: 1, value: 1}, {key: 2, value: 1}, {key: 3, value: 1},
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
|
@ -50,8 +50,20 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
|
||||
expect(pipe.transform('1.1234')).toEqual('1.123');
|
||||
});
|
||||
|
||||
it('should return null for NaN', () => {
|
||||
expect(pipe.transform(Number.NaN)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for undefined', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform({}))
|
||||
expect(() => pipe.transform({} as any))
|
||||
.toThrowError(
|
||||
`InvalidPipeArgument: '[object Object] is not a number' for pipe 'DecimalPipe'`);
|
||||
expect(() => pipe.transform('123abc'))
|
||||
@ -82,8 +94,20 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
|
||||
expect(pipe.transform(12.3456, '0.0-10')).toEqual('1,234.56%');
|
||||
});
|
||||
|
||||
it('should return null for NaN', () => {
|
||||
expect(pipe.transform(Number.NaN)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for undefined', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform({}))
|
||||
expect(() => pipe.transform({} as any))
|
||||
.toThrowError(
|
||||
`InvalidPipeArgument: '[object Object] is not a number' for pipe 'PercentPipe'`);
|
||||
});
|
||||
@ -125,8 +149,20 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
|
||||
expect(pipe.transform(5.1234, 'USD', 'Custom name')).toEqual('Custom name5.12');
|
||||
});
|
||||
|
||||
it('should return null for NaN', () => {
|
||||
expect(pipe.transform(Number.NaN)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for null', () => {
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null for undefined', () => {
|
||||
expect(pipe.transform(undefined)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
expect(() => pipe.transform({}))
|
||||
expect(() => pipe.transform({} as any))
|
||||
.toThrowError(
|
||||
`InvalidPipeArgument: '[object Object] is not a number' for pipe 'CurrencyPipe'`);
|
||||
});
|
||||
|
@ -47,8 +47,8 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
expect(pipe.transform(null, 1)).toBe(null);
|
||||
});
|
||||
|
||||
it('should return undefined if the value is undefined', () => {
|
||||
expect(pipe.transform(undefined, 1)).toBe(undefined);
|
||||
it('should return null if the value is undefined', () => {
|
||||
expect(pipe.transform(undefined, 1)).toBe(null);
|
||||
});
|
||||
|
||||
it('should return all items after START index when START is positive and END is omitted',
|
||||
|
@ -15,21 +15,30 @@ describe('BrowserViewportScroller', () => {
|
||||
let windowSpy: any;
|
||||
|
||||
beforeEach(() => {
|
||||
windowSpy = jasmine.createSpyObj('window', ['history']);
|
||||
windowSpy.scrollTo = 1;
|
||||
windowSpy = jasmine.createSpyObj('window', ['history', 'scrollTo']);
|
||||
windowSpy.history.scrollRestoration = 'auto';
|
||||
documentSpy = jasmine.createSpyObj('document', ['getElementById', 'getElementsByName']);
|
||||
scroller = new BrowserViewportScroller(documentSpy, windowSpy, null!);
|
||||
});
|
||||
|
||||
describe('setHistoryScrollRestoration', () => {
|
||||
it('should not crash when scrollRestoration is not writable', () => {
|
||||
function createNonWritableScrollRestoration() {
|
||||
Object.defineProperty(windowSpy.history, 'scrollRestoration', {
|
||||
value: 'auto',
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
it('should not crash when scrollRestoration is not writable', () => {
|
||||
createNonWritableScrollRestoration();
|
||||
expect(() => scroller.setHistoryScrollRestoration('manual')).not.toThrow();
|
||||
});
|
||||
|
||||
it('should still allow scrolling if scrollRestoration is not writable', () => {
|
||||
createNonWritableScrollRestoration();
|
||||
scroller.scrollToPosition([10, 10]);
|
||||
expect(windowSpy.scrollTo as jasmine.Spy).toHaveBeenCalledWith(10, 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scrollToAnchor', () => {
|
||||
|
@ -689,7 +689,7 @@ export class $locationShim {
|
||||
*
|
||||
* This method is supported only in HTML5 mode and only in browsers supporting
|
||||
* the HTML5 History API methods such as `pushState` and `replaceState`. If you need to support
|
||||
* older browsers (like IE9 or Android < 4.0), don't use this method.
|
||||
* older browsers (like Android < 4.0), don't use this method.
|
||||
*
|
||||
*/
|
||||
state(): unknown;
|
||||
|
@ -309,10 +309,7 @@ function toKeyValue(obj: {[k: string]: unknown}) {
|
||||
* Logic from https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1437
|
||||
*/
|
||||
function encodeUriSegment(val: string) {
|
||||
return encodeUriQuery(val, true)
|
||||
.replace(/%26/gi, '&')
|
||||
.replace(/%3D/gi, '=')
|
||||
.replace(/%2B/gi, '+');
|
||||
return encodeUriQuery(val, true).replace(/%26/g, '&').replace(/%3D/gi, '=').replace(/%2B/gi, '+');
|
||||
}
|
||||
|
||||
|
||||
@ -331,7 +328,7 @@ function encodeUriSegment(val: string) {
|
||||
*/
|
||||
function encodeUriQuery(val: string, pctEncodeSpaces: boolean = false) {
|
||||
return encodeURIComponent(val)
|
||||
.replace(/%40/gi, '@')
|
||||
.replace(/%40/g, '@')
|
||||
.replace(/%3A/gi, ':')
|
||||
.replace(/%24/g, '$')
|
||||
.replace(/%2C/gi, ',')
|
||||
|
@ -1,7 +1,6 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "pkg_npm", "ts_api_guardian_test", "ts_library")
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_config")
|
||||
load("//tools:defaults.bzl", "pkg_npm", "ts_api_guardian_test", "ts_config", "ts_library")
|
||||
|
||||
ts_config(
|
||||
name = "tsconfig",
|
||||
@ -32,6 +31,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/shims",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
"@npm//@bazel/typescript",
|
||||
"@npm//@types/node",
|
||||
|
@ -62,5 +62,10 @@ nodejs_test(
|
||||
"//packages/router:npm_package",
|
||||
] + glob(["**/*"]),
|
||||
entry_point = "test.js",
|
||||
tags = ["no-ivy-aot"],
|
||||
tags = [
|
||||
# TODO(josephperrott): reenable or remove test after investigating the cause of failures
|
||||
# on windows CI runs.
|
||||
"manual",
|
||||
"no-ivy-aot",
|
||||
],
|
||||
)
|
||||
|
15
packages/compiler-cli/linker/BUILD.bazel
Normal file
15
packages/compiler-cli/linker/BUILD.bazel
Normal file
@ -0,0 +1,15 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "linker",
|
||||
srcs = ["index.ts"] + glob([
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
18
packages/compiler-cli/linker/README.md
Normal file
18
packages/compiler-cli/linker/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Angular Linker
|
||||
|
||||
This package contains a `FileLinker` and supporting code to be able to "link" partial declarations of components, directives, etc in libraries to produce the full definitions.
|
||||
|
||||
The partial declaration format allows library packages to be published to npm without exposing the underlying Ivy instructions.
|
||||
|
||||
The tooling here allows application build tools (e.g. CLI) to produce fully compiled components, directives, etc at the point when the application is bundled.
|
||||
These linked files can be cached outside `node_modules` so it does not suffer from problems of mutating packages in `node_modules`.
|
||||
|
||||
Generally this tooling will be wrapped in a transpiler specific plugin, such as the provided [Babel plugin](./babel).
|
||||
|
||||
## Unit Testing
|
||||
|
||||
The unit tests are built and run using Bazel:
|
||||
|
||||
```bash
|
||||
yarn bazel test //packages/compiler-cli/linker/test
|
||||
```
|
19
packages/compiler-cli/linker/babel/BUILD.bazel
Normal file
19
packages/compiler-cli/linker/babel/BUILD.bazel
Normal file
@ -0,0 +1,19 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "babel",
|
||||
srcs = ["index.ts"] + glob([
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/linker",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"@npm//@babel/core",
|
||||
"@npm//@babel/types",
|
||||
"@npm//@types/babel__core",
|
||||
"@npm//@types/babel__traverse",
|
||||
],
|
||||
)
|
12
packages/compiler-cli/linker/babel/README.md
Normal file
12
packages/compiler-cli/linker/babel/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Angular linker - Babel plugin
|
||||
|
||||
This package contains a Babel plugin that can be used to find and link partially compiled declarations in library source code.
|
||||
See the [linker package README](../README.md) for more information.
|
||||
|
||||
## Unit Testing
|
||||
|
||||
The unit tests are built and run using Bazel:
|
||||
|
||||
```bash
|
||||
yarn bazel test //packages/compiler-cli/linker/babel/test
|
||||
```
|
8
packages/compiler-cli/linker/babel/index.ts
Normal file
8
packages/compiler-cli/linker/babel/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export {createEs2015LinkerPlugin} from './src/es2015_linker_plugin';
|
164
packages/compiler-cli/linker/babel/src/ast/babel_ast_factory.ts
Normal file
164
packages/compiler-cli/linker/babel/src/ast/babel_ast_factory.ts
Normal file
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {assert} from '../../../../linker';
|
||||
import {AstFactory, BinaryOperator, LeadingComment, ObjectLiteralProperty, SourceMapRange, TemplateLiteral, VariableDeclarationType} from '../../../../src/ngtsc/translator';
|
||||
|
||||
/**
|
||||
* A Babel flavored implementation of the AstFactory.
|
||||
*/
|
||||
export class BabelAstFactory implements AstFactory<t.Statement, t.Expression> {
|
||||
attachComments(statement: t.Statement, leadingComments: LeadingComment[]): void {
|
||||
// We must process the comments in reverse because `t.addComment()` will add new ones in front.
|
||||
for (let i = leadingComments.length - 1; i >= 0; i--) {
|
||||
const comment = leadingComments[i];
|
||||
t.addComment(statement, 'leading', comment.toString(), !comment.multiline);
|
||||
}
|
||||
}
|
||||
|
||||
createArrayLiteral = t.arrayExpression;
|
||||
|
||||
createAssignment(target: t.Expression, value: t.Expression): t.Expression {
|
||||
assert(target, isLExpression, 'must be a left hand side expression');
|
||||
return t.assignmentExpression('=', target, value);
|
||||
}
|
||||
|
||||
createBinaryExpression(
|
||||
leftOperand: t.Expression, operator: BinaryOperator,
|
||||
rightOperand: t.Expression): t.Expression {
|
||||
switch (operator) {
|
||||
case '&&':
|
||||
case '||':
|
||||
return t.logicalExpression(operator, leftOperand, rightOperand);
|
||||
default:
|
||||
return t.binaryExpression(operator, leftOperand, rightOperand);
|
||||
}
|
||||
}
|
||||
|
||||
createBlock = t.blockStatement;
|
||||
|
||||
createCallExpression(callee: t.Expression, args: t.Expression[], pure: boolean): t.Expression {
|
||||
const call = t.callExpression(callee, args);
|
||||
if (pure) {
|
||||
t.addComment(call, 'leading', ' @__PURE__ ', /* line */ false);
|
||||
}
|
||||
return call;
|
||||
}
|
||||
|
||||
createConditional = t.conditionalExpression;
|
||||
|
||||
createElementAccess(expression: t.Expression, element: t.Expression): t.Expression {
|
||||
return t.memberExpression(expression, element, /* computed */ true);
|
||||
}
|
||||
|
||||
createExpressionStatement = t.expressionStatement;
|
||||
|
||||
createFunctionDeclaration(functionName: string, parameters: string[], body: t.Statement):
|
||||
t.Statement {
|
||||
assert(body, t.isBlockStatement, 'a block');
|
||||
return t.functionDeclaration(
|
||||
t.identifier(functionName), parameters.map(param => t.identifier(param)), body);
|
||||
}
|
||||
|
||||
createFunctionExpression(functionName: string|null, parameters: string[], body: t.Statement):
|
||||
t.Expression {
|
||||
assert(body, t.isBlockStatement, 'a block');
|
||||
const name = functionName !== null ? t.identifier(functionName) : null;
|
||||
return t.functionExpression(name, parameters.map(param => t.identifier(param)), body);
|
||||
}
|
||||
|
||||
createIdentifier = t.identifier;
|
||||
|
||||
createIfStatement = t.ifStatement;
|
||||
|
||||
createLiteral(value: string|number|boolean|null|undefined): t.Expression {
|
||||
if (typeof value === 'string') {
|
||||
return t.stringLiteral(value);
|
||||
} else if (typeof value === 'number') {
|
||||
return t.numericLiteral(value);
|
||||
} else if (typeof value === 'boolean') {
|
||||
return t.booleanLiteral(value);
|
||||
} else if (value === undefined) {
|
||||
return t.identifier('undefined');
|
||||
} else if (value === null) {
|
||||
return t.nullLiteral();
|
||||
} else {
|
||||
throw new Error(`Invalid literal: ${value} (${typeof value})`);
|
||||
}
|
||||
}
|
||||
|
||||
createNewExpression = t.newExpression;
|
||||
|
||||
createObjectLiteral(properties: ObjectLiteralProperty<t.Expression>[]): t.Expression {
|
||||
return t.objectExpression(properties.map(prop => {
|
||||
const key =
|
||||
prop.quoted ? t.stringLiteral(prop.propertyName) : t.identifier(prop.propertyName);
|
||||
return t.objectProperty(key, prop.value);
|
||||
}));
|
||||
}
|
||||
|
||||
createParenthesizedExpression = t.parenthesizedExpression;
|
||||
|
||||
createPropertyAccess(expression: t.Expression, propertyName: string): t.Expression {
|
||||
return t.memberExpression(expression, t.identifier(propertyName), /* computed */ false);
|
||||
}
|
||||
|
||||
createReturnStatement = t.returnStatement;
|
||||
|
||||
createTaggedTemplate(tag: t.Expression, template: TemplateLiteral<t.Expression>): t.Expression {
|
||||
const elements = template.elements.map(
|
||||
(element, i) => this.setSourceMapRange(
|
||||
t.templateElement(element, i === template.elements.length - 1), element.range));
|
||||
return t.taggedTemplateExpression(tag, t.templateLiteral(elements, template.expressions));
|
||||
}
|
||||
|
||||
createThrowStatement = t.throwStatement;
|
||||
|
||||
createTypeOfExpression(expression: t.Expression): t.Expression {
|
||||
return t.unaryExpression('typeof', expression);
|
||||
}
|
||||
|
||||
createUnaryExpression = t.unaryExpression;
|
||||
|
||||
createVariableDeclaration(
|
||||
variableName: string, initializer: t.Expression|null,
|
||||
type: VariableDeclarationType): t.Statement {
|
||||
return t.variableDeclaration(
|
||||
type, [t.variableDeclarator(t.identifier(variableName), initializer)]);
|
||||
}
|
||||
|
||||
setSourceMapRange<T extends t.Statement|t.Expression|t.TemplateElement>(
|
||||
node: T, sourceMapRange: SourceMapRange|null): T {
|
||||
if (sourceMapRange === null) {
|
||||
return node;
|
||||
}
|
||||
// Note that the linker only works on a single file at a time, so there is no need to track the
|
||||
// filename. Babel will just use the current filename in the source-map.
|
||||
node.loc = {
|
||||
start: {
|
||||
line: sourceMapRange.start.line + 1, // lines are 1-based in Babel.
|
||||
column: sourceMapRange.start.column,
|
||||
},
|
||||
end: {
|
||||
line: sourceMapRange.end.line + 1, // lines are 1-based in Babel.
|
||||
column: sourceMapRange.end.column,
|
||||
},
|
||||
};
|
||||
node.start = sourceMapRange.start.offset;
|
||||
node.end = sourceMapRange.end.offset;
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
function isLExpression(expr: t.Expression): expr is Extract<t.LVal, t.Expression> {
|
||||
// Some LVal types are not expressions, which prevents us from using `t.isLVal()`
|
||||
// directly with `assert()`.
|
||||
return t.isLVal(expr);
|
||||
}
|
140
packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts
Normal file
140
packages/compiler-cli/linker/babel/src/ast/babel_ast_host.ts
Normal file
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {assert, AstHost, FatalLinkerError, Range} from '../../../../linker';
|
||||
|
||||
/**
|
||||
* This implementation of `AstHost` is able to get information from Babel AST nodes.
|
||||
*/
|
||||
export class BabelAstHost implements AstHost<t.Expression> {
|
||||
getSymbolName(node: t.Expression): string|null {
|
||||
if (t.isIdentifier(node)) {
|
||||
return node.name;
|
||||
} else if (t.isMemberExpression(node) && t.isIdentifier(node.property)) {
|
||||
return node.property.name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
isStringLiteral = t.isStringLiteral;
|
||||
|
||||
parseStringLiteral(str: t.Expression): string {
|
||||
assert(str, t.isStringLiteral, 'a string literal');
|
||||
return str.value;
|
||||
}
|
||||
|
||||
isNumericLiteral = t.isNumericLiteral;
|
||||
|
||||
parseNumericLiteral(num: t.Expression): number {
|
||||
assert(num, t.isNumericLiteral, 'a numeric literal');
|
||||
return num.value;
|
||||
}
|
||||
|
||||
isBooleanLiteral = t.isBooleanLiteral;
|
||||
|
||||
parseBooleanLiteral(bool: t.Expression): boolean {
|
||||
assert(bool, t.isBooleanLiteral, 'a boolean literal');
|
||||
return bool.value;
|
||||
}
|
||||
|
||||
isArrayLiteral = t.isArrayExpression;
|
||||
|
||||
parseArrayLiteral(array: t.Expression): t.Expression[] {
|
||||
assert(array, t.isArrayExpression, 'an array literal');
|
||||
return array.elements.map(element => {
|
||||
assert(element, isNotEmptyElement, 'element in array not to be empty');
|
||||
assert(element, isNotSpreadElement, 'element in array not to use spread syntax');
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
isObjectLiteral = t.isObjectExpression;
|
||||
|
||||
parseObjectLiteral(obj: t.Expression): Map<string, t.Expression> {
|
||||
assert(obj, t.isObjectExpression, 'an object literal');
|
||||
|
||||
const result = new Map<string, t.Expression>();
|
||||
for (const property of obj.properties) {
|
||||
assert(property, t.isObjectProperty, 'a property assignment');
|
||||
assert(property.value, t.isExpression, 'an expression');
|
||||
assert(property.key, isPropertyName, 'a property name');
|
||||
const key = t.isIdentifier(property.key) ? property.key.name : property.key.value;
|
||||
result.set(key, property.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
isFunctionExpression(node: t.Expression): node is Extract<t.Function, t.Expression> {
|
||||
return t.isFunction(node);
|
||||
}
|
||||
|
||||
parseReturnValue(fn: t.Expression): t.Expression {
|
||||
assert(fn, this.isFunctionExpression, 'a function');
|
||||
if (!t.isBlockStatement(fn.body)) {
|
||||
// it is a simple array function expression: `(...) => expr`
|
||||
return fn.body;
|
||||
}
|
||||
|
||||
// it is a function (arrow or normal) with a body. E.g.:
|
||||
// * `(...) => { stmt; ... }`
|
||||
// * `function(...) { stmt; ... }`
|
||||
|
||||
if (fn.body.body.length !== 1) {
|
||||
throw new FatalLinkerError(
|
||||
fn.body, 'Unsupported syntax, expected a function body with a single return statement.');
|
||||
}
|
||||
const stmt = fn.body.body[0];
|
||||
assert(stmt, t.isReturnStatement, 'a function body with a single return statement');
|
||||
if (stmt.argument === null) {
|
||||
throw new FatalLinkerError(stmt, 'Unsupported syntax, expected function to return a value.');
|
||||
}
|
||||
|
||||
return stmt.argument;
|
||||
}
|
||||
|
||||
getRange(node: t.Expression): Range {
|
||||
if (node.loc == null || node.start === null || node.end === null) {
|
||||
throw new FatalLinkerError(
|
||||
node, 'Unable to read range for node - it is missing location information.');
|
||||
}
|
||||
return {
|
||||
startLine: node.loc.start.line - 1, // Babel lines are 1-based
|
||||
startCol: node.loc.start.column,
|
||||
startPos: node.start,
|
||||
endPos: node.end,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the expression does not represent an empty element in an array literal.
|
||||
* For example in `[,foo]` the first element is "empty".
|
||||
*/
|
||||
function isNotEmptyElement(e: t.Expression|t.SpreadElement|null): e is t.Expression|
|
||||
t.SpreadElement {
|
||||
return e !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the expression is not a spread element of an array literal.
|
||||
* For example in `[x, ...rest]` the `...rest` expression is a spread element.
|
||||
*/
|
||||
function isNotSpreadElement(e: t.Expression|t.SpreadElement): e is t.Expression {
|
||||
return !t.isSpreadElement(e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the expression can be considered a text based property name.
|
||||
*/
|
||||
function isPropertyName(e: t.Expression): e is t.Identifier|t.StringLiteral|t.NumericLiteral {
|
||||
return t.isIdentifier(e) || t.isStringLiteral(e) || t.isNumericLiteral(e);
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {NodePath, Scope} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {DeclarationScope} from '../../../linker';
|
||||
|
||||
export type ConstantScopePath = NodePath<t.Function|t.Program>;
|
||||
|
||||
/**
|
||||
* This class represents the lexical scope of a partial declaration in Babel source code.
|
||||
*
|
||||
* Its only responsibility is to compute a reference object for the scope of shared constant
|
||||
* statements that will be generated during partial linking.
|
||||
*/
|
||||
export class BabelDeclarationScope implements DeclarationScope<ConstantScopePath, t.Expression> {
|
||||
/**
|
||||
* Construct a new `BabelDeclarationScope`.
|
||||
*
|
||||
* @param declarationScope the Babel scope containing the declaration call expression.
|
||||
*/
|
||||
constructor(private declarationScope: Scope) {}
|
||||
|
||||
/**
|
||||
* Compute the Babel `NodePath` that can be used to reference the lexical scope where any
|
||||
* shared constant statements would be inserted.
|
||||
*
|
||||
* There will only be a shared constant scope if the expression is in an ECMAScript module, or a
|
||||
* UMD module. Otherwise `null` is returned to indicate that constant statements must be emitted
|
||||
* locally to the generated linked definition, to avoid polluting the global scope.
|
||||
*
|
||||
* @param expression the expression that points to the Angular core framework import.
|
||||
*/
|
||||
getConstantScopeRef(expression: t.Expression): ConstantScopePath|null {
|
||||
// If the expression is of the form `a.b.c` then we want to get the far LHS (e.g. `a`).
|
||||
let bindingExpression = expression;
|
||||
while (t.isMemberExpression(bindingExpression)) {
|
||||
bindingExpression = bindingExpression.object;
|
||||
}
|
||||
|
||||
if (!t.isIdentifier(bindingExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The binding of the expression is where this identifier was declared.
|
||||
// This could be a variable declaration, an import namespace or a function parameter.
|
||||
const binding = this.declarationScope.getBinding(bindingExpression.name);
|
||||
if (binding === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We only support shared constant statements if the binding was in a UMD module (i.e. declared
|
||||
// within a `t.Function`) or an ECMASCript module (i.e. declared at the top level of a
|
||||
// `t.Program` that is marked as a module).
|
||||
const path = binding.scope.path;
|
||||
if (!path.isFunctionParent() && !(path.isProgram() && path.node.sourceType === 'module')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
167
packages/compiler-cli/linker/babel/src/es2015_linker_plugin.ts
Normal file
167
packages/compiler-cli/linker/babel/src/es2015_linker_plugin.ts
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {PluginObj} from '@babel/core';
|
||||
import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {FileLinker, isFatalLinkerError, LinkerEnvironment, LinkerOptions} from '../../../linker';
|
||||
|
||||
import {BabelAstFactory} from './ast/babel_ast_factory';
|
||||
import {BabelAstHost} from './ast/babel_ast_host';
|
||||
import {BabelDeclarationScope, ConstantScopePath} from './babel_declaration_scope';
|
||||
|
||||
/**
|
||||
* Create a Babel plugin that visits the program, identifying and linking partial declarations.
|
||||
*
|
||||
* The plugin delegates most of its work to a generic `FileLinker` for each file (`t.Program` in
|
||||
* Babel) that is visited.
|
||||
*/
|
||||
export function createEs2015LinkerPlugin(options: Partial<LinkerOptions> = {}): PluginObj {
|
||||
let fileLinker: FileLinker<ConstantScopePath, t.Statement, t.Expression>|null = null;
|
||||
|
||||
const linkerEnvironment = LinkerEnvironment.create<t.Statement, t.Expression>(
|
||||
new BabelAstHost(), new BabelAstFactory(), options);
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
Program: {
|
||||
|
||||
/**
|
||||
* Create a new `FileLinker` as we enter each file (`t.Program` in Babel).
|
||||
*/
|
||||
enter(path: NodePath<t.Program>): void {
|
||||
assertNull(fileLinker);
|
||||
const file: BabelFile = path.hub.file;
|
||||
fileLinker = new FileLinker(linkerEnvironment, file.opts.filename ?? '', file.code);
|
||||
},
|
||||
|
||||
/**
|
||||
* On exiting the file, insert any shared constant statements that were generated during
|
||||
* linking of the partial declarations.
|
||||
*/
|
||||
exit(): void {
|
||||
assertNotNull(fileLinker);
|
||||
for (const {constantScope, statements} of fileLinker.getConstantStatements()) {
|
||||
insertStatements(constantScope, statements);
|
||||
}
|
||||
fileLinker = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Test each call expression to see if it is a partial declaration; it if is then replace it
|
||||
* with the results of linking the declaration.
|
||||
*/
|
||||
CallExpression(call: NodePath<t.CallExpression>): void {
|
||||
try {
|
||||
assertNotNull(fileLinker);
|
||||
|
||||
const callee = call.node.callee;
|
||||
if (!t.isExpression(callee)) {
|
||||
return;
|
||||
}
|
||||
const calleeName = linkerEnvironment.host.getSymbolName(callee);
|
||||
if (calleeName === null) {
|
||||
return;
|
||||
}
|
||||
const args = call.node.arguments;
|
||||
if (!fileLinker.isPartialDeclaration(calleeName) || !isExpressionArray(args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const declarationScope = new BabelDeclarationScope(call.scope);
|
||||
const replacement = fileLinker.linkPartialDeclaration(calleeName, args, declarationScope);
|
||||
|
||||
call.replaceWith(replacement);
|
||||
} catch (e) {
|
||||
const node = isFatalLinkerError(e) ? e.node as t.Node : call.node;
|
||||
throw buildCodeFrameError(call.hub.file, e.message, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the `statements` at the location defined by `path`.
|
||||
*
|
||||
* The actual insertion strategy depends upon the type of the `path`.
|
||||
*/
|
||||
function insertStatements(path: ConstantScopePath, statements: t.Statement[]): void {
|
||||
if (path.isFunction()) {
|
||||
insertIntoFunction(path, statements);
|
||||
} else if (path.isProgram()) {
|
||||
insertIntoProgram(path, statements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the `statements` at the top of the body of the `fn` function.
|
||||
*/
|
||||
function insertIntoFunction(fn: NodePath<t.Function>, statements: t.Statement[]): void {
|
||||
const body = fn.get('body');
|
||||
body.unshiftContainer('body', statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the `statements` at the top of the `program`, below any import statements.
|
||||
*/
|
||||
function insertIntoProgram(program: NodePath<t.Program>, statements: t.Statement[]): void {
|
||||
const body = program.get('body');
|
||||
const importStatements = body.filter(statement => statement.isImportDeclaration());
|
||||
if (importStatements.length === 0) {
|
||||
program.unshiftContainer('body', statements);
|
||||
} else {
|
||||
importStatements[importStatements.length - 1].insertAfter(statements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all the `nodes` are Babel expressions.
|
||||
*/
|
||||
function isExpressionArray(nodes: t.Node[]): nodes is t.Expression[] {
|
||||
return nodes.every(node => t.isExpression(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given `obj` is `null`.
|
||||
*/
|
||||
function assertNull<T>(obj: T|null): asserts obj is null {
|
||||
if (obj !== null) {
|
||||
throw new Error('BUG - expected `obj` to be null');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given `obj` is not `null`.
|
||||
*/
|
||||
function assertNotNull<T>(obj: T|null): asserts obj is T {
|
||||
if (obj === null) {
|
||||
throw new Error('BUG - expected `obj` not to be null');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string representation of an error that includes the code frame of the `node`.
|
||||
*/
|
||||
function buildCodeFrameError(file: BabelFile, message: string, node: t.Node): string {
|
||||
const filename = file.opts.filename || '(unknown file)';
|
||||
const error = file.buildCodeFrameError(node, message);
|
||||
return `${filename}: ${error.message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface is making up for the fact that the Babel typings for `NodePath.hub.file` are
|
||||
* lacking.
|
||||
*/
|
||||
interface BabelFile {
|
||||
code: string;
|
||||
opts: {filename?: string;};
|
||||
|
||||
buildCodeFrameError(node: t.Node, message: string): Error;
|
||||
}
|
36
packages/compiler-cli/linker/babel/test/BUILD.bazel
Normal file
36
packages/compiler-cli/linker/babel/test/BUILD.bazel
Normal file
@ -0,0 +1,36 @@
|
||||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/linker",
|
||||
"//packages/compiler-cli/linker/babel",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"@npm//@babel/core",
|
||||
"@npm//@babel/generator",
|
||||
"@npm//@babel/parser",
|
||||
"@npm//@babel/template",
|
||||
"@npm//@babel/traverse",
|
||||
"@npm//@babel/types",
|
||||
"@npm//@types/babel__core",
|
||||
"@npm//@types/babel__generator",
|
||||
"@npm//@types/babel__template",
|
||||
"@npm//@types/babel__traverse",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular_es5"],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
)
|
@ -0,0 +1,382 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {leadingComment} from '@angular/compiler';
|
||||
import generate from '@babel/generator';
|
||||
import {expression, statement} from '@babel/template';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {BabelAstFactory} from '../../src/ast/babel_ast_factory';
|
||||
|
||||
describe('BabelAstFactory', () => {
|
||||
let factory: BabelAstFactory;
|
||||
beforeEach(() => factory = new BabelAstFactory());
|
||||
|
||||
describe('attachComments()', () => {
|
||||
it('should add the comments to the given statement', () => {
|
||||
const stmt = statement.ast`x = 10;`;
|
||||
factory.attachComments(
|
||||
stmt, [leadingComment('comment 1', true), leadingComment('comment 2', false)]);
|
||||
|
||||
expect(generate(stmt).code).toEqual([
|
||||
'/* comment 1 */',
|
||||
'//comment 2',
|
||||
'x = 10;',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createArrayLiteral()', () => {
|
||||
it('should create an array node containing the provided expressions', () => {
|
||||
const expr1 = expression.ast`42`;
|
||||
const expr2 = expression.ast`"moo"`;
|
||||
|
||||
const array = factory.createArrayLiteral([expr1, expr2]);
|
||||
expect(generate(array).code).toEqual('[42, "moo"]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAssignment()', () => {
|
||||
it('should create an assignment node using the target and value expressions', () => {
|
||||
const target = expression.ast`x`;
|
||||
const value = expression.ast`42`;
|
||||
const assignment = factory.createAssignment(target, value);
|
||||
expect(generate(assignment).code).toEqual('x = 42');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createBinaryExpression()', () => {
|
||||
it('should create a binary operation node using the left and right expressions', () => {
|
||||
const left = expression.ast`17`;
|
||||
const right = expression.ast`42`;
|
||||
const expr = factory.createBinaryExpression(left, '+', right);
|
||||
expect(generate(expr).code).toEqual('17 + 42');
|
||||
});
|
||||
|
||||
it('should create a binary operation node for logical operators', () => {
|
||||
const left = expression.ast`17`;
|
||||
const right = expression.ast`42`;
|
||||
const expr = factory.createBinaryExpression(left, '&&', right);
|
||||
expect(t.isLogicalExpression(expr)).toBe(true);
|
||||
expect(generate(expr).code).toEqual('17 && 42');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createBlock()', () => {
|
||||
it('should create a block statement containing the given statements', () => {
|
||||
const stmt1 = statement.ast`x = 10`;
|
||||
const stmt2 = statement.ast`y = 20`;
|
||||
const block = factory.createBlock([stmt1, stmt2]);
|
||||
expect(generate(block).code).toEqual([
|
||||
'{',
|
||||
' x = 10;',
|
||||
' y = 20;',
|
||||
'}',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCallExpression()', () => {
|
||||
it('should create a call on the `callee` with the given `args`', () => {
|
||||
const callee = expression.ast`foo`;
|
||||
const arg1 = expression.ast`42`;
|
||||
const arg2 = expression.ast`"moo"`;
|
||||
const call = factory.createCallExpression(callee, [arg1, arg2], false);
|
||||
expect(generate(call).code).toEqual('foo(42, "moo")');
|
||||
});
|
||||
|
||||
it('should create a call marked with a PURE comment if `pure` is true', () => {
|
||||
const callee = expression.ast`foo`;
|
||||
const arg1 = expression.ast`42`;
|
||||
const arg2 = expression.ast`"moo"`;
|
||||
const call = factory.createCallExpression(callee, [arg1, arg2], true);
|
||||
expect(generate(call).code).toEqual(['/* @__PURE__ */', 'foo(42, "moo")'].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createConditional()', () => {
|
||||
it('should create a condition expression', () => {
|
||||
const test = expression.ast`!test`;
|
||||
const thenExpr = expression.ast`42`;
|
||||
const elseExpr = expression.ast`"moo"`;
|
||||
const conditional = factory.createConditional(test, thenExpr, elseExpr);
|
||||
expect(generate(conditional).code).toEqual('!test ? 42 : "moo"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createElementAccess()', () => {
|
||||
it('should create an expression accessing the element of an array/object', () => {
|
||||
const expr = expression.ast`obj`;
|
||||
const element = expression.ast`"moo"`;
|
||||
const access = factory.createElementAccess(expr, element);
|
||||
expect(generate(access).code).toEqual('obj["moo"]');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createExpressionStatement()', () => {
|
||||
it('should create a statement node from the given expression', () => {
|
||||
const expr = expression.ast`x = 10`;
|
||||
const stmt = factory.createExpressionStatement(expr);
|
||||
expect(t.isStatement(stmt)).toBe(true);
|
||||
expect(generate(stmt).code).toEqual('x = 10;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFunctionDeclaration()', () => {
|
||||
it('should create a function declaration node with the given name, parameters and body statements',
|
||||
() => {
|
||||
const stmts = statement.ast`{x = 10; y = 20;}`;
|
||||
const fn = factory.createFunctionDeclaration('foo', ['arg1', 'arg2'], stmts);
|
||||
expect(generate(fn).code).toEqual([
|
||||
'function foo(arg1, arg2) {',
|
||||
' x = 10;',
|
||||
' y = 20;',
|
||||
'}',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFunctionExpression()', () => {
|
||||
it('should create a function expression node with the given name, parameters and body statements',
|
||||
() => {
|
||||
const stmts = statement.ast`{x = 10; y = 20;}`;
|
||||
const fn = factory.createFunctionExpression('foo', ['arg1', 'arg2'], stmts);
|
||||
expect(t.isStatement(fn)).toBe(false);
|
||||
expect(generate(fn).code).toEqual([
|
||||
'function foo(arg1, arg2) {',
|
||||
' x = 10;',
|
||||
' y = 20;',
|
||||
'}',
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create an anonymous function expression node if the name is null', () => {
|
||||
const stmts = statement.ast`{x = 10; y = 20;}`;
|
||||
const fn = factory.createFunctionExpression(null, ['arg1', 'arg2'], stmts);
|
||||
expect(generate(fn).code).toEqual([
|
||||
'function (arg1, arg2) {',
|
||||
' x = 10;',
|
||||
' y = 20;',
|
||||
'}',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createIdentifier()', () => {
|
||||
it('should create an identifier with the given name', () => {
|
||||
const id = factory.createIdentifier('someId') as t.Identifier;
|
||||
expect(t.isIdentifier(id)).toBe(true);
|
||||
expect(id.name).toEqual('someId');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createIfStatement()', () => {
|
||||
it('should create an if-else statement', () => {
|
||||
const test = expression.ast`!test`;
|
||||
const thenStmt = statement.ast`x = 10;`;
|
||||
const elseStmt = statement.ast`x = 42;`;
|
||||
const ifStmt = factory.createIfStatement(test, thenStmt, elseStmt);
|
||||
expect(generate(ifStmt).code).toEqual('if (!test) x = 10;else x = 42;');
|
||||
});
|
||||
|
||||
it('should create an if statement if the else expression is null', () => {
|
||||
const test = expression.ast`!test`;
|
||||
const thenStmt = statement.ast`x = 10;`;
|
||||
const ifStmt = factory.createIfStatement(test, thenStmt, null);
|
||||
expect(generate(ifStmt).code).toEqual('if (!test) x = 10;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createLiteral()', () => {
|
||||
it('should create a string literal', () => {
|
||||
const literal = factory.createLiteral('moo');
|
||||
expect(t.isStringLiteral(literal)).toBe(true);
|
||||
expect(generate(literal).code).toEqual('"moo"');
|
||||
});
|
||||
|
||||
it('should create a number literal', () => {
|
||||
const literal = factory.createLiteral(42);
|
||||
expect(t.isNumericLiteral(literal)).toBe(true);
|
||||
expect(generate(literal).code).toEqual('42');
|
||||
});
|
||||
|
||||
it('should create a number literal for `NaN`', () => {
|
||||
const literal = factory.createLiteral(NaN);
|
||||
expect(t.isNumericLiteral(literal)).toBe(true);
|
||||
expect(generate(literal).code).toEqual('NaN');
|
||||
});
|
||||
|
||||
it('should create a boolean literal', () => {
|
||||
const literal = factory.createLiteral(true);
|
||||
expect(t.isBooleanLiteral(literal)).toBe(true);
|
||||
expect(generate(literal).code).toEqual('true');
|
||||
});
|
||||
|
||||
it('should create an `undefined` literal', () => {
|
||||
const literal = factory.createLiteral(undefined);
|
||||
expect(t.isIdentifier(literal)).toBe(true);
|
||||
expect(generate(literal).code).toEqual('undefined');
|
||||
});
|
||||
|
||||
it('should create a null literal', () => {
|
||||
const literal = factory.createLiteral(null);
|
||||
expect(t.isNullLiteral(literal)).toBe(true);
|
||||
expect(generate(literal).code).toEqual('null');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNewExpression()', () => {
|
||||
it('should create a `new` operation on the constructor `expression` with the given `args`',
|
||||
() => {
|
||||
const expr = expression.ast`Foo`;
|
||||
const arg1 = expression.ast`42`;
|
||||
const arg2 = expression.ast`"moo"`;
|
||||
const call = factory.createNewExpression(expr, [arg1, arg2]);
|
||||
expect(generate(call).code).toEqual('new Foo(42, "moo")');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createObjectLiteral()', () => {
|
||||
it('should create an object literal node, with the given properties', () => {
|
||||
const prop1 = expression.ast`42`;
|
||||
const prop2 = expression.ast`"moo"`;
|
||||
const obj = factory.createObjectLiteral([
|
||||
{propertyName: 'prop1', value: prop1, quoted: false},
|
||||
{propertyName: 'prop2', value: prop2, quoted: true},
|
||||
]);
|
||||
expect(generate(obj).code).toEqual([
|
||||
'{',
|
||||
' prop1: 42,',
|
||||
' "prop2": "moo"',
|
||||
'}',
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createParenthesizedExpression()', () => {
|
||||
it('should add parentheses around the given expression', () => {
|
||||
const expr = expression.ast`a + b`;
|
||||
const paren = factory.createParenthesizedExpression(expr);
|
||||
expect(generate(paren).code).toEqual('(a + b)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPropertyAccess()', () => {
|
||||
it('should create a property access expression node', () => {
|
||||
const expr = expression.ast`obj`;
|
||||
const access = factory.createPropertyAccess(expr, 'moo');
|
||||
expect(generate(access).code).toEqual('obj.moo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createReturnStatement()', () => {
|
||||
it('should create a return statement returning the given expression', () => {
|
||||
const expr = expression.ast`42`;
|
||||
const returnStmt = factory.createReturnStatement(expr);
|
||||
expect(generate(returnStmt).code).toEqual('return 42;');
|
||||
});
|
||||
|
||||
it('should create a void return statement if the expression is null', () => {
|
||||
const returnStmt = factory.createReturnStatement(null);
|
||||
expect(generate(returnStmt).code).toEqual('return;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTaggedTemplate()', () => {
|
||||
it('should create a tagged template node from the tag, elements and expressions', () => {
|
||||
const elements = [
|
||||
{raw: 'raw1', cooked: 'cooked1', range: null},
|
||||
{raw: 'raw2', cooked: 'cooked2', range: null},
|
||||
{raw: 'raw3', cooked: 'cooked3', range: null},
|
||||
];
|
||||
const expressions = [
|
||||
expression.ast`42`,
|
||||
expression.ast`"moo"`,
|
||||
];
|
||||
const tag = expression.ast`tagFn`;
|
||||
const template = factory.createTaggedTemplate(tag, {elements, expressions});
|
||||
expect(generate(template).code).toEqual('tagFn`raw1${42}raw2${"moo"}raw3`');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createThrowStatement()', () => {
|
||||
it('should create a throw statement, throwing the given expression', () => {
|
||||
const expr = expression.ast`new Error("bad")`;
|
||||
const throwStmt = factory.createThrowStatement(expr);
|
||||
expect(generate(throwStmt).code).toEqual('throw new Error("bad");');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTypeOfExpression()', () => {
|
||||
it('should create a typeof expression node', () => {
|
||||
const expr = expression.ast`42`;
|
||||
const typeofExpr = factory.createTypeOfExpression(expr);
|
||||
expect(generate(typeofExpr).code).toEqual('typeof 42');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createUnaryExpression()', () => {
|
||||
it('should create a unary expression with the operator and operand', () => {
|
||||
const expr = expression.ast`value`;
|
||||
const unaryExpr = factory.createUnaryExpression('!', expr);
|
||||
expect(generate(unaryExpr).code).toEqual('!value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createVariableDeclaration()', () => {
|
||||
it('should create a variable declaration statement node for the given variable name and initializer',
|
||||
() => {
|
||||
const initializer = expression.ast`42`;
|
||||
const varDecl = factory.createVariableDeclaration('foo', initializer, 'let');
|
||||
expect(generate(varDecl).code).toEqual('let foo = 42;');
|
||||
});
|
||||
|
||||
it('should create a constant declaration statement node for the given variable name and initializer',
|
||||
() => {
|
||||
const initializer = expression.ast`42`;
|
||||
const varDecl = factory.createVariableDeclaration('foo', initializer, 'const');
|
||||
expect(generate(varDecl).code).toEqual('const foo = 42;');
|
||||
});
|
||||
|
||||
it('should create a downleveled variable declaration statement node for the given variable name and initializer',
|
||||
() => {
|
||||
const initializer = expression.ast`42`;
|
||||
const varDecl = factory.createVariableDeclaration('foo', initializer, 'var');
|
||||
expect(generate(varDecl).code).toEqual('var foo = 42;');
|
||||
});
|
||||
|
||||
it('should create an uninitialized variable declaration statement node for the given variable name and a null initializer',
|
||||
() => {
|
||||
const varDecl = factory.createVariableDeclaration('foo', null, 'let');
|
||||
expect(generate(varDecl).code).toEqual('let foo;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSourceMapRange()', () => {
|
||||
it('should attach the `sourceMapRange` to the given `node`', () => {
|
||||
const expr = expression.ast`42`;
|
||||
expect(expr.loc).toBeUndefined();
|
||||
expect(expr.start).toBeUndefined();
|
||||
expect(expr.end).toBeUndefined();
|
||||
|
||||
factory.setSourceMapRange(expr, {
|
||||
start: {line: 0, column: 1, offset: 1},
|
||||
end: {line: 2, column: 3, offset: 15},
|
||||
content: '-****\n*****\n****',
|
||||
url: 'original.ts'
|
||||
});
|
||||
|
||||
// Lines are 1-based in Babel.
|
||||
expect(expr.loc).toEqual({
|
||||
start: {line: 1, column: 1},
|
||||
end: {line: 3, column: 3},
|
||||
});
|
||||
expect(expr.start).toEqual(1);
|
||||
expect(expr.end).toEqual(15);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,305 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as t from '@babel/types';
|
||||
import template from '@babel/template';
|
||||
import {parse} from '@babel/parser';
|
||||
import {BabelAstHost} from '../../src/ast/babel_ast_host';
|
||||
|
||||
describe('BabelAstHost', () => {
|
||||
let host: BabelAstHost;
|
||||
beforeEach(() => host = new BabelAstHost());
|
||||
|
||||
describe('getSymbolName()', () => {
|
||||
it('should return the name of an identifier', () => {
|
||||
expect(host.getSymbolName(expr('someIdentifier'))).toEqual('someIdentifier');
|
||||
});
|
||||
|
||||
it('should return the name of an identifier at the end of a property access chain', () => {
|
||||
expect(host.getSymbolName(expr('a.b.c.someIdentifier'))).toEqual('someIdentifier');
|
||||
});
|
||||
|
||||
it('should return null if the expression has no identifier', () => {
|
||||
expect(host.getSymbolName(expr('42'))).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isStringLiteral()', () => {
|
||||
it('should return true if the expression is a string literal', () => {
|
||||
expect(host.isStringLiteral(expr('"moo"'))).toBe(true);
|
||||
expect(host.isStringLiteral(expr('\'moo\''))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not a string literal', () => {
|
||||
expect(host.isStringLiteral(expr('true'))).toBe(false);
|
||||
expect(host.isStringLiteral(expr('someIdentifier'))).toBe(false);
|
||||
expect(host.isStringLiteral(expr('42'))).toBe(false);
|
||||
expect(host.isStringLiteral(expr('{}'))).toBe(false);
|
||||
expect(host.isStringLiteral(expr('[]'))).toBe(false);
|
||||
expect(host.isStringLiteral(expr('null'))).toBe(false);
|
||||
expect(host.isStringLiteral(expr('\'a\' + \'b\''))).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the expression is a template string', () => {
|
||||
expect(host.isStringLiteral(expr('\`moo\`'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStringLiteral()', () => {
|
||||
it('should extract the string value', () => {
|
||||
expect(host.parseStringLiteral(expr('"moo"'))).toEqual('moo');
|
||||
expect(host.parseStringLiteral(expr('\'moo\''))).toEqual('moo');
|
||||
});
|
||||
|
||||
it('should error if the value is not a string literal', () => {
|
||||
expect(() => host.parseStringLiteral(expr('42')))
|
||||
.toThrowError('Unsupported syntax, expected a string literal.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNumericLiteral()', () => {
|
||||
it('should return true if the expression is a number literal', () => {
|
||||
expect(host.isNumericLiteral(expr('42'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not a number literal', () => {
|
||||
expect(host.isStringLiteral(expr('true'))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('"moo"'))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('\'moo\''))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('someIdentifier'))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('{}'))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('[]'))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('null'))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('\'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isNumericLiteral(expr('\`moo\`'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseNumericLiteral()', () => {
|
||||
it('should extract the number value', () => {
|
||||
expect(host.parseNumericLiteral(expr('42'))).toEqual(42);
|
||||
});
|
||||
|
||||
it('should error if the value is not a numeric literal', () => {
|
||||
expect(() => host.parseNumericLiteral(expr('"moo"')))
|
||||
.toThrowError('Unsupported syntax, expected a numeric literal.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBooleanLiteral()', () => {
|
||||
it('should return true if the expression is a boolean literal', () => {
|
||||
expect(host.isBooleanLiteral(expr('true'))).toBe(true);
|
||||
expect(host.isBooleanLiteral(expr('false'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not a boolean literal', () => {
|
||||
expect(host.isBooleanLiteral(expr('"moo"'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\'moo\''))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('someIdentifier'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('42'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('{}'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('[]'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('null'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\`moo\`'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseBooleanLiteral()', () => {
|
||||
it('should extract the boolean value', () => {
|
||||
expect(host.parseBooleanLiteral(expr('true'))).toEqual(true);
|
||||
expect(host.parseBooleanLiteral(expr('false'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should error if the value is not a boolean literal', () => {
|
||||
expect(() => host.parseBooleanLiteral(expr('"moo"')))
|
||||
.toThrowError('Unsupported syntax, expected a boolean literal.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isArrayLiteral()', () => {
|
||||
it('should return true if the expression is an array literal', () => {
|
||||
expect(host.isArrayLiteral(expr('[]'))).toBe(true);
|
||||
expect(host.isArrayLiteral(expr('[1, 2, 3]'))).toBe(true);
|
||||
expect(host.isArrayLiteral(expr('[[], []]'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not an array literal', () => {
|
||||
expect(host.isArrayLiteral(expr('"moo"'))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('\'moo\''))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('someIdentifier'))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('42'))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('{}'))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('null'))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('\'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isArrayLiteral(expr('\`moo\`'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseArrayLiteral()', () => {
|
||||
it('should extract the expressions in the array', () => {
|
||||
const moo = expr('\'moo\'');
|
||||
expect(host.parseArrayLiteral(expr('[]'))).toEqual([]);
|
||||
expect(host.parseArrayLiteral(expr('[\'moo\']'))).toEqual([moo]);
|
||||
});
|
||||
|
||||
it('should error if there is an empty item', () => {
|
||||
expect(() => host.parseArrayLiteral(expr('[,]')))
|
||||
.toThrowError('Unsupported syntax, expected element in array not to be empty.');
|
||||
});
|
||||
|
||||
it('should error if there is a spread element', () => {
|
||||
expect(() => host.parseArrayLiteral(expr('[...[0,1]]')))
|
||||
.toThrowError('Unsupported syntax, expected element in array not to use spread syntax.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObjectLiteral()', () => {
|
||||
it('should return true if the expression is an object literal', () => {
|
||||
expect(host.isObjectLiteral(rhs('x = {}'))).toBe(true);
|
||||
expect(host.isObjectLiteral(rhs('x = { foo: \'bar\' }'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not an object literal', () => {
|
||||
expect(host.isObjectLiteral(rhs('x = "moo"'))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = \'moo\''))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = someIdentifier'))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = 42'))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = []'))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = null'))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = \'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isObjectLiteral(rhs('x = \`moo\`'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseObjectLiteral()', () => {
|
||||
it('should extract the properties from the object', () => {
|
||||
const moo = expr('\'moo\'');
|
||||
expect(host.parseObjectLiteral(rhs('x = {}'))).toEqual(new Map());
|
||||
expect(host.parseObjectLiteral(rhs('x = {a: \'moo\'}'))).toEqual(new Map([['a', moo]]));
|
||||
});
|
||||
|
||||
it('should error if there is a method', () => {
|
||||
expect(() => host.parseObjectLiteral(rhs('x = { foo() {} }')))
|
||||
.toThrowError('Unsupported syntax, expected a property assignment.');
|
||||
});
|
||||
|
||||
it('should error if there is a spread element', () => {
|
||||
expect(() => host.parseObjectLiteral(rhs('x = {...{a:\'moo\'}}')))
|
||||
.toThrowError('Unsupported syntax, expected a property assignment.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFunctionExpression()', () => {
|
||||
it('should return true if the expression is a function', () => {
|
||||
expect(host.isFunctionExpression(rhs('x = function() {}'))).toBe(true);
|
||||
expect(host.isFunctionExpression(rhs('x = function foo() {}'))).toBe(true);
|
||||
expect(host.isFunctionExpression(rhs('x = () => {}'))).toBe(true);
|
||||
expect(host.isFunctionExpression(rhs('x = () => true'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is a function declaration', () => {
|
||||
expect(host.isFunctionExpression(expr('function foo() {}'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not a function expression', () => {
|
||||
expect(host.isFunctionExpression(expr('[]'))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('"moo"'))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('\'moo\''))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('someIdentifier'))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('42'))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('{}'))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('null'))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('\'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isFunctionExpression(expr('\`moo\`'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseReturnValue()', () => {
|
||||
it('should extract the return value of a function', () => {
|
||||
const moo = expr('\'moo\'');
|
||||
expect(host.parseReturnValue(rhs('x = function() { return \'moo\'; }'))).toEqual(moo);
|
||||
});
|
||||
|
||||
it('should extract the value of a simple arrow function', () => {
|
||||
const moo = expr('\'moo\'');
|
||||
expect(host.parseReturnValue(rhs('x = () => \'moo\''))).toEqual(moo);
|
||||
});
|
||||
|
||||
it('should extract the return value of an arrow function', () => {
|
||||
const moo = expr('\'moo\'');
|
||||
expect(host.parseReturnValue(rhs('x = () => { return \'moo\' }'))).toEqual(moo);
|
||||
});
|
||||
|
||||
it('should error if the body has 0 statements', () => {
|
||||
expect(() => host.parseReturnValue(rhs('x = function () { }')))
|
||||
.toThrowError(
|
||||
'Unsupported syntax, expected a function body with a single return statement.');
|
||||
expect(() => host.parseReturnValue(rhs('x = () => { }')))
|
||||
.toThrowError(
|
||||
'Unsupported syntax, expected a function body with a single return statement.');
|
||||
});
|
||||
|
||||
it('should error if the body has more than 1 statement', () => {
|
||||
expect(() => host.parseReturnValue(rhs('x = function () { const x = 10; return x; }')))
|
||||
.toThrowError(
|
||||
'Unsupported syntax, expected a function body with a single return statement.');
|
||||
expect(() => host.parseReturnValue(rhs('x = () => { const x = 10; return x; }')))
|
||||
.toThrowError(
|
||||
'Unsupported syntax, expected a function body with a single return statement.');
|
||||
});
|
||||
|
||||
it('should error if the single statement is not a return statement', () => {
|
||||
expect(() => host.parseReturnValue(rhs('x = function () { const x = 10; }')))
|
||||
.toThrowError(
|
||||
'Unsupported syntax, expected a function body with a single return statement.');
|
||||
expect(() => host.parseReturnValue(rhs('x = () => { const x = 10; }')))
|
||||
.toThrowError(
|
||||
'Unsupported syntax, expected a function body with a single return statement.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRange()', () => {
|
||||
it('should extract the range from the expression', () => {
|
||||
const file = parse('// preamble\nx = \'moo\';');
|
||||
const stmt = file.program.body[0];
|
||||
assertExpressionStatement(stmt);
|
||||
assertAssignmentExpression(stmt.expression);
|
||||
expect(host.getRange(stmt.expression.right))
|
||||
.toEqual({startLine: 1, startCol: 4, startPos: 16, endPos: 21});
|
||||
});
|
||||
|
||||
it('should error if there is no range information', () => {
|
||||
const moo = rhs('// preamble\nx = \'moo\';');
|
||||
expect(() => host.getRange(moo))
|
||||
.toThrowError('Unable to read range for node - it is missing location information.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expr(code: string): t.Expression {
|
||||
const stmt = template.ast(code);
|
||||
return (stmt as t.ExpressionStatement).expression;
|
||||
}
|
||||
|
||||
function rhs(code: string): t.Expression {
|
||||
const e = expr(code);
|
||||
assertAssignmentExpression(e);
|
||||
return e.right;
|
||||
}
|
||||
|
||||
function assertExpressionStatement(e: t.Node): asserts e is t.ExpressionStatement {
|
||||
if (!t.isExpressionStatement(e)) {
|
||||
throw new Error('Bad test - expected an expression statement');
|
||||
}
|
||||
}
|
||||
|
||||
function assertAssignmentExpression(e: t.Expression): asserts e is t.AssignmentExpression {
|
||||
if (!t.isAssignmentExpression(e)) {
|
||||
throw new Error('Bad test - expected an assignment expression');
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {parse} from '@babel/parser';
|
||||
import traverse, {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {BabelDeclarationScope} from '../src/babel_declaration_scope';
|
||||
|
||||
|
||||
describe('BabelDeclarationScope', () => {
|
||||
describe('getConstantScopeRef()', () => {
|
||||
it('should return a path to the ES module where the expression was imported', () => {
|
||||
const ast = parse(
|
||||
[
|
||||
'import * as core from \'@angular/core\';',
|
||||
'function foo() {',
|
||||
' var TEST = core;',
|
||||
'}',
|
||||
].join('\n'),
|
||||
{sourceType: 'module'});
|
||||
const nodePath = findVarDeclaration(ast, 'TEST');
|
||||
const scope = new BabelDeclarationScope(nodePath.scope);
|
||||
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
|
||||
expect(constantScope).not.toBe(null);
|
||||
expect(constantScope!.node).toBe(ast.program);
|
||||
});
|
||||
|
||||
it('should return a path to the ES Module where the expression is declared', () => {
|
||||
const ast = parse(
|
||||
[
|
||||
'var core;',
|
||||
'export function foo() {',
|
||||
' var TEST = core;',
|
||||
'}',
|
||||
].join('\n'),
|
||||
{sourceType: 'module'});
|
||||
const nodePath = findVarDeclaration(ast, 'TEST');
|
||||
const scope = new BabelDeclarationScope(nodePath.scope);
|
||||
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
|
||||
expect(constantScope).not.toBe(null);
|
||||
expect(constantScope!.node).toBe(ast.program);
|
||||
});
|
||||
|
||||
it('should return null if the file is not an ES module', () => {
|
||||
const ast = parse(
|
||||
[
|
||||
'var core;',
|
||||
'function foo() {',
|
||||
' var TEST = core;',
|
||||
'}',
|
||||
].join('\n'),
|
||||
{sourceType: 'script'});
|
||||
const nodePath = findVarDeclaration(ast, 'TEST');
|
||||
const scope = new BabelDeclarationScope(nodePath.scope);
|
||||
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
|
||||
expect(constantScope).toBe(null);
|
||||
});
|
||||
|
||||
it('should return the IIFE factory function where the expression is a parameter', () => {
|
||||
const ast = parse(
|
||||
[
|
||||
'var core;',
|
||||
'(function(core) {',
|
||||
' var BLOCK = \'block\';',
|
||||
' function foo() {',
|
||||
' var TEST = core;',
|
||||
' }',
|
||||
'})(core);',
|
||||
].join('\n'),
|
||||
{sourceType: 'script'});
|
||||
const nodePath = findVarDeclaration(ast, 'TEST');
|
||||
const fnPath = findFirstFunction(ast);
|
||||
const scope = new BabelDeclarationScope(nodePath.scope);
|
||||
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
|
||||
expect(constantScope).not.toBe(null);
|
||||
expect(constantScope!.isFunction()).toBe(true);
|
||||
expect(constantScope!.node).toEqual(fnPath.node);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function findVarDeclaration(
|
||||
file: t.File, varName: string): NodePath<t.VariableDeclarator&{init: t.Expression}> {
|
||||
let varDecl: NodePath<t.VariableDeclarator>|undefined = undefined;
|
||||
traverse(file, {
|
||||
VariableDeclarator: (path) => {
|
||||
const id = path.get('id');
|
||||
if (id.isIdentifier() && id.node.name === varName && path.get('init') !== null) {
|
||||
varDecl = path;
|
||||
path.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (varDecl === undefined) {
|
||||
throw new Error(`TEST BUG: expected to find variable declaration for ${varName}.`);
|
||||
}
|
||||
return varDecl;
|
||||
}
|
||||
|
||||
function findFirstFunction(file: t.File): NodePath<t.Function> {
|
||||
let fn: NodePath<t.Function>|undefined = undefined;
|
||||
traverse(file, {
|
||||
Function: (path) => {
|
||||
fn = path;
|
||||
path.stop();
|
||||
}
|
||||
});
|
||||
if (fn === undefined) {
|
||||
throw new Error(`TEST BUG: expected to find a function.`);
|
||||
}
|
||||
return fn;
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {NodePath, PluginObj, transformSync} from '@babel/core';
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
import {FileLinker} from '../../../linker';
|
||||
import {PartialDirectiveLinkerVersion1} from '../../src/file_linker/partial_linkers/partial_directive_linker_1';
|
||||
import {createEs2015LinkerPlugin} from '../src/es2015_linker_plugin';
|
||||
|
||||
describe('createEs2015LinkerPlugin()', () => {
|
||||
it('should return a Babel plugin visitor that handles Program (enter/exit) and CallExpression nodes',
|
||||
() => {
|
||||
const plugin = createEs2015LinkerPlugin();
|
||||
expect(plugin.visitor).toEqual({
|
||||
Program: {
|
||||
enter: jasmine.any(Function),
|
||||
exit: jasmine.any(Function),
|
||||
},
|
||||
CallExpression: jasmine.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that calls FileLinker.isPartialDeclaration() on each call expression',
|
||||
() => {
|
||||
const isPartialDeclarationSpy = spyOn(FileLinker.prototype, 'isPartialDeclaration');
|
||||
|
||||
transformSync(
|
||||
[
|
||||
'var core;', `fn1()`, 'fn2({prop: () => fn3({})});', `x.method(() => fn4());`,
|
||||
'spread(...x);'
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'unambiguous'},
|
||||
});
|
||||
expect(isPartialDeclarationSpy.calls.allArgs()).toEqual([
|
||||
['fn1'],
|
||||
['fn2'],
|
||||
['fn3'],
|
||||
['method'],
|
||||
['fn4'],
|
||||
['spread'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that calls FileLinker.linkPartialDeclaration() on each matching declaration',
|
||||
() => {
|
||||
const linkSpy = spyOn(FileLinker.prototype, 'linkPartialDeclaration')
|
||||
.and.returnValue(t.identifier('REPLACEMENT'));
|
||||
|
||||
transformSync(
|
||||
[
|
||||
'var core;',
|
||||
`$ngDeclareDirective({version: 1, ngImport: core, x: 1});`,
|
||||
`$ngDeclareComponent({version: 1, ngImport: core, foo: () => $ngDeclareDirective({version: 1, ngImport: core, x: 2})});`,
|
||||
`x.qux(() => $ngDeclareDirective({version: 1, ngImport: core, x: 3}));`,
|
||||
'spread(...x);',
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'unambiguous'},
|
||||
});
|
||||
|
||||
expect(humanizeLinkerCalls(linkSpy.calls)).toEqual([
|
||||
['$ngDeclareDirective', '{version:1,ngImport:core,x:1}'],
|
||||
[
|
||||
'$ngDeclareComponent',
|
||||
'{version:1,ngImport:core,foo:()=>$ngDeclareDirective({version:1,ngImport:core,x:2})}'
|
||||
],
|
||||
// Note we do not process `x:2` declaration since it is nested within another declaration
|
||||
['$ngDeclareDirective', '{version:1,ngImport:core,x:3}']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that replaces call expressions with the return value from FileLinker.linkPartialDeclaration()',
|
||||
() => {
|
||||
let replaceCount = 0;
|
||||
spyOn(FileLinker.prototype, 'linkPartialDeclaration')
|
||||
.and.callFake(() => t.identifier('REPLACEMENT_' + ++replaceCount));
|
||||
const result = transformSync(
|
||||
[
|
||||
'var core;',
|
||||
'$ngDeclareDirective({version: 1, ngImport: core});',
|
||||
'$ngDeclareDirective({version: 1, ngImport: core, foo: () => bar({})});',
|
||||
'x.qux();',
|
||||
'spread(...x);',
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'unambiguous'},
|
||||
generatorOpts: {compact: true},
|
||||
});
|
||||
expect(result!.code).toEqual('var core;REPLACEMENT_1;REPLACEMENT_2;x.qux();spread(...x);');
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that adds shared statements after any imports', () => {
|
||||
spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT'));
|
||||
const result = transformSync(
|
||||
[
|
||||
'import * as core from \'some-module\';',
|
||||
'import {id} from \'other-module\';',
|
||||
`$ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
`$ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
`$ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'unambiguous'},
|
||||
generatorOpts: {compact: true},
|
||||
});
|
||||
expect(result!.code)
|
||||
.toEqual(
|
||||
'import*as core from\'some-module\';import{id}from\'other-module\';const _c0=[1];const _c1=[2];const _c2=[3];"REPLACEMENT";"REPLACEMENT";"REPLACEMENT";');
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that adds shared statements at the start of the program if it is an ECMAScript Module and there are no imports',
|
||||
() => {
|
||||
spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT'));
|
||||
const result = transformSync(
|
||||
[
|
||||
'var core;',
|
||||
`$ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
`$ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
`$ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
// We declare the file as a module because this cannot be inferred from the source
|
||||
parserOpts: {sourceType: 'module'},
|
||||
generatorOpts: {compact: true},
|
||||
});
|
||||
expect(result!.code)
|
||||
.toEqual(
|
||||
'const _c0=[1];const _c1=[2];const _c2=[3];var core;"REPLACEMENT";"REPLACEMENT";"REPLACEMENT";');
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that adds shared statements at the start of the function body if the ngImport is from a function parameter',
|
||||
() => {
|
||||
spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT'));
|
||||
const result = transformSync(
|
||||
[
|
||||
'function run(core) {', ` $ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
` $ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
` $ngDeclareDirective({version: 1, ngImport: core})`, '}'
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'unambiguous'},
|
||||
generatorOpts: {compact: true},
|
||||
});
|
||||
expect(result!.code)
|
||||
.toEqual(
|
||||
'function run(core){const _c0=[1];const _c1=[2];const _c2=[3];"REPLACEMENT";"REPLACEMENT";"REPLACEMENT";}');
|
||||
});
|
||||
|
||||
it('should return a Babel plugin that adds shared statements into an IIFE if no scope could not be derived for the ngImport',
|
||||
() => {
|
||||
spyOnLinkPartialDeclarationWithConstants(o.literal('REPLACEMENT'));
|
||||
const result = transformSync(
|
||||
[
|
||||
'function run() {',
|
||||
` $ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
` $ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
` $ngDeclareDirective({version: 1, ngImport: core})`,
|
||||
'}',
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin()],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'unambiguous'},
|
||||
generatorOpts: {compact: true},
|
||||
});
|
||||
expect(result!.code).toEqual([
|
||||
`function run(){`,
|
||||
`(function(){const _c0=[1];return"REPLACEMENT";})();`,
|
||||
`(function(){const _c0=[2];return"REPLACEMENT";})();`,
|
||||
`(function(){const _c0=[3];return"REPLACEMENT";})();`,
|
||||
`}`,
|
||||
].join(''));
|
||||
});
|
||||
|
||||
it('should still execute other plugins that match AST nodes inside the result of the replacement',
|
||||
() => {
|
||||
spyOnLinkPartialDeclarationWithConstants(o.fn([], [], null, null, 'FOO'));
|
||||
const result = transformSync(
|
||||
[
|
||||
`$ngDeclareDirective({version: 1, ngImport: core}); FOO;`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [
|
||||
createEs2015LinkerPlugin(),
|
||||
createIdentifierMapperPlugin('FOO', 'BAR'),
|
||||
createIdentifierMapperPlugin('_c0', 'x1'),
|
||||
],
|
||||
filename: '/test.js',
|
||||
parserOpts: {sourceType: 'module'},
|
||||
generatorOpts: {compact: true},
|
||||
});
|
||||
expect(result!.code).toEqual([
|
||||
`(function(){const x1=[1];return function BAR(){};})();BAR;`,
|
||||
].join(''));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert the arguments of the spied-on `calls` into a human readable array.
|
||||
*/
|
||||
function humanizeLinkerCalls(
|
||||
calls: jasmine.Calls<typeof FileLinker.prototype.linkPartialDeclaration>) {
|
||||
return calls.all().map(({args: [fn, args]}) => [fn, generate(args[0], {compact: true}).code]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spy on the `PartialDirectiveLinkerVersion1.linkPartialDeclaration()` method, triggering
|
||||
* shared constants to be created.
|
||||
*/
|
||||
function spyOnLinkPartialDeclarationWithConstants(replacement: o.Expression) {
|
||||
let callCount = 0;
|
||||
spyOn(PartialDirectiveLinkerVersion1.prototype, 'linkPartialDeclaration')
|
||||
.and.callFake(((sourceUrl, code, constantPool) => {
|
||||
const constArray = o.literalArr([o.literal(++callCount)]);
|
||||
// We have to add the constant twice or it will not create a shared statement
|
||||
constantPool.getConstLiteral(constArray);
|
||||
constantPool.getConstLiteral(constArray);
|
||||
return replacement;
|
||||
}) as typeof PartialDirectiveLinkerVersion1.prototype.linkPartialDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Babel plugin that will replace all identifiers that match `<src>` with identifiers
|
||||
* called `<dest>`.
|
||||
*/
|
||||
function createIdentifierMapperPlugin(src: string, dest: string): PluginObj {
|
||||
return {
|
||||
visitor: {
|
||||
Identifier(path: NodePath<t.Identifier>) {
|
||||
if (path.node.name === src) {
|
||||
path.replaceWith(t.identifier(dest));
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
14
packages/compiler-cli/linker/index.ts
Normal file
14
packages/compiler-cli/linker/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export {AstHost, Range} from './src/ast/ast_host';
|
||||
export {assert} from './src/ast/utils';
|
||||
export {FatalLinkerError, isFatalLinkerError} from './src/fatal_linker_error';
|
||||
export {DeclarationScope} from './src/file_linker/declaration_scope';
|
||||
export {FileLinker} from './src/file_linker/file_linker';
|
||||
export {LinkerEnvironment} from './src/file_linker/linker_environment';
|
||||
export {LinkerOptions} from './src/file_linker/linker_options';
|
95
packages/compiler-cli/linker/src/ast/ast_host.ts
Normal file
95
packages/compiler-cli/linker/src/ast/ast_host.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* An abstraction for getting information from an AST while being agnostic to the underlying AST
|
||||
* implementation.
|
||||
*/
|
||||
export interface AstHost<TExpression> {
|
||||
/**
|
||||
* Get the name of the symbol represented by the given expression node, or `null` if it is not a
|
||||
* symbol.
|
||||
*/
|
||||
getSymbolName(node: TExpression): string|null;
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is a string literal, or false otherwise.
|
||||
*/
|
||||
isStringLiteral(node: TExpression): boolean;
|
||||
/**
|
||||
* Parse the string value from the given expression, or throw if it is not a string literal.
|
||||
*/
|
||||
parseStringLiteral(str: TExpression): string;
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is a numeric literal, or false otherwise.
|
||||
*/
|
||||
isNumericLiteral(node: TExpression): boolean;
|
||||
/**
|
||||
* Parse the numeric value from the given expression, or throw if it is not a numeric literal.
|
||||
*/
|
||||
parseNumericLiteral(num: TExpression): number;
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is a boolean literal, or false otherwise.
|
||||
*/
|
||||
isBooleanLiteral(node: TExpression): boolean;
|
||||
/**
|
||||
* Parse the boolean value from the given expression, or throw if it is not a boolean literal.
|
||||
*/
|
||||
parseBooleanLiteral(bool: TExpression): boolean;
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is an array literal, or false otherwise.
|
||||
*/
|
||||
isArrayLiteral(node: TExpression): boolean;
|
||||
/**
|
||||
* Parse an array of expressions from the given expression, or throw if it is not an array
|
||||
* literal.
|
||||
*/
|
||||
parseArrayLiteral(array: TExpression): TExpression[];
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is an object literal, or false otherwise.
|
||||
*/
|
||||
isObjectLiteral(node: TExpression): boolean;
|
||||
/**
|
||||
* Parse the given expression into a map of object property names to property expressions, or
|
||||
* throw if it is not an object literal.
|
||||
*/
|
||||
parseObjectLiteral(obj: TExpression): Map<string, TExpression>;
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is a function, or false otherwise.
|
||||
*/
|
||||
isFunctionExpression(node: TExpression): boolean;
|
||||
/**
|
||||
* Compute the "value" of a function expression by parsing its body for a single `return`
|
||||
* statement, extracting the returned expression, or throw if it is not possible.
|
||||
*/
|
||||
parseReturnValue(fn: TExpression): TExpression;
|
||||
|
||||
/**
|
||||
* Compute the location range of the expression in the source file, to be used for source-mapping.
|
||||
*/
|
||||
getRange(node: TExpression): Range;
|
||||
}
|
||||
|
||||
/**
|
||||
* The location of the start and end of an expression in the original source file.
|
||||
*/
|
||||
export interface Range {
|
||||
/** 0-based character position of the range start in the source file text. */
|
||||
startPos: number;
|
||||
/** 0-based line index of the range start in the source file text. */
|
||||
startLine: number;
|
||||
/** 0-based column position of the range start in the source file text. */
|
||||
startCol: number;
|
||||
/** 0-based character position of the range end in the source file text. */
|
||||
endPos: number;
|
||||
}
|
240
packages/compiler-cli/linker/src/ast/ast_value.ts
Normal file
240
packages/compiler-cli/linker/src/ast/ast_value.ts
Normal file
@ -0,0 +1,240 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as o from '@angular/compiler';
|
||||
import {FatalLinkerError} from '../fatal_linker_error';
|
||||
import {AstHost, Range} from './ast_host';
|
||||
|
||||
/**
|
||||
* This helper class wraps an object expression along with an `AstHost` object, exposing helper
|
||||
* methods that make it easier to extract the properties of the object.
|
||||
*/
|
||||
export class AstObject<TExpression> {
|
||||
/**
|
||||
* Create a new `AstObject` from the given `expression` and `host`.
|
||||
*/
|
||||
static parse<TExpression>(expression: TExpression, host: AstHost<TExpression>):
|
||||
AstObject<TExpression> {
|
||||
const obj = host.parseObjectLiteral(expression);
|
||||
return new AstObject<TExpression>(expression, obj, host);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
readonly expression: TExpression, private obj: Map<string, TExpression>,
|
||||
private host: AstHost<TExpression>) {}
|
||||
|
||||
/**
|
||||
* Returns true if the object has a property called `propertyName`.
|
||||
*/
|
||||
has(propertyName: string): boolean {
|
||||
return this.obj.has(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number value of the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property or the property is not a number.
|
||||
*/
|
||||
getNumber(propertyName: string): number {
|
||||
return this.host.parseNumericLiteral(this.getRequiredProperty(propertyName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string value of the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property or the property is not a string.
|
||||
*/
|
||||
getString(propertyName: string): string {
|
||||
return this.host.parseStringLiteral(this.getRequiredProperty(propertyName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boolean value of the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property or the property is not a boolean.
|
||||
*/
|
||||
getBoolean(propertyName: string): boolean {
|
||||
return this.host.parseBooleanLiteral(this.getRequiredProperty(propertyName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nested `AstObject` parsed from the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property or the property is not an object.
|
||||
*/
|
||||
getObject(propertyName: string): AstObject<TExpression> {
|
||||
const expr = this.getRequiredProperty(propertyName);
|
||||
const obj = this.host.parseObjectLiteral(expr);
|
||||
return new AstObject(expr, obj, this.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of `AstValue` objects parsed from the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property or the property is not an array.
|
||||
*/
|
||||
getArray(propertyName: string): AstValue<TExpression>[] {
|
||||
const arr = this.host.parseArrayLiteral(this.getRequiredProperty(propertyName));
|
||||
return arr.map(entry => new AstValue(entry, this.host));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `WrappedNodeExpr` object that wraps the expression at the property called
|
||||
* `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property.
|
||||
*/
|
||||
getOpaque(propertyName: string): o.WrappedNodeExpr<TExpression> {
|
||||
return new o.WrappedNodeExpr(this.getRequiredProperty(propertyName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw `TExpression` value of the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property.
|
||||
*/
|
||||
getNode(propertyName: string): TExpression {
|
||||
return this.getRequiredProperty(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an `AstValue` that wraps the value of the property called `propertyName`.
|
||||
*
|
||||
* Throws an error if there is no such property.
|
||||
*/
|
||||
getValue(propertyName: string): AstValue<TExpression> {
|
||||
return new AstValue(this.getRequiredProperty(propertyName), this.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the AstObject to a raw JavaScript object, mapping each property value (as an
|
||||
* `AstValue`) to the generic type (`T`) via the `mapper` function.
|
||||
*/
|
||||
toLiteral<T>(mapper: (value: AstValue<TExpression>) => T): {[key: string]: T} {
|
||||
const result: {[key: string]: T} = {};
|
||||
for (const [key, expression] of this.obj) {
|
||||
result[key] = mapper(new AstValue(expression, this.host));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private getRequiredProperty(propertyName: string): TExpression {
|
||||
if (!this.obj.has(propertyName)) {
|
||||
throw new FatalLinkerError(
|
||||
this.expression, `Expected property '${propertyName}' to be present.`);
|
||||
}
|
||||
return this.obj.get(propertyName)!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper class wraps an `expression`, exposing methods that use the `host` to give
|
||||
* access to the underlying value of the wrapped expression.
|
||||
*/
|
||||
export class AstValue<TExpression> {
|
||||
constructor(private expression: TExpression, private host: AstHost<TExpression>) {}
|
||||
|
||||
/**
|
||||
* Is this value a number?
|
||||
*/
|
||||
isNumber(): boolean {
|
||||
return this.host.isNumericLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the number from this value, or error if it is not a number.
|
||||
*/
|
||||
getNumber(): number {
|
||||
return this.host.parseNumericLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this value a string?
|
||||
*/
|
||||
isString(): boolean {
|
||||
return this.host.isStringLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the string from this value, or error if it is not a string.
|
||||
*/
|
||||
getString(): string {
|
||||
return this.host.parseStringLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this value a boolean?
|
||||
*/
|
||||
isBoolean(): boolean {
|
||||
return this.host.isBooleanLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the boolean from this value, or error if it is not a boolean.
|
||||
*/
|
||||
getBoolean(): boolean {
|
||||
return this.host.parseBooleanLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this value an object literal?
|
||||
*/
|
||||
isObject(): boolean {
|
||||
return this.host.isObjectLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse this value into an `AstObject`, or error if it is not an object literal.
|
||||
*/
|
||||
getObject(): AstObject<TExpression> {
|
||||
return AstObject.parse(this.expression, this.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this value an array literal?
|
||||
*/
|
||||
isArray(): boolean {
|
||||
return this.host.isArrayLiteral(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse this value into an array of `AstValue` objects, or error if it is not an array literal.
|
||||
*/
|
||||
getArray(): AstValue<TExpression>[] {
|
||||
const arr = this.host.parseArrayLiteral(this.expression);
|
||||
return arr.map(entry => new AstValue(entry, this.host));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this value a function expression?
|
||||
*/
|
||||
isFunction(): boolean {
|
||||
return this.host.isFunctionExpression(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the return value as an `AstValue` from this value as a function expression, or error if
|
||||
* it is not a function expression.
|
||||
*/
|
||||
getFunctionReturnValue(): AstValue<TExpression> {
|
||||
return new AstValue(this.host.parseReturnValue(this.expression), this.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the `TExpression` of this value wrapped in a `WrappedNodeExpr`.
|
||||
*/
|
||||
getOpaque(): o.WrappedNodeExpr<TExpression> {
|
||||
return new o.WrappedNodeExpr(this.expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range of the location of this value in the original source.
|
||||
*/
|
||||
getRange(): Range {
|
||||
return this.host.getRange(this.expression);
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
import {AstHost, Range} from '../ast_host';
|
||||
import {assert} from '../utils';
|
||||
|
||||
|
||||
/**
|
||||
* This implementation of `AstHost` is able to get information from TypeScript AST nodes.
|
||||
*
|
||||
* This host is not actually used at runtime in the current code.
|
||||
*
|
||||
* It is implemented here to ensure that the `AstHost` abstraction is not unfairly skewed towards
|
||||
* the Babel implementation. It could also provide a basis for a 3rd TypeScript compiler plugin to
|
||||
* do linking in the future.
|
||||
*/
|
||||
export class TypeScriptAstHost implements AstHost<ts.Expression> {
|
||||
getSymbolName(node: ts.Expression): string|null {
|
||||
if (ts.isIdentifier(node)) {
|
||||
return node.text;
|
||||
} else if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {
|
||||
return node.name.text;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
isStringLiteral = ts.isStringLiteral;
|
||||
|
||||
parseStringLiteral(str: ts.Expression): string {
|
||||
assert(str, this.isStringLiteral, 'a string literal');
|
||||
return str.text;
|
||||
}
|
||||
|
||||
isNumericLiteral = ts.isNumericLiteral;
|
||||
|
||||
parseNumericLiteral(num: ts.Expression): number {
|
||||
assert(num, this.isNumericLiteral, 'a numeric literal');
|
||||
return parseInt(num.text);
|
||||
}
|
||||
|
||||
isBooleanLiteral(node: ts.Expression): node is ts.FalseLiteral|ts.TrueLiteral {
|
||||
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword;
|
||||
}
|
||||
|
||||
parseBooleanLiteral(bool: ts.Expression): boolean {
|
||||
assert(bool, this.isBooleanLiteral, 'a boolean literal');
|
||||
return bool.kind === ts.SyntaxKind.TrueKeyword;
|
||||
}
|
||||
|
||||
isArrayLiteral = ts.isArrayLiteralExpression;
|
||||
|
||||
parseArrayLiteral(array: ts.Expression): ts.Expression[] {
|
||||
assert(array, this.isArrayLiteral, 'an array literal');
|
||||
return array.elements.map(element => {
|
||||
assert(element, isNotEmptyElement, 'element in array not to be empty');
|
||||
assert(element, isNotSpreadElement, 'element in array not to use spread syntax');
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
||||
isObjectLiteral = ts.isObjectLiteralExpression;
|
||||
|
||||
parseObjectLiteral(obj: ts.Expression): Map<string, ts.Expression> {
|
||||
assert(obj, this.isObjectLiteral, 'an object literal');
|
||||
|
||||
const result = new Map<string, ts.Expression>();
|
||||
for (const property of obj.properties) {
|
||||
assert(property, ts.isPropertyAssignment, 'a property assignment');
|
||||
assert(property.name, isPropertyName, 'a property name');
|
||||
result.set(property.name.text, property.initializer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
isFunctionExpression(node: ts.Expression): node is ts.FunctionExpression|ts.ArrowFunction {
|
||||
return ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
||||
}
|
||||
|
||||
parseReturnValue(fn: ts.Expression): ts.Expression {
|
||||
assert(fn, this.isFunctionExpression, 'a function');
|
||||
if (!ts.isBlock(fn.body)) {
|
||||
// it is a simple array function expression: `(...) => expr`
|
||||
return fn.body;
|
||||
}
|
||||
|
||||
// it is a function (arrow or normal) with a body. E.g.:
|
||||
// * `(...) => { stmt; ... }`
|
||||
// * `function(...) { stmt; ... }`
|
||||
|
||||
if (fn.body.statements.length !== 1) {
|
||||
throw new FatalLinkerError(
|
||||
fn.body, 'Unsupported syntax, expected a function body with a single return statement.');
|
||||
}
|
||||
const stmt = fn.body.statements[0];
|
||||
assert(stmt, ts.isReturnStatement, 'a function body with a single return statement');
|
||||
if (stmt.expression === undefined) {
|
||||
throw new FatalLinkerError(stmt, 'Unsupported syntax, expected function to return a value.');
|
||||
}
|
||||
|
||||
return stmt.expression;
|
||||
}
|
||||
|
||||
getRange(node: ts.Expression): Range {
|
||||
const file = node.getSourceFile();
|
||||
if (file === undefined) {
|
||||
throw new FatalLinkerError(
|
||||
node, 'Unable to read range for node - it is missing parent information.');
|
||||
}
|
||||
const startPos = node.getStart();
|
||||
const endPos = node.getEnd();
|
||||
const {line: startLine, character: startCol} = ts.getLineAndCharacterOfPosition(file, startPos);
|
||||
return {startLine, startCol, startPos, endPos};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the expression does not represent an empty element in an array literal.
|
||||
* For example in `[,foo]` the first element is "empty".
|
||||
*/
|
||||
function isNotEmptyElement(e: ts.Expression|ts.SpreadElement|
|
||||
ts.OmittedExpression): e is ts.Expression|ts.SpreadElement {
|
||||
return !ts.isOmittedExpression(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the expression is not a spread element of an array literal.
|
||||
* For example in `[x, ...rest]` the `...rest` expression is a spread element.
|
||||
*/
|
||||
function isNotSpreadElement(e: ts.Expression|ts.SpreadElement): e is ts.Expression {
|
||||
return !ts.isSpreadElement(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the expression can be considered a text based property name.
|
||||
*/
|
||||
function isPropertyName(e: ts.PropertyName): e is ts.Identifier|ts.StringLiteral|ts.NumericLiteral {
|
||||
return ts.isIdentifier(e) || ts.isStringLiteral(e) || ts.isNumericLiteral(e);
|
||||
}
|
18
packages/compiler-cli/linker/src/ast/utils.ts
Normal file
18
packages/compiler-cli/linker/src/ast/utils.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {FatalLinkerError} from '../fatal_linker_error';
|
||||
|
||||
/**
|
||||
* Assert that the given `node` is of the type guarded by the `predicate` function.
|
||||
*/
|
||||
export function assert<T, K extends T>(
|
||||
node: T, predicate: (node: T) => node is K, expected: string): asserts node is K {
|
||||
if (!predicate(node)) {
|
||||
throw new FatalLinkerError(node, `Unsupported syntax, expected ${expected}.`);
|
||||
}
|
||||
}
|
31
packages/compiler-cli/linker/src/fatal_linker_error.ts
Normal file
31
packages/compiler-cli/linker/src/fatal_linker_error.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* An unrecoverable error during linking.
|
||||
*/
|
||||
export class FatalLinkerError extends Error {
|
||||
readonly type = 'FatalLinkerError';
|
||||
|
||||
/**
|
||||
* Create a new FatalLinkerError.
|
||||
*
|
||||
* @param node The AST node where the error occurred.
|
||||
* @param message A description of the error.
|
||||
*/
|
||||
constructor(public node: unknown, message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given object `e` is a FatalLinkerError.
|
||||
*/
|
||||
export function isFatalLinkerError(e: any): e is FatalLinkerError {
|
||||
return e && e.type === 'FatalLinkerError';
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* This interface represents the lexical scope of a partial declaration in the source code.
|
||||
*
|
||||
* For example, if you had the following code:
|
||||
*
|
||||
* ```
|
||||
* function foo() {
|
||||
* function bar () {
|
||||
* $ngDeclareDirective({...});
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The `DeclarationScope` of the `$ngDeclareDirective()` call is the body of the `bar()` function.
|
||||
*
|
||||
* The `FileLinker` uses this object to identify the lexical scope of any constant statements that
|
||||
* might be generated by the linking process (i.e. where the `ConstantPool` lives for a set of
|
||||
* partial linkers).
|
||||
*/
|
||||
export interface DeclarationScope<TSharedConstantScope, TExpression> {
|
||||
/**
|
||||
* Get a `TSharedConstantScope` object that can be used to reference the lexical scope where any
|
||||
* shared constant statements would be inserted.
|
||||
*
|
||||
* This object is generic because different AST implementations will need different
|
||||
* `TConstantScope` types to be able to insert shared constant statements. For example in Babel
|
||||
* this would be a `NodePath` object; in TS it would just be a `Node` object.
|
||||
*
|
||||
* If it is not possible to find such a shared scope, then constant statements will be wrapped up
|
||||
* with their generated linked definition expression, in the form of an IIFE.
|
||||
*
|
||||
* @param expression the expression that points to the Angular core framework import.
|
||||
* @returns a reference to a reference object for where the shared constant statements will be
|
||||
* inserted, or `null` if it is not possible to have a shared scope.
|
||||
*/
|
||||
getConstantScopeRef(expression: TExpression): TSharedConstantScope|null;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {LinkerImportGenerator} from '../../linker_import_generator';
|
||||
import {LinkerEnvironment} from '../linker_environment';
|
||||
|
||||
/**
|
||||
* This class represents (from the point of view of the `FileLinker`) the scope in which
|
||||
* statements and expressions related to a linked partial declaration will be emitted.
|
||||
*
|
||||
* It holds a copy of a `ConstantPool` that is used to capture any constant statements that need to
|
||||
* be emitted in this context.
|
||||
*
|
||||
* This implementation will emit the definition and the constant statements separately.
|
||||
*/
|
||||
export class EmitScope<TStatement, TExpression> {
|
||||
readonly constantPool = new ConstantPool();
|
||||
|
||||
constructor(
|
||||
protected readonly ngImport: TExpression,
|
||||
protected readonly linkerEnvironment: LinkerEnvironment<TStatement, TExpression>) {}
|
||||
|
||||
/**
|
||||
* Translate the given Output AST definition expression into a generic `TExpression`.
|
||||
*
|
||||
* Use a `LinkerImportGenerator` to handle any imports in the definition.
|
||||
*/
|
||||
translateDefinition(definition: o.Expression): TExpression {
|
||||
return this.linkerEnvironment.translator.translateExpression(
|
||||
definition, new LinkerImportGenerator(this.ngImport));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any constant statements that are shared between all uses of this `EmitScope`.
|
||||
*/
|
||||
getConstantStatements(): TStatement[] {
|
||||
const {translator} = this.linkerEnvironment;
|
||||
const importGenerator = new LinkerImportGenerator(this.ngImport);
|
||||
return this.constantPool.statements.map(
|
||||
statement => translator.translateStatement(statement, importGenerator));
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {EmitScope} from './emit_scope';
|
||||
|
||||
/**
|
||||
* This class is a specialization of the `EmitScope` class that is designed for the situation where
|
||||
* there is no clear shared scope for constant statements. In this case they are bundled with the
|
||||
* translated definition inside an IIFE.
|
||||
*/
|
||||
export class IifeEmitScope<TStatement, TExpression> extends EmitScope<TStatement, TExpression> {
|
||||
/**
|
||||
* Translate the given Output AST definition expression into a generic `TExpression`.
|
||||
*
|
||||
* Wraps the output from `EmitScope.translateDefinition()` and `EmitScope.getConstantStatements()`
|
||||
* in an IIFE.
|
||||
*/
|
||||
translateDefinition(definition: o.Expression): TExpression {
|
||||
const {factory} = this.linkerEnvironment;
|
||||
const constantStatements = super.getConstantStatements();
|
||||
|
||||
const returnStatement = factory.createReturnStatement(super.translateDefinition(definition));
|
||||
const body = factory.createBlock([...constantStatements, returnStatement]);
|
||||
const fn = factory.createFunctionExpression(/* name */ null, /* args */[], body);
|
||||
return factory.createCallExpression(fn, /* args */[], /* pure */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is not valid to call this method, since there will be no shared constant statements - they
|
||||
* are already emitted in the IIFE alongside the translated definition.
|
||||
*/
|
||||
getConstantStatements(): TStatement[] {
|
||||
throw new Error('BUG - IifeEmitScope should not expose any constant statements');
|
||||
}
|
||||
}
|
94
packages/compiler-cli/linker/src/file_linker/file_linker.ts
Normal file
94
packages/compiler-cli/linker/src/file_linker/file_linker.ts
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AstObject} from '../ast/ast_value';
|
||||
import {DeclarationScope} from './declaration_scope';
|
||||
import {EmitScope} from './emit_scopes/emit_scope';
|
||||
import {IifeEmitScope} from './emit_scopes/iife_emit_scope';
|
||||
import {LinkerEnvironment} from './linker_environment';
|
||||
import {PartialLinkerSelector} from './partial_linkers/partial_linker_selector';
|
||||
|
||||
export const NO_STATEMENTS: Readonly<any[]> = [] as const;
|
||||
|
||||
/**
|
||||
* This class is responsible for linking all the partial declarations found in a single file.
|
||||
*/
|
||||
export class FileLinker<TConstantScope, TStatement, TExpression> {
|
||||
private linkerSelector = new PartialLinkerSelector<TStatement, TExpression>();
|
||||
private emitScopes = new Map<TConstantScope, EmitScope<TStatement, TExpression>>();
|
||||
|
||||
constructor(
|
||||
private linkerEnvironment: LinkerEnvironment<TStatement, TExpression>,
|
||||
private sourceUrl: string, readonly code: string) {}
|
||||
|
||||
/**
|
||||
* Return true if the given callee name matches a partial declaration that can be linked.
|
||||
*/
|
||||
isPartialDeclaration(calleeName: string): boolean {
|
||||
return this.linkerSelector.supportsDeclaration(calleeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link the metadata extracted from the args of a call to a partial declaration function.
|
||||
*
|
||||
* The `declarationScope` is used to determine the scope and strategy of emission of the linked
|
||||
* definition and any shared constant statements.
|
||||
*
|
||||
* @param declarationFn the name of the function used to declare the partial declaration - e.g.
|
||||
* `$ngDeclareDirective`.
|
||||
* @param args the arguments passed to the declaration function.
|
||||
* @param declarationScope the scope that contains this call to the declaration function.
|
||||
*/
|
||||
linkPartialDeclaration(
|
||||
declarationFn: string, args: TExpression[],
|
||||
declarationScope: DeclarationScope<TConstantScope, TExpression>): TExpression {
|
||||
if (args.length !== 1) {
|
||||
throw new Error(
|
||||
`Invalid function call: It should have only a single object literal argument, but contained ${
|
||||
args.length}.`);
|
||||
}
|
||||
|
||||
const metaObj = AstObject.parse(args[0], this.linkerEnvironment.host);
|
||||
const ngImport = metaObj.getNode('ngImport');
|
||||
const emitScope = this.getEmitScope(ngImport, declarationScope);
|
||||
|
||||
const version = metaObj.getNumber('version');
|
||||
const linker = this.linkerSelector.getLinker(declarationFn, version);
|
||||
const definition =
|
||||
linker.linkPartialDeclaration(this.sourceUrl, this.code, emitScope.constantPool, metaObj);
|
||||
|
||||
return emitScope.translateDefinition(definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the shared constant statements and their associated constant scope references, so
|
||||
* that they can be inserted into the source code.
|
||||
*/
|
||||
getConstantStatements(): {constantScope: TConstantScope, statements: TStatement[]}[] {
|
||||
const results: {constantScope: TConstantScope, statements: TStatement[]}[] = [];
|
||||
for (const [constantScope, emitScope] of this.emitScopes.entries()) {
|
||||
const statements = emitScope.getConstantStatements();
|
||||
results.push({constantScope, statements});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private getEmitScope(
|
||||
ngImport: TExpression, declarationScope: DeclarationScope<TConstantScope, TExpression>):
|
||||
EmitScope<TStatement, TExpression> {
|
||||
const constantScope = declarationScope.getConstantScopeRef(ngImport);
|
||||
if (constantScope === null) {
|
||||
// There is no constant scope so we will emit extra statements into the definition IIFE.
|
||||
return new IifeEmitScope(ngImport, this.linkerEnvironment);
|
||||
}
|
||||
|
||||
if (!this.emitScopes.has(constantScope)) {
|
||||
this.emitScopes.set(constantScope, new EmitScope(ngImport, this.linkerEnvironment));
|
||||
}
|
||||
return this.emitScopes.get(constantScope)!;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AstFactory} from '@angular/compiler-cli/src/ngtsc/translator';
|
||||
|
||||
import {AstHost} from '../ast/ast_host';
|
||||
import {DEFAULT_LINKER_OPTIONS, LinkerOptions} from './linker_options';
|
||||
import {Translator} from './translator';
|
||||
|
||||
export class LinkerEnvironment<TStatement, TExpression> {
|
||||
readonly translator = new Translator<TStatement, TExpression>(this.factory);
|
||||
private constructor(
|
||||
readonly host: AstHost<TExpression>, readonly factory: AstFactory<TStatement, TExpression>,
|
||||
readonly options: LinkerOptions) {}
|
||||
|
||||
static create<TStatement, TExpression>(
|
||||
host: AstHost<TExpression>, factory: AstFactory<TStatement, TExpression>,
|
||||
options: Partial<LinkerOptions>): LinkerEnvironment<TStatement, TExpression> {
|
||||
return new LinkerEnvironment(host, factory, {...DEFAULT_LINKER_OPTIONS, ...options});
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options to configure the linking behavior.
|
||||
*/
|
||||
export interface LinkerOptions {
|
||||
/**
|
||||
* Whether to generate legacy i18n message ids.
|
||||
* The default is `true`.
|
||||
*/
|
||||
enableI18nLegacyMessageIdFormat: boolean;
|
||||
/**
|
||||
* Whether to convert all line-endings in ICU expressions to `\n` characters.
|
||||
* The default is `false`.
|
||||
*/
|
||||
i18nNormalizeLineEndingsInICUs: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default linker options to use if properties are not provided.
|
||||
*/
|
||||
export const DEFAULT_LINKER_OPTIONS: LinkerOptions = {
|
||||
enableI18nLegacyMessageIdFormat: true,
|
||||
i18nNormalizeLineEndingsInICUs: false,
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ConstantPool} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `$ngDeclareComponent()` call expressions.
|
||||
*/
|
||||
export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
PartialLinker<TStatement, TExpression> {
|
||||
linkPartialDeclaration(
|
||||
sourceUrl: string, code: string, constantPool: ConstantPool,
|
||||
metaObj: AstObject<TExpression>): o.Expression {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user