refactor(ivy): split apart the 'metadata' package in the ngtsc compiler (#27743)

This refactoring moves code around between a few of the ngtsc subpackages,
with the goal of having a more logical package structure. Additional
interfaces are also introduced where they make sense.

The 'metadata' package formerly contained both the partial evaluator,
the TypeScriptReflectionHost as well as some other reflection functions,
and the Reference interface and various implementations. This package
was split into 3 parts.

The partial evaluator now has its own package 'partial_evaluator', and
exists behind an interface PartialEvaluator instead of a top-level
function. In the future this will be useful for reducing churn as the
partial evaluator becomes more complicated.

The TypeScriptReflectionHost and other miscellaneous functions have moved
into a new 'reflection' package. The former 'host' package which contained
the ReflectionHost interface and associated types was also merged into this
new 'reflection' package.

Finally, the Reference APIs were moved to the 'imports' package, which will
consolidate all import-related logic in ngtsc.

PR Close #27743
This commit is contained in:
Alex Rickabaugh
2018-12-18 09:48:15 -08:00
committed by Kara Erickson
parent 37b716b298
commit 2a6108af97
64 changed files with 534 additions and 433 deletions

View File

@ -0,0 +1,26 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
ts_library(
name = "test_lib",
testonly = True,
srcs = glob([
"**/*.ts",
]),
deps = [
"//packages:types",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/testing",
"@ngdeps//typescript",
],
)
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
deps = [
":test_lib",
"//tools/testing:node_no_angular",
],
)

View File

@ -0,0 +1,227 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {CtorParameter} from '../src/host';
import {TypeScriptReflectionHost} from '../src/typescript';
describe('reflector', () => {
describe('ctor params', () => {
it('should reflect a single argument', () => {
const {program} = makeProgram([{
name: 'entry.ts',
contents: `
class Bar {}
class Foo {
constructor(bar: Bar) {}
}
`
}]);
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(1);
expectParameter(args[0], 'bar', 'Bar');
});
it('should reflect a decorated argument', () => {
const {program} = makeProgram([
{
name: 'dec.ts',
contents: `
export function dec(target: any, key: string, index: number) {
}
`
},
{
name: 'entry.ts',
contents: `
import {dec} from './dec';
class Bar {}
class Foo {
constructor(@dec bar: Bar) {}
}
`
}
]);
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(1);
expectParameter(args[0], 'bar', 'Bar', 'dec', './dec');
});
it('should reflect a decorated argument with a call', () => {
const {program} = makeProgram([
{
name: 'dec.ts',
contents: `
export function dec(target: any, key: string, index: number) {
}
`
},
{
name: 'entry.ts',
contents: `
import {dec} from './dec';
class Bar {}
class Foo {
constructor(@dec bar: Bar) {}
}
`
}
]);
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(1);
expectParameter(args[0], 'bar', 'Bar', 'dec', './dec');
});
it('should reflect a decorated argument with an indirection', () => {
const {program} = makeProgram([
{
name: 'bar.ts',
contents: `
export class Bar {}
`
},
{
name: 'entry.ts',
contents: `
import {Bar} from './bar';
import * as star from './bar';
class Foo {
constructor(bar: Bar, otherBar: star.Bar) {}
}
`
}
]);
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(2);
expectParameter(args[0], 'bar', 'Bar');
expectParameter(args[1], 'otherBar', 'star.Bar');
});
it('should reflect an nullable argument', () => {
const {program} = makeProgram([
{
name: 'bar.ts',
contents: `
export class Bar {}
`
},
{
name: 'entry.ts',
contents: `
import {Bar} from './bar';
class Foo {
constructor(bar: Bar|null) {}
}
`
}
]);
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const args = host.getConstructorParameters(clazz) !;
expect(args.length).toBe(1);
expectParameter(args[0], 'bar', 'Bar');
});
});
it('should reflect a re-export', () => {
const {program} = makeProgram([
{name: '/node_modules/absolute/index.ts', contents: 'export class Target {}'},
{name: 'local1.ts', contents: `export {Target as AliasTarget} from 'absolute';`},
{name: 'local2.ts', contents: `export {AliasTarget as Target} from './local1';`}, {
name: 'entry.ts',
contents: `
import {Target} from './local2';
import {Target as DirectTarget} from 'absolute';
const target = Target;
const directTarget = DirectTarget;
`
}
]);
const target = getDeclaration(program, 'entry.ts', 'target', ts.isVariableDeclaration);
if (target.initializer === undefined || !ts.isIdentifier(target.initializer)) {
return fail('Unexpected initializer for target');
}
const directTarget =
getDeclaration(program, 'entry.ts', 'directTarget', ts.isVariableDeclaration);
if (directTarget.initializer === undefined || !ts.isIdentifier(directTarget.initializer)) {
return fail('Unexpected initializer for directTarget');
}
const Target = target.initializer;
const DirectTarget = directTarget.initializer;
const checker = program.getTypeChecker();
const host = new TypeScriptReflectionHost(checker);
const targetDecl = host.getDeclarationOfIdentifier(Target);
const directTargetDecl = host.getDeclarationOfIdentifier(DirectTarget);
if (targetDecl === null) {
return fail('No declaration found for Target');
} else if (directTargetDecl === null) {
return fail('No declaration found for DirectTarget');
}
expect(targetDecl.node.getSourceFile().fileName).toBe('/node_modules/absolute/index.ts');
expect(ts.isClassDeclaration(targetDecl.node)).toBe(true);
expect(directTargetDecl.viaModule).toBe('absolute');
expect(directTargetDecl.node).toBe(targetDecl.node);
});
});
function expectParameter(
param: CtorParameter, name: string, type?: string, decorator?: string,
decoratorFrom?: string): void {
expect(param.name !).toEqual(name);
if (type === undefined) {
expect(param.typeExpression).toBeNull();
} else {
expect(param.typeExpression).not.toBeNull();
expect(argExpressionToString(param.typeExpression !)).toEqual(type);
}
if (decorator !== undefined) {
expect(param.decorators).not.toBeNull();
expect(param.decorators !.length).toBeGreaterThan(0);
expect(param.decorators !.some(
dec => dec.name === decorator && dec.import !== null &&
dec.import.from === decoratorFrom))
.toBe(true);
}
}
function argExpressionToString(name: ts.Node | null): string {
if (name == null) {
throw new Error('\'name\' argument can\'t be null');
}
if (ts.isIdentifier(name)) {
return name.text;
} else if (ts.isPropertyAccessExpression(name)) {
return `${argExpressionToString(name.expression)}.${name.name.text}`;
} else {
throw new Error(`Unexpected node in arg expression: ${ts.SyntaxKind[name.kind]}.`);
}
}