2018-07-16 03:51:14 -04:00
|
|
|
/**
|
|
|
|
* @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';
|
2018-11-25 16:40:25 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
|
|
|
|
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
|
2019-04-28 15:47:57 -04:00
|
|
|
import {ClassMemberKind, CtorParameter, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {getDeclaration} from '../../../src/ngtsc/testing';
|
|
|
|
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
|
2018-08-22 15:33:17 -04:00
|
|
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
2019-03-29 06:13:14 -04:00
|
|
|
import {MockLogger} from '../helpers/mock_logger';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {getRootFiles, makeTestBundleProgram} from '../helpers/utils';
|
2018-07-16 03:51:14 -04:00
|
|
|
|
2019-03-04 14:43:55 -05:00
|
|
|
import {expectTypeValueReferencesForParameters} from './util';
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
runInEachFileSystem(() => {
|
|
|
|
describe('Esm2015ReflectionHost', () => {
|
|
|
|
|
|
|
|
let _: typeof absoluteFrom;
|
|
|
|
|
|
|
|
let SOME_DIRECTIVE_FILE: TestFile;
|
2019-05-23 17:40:17 -04:00
|
|
|
let CTOR_DECORATORS_ARRAY_FILE: TestFile;
|
2019-06-06 15:22:32 -04:00
|
|
|
let ACCESSORS_FILE: TestFile;
|
|
|
|
let SIMPLE_CLASS_FILE: TestFile;
|
|
|
|
let CLASS_EXPRESSION_FILE: TestFile;
|
|
|
|
let FOO_FUNCTION_FILE: TestFile;
|
|
|
|
let INVALID_DECORATORS_FILE: TestFile;
|
|
|
|
let INVALID_DECORATOR_ARGS_FILE: TestFile;
|
|
|
|
let INVALID_PROP_DECORATORS_FILE: TestFile;
|
|
|
|
let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile;
|
|
|
|
let INVALID_CTOR_DECORATORS_FILE: TestFile;
|
|
|
|
let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile;
|
|
|
|
let IMPORTS_FILES: TestFile[];
|
|
|
|
let EXPORTS_FILES: TestFile[];
|
|
|
|
let FUNCTION_BODY_FILE: TestFile;
|
|
|
|
let MARKER_FILE: TestFile;
|
|
|
|
let DECORATED_FILES: TestFile[];
|
|
|
|
let ARITY_CLASSES: TestFile[];
|
|
|
|
let TYPINGS_SRC_FILES: TestFile[];
|
|
|
|
let TYPINGS_DTS_FILES: TestFile[];
|
|
|
|
let MODULE_WITH_PROVIDERS_PROGRAM: TestFile[];
|
|
|
|
let NAMESPACED_IMPORT_FILE: TestFile;
|
2019-10-16 12:15:01 -04:00
|
|
|
let INDEX_SIGNATURE_PROP_FILE: TestFile;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
_ = absoluteFrom;
|
|
|
|
|
|
|
|
SOME_DIRECTIVE_FILE = {
|
|
|
|
name: _('/some_directive.js'),
|
|
|
|
contents: `
|
2019-11-18 14:53:25 -05:00
|
|
|
import { Directive, Inject, InjectionToken, Input, HostListener, HostBinding, ViewContainerRef, TemplateRef } from '@angular/core';
|
2018-10-10 09:17:32 -04:00
|
|
|
|
|
|
|
const INJECTED_TOKEN = new InjectionToken('injected');
|
2019-11-18 14:53:25 -05:00
|
|
|
const TestToken = {};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
|
|
|
class SomeDirective {
|
|
|
|
constructor(_viewContainer, _template, injected) {
|
|
|
|
this.instanceProperty = 'instance';
|
|
|
|
}
|
|
|
|
instanceMethod() {}
|
|
|
|
|
|
|
|
onClick() {}
|
|
|
|
|
|
|
|
@HostBinding('class.foo')
|
|
|
|
get isClassFoo() { return false; }
|
|
|
|
|
|
|
|
static staticMethod() {}
|
|
|
|
}
|
|
|
|
SomeDirective.staticProperty = 'static';
|
|
|
|
SomeDirective.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[someDirective]' },] }
|
|
|
|
];
|
|
|
|
SomeDirective.ctorParameters = () => [
|
|
|
|
{ type: ViewContainerRef, },
|
|
|
|
{ type: TemplateRef, },
|
|
|
|
{ type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
|
|
|
|
];
|
|
|
|
SomeDirective.propDecorators = {
|
|
|
|
"input1": [{ type: Input },],
|
|
|
|
"input2": [{ type: Input },],
|
|
|
|
"target": [{ type: HostBinding, args: ['attr.target',] }, { type: Input },],
|
|
|
|
"onClick": [{ type: HostListener, args: ['click',] },],
|
|
|
|
};
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-05-23 17:40:17 -04:00
|
|
|
CTOR_DECORATORS_ARRAY_FILE = {
|
|
|
|
name: _('/ctor_decorated_as_array.js'),
|
|
|
|
contents: `
|
|
|
|
class CtorDecoratedAsArray {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }];
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
ACCESSORS_FILE = {
|
|
|
|
name: _('/accessors.js'),
|
|
|
|
contents: `
|
2019-01-27 11:21:29 -05:00
|
|
|
import { Directive, Input, Output } from '@angular/core';
|
|
|
|
|
|
|
|
class SomeDirective {
|
|
|
|
set setterAndGetter(value) { this.value = value; }
|
|
|
|
get setterAndGetter() { return null; }
|
|
|
|
}
|
|
|
|
SomeDirective.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[someDirective]' },] }
|
|
|
|
];
|
|
|
|
SomeDirective.propDecorators = {
|
|
|
|
"setterAndGetter": [{ type: Input },],
|
|
|
|
};
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-01-27 11:21:29 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
SIMPLE_CLASS_FILE = {
|
|
|
|
name: _('/simple_class.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
class EmptyClass {}
|
|
|
|
class NoDecoratorConstructorClass {
|
|
|
|
constructor(foo) {}
|
|
|
|
}
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
CLASS_EXPRESSION_FILE = {
|
|
|
|
name: _('/class_expression.js'),
|
|
|
|
contents: `
|
2019-09-11 16:08:53 -04:00
|
|
|
import {Directive} from '@angular/core';
|
2019-03-05 17:29:28 -05:00
|
|
|
var AliasedClass_1;
|
|
|
|
let EmptyClass = class EmptyClass {};
|
|
|
|
let AliasedClass = AliasedClass_1 = class AliasedClass {}
|
2019-09-11 16:08:53 -04:00
|
|
|
AliasedClass.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[someDirective]' },] }
|
|
|
|
];
|
2019-03-05 17:29:28 -05:00
|
|
|
let usageOfAliasedClass = AliasedClass_1;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-03-05 17:29:28 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
FOO_FUNCTION_FILE = {
|
|
|
|
name: _('/foo_function.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import { Directive } from '@angular/core';
|
|
|
|
|
|
|
|
function foo() {}
|
|
|
|
foo.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[ignored]' },] }
|
|
|
|
];
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_DECORATORS_FILE = {
|
|
|
|
name: _('/invalid_decorators.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Directive} from '@angular/core';
|
|
|
|
class NotArrayLiteral {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.decorators = () => [
|
|
|
|
{ type: Directive, args: [{ selector: '[ignored]' },] },
|
|
|
|
];
|
|
|
|
|
|
|
|
class NotObjectLiteral {
|
|
|
|
}
|
|
|
|
NotObjectLiteral.decorators = [
|
|
|
|
"This is not an object literal",
|
|
|
|
{ type: Directive },
|
|
|
|
];
|
|
|
|
|
|
|
|
class NoTypeProperty {
|
|
|
|
}
|
|
|
|
NoTypeProperty.decorators = [
|
|
|
|
{ notType: Directive },
|
|
|
|
{ type: Directive },
|
|
|
|
];
|
|
|
|
|
|
|
|
class NotIdentifier {
|
|
|
|
}
|
|
|
|
NotIdentifier.decorators = [
|
|
|
|
{ type: 'StringsLiteralsAreNotIdentifiers' },
|
|
|
|
{ type: Directive },
|
|
|
|
];
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_DECORATOR_ARGS_FILE = {
|
|
|
|
name: _('/invalid_decorator_args.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Directive} from '@angular/core';
|
|
|
|
class NoArgsProperty {
|
|
|
|
}
|
|
|
|
NoArgsProperty.decorators = [
|
2019-11-18 14:53:25 -05:00
|
|
|
|
2018-10-10 09:17:32 -04:00
|
|
|
{ type: Directive },
|
|
|
|
];
|
|
|
|
|
|
|
|
const args = [{ selector: '[ignored]' },];
|
|
|
|
class NoPropertyAssignment {
|
|
|
|
}
|
|
|
|
NoPropertyAssignment.decorators = [
|
|
|
|
{ type: Directive, args },
|
|
|
|
];
|
|
|
|
|
|
|
|
class NotArrayLiteral {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.decorators = [
|
|
|
|
{ type: Directive, args: () => [{ selector: '[ignored]' },] },
|
|
|
|
];
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_PROP_DECORATORS_FILE = {
|
|
|
|
name: _('/invalid_prop_decorators.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Input} from '@angular/core';
|
|
|
|
class NotObjectLiteral {
|
|
|
|
}
|
|
|
|
NotObjectLiteral.propDecorators = () => ({
|
|
|
|
"prop": [{ type: Input },]
|
|
|
|
});
|
|
|
|
|
|
|
|
class NotObjectLiteralProp {
|
|
|
|
}
|
|
|
|
NotObjectLiteralProp.propDecorators = {
|
|
|
|
"prop": [
|
|
|
|
"This is not an object literal",
|
|
|
|
{ type: Input },
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
class NoTypeProperty {
|
|
|
|
}
|
|
|
|
NoTypeProperty.propDecorators = {
|
|
|
|
"prop": [
|
|
|
|
{ notType: Input },
|
|
|
|
{ type: Input },
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
class NotIdentifier {
|
|
|
|
}
|
|
|
|
NotIdentifier.propDecorators = {
|
|
|
|
"prop": [
|
|
|
|
{ type: 'StringsLiteralsAreNotIdentifiers' },
|
|
|
|
{ type: Input },
|
|
|
|
]
|
|
|
|
};
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_PROP_DECORATOR_ARGS_FILE = {
|
|
|
|
name: _('/invalid_prop_decorator_args.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Input} from '@angular/core';
|
|
|
|
class NoArgsProperty {
|
|
|
|
}
|
|
|
|
NoArgsProperty.propDecorators = {
|
|
|
|
"prop": [{ type: Input },]
|
|
|
|
};
|
|
|
|
|
|
|
|
const args = [{ selector: '[ignored]' },];
|
|
|
|
class NoPropertyAssignment {
|
|
|
|
}
|
|
|
|
NoPropertyAssignment.propDecorators = {
|
|
|
|
"prop": [{ type: Input, args },]
|
|
|
|
};
|
|
|
|
|
|
|
|
class NotArrayLiteral {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.propDecorators = {
|
|
|
|
"prop": [{ type: Input, args: () => [{ selector: '[ignored]' },] },],
|
|
|
|
};
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_CTOR_DECORATORS_FILE = {
|
|
|
|
name: _('/invalid_ctor_decorators.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Inject} from '@angular/core';
|
|
|
|
class NoParameters {
|
|
|
|
constructor() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const NotFromCoreDecorator = {};
|
|
|
|
class NotFromCore {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NotFromCore.ctorParameters = () => [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: NotFromCoreDecorator },] },
|
|
|
|
]
|
|
|
|
|
|
|
|
class NotArrowFunction {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NotArrowFunction.ctorParameters = function() {
|
|
|
|
return { type: 'ParamType', decorators: [{ type: Inject },] };
|
|
|
|
};
|
|
|
|
|
|
|
|
class NotArrayLiteral {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NotArrayLiteral.ctorParameters = () => 'StringsAreNotArrayLiterals';
|
|
|
|
|
|
|
|
class NotObjectLiteral {
|
|
|
|
constructor(arg1, arg2) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NotObjectLiteral.ctorParameters = () => [
|
|
|
|
"This is not an object literal",
|
|
|
|
{ type: 'ParamType', decorators: [{ type: Inject },] },
|
|
|
|
];
|
|
|
|
|
|
|
|
class NoTypeProperty {
|
|
|
|
constructor(arg1, arg2) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NoTypeProperty.ctorParameters = () => [
|
|
|
|
{
|
|
|
|
type: 'ParamType',
|
|
|
|
decorators: [
|
|
|
|
{ notType: Inject },
|
|
|
|
{ type: Inject },
|
|
|
|
]
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
class NotIdentifier {
|
|
|
|
constructor(arg1, arg2) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NotIdentifier.ctorParameters = () => [
|
|
|
|
{
|
|
|
|
type: 'ParamType',
|
|
|
|
decorators: [
|
|
|
|
{ type: 'StringsLiteralsAreNotIdentifiers' },
|
|
|
|
{ type: Inject },
|
|
|
|
]
|
|
|
|
},
|
|
|
|
];
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_CTOR_DECORATOR_ARGS_FILE = {
|
|
|
|
name: _('/invalid_ctor_decorator_args.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Inject} from '@angular/core';
|
|
|
|
class NoArgsProperty {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NoArgsProperty.ctorParameters = () => [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: Inject },] },
|
|
|
|
];
|
|
|
|
|
|
|
|
const args = [{ selector: '[ignored]' },];
|
|
|
|
class NoPropertyAssignment {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NoPropertyAssignment.ctorParameters = () => [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: Inject, args },] },
|
|
|
|
];
|
|
|
|
|
|
|
|
class NotArrayLiteral {
|
|
|
|
constructor(arg1) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NotArrayLiteral.ctorParameters = () => [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: Inject, args: () => [{ selector: '[ignored]' },] },] },
|
|
|
|
];
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
IMPORTS_FILES = [
|
|
|
|
{
|
|
|
|
name: _('/index.js'),
|
|
|
|
contents: `
|
|
|
|
import * as a from './a';
|
|
|
|
import * as b from './b';
|
|
|
|
import * as c from './c';
|
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/a.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
export const a = 'a';
|
2018-07-16 03:51:14 -04:00
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/b.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {a} from './a.js';
|
|
|
|
import {a as foo} from './a.js';
|
|
|
|
|
|
|
|
const b = a;
|
|
|
|
const c = foo;
|
|
|
|
const d = b;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
];
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
EXPORTS_FILES = [
|
|
|
|
{
|
|
|
|
name: _('/index.js'),
|
|
|
|
contents: `
|
|
|
|
import * as a from './a';
|
|
|
|
import * as b from './b';
|
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/a.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
export const a = 'a';
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/b.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
import {Directive} from '@angular/core';
|
|
|
|
import {a} from './a';
|
|
|
|
import {a as foo} from './a';
|
|
|
|
export {Directive} from '@angular/core';
|
|
|
|
export {a} from './a';
|
|
|
|
export const b = a;
|
|
|
|
export const c = foo;
|
|
|
|
export const d = b;
|
|
|
|
export const e = 'e';
|
|
|
|
export const DirectiveX = Directive;
|
|
|
|
export class SomeClass {}
|
2018-07-16 03:51:14 -04:00
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
];
|
2018-07-16 03:51:14 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
FUNCTION_BODY_FILE = {
|
|
|
|
name: _('/function_body.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
function foo(x) {
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
function bar(x, y = 42) {
|
|
|
|
return x + y;
|
|
|
|
}
|
|
|
|
function baz(x) {
|
|
|
|
let y;
|
|
|
|
if (y === void 0) { y = 42; }
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
let y;
|
|
|
|
function qux(x) {
|
|
|
|
if (x === void 0) { y = 42; }
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
function moo() {
|
|
|
|
let x;
|
|
|
|
if (x === void 0) { x = 42; }
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
let x;
|
|
|
|
function juu() {
|
|
|
|
if (x === void 0) { x = 42; }
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
MARKER_FILE = {
|
|
|
|
name: _('/marker.js'),
|
|
|
|
contents: `
|
refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally
compile one TS file or another depending on whether ngc or ngtsc was the
selected compiler. This was done because we wanted to avoid importing
certain modules (and thus pulling them into the build) if Ivy was on or
off. This mechanism had a major drawback: ivy_switch became a bottleneck
in the import graph, as it both imports from many places in the codebase
and is imported by many modules in the codebase. This frequently resulted
in cyclic imports which caused issues both with TS and Closure compilation.
It turns out ngcc needs both code paths in the bundle to perform the switch
during its operation anyway, so import switching was later abandoned. This
means that there's no real reason why the ivy_switch mechanism needed to
operate at the Bazel level, and for the ivy_switch file to be a bottleneck.
This commit removes the Bazel-level ivy_switch mechanism, and introduces
an additional TypeScript transform in ngtsc (and the pass-through tsc
compiler used for testing JIT) to perform the same operation that ngcc
does, and flip the switch during ngtsc compilation. This allows the
ivy_switch file to be removed, and the individual switches to be located
directly next to their consumers in the codebase, greatly mitigating the
circular import issues and making the mechanism much easier to use.
As part of this commit, the tag for marking switched variables was changed
from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which
flips these tags. Most variables were renamed from R3_* to SWITCH_* as well,
since they're referenced mostly in render2 code.
Test strategy: existing test coverage is more than sufficient - if this
didn't work correctly it would break the hello world and todo apps.
PR Close #26550
2018-10-17 18:44:44 -04:00
|
|
|
let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;
|
2018-08-17 02:50:55 -04:00
|
|
|
|
refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally
compile one TS file or another depending on whether ngc or ngtsc was the
selected compiler. This was done because we wanted to avoid importing
certain modules (and thus pulling them into the build) if Ivy was on or
off. This mechanism had a major drawback: ivy_switch became a bottleneck
in the import graph, as it both imports from many places in the codebase
and is imported by many modules in the codebase. This frequently resulted
in cyclic imports which caused issues both with TS and Closure compilation.
It turns out ngcc needs both code paths in the bundle to perform the switch
during its operation anyway, so import switching was later abandoned. This
means that there's no real reason why the ivy_switch mechanism needed to
operate at the Bazel level, and for the ivy_switch file to be a bottleneck.
This commit removes the Bazel-level ivy_switch mechanism, and introduces
an additional TypeScript transform in ngtsc (and the pass-through tsc
compiler used for testing JIT) to perform the same operation that ngcc
does, and flip the switch during ngtsc compilation. This allows the
ivy_switch file to be removed, and the individual switches to be located
directly next to their consumers in the codebase, greatly mitigating the
circular import issues and making the mechanism much easier to use.
As part of this commit, the tag for marking switched variables was changed
from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which
flips these tags. Most variables were renamed from R3_* to SWITCH_* as well,
since they're referenced mostly in render2 code.
Test strategy: existing test coverage is more than sufficient - if this
didn't work correctly it would break the hello world and todo apps.
PR Close #26550
2018-10-17 18:44:44 -04:00
|
|
|
function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {
|
2018-08-17 02:50:55 -04:00
|
|
|
const compilerFactory = injector.get(CompilerFactory);
|
|
|
|
const compiler = compilerFactory.createCompiler([options]);
|
|
|
|
return compiler.compileModuleAsync(moduleType);
|
|
|
|
}
|
|
|
|
|
refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally
compile one TS file or another depending on whether ngc or ngtsc was the
selected compiler. This was done because we wanted to avoid importing
certain modules (and thus pulling them into the build) if Ivy was on or
off. This mechanism had a major drawback: ivy_switch became a bottleneck
in the import graph, as it both imports from many places in the codebase
and is imported by many modules in the codebase. This frequently resulted
in cyclic imports which caused issues both with TS and Closure compilation.
It turns out ngcc needs both code paths in the bundle to perform the switch
during its operation anyway, so import switching was later abandoned. This
means that there's no real reason why the ivy_switch mechanism needed to
operate at the Bazel level, and for the ivy_switch file to be a bottleneck.
This commit removes the Bazel-level ivy_switch mechanism, and introduces
an additional TypeScript transform in ngtsc (and the pass-through tsc
compiler used for testing JIT) to perform the same operation that ngcc
does, and flip the switch during ngtsc compilation. This allows the
ivy_switch file to be removed, and the individual switches to be located
directly next to their consumers in the codebase, greatly mitigating the
circular import issues and making the mechanism much easier to use.
As part of this commit, the tag for marking switched variables was changed
from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which
flips these tags. Most variables were renamed from R3_* to SWITCH_* as well,
since they're referenced mostly in render2 code.
Test strategy: existing test coverage is more than sufficient - if this
didn't work correctly it would break the hello world and todo apps.
PR Close #26550
2018-10-17 18:44:44 -04:00
|
|
|
function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
|
2018-08-17 02:50:55 -04:00
|
|
|
ngDevMode && assertNgModuleType(moduleType);
|
|
|
|
return Promise.resolve(new R3NgModuleFactory(moduleType));
|
|
|
|
}
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2018-08-17 02:50:55 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
DECORATED_FILES = [
|
|
|
|
{
|
|
|
|
name: _('/primary.js'),
|
|
|
|
contents: `
|
2018-09-26 12:24:43 -04:00
|
|
|
import {Directive} from '@angular/core';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {D} from './secondary';
|
2018-09-26 12:24:43 -04:00
|
|
|
class A {}
|
|
|
|
A.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[a]' }] }
|
|
|
|
];
|
|
|
|
function x() {}
|
|
|
|
function y() {}
|
|
|
|
class B {}
|
|
|
|
B.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[b]' }] }
|
|
|
|
];
|
|
|
|
class C {}
|
|
|
|
export { A, x, C };
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/secondary.js'),
|
|
|
|
contents: `
|
2018-09-26 12:24:43 -04:00
|
|
|
import {Directive} from '@angular/core';
|
|
|
|
class D {}
|
|
|
|
D.decorators = [
|
|
|
|
{ type: Directive, args: [{ selector: '[d]' }] }
|
|
|
|
];
|
2018-10-16 03:56:54 -04:00
|
|
|
export {D};
|
2018-09-26 12:24:43 -04:00
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
}
|
|
|
|
];
|
2018-09-26 12:24:43 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
ARITY_CLASSES = [
|
|
|
|
{
|
|
|
|
name: _('/src/class.js'),
|
|
|
|
contents: `
|
2018-10-10 09:17:32 -04:00
|
|
|
export class NoTypeParam {}
|
|
|
|
export class OneTypeParam {}
|
|
|
|
export class TwoTypeParams {}
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/typings/class.d.ts'),
|
|
|
|
contents: `
|
2018-10-16 03:56:54 -04:00
|
|
|
export declare class NoTypeParam {}
|
|
|
|
export declare class OneTypeParam<T> {}
|
|
|
|
export declare class TwoTypeParams<T, K> {}
|
2018-10-10 09:17:32 -04:00
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
TYPINGS_SRC_FILES = [
|
|
|
|
{
|
2019-08-13 20:02:44 -04:00
|
|
|
name: _('/ep/src/index.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-08-13 20:02:44 -04:00
|
|
|
import 'an_external_lib';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {InternalClass} from './internal';
|
|
|
|
import * as func1 from './func1';
|
|
|
|
import * as missing from './missing-class';
|
|
|
|
import * as flatFile from './flat-file';
|
|
|
|
export * from './class1';
|
|
|
|
export * from './class2';
|
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
2019-08-13 20:02:44 -04:00
|
|
|
name: _('/ep/src/class1.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: 'export class Class1 {}\nexport class MissingClass1 {}'
|
|
|
|
},
|
2019-08-13 20:02:44 -04:00
|
|
|
{name: _('/ep/src/class2.js'), contents: 'export class Class2 {}'},
|
|
|
|
{name: _('/ep/src/func1.js'), contents: 'export function mooFn() {}'},
|
|
|
|
{
|
|
|
|
name: _('/ep/src/internal.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: 'export class InternalClass {}\nexport class Class2 {}'
|
|
|
|
},
|
2019-08-13 20:02:44 -04:00
|
|
|
{name: _('/ep/src/missing-class.js'), contents: 'export class MissingClass2 {}'},
|
|
|
|
{
|
|
|
|
name: _('/ep/src/flat-file.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents:
|
|
|
|
'export class Class1 {}\nexport class MissingClass1 {}\nexport class MissingClass2 {}\class Class3 {}\nexport {Class3 as xClass3};',
|
2019-08-13 20:02:44 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/ep/src/shadow-class.js'),
|
|
|
|
contents: 'export class ShadowClass {}',
|
|
|
|
},
|
2019-06-06 15:22:32 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
TYPINGS_DTS_FILES = [
|
|
|
|
{
|
2019-08-13 20:02:44 -04:00
|
|
|
name: _('/ep/typings/index.d.ts'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-08-13 20:02:44 -04:00
|
|
|
import '../../an_external_lib/index';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {InternalClass} from './internal';
|
|
|
|
import {mooFn} from './func1';
|
|
|
|
export * from './class1';
|
|
|
|
export * from './class2';
|
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
2019-08-13 20:02:44 -04:00
|
|
|
name: _('/ep/typings/class1.d.ts'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `export declare class Class1 {}\nexport declare class OtherClass {}`
|
|
|
|
},
|
|
|
|
{
|
2019-08-13 20:02:44 -04:00
|
|
|
name: _('/ep/typings/class2.d.ts'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents:
|
|
|
|
`export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';`
|
|
|
|
},
|
2019-08-13 20:02:44 -04:00
|
|
|
{name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'},
|
2019-06-06 15:22:32 -04:00
|
|
|
{
|
2019-08-13 20:02:44 -04:00
|
|
|
name: _('/ep/typings/internal.d.ts'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `export declare class InternalClass {}\nexport declare class Class2 {}`
|
|
|
|
},
|
2019-08-13 20:02:44 -04:00
|
|
|
{name: _('/ep/typings/class3.d.ts'), contents: `export declare class Class3 {}`},
|
|
|
|
{name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`},
|
|
|
|
{name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'},
|
2019-06-06 15:22:32 -04:00
|
|
|
];
|
|
|
|
|
2019-08-13 20:02:44 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
MODULE_WITH_PROVIDERS_PROGRAM = [
|
|
|
|
{
|
|
|
|
name: _('/src/index.js'),
|
|
|
|
contents: `
|
|
|
|
import * as functions from './functions';
|
|
|
|
import * as methods from './methods';
|
|
|
|
import * as aliased_class from './aliased_class';
|
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/src/functions.js'),
|
|
|
|
contents: `
|
2018-12-07 08:10:52 -05:00
|
|
|
import {ExternalModule} from './module';
|
2019-04-28 15:48:34 -04:00
|
|
|
import * as mod from './module';
|
2018-12-07 08:10:52 -05:00
|
|
|
export class SomeService {}
|
|
|
|
export class InternalModule {}
|
|
|
|
export function aNumber() { return 42; }
|
|
|
|
export function aString() { return 'foo'; }
|
|
|
|
export function emptyObject() { return {}; }
|
|
|
|
export function ngModuleIdentifier() { return { ngModule: InternalModule }; }
|
|
|
|
export function ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
|
|
|
export function ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
|
|
|
export function onlyProviders() { return { providers: [SomeService] }; }
|
|
|
|
export function ngModuleNumber() { return { ngModule: 42 }; }
|
|
|
|
export function ngModuleString() { return { ngModule: 'foo' }; }
|
|
|
|
export function ngModuleObject() { return { ngModule: { foo: 42 } }; }
|
|
|
|
export function externalNgModule() { return { ngModule: ExternalModule }; }
|
2019-04-28 15:48:34 -04:00
|
|
|
export function namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
|
2018-12-07 08:10:52 -05:00
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/src/methods.js'),
|
|
|
|
contents: `
|
2018-12-07 08:10:52 -05:00
|
|
|
import {ExternalModule} from './module';
|
2019-04-28 15:48:34 -04:00
|
|
|
import * as mod from './module';
|
2018-12-07 08:10:52 -05:00
|
|
|
export class SomeService {}
|
|
|
|
export class InternalModule {
|
|
|
|
static aNumber() { return 42; }
|
|
|
|
static aString() { return 'foo'; }
|
|
|
|
static emptyObject() { return {}; }
|
|
|
|
static ngModuleIdentifier() { return { ngModule: InternalModule }; }
|
|
|
|
static ngModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
|
|
|
static ngModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
|
|
|
static onlyProviders() { return { providers: [SomeService] }; }
|
|
|
|
static ngModuleNumber() { return { ngModule: 42 }; }
|
|
|
|
static ngModuleString() { return { ngModule: 'foo' }; }
|
|
|
|
static ngModuleObject() { return { ngModule: { foo: 42 } }; }
|
|
|
|
static externalNgModule() { return { ngModule: ExternalModule }; }
|
2019-04-28 15:48:34 -04:00
|
|
|
static namespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
|
2018-12-07 08:10:52 -05:00
|
|
|
|
|
|
|
instanceNgModuleIdentifier() { return { ngModule: InternalModule }; }
|
|
|
|
instanceNgModuleWithEmptyProviders() { return { ngModule: InternalModule, providers: [] }; }
|
|
|
|
instanceNgModuleWithProviders() { return { ngModule: InternalModule, providers: [SomeService] }; }
|
|
|
|
instanceExternalNgModule() { return { ngModule: ExternalModule }; }
|
2019-04-28 15:48:34 -04:00
|
|
|
instanceNamespacedExternalNgModule() { return { ngModule: mod.ExternalModule }; }
|
2018-12-07 08:10:52 -05:00
|
|
|
}
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/src/aliased_class.js'),
|
|
|
|
contents: `
|
2019-03-05 17:29:28 -05:00
|
|
|
var AliasedModule_1;
|
|
|
|
let AliasedModule = AliasedModule_1 = class AliasedModule {
|
|
|
|
static forRoot() { return { ngModule: AliasedModule_1 }; }
|
|
|
|
};
|
|
|
|
export { AliasedModule };
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{name: _('/src/module.js'), contents: 'export class ExternalModule {}'},
|
|
|
|
];
|
2018-12-07 08:10:52 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
NAMESPACED_IMPORT_FILE = {
|
|
|
|
name: _('/some_directive.js'),
|
|
|
|
contents: `
|
2019-04-28 15:48:34 -04:00
|
|
|
import * as core from '@angular/core';
|
|
|
|
|
|
|
|
class SomeDirective {
|
|
|
|
}
|
|
|
|
SomeDirective.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[someDirective]' },] }
|
|
|
|
];
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-10-16 12:15:01 -04:00
|
|
|
|
|
|
|
INDEX_SIGNATURE_PROP_FILE = {
|
|
|
|
name: _('/index_signature_prop.d.ts'),
|
|
|
|
contents: `
|
|
|
|
abstract class IndexSignatureClass {
|
|
|
|
[key: string]: any;
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
};
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getDecoratorsOfDeclaration()', () => {
|
|
|
|
it('should find the decorators on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators).toBeDefined();
|
|
|
|
expect(decorators.length).toEqual(1);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const decorator = decorators[0];
|
|
|
|
expect(decorator.name).toEqual('Directive');
|
|
|
|
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
|
|
|
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
|
|
|
'{ selector: \'[someDirective]\' }',
|
|
|
|
]);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-09-11 16:08:53 -04:00
|
|
|
it('should find the decorators on an aliased class', () => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'AliasedClass', isNamedVariableDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
|
|
|
expect(decorators).toBeDefined();
|
|
|
|
expect(decorators.length).toEqual(1);
|
|
|
|
|
|
|
|
const decorator = decorators[0];
|
|
|
|
expect(decorator.name).toEqual('Directive');
|
|
|
|
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
|
|
|
|
expect(decorator.args !.map(arg => arg.getText())).toEqual([
|
|
|
|
'{ selector: \'[someDirective]\' }',
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return null if the symbol is not a class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const functionNode =
|
|
|
|
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(functionNode);
|
|
|
|
expect(decorators).toBe(null);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return null if there are no decorators', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode);
|
|
|
|
expect(decorators).toBe(null);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should ignore `decorators` if it is not an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode);
|
|
|
|
expect(decorators).toEqual([]);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should ignore decorator elements that are not object literals', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should ignore decorator elements that have no `type` property', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', isNamedClassDeclaration);
|
2018-10-10 09:17:32 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should ignore decorator elements whose `type` value is not an identifier', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', isNamedClassDeclaration);
|
2018-10-10 09:17:32 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.
Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on their
name and any remaining decorators are simply ignored.
This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.
PR Close #31614
2019-07-19 17:17:22 -04:00
|
|
|
it('should have import information on decorators', () => {
|
2019-06-06 15:22:32 -04:00
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
2018-10-10 09:17:32 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators.length).toEqual(1);
|
refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.
Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on their
name and any remaining decorators are simply ignored.
This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.
PR Close #31614
2019-07-19 17:17:22 -04:00
|
|
|
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('(returned decorators `args`)', () => {
|
|
|
|
it('should be an empty array if decorator has no `args` property', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Directive');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be an empty array if decorator\'s `args` has no property assignment', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Directive');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be an empty array if `args` property value is not an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Directive');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getMembersOfClass()', () => {
|
|
|
|
it('should find decorated properties on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const input1 = members.find(member => member.name === 'input1') !;
|
|
|
|
expect(input1.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(input1.isStatic).toEqual(false);
|
|
|
|
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
|
refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.
Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on their
name and any remaining decorators are simply ignored.
This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.
PR Close #31614
2019-07-19 17:17:22 -04:00
|
|
|
expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
|
2019-01-27 11:21:29 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const input2 = members.find(member => member.name === 'input2') !;
|
|
|
|
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(input2.isStatic).toEqual(false);
|
refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.
Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on their
name and any remaining decorators are simply ignored.
This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.
PR Close #31614
2019-07-19 17:17:22 -04:00
|
|
|
expect(input2.decorators !.map(d => d.name)).toEqual(['Input']);
|
|
|
|
expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find non decorated properties on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const instanceProperty = members.find(member => member.name === 'instanceProperty') !;
|
|
|
|
expect(instanceProperty.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(instanceProperty.isStatic).toEqual(false);
|
|
|
|
expect(ts.isBinaryExpression(instanceProperty.implementation !)).toEqual(true);
|
|
|
|
expect(instanceProperty.value !.getText()).toEqual(`'instance'`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle equally named getter/setter pairs correctly', () => {
|
|
|
|
loadTestFiles([ACCESSORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(ACCESSORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, ACCESSORS_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const [combinedSetter, combinedGetter] =
|
|
|
|
members.filter(member => member.name === 'setterAndGetter');
|
|
|
|
expect(combinedSetter.kind).toEqual(ClassMemberKind.Setter);
|
|
|
|
expect(combinedSetter.isStatic).toEqual(false);
|
|
|
|
expect(ts.isSetAccessor(combinedSetter.implementation !)).toEqual(true);
|
|
|
|
expect(combinedSetter.value).toBeNull();
|
|
|
|
expect(combinedSetter.decorators !.map(d => d.name)).toEqual(['Input']);
|
|
|
|
expect(combinedGetter.kind).toEqual(ClassMemberKind.Getter);
|
|
|
|
expect(combinedGetter.isStatic).toEqual(false);
|
|
|
|
expect(ts.isGetAccessor(combinedGetter.implementation !)).toEqual(true);
|
|
|
|
expect(combinedGetter.value).toBeNull();
|
|
|
|
expect(combinedGetter.decorators !.map(d => d.name)).toEqual([]);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find static methods on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const staticMethod = members.find(member => member.name === 'staticMethod') !;
|
|
|
|
expect(staticMethod.kind).toEqual(ClassMemberKind.Method);
|
|
|
|
expect(staticMethod.isStatic).toEqual(true);
|
|
|
|
expect(ts.isMethodDeclaration(staticMethod.implementation !)).toEqual(true);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find static properties on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const staticProperty = members.find(member => member.name === 'staticProperty') !;
|
|
|
|
expect(staticProperty.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(staticProperty.isStatic).toEqual(true);
|
|
|
|
expect(ts.isPropertyAccessExpression(staticProperty.implementation !)).toEqual(true);
|
|
|
|
expect(staticProperty.value !.getText()).toEqual(`'static'`);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-10-16 12:15:01 -04:00
|
|
|
it('should ignore index signature properties', () => {
|
|
|
|
loadTestFiles([INDEX_SIGNATURE_PROP_FILE]);
|
|
|
|
const logger = new MockLogger();
|
|
|
|
const {program} = makeTestBundleProgram(INDEX_SIGNATURE_PROP_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(logger, false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INDEX_SIGNATURE_PROP_FILE.name, 'IndexSignatureClass',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
|
|
|
expect(members).toEqual([]);
|
|
|
|
expect(logger.logs.warn).toEqual([]);
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should throw if the symbol is not a class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const functionNode =
|
|
|
|
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
|
|
|
expect(() => {
|
|
|
|
host.getMembersOfClass(functionNode);
|
|
|
|
}).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return an empty array if there are no prop decorators', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
|
|
|
expect(members).toEqual([]);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should not process decorated properties in `propDecorators` if it is not an object literal',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
|
|
|
expect(members.map(member => member.name)).not.toContain('prop');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore prop decorator elements that are not object literals', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp',
|
2019-03-20 06:10:58 -04:00
|
|
|
isNamedClassDeclaration);
|
2018-10-10 09:17:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
const prop = members.find(m => m.name === 'prop') !;
|
|
|
|
const decorators = prop.decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should ignore prop decorator elements that have no `type` property', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', isNamedClassDeclaration);
|
2018-10-10 09:17:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
const prop = members.find(m => m.name === 'prop') !;
|
|
|
|
const decorators = prop.decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should ignore prop decorator elements whose `type` value is not an identifier', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', isNamedClassDeclaration);
|
2018-10-10 09:17:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
const prop = members.find(m => m.name === 'prop') !;
|
|
|
|
const decorators = prop.decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('(returned prop decorators `args`)', () => {
|
|
|
|
it('should be an empty array if prop decorator has no `args` property', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
const prop = members.find(m => m.name === 'prop') !;
|
|
|
|
const decorators = prop.decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Input');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be an empty array if prop decorator\'s `args` has no property assignment',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
const prop = members.find(m => m.name === 'prop') !;
|
|
|
|
const decorators = prop.decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Input');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be an empty array if `args` property value is not an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
const prop = members.find(m => m.name === 'prop') !;
|
|
|
|
const decorators = prop.decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Input');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getConstructorParameters()', () => {
|
|
|
|
it('should find the decorated constructor parameters', () => {
|
2019-11-18 14:53:25 -05:00
|
|
|
loadFakeCore(getFileSystem());
|
2019-06-06 15:22:32 -04:00
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters).toBeDefined();
|
|
|
|
expect(parameters.map(parameter => parameter.name)).toEqual([
|
|
|
|
'_viewContainer', '_template', 'injected'
|
|
|
|
]);
|
|
|
|
expectTypeValueReferencesForParameters(
|
2019-11-18 14:53:25 -05:00
|
|
|
parameters, ['ViewContainerRef', 'TemplateRef', null], '@angular/core');
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-05-23 17:40:17 -04:00
|
|
|
it('should accept `ctorParameters` as an array', () => {
|
|
|
|
loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
|
|
|
|
|
|
|
expect(parameters).toBeDefined();
|
|
|
|
expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']);
|
|
|
|
expectTypeValueReferencesForParameters(parameters, ['ParamType']);
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should throw if the symbol is not a class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const functionNode =
|
|
|
|
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
|
|
|
expect(() => { host.getConstructorParameters(functionNode); })
|
|
|
|
.toThrowError(
|
|
|
|
'Attempted to get constructor parameters of a non-class: "function foo() {}"');
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return `null` if there is no constructor', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
expect(parameters).toBe(null);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return an array even if there are no decorators', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters).toEqual(jasmine.any(Array));
|
|
|
|
expect(parameters.length).toEqual(1);
|
|
|
|
expect(parameters[0].name).toEqual('foo');
|
|
|
|
expect(parameters[0].decorators).toBe(null);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return an empty array if there are no constructor parameters', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
|
|
|
expect(parameters).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore decorators that are not imported from core', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NotFromCore', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
|
|
|
|
|
|
|
expect(parameters.length).toBe(1);
|
|
|
|
expect(parameters[0]).toEqual(jasmine.objectContaining<CtorParameter>({
|
|
|
|
name: 'arg1',
|
|
|
|
decorators: [],
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore `ctorParameters` if it is not an arrow function', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrowFunction',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
|
|
|
|
|
|
|
expect(parameters.length).toBe(1);
|
|
|
|
expect(parameters[0]).toEqual(jasmine.objectContaining<CtorParameter>({
|
|
|
|
name: 'arg1',
|
|
|
|
decorators: null,
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore `ctorParameters` if it does not return an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
|
|
|
|
|
|
|
expect(parameters.length).toBe(1);
|
|
|
|
expect(parameters[0]).toEqual(jasmine.objectContaining<CtorParameter>({
|
|
|
|
name: 'arg1',
|
|
|
|
decorators: null,
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('synthesized constructors', () => {
|
|
|
|
function getConstructorParameters(constructor: string) {
|
|
|
|
const file = {
|
|
|
|
name: _('/synthesized_constructors.js'),
|
|
|
|
contents: `
|
2019-01-02 17:25:58 -05:00
|
|
|
class BaseClass {}
|
|
|
|
class TestClass extends BaseClass {
|
|
|
|
${constructor}
|
|
|
|
}
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, file.name, 'TestClass', isNamedClassDeclaration);
|
|
|
|
return host.getConstructorParameters(classNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
it('recognizes super call as first statement', () => {
|
|
|
|
const parameters = getConstructorParameters(`
|
2019-01-02 17:25:58 -05:00
|
|
|
constructor() {
|
|
|
|
super(...arguments);
|
|
|
|
this.synthesizedProperty = null;
|
|
|
|
}`);
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters).toBeNull();
|
|
|
|
});
|
2019-01-02 17:25:58 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('does not consider super call without spread element as synthesized', () => {
|
|
|
|
const parameters = getConstructorParameters(`
|
2019-01-02 17:25:58 -05:00
|
|
|
constructor() {
|
|
|
|
super(arguments);
|
|
|
|
}`);
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters !.length).toBe(0);
|
|
|
|
});
|
2019-01-02 17:25:58 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('does not consider constructors with parameters as synthesized', () => {
|
|
|
|
const parameters = getConstructorParameters(`
|
2019-01-02 17:25:58 -05:00
|
|
|
constructor(arg) {
|
|
|
|
super(...arguments);
|
|
|
|
}`);
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters !.length).toBe(1);
|
|
|
|
});
|
2019-01-02 17:25:58 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('does not consider manual super calls as synthesized', () => {
|
|
|
|
const parameters = getConstructorParameters(`
|
2019-01-02 17:25:58 -05:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
}`);
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters !.length).toBe(0);
|
|
|
|
});
|
2019-01-02 17:25:58 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('does not consider empty constructors as synthesized', () => {
|
|
|
|
const parameters = getConstructorParameters(`
|
2019-01-02 17:25:58 -05:00
|
|
|
constructor() {
|
|
|
|
}`);
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(parameters !.length).toBe(0);
|
|
|
|
});
|
2019-01-02 17:25:58 -05:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('(returned parameters `decorators`)', () => {
|
|
|
|
it('should ignore param decorator elements that are not object literals', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
|
|
|
expect(parameters !.length).toBe(2);
|
|
|
|
expect(parameters ![0]).toEqual(jasmine.objectContaining<CtorParameter>({
|
|
|
|
name: 'arg1',
|
|
|
|
decorators: null,
|
|
|
|
}));
|
|
|
|
expect(parameters ![1]).toEqual(jasmine.objectContaining<CtorParameter>({
|
|
|
|
name: 'arg2',
|
|
|
|
decorators: jasmine.any(Array) as any
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore param decorator elements that have no `type` property', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
const decorators = parameters ![0].decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore param decorator elements whose `type` value is not an identifier', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
const decorators = parameters ![0].decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
|
|
|
|
});
|
|
|
|
|
refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.
Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on their
name and any remaining decorators are simply ignored.
This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.
PR Close #31614
2019-07-19 17:17:22 -04:00
|
|
|
it('should have import information on decorators', () => {
|
2019-06-06 15:22:32 -04:00
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode) !;
|
|
|
|
const decorators = parameters[2].decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toEqual(1);
|
refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.
Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on their
name and any remaining decorators are simply ignored.
This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.
PR Close #31614
2019-07-19 17:17:22 -04:00
|
|
|
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('(returned parameters `decorators.args`)', () => {
|
|
|
|
it('should be an empty array if param decorator has no `args` property', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
expect(parameters !.length).toBe(1);
|
|
|
|
const decorators = parameters ![0].decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Inject');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be an empty array if param decorator\'s `args` has no property assignment',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
const decorators = parameters ![0].decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Inject');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be an empty array if `args` property value is not an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(
|
|
|
|
program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral',
|
|
|
|
isNamedClassDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
const decorators = parameters ![0].decorators !;
|
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Inject');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('getDefinitionOfFunction()', () => {
|
|
|
|
it('should return an object describing the function declaration passed as an argument',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([FUNCTION_BODY_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(FUNCTION_BODY_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
|
|
|
|
const fooNode = getDeclaration(
|
|
|
|
program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
|
|
|
|
const fooDef = host.getDefinitionOfFunction(fooNode) !;
|
|
|
|
expect(fooDef.node).toBe(fooNode);
|
|
|
|
expect(fooDef.body !.length).toEqual(1);
|
|
|
|
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
|
|
|
|
expect(fooDef.parameters.length).toEqual(1);
|
|
|
|
expect(fooDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(fooDef.parameters[0].initializer).toBe(null);
|
|
|
|
|
|
|
|
const barNode = getDeclaration(
|
|
|
|
program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
|
|
|
|
const barDef = host.getDefinitionOfFunction(barNode) !;
|
|
|
|
expect(barDef.node).toBe(barNode);
|
|
|
|
expect(barDef.body !.length).toEqual(1);
|
|
|
|
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
|
|
|
|
expect(barDef.body ![0].getText()).toEqual(`return x + y;`);
|
|
|
|
expect(barDef.parameters.length).toEqual(2);
|
|
|
|
expect(barDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(fooDef.parameters[0].initializer).toBe(null);
|
|
|
|
expect(barDef.parameters[1].name).toEqual('y');
|
|
|
|
expect(barDef.parameters[1].initializer !.getText()).toEqual('42');
|
|
|
|
|
|
|
|
const bazNode = getDeclaration(
|
|
|
|
program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
|
|
|
|
const bazDef = host.getDefinitionOfFunction(bazNode) !;
|
|
|
|
expect(bazDef.node).toBe(bazNode);
|
|
|
|
expect(bazDef.body !.length).toEqual(3);
|
|
|
|
expect(bazDef.parameters.length).toEqual(1);
|
|
|
|
expect(bazDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(bazDef.parameters[0].initializer).toBe(null);
|
|
|
|
|
|
|
|
const quxNode = getDeclaration(
|
|
|
|
program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
|
|
|
|
const quxDef = host.getDefinitionOfFunction(quxNode) !;
|
|
|
|
expect(quxDef.node).toBe(quxNode);
|
|
|
|
expect(quxDef.body !.length).toEqual(2);
|
|
|
|
expect(quxDef.parameters.length).toEqual(1);
|
|
|
|
expect(quxDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(quxDef.parameters[0].initializer).toBe(null);
|
|
|
|
|
|
|
|
const mooNode = getDeclaration(
|
|
|
|
program, FUNCTION_BODY_FILE.name, 'moo', isNamedFunctionDeclaration) !;
|
|
|
|
const mooDef = host.getDefinitionOfFunction(mooNode) !;
|
|
|
|
expect(mooDef.node).toBe(mooNode);
|
|
|
|
expect(mooDef.body !.length).toEqual(3);
|
|
|
|
expect(mooDef.parameters).toEqual([]);
|
|
|
|
|
|
|
|
const juuNode = getDeclaration(
|
|
|
|
program, FUNCTION_BODY_FILE.name, 'juu', isNamedFunctionDeclaration) !;
|
|
|
|
const juuDef = host.getDefinitionOfFunction(juuNode) !;
|
|
|
|
expect(juuDef.node).toBe(juuNode);
|
|
|
|
expect(juuDef.body !.length).toEqual(2);
|
|
|
|
expect(juuDef.parameters).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getImportOfIdentifier()', () => {
|
|
|
|
it('should find the import of an identifier', () => {
|
|
|
|
loadTestFiles(IMPORTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(_('/index.js'));
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2019-06-06 15:22:32 -04:00
|
|
|
const variableNode = getDeclaration(program, _('/b.js'), 'b', isNamedVariableDeclaration);
|
|
|
|
const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(importOfIdent).toEqual({name: 'a', from: './a.js'});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find the name by which the identifier was exported, not imported', () => {
|
|
|
|
loadTestFiles(IMPORTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(_('/index.js'));
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2019-06-06 15:22:32 -04:00
|
|
|
const variableNode = getDeclaration(program, _('/b.js'), 'c', isNamedVariableDeclaration);
|
|
|
|
const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(importOfIdent).toEqual({name: 'a', from: './a.js'});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return null if the identifier was not imported', () => {
|
|
|
|
loadTestFiles(IMPORTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(_('/index.js'));
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2019-06-06 15:22:32 -04:00
|
|
|
const variableNode = getDeclaration(program, _('/b.js'), 'd', isNamedVariableDeclaration);
|
|
|
|
const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier);
|
2018-10-10 09:17:32 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(importOfIdent).toBeNull();
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getDeclarationOfIdentifier()', () => {
|
|
|
|
it('should return the declaration of a locally defined identifier', () => {
|
2019-11-18 14:53:25 -05:00
|
|
|
loadFakeCore(getFileSystem());
|
2019-06-06 15:22:32 -04:00
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
2019-11-18 14:53:25 -05:00
|
|
|
const actualDeclaration = host.getDeclarationOfIdentifier(classNode.name);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(actualDeclaration).not.toBe(null);
|
2019-11-18 14:53:25 -05:00
|
|
|
expect(actualDeclaration !.node).toBe(classNode);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(actualDeclaration !.viaModule).toBe(null);
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return the declaration of an externally defined identifier', () => {
|
|
|
|
loadFakeCore(getFileSystem());
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
|
|
|
|
const classDecorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
const identifierOfDirective = ((classDecorators[0].node as ts.ObjectLiteralExpression)
|
|
|
|
.properties[0] as ts.PropertyAssignment)
|
|
|
|
.initializer as ts.Identifier;
|
|
|
|
|
|
|
|
const expectedDeclarationNode = getDeclaration(
|
|
|
|
program, _('/node_modules/@angular/core/index.d.ts'), 'Directive',
|
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective);
|
|
|
|
expect(actualDeclaration).not.toBe(null);
|
|
|
|
expect(actualDeclaration !.node).toBe(expectedDeclarationNode);
|
|
|
|
expect(actualDeclaration !.viaModule).toBe('@angular/core');
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return the source-file of an import namespace', () => {
|
|
|
|
loadFakeCore(getFileSystem());
|
|
|
|
loadTestFiles([NAMESPACED_IMPORT_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(NAMESPACED_IMPORT_FILE.name);
|
2019-03-29 06:13:14 -04:00
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
2018-10-10 09:17:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-06-06 15:22:32 -04:00
|
|
|
program, NAMESPACED_IMPORT_FILE.name, 'SomeDirective', ts.isClassDeclaration);
|
|
|
|
const classDecorators = host.getDecoratorsOfDeclaration(classNode) !;
|
|
|
|
const identifier = (((classDecorators[0].node as ts.ObjectLiteralExpression)
|
|
|
|
.properties[0] as ts.PropertyAssignment)
|
|
|
|
.initializer as ts.PropertyAccessExpression)
|
|
|
|
.expression as ts.Identifier;
|
|
|
|
|
|
|
|
const expectedDeclarationNode =
|
|
|
|
getSourceFileOrError(program, _('/node_modules/@angular/core/index.d.ts'));
|
|
|
|
const actualDeclaration = host.getDeclarationOfIdentifier(identifier);
|
|
|
|
expect(actualDeclaration).not.toBe(null);
|
|
|
|
expect(actualDeclaration !.node).toBe(expectedDeclarationNode);
|
2019-10-30 15:02:30 -04:00
|
|
|
expect(actualDeclaration !.viaModule).toBe('@angular/core');
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
2019-04-28 15:48:34 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return the original declaration of an aliased class', () => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classDeclaration = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'AliasedClass', ts.isVariableDeclaration);
|
|
|
|
const usageOfAliasedClass = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'usageOfAliasedClass', ts.isVariableDeclaration);
|
|
|
|
const aliasedClassIdentifier = usageOfAliasedClass.initializer as ts.Identifier;
|
|
|
|
expect(aliasedClassIdentifier.text).toBe('AliasedClass_1');
|
|
|
|
expect(host.getDeclarationOfIdentifier(aliasedClassIdentifier) !.node)
|
|
|
|
.toBe(classDeclaration);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getExportsOfModule()', () => {
|
|
|
|
it('should return a map of all the exports from a given module', () => {
|
|
|
|
loadFakeCore(getFileSystem());
|
|
|
|
loadTestFiles(EXPORTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(_('/index.js'));
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const file = getSourceFileOrError(program, _('/b.js'));
|
|
|
|
const exportDeclarations = host.getExportsOfModule(file);
|
|
|
|
expect(exportDeclarations).not.toBe(null);
|
|
|
|
expect(Array.from(exportDeclarations !.keys())).toEqual([
|
|
|
|
'Directive',
|
|
|
|
'a',
|
|
|
|
'b',
|
|
|
|
'c',
|
|
|
|
'd',
|
|
|
|
'e',
|
|
|
|
'DirectiveX',
|
|
|
|
'SomeClass',
|
|
|
|
]);
|
|
|
|
|
fix(ivy): in ngcc, handle inline exports in commonjs code (#32129)
One of the compiler's tasks is to enumerate the exports of a given ES
module. This can happen for example to resolve `foo.bar` where `foo` is a
namespace import:
```typescript
import * as foo from './foo';
@NgModule({
directives: [foo.DIRECTIVES],
})
```
In this case, the compiler must enumerate the exports of `foo.ts` in order
to evaluate the expression `foo.DIRECTIVES`.
When this operation occurs under ngcc, it must deal with the different
module formats and types of exports that occur. In commonjs code, a problem
arises when certain exports are downleveled.
```typescript
export const DIRECTIVES = [
FooDir,
BarDir,
];
```
can be downleveled to:
```javascript
exports.DIRECTIVES = [
FooDir,
BarDir,
```
Previously, ngtsc and ngcc expected that any export would have an associated
`ts.Declaration` node. `export class`, `export function`, etc. all retain
`ts.Declaration`s even when downleveled. But the `export const` construct
above does not. Therefore, ngcc would not detect `DIRECTIVES` as an export
of `foo.ts`, and the evaluation of `foo.DIRECTIVES` would therefore fail.
To solve this problem, the core concept of an exported `Declaration`
according to the `ReflectionHost` API is split into a `ConcreteDeclaration`
which has a `ts.Declaration`, and an `InlineDeclaration` which instead has
a `ts.Expression`. Differentiating between these allows ngcc to return an
`InlineDeclaration` for `DIRECTIVES` and correctly keep track of this
export.
PR Close #32129
2019-08-13 19:08:53 -04:00
|
|
|
const values =
|
|
|
|
Array.from(exportDeclarations !.values())
|
|
|
|
.map(declaration => [declaration.node !.getText(), declaration.viaModule]);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(values).toEqual([
|
|
|
|
[`Directive: FnWithArg<(clazz: any) => any>`, null],
|
|
|
|
[`a = 'a'`, null],
|
|
|
|
[`b = a`, null],
|
|
|
|
[`c = foo`, null],
|
|
|
|
[`d = b`, null],
|
|
|
|
[`e = 'e'`, null],
|
|
|
|
[`DirectiveX = Directive`, null],
|
|
|
|
['export class SomeClass {}', null],
|
|
|
|
]);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
fix(ngcc): consistently use outer declaration for classes (#32539)
In ngcc's reflection hosts for compiled JS bundles, such as ESM2015,
special care needs to be taken for classes as there may be an outer
declaration (referred to as "declaration") and an inner declaration
(referred to as "implementation") for a given class. Therefore, there
will also be two `ts.Symbol`s bound per class, and ngcc needs to switch
between those declarations and symbols depending on where certain
information can be found.
Prior to this commit, the `NgccReflectionHost` interface had methods
`getClassSymbol` and `findClassSymbols` that would return a `ts.Symbol`.
These class symbols would be used to kick off compilation of components
using ngtsc, so it is important for these symbols to correspond with the
publicly visible outer declaration of the class. However, the ESM2015
reflection host used to return the `ts.Symbol` for the inner
declaration, if the class was declared as follows:
```javascript
var MyClass = class MyClass {};
```
For the above code, `Esm2015ReflectionHost.getClassSymbol` would return
the `ts.Symbol` corresponding with the `class MyClass {}` declaration,
whereas it should have corresponded with the `var MyClass` declaration.
As a consequence, no `NgModule` could be resolved for the component, so
no components/directives would be in scope for the component. This
resulted in errors during runtime.
This commit resolves the issue by introducing a `NgccClassSymbol` that
contains references to both the outer and inner `ts.Symbol`, instead of
just a single `ts.Symbol`. This avoids the unclarity of whether a
`ts.Symbol` corresponds with the outer or inner declaration.
More details can be found here: https://hackmd.io/7nkgWOFWQlSRAuIW_8KPPw
Fixes #32078
Closes FW-1507
PR Close #32539
2019-09-03 15:26:58 -04:00
|
|
|
describe('getClassSymbol()', () => {
|
|
|
|
it('should return the class symbol for an ES2015 class', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
const classSymbol = host.getClassSymbol(node);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
|
|
|
expect(classSymbol !.declaration.valueDeclaration).toBe(node);
|
|
|
|
expect(classSymbol !.implementation.valueDeclaration).toBe(node);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the class symbol for a class expression (outer variable declaration)',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const outerNode = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
|
|
|
const innerNode = (outerNode.initializer as ts.ClassExpression);
|
|
|
|
const classSymbol = host.getClassSymbol(outerNode);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
|
|
|
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
|
|
|
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the class symbol for a class expression (inner class expression)', () => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const outerNode = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
|
|
|
const innerNode = (outerNode.initializer as ts.ClassExpression);
|
|
|
|
const classSymbol = host.getClassSymbol(innerNode);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
|
|
|
expect(classSymbol !.declaration.valueDeclaration).toBe(outerNode);
|
|
|
|
expect(classSymbol !.implementation.valueDeclaration).toBe(innerNode);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the same class symbol (of the outer declaration) for outer and inner declarations',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const outerNode = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
|
|
|
const innerNode = (outerNode.initializer as ts.ClassExpression);
|
|
|
|
|
|
|
|
const innerSymbol = host.getClassSymbol(innerNode) !;
|
|
|
|
const outerSymbol = host.getClassSymbol(outerNode) !;
|
|
|
|
expect(innerSymbol.declaration).toBe(outerSymbol.declaration);
|
|
|
|
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return undefined if node is not a class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
|
|
|
const classSymbol = host.getClassSymbol(node);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeUndefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return undefined if variable declaration is not initialized using a class expression',
|
|
|
|
() => {
|
|
|
|
const testFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `var MyClass = null;`,
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const {program} = makeTestBundleProgram(testFile.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, testFile.name, 'MyClass', isNamedVariableDeclaration);
|
|
|
|
const classSymbol = host.getClassSymbol(node);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeUndefined();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('isClass()', () => {
|
|
|
|
it('should return true if a given node is a TS class declaration', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
expect(host.isClass(node)).toBe(true);
|
|
|
|
});
|
2019-03-05 17:29:28 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return true if a given node is a class expression assigned into a variable',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
|
|
|
|
expect(host.isClass(node)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return true if a given node is a class expression assigned into two variables',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([CLASS_EXPRESSION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(CLASS_EXPRESSION_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node = getDeclaration(
|
|
|
|
program, CLASS_EXPRESSION_FILE.name, 'AliasedClass', ts.isVariableDeclaration);
|
|
|
|
expect(host.isClass(node)).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return false if a given node is a TS function declaration', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
|
|
|
expect(host.isClass(node)).toBe(false);
|
|
|
|
});
|
2018-10-10 09:17:32 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('hasBaseClass()', () => {
|
|
|
|
it('should not consider a class without extends clause as having a base class', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `class TestClass {}`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(program, file.name, 'TestClass', isNamedClassDeclaration);
|
|
|
|
expect(host.hasBaseClass(classNode)).toBe(false);
|
|
|
|
});
|
2019-03-05 17:29:28 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should consider a class with extends clause as having a base class', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `
|
2019-03-05 17:29:28 -05:00
|
|
|
class BaseClass {}
|
|
|
|
class TestClass extends BaseClass {}`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(program, file.name, 'TestClass', isNamedClassDeclaration);
|
|
|
|
expect(host.hasBaseClass(classNode)).toBe(true);
|
|
|
|
});
|
2019-03-05 17:29:28 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should consider an aliased class with extends clause as having a base class', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `
|
2019-03-05 17:29:28 -05:00
|
|
|
let TestClass_1;
|
|
|
|
class BaseClass {}
|
|
|
|
let TestClass = TestClass_1 = class TestClass extends BaseClass {}`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, file.name, 'TestClass', isNamedVariableDeclaration);
|
|
|
|
expect(host.hasBaseClass(classNode)).toBe(true);
|
|
|
|
});
|
2019-03-05 17:29:28 -05:00
|
|
|
});
|
|
|
|
|
2019-07-18 16:05:31 -04:00
|
|
|
describe('getBaseClassExpression()', () => {
|
|
|
|
it('should not consider a class without extends clause as having a base class', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `class TestClass {}`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(program, file.name, 'TestClass', isNamedClassDeclaration);
|
|
|
|
expect(host.getBaseClassExpression(classNode)).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the base class of a class with an `extends` clause', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `
|
|
|
|
class BaseClass {}
|
|
|
|
class TestClass extends BaseClass {}`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode = getDeclaration(program, file.name, 'TestClass', isNamedClassDeclaration);
|
|
|
|
const baseIdentifier = host.getBaseClassExpression(classNode) !;
|
|
|
|
if (!ts.isIdentifier(baseIdentifier)) {
|
|
|
|
throw new Error(`Expected ${baseIdentifier.getText()} to be an identifier.`);
|
|
|
|
}
|
|
|
|
expect(baseIdentifier.text).toEqual('BaseClass');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the base class of an aliased class with an `extends` clause', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `
|
|
|
|
let TestClass_1;
|
|
|
|
class BaseClass {}
|
|
|
|
let TestClass = TestClass_1 = class TestClass extends BaseClass {}`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, file.name, 'TestClass', isNamedVariableDeclaration);
|
|
|
|
const baseIdentifier = host.getBaseClassExpression(classNode) !;
|
|
|
|
if (!ts.isIdentifier(baseIdentifier)) {
|
|
|
|
throw new Error(`Expected ${baseIdentifier.getText()} to be an identifier.`);
|
|
|
|
}
|
|
|
|
expect(baseIdentifier.text).toEqual('BaseClass');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the base class expression of a class with a dynamic `extends` expression',
|
|
|
|
() => {
|
|
|
|
const file = {
|
|
|
|
name: _('/base_class.js'),
|
|
|
|
contents: `
|
|
|
|
class BaseClass {}
|
|
|
|
function foo() { return BaseClass; }
|
|
|
|
class TestClass extends foo() {}`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const {program} = makeTestBundleProgram(file.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classNode =
|
|
|
|
getDeclaration(program, file.name, 'TestClass', isNamedClassDeclaration);
|
|
|
|
const baseExpression = host.getBaseClassExpression(classNode) !;
|
|
|
|
expect(baseExpression.getText()).toEqual('foo()');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getGenericArityOfClass()', () => {
|
|
|
|
it('should properly count type parameters', () => {
|
|
|
|
loadTestFiles(ARITY_CLASSES);
|
|
|
|
const {program} = makeTestBundleProgram(ARITY_CLASSES[0].name);
|
|
|
|
const dts = makeTestBundleProgram(ARITY_CLASSES[1].name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
const noTypeParamClass =
|
|
|
|
getDeclaration(program, _('/src/class.js'), 'NoTypeParam', isNamedClassDeclaration);
|
|
|
|
expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0);
|
|
|
|
const oneTypeParamClass =
|
|
|
|
getDeclaration(program, _('/src/class.js'), 'OneTypeParam', isNamedClassDeclaration);
|
|
|
|
expect(host.getGenericArityOfClass(oneTypeParamClass)).toBe(1);
|
|
|
|
const twoTypeParamsClass =
|
|
|
|
getDeclaration(program, _('/src/class.js'), 'TwoTypeParams', isNamedClassDeclaration);
|
|
|
|
expect(host.getGenericArityOfClass(twoTypeParamsClass)).toBe(2);
|
|
|
|
});
|
2018-07-16 03:51:14 -04:00
|
|
|
});
|
2018-09-26 12:24:43 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getSwitchableDeclarations()', () => {
|
|
|
|
it('should return a collection of all the switchable variable declarations in the given module',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([MARKER_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(MARKER_FILE.name);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const file = getSourceFileOrError(program, MARKER_FILE.name);
|
|
|
|
const declarations = host.getSwitchableDeclarations(file);
|
|
|
|
expect(declarations.map(d => [d.name.getText(), d.initializer !.getText()])).toEqual([
|
|
|
|
['compileNgModuleFactory', 'compileNgModuleFactory__PRE_R3__']
|
|
|
|
]);
|
|
|
|
});
|
2019-06-03 12:41:47 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('findClassSymbols()', () => {
|
|
|
|
it('should return an array of all classes in the given source file', () => {
|
|
|
|
loadTestFiles(DECORATED_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const primaryFile = getSourceFileOrError(program, DECORATED_FILES[0].name);
|
|
|
|
const secondaryFile = getSourceFileOrError(program, DECORATED_FILES[1].name);
|
2018-10-16 03:56:54 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
|
|
|
|
expect(classSymbolsPrimary.length).toEqual(3);
|
|
|
|
expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B', 'C']);
|
2018-10-16 03:56:54 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
|
|
|
|
expect(classSymbolsSecondary.length).toEqual(1);
|
|
|
|
expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']);
|
|
|
|
});
|
|
|
|
});
|
2018-11-29 03:26:00 -05:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getDecoratorsOfSymbol()', () => {
|
|
|
|
it('should return decorators of class symbol', () => {
|
|
|
|
loadTestFiles(DECORATED_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const primaryFile = getSourceFileOrError(program, DECORATED_FILES[0].name);
|
|
|
|
const secondaryFile = getSourceFileOrError(program, DECORATED_FILES[1].name);
|
|
|
|
|
|
|
|
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
|
|
|
|
const classDecoratorsPrimary = classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s));
|
|
|
|
expect(classDecoratorsPrimary.length).toEqual(3);
|
|
|
|
expect(classDecoratorsPrimary[0] !.map(d => d.name)).toEqual(['Directive']);
|
|
|
|
expect(classDecoratorsPrimary[1] !.map(d => d.name)).toEqual(['Directive']);
|
|
|
|
expect(classDecoratorsPrimary[2]).toBe(null);
|
|
|
|
|
|
|
|
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
|
|
|
|
const classDecoratorsSecondary =
|
|
|
|
classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s));
|
|
|
|
expect(classDecoratorsSecondary.length).toEqual(1);
|
|
|
|
expect(classDecoratorsSecondary[0] !.map(d => d.name)).toEqual(['Directive']);
|
|
|
|
});
|
2019-10-22 14:32:38 -04:00
|
|
|
|
|
|
|
it('should return a cloned array on each invocation', () => {
|
|
|
|
loadTestFiles(DECORATED_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classDecl =
|
|
|
|
getDeclaration(program, DECORATED_FILES[0].name, 'A', ts.isClassDeclaration) !;
|
|
|
|
const classSymbol = host.getClassSymbol(classDecl) !;
|
|
|
|
|
|
|
|
const firstResult = host.getDecoratorsOfSymbol(classSymbol);
|
|
|
|
const secondResult = host.getDecoratorsOfSymbol(classSymbol);
|
|
|
|
|
|
|
|
expect(firstResult).not.toBe(secondResult);
|
|
|
|
});
|
2018-11-29 03:26:00 -05:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getDtsDeclarationsOfClass()', () => {
|
|
|
|
it('should find the dts declaration that has the same relative path to the source file',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const class1 =
|
2019-08-13 20:02:44 -04:00
|
|
|
getDeclaration(program, _('/ep/src/class1.js'), 'Class1', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(class1);
|
2019-08-13 20:02:44 -04:00
|
|
|
expect(dtsDeclaration !.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the dts declaration for exported functions', () => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const mooFn =
|
2019-08-13 20:02:44 -04:00
|
|
|
getDeclaration(program, _('/ep/src/func1.js'), 'mooFn', isNamedFunctionDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(mooFn);
|
2019-08-13 20:02:44 -04:00
|
|
|
expect(dtsDeclaration !.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2018-10-16 03:56:54 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return null if there is no matching class in the matching dts file', () => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
2019-08-13 20:02:44 -04:00
|
|
|
const missingClass = getDeclaration(
|
|
|
|
program, _('/ep/src/missing-class.js'), 'MissingClass2', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
expect(host.getDtsDeclaration(missingClass)).toBe(null);
|
|
|
|
});
|
2018-10-16 03:56:54 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return null if there is no matching dts file', () => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const missingClass = getDeclaration(
|
2019-08-13 20:02:44 -04:00
|
|
|
program, _('/ep/src/missing-class.js'), 'MissingClass2', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
expect(host.getDtsDeclaration(missingClass)).toBe(null);
|
|
|
|
});
|
2018-10-16 03:56:54 -04:00
|
|
|
|
2019-08-13 20:02:44 -04:00
|
|
|
it('should ignore dts files outside of the entrypoint', () => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(
|
|
|
|
getRootFiles(TYPINGS_SRC_FILES)[0], false, [_('/ep/src/shadow-class.js')]);
|
|
|
|
const dts = makeTestBundleProgram(
|
|
|
|
getRootFiles(TYPINGS_DTS_FILES)[0], false, [_('/ep/typings/shadow-class.d.ts')]);
|
|
|
|
const missingClass = getDeclaration(
|
|
|
|
program, _('/ep/src/shadow-class.js'), 'ShadowClass', isNamedClassDeclaration);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const dtsDecl = host.getDtsDeclaration(missingClass) !;
|
|
|
|
expect(dtsDecl).not.toBeNull();
|
|
|
|
expect(dtsDecl.getSourceFile().fileName).toEqual(_('/ep/typings/shadow-class.d.ts'));
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find the dts file that contains a matching class declaration, even if the source files do not match',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
2019-08-13 20:02:44 -04:00
|
|
|
const class1 = getDeclaration(
|
|
|
|
program, _('/ep/src/flat-file.js'), 'Class1', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(class1);
|
2019-08-13 20:02:44 -04:00
|
|
|
expect(dtsDeclaration !.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find aliased exports', () => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const class3 =
|
2019-08-13 20:02:44 -04:00
|
|
|
getDeclaration(program, _('/ep/src/flat-file.js'), 'Class3', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(class3);
|
2019-08-13 20:02:44 -04:00
|
|
|
expect(dtsDeclaration !.getSourceFile().fileName).toEqual(_('/ep/typings/class3.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2018-10-16 03:56:54 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find the dts file that contains a matching class declaration, even if the class is not publicly exported',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const internalClass = getDeclaration(
|
2019-08-13 20:02:44 -04:00
|
|
|
program, _('/ep/src/internal.js'), 'InternalClass', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(internalClass);
|
2019-08-13 20:02:44 -04:00
|
|
|
expect(dtsDeclaration !.getSourceFile().fileName)
|
|
|
|
.toEqual(_('/ep/typings/internal.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should prefer the publicly exported class if there are multiple classes with the same name',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const class2 =
|
2019-08-13 20:02:44 -04:00
|
|
|
getDeclaration(program, _('/ep/src/class2.js'), 'Class2', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const internalClass2 =
|
2019-08-13 20:02:44 -04:00
|
|
|
getDeclaration(program, _('/ep/src/internal.js'), 'Class2', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
|
|
|
|
|
|
|
|
const class2DtsDeclaration = host.getDtsDeclaration(class2);
|
|
|
|
expect(class2DtsDeclaration !.getSourceFile().fileName)
|
2019-08-13 20:02:44 -04:00
|
|
|
.toEqual(_('/ep/typings/class2.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2);
|
|
|
|
expect(internalClass2DtsDeclaration !.getSourceFile().fileName)
|
2019-08-13 20:02:44 -04:00
|
|
|
.toEqual(_('/ep/typings/class2.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2018-10-16 03:56:54 -04:00
|
|
|
});
|
|
|
|
|
2019-11-01 12:55:10 -04:00
|
|
|
describe('getInternalNameOfClass()', () => {
|
|
|
|
it('should return the name of the class (there is no separate inner class in ES2015)', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
expect(host.getInternalNameOfClass(node).text).toEqual('EmptyClass');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getAdjacentNameOfClass()', () => {
|
|
|
|
it('should return the name of the class (there is no separate inner class in ES2015)', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
|
|
|
const {program} = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const node =
|
|
|
|
getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
|
|
|
expect(host.getAdjacentNameOfClass(node).text).toEqual('EmptyClass');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getModuleWithProvidersFunctions()', () => {
|
|
|
|
it('should find every exported function that returns an object that looks like a ModuleWithProviders object',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const file = getSourceFileOrError(program, _('/src/functions.js'));
|
|
|
|
const fns = host.getModuleWithProvidersFunctions(file);
|
|
|
|
expect(fns.map(fn => [fn.declaration.name !.getText(), fn.ngModule.node.name.text]))
|
|
|
|
.toEqual([
|
|
|
|
['ngModuleIdentifier', 'InternalModule'],
|
|
|
|
['ngModuleWithEmptyProviders', 'InternalModule'],
|
|
|
|
['ngModuleWithProviders', 'InternalModule'],
|
|
|
|
['externalNgModule', 'ExternalModule'],
|
|
|
|
['namespacedExternalNgModule', 'ExternalModule'],
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find every static method on exported classes that return an object that looks like a ModuleWithProviders object',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
|
|
|
|
const host =
|
|
|
|
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const file = getSourceFileOrError(program, _('/src/methods.js'));
|
|
|
|
const fn = host.getModuleWithProvidersFunctions(file);
|
|
|
|
expect(fn.map(fn => [fn.declaration.name !.getText(), fn.ngModule.node.name.text]))
|
|
|
|
.toEqual([
|
|
|
|
['ngModuleIdentifier', 'InternalModule'],
|
|
|
|
['ngModuleWithEmptyProviders', 'InternalModule'],
|
|
|
|
['ngModuleWithProviders', 'InternalModule'],
|
|
|
|
['externalNgModule', 'ExternalModule'],
|
|
|
|
['namespacedExternalNgModule', 'ExternalModule'],
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
// https://github.com/angular/angular/issues/29078
|
|
|
|
it('should resolve aliased module references to their original declaration', () => {
|
|
|
|
loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM);
|
|
|
|
const {program} = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const file = getSourceFileOrError(program, _('/src/aliased_class.js'));
|
|
|
|
const fn = host.getModuleWithProvidersFunctions(file);
|
|
|
|
expect(fn.map(fn => [fn.declaration.name !.getText(), fn.ngModule.node.name.text]))
|
|
|
|
.toEqual([
|
|
|
|
['forRoot', 'AliasedModule'],
|
|
|
|
]);
|
|
|
|
});
|
2019-03-05 17:29:28 -05:00
|
|
|
});
|
2019-11-08 06:01:26 -05:00
|
|
|
|
|
|
|
describe('getEndOfClass()', () => {
|
|
|
|
it('should return the last static property of the class', () => {
|
|
|
|
const testFile: TestFile = {
|
|
|
|
name: _('/node_modules/test-package/some/file.js'),
|
|
|
|
contents: `import {Directive, NgZone, Console} from '@angular/core';\n` +
|
|
|
|
`export class SomeDirective {\n` +
|
|
|
|
` constructor(zone, cons) {}\n` +
|
|
|
|
` method() {}\n` +
|
|
|
|
`}\n` +
|
|
|
|
`SomeDirective.decorators = [\n` +
|
|
|
|
` { type: Directive, args: [{ selector: '[a]' }] },\n` +
|
|
|
|
` { type: OtherA }\n` +
|
|
|
|
`];\n` +
|
|
|
|
`SomeDirective.ctorParameters = () => [\n` +
|
|
|
|
` { type: NgZone },\n` +
|
|
|
|
` { type: Console }\n` +
|
|
|
|
`];\n` +
|
|
|
|
`callSomeFunction();\n` +
|
|
|
|
`var value = 100;\n`
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const {program} = makeTestBundleProgram(testFile.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classSymbol = host.findClassSymbols(program.getSourceFile(testFile.name) !)[0];
|
|
|
|
const endOfClass = host.getEndOfClass(classSymbol);
|
|
|
|
expect(endOfClass.getText())
|
|
|
|
.toEqual(
|
|
|
|
`SomeDirective.ctorParameters = () => [\n` +
|
|
|
|
` { type: NgZone },\n` +
|
|
|
|
` { type: Console }\n` +
|
|
|
|
`];`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the class declaration if there are no extra statements', () => {
|
|
|
|
const testFile: TestFile = {
|
|
|
|
name: _('/node_modules/test-package/some/file.js'),
|
|
|
|
contents: `export class SomeDirective {\n` +
|
|
|
|
` constructor(zone, cons) {}\n` +
|
|
|
|
` method() {}\n` +
|
|
|
|
`}\n` +
|
|
|
|
`callSomeFunction();\n` +
|
|
|
|
`var value = 100;\n`
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const {program} = makeTestBundleProgram(testFile.name);
|
|
|
|
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
|
|
|
const classSymbol = host.findClassSymbols(program.getSourceFile(testFile.name) !)[0];
|
|
|
|
const endOfClass = host.getEndOfClass(classSymbol);
|
|
|
|
expect(endOfClass.getText())
|
|
|
|
.toEqual(
|
|
|
|
`export class SomeDirective {\n` +
|
|
|
|
` constructor(zone, cons) {}\n` +
|
|
|
|
` method() {}\n` +
|
|
|
|
`}`);
|
|
|
|
});
|
|
|
|
});
|
2018-12-07 08:10:52 -05:00
|
|
|
});
|
2018-07-16 03:51:14 -04:00
|
|
|
});
|