refactor(ivy): validate that identifier identity in emitted output (#21877)
Modifies validation syntax to generate back references to ensure that identifiers are used consistently. Introduced … to allow validating constant definition and usage. PR Close #21877
This commit is contained in:
parent
7007f51c35
commit
aa456edafc
|
@ -618,7 +618,11 @@ export function expectNoDiagnostics(program: ts.Program) {
|
|||
if (diagnostics && diagnostics.length) {
|
||||
throw new Error(
|
||||
'Errors from TypeScript:\n' +
|
||||
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
||||
diagnostics
|
||||
.map(
|
||||
d =>
|
||||
`${fileInfo(d)}${ts.flattenDiagnosticMessageText(d.messageText, '\n')}${lineInfo(d)}`)
|
||||
.join(' \n'));
|
||||
}
|
||||
}
|
||||
expectNoDiagnostics(program.getOptionsDiagnostics());
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ConstantPool} from '../../src/constant_pool';
|
||||
import * as o from '../../src/output/output_ast';
|
||||
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
||||
import {OutputContext} from '../../src/util';
|
||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
|
||||
|
||||
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
||||
const OPERATOR =
|
||||
/!|%|\*|\/|\^|\&|\&\&\|\||\|\||\(|\)|\{|\}|\[|\]|:|;|\.|<|<=|>|>=|=|==|===|!=|!==|=>|\+|\+\+|-|--|@|,|\.|\.\.\./;
|
||||
const STRING = /\'[^'\n]*\'|"[^'\n]*"|`[^`]*`/;
|
||||
const NUMBER = /[0-9]+/;
|
||||
const ELLIPSIS = '…';
|
||||
const TOKEN = new RegExp(
|
||||
`^((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source}|${ELLIPSIS})`);
|
||||
const WHITESPACE = /^\s+/;
|
||||
|
||||
type Piece = string | RegExp;
|
||||
|
||||
const IDENT = /[A-Za-z$_][A-Za-z0-9$_]*/;
|
||||
const SKIP = /(?:.|\n|\r)*/;
|
||||
const MATCHING_IDENT = /^\$.*\$$/;
|
||||
|
||||
function tokenize(text: string): Piece[] {
|
||||
function matches(exp: RegExp): string|false {
|
||||
const m = text.match(exp);
|
||||
if (!m) return false;
|
||||
text = text.substr(m[0].length);
|
||||
return m[0];
|
||||
}
|
||||
function next(): string {
|
||||
const result = matches(TOKEN);
|
||||
if (!result) {
|
||||
throw Error(`Invalid test, no token found for '${text.substr(0, 30)}...'`);
|
||||
}
|
||||
matches(WHITESPACE);
|
||||
return result;
|
||||
}
|
||||
|
||||
const pieces: Piece[] = [];
|
||||
matches(WHITESPACE);
|
||||
while (text) {
|
||||
const token = next();
|
||||
if (token === 'IDENT') {
|
||||
pieces.push(IDENT);
|
||||
} else if (token === ELLIPSIS) {
|
||||
pieces.push(SKIP);
|
||||
} else {
|
||||
pieces.push(token);
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
|
||||
const contextWidth = 100;
|
||||
export function expectEmit(source: string, emitted: string, description: string) {
|
||||
const pieces = tokenize(emitted);
|
||||
const expr = r(...pieces);
|
||||
if (!expr.test(source)) {
|
||||
let last: number = 0;
|
||||
for (let i = 1; i < pieces.length; i++) {
|
||||
const t = r(...pieces.slice(0, i));
|
||||
const m = source.match(t);
|
||||
const expected = pieces[i - 1] == IDENT ? '<IDENT>' : pieces[i - 1];
|
||||
if (!m) {
|
||||
const contextPieceWidth = contextWidth / 2;
|
||||
fail(
|
||||
`${description}: Expected to find ${expected} '${source.substr(0,last)}[<---HERE expected "${expected}"]${source.substr(last)}'`);
|
||||
return;
|
||||
} else {
|
||||
last = (m.index || 0) + m[0].length;
|
||||
}
|
||||
}
|
||||
fail(
|
||||
`Test helper failure: Expected expression failed but the reporting logic could not find where it failed in: ${source}`);
|
||||
}
|
||||
}
|
||||
|
||||
const IDENT_LIKE = /^[a-z][A-Z]/;
|
||||
const SPECIAL_RE_CHAR = /\/|\(|\)|\||\*|\+|\[|\]|\{|\}|\$/g;
|
||||
function r(...pieces: (string | RegExp)[]): RegExp {
|
||||
const results: string[] = [];
|
||||
let first = true;
|
||||
let group = 0;
|
||||
|
||||
const groups = new Map<string, number>();
|
||||
for (const piece of pieces) {
|
||||
if (!first)
|
||||
results.push(`\\s${typeof piece === 'string' && IDENT_LIKE.test(piece) ? '+' : '*'}`);
|
||||
first = false;
|
||||
if (typeof piece === 'string') {
|
||||
if (MATCHING_IDENT.test(piece)) {
|
||||
const matchGroup = groups.get(piece);
|
||||
if (!matchGroup) {
|
||||
results.push('(' + IDENT.source + ')');
|
||||
const newGroup = ++group;
|
||||
groups.set(piece, newGroup);
|
||||
} else {
|
||||
results.push(`\\${matchGroup}`);
|
||||
}
|
||||
} else {
|
||||
results.push(piece.replace(SPECIAL_RE_CHAR, s => '\\' + s));
|
||||
}
|
||||
} else {
|
||||
results.push('(?:' + piece.source + ')');
|
||||
}
|
||||
}
|
||||
return new RegExp(results.join(''));
|
||||
}
|
||||
|
||||
export function compile(
|
||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error;}) {
|
||||
const testFiles = toMockFileArray(data);
|
||||
const scripts = testFiles.map(entry => entry.fileName);
|
||||
const angularFilesArray = toMockFileArray(angularFiles);
|
||||
const files = arrayToMockDir([...testFiles, ...angularFilesArray]);
|
||||
const mockCompilerHost = new MockCompilerHost(scripts, files);
|
||||
const compilerHost = new MockAotCompilerHost(mockCompilerHost);
|
||||
|
||||
const program = ts.createProgram(scripts, {...settings}, mockCompilerHost);
|
||||
expectNoDiagnostics(program);
|
||||
|
||||
// TODO(chuckj): Replace with a variant of createAotCompiler() when the r3_view_compiler is
|
||||
// integrated
|
||||
const translations = options.translations || '';
|
||||
|
||||
const urlResolver = createAotUrlResolver(compilerHost);
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||
const staticReflector =
|
||||
new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector);
|
||||
const htmlParser = new I18NHtmlParser(
|
||||
new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console);
|
||||
const config = new CompilerConfig({
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
useJit: false,
|
||||
enableLegacyTemplate: options.enableLegacyTemplate === true,
|
||||
missingTranslation: options.missingTranslation,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
strictInjectionParameters: options.strictInjectionParameters,
|
||||
});
|
||||
const normalizer = new DirectiveNormalizer(
|
||||
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const templateParser = new TemplateParser(
|
||||
config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
config, htmlParser, new NgModuleResolver(staticReflector),
|
||||
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
|
||||
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector);
|
||||
|
||||
|
||||
|
||||
// Create the TypeScript program
|
||||
const sourceFiles = program.getSourceFiles().map(sf => sf.fileName);
|
||||
|
||||
// Analyze the modules
|
||||
// TODO(chuckj): Eventually this should not be necessary as the ts.SourceFile should be sufficient
|
||||
// to generate a template definition.
|
||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||
|
||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
|
||||
const fakeOutputContext: OutputContext = {
|
||||
genFilePath: 'fakeFactory.ts',
|
||||
statements: [],
|
||||
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
if (!symbol) {
|
||||
throw new Error('Invalid: undefined passed to as a symbol');
|
||||
}
|
||||
throw new Error(`Invalid: ${(symbol as any).constructor.name} is not a symbol`);
|
||||
}
|
||||
return (symbol.members || [])
|
||||
.reduce(
|
||||
(expr, member) => expr.prop(member),
|
||||
<o.Expression>o.importExpr(new o.ExternalReference(symbol.filePath, symbol.name)));
|
||||
},
|
||||
constantPool: new ConstantPool()
|
||||
};
|
||||
|
||||
// Load All directives
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||
}
|
||||
|
||||
// Compile the directives.
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
|
||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||
continue;
|
||||
}
|
||||
if (resolver.isDirective(directive)) {
|
||||
const metadata = resolver.getDirectiveMetadata(directive);
|
||||
if (metadata.isComponent) {
|
||||
const fakeUrl = 'ng://fake-template-url.html';
|
||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||
|
||||
const directives = module.transitiveModule.directives.map(
|
||||
dir => resolver.getDirectiveSummary(dir.reference));
|
||||
const pipes =
|
||||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
||||
const parsedTemplate = templateParser.parse(
|
||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||
|
||||
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
|
||||
} else {
|
||||
compileDirective(fakeOutputContext, metadata, staticReflector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fakeOutputContext.statements.unshift(...fakeOutputContext.constantPool.statements);
|
||||
|
||||
const emitter = new TypeScriptEmitter();
|
||||
|
||||
const moduleName = compilerHost.fileNameToModuleName(
|
||||
fakeOutputContext.genFilePath, fakeOutputContext.genFilePath);
|
||||
|
||||
const result = emitter.emitStatementsAndContext(
|
||||
fakeOutputContext.genFilePath, fakeOutputContext.statements, '', false,
|
||||
/* referenceFilter */ undefined,
|
||||
/* importFilter */ e => e.moduleName != null && e.moduleName.startsWith('/app'));
|
||||
|
||||
return {source: result.sourceText, outputContext: fakeOutputContext};
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('mock_compiler', () => {
|
||||
// This produces a MockDirectory of the file needed to compile an Angular application.
|
||||
// This setup is performed in a beforeAll which populates the map returned.
|
||||
const angularFiles = setup({
|
||||
compileAngular: true,
|
||||
compileAnimations: false,
|
||||
compileCommon: true,
|
||||
});
|
||||
|
||||
describe('compiling', () => {
|
||||
// To use compile you need to supply the files in a MockDirectory that can be merged
|
||||
// with a set of "environment" files such as the angular files.
|
||||
it('should be able to compile a simple application', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'hello.component.ts': `
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({template: 'Hello {{name}}!'})
|
||||
export class HelloComponent {
|
||||
@Input() name: string = 'world';
|
||||
}
|
||||
`,
|
||||
'hello.module.ts': `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {HelloComponent} from './hello.component';
|
||||
|
||||
@NgModule({declarations: [HelloComponent]})
|
||||
export class HelloModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
// result.source contains just the emitted factory declarations regardless of the original
|
||||
// module.
|
||||
expect(result.source).toContain('Hello');
|
||||
|
||||
// The output context is also returned if the actual output ast is needed.
|
||||
expect(result.outputContext.statements.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expecting emitted output', () => {
|
||||
it('should be able to find a simple expression in the output', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'hello.component.ts': `
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
|
||||
export class HelloComponent {
|
||||
@Input() name: string = 'world';
|
||||
}
|
||||
`,
|
||||
'hello.module.ts': `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {HelloComponent} from './hello.component';
|
||||
|
||||
@NgModule({declarations: [HelloComponent]})
|
||||
export class HelloModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
// The expression can expected directly.
|
||||
expectEmit(result.source, 'name.length', 'name length expression not found');
|
||||
|
||||
// Whitespace is not significant
|
||||
expectEmit(
|
||||
result.source, 'name \n\n . \n length',
|
||||
'name length expression not found (whitespace)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to skip untested regions', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'hello.component.ts': `
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
|
||||
export class HelloComponent {
|
||||
@Input() name: string = 'world';
|
||||
}
|
||||
`,
|
||||
'hello.module.ts': `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {HelloComponent} from './hello.component';
|
||||
|
||||
@NgModule({declarations: [HelloComponent]})
|
||||
export class HelloModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
// The special character … means anything can be generated between the two sections allowing
|
||||
// skipping sections of the output that are not under test. The ellipsis unicode char (…) is
|
||||
// used instead of '...' because '...' is legal JavaScript (the spread operator) and might
|
||||
// need to be tested.
|
||||
expectEmit(result.source, 'ctx.name … ctx.name.length', 'could not find correct length access');
|
||||
});
|
||||
|
||||
it('should be able to enforce consistent identifiers', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'hello.component.ts': `
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({template: 'Hello {{name}}! Your name as {{name.length}} characters'})
|
||||
export class HelloComponent {
|
||||
@Input() name: string = 'world';
|
||||
}
|
||||
`,
|
||||
'hello.module.ts': `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {HelloComponent} from './hello.component';
|
||||
|
||||
@NgModule({declarations: [HelloComponent]})
|
||||
export class HelloModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
// IDENT can be used a wild card for any identifier
|
||||
expectEmit(result.source, 'IDENT.name', 'could not find context access');
|
||||
|
||||
// $<ident>$ can be used as a wild-card but all the content matched by the identifiers must
|
||||
// match each other.
|
||||
// This is useful if the code generator is free to invent a name but should use the name
|
||||
// consistently.
|
||||
expectEmit(
|
||||
result.source, '$ctx$.$name$ … $ctx$.$name$.length',
|
||||
'could not find correct length access');
|
||||
});
|
||||
});
|
|
@ -6,15 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ConstantPool} from '../../src/constant_pool';
|
||||
import * as o from '../../src/output/output_ast';
|
||||
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
|
||||
import {OutputContext} from '../../src/util';
|
||||
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, settings, setup, toMockFileArray} from '../aot/test_util';
|
||||
import {MockDirectory, setup} from '../aot/test_util';
|
||||
import {compile, expectEmit} from './mock_compile';
|
||||
|
||||
describe('r3_view_compiler', () => {
|
||||
const angularFiles = setup({
|
||||
|
@ -112,7 +105,7 @@ describe('r3_view_compiler', () => {
|
|||
selector: 'my-app',
|
||||
template: ' {{list[0]}} {{list[1]}} {{list[2]}} {{list[3]}} {{list[4]}} {{list[5]}} {{list[6]}} {{list[7]}} {{list[8]}} '
|
||||
})
|
||||
export class MyApp implements OnInit {
|
||||
export class MyApp {
|
||||
list: any[] = [];
|
||||
}
|
||||
|
||||
|
@ -121,7 +114,7 @@ describe('r3_view_compiler', () => {
|
|||
}
|
||||
};
|
||||
|
||||
const bV_call = `IDENT.ɵbV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3],
|
||||
const bV_call = `$r3$.ɵbV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3],
|
||||
' ',ctx.list[4],' ',ctx.list[5],' ',ctx.list[6],' ',ctx.list[7],' ',ctx.list[8],
|
||||
' '])`;
|
||||
const result = compile(files, angularFiles);
|
||||
|
@ -157,27 +150,26 @@ describe('r3_view_compiler', () => {
|
|||
|
||||
// The template should look like this (where IDENT is a wild card for an identifier):
|
||||
const template = `
|
||||
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
|
||||
…
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'div', IDENT);
|
||||
IDENT.ɵT(1, 'Hello ');
|
||||
IDENT.ɵE(2, 'b');
|
||||
IDENT.ɵT(3, 'World');
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵT(4, '!');
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'div', $c1$);
|
||||
$r3$.ɵT(1, 'Hello ');
|
||||
$r3$.ɵE(2, 'b');
|
||||
$r3$.ɵT(3, 'World');
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(4, '!');
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// The compiler should also emit a const array like this:
|
||||
const constants = `const IDENT = ['class', 'my-app', 'title', 'Hello'];`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
|
||||
expectEmit(result.source, factory, 'Incorrect factory');
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
expectEmit(result.source, constants, 'Incorrect shared constants');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -206,20 +198,20 @@ describe('r3_view_compiler', () => {
|
|||
|
||||
// ChildComponent definition should be:
|
||||
const ChildComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ChildComponent,
|
||||
tag: 'child',
|
||||
factory: function ChildComponent_Factory() { return new ChildComponent(); },
|
||||
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵT(0, 'child-view');
|
||||
$r3$.ɵT(0, 'child-view');
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
// SomeDirective definition should be:
|
||||
const SomeDirectiveDefinition = `
|
||||
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: SomeDirective,
|
||||
factory: function SomeDirective_Factory() {return new SomeDirective(); }
|
||||
});
|
||||
|
@ -227,28 +219,27 @@ describe('r3_view_compiler', () => {
|
|||
|
||||
// MyComponent definition should be:
|
||||
const MyComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = ['some-directive', ''];
|
||||
const $c2$ = [SomeDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, ChildComponent, IDENT, IDENT);
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵT(3, '!');
|
||||
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(3, '!');
|
||||
}
|
||||
ChildComponent.ngComponentDef.h(1, 0);
|
||||
SomeDirective.ngDirectiveDef.h(2, 0);
|
||||
IDENT.ɵr(1, 0);
|
||||
IDENT.ɵr(2, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
$r3$.ɵr(2, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
// The following constants should be emitted as well.
|
||||
const AttributesConstant = `const IDENT = ['some-directive', ''];`;
|
||||
|
||||
const DirectivesConstant = `const IDENT = [SomeDirective];`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
@ -256,8 +247,6 @@ describe('r3_view_compiler', () => {
|
|||
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
|
||||
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
|
||||
expectEmit(source, AttributesConstant, 'Incorrect shared attributes constant');
|
||||
expectEmit(source, DirectivesConstant, 'Incorrect share directives constant');
|
||||
});
|
||||
|
||||
it('should support structural directives', () => {
|
||||
|
@ -286,47 +275,46 @@ describe('r3_view_compiler', () => {
|
|||
};
|
||||
|
||||
const IfDirectiveDefinition = `
|
||||
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: IfDirective,
|
||||
factory: function IfDirective_Factory() { return new IfDirective(IDENT.ɵinjectTemplateRef()); }
|
||||
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
|
||||
});`;
|
||||
const MyComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = ['foo', ''];
|
||||
const $c2$ = [IfDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'ul', null, null, IDENT);
|
||||
IDENT.ɵC(2, IDENT, MyComponent_IfDirective_Template_2);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'ul', null, null, $c1$);
|
||||
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const IDENT = IDENT.ɵm(1);
|
||||
const $foo$ = $r3$.ɵm(1);
|
||||
IfDirective.ngDirectiveDef.h(3,2);
|
||||
IDENT.ɵcR(2);
|
||||
IDENT.ɵr(3,2);
|
||||
IDENT.ɵcr();
|
||||
$r3$.ɵcR(2);
|
||||
$r3$.ɵr(3,2);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'li');
|
||||
IDENT.ɵT(1);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
IDENT.ɵt(1, IDENT.ɵb2('', ctx.salutation, ' ', IDENT, ''));
|
||||
$r3$.ɵt(1, $r3$.ɵb2('', ctx.salutation, ' ', $foo$, ''));
|
||||
}
|
||||
}
|
||||
});`;
|
||||
const locals = `const IDENT = ['foo', ''];`;
|
||||
const directives = `const IDENT = [IfDirective];`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
expectEmit(source, locals, 'Incorrect share locals constant');
|
||||
expectEmit(source, directives, 'Incorrect shared directive constant');
|
||||
});
|
||||
|
||||
it('should support content projection', () => {
|
||||
|
@ -359,50 +347,49 @@ describe('r3_view_compiler', () => {
|
|||
};
|
||||
|
||||
const SimpleComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
tag: 'simple',
|
||||
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
|
||||
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵpD(0);
|
||||
IDENT.ɵE(1, 'div');
|
||||
IDENT.ɵP(2, 0);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵpD(0);
|
||||
$r3$.ɵE(1, 'div');
|
||||
$r3$.ɵP(2, 0);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
});`;
|
||||
|
||||
const ComplexComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
|
||||
const $c2$ = ['id','first'];
|
||||
const $c3$ = ['id','second'];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: ComplexComponent,
|
||||
tag: 'complex',
|
||||
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
|
||||
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵpD(0, IDENT);
|
||||
IDENT.ɵE(1, 'div', IDENT);
|
||||
IDENT.ɵP(2, 0, 1);
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵE(3, 'div', IDENT);
|
||||
IDENT.ɵP(4, 0, 2);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵpD(0, $c1$);
|
||||
$r3$.ɵE(1, 'div', $c2$);
|
||||
$r3$.ɵP(2, 0, 1);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(3, 'div', $c3$);
|
||||
$r3$.ɵP(4, 0, 2);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const ComplexComponent_ProjectionConst = `
|
||||
const IDENT = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
|
||||
expectEmit(
|
||||
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
|
||||
expectEmit(result.source, ComplexComponent_ProjectionConst, 'Incorrect projection const');
|
||||
});
|
||||
|
||||
it('local reference', () => {
|
||||
|
@ -421,29 +408,28 @@ describe('r3_view_compiler', () => {
|
|||
};
|
||||
|
||||
const MyComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = ['user', ''];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'input', null, null, IDENT);
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵT(2);
|
||||
$r3$.ɵE(0, 'input', null, null, $c1$);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵT(2);
|
||||
}
|
||||
const IDENT = IDENT.ɵm(1);
|
||||
IDENT.ɵt(2, IDENT.ɵb1('Hello ', IDENT.value, '!'));
|
||||
const $user$ = $r3$.ɵm(1);
|
||||
$r3$.ɵt(2, $r3$.ɵb1('Hello ', $user$.value, '!'));
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const locals = `const IDENT = ['user', ''];`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
expectEmit(source, locals, 'Incorrect locals constant definition');
|
||||
});
|
||||
|
||||
describe('lifecycle hooks', () => {
|
||||
|
@ -484,7 +470,7 @@ describe('r3_view_compiler', () => {
|
|||
name2 = '2';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [LifecycleComp, SimpleLayout]}
|
||||
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
|
||||
export class LifecycleModule {}
|
||||
`
|
||||
}
|
||||
|
@ -492,33 +478,35 @@ describe('r3_view_compiler', () => {
|
|||
|
||||
it('should gen hooks with a few simple components', () => {
|
||||
const LifecycleCompDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: LifecycleComp,
|
||||
tag: 'lifecycle-comp',
|
||||
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
|
||||
template: function LifecycleComp_Template(ctx: any, cm: boolean) {},
|
||||
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
|
||||
inputs: {nameMin: 'name'},
|
||||
features: [IDENT.ɵNgOnChangesFeature(LifecycleComp)]
|
||||
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
|
||||
});`;
|
||||
|
||||
const SimpleLayoutDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = LifecycleComp.ngComponentDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: SimpleLayout,
|
||||
tag: 'simple-layout',
|
||||
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
|
||||
template: function SimpleLayout_Template(ctx: any, cm: boolean) {
|
||||
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, LifecycleComp);
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵE(2, LifecycleComp);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, LifecycleComp);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(2, LifecycleComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
IDENT.ɵp(0, 'name', IDENT.ɵb(ctx.name1));
|
||||
IDENT.ɵp(2, 'name', IDENT.ɵb(ctx.name2));
|
||||
IDENT.h(1, 0);
|
||||
IDENT.h(3, 2);
|
||||
IDENT.ɵr(1, 0);
|
||||
IDENT.ɵr(3, 2);
|
||||
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
|
||||
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
|
||||
$c1$.h(1, 0);
|
||||
$c1$.h(3, 2);
|
||||
$r3$.ɵr(1, 0);
|
||||
$r3$.ɵr(3, 2);
|
||||
}
|
||||
});`;
|
||||
|
||||
|
@ -610,41 +598,43 @@ describe('r3_view_compiler', () => {
|
|||
|
||||
// TODO(chuckj): Enforce this when the directives are specified
|
||||
const ForDirectiveDefinition = `
|
||||
static ngDirectiveDef = IDENT.ɵdefineDirective({
|
||||
static ngDirectiveDef = $r3$.ɵdefineDirective({
|
||||
type: ForOfDirective,
|
||||
factory: function ForOfDirective_Factory() {
|
||||
return new ForOfDirective(IDENT.ɵinjectViewContainerRef(), IDENT.ɵinjectTemplateRef());
|
||||
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
|
||||
},
|
||||
features: [IDENT.ɵNgOnChangesFeature(NgForOf)],
|
||||
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
|
||||
inputs: {forOf: 'forOf'}
|
||||
});
|
||||
`;
|
||||
|
||||
const MyComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = [ForOfDirective];
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'ul');
|
||||
IDENT.ɵC(1, IDENT, MyComponent_ForOfDirective_Template_1);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||
ForOfDirective.ngDirectiveDef.h(2, 1);
|
||||
IDENT.ɵcR(1);
|
||||
IDENT.ɵr(2, 1);
|
||||
IDENT.ɵcr();
|
||||
$r3$.ɵcR(1);
|
||||
$r3$.ɵr(2, 1);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'li');
|
||||
IDENT.ɵT(1);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const IDENT = ctx0.$implicit;
|
||||
IDENT.ɵt(1, IDENT.ɵb1('', IDENT.name, ''));
|
||||
const $item$ = ctx0.$implicit;
|
||||
$r3$.ɵt(1, $r3$.ɵb1('', $item$.name, ''));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -681,7 +671,7 @@ describe('r3_view_compiler', () => {
|
|||
</ul>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
items: Item[] = [
|
||||
items = [
|
||||
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
|
||||
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
|
||||
];
|
||||
|
@ -696,50 +686,53 @@ describe('r3_view_compiler', () => {
|
|||
};
|
||||
|
||||
const MyComponentDefinition = `
|
||||
static ngComponentDef = IDENT.ɵdefineComponent({
|
||||
const $c1$ = [ForOfDirective];
|
||||
const $c2$ = ForOfDirective.ngDirectiveDef;
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyComponent,
|
||||
tag: 'my-component',
|
||||
factory: function MyComponent_Factory() { return new MyComponent(); },
|
||||
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'ul');
|
||||
IDENT.ɵC(1, IDENT, MyComponent_ForOfDirective_Template_1);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'ul');
|
||||
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
IDENT.ɵp(1, 'forOf', IDENT.ɵb(ctx.items));
|
||||
IDENT.h(2,1);
|
||||
IDENT.ɵcR(1);
|
||||
IDENT.ɵr(2, 1);
|
||||
IDENT.ɵcr();
|
||||
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
|
||||
$c2$.h(2,1);
|
||||
$r3$.ɵcR(1);
|
||||
$r3$.ɵr(2, 1);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'li');
|
||||
IDENT.ɵE(1, 'div');
|
||||
IDENT.ɵT(2);
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵE(3, 'ul');
|
||||
IDENT.ɵC(4, IDENT, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
||||
IDENT.ɵe();
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵE(1, 'div');
|
||||
$r3$.ɵT(2);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵE(3, 'ul');
|
||||
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
|
||||
$r3$.ɵe();
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const IDENT = ctx0.$implicit;
|
||||
IDENT.ɵp(4, 'forOf', IDENT.ɵb(IDENT.infos));
|
||||
IDENT.h(5,4);
|
||||
IDENT.ɵt(2, IDENT.ɵb1('', IDENT.name, ''));
|
||||
IDENT.ɵcR(4);
|
||||
IDENT.ɵr(5, 4);
|
||||
IDENT.ɵcr();
|
||||
const $item$ = ctx0.$implicit;
|
||||
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
|
||||
$c2$.h(5,4);
|
||||
$r3$.ɵt(2, $r3$.ɵb1('', IDENT.name, ''));
|
||||
$r3$.ɵcR(4);
|
||||
$r3$.ɵr(5, 4);
|
||||
$r3$.ɵcr();
|
||||
|
||||
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
|
||||
ctx1: IDENT, cm: IDENT) {
|
||||
if (cm) {
|
||||
IDENT.ɵE(0, 'li');
|
||||
IDENT.ɵT(1);
|
||||
IDENT.ɵe();
|
||||
$r3$.ɵE(0, 'li');
|
||||
$r3$.ɵT(1);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
const IDENT = ctx1.$implicit;
|
||||
IDENT.ɵt(1, IDENT.ɵb2(' ', IDENT.name, ': ', IDENT.description, ' '));
|
||||
const $info$ = ctx1.$implicit;
|
||||
$r3$.ɵt(1, $r3$.ɵb2(' ', $item$.name, ': ', $info$.description, ' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -752,207 +745,3 @@ describe('r3_view_compiler', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
const IDENTIFIER = /[A-Za-z_$ɵ][A-Za-z0-9_$]*/;
|
||||
const OPERATOR =
|
||||
/!|%|\*|\/|\^|\&|\&\&\|\||\|\||\(|\)|\{|\}|\[|\]|:|;|\.|<|<=|>|>=|=|==|===|!=|!==|=>|\+|\+\+|-|--|@|,|\.|\.\.\./;
|
||||
const STRING = /\'[^'\n]*\'|"[^'\n]*"|`[^`]*`/;
|
||||
const NUMBER = /[0-9]+/;
|
||||
const TOKEN = new RegExp(
|
||||
`^((${IDENTIFIER.source})|(${OPERATOR.source})|(${STRING.source})|${NUMBER.source})`);
|
||||
const WHITESPACE = /^\s+/;
|
||||
|
||||
type Piece = string | RegExp;
|
||||
|
||||
const IDENT = /[A-Za-z$_][A-Za-z0-9$_]*/;
|
||||
|
||||
function tokenize(text: string): Piece[] {
|
||||
function matches(exp: RegExp): string|false {
|
||||
const m = text.match(exp);
|
||||
if (!m) return false;
|
||||
text = text.substr(m[0].length);
|
||||
return m[0];
|
||||
}
|
||||
function next(): string {
|
||||
const result = matches(TOKEN);
|
||||
if (!result) {
|
||||
throw Error(`Invalid test, no token found for '${text.substr(0, 30)}...'`);
|
||||
}
|
||||
matches(WHITESPACE);
|
||||
return result;
|
||||
}
|
||||
|
||||
const pieces: Piece[] = [];
|
||||
matches(WHITESPACE);
|
||||
while (text) {
|
||||
const token = next();
|
||||
if (token === 'IDENT') {
|
||||
pieces.push(IDENT);
|
||||
} else {
|
||||
pieces.push(token);
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
|
||||
const contextWidth = 100;
|
||||
function expectEmit(source: string, emitted: string, description: string) {
|
||||
const pieces = tokenize(emitted);
|
||||
const expr = r(...pieces);
|
||||
if (!expr.test(source)) {
|
||||
let last: number = 0;
|
||||
for (let i = 1; i < pieces.length; i++) {
|
||||
let t = r(...pieces.slice(0, i));
|
||||
let m = source.match(t);
|
||||
let expected = pieces[i - 1] == IDENT ? '<IDENT>' : pieces[i - 1];
|
||||
if (!m) {
|
||||
const contextPieceWidth = contextWidth / 2;
|
||||
fail(
|
||||
`${description}: Expected to find ${expected} '${source.substr(0,last)}[<---HERE expected "${expected}"]${source.substr(last)}'`);
|
||||
return;
|
||||
} else {
|
||||
last = (m.index || 0) + m[0].length;
|
||||
}
|
||||
}
|
||||
fail(
|
||||
`Test helper failure: Expected expression failed but the reporting logic could not find where it failed in: ${source}`);
|
||||
}
|
||||
}
|
||||
|
||||
const IDENT_LIKE = /^[a-z][A-Z]/;
|
||||
const SPECIAL_RE_CHAR = /\/|\(|\)|\||\*|\+|\[|\]|\{|\}|\$/g;
|
||||
function r(...pieces: (string | RegExp)[]): RegExp {
|
||||
let results: string[] = [];
|
||||
let first = true;
|
||||
for (const piece of pieces) {
|
||||
if (!first)
|
||||
results.push(`\\s${typeof piece === 'string' && IDENT_LIKE.test(piece) ? '+' : '*'}`);
|
||||
first = false;
|
||||
if (typeof piece === 'string') {
|
||||
results.push(piece.replace(SPECIAL_RE_CHAR, s => '\\' + s));
|
||||
} else {
|
||||
results.push('(' + piece.source + ')');
|
||||
}
|
||||
}
|
||||
return new RegExp(results.join(''));
|
||||
}
|
||||
|
||||
function compile(
|
||||
data: MockDirectory, angularFiles: MockData, options: AotCompilerOptions = {},
|
||||
errorCollector: (error: any, fileName?: string) => void = error => { throw error; }) {
|
||||
const testFiles = toMockFileArray(data);
|
||||
const scripts = testFiles.map(entry => entry.fileName);
|
||||
const angularFilesArray = toMockFileArray(angularFiles);
|
||||
const files = arrayToMockDir([...testFiles, ...angularFilesArray]);
|
||||
const mockCompilerHost = new MockCompilerHost(scripts, files);
|
||||
const compilerHost = new MockAotCompilerHost(mockCompilerHost);
|
||||
|
||||
const program = ts.createProgram(scripts, {...settings}, mockCompilerHost);
|
||||
|
||||
// TODO(chuckj): Replace with a variant of createAotCompiler() when the r3_view_compiler is
|
||||
// integrated
|
||||
const translations = options.translations || '';
|
||||
|
||||
const urlResolver = createAotUrlResolver(compilerHost);
|
||||
const symbolCache = new StaticSymbolCache();
|
||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||
const staticReflector =
|
||||
new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector);
|
||||
const htmlParser = new I18NHtmlParser(
|
||||
new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console);
|
||||
const config = new CompilerConfig({
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
useJit: false,
|
||||
enableLegacyTemplate: options.enableLegacyTemplate === true,
|
||||
missingTranslation: options.missingTranslation,
|
||||
preserveWhitespaces: options.preserveWhitespaces,
|
||||
strictInjectionParameters: options.strictInjectionParameters,
|
||||
});
|
||||
const normalizer = new DirectiveNormalizer(
|
||||
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const templateParser = new TemplateParser(
|
||||
config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
config, htmlParser, new NgModuleResolver(staticReflector),
|
||||
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
|
||||
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector);
|
||||
|
||||
|
||||
|
||||
// Create the TypeScript program
|
||||
const sourceFiles = program.getSourceFiles().map(sf => sf.fileName);
|
||||
|
||||
// Analyze the modules
|
||||
// TODO(chuckj): Eventually this should not be necessary as the ts.SourceFile should be sufficient
|
||||
// to generate a template definition.
|
||||
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
|
||||
|
||||
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
|
||||
|
||||
const fakeOutputContext: OutputContext = {
|
||||
genFilePath: 'fakeFactory.ts',
|
||||
statements: [],
|
||||
importExpr(symbol: StaticSymbol, typeParams: o.Type[]) {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
if (!symbol) {
|
||||
throw new Error('Invalid: undefined passed to as a symbol');
|
||||
}
|
||||
throw new Error(`Invalid: ${(symbol as any).constructor.name} is not a symbol`);
|
||||
}
|
||||
return (symbol.members || [])
|
||||
.reduce(
|
||||
(expr, member) => expr.prop(member),
|
||||
<o.Expression>o.importExpr(new o.ExternalReference(symbol.filePath, symbol.name)));
|
||||
},
|
||||
constantPool: new ConstantPool()
|
||||
};
|
||||
|
||||
// Load All directives
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
|
||||
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
|
||||
}
|
||||
|
||||
// Compile the directives.
|
||||
for (const directive of directives) {
|
||||
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
|
||||
if (!module || !module.type.reference.filePath.startsWith('/app')) {
|
||||
continue;
|
||||
}
|
||||
if (resolver.isDirective(directive)) {
|
||||
const metadata = resolver.getDirectiveMetadata(directive);
|
||||
if (metadata.isComponent) {
|
||||
const fakeUrl = 'ng://fake-template-url.html';
|
||||
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
|
||||
|
||||
const directives = module.transitiveModule.directives.map(
|
||||
dir => resolver.getDirectiveSummary(dir.reference));
|
||||
const pipes =
|
||||
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
|
||||
const parsedTemplate = templateParser.parse(
|
||||
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
|
||||
|
||||
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
|
||||
} else {
|
||||
compileDirective(fakeOutputContext, metadata, staticReflector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fakeOutputContext.statements.unshift(...fakeOutputContext.constantPool.statements);
|
||||
|
||||
const emitter = new TypeScriptEmitter();
|
||||
|
||||
const moduleName = compilerHost.fileNameToModuleName(
|
||||
fakeOutputContext.genFilePath, fakeOutputContext.genFilePath);
|
||||
|
||||
const result = emitter.emitStatementsAndContext(
|
||||
fakeOutputContext.genFilePath, fakeOutputContext.statements, '', false,
|
||||
/* referenceFilter */ undefined,
|
||||
/* importFilter */ e => e.moduleName != null && e.moduleName.startsWith('/app'));
|
||||
|
||||
return {source: result.sourceText, outputContext: fakeOutputContext};
|
||||
}
|
Loading…
Reference in New Issue