提交代码进行测试
This commit is contained in:
parent
03cab937c1
commit
ea5de990f7
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
_
|
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn -s ng-dev commit-message pre-commit-validate --file $1;
|
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn -s ng-dev format staged;
|
4
.husky/prepare-commit-msg
Normal file
4
.husky/prepare-commit-msg
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname $0)/_/husky.sh"
|
||||
|
||||
yarn -s ng-dev commit-message restore-commit-message-draft $1 $2;
|
22590
aio/package-lock.json
generated
Normal file
22590
aio/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -104,6 +104,7 @@
|
||||
"@angular/service-worker": "11.2.3",
|
||||
"@bazel/bazelisk": "^1.7.5",
|
||||
"@webcomponents/custom-elements": "1.2.1",
|
||||
"husky": "^6.0.0",
|
||||
"rxjs": "^6.5.3",
|
||||
"tslib": "^2.1.0",
|
||||
"zone.js": "~0.11.4"
|
||||
@ -127,7 +128,7 @@
|
||||
"codelyzer": "^6.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.13",
|
||||
"dgeni": "^0.4.14",
|
||||
"dgeni-packages": "^0.28.4",
|
||||
"entities": "^1.1.1",
|
||||
"esbuild": "^0.9.0",
|
||||
@ -154,9 +155,9 @@
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"light-server": "^2.6.2",
|
||||
"lighthouse": "^7.2.0",
|
||||
"lighthouse": "^7.3.0",
|
||||
"lighthouse-logger": "^1.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lunr": "^2.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protractor": "~7.0.0",
|
||||
|
17
integration/cli-elements-universal/.browserslistrc
Normal file
17
integration/cli-elements-universal/.browserslistrc
Normal file
@ -0,0 +1,17 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
16
integration/cli-elements-universal/.editorconfig
Normal file
16
integration/cli-elements-universal/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
46
integration/cli-elements-universal/.gitignore
vendored
Normal file
46
integration/cli-elements-universal/.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
13
integration/cli-elements-universal/README.md
Normal file
13
integration/cli-elements-universal/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# CliElementsUniversal
|
||||
|
||||
This project tests the integration of Angular Elements (`@angular/elements`) with SSR (via `@angular/platform-server`).
|
||||
|
||||
The project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.1.4.
|
||||
Support for Angular Elements was added with `ng add @angular/elements` and for SSR with `ng generate app-shell`.
|
||||
|
||||
What this project tests is that an app can be successfully SSR'd even when it uses `@angular/elements`, which relies on certain DOM built-ins being available as soon as it is imported.
|
||||
This is tested by generating the [app-shell](https://angular.io/guide/app-shell) (using `ng run cli-elements-universal:app-shell:production`) and then verifying that the `index.html` file was generated correctly.
|
||||
(See, the `test-ssr` script in [package.json](./package.json).)
|
||||
|
||||
NOTE:
|
||||
Currently, `domino` (the server-side DOM implementation used by `@angular/platform-server`) does not support [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components), so the Custom Elements functionality does not work on the server.
|
168
integration/cli-elements-universal/angular.json
Normal file
168
integration/cli-elements-universal/angular.json
Normal file
@ -0,0 +1,168 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"cli-elements-universal": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/cli-elements-universal/browser",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"progress": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "cli-elements-universal:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "cli-elements-universal:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "cli-elements-universal:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"watch": false
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json",
|
||||
"tsconfig.server.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "cli-elements-universal:serve",
|
||||
"webdriverUpdate": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "cli-elements-universal:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/cli-elements-universal/server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "tsconfig.server.json",
|
||||
"progress": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"outputHashing": "media",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"sourceMap": false,
|
||||
"optimization": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"app-shell": {
|
||||
"builder": "@angular-devkit/build-angular:app-shell",
|
||||
"options": {
|
||||
"browserTarget": "cli-elements-universal:build",
|
||||
"serverTarget": "cli-elements-universal:server",
|
||||
"route": ""
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "cli-elements-universal:build:production",
|
||||
"serverTarget": "cli-elements-universal:server:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "cli-elements-universal"
|
||||
}
|
42
integration/cli-elements-universal/e2e/protractor.conf.js
Normal file
42
integration/cli-elements-universal/e2e/protractor.conf.js
Normal file
@ -0,0 +1,42 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
chromeOptions: {
|
||||
binary: require('puppeteer').executablePath(),
|
||||
// See /integration/README.md#browser-tests for more info on these args
|
||||
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
|
||||
},
|
||||
},
|
||||
directConnect: true,
|
||||
SELENIUM_PROMISE_MANAGER: false,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({
|
||||
spec: {
|
||||
displayStacktrace: StacktraceOption.PRETTY
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
22
integration/cli-elements-universal/e2e/src/app.e2e-spec.ts
Normal file
22
integration/cli-elements-universal/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser.
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
const errorLogs = logs.filter(({level}) => level === logging.Level.SEVERE);
|
||||
expect(errorLogs).toEqual([]);
|
||||
});
|
||||
|
||||
it('should display welcome message', async () => {
|
||||
await page.navigateTo();
|
||||
expect(await page.getTitleText()).toBe('cli-elements-universal app is running!');
|
||||
});
|
||||
});
|
11
integration/cli-elements-universal/e2e/src/app.po.ts
Normal file
11
integration/cli-elements-universal/e2e/src/app.po.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
async navigateTo(): Promise<unknown> {
|
||||
return browser.get(browser.baseUrl);
|
||||
}
|
||||
|
||||
async getTitleText(): Promise<string> {
|
||||
return element(by.css('h1')).getText();
|
||||
}
|
||||
}
|
13
integration/cli-elements-universal/e2e/tsconfig.json
Normal file
13
integration/cli-elements-universal/e2e/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
47
integration/cli-elements-universal/karma.conf.js
Normal file
47
integration/cli-elements-universal/karma.conf.js
Normal file
@ -0,0 +1,47 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
// Env var CHROME_BIN is later picked up by karma-chrome-launcher that is triggered by
|
||||
// `browsers: ['ChromeHeadlessNoSandbox']` below.
|
||||
// See https://github.com/karma-runner/karma-chrome-launcher#usage for more info.
|
||||
process.env.CHROME_BIN = require('puppeteer').executablePath();
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/cli-elements-universal'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
],
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
customLaunchers: {
|
||||
ChromeHeadlessNoSandbox: {
|
||||
base: 'ChromeHeadless',
|
||||
// See /integration/README.md#browser-tests for more info on these args
|
||||
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
|
||||
}
|
||||
},
|
||||
browsers: ['ChromeHeadlessNoSandbox'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
55
integration/cli-elements-universal/package.json
Normal file
55
integration/cli-elements-universal/package.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "cli-elements-universal",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"pretest": "ng version",
|
||||
"test": "ng test && yarn e2e --prod && yarn test-ssr",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e --port 0",
|
||||
"pretest-ssr": "yarn ng run cli-elements-universal:app-shell:production",
|
||||
"test-ssr": "node --eval \"assert(fs.readFileSync('dist/cli-elements-universal/browser/index.html', 'utf8').includes('<app-title-ce>'), 'Expected \\'index.html\\' to contain \\'<app-title-ce>\\'.');\""
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/elements": "file:../../dist/packages-dist/elements",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"document-register-element": "^1.7.2",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"tslib": "file:../../node_modules/tslib",
|
||||
"zone.js": "file:../../dist/zone.js-dist/archive/zone.js.tgz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "file:../../node_modules/@angular-devkit/build-angular",
|
||||
"@angular/cli": "file:../../node_modules/@angular/cli",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@types/jasmine": "file:../../node_modules/@types/jasmine",
|
||||
"@types/node": "file:../../node_modules/@types/node",
|
||||
"codelyzer": "^6.0.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~5.2.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.0.3",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"protractor": "file:../../node_modules/protractor",
|
||||
"puppeteer": "file:../../node_modules/puppeteer",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "file:../../node_modules/typescript"
|
||||
},
|
||||
"//resolutions-comment": "Ensure a single version of webdriver-manager which comes from root node_modules that has already run webdriver-manager update",
|
||||
"resolutions": {
|
||||
"**/webdriver-manager": "file:../../node_modules/webdriver-manager"
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
TestTitleComponent,
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
],
|
||||
});
|
||||
await TestBed.compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const appComp = fixture.componentInstance;
|
||||
|
||||
expect(appComp).toBeTruthy();
|
||||
expect(appComp.title).toBe('cli-elements-universal');
|
||||
});
|
||||
|
||||
it('should pass the app title to the `TitleComponent`', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const titleDebugElement = fixture.debugElement.query(By.directive(TestTitleComponent));
|
||||
const titleComp: TestTitleComponent = titleDebugElement.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(titleComp).toBeTruthy();
|
||||
expect(titleComp.appName).toBe('cli-elements-universal');
|
||||
});
|
||||
|
||||
// Helpers
|
||||
@Component({
|
||||
selector: 'app-title-ce',
|
||||
template: '',
|
||||
})
|
||||
class TestTitleComponent {
|
||||
@Input() appName = '';
|
||||
}
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<app-title-ce [appName]="title"></app-title-ce>',
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'cli-elements-universal';
|
||||
}
|
32
integration/cli-elements-universal/src/app/app.module.ts
Normal file
32
integration/cli-elements-universal/src/app/app.module.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, Inject, Injector, NgModule, PLATFORM_ID } from '@angular/core';
|
||||
import { createCustomElement } from '@angular/elements';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { TitleComponent } from './title.component';
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [
|
||||
AppComponent,
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
TitleComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
RouterModule.forRoot([]),
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(injector: Injector, @Inject(PLATFORM_ID) platformId: object) {
|
||||
if (isPlatformBrowser(platformId)) {
|
||||
customElements.define('app-title-ce', createCustomElement(TitleComponent, {injector}));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ServerModule } from '@angular/platform-server';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [
|
||||
AppComponent,
|
||||
],
|
||||
imports: [
|
||||
AppModule,
|
||||
RouterModule.forRoot([]),
|
||||
ServerModule,
|
||||
],
|
||||
})
|
||||
export class AppServerModule {}
|
@ -0,0 +1,27 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TitleComponent } from './title.component';
|
||||
|
||||
describe('TitleComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({declarations: [TitleComponent]});
|
||||
await TestBed.compileComponents();
|
||||
});
|
||||
|
||||
it('should create the component', () => {
|
||||
const fixture = TestBed.createComponent(TitleComponent);
|
||||
const titleComp = fixture.componentInstance;
|
||||
|
||||
expect(titleComp).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the title using the specified app name', () => {
|
||||
const fixture = TestBed.createComponent(TitleComponent);
|
||||
const titleComp = fixture.componentInstance;
|
||||
const titleElem = fixture.nativeElement;
|
||||
|
||||
titleComp.appName = 'Test';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(titleElem.querySelector('h1').textContent).toBe('Test app is running!');
|
||||
});
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-title',
|
||||
template: '<h1>{{ appName }} app is running!</h1>',
|
||||
})
|
||||
export class TitleComponent {
|
||||
@Input() appName = 'Unknown';
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
BIN
integration/cli-elements-universal/src/favicon.ico
Normal file
BIN
integration/cli-elements-universal/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 948 B |
13
integration/cli-elements-universal/src/index.html
Normal file
13
integration/cli-elements-universal/src/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CliElementsUniversal</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
11
integration/cli-elements-universal/src/main.server.ts
Normal file
11
integration/cli-elements-universal/src/main.server.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import '@angular/platform-server/init';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
export { AppServerModule } from './app/app.server.module';
|
||||
export { renderModule, renderModuleFactory } from '@angular/platform-server';
|
14
integration/cli-elements-universal/src/main.ts
Normal file
14
integration/cli-elements-universal/src/main.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
});
|
64
integration/cli-elements-universal/src/polyfills.ts
Normal file
64
integration/cli-elements-universal/src/polyfills.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
import 'document-register-element';
|
1
integration/cli-elements-universal/src/styles.css
Normal file
1
integration/cli-elements-universal/src/styles.css
Normal file
@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
25
integration/cli-elements-universal/src/test.ts
Normal file
25
integration/cli-elements-universal/src/test.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
15
integration/cli-elements-universal/tsconfig.app.json
Normal file
15
integration/cli-elements-universal/tsconfig.app.json
Normal file
@ -0,0 +1,15 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
29
integration/cli-elements-universal/tsconfig.json
Normal file
29
integration/cli-elements-universal/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
17
integration/cli-elements-universal/tsconfig.server.json
Normal file
17
integration/cli-elements-universal/tsconfig.server.json
Normal file
@ -0,0 +1,17 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/server",
|
||||
"target": "es2016",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/main.server.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"entryModule": "./src/app/app.server.module#AppServerModule"
|
||||
}
|
||||
}
|
18
integration/cli-elements-universal/tsconfig.spec.json
Normal file
18
integration/cli-elements-universal/tsconfig.spec.json
Normal file
@ -0,0 +1,18 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
152
integration/cli-elements-universal/tslint.json
Normal file
152
integration/cli-elements-universal/tslint.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"align": {
|
||||
"options": [
|
||||
"parameters",
|
||||
"statements"
|
||||
]
|
||||
},
|
||||
"array-type": false,
|
||||
"arrow-return-shorthand": true,
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"eofline": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": {
|
||||
"options": [
|
||||
"spaces"
|
||||
]
|
||||
},
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"semicolon": {
|
||||
"options": [
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"space-before-function-paren": {
|
||||
"options": {
|
||||
"anonymous": "never",
|
||||
"asyncArrow": "always",
|
||||
"constructor": "never",
|
||||
"method": "never",
|
||||
"named": "never"
|
||||
}
|
||||
},
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature"
|
||||
],
|
||||
"typedef-whitespace": {
|
||||
"options": [
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
},
|
||||
{
|
||||
"call-signature": "onespace",
|
||||
"index-signature": "onespace",
|
||||
"parameter": "onespace",
|
||||
"property-declaration": "onespace",
|
||||
"variable-declaration": "onespace"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variable-name": {
|
||||
"options": [
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case"
|
||||
]
|
||||
},
|
||||
"whitespace": {
|
||||
"options": [
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type",
|
||||
"check-typecast"
|
||||
]
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
9187
integration/cli-elements-universal/yarn.lock
Normal file
9187
integration/cli-elements-universal/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
13
integration/forms/.editorconfig
Normal file
13
integration/forms/.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
46
integration/forms/.gitignore
vendored
Normal file
46
integration/forms/.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
27
integration/forms/README.md
Normal file
27
integration/forms/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Forms test app
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli).
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](https://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
144
integration/forms/angular.json
Normal file
144
integration/forms/angular.json
Normal file
@ -0,0 +1,144 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"forms": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"progress": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "forms:build"
|
||||
},
|
||||
"configurations": {
|
||||
"dev": {
|
||||
"browserTarget": "forms:build:dev"
|
||||
},
|
||||
"production": {
|
||||
"browserTarget": "forms:build:production"
|
||||
},
|
||||
"ci": {
|
||||
"progress": false
|
||||
},
|
||||
"ci-production": {
|
||||
"browserTarget": "forms:build:production",
|
||||
"progress": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "forms:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"progress": false,
|
||||
"watch": false
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "forms:serve",
|
||||
"webdriverUpdate": false
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "forms:serve:production"
|
||||
},
|
||||
"ci": {
|
||||
"devServerTarget": "forms:serve:ci"
|
||||
},
|
||||
"ci-production": {
|
||||
"devServerTarget": "forms:serve:ci-production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
"defaultProject": "forms"
|
||||
}
|
12
integration/forms/browserslist
Normal file
12
integration/forms/browserslist
Normal file
@ -0,0 +1,12 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
not IE 11 # For IE 11 support, remove 'not'.
|
36
integration/forms/e2e/protractor.conf.js
Normal file
36
integration/forms/e2e/protractor.conf.js
Normal file
@ -0,0 +1,36 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
chromeOptions: {
|
||||
binary: require('puppeteer').executablePath(),
|
||||
// See /integration/README.md#browser-tests for more info on these args
|
||||
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
|
||||
},
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
24
integration/forms/e2e/src/app.e2e-spec.ts
Normal file
24
integration/forms/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {browser, logging} from 'protractor';
|
||||
|
||||
import {AppPage} from './app.po';
|
||||
|
||||
describe('forms app', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display title', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Forms app');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
11
integration/forms/e2e/src/app.po.ts
Normal file
11
integration/forms/e2e/src/app.po.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {browser, by, element} from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get(browser.baseUrl) as Promise<any>;
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
13
integration/forms/e2e/tsconfig.json
Normal file
13
integration/forms/e2e/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
44
integration/forms/karma.conf.js
Normal file
44
integration/forms/karma.conf.js
Normal file
@ -0,0 +1,44 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
// Env var CHROME_BIN is later picked up by karma-chrome-launcher that is triggered by
|
||||
// `browsers: ['ChromeHeadlessNoSandbox']` below.
|
||||
// See https://github.com/karma-runner/karma-chrome-launcher#usage for more info.
|
||||
process.env.CHROME_BIN = require('puppeteer').executablePath();
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/latest-app'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
customLaunchers: {
|
||||
ChromeHeadlessNoSandbox: {
|
||||
base: 'ChromeHeadless',
|
||||
// See /integration/README.md#browser-tests for more info on these args
|
||||
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
|
||||
}
|
||||
},
|
||||
browsers: ['ChromeHeadlessNoSandbox'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
54
integration/forms/package.json
Normal file
54
integration/forms/package.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "forms",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --prod",
|
||||
"pretest": "ng version",
|
||||
"test": "ng test && yarn e2e --configuration=ci && yarn e2e --configuration=ci-production",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e --port 0",
|
||||
"postinstall": "ngcc --properties es2015 --create-ivy-entry-points"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"tslib": "file:../../node_modules/tslib",
|
||||
"zone.js": "file:../../dist/zone.js-dist/archive/zone.js.tgz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "file:../../node_modules/@angular-devkit/build-angular",
|
||||
"@angular/cli": "file:../../node_modules/@angular/cli",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/language-service": "file:../../dist/packages-dist/language-service",
|
||||
"@types/jasmine": "file:../../node_modules/@types/jasmine",
|
||||
"@types/jasminewd2": "file:../../node_modules/@types/jasminewd2",
|
||||
"@types/node": "file:../../node_modules/@types/node",
|
||||
"codelyzer": "5.2.0",
|
||||
"jasmine-core": "3.5.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"karma": "4.4.0",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "2.1.0",
|
||||
"karma-jasmine": "2.0.1",
|
||||
"karma-jasmine-html-reporter": "1.4.2",
|
||||
"protractor": "file:../../node_modules/protractor",
|
||||
"puppeteer": "file:../../node_modules/puppeteer",
|
||||
"ts-node": "8.3.0",
|
||||
"tslint": "5.18.0",
|
||||
"typescript": "file:../../node_modules/typescript"
|
||||
},
|
||||
"//resolutions-comment": "Ensure a single version of webdriver-manager which comes from root node_modules that has already run webdriver-manager update",
|
||||
"resolutions": {
|
||||
"**/webdriver-manager": "file:../../node_modules/webdriver-manager"
|
||||
}
|
||||
}
|
0
integration/forms/src/app/app.component.css
Normal file
0
integration/forms/src/app/app.component.css
Normal file
3
integration/forms/src/app/app.component.html
Normal file
3
integration/forms/src/app/app.component.html
Normal file
@ -0,0 +1,3 @@
|
||||
<h1>Forms app</h1>
|
||||
<app-template-forms></app-template-forms>
|
||||
<app-reactive-forms></app-reactive-forms>
|
18
integration/forms/src/app/app.component.spec.ts
Normal file
18
integration/forms/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
});
|
114
integration/forms/src/app/app.component.ts
Normal file
114
integration/forms/src/app/app.component.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-template-forms',
|
||||
template: `
|
||||
<form novalidate>
|
||||
<div ngModelGroup="profileForm">
|
||||
<div>
|
||||
First Name:
|
||||
<input name="first" ngModel required />
|
||||
</div>
|
||||
<div>
|
||||
Last Name:
|
||||
<input name="last" ngModel />
|
||||
</div>
|
||||
<div>
|
||||
Subscribe:
|
||||
<input name="subscribed" type="checkbox" ngModel />
|
||||
</div>
|
||||
|
||||
<div>Disabled: <input name="foo" ngModel disabled /></div>
|
||||
|
||||
<div *ngFor="let city of addresses; let i = index">
|
||||
City <input [(ngModel)]="addresses[i].city" name="name" />
|
||||
</div>
|
||||
|
||||
<button (click)="addCity()">Add City</button>
|
||||
</div>
|
||||
</form>`,
|
||||
})
|
||||
export class TemplateFormsComponent {
|
||||
name = {first: 'Nancy', last: 'Drew', subscribed: true};
|
||||
addresses = [{city: 'Toronto'}];
|
||||
constructor() {
|
||||
// We use this reference in our test
|
||||
(window as any).templateFormsComponent = this;
|
||||
}
|
||||
|
||||
addCity() {
|
||||
this.addresses.push(({city: ''}));
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-reactive-forms',
|
||||
template: `
|
||||
<form [formGroup]="profileForm">
|
||||
<div>
|
||||
First Name:
|
||||
<input type="text" formControlName="firstName" />
|
||||
</div>
|
||||
<div>
|
||||
Last Name:
|
||||
<input type="text" formControlName="lastName" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Subscribe:
|
||||
<input type="checkbox" formControlName="subscribed" />
|
||||
</div>
|
||||
|
||||
<div>Disabled: <input formControlName="disabledInput" /></div>
|
||||
<div formArrayName="addresses">
|
||||
<div *ngFor="let item of itemControls; let i = index" [formGroupName]="i">
|
||||
<div>City: <input formControlName="city" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<button (click)="addCity()">Add City</button>
|
||||
</form>`,
|
||||
})
|
||||
export class ReactiveFormsComponent {
|
||||
profileForm!: FormGroup;
|
||||
addresses!: FormArray;
|
||||
|
||||
get itemControls() {
|
||||
return (this.profileForm.get('addresses') as FormArray).controls;
|
||||
}
|
||||
|
||||
constructor(private formBuilder: FormBuilder) {
|
||||
// We use this reference in our test
|
||||
(window as any).reactiveFormsComponent = this;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.profileForm = new FormGroup({
|
||||
firstName: new FormControl('', Validators.required),
|
||||
lastName: new FormControl(''),
|
||||
addresses: new FormArray([]),
|
||||
subscribed: new FormControl(),
|
||||
disabledInput: new FormControl({value: '', disabled: true}),
|
||||
});
|
||||
|
||||
this.addCity();
|
||||
}
|
||||
|
||||
createItem(): FormGroup {
|
||||
return this.formBuilder.group({
|
||||
city: '',
|
||||
});
|
||||
}
|
||||
|
||||
addCity(): void {
|
||||
this.addresses = this.profileForm.get('addresses') as FormArray;
|
||||
this.addresses.push(this.createItem());
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
23
integration/forms/src/app/app.module.ts
Normal file
23
integration/forms/src/app/app.module.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
|
||||
import {AppComponent, ReactiveFormsComponent, TemplateFormsComponent} from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
TemplateFormsComponent,
|
||||
ReactiveFormsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
0
integration/forms/src/assets/.gitkeep
Normal file
0
integration/forms/src/assets/.gitkeep
Normal file
3
integration/forms/src/environments/environment.prod.ts
Normal file
3
integration/forms/src/environments/environment.prod.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
16
integration/forms/src/environments/environment.ts
Normal file
16
integration/forms/src/environments/environment.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
|
BIN
integration/forms/src/favicon.ico
Normal file
BIN
integration/forms/src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 948 B |
14
integration/forms/src/index.html
Normal file
14
integration/forms/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Forms</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
12
integration/forms/src/main.ts
Normal file
12
integration/forms/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
63
integration/forms/src/polyfills.ts
Normal file
63
integration/forms/src/polyfills.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags.ts';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
1
integration/forms/src/styles.css
Normal file
1
integration/forms/src/styles.css
Normal file
@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
20
integration/forms/src/test.ts
Normal file
20
integration/forms/src/test.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: any;
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
14
integration/forms/tsconfig.app.json
Normal file
14
integration/forms/tsconfig.app.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
26
integration/forms/tsconfig.json
Normal file
26
integration/forms/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
}
|
||||
}
|
18
integration/forms/tsconfig.spec.json
Normal file
18
integration/forms/tsconfig.spec.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
91
integration/forms/tslint.json
Normal file
91
integration/forms/tslint.json
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"array-type": false,
|
||||
"arrow-parens": false,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
],
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": false,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"trailing-comma": false,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
]
|
||||
}
|
10009
integration/forms/yarn.lock
Normal file
10009
integration/forms/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
69
integration/typings_test_ts42/include-all.ts
Normal file
69
integration/typings_test_ts42/include-all.ts
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @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 animations from '@angular/animations';
|
||||
import * as animationsBrowser from '@angular/animations/browser';
|
||||
import * as animationsBrowserTesting from '@angular/animations/browser/testing';
|
||||
import * as common from '@angular/common';
|
||||
import * as commonHttp from '@angular/common/http';
|
||||
import * as commonTesting from '@angular/common/testing';
|
||||
import * as commonHttpTesting from '@angular/common/testing';
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as core from '@angular/core';
|
||||
import * as coreTesting from '@angular/core/testing';
|
||||
import * as elements from '@angular/elements';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynamic/testing';
|
||||
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerInit from '@angular/platform-server/init';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as routerUpgrade from '@angular/router/upgrade';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
import * as upgradeStatic from '@angular/upgrade/static';
|
||||
import * as upgradeTesting from '@angular/upgrade/static/testing';
|
||||
|
||||
export default {
|
||||
animations,
|
||||
animationsBrowser,
|
||||
animationsBrowserTesting,
|
||||
common,
|
||||
commonTesting,
|
||||
commonHttp,
|
||||
commonHttpTesting,
|
||||
compiler,
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
elements,
|
||||
forms,
|
||||
platformBrowser,
|
||||
platformBrowserTesting,
|
||||
platformBrowserDynamic,
|
||||
platformBrowserDynamicTesting,
|
||||
platformBrowserAnimations,
|
||||
platformServer,
|
||||
platformServerInit,
|
||||
platformServerTesting,
|
||||
router,
|
||||
routerTesting,
|
||||
routerUpgrade,
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
upgradeStatic,
|
||||
upgradeTesting,
|
||||
};
|
28
integration/typings_test_ts42/package.json
Normal file
28
integration/typings_test_ts42/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 4.2 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/elements": "file:../../dist/packages-dist/elements",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "file:../../node_modules/@types/jasmine",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"typescript": "4.2.3",
|
||||
"zone.js": "file:../../dist/zone.js-dist/archive/zone.js.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tsc"
|
||||
}
|
||||
}
|
26
integration/typings_test_ts42/tsconfig.json
Normal file
26
integration/typings_test_ts42/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.iterable",
|
||||
"es2015.promise"
|
||||
],
|
||||
"types": [],
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
"node_modules/@types/jasmine/index.d.ts"
|
||||
]
|
||||
}
|
41
packages/animations/test/animation_group_player_spec.ts
Normal file
41
packages/animations/test/animation_group_player_spec.ts
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @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 {fakeAsync} from '@angular/core/testing';
|
||||
import {NoopAnimationPlayer} from '../src/animations';
|
||||
import {AnimationGroupPlayer} from '../src/players/animation_group_player';
|
||||
|
||||
|
||||
describe('AnimationGroupPlayer', () => {
|
||||
it('should getPosition of an empty group', fakeAsync(() => {
|
||||
const players: NoopAnimationPlayer[] = [];
|
||||
const groupPlayer = new AnimationGroupPlayer(players);
|
||||
expect(groupPlayer.getPosition()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should getPosition of a single player in a group', fakeAsync(() => {
|
||||
const player = new NoopAnimationPlayer(5, 5);
|
||||
player.setPosition(0.2);
|
||||
const players = [player];
|
||||
const groupPlayer = new AnimationGroupPlayer(players);
|
||||
expect(groupPlayer.getPosition()).toBe(0.2);
|
||||
}));
|
||||
|
||||
it('should getPosition based on the longest player in the group', fakeAsync(() => {
|
||||
const longestPlayer = new NoopAnimationPlayer(5, 5);
|
||||
longestPlayer.setPosition(0.2);
|
||||
const players = [
|
||||
new NoopAnimationPlayer(1, 4),
|
||||
new NoopAnimationPlayer(4, 1),
|
||||
new NoopAnimationPlayer(7, 0),
|
||||
longestPlayer,
|
||||
new NoopAnimationPlayer(1, 1),
|
||||
];
|
||||
const groupPlayer = new AnimationGroupPlayer(players);
|
||||
expect(groupPlayer.getPosition()).toBe(0.2);
|
||||
}));
|
||||
});
|
20
packages/bazel/src/ng_perf.bzl
Normal file
20
packages/bazel/src/ng_perf.bzl
Normal file
@ -0,0 +1,20 @@
|
||||
# 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
|
||||
|
||||
load(":ng_module.bzl", "NgPerfInfo")
|
||||
|
||||
def _ng_perf_flag_impl(ctx):
|
||||
return NgPerfInfo(enable_perf_logging = ctx.build_setting_value)
|
||||
|
||||
# `ng_perf_flag` is a special `build_setting` rule which ultimately enables a command-line boolean
|
||||
# flag to control whether the `ng_module` rule produces performance tracing JSON files (in Ivy mode)
|
||||
# as declared outputs.
|
||||
#
|
||||
# It does this via the `NgPerfInfo` provider and the `perf_flag` attriubute on `ng_module`. For more
|
||||
# details, see: https://docs.bazel.build/versions/master/skylark/config.html
|
||||
ng_perf_flag = rule(
|
||||
implementation = _ng_perf_flag_impl,
|
||||
build_setting = config.bool(flag = True),
|
||||
)
|
45
packages/bazel/test/ngc-wrapped/ivy_enabled/BUILD.bazel
Normal file
45
packages/bazel/test/ngc-wrapped/ivy_enabled/BUILD.bazel
Normal file
@ -0,0 +1,45 @@
|
||||
load("//tools:defaults.bzl", "jasmine_node_test", "ng_module", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "ng_module_ivy_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["ng_module_ivy_test.ts"],
|
||||
tags = ["ivy-only"],
|
||||
)
|
||||
|
||||
# `ng_module` with `compilation_mode` explicitly set to `partial`.
|
||||
ng_module(
|
||||
name = "test_module_partial_compilation",
|
||||
srcs = ["test_module_partial_compilation.ts"],
|
||||
compilation_mode = "partial",
|
||||
tags = ["ivy-only"],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
# `ng_module` with `compilation_mode` explicitly set to `full`.
|
||||
ng_module(
|
||||
name = "test_module_full_compilation",
|
||||
srcs = ["test_module_full_compilation.ts"],
|
||||
compilation_mode = "full",
|
||||
tags = ["ivy-only"],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
# `ng_module` with no specific `compilation_mode` attribute specified.
|
||||
ng_module(
|
||||
name = "test_module_default_compilation",
|
||||
srcs = ["test_module_default_compilation.ts"],
|
||||
tags = ["ivy-only"],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "ng_module_ivy_test",
|
||||
srcs = [":ng_module_ivy_test_lib"],
|
||||
data = [
|
||||
":test_module_default_compilation",
|
||||
":test_module_full_compilation",
|
||||
":test_module_partial_compilation",
|
||||
],
|
||||
tags = ["ivy-only"],
|
||||
)
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @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 {readFileSync} from 'fs';
|
||||
|
||||
/** Runfiles helper from bazel to resolve file name paths. */
|
||||
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']!);
|
||||
|
||||
describe('ng_module with ivy enabled', () => {
|
||||
describe('default compilation mode', () => {
|
||||
it('should generate definitions', () => {
|
||||
const outputFile = runfiles.resolveWorkspaceRelative(
|
||||
'packages/bazel/test/ngc-wrapped/ivy_enabled/test_module_default_compilation.js');
|
||||
const fileContent = readFileSync(outputFile, 'utf8');
|
||||
expect(fileContent).toContain(`TestComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent(`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('full compilation mode', () => {
|
||||
it('should generate definitions', () => {
|
||||
const outputFile = runfiles.resolveWorkspaceRelative(
|
||||
'packages/bazel/test/ngc-wrapped/ivy_enabled/test_module_full_compilation.js');
|
||||
const fileContent = readFileSync(outputFile, 'utf8');
|
||||
expect(fileContent).toContain(`TestComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent(`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('partial compilation mode', () => {
|
||||
it('should generate declarations', () => {
|
||||
const outputFile = runfiles.resolveWorkspaceRelative(
|
||||
'packages/bazel/test/ngc-wrapped/ivy_enabled/test_module_partial_compilation.js');
|
||||
const fileContent = readFileSync(outputFile, 'utf8');
|
||||
expect(fileContent).toContain(`TestComponent.ɵcmp = i0.ɵɵngDeclareComponent(`);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @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 {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @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 {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @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 {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
99
packages/common/http/src/context.ts
Normal file
99
packages/common/http/src/context.ts
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* A token used to manipulate and access values stored in `HttpContext`.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class HttpContextToken<T> {
|
||||
constructor(public readonly defaultValue: () => T) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http context stores arbitrary user defined values and ensures type safety without
|
||||
* actually knowing the types. It is backed by a `Map` and guarantees that keys do not clash.
|
||||
*
|
||||
* This context is mutable and is shared between cloned requests unless explicitly specified.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Usage Example
|
||||
*
|
||||
* ```typescript
|
||||
* // inside cache.interceptors.ts
|
||||
* export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);
|
||||
*
|
||||
* export class CacheInterceptor implements HttpInterceptor {
|
||||
*
|
||||
* intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
|
||||
* if (req.context.get(IS_CACHE_ENABLED) === true) {
|
||||
* return ...;
|
||||
* }
|
||||
* return delegate.handle(req);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // inside a service
|
||||
*
|
||||
* this.httpClient.get('/api/weather', {
|
||||
* context: new HttpContext().set(IS_CACHE_ENABLED, true)
|
||||
* }).subscribe(...);
|
||||
* ```
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class HttpContext {
|
||||
private readonly map = new Map<HttpContextToken<unknown>, unknown>();
|
||||
|
||||
/**
|
||||
* Store a value in the context. If a value is already present it will be overwritten.
|
||||
*
|
||||
* @param token The reference to an instance of `HttpContextToken`.
|
||||
* @param value The value to store.
|
||||
*
|
||||
* @returns A reference to itself for easy chaining.
|
||||
*/
|
||||
set<T>(token: HttpContextToken<T>, value: T): HttpContext {
|
||||
this.map.set(token, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with the given token.
|
||||
*
|
||||
* @param token The reference to an instance of `HttpContextToken`.
|
||||
*
|
||||
* @returns The stored value or default if one is defined.
|
||||
*/
|
||||
get<T>(token: HttpContextToken<T>): T {
|
||||
if (!this.map.has(token)) {
|
||||
this.map.set(token, token.defaultValue());
|
||||
}
|
||||
return this.map.get(token) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the value associated with the given token.
|
||||
*
|
||||
* @param token The reference to an instance of `HttpContextToken`.
|
||||
*
|
||||
* @returns A reference to itself for easy chaining.
|
||||
*/
|
||||
delete(token: HttpContextToken<unknown>): HttpContext {
|
||||
this.map.delete(token);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a list of tokens currently stored in the context.
|
||||
*/
|
||||
keys(): IterableIterator<HttpContextToken<unknown>> {
|
||||
return this.map.keys();
|
||||
}
|
||||
}
|
59
packages/common/http/test/context_spec.ts
Normal file
59
packages/common/http/test/context_spec.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @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 {HttpContext, HttpContextToken} from '../src/context';
|
||||
|
||||
const IS_ENABLED = new HttpContextToken<boolean>(() => false);
|
||||
const CACHE_OPTION =
|
||||
new HttpContextToken<{cache: boolean, expiresIn?: number}>(() => ({cache: false}));
|
||||
|
||||
describe('HttpContext', () => {
|
||||
let context: HttpContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new HttpContext();
|
||||
});
|
||||
|
||||
describe('with basic value', () => {
|
||||
it('should test public api', () => {
|
||||
expect(context.get(IS_ENABLED)).toBe(false);
|
||||
expect([...context.keys()]).toEqual([
|
||||
IS_ENABLED
|
||||
]); // value from factory function is stored in the map upon access
|
||||
|
||||
context.set(IS_ENABLED, true);
|
||||
expect(context.get(IS_ENABLED)).toBe(true);
|
||||
expect([...context.keys()]).toEqual([IS_ENABLED]);
|
||||
|
||||
context.delete(IS_ENABLED);
|
||||
expect([...context.keys()]).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with complex value', () => {
|
||||
it('should test public api', () => {
|
||||
expect(context.get(CACHE_OPTION)).toEqual({cache: false});
|
||||
expect([...context.keys()]).toEqual([CACHE_OPTION]);
|
||||
|
||||
const value = {cache: true, expiresIn: 30};
|
||||
context.set(CACHE_OPTION, value);
|
||||
expect(context.get(CACHE_OPTION)).toBe(value);
|
||||
expect([...context.keys()]).toEqual([CACHE_OPTION]);
|
||||
|
||||
context.delete(CACHE_OPTION);
|
||||
expect([...context.keys()]).toEqual([]);
|
||||
});
|
||||
|
||||
it('should ensure that same reference is returned for default value between multiple accesses',
|
||||
() => {
|
||||
const value = context.get(CACHE_OPTION); // will get default value
|
||||
expect(value).toEqual({cache: false});
|
||||
expect(context.get(CACHE_OPTION)).toBe(value);
|
||||
});
|
||||
});
|
||||
});
|
16
packages/common/src/xhr.ts
Normal file
16
packages/common/src/xhr.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* A wrapper around the `XMLHttpRequest` constructor.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export abstract class XhrFactory {
|
||||
abstract build(): XMLHttpRequest;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @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 {LinkerOptions} from '../..';
|
||||
import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../../../src/ngtsc/logging';
|
||||
|
||||
export interface LinkerPluginOptions extends Partial<LinkerOptions> {
|
||||
/**
|
||||
* File-system, used to load up the input source-map and content.
|
||||
*/
|
||||
fileSystem: ReadonlyFileSystem;
|
||||
|
||||
/**
|
||||
* Logger used by the linker.
|
||||
*/
|
||||
logger: Logger;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @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 {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {SourceFile, SourceFileLoader} from '../../../src/ngtsc/sourcemaps';
|
||||
|
||||
/**
|
||||
* A function that will return a `SourceFile` object (or null) for the current file being linked.
|
||||
*/
|
||||
export type GetSourceFileFn = () => SourceFile|null;
|
||||
|
||||
/**
|
||||
* Create a `GetSourceFileFn` that will return the `SourceFile` being linked or `null`, if not
|
||||
* available.
|
||||
*/
|
||||
export function createGetSourceFile(
|
||||
sourceUrl: AbsoluteFsPath, code: string, loader: SourceFileLoader|null): GetSourceFileFn {
|
||||
if (loader === null) {
|
||||
// No source-mapping so just return a function that always returns `null`.
|
||||
return () => null;
|
||||
} else {
|
||||
// Source-mapping is available so return a function that will load (and cache) the `SourceFile`.
|
||||
let sourceFile: SourceFile|null|undefined = undefined;
|
||||
return () => {
|
||||
if (sourceFile === undefined) {
|
||||
sourceFile = loader.loadSourceFile(sourceUrl, code);
|
||||
}
|
||||
return sourceFile;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @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 {declarationFunctions} from './partial_linkers/partial_linker_selector';
|
||||
|
||||
/**
|
||||
* Determines if the provided source file may need to be processed by the linker, i.e. whether it
|
||||
* potentially contains any declarations. If true is returned, then the source file should be
|
||||
* processed by the linker as it may contain declarations that need to be fully compiled. If false
|
||||
* is returned, parsing and processing of the source file can safely be skipped to improve
|
||||
* performance.
|
||||
*
|
||||
* This function may return true even for source files that don't actually contain any declarations
|
||||
* that need to be compiled.
|
||||
*
|
||||
* @param path the absolute path of the source file for which to determine whether linking may be
|
||||
* needed.
|
||||
* @param source the source file content as a string.
|
||||
* @returns whether the source file may contain declarations that need to be linked.
|
||||
*/
|
||||
export function needsLinking(path: string, source: string): boolean {
|
||||
return declarationFunctions.some(fn => source.includes(fn));
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @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 {compileFactoryFunction, ConstantPool, FactoryTarget, R3DeclareDependencyMetadata, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {parseEnum, wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareFactory()` call expressions.
|
||||
*/
|
||||
export class PartialFactoryLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3FactoryMeta(metaObj);
|
||||
const def = compileFactoryFunction(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3FactoryMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3FactoryMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>): R3FactoryMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
return {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
typeArgumentCount: 0,
|
||||
target: parseEnum(metaObj.getValue('target'), FactoryTarget),
|
||||
deps: getDeps(metaObj, 'deps'),
|
||||
};
|
||||
}
|
||||
|
||||
function getDeps<TExpression>(
|
||||
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>,
|
||||
propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' {
|
||||
if (!metaObj.has(propName)) {
|
||||
return null;
|
||||
}
|
||||
const deps = metaObj.getValue(propName);
|
||||
if (deps.isArray()) {
|
||||
return deps.getArray().map(dep => getDep(dep.getObject()));
|
||||
}
|
||||
if (deps.isString()) {
|
||||
return 'invalid';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDep<TExpression>(depObj: AstObject<R3DeclareDependencyMetadata, TExpression>):
|
||||
R3DependencyMetadata {
|
||||
const isAttribute = depObj.has('attribute') && depObj.getBoolean('attribute');
|
||||
const token = depObj.getOpaque('token');
|
||||
// Normally `attribute` is a string literal and so its `attributeNameType` is the same string
|
||||
// literal. If the `attribute` is some other expression, the `attributeNameType` would be the
|
||||
// `unknown` type. It is not possible to generate this when linking, since it only deals with JS
|
||||
// and not typings. When linking the existence of the `attributeNameType` only acts as a marker to
|
||||
// change the injection instruction that is generated, so we just pass the literal string
|
||||
// `"unknown"`.
|
||||
const attributeNameType = isAttribute ? o.literal('unknown') : null;
|
||||
const dep: R3DependencyMetadata = {
|
||||
token,
|
||||
attributeNameType,
|
||||
host: depObj.has('host') && depObj.getBoolean('host'),
|
||||
optional: depObj.has('optional') && depObj.getBoolean('optional'),
|
||||
self: depObj.has('self') && depObj.getBoolean('self'),
|
||||
skipSelf: depObj.has('skipSelf') && depObj.getBoolean('skipSelf'),
|
||||
};
|
||||
return dep;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @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 {compileInjector, ConstantPool, R3DeclareInjectorMetadata, R3InjectorMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareInjector()` call expressions.
|
||||
*/
|
||||
export class PartialInjectorLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3InjectorMeta(metaObj);
|
||||
const def = compileInjector(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3InjectorMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3InjectorMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareInjectorMetadata, TExpression>): R3InjectorMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
return {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
providers: metaObj.has('providers') ? metaObj.getOpaque('providers') : null,
|
||||
imports: metaObj.has('imports') ? metaObj.getArray('imports').map(i => i.getOpaque()) : [],
|
||||
};
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @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 {compileNgModule, ConstantPool, R3DeclareNgModuleMetadata, R3NgModuleMetadata, R3PartialDeclaration, R3Reference} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject, AstValue} from '../../ast/ast_value';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareNgModule()` call expressions.
|
||||
*/
|
||||
export class PartialNgModuleLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
constructor(
|
||||
/**
|
||||
* If true then emit the additional declarations, imports, exports, etc in the NgModule
|
||||
* definition. These are only used by JIT compilation.
|
||||
*/
|
||||
private emitInline: boolean) {}
|
||||
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3NgModuleMeta(metaObj, this.emitInline);
|
||||
const def = compileNgModule(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3NgModuleMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3NgModuleMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareNgModuleMetadata, TExpression>,
|
||||
emitInline: boolean): R3NgModuleMetadata {
|
||||
const wrappedType = metaObj.getOpaque('type');
|
||||
|
||||
const meta: R3NgModuleMetadata = {
|
||||
type: wrapReference(wrappedType),
|
||||
internalType: wrappedType,
|
||||
adjacentType: wrappedType,
|
||||
bootstrap: [],
|
||||
declarations: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
emitInline,
|
||||
containsForwardDecls: false,
|
||||
schemas: [],
|
||||
id: metaObj.has('id') ? metaObj.getOpaque('id') : null,
|
||||
};
|
||||
|
||||
// Each of `bootstrap`, `declarations`, `imports` and `exports` are normally an array. But if any
|
||||
// of the references are not yet declared, then the arrays must be wrapped in a function to
|
||||
// prevent errors at runtime when accessing the values.
|
||||
|
||||
// The following blocks of code will unwrap the arrays from such functions, because
|
||||
// `R3NgModuleMetadata` expects arrays of `R3Reference` objects.
|
||||
|
||||
// Further, since the `ɵɵdefineNgModule()` will also suffer from the forward declaration problem,
|
||||
// we must update the `containsForwardDecls` property if a function wrapper was found.
|
||||
|
||||
if (metaObj.has('bootstrap')) {
|
||||
const bootstrap: AstValue<unknown, TExpression> = metaObj.getValue('bootstrap');
|
||||
if (bootstrap.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.bootstrap = wrapReferences(unwrapForwardRefs(bootstrap));
|
||||
} else
|
||||
meta.bootstrap = wrapReferences(bootstrap);
|
||||
}
|
||||
|
||||
if (metaObj.has('declarations')) {
|
||||
const declarations: AstValue<unknown, TExpression> = metaObj.getValue('declarations');
|
||||
if (declarations.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.declarations = wrapReferences(unwrapForwardRefs(declarations));
|
||||
} else
|
||||
meta.declarations = wrapReferences(declarations);
|
||||
}
|
||||
|
||||
if (metaObj.has('imports')) {
|
||||
const imports: AstValue<unknown, TExpression> = metaObj.getValue('imports');
|
||||
if (imports.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.imports = wrapReferences(unwrapForwardRefs(imports));
|
||||
} else
|
||||
meta.imports = wrapReferences(imports);
|
||||
}
|
||||
|
||||
if (metaObj.has('exports')) {
|
||||
const exports: AstValue<unknown, TExpression> = metaObj.getValue('exports');
|
||||
if (exports.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.exports = wrapReferences(unwrapForwardRefs(exports));
|
||||
} else
|
||||
meta.exports = wrapReferences(exports);
|
||||
}
|
||||
|
||||
if (metaObj.has('schemas')) {
|
||||
const schemas: AstValue<unknown, TExpression> = metaObj.getValue('schemas');
|
||||
meta.schemas = wrapReferences(schemas);
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an array from the body of the function.
|
||||
*
|
||||
* If `field` is `function() { return [exp1, exp2, exp3]; }` then we return `[exp1, exp2, exp3]`.
|
||||
*
|
||||
*/
|
||||
function unwrapForwardRefs<TExpression>(field: AstValue<unknown, TExpression>):
|
||||
AstValue<TExpression[], TExpression> {
|
||||
return (field as AstValue<Function, TExpression>).getFunctionReturnValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the array of expressions into an array of R3 references.
|
||||
*/
|
||||
function wrapReferences<TExpression>(values: AstValue<TExpression[], TExpression>): R3Reference[] {
|
||||
return values.getArray().map(i => wrapReference(i.getOpaque()));
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @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 {compilePipeFromMetadata, ConstantPool, R3DeclarePipeMetadata, R3PartialDeclaration, R3PipeMetadata} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclarePipe()` call expressions.
|
||||
*/
|
||||
export class PartialPipeLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
constructor() {}
|
||||
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3PipeMeta(metaObj);
|
||||
const def = compilePipeFromMetadata(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3PipeMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3PipeMeta<TExpression>(metaObj: AstObject<R3DeclarePipeMetadata, TExpression>):
|
||||
R3PipeMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
const pure = metaObj.has('pure') ? metaObj.getBoolean('pure') : true;
|
||||
|
||||
return {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
typeArgumentCount: 0,
|
||||
deps: null,
|
||||
pipeName: metaObj.getString('name'),
|
||||
pure,
|
||||
};
|
||||
}
|
@ -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
|
||||
*/
|
||||
import {R3Reference} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {AstValue} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
|
||||
return {value: wrapped, type: wrapped};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the value of an enum from the AST value's symbol name.
|
||||
*/
|
||||
export function parseEnum<TExpression, TEnum>(
|
||||
value: AstValue<unknown, TExpression>, Enum: TEnum): TEnum[keyof TEnum] {
|
||||
const symbolName = value.getSymbolName();
|
||||
if (symbolName === null) {
|
||||
throw new FatalLinkerError(value.expression, 'Expected value to have a symbol name');
|
||||
}
|
||||
const enumValue = Enum[symbolName as keyof typeof Enum];
|
||||
if (enumValue === undefined) {
|
||||
throw new FatalLinkerError(value.expression, `Unsupported enum value for ${Enum}`);
|
||||
}
|
||||
return enumValue;
|
||||
}
|
@ -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 {needsLinking} from '../../src/file_linker/needs_linking';
|
||||
|
||||
describe('needsLinking', () => {
|
||||
it('should return true for directive declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Dir {
|
||||
ɵdir = ɵɵngDeclareDirective({type: Dir});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for namespaced directive declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Dir {
|
||||
ɵdir = ng.ɵɵngDeclareDirective({type: Dir});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for unrelated usages of ɵɵngDeclareDirective', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const fnName = 'ɵɵngDeclareDirective';
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return false when the file does not contain ɵɵngDeclareDirective', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const foo = ngDeclareDirective;
|
||||
`)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should return true for component declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Cmp {
|
||||
ɵdir = ɵɵngDeclareComponent({type: Cmp});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for namespaced component declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Cmp {
|
||||
ɵdir = ng.ɵɵngDeclareComponent({type: Cmp});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for unrelated usages of ɵɵngDeclareComponent', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const fnName = 'ɵɵngDeclareComponent';
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return false when the file does not contain ɵɵngDeclareComponent', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const foo = ngDeclareComponent;
|
||||
`)).toBeFalse();
|
||||
});
|
||||
});
|
16
packages/compiler-cli/src/ngtsc/core/src/config.ts
Normal file
16
packages/compiler-cli/src/ngtsc/core/src/config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @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 file exists as a target for g3 patches which change the Angular compiler's behavior.
|
||||
// Separating the patched code in a separate file eliminates the possibility of conflicts with the
|
||||
// patch diffs when making changes to the rest of the compiler codebase.
|
||||
|
||||
// In ngtsc we no longer want to compile undecorated classes with Angular features.
|
||||
// Migrations for these patterns ran as part of `ng update` and we want to ensure
|
||||
// that projects do not regress. See https://hackmd.io/@alx/ryfYYuvzH for more details.
|
||||
export const compileUndecoratedClassesWithAngularFeatures = false;
|
@ -0,0 +1,16 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "semantic_graph",
|
||||
srcs = ["index.ts"] + glob([
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @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 {SemanticReference, SemanticSymbol} from './src/api';
|
||||
export {SemanticDepGraph, SemanticDepGraphUpdater} from './src/graph';
|
||||
export {areTypeParametersEqual, extractSemanticTypeParameters, SemanticTypeParameter} from './src/type_parameters';
|
||||
export {isArrayEqual, isReferenceEqual, isSetEqual, isSymbolEqual} from './src/util';
|
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @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 {absoluteFromSourceFile, AbsoluteFsPath} from '../../../file_system';
|
||||
import {ClassDeclaration} from '../../../reflection';
|
||||
|
||||
/**
|
||||
* Represents a symbol that is recognizable across incremental rebuilds, which enables the captured
|
||||
* metadata to be compared to the prior compilation. This allows for semantic understanding of
|
||||
* the changes that have been made in a rebuild, which potentially enables more reuse of work
|
||||
* from the prior compilation.
|
||||
*/
|
||||
export abstract class SemanticSymbol {
|
||||
/**
|
||||
* The path of the file that declares this symbol.
|
||||
*/
|
||||
public readonly path: AbsoluteFsPath;
|
||||
|
||||
/**
|
||||
* The identifier of this symbol, or null if no identifier could be determined. It should
|
||||
* uniquely identify the symbol relative to `file`. This is typically just the name of a
|
||||
* top-level class declaration, as that uniquely identifies the class within the file.
|
||||
*
|
||||
* If the identifier is null, then this symbol cannot be recognized across rebuilds. In that
|
||||
* case, the symbol is always assumed to have semantically changed to guarantee a proper
|
||||
* rebuild.
|
||||
*/
|
||||
public readonly identifier: string|null;
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The declaration for this symbol.
|
||||
*/
|
||||
public readonly decl: ClassDeclaration,
|
||||
) {
|
||||
this.path = absoluteFromSourceFile(decl.getSourceFile());
|
||||
this.identifier = getSymbolIdentifier(decl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the symbol to be compared to the equivalent symbol in the previous compilation. The
|
||||
* return value indicates whether the symbol has been changed in a way such that its public API
|
||||
* is affected.
|
||||
*
|
||||
* This method determines whether a change to _this_ symbol require the symbols that
|
||||
* use to this symbol to be re-emitted.
|
||||
*
|
||||
* Note: `previousSymbol` is obtained from the most recently succeeded compilation. Symbols of
|
||||
* failed compilations are never provided.
|
||||
*
|
||||
* @param previousSymbol The symbol from a prior compilation.
|
||||
*/
|
||||
abstract isPublicApiAffected(previousSymbol: SemanticSymbol): boolean;
|
||||
|
||||
/**
|
||||
* Allows the symbol to determine whether its emit is affected. The equivalent symbol from a prior
|
||||
* build is given, in addition to the set of symbols of which the public API has changed.
|
||||
*
|
||||
* This method determines whether a change to _other_ symbols, i.e. those present in
|
||||
* `publicApiAffected`, should cause _this_ symbol to be re-emitted.
|
||||
*
|
||||
* @param previousSymbol The equivalent symbol from a prior compilation. Note that it may be a
|
||||
* different type of symbol, if e.g. a Component was changed into a Directive with the same name.
|
||||
* @param publicApiAffected The set of symbols of which the public API has changed.
|
||||
*/
|
||||
isEmitAffected?(previousSymbol: SemanticSymbol, publicApiAffected: Set<SemanticSymbol>): boolean;
|
||||
|
||||
/**
|
||||
* Similar to `isPublicApiAffected`, but here equivalent symbol from a prior compilation needs
|
||||
* to be compared to see if the type-check block of components that use this symbol is affected.
|
||||
*
|
||||
* This method determines whether a change to _this_ symbol require the symbols that
|
||||
* use to this symbol to have their type-check block regenerated.
|
||||
*
|
||||
* Note: `previousSymbol` is obtained from the most recently succeeded compilation. Symbols of
|
||||
* failed compilations are never provided.
|
||||
*
|
||||
* @param previousSymbol The symbol from a prior compilation.
|
||||
*/
|
||||
abstract isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean;
|
||||
|
||||
/**
|
||||
* Similar to `isEmitAffected`, but focused on the type-check block of this symbol. This method
|
||||
* determines whether a change to _other_ symbols, i.e. those present in `typeCheckApiAffected`,
|
||||
* should cause _this_ symbol's type-check block to be regenerated.
|
||||
*
|
||||
* @param previousSymbol The equivalent symbol from a prior compilation. Note that it may be a
|
||||
* different type of symbol, if e.g. a Component was changed into a Directive with the same name.
|
||||
* @param typeCheckApiAffected The set of symbols of which the type-check API has changed.
|
||||
*/
|
||||
isTypeCheckBlockAffected?
|
||||
(previousSymbol: SemanticSymbol, typeCheckApiAffected: Set<SemanticSymbol>): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a reference to a semantic symbol that has been emitted into a source file. The
|
||||
* reference may refer to the symbol using a different name than the semantic symbol's declared
|
||||
* name, e.g. in case a re-export under a different name was chosen by a reference emitter.
|
||||
* Consequently, to know that an emitted reference is still valid not only requires that the
|
||||
* semantic symbol is still valid, but also that the path by which the symbol is imported has not
|
||||
* changed.
|
||||
*/
|
||||
export interface SemanticReference {
|
||||
symbol: SemanticSymbol;
|
||||
|
||||
/**
|
||||
* The path by which the symbol has been referenced.
|
||||
*/
|
||||
importPath: string|null;
|
||||
}
|
||||
|
||||
function getSymbolIdentifier(decl: ClassDeclaration): string|null {
|
||||
if (!ts.isSourceFile(decl.parent)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this is a top-level class declaration, the class name is used as unique identifier.
|
||||
// Other scenarios are currently not supported and causes the symbol not to be identified
|
||||
// across rebuilds, unless the declaration node has not changed.
|
||||
return decl.name.text;
|
||||
}
|
@ -0,0 +1,281 @@
|
||||
/**
|
||||
* @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 {Expression, ExternalExpr} from '@angular/compiler';
|
||||
import {AbsoluteFsPath} from '../../../file_system';
|
||||
import {ClassDeclaration} from '../../../reflection';
|
||||
import {SemanticReference, SemanticSymbol} from './api';
|
||||
|
||||
export interface SemanticDependencyResult {
|
||||
/**
|
||||
* The files that need to be re-emitted.
|
||||
*/
|
||||
needsEmit: Set<AbsoluteFsPath>;
|
||||
|
||||
/**
|
||||
* The files for which the type-check block should be regenerated.
|
||||
*/
|
||||
needsTypeCheckEmit: Set<AbsoluteFsPath>;
|
||||
|
||||
/**
|
||||
* The newly built graph that represents the current compilation.
|
||||
*/
|
||||
newGraph: SemanticDepGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a declaration for which no semantic symbol has been registered. For example,
|
||||
* declarations from external dependencies have not been explicitly registered and are represented
|
||||
* by this symbol. This allows the unresolved symbol to still be compared to a symbol from a prior
|
||||
* compilation.
|
||||
*/
|
||||
class OpaqueSymbol extends SemanticSymbol {
|
||||
isPublicApiAffected(): false {
|
||||
return false;
|
||||
}
|
||||
|
||||
isTypeCheckApiAffected(): false {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The semantic dependency graph of a single compilation.
|
||||
*/
|
||||
export class SemanticDepGraph {
|
||||
readonly files = new Map<AbsoluteFsPath, Map<string, SemanticSymbol>>();
|
||||
readonly symbolByDecl = new Map<ClassDeclaration, SemanticSymbol>();
|
||||
|
||||
/**
|
||||
* Registers a symbol in the graph. The symbol is given a unique identifier if possible, such that
|
||||
* its equivalent symbol can be obtained from a prior graph even if its declaration node has
|
||||
* changed across rebuilds. Symbols without an identifier are only able to find themselves in a
|
||||
* prior graph if their declaration node is identical.
|
||||
*/
|
||||
registerSymbol(symbol: SemanticSymbol): void {
|
||||
this.symbolByDecl.set(symbol.decl, symbol);
|
||||
|
||||
if (symbol.identifier !== null) {
|
||||
// If the symbol has a unique identifier, record it in the file that declares it. This enables
|
||||
// the symbol to be requested by its unique name.
|
||||
if (!this.files.has(symbol.path)) {
|
||||
this.files.set(symbol.path, new Map<string, SemanticSymbol>());
|
||||
}
|
||||
this.files.get(symbol.path)!.set(symbol.identifier, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve a symbol in this graph that represents the given symbol from another graph.
|
||||
* If no matching symbol could be found, null is returned.
|
||||
*
|
||||
* @param symbol The symbol from another graph for which its equivalent in this graph should be
|
||||
* found.
|
||||
*/
|
||||
getEquivalentSymbol(symbol: SemanticSymbol): SemanticSymbol|null {
|
||||
// First lookup the symbol by its declaration. It is typical for the declaration to not have
|
||||
// changed across rebuilds, so this is likely to find the symbol. Using the declaration also
|
||||
// allows to diff symbols for which no unique identifier could be determined.
|
||||
let previousSymbol = this.getSymbolByDecl(symbol.decl);
|
||||
if (previousSymbol === null && symbol.identifier !== null) {
|
||||
// The declaration could not be resolved to a symbol in a prior compilation, which may
|
||||
// happen because the file containing the declaration has changed. In that case we want to
|
||||
// lookup the symbol based on its unique identifier, as that allows us to still compare the
|
||||
// changed declaration to the prior compilation.
|
||||
previousSymbol = this.getSymbolByName(symbol.path, symbol.identifier);
|
||||
}
|
||||
|
||||
return previousSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the symbol by its identifier.
|
||||
*/
|
||||
private getSymbolByName(path: AbsoluteFsPath, identifier: string): SemanticSymbol|null {
|
||||
if (!this.files.has(path)) {
|
||||
return null;
|
||||
}
|
||||
const file = this.files.get(path)!;
|
||||
if (!file.has(identifier)) {
|
||||
return null;
|
||||
}
|
||||
return file.get(identifier)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve the declaration to its semantic symbol.
|
||||
*/
|
||||
getSymbolByDecl(decl: ClassDeclaration): SemanticSymbol|null {
|
||||
if (!this.symbolByDecl.has(decl)) {
|
||||
return null;
|
||||
}
|
||||
return this.symbolByDecl.get(decl)!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the logic to go from a previous dependency graph to a new one, along with information
|
||||
* on which files have been affected.
|
||||
*/
|
||||
export class SemanticDepGraphUpdater {
|
||||
private readonly newGraph = new SemanticDepGraph();
|
||||
|
||||
/**
|
||||
* Contains opaque symbols that were created for declarations for which there was no symbol
|
||||
* registered, which happens for e.g. external declarations.
|
||||
*/
|
||||
private readonly opaqueSymbols = new Map<ClassDeclaration, OpaqueSymbol>();
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The semantic dependency graph of the most recently succeeded compilation, or null if this
|
||||
* is the initial build.
|
||||
*/
|
||||
private priorGraph: SemanticDepGraph|null) {}
|
||||
|
||||
/**
|
||||
* Registers the symbol in the new graph that is being created.
|
||||
*/
|
||||
registerSymbol(symbol: SemanticSymbol): void {
|
||||
this.newGraph.registerSymbol(symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all facts that have been gathered to create a new semantic dependency graph. In this
|
||||
* process, the semantic impact of the changes is determined which results in a set of files that
|
||||
* need to be emitted and/or type-checked.
|
||||
*/
|
||||
finalize(): SemanticDependencyResult {
|
||||
if (this.priorGraph === null) {
|
||||
// If no prior dependency graph is available then this was the initial build, in which case
|
||||
// we don't need to determine the semantic impact as everything is already considered
|
||||
// logically changed.
|
||||
return {
|
||||
needsEmit: new Set<AbsoluteFsPath>(),
|
||||
needsTypeCheckEmit: new Set<AbsoluteFsPath>(),
|
||||
newGraph: this.newGraph,
|
||||
};
|
||||
}
|
||||
|
||||
const needsEmit = this.determineInvalidatedFiles(this.priorGraph);
|
||||
const needsTypeCheckEmit = this.determineInvalidatedTypeCheckFiles(this.priorGraph);
|
||||
return {
|
||||
needsEmit,
|
||||
needsTypeCheckEmit,
|
||||
newGraph: this.newGraph,
|
||||
};
|
||||
}
|
||||
|
||||
private determineInvalidatedFiles(priorGraph: SemanticDepGraph): Set<AbsoluteFsPath> {
|
||||
const isPublicApiAffected = new Set<SemanticSymbol>();
|
||||
|
||||
// The first phase is to collect all symbols which have their public API affected. Any symbols
|
||||
// that cannot be matched up with a symbol from the prior graph are considered affected.
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null || symbol.isPublicApiAffected(previousSymbol)) {
|
||||
isPublicApiAffected.add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// The second phase is to find all symbols for which the emit result is affected, either because
|
||||
// their used declarations have changed or any of those used declarations has had its public API
|
||||
// affected as determined in the first phase.
|
||||
const needsEmit = new Set<AbsoluteFsPath>();
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
if (symbol.isEmitAffected === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null || symbol.isEmitAffected(previousSymbol, isPublicApiAffected)) {
|
||||
needsEmit.add(symbol.path);
|
||||
}
|
||||
}
|
||||
|
||||
return needsEmit;
|
||||
}
|
||||
|
||||
private determineInvalidatedTypeCheckFiles(priorGraph: SemanticDepGraph): Set<AbsoluteFsPath> {
|
||||
const isTypeCheckApiAffected = new Set<SemanticSymbol>();
|
||||
|
||||
// The first phase is to collect all symbols which have their public API affected. Any symbols
|
||||
// that cannot be matched up with a symbol from the prior graph are considered affected.
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null || symbol.isTypeCheckApiAffected(previousSymbol)) {
|
||||
isTypeCheckApiAffected.add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// The second phase is to find all symbols for which the emit result is affected, either because
|
||||
// their used declarations have changed or any of those used declarations has had its public API
|
||||
// affected as determined in the first phase.
|
||||
const needsTypeCheckEmit = new Set<AbsoluteFsPath>();
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
if (symbol.isTypeCheckBlockAffected === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null ||
|
||||
symbol.isTypeCheckBlockAffected(previousSymbol, isTypeCheckApiAffected)) {
|
||||
needsTypeCheckEmit.add(symbol.path);
|
||||
}
|
||||
}
|
||||
|
||||
return needsTypeCheckEmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `SemanticReference` for the reference to `decl` using the expression `expr`. See
|
||||
* the documentation of `SemanticReference` for details.
|
||||
*/
|
||||
getSemanticReference(decl: ClassDeclaration, expr: Expression): SemanticReference {
|
||||
return {
|
||||
symbol: this.getSymbol(decl),
|
||||
importPath: getImportPath(expr),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `SemanticSymbol` that was registered for `decl` during the current compilation, or
|
||||
* returns an opaque symbol that represents `decl`.
|
||||
*/
|
||||
getSymbol(decl: ClassDeclaration): SemanticSymbol {
|
||||
const symbol = this.newGraph.getSymbolByDecl(decl);
|
||||
if (symbol === null) {
|
||||
// No symbol has been recorded for the provided declaration, which would be the case if the
|
||||
// declaration is external. Return an opaque symbol in that case, to allow the external
|
||||
// declaration to be compared to a prior compilation.
|
||||
return this.getOpaqueSymbol(decl);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates an `OpaqueSymbol` for the provided class declaration.
|
||||
*/
|
||||
private getOpaqueSymbol(decl: ClassDeclaration): OpaqueSymbol {
|
||||
if (this.opaqueSymbols.has(decl)) {
|
||||
return this.opaqueSymbols.get(decl)!;
|
||||
}
|
||||
|
||||
const symbol = new OpaqueSymbol(decl);
|
||||
this.opaqueSymbols.set(decl, symbol);
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
|
||||
function getImportPath(expr: Expression): string|null {
|
||||
if (expr instanceof ExternalExpr) {
|
||||
return `${expr.value.moduleName}\$${expr.value.name}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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 {ClassDeclaration} from '../../../reflection';
|
||||
import {isArrayEqual} from './util';
|
||||
|
||||
/**
|
||||
* Describes a generic type parameter of a semantic symbol. A class declaration with type parameters
|
||||
* needs special consideration in certain contexts. For example, template type-check blocks may
|
||||
* contain type constructors of used directives which include the type parameters of the directive.
|
||||
* As a consequence, if a change is made that affects the type parameters of said directive, any
|
||||
* template type-check blocks that use the directive need to be regenerated.
|
||||
*
|
||||
* This type represents a single generic type parameter. It currently only tracks whether the
|
||||
* type parameter has a constraint, i.e. has an `extends` clause. When a constraint is present, we
|
||||
* currently assume that the type parameter is affected in each incremental rebuild; proving that
|
||||
* a type parameter with constraint is not affected is non-trivial as it requires full semantic
|
||||
* understanding of the type constraint.
|
||||
*/
|
||||
export interface SemanticTypeParameter {
|
||||
/**
|
||||
* Whether a type constraint, i.e. an `extends` clause is present on the type parameter.
|
||||
*/
|
||||
hasGenericTypeBound: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the type parameters of the given class into their semantic representation. If the class
|
||||
* does not have any type parameters, then `null` is returned.
|
||||
*/
|
||||
export function extractSemanticTypeParameters(node: ClassDeclaration): SemanticTypeParameter[]|
|
||||
null {
|
||||
if (!ts.isClassDeclaration(node) || node.typeParameters === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.typeParameters.map(
|
||||
typeParam => ({hasGenericTypeBound: typeParam.constraint !== undefined}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the list of type parameters to determine if they can be considered equal.
|
||||
*/
|
||||
export function areTypeParametersEqual(
|
||||
current: SemanticTypeParameter[]|null, previous: SemanticTypeParameter[]|null): boolean {
|
||||
// First compare all type parameters one-to-one; any differences mean that the list of type
|
||||
// parameters has changed.
|
||||
if (!isArrayEqual(current, previous, isTypeParameterEqual)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is a current list of type parameters and if any of them has a generic type constraint,
|
||||
// then the meaning of that type parameter may have changed without us being aware; as such we
|
||||
// have to assume that the type parameters have in fact changed.
|
||||
if (current !== null && current.some(typeParam => typeParam.hasGenericTypeBound)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTypeParameterEqual(a: SemanticTypeParameter, b: SemanticTypeParameter): boolean {
|
||||
return a.hasGenericTypeBound === b.hasGenericTypeBound;
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @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 {SemanticReference, SemanticSymbol} from './api';
|
||||
|
||||
/**
|
||||
* Determines whether the provided symbols represent the same declaration.
|
||||
*/
|
||||
export function isSymbolEqual(a: SemanticSymbol, b: SemanticSymbol): boolean {
|
||||
if (a.decl === b.decl) {
|
||||
// If the declaration is identical then it must represent the same symbol.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.identifier === null || b.identifier === null) {
|
||||
// Unidentifiable symbols are assumed to be different.
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.path === b.path && a.identifier === b.identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided references to a semantic symbol are still equal, i.e. represent
|
||||
* the same symbol and are imported by the same path.
|
||||
*/
|
||||
export function isReferenceEqual(a: SemanticReference, b: SemanticReference): boolean {
|
||||
if (!isSymbolEqual(a.symbol, b.symbol)) {
|
||||
// If the reference's target symbols are different, the reference itself is different.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The reference still corresponds with the same symbol, now check that the path by which it is
|
||||
// imported has not changed.
|
||||
return a.importPath === b.importPath;
|
||||
}
|
||||
|
||||
export function referenceEquality<T>(a: T, b: T): boolean {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided arrays are equal to each other, using the provided equality tester
|
||||
* that is called for all entries in the array.
|
||||
*/
|
||||
export function isArrayEqual<T>(
|
||||
a: readonly T[]|null, b: readonly T[]|null,
|
||||
equalityTester: (a: T, b: T) => boolean = referenceEquality): boolean {
|
||||
if (a === null || b === null) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !a.some((item, index) => !equalityTester(item, b[index]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided sets are equal to each other, using the provided equality tester.
|
||||
* Sets that only differ in ordering are considered equal.
|
||||
*/
|
||||
export function isSetEqual<T>(
|
||||
a: ReadonlySet<T>|null, b: ReadonlySet<T>|null,
|
||||
equalityTester: (a: T, b: T) => boolean = referenceEquality): boolean {
|
||||
if (a === null || b === null) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const itemA of a) {
|
||||
let found = false;
|
||||
for (const itemB of b) {
|
||||
if (equalityTester(itemA, itemB)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
35
packages/compiler-cli/src/ngtsc/metadata/test/BUILD.bazel
Normal file
35
packages/compiler-cli/src/ngtsc/metadata/test/BUILD.bazel
Normal file
@ -0,0 +1,35 @@
|
||||
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/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular_es5"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps =
|
||||
[
|
||||
":test_lib",
|
||||
],
|
||||
)
|
93
packages/compiler-cli/src/ngtsc/metadata/test/dts_spec.ts
Normal file
93
packages/compiler-cli/src/ngtsc/metadata/test/dts_spec.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @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 {ExternalExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {Reference} from '../../imports';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {loadFakeCore, makeProgram} from '../../testing';
|
||||
|
||||
import {DtsMetadataReader} from '../src/dts';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
beforeEach(() => {
|
||||
loadFakeCore(getFileSystem());
|
||||
});
|
||||
|
||||
describe('DtsMetadataReader', () => {
|
||||
it('should not assume directives are structural', () => {
|
||||
const mainPath = absoluteFrom('/main.d.ts');
|
||||
const {program} = makeProgram(
|
||||
[{
|
||||
name: mainPath,
|
||||
contents: `
|
||||
import {ViewContainerRef} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
export declare class TestDir {
|
||||
constructor(p0: ViewContainerRef);
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<TestDir, "[test]", never, {}, {}, never>
|
||||
}
|
||||
`
|
||||
}],
|
||||
{
|
||||
skipLibCheck: true,
|
||||
lib: ['es6', 'dom'],
|
||||
});
|
||||
|
||||
const sf = getSourceFileOrError(program, mainPath);
|
||||
const clazz = sf.statements[2];
|
||||
if (!isNamedClassDeclaration(clazz)) {
|
||||
return fail('Expected class declaration');
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const dtsReader =
|
||||
new DtsMetadataReader(typeChecker, new TypeScriptReflectionHost(typeChecker));
|
||||
|
||||
const meta = dtsReader.getDirectiveMetadata(new Reference(clazz))!;
|
||||
expect(meta.isStructural).toBeFalse();
|
||||
});
|
||||
|
||||
it('should identify a structural directive by its constructor', () => {
|
||||
const mainPath = absoluteFrom('/main.d.ts');
|
||||
const {program} = makeProgram(
|
||||
[{
|
||||
name: mainPath,
|
||||
contents: `
|
||||
import {TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
export declare class TestDir {
|
||||
constructor(p0: ViewContainerRef, p1: TemplateRef);
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<TestDir, "[test]", never, {}, {}, never>
|
||||
}
|
||||
`
|
||||
}],
|
||||
{
|
||||
skipLibCheck: true,
|
||||
lib: ['es6', 'dom'],
|
||||
});
|
||||
|
||||
const sf = getSourceFileOrError(program, mainPath);
|
||||
const clazz = sf.statements[2];
|
||||
if (!isNamedClassDeclaration(clazz)) {
|
||||
return fail('Expected class declaration');
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const dtsReader =
|
||||
new DtsMetadataReader(typeChecker, new TypeScriptReflectionHost(typeChecker));
|
||||
|
||||
const meta = dtsReader.getDirectiveMetadata(new Reference(clazz))!;
|
||||
expect(meta.isStructural).toBeTrue();
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user