2019-04-29 04:54:30 -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';
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
|
2020-04-03 09:37:45 -04:00
|
|
|
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
|
2020-04-09 18:35:40 -04:00
|
|
|
import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost} from '../../../src/ngtsc/reflection';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {getDeclaration} from '../../../src/ngtsc/testing';
|
|
|
|
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
|
2019-04-29 04:54:30 -04:00
|
|
|
import {CommonJsReflectionHost} from '../../src/host/commonjs_host';
|
2020-04-03 09:37:45 -04:00
|
|
|
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
|
2019-04-29 04:54:30 -04:00
|
|
|
import {getIifeBody} from '../../src/host/esm5_host';
|
2020-04-03 09:37:45 -04:00
|
|
|
import {NgccReflectionHost} from '../../src/host/ngcc_host';
|
|
|
|
import {BundleProgram} from '../../src/packages/bundle_program';
|
2019-04-29 04:54:30 -04:00
|
|
|
import {MockLogger} from '../helpers/mock_logger';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {getRootFiles, makeTestBundleProgram, makeTestDtsBundleProgram} from '../helpers/utils';
|
2019-04-29 04:54:30 -04:00
|
|
|
|
|
|
|
import {expectTypeValueReferencesForParameters} from './util';
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
runInEachFileSystem(() => {
|
|
|
|
describe('CommonJsReflectionHost', () => {
|
|
|
|
let _: typeof absoluteFrom;
|
|
|
|
|
|
|
|
let SOME_DIRECTIVE_FILE: TestFile;
|
|
|
|
let TOPLEVEL_DECORATORS_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 SIMPLE_ES2015_CLASS_FILE: TestFile;
|
|
|
|
let SIMPLE_CLASS_FILE: TestFile;
|
|
|
|
let FOO_FUNCTION_FILE: TestFile;
|
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
|
|
|
let INLINE_EXPORT_FILE: TestFile;
|
2019-06-06 15:22:32 -04:00
|
|
|
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 DECORATED_FILES: TestFile[];
|
|
|
|
let TYPINGS_SRC_FILES: TestFile[];
|
|
|
|
let TYPINGS_DTS_FILES: TestFile[];
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
// Helpers
|
|
|
|
const createHost = (bundle: BundleProgram, ngccHost: CommonJsReflectionHost) => {
|
|
|
|
const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker());
|
|
|
|
return new DelegatingReflectionHost(tsHost, ngccHost);
|
|
|
|
};
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
beforeEach(() => {
|
|
|
|
_ = absoluteFrom;
|
|
|
|
|
|
|
|
SOME_DIRECTIVE_FILE = {
|
|
|
|
name: _('/some_directive.cjs.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
|
|
|
|
var INJECTED_TOKEN = new InjectionToken('injected');
|
|
|
|
var ViewContainerRef = {};
|
|
|
|
var TemplateRef = {};
|
|
|
|
|
|
|
|
var SomeDirective = (function() {
|
|
|
|
function SomeDirective(_viewContainer, _template, injected) {
|
|
|
|
this.instanceProperty = 'instance';
|
|
|
|
}
|
|
|
|
SomeDirective.prototype = {
|
|
|
|
instanceMethod: function() {},
|
|
|
|
};
|
|
|
|
SomeDirective.staticMethod = function() {};
|
|
|
|
SomeDirective.staticProperty = 'static';
|
|
|
|
SomeDirective.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[someDirective]' },] }
|
|
|
|
];
|
|
|
|
SomeDirective.ctorParameters = function() { return [
|
|
|
|
{ type: ViewContainerRef, },
|
|
|
|
{ type: TemplateRef, },
|
|
|
|
{ type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] },
|
|
|
|
]; };
|
|
|
|
SomeDirective.propDecorators = {
|
|
|
|
"input1": [{ type: core.Input },],
|
|
|
|
"input2": [{ type: core.Input },],
|
|
|
|
};
|
|
|
|
return SomeDirective;
|
|
|
|
}());
|
|
|
|
exports.SomeDirective = SomeDirective;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
TOPLEVEL_DECORATORS_FILE = {
|
|
|
|
name: _('/toplevel_decorators.cjs.js'),
|
|
|
|
contents: `
|
2019-05-31 16:56:25 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
|
|
|
|
var INJECTED_TOKEN = new InjectionToken('injected');
|
|
|
|
var ViewContainerRef = {};
|
|
|
|
var TemplateRef = {};
|
|
|
|
|
|
|
|
var SomeDirective = (function() {
|
|
|
|
function SomeDirective(_viewContainer, _template, injected) {}
|
|
|
|
return SomeDirective;
|
|
|
|
}());
|
|
|
|
SomeDirective.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[someDirective]' },] }
|
|
|
|
];
|
|
|
|
SomeDirective.ctorParameters = function() { return [
|
|
|
|
{ type: ViewContainerRef, },
|
|
|
|
{ type: TemplateRef, },
|
|
|
|
{ type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] },
|
|
|
|
]; };
|
|
|
|
SomeDirective.propDecorators = {
|
|
|
|
"input1": [{ type: core.Input },],
|
|
|
|
"input2": [{ type: core.Input },],
|
|
|
|
};
|
|
|
|
exports.SomeDirective = SomeDirective;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-05-31 16:56:25 -04:00
|
|
|
|
2019-05-23 17:40:17 -04:00
|
|
|
CTOR_DECORATORS_ARRAY_FILE = {
|
|
|
|
name: _('/ctor_decorated_as_array.js'),
|
|
|
|
contents: `
|
|
|
|
var core = require('@angular/core');
|
|
|
|
var CtorDecoratedAsArray = (function() {
|
2019-12-05 14:02:57 -05:00
|
|
|
function CtorDecoratedAsArray(arg1) {
|
2019-05-23 17:40:17 -04:00
|
|
|
}
|
|
|
|
CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }];
|
|
|
|
return CtorDecoratedAsArray;
|
|
|
|
}());
|
|
|
|
exports.SomeDirective = SomeDirective;
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
SIMPLE_ES2015_CLASS_FILE = {
|
|
|
|
name: _('/simple_es2015_class.d.ts'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
export class EmptyClass {}
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
SIMPLE_CLASS_FILE = {
|
|
|
|
name: _('/simple_class.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var EmptyClass = (function() {
|
|
|
|
function EmptyClass() {
|
|
|
|
}
|
|
|
|
return EmptyClass;
|
|
|
|
}());
|
2019-11-13 03:40:51 -05:00
|
|
|
var NoParensClass = function() {
|
|
|
|
function EmptyClass() {
|
|
|
|
}
|
|
|
|
return EmptyClass;
|
|
|
|
}();
|
|
|
|
var InnerParensClass = (function() {
|
|
|
|
function EmptyClass() {
|
|
|
|
}
|
|
|
|
return EmptyClass;
|
|
|
|
})();
|
2019-04-29 04:54:30 -04:00
|
|
|
var NoDecoratorConstructorClass = (function() {
|
|
|
|
function NoDecoratorConstructorClass(foo) {
|
|
|
|
}
|
|
|
|
return NoDecoratorConstructorClass;
|
|
|
|
}());
|
2019-11-01 12:55:10 -04:00
|
|
|
var OuterClass1 = (function() {
|
|
|
|
function InnerClass1() {
|
|
|
|
}
|
|
|
|
return InnerClass1;
|
|
|
|
}());
|
|
|
|
var OuterClass2 = (function() {
|
|
|
|
function InnerClass2() {
|
|
|
|
}
|
|
|
|
InnerClass2_1 = InnerClass12
|
|
|
|
var InnerClass2_1;
|
|
|
|
return InnerClass2;
|
|
|
|
}());
|
|
|
|
var SuperClass = (function() { function SuperClass() {} return SuperClass; }());
|
|
|
|
var ChildClass = /** @class */ (function (_super) {
|
|
|
|
__extends(ChildClass, _super);
|
|
|
|
function InnerChildClass() {}
|
|
|
|
return InnerChildClass;
|
|
|
|
}(SuperClass);
|
2019-04-29 04:54:30 -04:00
|
|
|
exports.EmptyClass = EmptyClass;
|
|
|
|
exports.NoDecoratorConstructorClass = NoDecoratorConstructorClass;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
FOO_FUNCTION_FILE = {
|
|
|
|
name: _('/foo_function.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
function foo() {}
|
|
|
|
foo.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[ignored]' },] }
|
|
|
|
];
|
|
|
|
exports.foo = foo;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
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
|
|
|
INLINE_EXPORT_FILE = {
|
|
|
|
name: _('/inline_export.js'),
|
|
|
|
contents: `
|
|
|
|
var core = require('@angular/core');
|
|
|
|
function foo() {}
|
|
|
|
foo.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[ignored]' },] }
|
|
|
|
];
|
|
|
|
exports.directives = [foo];
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_DECORATORS_FILE = {
|
|
|
|
name: _('/invalid_decorators.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var NotArrayLiteral = (function() {
|
|
|
|
function NotArrayLiteral() {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.decorators = () => [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[ignored]' },] },
|
|
|
|
];
|
|
|
|
return NotArrayLiteral;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotObjectLiteral = (function() {
|
|
|
|
function NotObjectLiteral() {
|
|
|
|
}
|
|
|
|
NotObjectLiteral.decorators = [
|
|
|
|
"This is not an object literal",
|
|
|
|
{ type: core.Directive },
|
|
|
|
];
|
|
|
|
return NotObjectLiteral;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NoTypeProperty = (function() {
|
|
|
|
function NoTypeProperty() {
|
|
|
|
}
|
|
|
|
NoTypeProperty.decorators = [
|
|
|
|
{ notType: core.Directive },
|
|
|
|
{ type: core.Directive },
|
|
|
|
];
|
|
|
|
return NoTypeProperty;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotIdentifier = (function() {
|
|
|
|
function NotIdentifier() {
|
|
|
|
}
|
|
|
|
NotIdentifier.decorators = [
|
|
|
|
{ type: 'StringsLiteralsAreNotIdentifiers' },
|
|
|
|
{ type: core.Directive },
|
|
|
|
];
|
|
|
|
return NotIdentifier;
|
|
|
|
}());
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_DECORATOR_ARGS_FILE = {
|
|
|
|
name: _('/invalid_decorator_args.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var NoArgsProperty = (function() {
|
|
|
|
function NoArgsProperty() {
|
|
|
|
}
|
|
|
|
NoArgsProperty.decorators = [
|
|
|
|
{ type: core.Directive },
|
|
|
|
];
|
|
|
|
return NoArgsProperty;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var args = [{ selector: '[ignored]' },];
|
|
|
|
var NoPropertyAssignment = (function() {
|
|
|
|
function NoPropertyAssignment() {
|
|
|
|
}
|
|
|
|
NoPropertyAssignment.decorators = [
|
|
|
|
{ type: core.Directive, args },
|
|
|
|
];
|
|
|
|
return NoPropertyAssignment;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotArrayLiteral = (function() {
|
|
|
|
function NotArrayLiteral() {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.decorators = [
|
|
|
|
{ type: core.Directive, args: () => [{ selector: '[ignored]' },] },
|
|
|
|
];
|
|
|
|
return NotArrayLiteral;
|
|
|
|
}());
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_PROP_DECORATORS_FILE = {
|
|
|
|
name: _('/invalid_prop_decorators.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var NotObjectLiteral = (function() {
|
|
|
|
function NotObjectLiteral() {
|
|
|
|
}
|
|
|
|
NotObjectLiteral.propDecorators = () => ({
|
|
|
|
"prop": [{ type: core.Directive },]
|
|
|
|
});
|
|
|
|
return NotObjectLiteral;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotObjectLiteralProp = (function() {
|
|
|
|
function NotObjectLiteralProp() {
|
|
|
|
}
|
|
|
|
NotObjectLiteralProp.propDecorators = {
|
|
|
|
"prop": [
|
|
|
|
"This is not an object literal",
|
|
|
|
{ type: core.Directive },
|
|
|
|
]
|
|
|
|
};
|
|
|
|
return NotObjectLiteralProp;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NoTypeProperty = (function() {
|
|
|
|
function NoTypeProperty() {
|
|
|
|
}
|
|
|
|
NoTypeProperty.propDecorators = {
|
|
|
|
"prop": [
|
|
|
|
{ notType: core.Directive },
|
|
|
|
{ type: core.Directive },
|
|
|
|
]
|
|
|
|
};
|
|
|
|
return NoTypeProperty;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotIdentifier = (function() {
|
|
|
|
function NotIdentifier() {
|
|
|
|
}
|
|
|
|
NotIdentifier.propDecorators = {
|
|
|
|
"prop": [
|
|
|
|
{ type: 'StringsLiteralsAreNotIdentifiers' },
|
|
|
|
{ type: core.Directive },
|
|
|
|
]
|
|
|
|
};
|
|
|
|
return NotIdentifier;
|
|
|
|
}());
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_PROP_DECORATOR_ARGS_FILE = {
|
|
|
|
name: _('/invalid_prop_decorator_args.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var NoArgsProperty = (function() {
|
|
|
|
function NoArgsProperty() {
|
|
|
|
}
|
|
|
|
NoArgsProperty.propDecorators = {
|
|
|
|
"prop": [{ type: core.Input },]
|
|
|
|
};
|
|
|
|
return NoArgsProperty;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var args = [{ selector: '[ignored]' },];
|
|
|
|
var NoPropertyAssignment = (function() {
|
|
|
|
function NoPropertyAssignment() {
|
|
|
|
}
|
|
|
|
NoPropertyAssignment.propDecorators = {
|
|
|
|
"prop": [{ type: core.Input, args },]
|
|
|
|
};
|
|
|
|
return NoPropertyAssignment;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotArrayLiteral = (function() {
|
|
|
|
function NotArrayLiteral() {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.propDecorators = {
|
|
|
|
"prop": [{ type: core.Input, args: () => [{ selector: '[ignored]' },] },],
|
|
|
|
};
|
|
|
|
return NotArrayLiteral;
|
|
|
|
}());
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_CTOR_DECORATORS_FILE = {
|
|
|
|
name: _('/invalid_ctor_decorators.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var NoParameters = (function() {
|
|
|
|
function NoParameters() {}
|
|
|
|
return NoParameters;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var ArrowFunction = (function() {
|
|
|
|
function ArrowFunction(arg1) {
|
|
|
|
}
|
|
|
|
ArrowFunction.ctorParameters = () => [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: core.Inject },] }
|
|
|
|
];
|
|
|
|
return ArrowFunction;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotArrayLiteral = (function() {
|
|
|
|
function NotArrayLiteral(arg1) {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.ctorParameters = function() { return 'StringsAreNotArrayLiterals'; };
|
|
|
|
return NotArrayLiteral;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotObjectLiteral = (function() {
|
|
|
|
function NotObjectLiteral(arg1, arg2) {
|
|
|
|
}
|
|
|
|
NotObjectLiteral.ctorParameters = function() { return [
|
|
|
|
"This is not an object literal",
|
|
|
|
{ type: 'ParamType', decorators: [{ type: core.Inject },] },
|
|
|
|
]; };
|
|
|
|
return NotObjectLiteral;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NoTypeProperty = (function() {
|
|
|
|
function NoTypeProperty(arg1, arg2) {
|
|
|
|
}
|
|
|
|
NoTypeProperty.ctorParameters = function() { return [
|
|
|
|
{
|
|
|
|
type: 'ParamType',
|
|
|
|
decorators: [
|
|
|
|
{ notType: core.Inject },
|
|
|
|
{ type: core.Inject },
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]; };
|
|
|
|
return NoTypeProperty;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotIdentifier = (function() {
|
|
|
|
function NotIdentifier(arg1, arg2) {
|
|
|
|
}
|
|
|
|
NotIdentifier.ctorParameters = function() { return [
|
|
|
|
{
|
|
|
|
type: 'ParamType',
|
|
|
|
decorators: [
|
|
|
|
{ type: 'StringsLiteralsAreNotIdentifiers' },
|
|
|
|
{ type: core.Inject },
|
|
|
|
]
|
|
|
|
},
|
|
|
|
]; };
|
|
|
|
return NotIdentifier;
|
|
|
|
}());
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
INVALID_CTOR_DECORATOR_ARGS_FILE = {
|
|
|
|
name: _('/invalid_ctor_decorator_args.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var NoArgsProperty = (function() {
|
|
|
|
function NoArgsProperty(arg1) {
|
|
|
|
}
|
|
|
|
NoArgsProperty.ctorParameters = function() { return [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: core.Inject },] },
|
|
|
|
]; };
|
|
|
|
return NoArgsProperty;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var args = [{ selector: '[ignored]' },];
|
|
|
|
var NoPropertyAssignment = (function() {
|
|
|
|
function NoPropertyAssignment(arg1) {
|
|
|
|
}
|
|
|
|
NoPropertyAssignment.ctorParameters = function() { return [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: core.Inject, args },] },
|
|
|
|
]; };
|
|
|
|
return NoPropertyAssignment;
|
|
|
|
}());
|
|
|
|
|
|
|
|
var NotArrayLiteral = (function() {
|
|
|
|
function NotArrayLiteral(arg1) {
|
|
|
|
}
|
|
|
|
NotArrayLiteral.ctorParameters = function() { return [
|
|
|
|
{ type: 'ParamType', decorators: [{ type: core.Inject, args: () => [{ selector: '[ignored]' },] },] },
|
|
|
|
]; };
|
|
|
|
return NotArrayLiteral;
|
|
|
|
}());
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
IMPORTS_FILES = [
|
|
|
|
{
|
|
|
|
name: _('/index.js'),
|
|
|
|
contents: `
|
|
|
|
var file_a = require('./file_a');
|
|
|
|
var file_b = require('./file_b');
|
|
|
|
var file_c = require('./file_c');
|
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/file_a.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var a = 'a';
|
|
|
|
exports.a = a;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/file_b.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var file_a = require('./file_a');
|
|
|
|
var b = file_a.a;
|
|
|
|
var c = 'c';
|
|
|
|
var d = c;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/file_c.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var file_a = require('./file_a');
|
|
|
|
var c = file_a.a;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
EXPORTS_FILES = [
|
|
|
|
{
|
|
|
|
name: _('/index.js'),
|
|
|
|
contents: `
|
|
|
|
var a_module = require('./a_module');
|
|
|
|
var b_module = require('./b_module');
|
|
|
|
var xtra_module = require('./xtra_module');
|
2019-12-05 14:02:57 -05:00
|
|
|
var wildcard_reexports_emitted_helpers = require('./wildcard_reexports_emitted_helpers');
|
|
|
|
var wildcard_reexports_imported_helpers = require('./wildcard_reexports_imported_helpers');
|
2019-06-06 15:22:32 -04:00
|
|
|
`
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/a_module.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var a = 'a';
|
|
|
|
exports.a = a;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/b_module.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
2019-06-06 15:22:32 -04:00
|
|
|
var a_module = require('./a_module');
|
2019-04-29 04:54:30 -04:00
|
|
|
var b = a_module.a;
|
|
|
|
var e = 'e';
|
|
|
|
var SomeClass = (function() {
|
|
|
|
function SomeClass() {}
|
|
|
|
return SomeClass;
|
|
|
|
}());
|
|
|
|
|
|
|
|
exports.Directive = core.Directive;
|
|
|
|
exports.a = a_module.a;
|
|
|
|
exports.b = b;
|
|
|
|
exports.c = a_module.a;
|
|
|
|
exports.d = b;
|
|
|
|
exports.e = e;
|
|
|
|
exports.DirectiveX = core.Directive;
|
|
|
|
exports.SomeClass = SomeClass;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/xtra_module.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var xtra1 = 'xtra1';
|
|
|
|
var xtra2 = 'xtra2';
|
|
|
|
exports.xtra1 = xtra1;
|
|
|
|
exports.xtra2 = xtra2;
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
2019-12-05 14:02:57 -05:00
|
|
|
name: _('/wildcard_reexports_emitted_helpers.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-12-05 14:02:57 -05:00
|
|
|
function __export(m) {
|
|
|
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
|
|
}
|
|
|
|
var b_module = require("./b_module");
|
|
|
|
__export(b_module);
|
|
|
|
__export(require("./xtra_module"));
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/wildcard_reexports_imported_helpers.js'),
|
|
|
|
contents: `
|
|
|
|
var tslib_1 = require("tslib");
|
|
|
|
var b_module = require("./b_module");
|
|
|
|
tslib_1.__exportStar(b_module, exports);
|
|
|
|
tslib_1.__exportStar(require("./xtra_module"), exports);
|
|
|
|
`,
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
];
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
FUNCTION_BODY_FILE = {
|
|
|
|
name: _('/function_body.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
function foo(x) {
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
function bar(x, y) {
|
|
|
|
if (y === void 0) { y = 42; }
|
|
|
|
return x + y;
|
|
|
|
}
|
|
|
|
function complex() {
|
|
|
|
var x = 42;
|
|
|
|
return 42;
|
|
|
|
}
|
|
|
|
function baz(x) {
|
|
|
|
var y;
|
|
|
|
if (x === void 0) { y = 42; }
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
var y;
|
|
|
|
function qux(x) {
|
|
|
|
if (x === void 0) { y = 42; }
|
|
|
|
return y;
|
|
|
|
}
|
|
|
|
function moo() {
|
|
|
|
var x;
|
|
|
|
if (x === void 0) { x = 42; }
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
var x;
|
|
|
|
function juu() {
|
|
|
|
if (x === void 0) { x = 42; }
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
};
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
DECORATED_FILES = [
|
|
|
|
{
|
|
|
|
name: _('/primary.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var secondary = require('./secondary');
|
|
|
|
var A = (function() {
|
|
|
|
function A() {}
|
|
|
|
A.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[a]' }] }
|
|
|
|
];
|
|
|
|
return A;
|
|
|
|
}());
|
|
|
|
var B = (function() {
|
|
|
|
function B() {}
|
|
|
|
B.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[b]' }] }
|
|
|
|
];
|
|
|
|
return B;
|
|
|
|
}());
|
|
|
|
function x() {}
|
|
|
|
function y() {}
|
|
|
|
var C = (function() {
|
|
|
|
function C() {}
|
|
|
|
return C;
|
|
|
|
});
|
|
|
|
exports.A = A;
|
|
|
|
exports.x = x;
|
|
|
|
exports.C = C;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/secondary.js'),
|
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var core = require('@angular/core');
|
|
|
|
var D = (function() {
|
|
|
|
function D() {}
|
|
|
|
D.decorators = [
|
|
|
|
{ type: core.Directive, args: [{ selector: '[d]' }] }
|
|
|
|
];
|
|
|
|
return D;
|
|
|
|
}());
|
|
|
|
exports.D = D;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
}
|
|
|
|
];
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
TYPINGS_SRC_FILES = [
|
|
|
|
{
|
2019-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/src/index.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var internal = require('./internal');
|
|
|
|
var class1 = require('./class1');
|
|
|
|
var class2 = require('./class2');
|
2019-06-06 15:22:32 -04:00
|
|
|
var missing = require('./missing-class');
|
|
|
|
var flatFile = require('./flat-file');
|
|
|
|
var func1 = require('./func1');
|
2019-04-29 04:54:30 -04:00
|
|
|
function __export(m) {
|
|
|
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
|
|
}
|
|
|
|
var InternalClass = internal.InternalClass;
|
|
|
|
__export(class1);
|
|
|
|
__export(class2);
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
2019-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/src/class1.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var Class1 = (function() {
|
|
|
|
function Class1() {}
|
|
|
|
return Class1;
|
|
|
|
}());
|
|
|
|
var MissingClass1 = (function() {
|
|
|
|
function MissingClass1() {}
|
|
|
|
return MissingClass1;
|
|
|
|
}());
|
|
|
|
exports.Class1 = Class1;
|
|
|
|
exports.MissingClass1 = MissingClass1;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
2019-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/src/class2.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var Class2 = (function() {
|
|
|
|
function Class2() {}
|
|
|
|
return Class2;
|
|
|
|
}());
|
|
|
|
exports.Class2 = Class2;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
2019-12-18 09:03:05 -05:00
|
|
|
{name: _('/ep/src/func1.js'), contents: 'function mooFn() {} export {mooFn}'}, {
|
|
|
|
name: _('/ep/src/internal.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var InternalClass = (function() {
|
|
|
|
function InternalClass() {}
|
|
|
|
return InternalClass;
|
|
|
|
}());
|
|
|
|
var Class2 = (function() {
|
|
|
|
function Class2() {}
|
|
|
|
return Class2;
|
|
|
|
}());
|
|
|
|
exports.InternalClass =InternalClass;
|
|
|
|
exports.Class2 = Class2;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
2019-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/src/missing-class.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var MissingClass2 = (function() {
|
|
|
|
function MissingClass2() {}
|
|
|
|
return MissingClass2;
|
|
|
|
}());
|
|
|
|
exports. MissingClass2 = MissingClass2;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
2019-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/src/flat-file.js'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-04-29 04:54:30 -04:00
|
|
|
var Class1 = (function() {
|
|
|
|
function Class1() {}
|
|
|
|
return Class1;
|
|
|
|
}());
|
|
|
|
var MissingClass1 = (function() {
|
|
|
|
function MissingClass1() {}
|
|
|
|
return MissingClass1;
|
|
|
|
}());
|
|
|
|
var MissingClass2 = (function() {
|
|
|
|
function MissingClass2() {}
|
|
|
|
return MissingClass2;
|
|
|
|
}());
|
2019-12-18 09:03:05 -05:00
|
|
|
var SourceClass = (function() {
|
|
|
|
function SourceClass() {}
|
|
|
|
return SourceClass;
|
2019-04-29 04:54:30 -04:00
|
|
|
}());
|
|
|
|
exports.Class1 = Class1;
|
2019-12-18 09:03:05 -05:00
|
|
|
exports.AliasedClass = SourceClass;
|
2019-04-29 04:54:30 -04:00
|
|
|
exports.MissingClass1 = MissingClass1;
|
|
|
|
exports.MissingClass2 = MissingClass2;
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
TYPINGS_DTS_FILES = [
|
|
|
|
{
|
2019-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/typings/index.d.ts'),
|
2019-06-06 15:22:32 -04:00
|
|
|
contents: `
|
2019-12-18 09:03:05 -05:00
|
|
|
import '../../an_external_lib/index';
|
|
|
|
import {InternalClass} from './internal';
|
|
|
|
import {mooFn} from './func1';
|
|
|
|
export * from './class1';
|
|
|
|
export * from './class2';
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
|
|
|
{
|
2019-12-18 09:03:05 -05: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-12-18 09:03:05 -05:00
|
|
|
name: _('/ep/typings/class2.d.ts'),
|
|
|
|
contents: `
|
|
|
|
export declare class Class2 {}
|
|
|
|
export declare interface SomeInterface {}
|
|
|
|
export {TypingsClass as AliasedClass} from './typings-class';
|
|
|
|
`
|
2019-06-06 15:22:32 -04:00
|
|
|
},
|
2019-12-18 09:03:05 -05:00
|
|
|
{name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'},
|
2019-06-06 15:22:32 -04:00
|
|
|
{
|
2019-12-18 09:03:05 -05: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-12-18 09:03:05 -05:00
|
|
|
{
|
|
|
|
name: _('/ep/typings/typings-class.d.ts'),
|
|
|
|
contents: `export declare class TypingsClass {}`
|
|
|
|
},
|
|
|
|
{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-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('CommonJsReflectionHost', () => {
|
|
|
|
describe('getDecoratorsOfDeclaration()', () => {
|
|
|
|
it('should find the decorators on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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'});
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(decorator.args!.map(arg => arg.getText())).toEqual([
|
2019-06-06 15:22:32 -04:00
|
|
|
'{ selector: \'[someDirective]\' }',
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the decorators on a class at the top level', () => {
|
|
|
|
loadTestFiles([TOPLEVEL_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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'});
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(decorator.args!.map(arg => arg.getText())).toEqual([
|
2019-06-06 15:22:32 -04:00
|
|
|
'{ selector: \'[someDirective]\' }',
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return null if the symbol is not a class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const functionNode = getDeclaration(
|
|
|
|
bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(functionNode);
|
|
|
|
expect(decorators).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return null if there are no decorators', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode);
|
|
|
|
expect(decorators).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore `decorators` if it is not an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode);
|
|
|
|
expect(decorators).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore decorator elements that are not object literals', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore decorator elements that have no `type` property', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore decorator elements whose `type` value is not an identifier', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('(returned decorators `args`)', () => {
|
|
|
|
it('should be an empty array if decorator has no `args` property', () => {
|
|
|
|
loadTestFiles([INVALID_DECORATOR_ARGS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = host.getDecoratorsOfDeclaration(classNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Directive');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getMembersOfClass()', () => {
|
|
|
|
it('should find decorated members on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const input1 = members.find(member => member.name === 'input1')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(input1.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(input1.isStatic).toEqual(false);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(input1.decorators!.map(d => d.name)).toEqual(['Input']);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const input2 = members.find(member => member.name === 'input2')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(input2.isStatic).toEqual(false);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(input1.decorators!.map(d => d.name)).toEqual(['Input']);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find decorated members on a class at the top level', () => {
|
|
|
|
loadTestFiles([TOPLEVEL_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const input1 = members.find(member => member.name === 'input1')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(input1.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(input1.isStatic).toEqual(false);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(input1.decorators!.map(d => d.name)).toEqual(['Input']);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const input2 = members.find(member => member.name === 'input2')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(input2.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(input2.isStatic).toEqual(false);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(input1.decorators!.map(d => d.name)).toEqual(['Input']);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find non decorated properties on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const instanceProperty = members.find(member => member.name === 'instanceProperty')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(instanceProperty.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(instanceProperty.isStatic).toEqual(false);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true);
|
|
|
|
expect(instanceProperty.value!.getText()).toEqual(`'instance'`);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find static methods on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const staticMethod = members.find(member => member.name === 'staticMethod')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(staticMethod.kind).toEqual(ClassMemberKind.Method);
|
|
|
|
expect(staticMethod.isStatic).toEqual(true);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find static properties on a class', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const staticProperty = members.find(member => member.name === 'staticProperty')!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(staticProperty.kind).toEqual(ClassMemberKind.Property);
|
|
|
|
expect(staticProperty.isStatic).toEqual(true);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true);
|
|
|
|
expect(staticProperty.value!.getText()).toEqual(`'static'`);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should throw if the symbol is not a class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const functionNode = getDeclaration(
|
|
|
|
bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const members = host.getMembersOfClass(classNode);
|
|
|
|
|
|
|
|
expect(members).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not process decorated properties in `propDecorators` if it is not an object literal',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const prop = members.find(m => m.name === 'prop')!;
|
|
|
|
const decorators = prop.decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore prop decorator elements that have no `type` property', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const prop = members.find(m => m.name === 'prop')!;
|
|
|
|
const decorators = prop.decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore prop decorator elements whose `type` value is not an identifier', () => {
|
|
|
|
loadTestFiles([INVALID_PROP_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const prop = members.find(m => m.name === 'prop')!;
|
|
|
|
const decorators = prop.decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
|
|
|
|
});
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -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'});
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const prop = members.find(m => m.name === 'prop')!;
|
|
|
|
const decorators = prop.decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const prop = members.find(m => m.name === 'prop')!;
|
|
|
|
const decorators = prop.decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const members = host.getMembersOfClass(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const prop = members.find(m => m.name === 'prop')!;
|
|
|
|
const decorators = prop.decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Input');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getConstructorParameters', () => {
|
|
|
|
it('should find the decorated constructor parameters', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
|
|
|
expect(parameters).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters!.map(parameter => parameter.name)).toEqual([
|
2019-06-06 15:22:32 -04:00
|
|
|
'_viewContainer', '_template', 'injected'
|
|
|
|
]);
|
2020-04-03 09:37:45 -04:00
|
|
|
expectTypeValueReferencesForParameters(parameters!, [
|
2019-06-06 15:22:32 -04:00
|
|
|
'ViewContainerRef',
|
|
|
|
'TemplateRef',
|
|
|
|
null,
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the decorated constructor parameters at the top level', () => {
|
|
|
|
loadTestFiles([TOPLEVEL_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
|
|
|
expect(parameters).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters!.map(parameter => parameter.name)).toEqual([
|
2019-06-06 15:22:32 -04:00
|
|
|
'_viewContainer', '_template', 'injected'
|
|
|
|
]);
|
2020-04-03 09:37:45 -04:00
|
|
|
expectTypeValueReferencesForParameters(parameters!, [
|
2019-06-06 15:22:32 -04:00
|
|
|
'ViewContainerRef',
|
|
|
|
'TemplateRef',
|
|
|
|
null,
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
2019-05-23 17:40:17 -04:00
|
|
|
it('should accept `ctorParameters` as an array', () => {
|
|
|
|
loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-05-23 17:40:17 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray',
|
2019-05-23 17:40:17 -04:00
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const parameters = host.getConstructorParameters(classNode)!;
|
2019-05-23 17:40:17 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const functionNode = getDeclaration(
|
|
|
|
bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(() => {
|
|
|
|
host.getConstructorParameters(functionNode);
|
|
|
|
})
|
2019-06-06 15:22:32 -04:00
|
|
|
.toThrowError(
|
|
|
|
'Attempted to get constructor parameters of a non-class: "function foo() {}"');
|
|
|
|
});
|
|
|
|
|
|
|
|
// In ES5 there is no such thing as a constructor-less class
|
|
|
|
// it('should return `null` if there is no constructor', () => { });
|
|
|
|
|
|
|
|
it('should return an array even if there are no decorators', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
|
|
|
expect(parameters).toEqual(jasmine.any(Array));
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters!.length).toEqual(1);
|
|
|
|
expect(parameters![0].name).toEqual('foo');
|
|
|
|
expect(parameters![0].decorators).toBe(null);
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
|
|
|
expect(parameters).toEqual([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
// In ES5 there are no arrow functions
|
|
|
|
// it('should ignore `ctorParameters` if it is an arrow function', () => { });
|
|
|
|
|
|
|
|
it('should ignore `ctorParameters` if it does not return an array literal', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters!.length).toBe(1);
|
|
|
|
expect(parameters![0]).toEqual(jasmine.objectContaining<CtorParameter>({
|
2019-06-06 15:22:32 -04:00
|
|
|
name: 'arg1',
|
|
|
|
decorators: null,
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('(returned parameters `decorators`)', () => {
|
|
|
|
it('should ignore param decorator elements that are not object literals', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters!.length).toBe(2);
|
|
|
|
expect(parameters![0]).toEqual(jasmine.objectContaining<CtorParameter>({
|
2019-06-06 15:22:32 -04:00
|
|
|
name: 'arg1',
|
|
|
|
decorators: null,
|
|
|
|
}));
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters![1]).toEqual(jasmine.objectContaining<CtorParameter>({
|
2019-06-06 15:22:32 -04:00
|
|
|
name: 'arg2',
|
|
|
|
decorators: jasmine.any(Array) as any
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should ignore param decorator elements that have no `type` property', () => {
|
|
|
|
loadTestFiles([INVALID_CTOR_DECORATORS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = parameters![0].decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = parameters![0].decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = parameters![2].decorators!;
|
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].name).toBe('Inject');
|
|
|
|
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(parameters!.length).toBe(1);
|
|
|
|
const decorators = parameters![0].decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = parameters![0].decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral',
|
2019-06-06 15:22:32 -04:00
|
|
|
isNamedVariableDeclaration);
|
|
|
|
const parameters = host.getConstructorParameters(classNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
const decorators = parameters![0].decorators!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(decorators.length).toBe(1);
|
|
|
|
expect(decorators[0].name).toBe('Inject');
|
|
|
|
expect(decorators[0].args).toEqual([]);
|
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -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]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const fooNode = getDeclaration(
|
2020-04-03 09:37:45 -04:00
|
|
|
bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!;
|
|
|
|
const fooDef = host.getDefinitionOfFunction(fooNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(fooDef.node).toBe(fooNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(fooDef.body!.length).toEqual(1);
|
|
|
|
expect(fooDef.body![0].getText()).toEqual(`return x;`);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(fooDef.parameters.length).toEqual(1);
|
|
|
|
expect(fooDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(fooDef.parameters[0].initializer).toBe(null);
|
|
|
|
|
|
|
|
const barNode = getDeclaration(
|
2020-04-03 09:37:45 -04:00
|
|
|
bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!;
|
|
|
|
const barDef = host.getDefinitionOfFunction(barNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(barDef.node).toBe(barNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(barDef.body!.length).toEqual(1);
|
|
|
|
expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy();
|
|
|
|
expect(barDef.body![0].getText()).toEqual(`return x + y;`);
|
2019-06-06 15:22:32 -04:00
|
|
|
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');
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(barDef.parameters[1].initializer!.getText()).toEqual('42');
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const bazNode = getDeclaration(
|
2020-04-03 09:37:45 -04:00
|
|
|
bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!;
|
|
|
|
const bazDef = host.getDefinitionOfFunction(bazNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(bazDef.node).toBe(bazNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(bazDef.body!.length).toEqual(3);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(bazDef.parameters.length).toEqual(1);
|
|
|
|
expect(bazDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(bazDef.parameters[0].initializer).toBe(null);
|
|
|
|
|
|
|
|
const quxNode = getDeclaration(
|
2020-04-03 09:37:45 -04:00
|
|
|
bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!;
|
|
|
|
const quxDef = host.getDefinitionOfFunction(quxNode)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(quxDef.node).toBe(quxNode);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(quxDef.body!.length).toEqual(2);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(quxDef.parameters.length).toEqual(1);
|
|
|
|
expect(quxDef.parameters[0].name).toEqual('x');
|
|
|
|
expect(quxDef.parameters[0].initializer).toBe(null);
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getImportOfIdentifier', () => {
|
|
|
|
it('should find the import of an identifier', () => {
|
|
|
|
loadTestFiles(IMPORTS_FILES);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const variableNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, _('/file_b.js'), 'b', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const identifier = (variableNode.initializer &&
|
2020-03-04 20:27:27 -05:00
|
|
|
ts.isPropertyAccessExpression(variableNode.initializer) &&
|
|
|
|
ts.isIdentifier(variableNode.initializer.name)) ?
|
2019-06-06 15:22:32 -04:00
|
|
|
variableNode.initializer.name :
|
|
|
|
null;
|
|
|
|
|
|
|
|
expect(identifier).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
const importOfIdent = host.getImportOfIdentifier(identifier!);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(importOfIdent).toEqual({name: 'a', from: './file_a'});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return null if the identifier was not imported', () => {
|
|
|
|
loadTestFiles(IMPORTS_FILES);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const variableNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, _('/file_b.js'), 'd', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const importOfIdent =
|
|
|
|
host.getImportOfIdentifier(variableNode.initializer as ts.Identifier);
|
|
|
|
|
|
|
|
expect(importOfIdent).toBeNull();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle factory functions not wrapped in parentheses', () => {
|
|
|
|
loadTestFiles(IMPORTS_FILES);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const variableNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, _('/file_c.js'), 'c', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const identifier = (variableNode.initializer &&
|
2020-03-04 20:27:27 -05:00
|
|
|
ts.isPropertyAccessExpression(variableNode.initializer) &&
|
|
|
|
ts.isIdentifier(variableNode.initializer.name)) ?
|
2019-06-06 15:22:32 -04:00
|
|
|
variableNode.initializer.name :
|
|
|
|
null;
|
|
|
|
|
|
|
|
expect(identifier).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
const importOfIdent = host.getImportOfIdentifier(identifier!);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(importOfIdent).toEqual({name: 'a', from: './file_a'});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getDeclarationOfIdentifier', () => {
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
// Helpers
|
|
|
|
const createTestForTsHelper =
|
2020-04-03 09:37:45 -04:00
|
|
|
(program: ts.Program, host: NgccReflectionHost, srcFile: TestFile,
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
getHelperDeclaration: (name: string) => ts.Declaration) =>
|
|
|
|
(varName: string, helperName: string, knownAs: KnownDeclaration,
|
2020-04-03 09:37:45 -04:00
|
|
|
viaModule: string|null = null) => {
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
const node =
|
|
|
|
getDeclaration(program, srcFile.name, varName, ts.isVariableDeclaration);
|
|
|
|
const helperIdentifier = getIdentifierFromCallExpression(node);
|
|
|
|
const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier);
|
|
|
|
|
|
|
|
expect(helperDeclaration).toEqual({
|
|
|
|
known: knownAs,
|
2020-04-03 09:37:45 -04:00
|
|
|
node: getHelperDeclaration(helperName),
|
|
|
|
viaModule,
|
2020-04-09 18:35:40 -04:00
|
|
|
identity: null,
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => {
|
|
|
|
if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) {
|
|
|
|
const expr = decl.initializer.expression;
|
|
|
|
if (ts.isIdentifier(expr)) return expr;
|
2020-03-04 20:27:27 -05:00
|
|
|
if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) return expr.name;
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
}
|
|
|
|
throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`);
|
|
|
|
};
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return the declaration of a locally defined identifier', () => {
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const ctrDecorators = host.getConstructorParameters(classNode)!;
|
|
|
|
const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as {
|
2019-06-06 15:22:32 -04:00
|
|
|
local: true,
|
|
|
|
expression: ts.Identifier,
|
|
|
|
defaultImportStatement: null,
|
|
|
|
}).expression;
|
|
|
|
|
|
|
|
const expectedDeclarationNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef',
|
|
|
|
isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef);
|
|
|
|
expect(actualDeclaration).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(actualDeclaration!.node).toBe(expectedDeclarationNode);
|
|
|
|
expect(actualDeclaration!.viaModule).toBe(null);
|
2020-04-09 18:35:40 -04:00
|
|
|
expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
2020-02-18 06:17:59 -05:00
|
|
|
it('should return the correct declaration for an outer alias identifier', () => {
|
|
|
|
const PROGRAM_FILE: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
var AliasedClass = AliasedClass_1 = (function () {
|
|
|
|
function InnerClass() {
|
|
|
|
}
|
|
|
|
return InnerClass;
|
|
|
|
}());
|
|
|
|
var AliasedClass_1;
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
|
|
|
|
loadTestFiles([PROGRAM_FILE]);
|
|
|
|
const bundle = makeTestBundleProgram(PROGRAM_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2020-02-18 06:17:59 -05:00
|
|
|
|
|
|
|
const expectedDeclaration = getDeclaration(
|
|
|
|
bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration);
|
|
|
|
// Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`).
|
|
|
|
const aliasIdentifier =
|
|
|
|
(expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier;
|
|
|
|
const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier);
|
|
|
|
|
|
|
|
expect(aliasIdentifier.getText()).toBe('AliasedClass_1');
|
|
|
|
expect(actualDeclaration).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(actualDeclaration!.node).toBe(expectedDeclaration);
|
2020-02-18 06:17:59 -05:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return the source-file of an import namespace', () => {
|
|
|
|
loadFakeCore(getFileSystem());
|
|
|
|
loadTestFiles([SOME_DIRECTIVE_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const classDecorators = host.getDecoratorsOfDeclaration(classNode)!;
|
|
|
|
const identifierOfDirective =
|
|
|
|
(((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as
|
|
|
|
ts.PropertyAssignment)
|
|
|
|
.initializer as ts.PropertyAccessExpression)
|
|
|
|
.expression as ts.Identifier;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const expectedDeclarationNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective);
|
|
|
|
expect(actualDeclaration).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(actualDeclaration!.node).toBe(expectedDeclarationNode);
|
|
|
|
expect(actualDeclaration!.viaModule).toBe('@angular/core');
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2019-10-02 03:30:53 -04:00
|
|
|
|
|
|
|
it('should return viaModule: null for relative imports', () => {
|
|
|
|
loadTestFiles([
|
|
|
|
{
|
|
|
|
name: _('/index.js'),
|
|
|
|
contents: `
|
|
|
|
const a = require('./a');
|
|
|
|
var b = a.b;
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/a.js'),
|
|
|
|
contents: `
|
|
|
|
exports.b = 1;
|
|
|
|
`
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-10-02 03:30:53 -04:00
|
|
|
const variableNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, _('/index.js'), 'b', isNamedVariableDeclaration);
|
2019-10-02 03:30:53 -04:00
|
|
|
const identifier = variableNode.name as ts.Identifier;
|
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const importOfIdent = host.getDeclarationOfIdentifier(identifier!)!;
|
2019-10-02 03:30:53 -04:00
|
|
|
expect(importOfIdent.node).not.toBeNull();
|
|
|
|
expect(importOfIdent.viaModule).toBeNull();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return a viaModule for absolute imports', () => {
|
|
|
|
loadTestFiles([
|
|
|
|
{
|
|
|
|
name: _('/index.js'),
|
|
|
|
contents: `
|
|
|
|
var a = require('lib');
|
|
|
|
var b = a.x;
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/node_modules/lib/index.d.ts'),
|
|
|
|
contents: 'export declare const x: number;',
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-10-02 03:30:53 -04:00
|
|
|
const variableNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, _('/index.js'), 'b', isNamedVariableDeclaration);
|
2020-03-04 20:27:27 -05:00
|
|
|
const identifier =
|
2020-04-03 09:37:45 -04:00
|
|
|
(variableNode.initializer! as ts.PropertyAccessExpression).name as ts.Identifier;
|
2019-10-02 03:30:53 -04:00
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const importOfIdent = host.getDeclarationOfIdentifier(identifier!)!;
|
2019-10-02 03:30:53 -04:00
|
|
|
expect(importOfIdent.viaModule).toBe('lib');
|
|
|
|
});
|
2020-02-10 12:25:36 -05:00
|
|
|
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
it('should recognize TypeScript helpers (as function declarations)', () => {
|
|
|
|
const file: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
function __assign(t, ...sources) { /* ... */ }
|
|
|
|
function __spread(...args) { /* ... */ }
|
|
|
|
function __spreadArrays(...args) { /* ... */ }
|
|
|
|
|
|
|
|
var a = __assign({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = __spread(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
|
|
|
|
const testForHelper = createTestForTsHelper(
|
|
|
|
bundle.program, host, file,
|
|
|
|
helperName =>
|
|
|
|
getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration));
|
|
|
|
|
|
|
|
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
|
|
|
|
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
|
|
|
|
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should recognize suffixed TypeScript helpers (as function declarations)', () => {
|
|
|
|
const file: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
function __assign$1(t, ...sources) { /* ... */ }
|
|
|
|
function __spread$2(...args) { /* ... */ }
|
|
|
|
function __spreadArrays$3(...args) { /* ... */ }
|
|
|
|
|
|
|
|
var a = __assign$1({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = __spread$2(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
|
|
|
|
const testForHelper = createTestForTsHelper(
|
|
|
|
bundle.program, host, file,
|
|
|
|
helperName =>
|
|
|
|
getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration));
|
|
|
|
|
|
|
|
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
|
|
|
|
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
|
|
|
|
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should recognize TypeScript helpers (as variable declarations)', () => {
|
|
|
|
const file: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ }
|
|
|
|
var __spread = (this && this.__spread) || function (...args) { /* ... */ }
|
|
|
|
var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ }
|
|
|
|
|
|
|
|
var a = __assign({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = __spread(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
|
|
|
|
const testForHelper = createTestForTsHelper(
|
|
|
|
bundle.program, host, file,
|
|
|
|
helperName =>
|
|
|
|
getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration));
|
|
|
|
|
|
|
|
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
|
|
|
|
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
|
|
|
|
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should recognize suffixed TypeScript helpers (as variable declarations)', () => {
|
|
|
|
const file: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ }
|
|
|
|
var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ }
|
|
|
|
var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ }
|
|
|
|
|
|
|
|
var a = __assign$1({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = __spread$2(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
|
|
|
|
const testForHelper = createTestForTsHelper(
|
|
|
|
bundle.program, host, file,
|
|
|
|
helperName =>
|
|
|
|
getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration));
|
|
|
|
|
|
|
|
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
|
|
|
|
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
|
|
|
|
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should recognize imported TypeScript helpers', () => {
|
|
|
|
const files: TestFile[] = [
|
|
|
|
{
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
var tslib_1 = require('tslib');
|
|
|
|
|
|
|
|
var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: _('/node_modules/tslib/index.d.ts'),
|
|
|
|
contents: `
|
|
|
|
export declare function __assign(t: any, ...sources: any[]): any;
|
|
|
|
export declare function __spread(...args: any[][]): any[];
|
|
|
|
export declare function __spreadArrays(...args: any[][]): any[];
|
|
|
|
`,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
loadTestFiles(files);
|
|
|
|
|
|
|
|
const [testFile, tslibFile] = files;
|
|
|
|
const bundle = makeTestBundleProgram(testFile.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
const tslibSourceFile = getSourceFileOrError(bundle.program, tslibFile.name);
|
|
|
|
|
|
|
|
const testForHelper =
|
|
|
|
createTestForTsHelper(bundle.program, host, testFile, () => tslibSourceFile);
|
|
|
|
|
|
|
|
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib');
|
|
|
|
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib');
|
|
|
|
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib');
|
|
|
|
});
|
fix(ngcc): detect non-emitted, non-imported TypeScript helpers (#36418)
When TypeScript downlevels ES2015+ code to ES5, it uses some helper
functions to emulate some ES2015+ features, such as spread syntax. The
TypeScript compiler can be configured to emit these helpers into the
transpiled code (which is controlled by the `noEmitHelpers` option -
false by default). It can also be configured to import these helpers
from the `tslib` module (which is controlled by the `importHelpers`
option - false by default).
While most of the time the helpers will be either emitted or imported,
it is possible that one configures their app to neither emit nor import
them. In that case, the helpers could, for example, be made available on
the global object. This is what `@nativescript/angular`
v9.0.0-next-2019-11-12-155500-01 does. See, for example, [common.js][1].
Ngcc must be able to detect and statically evaluate these helpers.
Previously, it was only able to detect emitted or imported helpers.
This commit adds support for detecting these helpers if they are neither
emitted nor imported. It does this by checking identifiers for which no
declaration (either concrete or inline) can be found against a list of
known TypeScript helper function names.
[1]: https://unpkg.com/browse/@nativescript/angular@9.0.0-next-2019-11-12-155500-01/common.js
PR Close #36418
2020-04-07 12:14:57 -04:00
|
|
|
|
|
|
|
it('should recognize undeclared, unimported TypeScript helpers (by name)', () => {
|
|
|
|
const file: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
var a = __assign({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = __spread(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
|
|
|
|
|
|
const testForHelper =
|
|
|
|
(varName: string, helperName: string, knownAs: KnownDeclaration) => {
|
|
|
|
const node =
|
|
|
|
getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration);
|
|
|
|
const helperIdentifier = getIdentifierFromCallExpression(node);
|
|
|
|
const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier);
|
|
|
|
|
|
|
|
expect(helperDeclaration).toEqual({
|
|
|
|
known: knownAs,
|
|
|
|
expression: helperIdentifier,
|
|
|
|
node: null,
|
|
|
|
viaModule: null,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
|
|
|
|
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
|
|
|
|
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => {
|
|
|
|
const file: TestFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `
|
|
|
|
var a = __assign$1({foo: 'bar'}, {baz: 'qux'});
|
|
|
|
var b = __spread$2(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']);
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([file]);
|
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
|
|
|
|
|
|
const testForHelper =
|
|
|
|
(varName: string, helperName: string, knownAs: KnownDeclaration) => {
|
|
|
|
const node =
|
|
|
|
getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration);
|
|
|
|
const helperIdentifier = getIdentifierFromCallExpression(node);
|
|
|
|
const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier);
|
|
|
|
|
|
|
|
expect(helperDeclaration).toEqual({
|
|
|
|
known: knownAs,
|
|
|
|
expression: helperIdentifier,
|
|
|
|
node: null,
|
|
|
|
viaModule: null,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
|
|
|
|
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
|
|
|
|
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
|
|
|
|
});
|
2019-04-29 04:54:30 -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);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const file = getSourceFileOrError(bundle.program, _('/b_module.js'));
|
2019-06-06 15:22:32 -04:00
|
|
|
const exportDeclarations = host.getExportsOfModule(file);
|
|
|
|
expect(exportDeclarations).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(Array.from(exportDeclarations!.entries())
|
|
|
|
.map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule]))
|
2019-06-06 15:22:32 -04:00
|
|
|
.toEqual([
|
|
|
|
['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'],
|
2019-10-02 03:30:53 -04:00
|
|
|
['a', `a = 'a'`, null],
|
2019-06-06 15:22:32 -04:00
|
|
|
['b', `b = a_module.a`, null],
|
2019-10-02 03:30:53 -04:00
|
|
|
['c', `a = 'a'`, null],
|
2019-06-06 15:22:32 -04:00
|
|
|
['d', `b = a_module.a`, null],
|
|
|
|
['e', `e = 'e'`, null],
|
|
|
|
['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'],
|
|
|
|
[
|
|
|
|
'SomeClass',
|
|
|
|
`SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n}())`,
|
|
|
|
null
|
|
|
|
],
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
2019-12-05 14:02:57 -05:00
|
|
|
it('should handle wildcard re-exports of other modules (with emitted helpers)', () => {
|
|
|
|
loadFakeCore(getFileSystem());
|
|
|
|
loadTestFiles(EXPORTS_FILES);
|
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-05 14:02:57 -05:00
|
|
|
const file =
|
|
|
|
getSourceFileOrError(bundle.program, _('/wildcard_reexports_emitted_helpers.js'));
|
|
|
|
const exportDeclarations = host.getExportsOfModule(file);
|
|
|
|
expect(exportDeclarations).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(Array.from(exportDeclarations!.entries())
|
|
|
|
.map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule]))
|
2019-12-05 14:02:57 -05:00
|
|
|
.toEqual([
|
|
|
|
['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')],
|
|
|
|
['a', `a = 'a'`, _('/b_module')],
|
|
|
|
['b', `b = a_module.a`, _('/b_module')],
|
|
|
|
['c', `a = 'a'`, _('/b_module')],
|
|
|
|
['d', `b = a_module.a`, _('/b_module')],
|
|
|
|
['e', `e = 'e'`, _('/b_module')],
|
|
|
|
['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')],
|
|
|
|
[
|
|
|
|
'SomeClass',
|
|
|
|
`SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n}())`,
|
|
|
|
_('/b_module')
|
|
|
|
],
|
|
|
|
['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')],
|
|
|
|
['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')],
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle wildcard re-exports of other modules (with imported helpers)', () => {
|
2019-06-06 15:22:32 -04:00
|
|
|
loadFakeCore(getFileSystem());
|
|
|
|
loadTestFiles(EXPORTS_FILES);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/index.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-05 14:02:57 -05:00
|
|
|
const file =
|
|
|
|
getSourceFileOrError(bundle.program, _('/wildcard_reexports_imported_helpers.js'));
|
2019-06-06 15:22:32 -04:00
|
|
|
const exportDeclarations = host.getExportsOfModule(file);
|
|
|
|
expect(exportDeclarations).not.toBe(null);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(Array.from(exportDeclarations!.entries())
|
|
|
|
.map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule]))
|
2019-06-06 15:22:32 -04:00
|
|
|
.toEqual([
|
|
|
|
['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')],
|
|
|
|
['a', `a = 'a'`, _('/b_module')],
|
|
|
|
['b', `b = a_module.a`, _('/b_module')],
|
|
|
|
['c', `a = 'a'`, _('/b_module')],
|
|
|
|
['d', `b = a_module.a`, _('/b_module')],
|
|
|
|
['e', `e = 'e'`, _('/b_module')],
|
|
|
|
['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')],
|
|
|
|
[
|
|
|
|
'SomeClass',
|
|
|
|
`SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n}())`,
|
|
|
|
_('/b_module')
|
|
|
|
],
|
|
|
|
['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')],
|
|
|
|
['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')],
|
|
|
|
]);
|
|
|
|
});
|
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
|
|
|
|
|
|
|
it('should handle inline exports', () => {
|
|
|
|
loadTestFiles([INLINE_EXPORT_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/inline_export.js'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const file = getSourceFileOrError(bundle.program, _('/inline_export.js'));
|
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 exportDeclarations = host.getExportsOfModule(file);
|
|
|
|
expect(exportDeclarations).not.toBeNull();
|
2020-04-03 09:37:45 -04:00
|
|
|
const decl = exportDeclarations!.get('directives') as InlineDeclaration;
|
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
|
|
|
expect(decl).not.toBeUndefined();
|
|
|
|
expect(decl.node).toBeNull();
|
|
|
|
expect(decl.expression).toBeDefined();
|
|
|
|
});
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
|
|
|
|
it('should recognize declarations of known TypeScript helpers', () => {
|
|
|
|
const tslib = {
|
|
|
|
name: _('/tslib.d.ts'),
|
|
|
|
contents: `
|
|
|
|
export declare function __assign(t: any, ...sources: any[]): any;
|
|
|
|
export declare function __spread(...args: any[][]): any[];
|
|
|
|
export declare function __spreadArrays(...args: any[][]): any[];
|
|
|
|
export declare function __unknownHelper(...args: any[]): any;
|
|
|
|
`,
|
|
|
|
};
|
|
|
|
loadTestFiles([tslib]);
|
|
|
|
const bundle = makeTestBundleProgram(tslib.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
const sf = getSourceFileOrError(bundle.program, tslib.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const exportDeclarations = host.getExportsOfModule(sf)!;
|
fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).
Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.
In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.
In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).
As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.
---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).
Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.
The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.
Jira issue: https://angular-team.atlassian.net/browse/FW-1689
PR Close #35191
2020-02-06 11:44:49 -05:00
|
|
|
|
|
|
|
expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known]))
|
|
|
|
.toEqual([
|
|
|
|
['__assign', KnownDeclaration.TsHelperAssign],
|
|
|
|
['__spread', KnownDeclaration.TsHelperSpread],
|
|
|
|
['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays],
|
|
|
|
['__unknownHelper', null],
|
|
|
|
]);
|
|
|
|
});
|
2020-04-09 18:35:40 -04:00
|
|
|
|
|
|
|
it('should recognize enum declarations with string values', () => {
|
|
|
|
const testFile: TestFile = {
|
|
|
|
name: _('/node_modules/test-package/some/file.js'),
|
|
|
|
contents: `
|
|
|
|
var Enum;
|
|
|
|
(function (Enum) {
|
|
|
|
Enum["ValueA"] = "1";
|
|
|
|
Enum["ValueB"] = "2";
|
|
|
|
})(exports.Enum || (exports.Enum = {}));
|
|
|
|
|
|
|
|
var value = Enum;`
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const bundle = makeTestBundleProgram(testFile.name);
|
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
|
|
const valueDecl = getDeclaration(
|
|
|
|
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
|
|
|
|
ts.isVariableDeclaration);
|
|
|
|
const declaration = host.getDeclarationOfIdentifier(
|
|
|
|
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
|
|
|
|
|
|
|
|
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
|
|
|
|
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
|
|
|
|
expect(enumMembers!.length).toBe(2);
|
|
|
|
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
|
|
|
|
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
|
|
|
|
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
|
|
|
|
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should recognize enum declarations with numeric values', () => {
|
|
|
|
const testFile: TestFile = {
|
|
|
|
name: _('/node_modules/test-package/some/file.js'),
|
|
|
|
contents: `
|
|
|
|
var Enum;
|
|
|
|
(function (Enum) {
|
|
|
|
Enum[Enum["ValueA"] = "1"] = "ValueA";
|
|
|
|
Enum[Enum["ValueB"] = "2"] = "ValueB";
|
|
|
|
})(exports.Enum || (exports.Enum = {}));
|
|
|
|
|
|
|
|
var value = Enum;`
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const bundle = makeTestBundleProgram(testFile.name);
|
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
|
|
const valueDecl = getDeclaration(
|
|
|
|
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
|
|
|
|
ts.isVariableDeclaration);
|
|
|
|
const declaration = host.getDeclarationOfIdentifier(
|
|
|
|
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
|
|
|
|
|
|
|
|
const enumMembers = (declaration.identity as DownleveledEnum).enumMembers;
|
|
|
|
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
|
|
|
|
expect(enumMembers!.length).toBe(2);
|
|
|
|
expect(enumMembers![0].name.getText()).toBe('"ValueA"');
|
|
|
|
expect(enumMembers![0].initializer!.getText()).toBe('"1"');
|
|
|
|
expect(enumMembers![1].name.getText()).toBe('"ValueB"');
|
|
|
|
expect(enumMembers![1].initializer!.getText()).toBe('"2"');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not consider IIFEs that do no assign members to the parameter as an enum declaration',
|
|
|
|
() => {
|
|
|
|
const testFile: TestFile = {
|
|
|
|
name: _('/node_modules/test-package/some/file.js'),
|
|
|
|
contents: `
|
|
|
|
var Enum;
|
|
|
|
(function (E) {
|
|
|
|
Enum["ValueA"] = "1";
|
|
|
|
Enum["ValueB"] = "2";
|
|
|
|
})(exports.Enum || (exports.Enum = {}));
|
|
|
|
|
|
|
|
var value = Enum;`
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const bundle = makeTestBundleProgram(testFile.name);
|
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
|
|
const valueDecl = getDeclaration(
|
|
|
|
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
|
|
|
|
ts.isVariableDeclaration);
|
|
|
|
const declaration = host.getDeclarationOfIdentifier(
|
|
|
|
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
|
|
|
|
|
|
|
|
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
|
|
|
|
expect(declaration.identity).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not consider IIFEs without call argument as an enum declaration', () => {
|
|
|
|
const testFile: TestFile = {
|
|
|
|
name: _('/node_modules/test-package/some/file.js'),
|
|
|
|
contents: `
|
|
|
|
var Enum;
|
|
|
|
(function (Enum) {
|
|
|
|
Enum["ValueA"] = "1";
|
|
|
|
Enum["ValueB"] = "2";
|
|
|
|
})();
|
|
|
|
|
|
|
|
var value = Enum;`
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
|
|
|
const bundle = makeTestBundleProgram(testFile.name);
|
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
|
|
|
const valueDecl = getDeclaration(
|
|
|
|
bundle.program, _('/node_modules/test-package/some/file.js'), 'value',
|
|
|
|
ts.isVariableDeclaration);
|
|
|
|
const declaration = host.getDeclarationOfIdentifier(
|
|
|
|
valueDecl.initializer as ts.Identifier) as ConcreteDeclaration;
|
|
|
|
|
|
|
|
expect(declaration.node.parent.parent.getText()).toBe('var Enum;');
|
|
|
|
expect(declaration.identity).toBe(null);
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getClassSymbol()', () => {
|
|
|
|
it('should return the class symbol for an ES2015 class', () => {
|
|
|
|
loadTestFiles([SIMPLE_ES2015_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const node = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const classSymbol = host.getClassSymbol(node);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classSymbol!.declaration.valueDeclaration).toBe(node);
|
|
|
|
expect(classSymbol!.implementation.valueDeclaration).toBe(node);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the class symbol for an ES5 class (outer variable declaration)', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
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
|
|
|
const outerNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
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
|
|
|
const classSymbol = host.getClassSymbol(outerNode);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode);
|
|
|
|
expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the class symbol for an ES5 class (inner function declaration)', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const outerNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
const classSymbol = host.getClassSymbol(innerNode);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode);
|
|
|
|
expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the same class symbol (of the outer declaration) for outer and inner declarations',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const outerNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
2019-06-06 15:22:32 -04:00
|
|
|
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerSymbol = host.getClassSymbol(innerNode)!;
|
|
|
|
const outerSymbol = host.getClassSymbol(outerNode)!;
|
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
|
|
|
expect(innerSymbol.declaration).toBe(outerSymbol.declaration);
|
|
|
|
expect(innerSymbol.implementation).toBe(outerSymbol.implementation);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
2019-11-13 03:40:51 -05:00
|
|
|
it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-11-13 03:40:51 -05:00
|
|
|
const outerNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
2019-11-13 03:40:51 -05:00
|
|
|
const classSymbol = host.getClassSymbol(outerNode);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode);
|
|
|
|
expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode);
|
2019-11-13 03:40:51 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-11-13 03:40:51 -05:00
|
|
|
const outerNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass',
|
|
|
|
isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
2019-11-13 03:40:51 -05:00
|
|
|
const classSymbol = host.getClassSymbol(outerNode);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeDefined();
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode);
|
|
|
|
expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode);
|
2019-11-13 03:40:51 -05:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should return undefined if node is not an ES5 class', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const node = getDeclaration(
|
|
|
|
bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const classSymbol = host.getClassSymbol(node);
|
|
|
|
|
|
|
|
expect(classSymbol).toBeUndefined();
|
|
|
|
});
|
|
|
|
|
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
|
|
|
it('should return undefined if variable declaration is not initialized using an IIFE',
|
|
|
|
() => {
|
|
|
|
const testFile = {
|
|
|
|
name: _('/test.js'),
|
|
|
|
contents: `var MyClass = null;`,
|
|
|
|
};
|
|
|
|
loadTestFiles([testFile]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(testFile.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const node = getDeclaration(
|
|
|
|
bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration);
|
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
|
|
|
const classSymbol = host.getClassSymbol(node);
|
2019-06-06 15:22: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
|
|
|
expect(classSymbol).toBeUndefined();
|
|
|
|
});
|
|
|
|
});
|
2019-06-06 15:22: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('isClass()', () => {
|
|
|
|
it('should return true if a given node is a TS class declaration', () => {
|
|
|
|
loadTestFiles([SIMPLE_ES2015_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
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
|
|
|
const node = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration);
|
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
|
|
|
expect(host.isClass(node)).toBe(true);
|
2019-06-06 15:22: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
|
|
|
it('should return true if a given node is the outer variable declaration of a class',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
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
|
|
|
const node = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
|
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
|
|
|
expect(host.isClass(node)).toBe(true);
|
|
|
|
});
|
2019-06-06 15:22: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
|
|
|
it('should return true if a given node is the inner variable declaration of a class',
|
|
|
|
() => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
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
|
|
|
const outerNode = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const innerNode = getIifeBody(outerNode)!.statements.find(isNamedFunctionDeclaration)!;
|
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
|
|
|
expect(host.isClass(innerNode)).toBe(true);
|
|
|
|
});
|
2019-06-06 15:22: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
|
|
|
it('should return false if a given node is a function declaration', () => {
|
|
|
|
loadTestFiles([FOO_FUNCTION_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const node = getDeclaration(
|
|
|
|
bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration);
|
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
|
|
|
expect(host.isClass(node)).toBe(false);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('hasBaseClass()', () => {
|
|
|
|
function hasBaseClass(source: string) {
|
|
|
|
const file = {
|
|
|
|
name: _('/synthesized_constructors.js'),
|
|
|
|
contents: source,
|
|
|
|
};
|
|
|
|
|
|
|
|
loadTestFiles([file]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-06-06 15:22:32 -04:00
|
|
|
const classNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
return host.hasBaseClass(classNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
it('should consider an IIFE with _super parameter as having a base class', () => {
|
|
|
|
const result = hasBaseClass(`
|
2019-04-29 04:54:30 -04:00
|
|
|
var TestClass = /** @class */ (function (_super) {
|
|
|
|
__extends(TestClass, _super);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(null));`);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(result).toBe(true);
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should consider an IIFE with a unique name generated for the _super parameter as having a base class',
|
|
|
|
() => {
|
|
|
|
const result = hasBaseClass(`
|
2019-04-29 04:54:30 -04:00
|
|
|
var TestClass = /** @class */ (function (_super_1) {
|
|
|
|
__extends(TestClass, _super_1);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(null));`);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(result).toBe(true);
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should not consider an IIFE without parameter as having a base class', () => {
|
|
|
|
const result = hasBaseClass(`
|
2019-04-29 04:54:30 -04:00
|
|
|
var TestClass = /** @class */ (function () {
|
|
|
|
__extends(TestClass, _super);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(null));`);
|
2019-06-06 15:22:32 -04:00
|
|
|
expect(result).toBe(false);
|
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-07-18 16:05:31 -04:00
|
|
|
describe('getBaseClassExpression()', () => {
|
|
|
|
function getBaseClassIdentifier(source: string): ts.Identifier|null {
|
|
|
|
const file = {
|
|
|
|
name: _('/synthesized_constructors.js'),
|
|
|
|
contents: source,
|
|
|
|
};
|
|
|
|
|
|
|
|
loadTestFiles([file]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-07-18 16:05:31 -04:00
|
|
|
const classNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
2019-07-18 16:05:31 -04:00
|
|
|
const expression = host.getBaseClassExpression(classNode);
|
|
|
|
if (expression !== null && !ts.isIdentifier(expression)) {
|
|
|
|
throw new Error(
|
|
|
|
'Expected class to inherit via an identifier but got: ' + expression.getText());
|
|
|
|
}
|
|
|
|
return expression;
|
|
|
|
}
|
|
|
|
|
|
|
|
it('should find the base class of an IIFE with _super parameter', () => {
|
|
|
|
const identifier = getBaseClassIdentifier(`
|
|
|
|
var BaseClass = /** @class */ (function () {
|
|
|
|
function BaseClass() {}
|
|
|
|
return BaseClass;
|
|
|
|
}());
|
|
|
|
var TestClass = /** @class */ (function (_super) {
|
|
|
|
__extends(TestClass, _super);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(BaseClass));`);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(identifier!.text).toBe('BaseClass');
|
2019-07-18 16:05:31 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should find the base class of an IIFE with a unique name generated for the _super parameter',
|
|
|
|
() => {
|
|
|
|
const identifier = getBaseClassIdentifier(`
|
|
|
|
var BaseClass = /** @class */ (function () {
|
|
|
|
function BaseClass() {}
|
|
|
|
return BaseClass;
|
|
|
|
}());
|
|
|
|
var TestClass = /** @class */ (function (_super_1) {
|
|
|
|
__extends(TestClass, _super_1);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(BaseClass));`);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(identifier!.text).toBe('BaseClass');
|
2019-07-18 16:05:31 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('should not find a base class for an IIFE without parameter', () => {
|
|
|
|
const identifier = getBaseClassIdentifier(`
|
|
|
|
var BaseClass = /** @class */ (function () {
|
|
|
|
function BaseClass() {}
|
|
|
|
return BaseClass;
|
|
|
|
}());
|
|
|
|
var TestClass = /** @class */ (function () {
|
|
|
|
__extends(TestClass, _super);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(BaseClass));`);
|
|
|
|
expect(identifier).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should find a dynamic base class expression of an IIFE', () => {
|
|
|
|
const file = {
|
|
|
|
name: _('/synthesized_constructors.js'),
|
|
|
|
contents: `
|
|
|
|
var BaseClass = /** @class */ (function () {
|
|
|
|
function BaseClass() {}
|
|
|
|
return BaseClass;
|
|
|
|
}());
|
|
|
|
function foo() { return BaseClass; }
|
|
|
|
var TestClass = /** @class */ (function (_super) {
|
|
|
|
__extends(TestClass, _super);
|
|
|
|
function TestClass() {}
|
|
|
|
return TestClass;
|
|
|
|
}(foo()));`,
|
|
|
|
};
|
|
|
|
|
|
|
|
loadTestFiles([file]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(file.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-07-18 16:05:31 -04:00
|
|
|
const classNode =
|
2019-12-18 09:03:04 -05:00
|
|
|
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const expression = host.getBaseClassExpression(classNode)!;
|
2019-07-18 16:05:31 -04:00
|
|
|
expect(expression.getText()).toBe('foo()');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
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);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name);
|
|
|
|
const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
|
|
|
|
expect(classSymbolsPrimary.length).toEqual(2);
|
|
|
|
expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']);
|
|
|
|
|
|
|
|
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
|
|
|
|
expect(classSymbolsSecondary.length).toEqual(1);
|
|
|
|
expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']);
|
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
describe('getDecoratorsOfSymbol()', () => {
|
|
|
|
it('should return decorators of class symbol', () => {
|
|
|
|
loadTestFiles(DECORATED_FILES);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(getRootFiles(DECORATED_FILES)[0]);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-12-18 09:03:04 -05:00
|
|
|
const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name);
|
|
|
|
const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const classSymbolsPrimary = host.findClassSymbols(primaryFile);
|
|
|
|
const classDecoratorsPrimary =
|
|
|
|
classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s));
|
|
|
|
expect(classDecoratorsPrimary.length).toEqual(2);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']);
|
|
|
|
expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']);
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const classSymbolsSecondary = host.findClassSymbols(secondaryFile);
|
|
|
|
const classDecoratorsSecondary =
|
|
|
|
classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s));
|
|
|
|
expect(classDecoratorsSecondary.length).toEqual(1);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']);
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
|
fix(ngcc): do not collect private declarations from external packages (#34811)
Previously, while trying to build an `NgccReflectionHost`'s
`privateDtsDeclarationMap`, `computePrivateDtsDeclarationMap()` would
try to collect exported declarations from all source files of the
program (i.e. without checking whether they were within the target
package, as happens for declarations in `.d.ts` files).
Most of the time, that would not be a problem, because external packages
would be represented as `.d.ts` files in the program. But when an
external package had no typings, the JS files would be used instead. As
a result, the `ReflectionHost` would try to (unnecessarilly) parse the
file in order to extract exported declarations, which in turn would be
harmless in most cases.
There are certain cases, though, where the `ReflectionHost` would throw
an error, because it cannot parse the external package's JS file. This
could happen, for example, in `UmdReflectionHost`, which expects the
file to contain exactly one statement. See #34544 for more details on a
real-world failure.
This commit fixes the issue by ensuring that
`computePrivateDtsDeclarationMap()` will only collect exported
declarations from files within the target package.
Jira issue: [FW-1794](https://angular-team.atlassian.net/browse/FW-1794)
Fixes #34544
PR Close #34811
2020-01-15 14:54:36 -05:00
|
|
|
describe('getDtsDeclaration()', () => {
|
2019-06-06 15:22:32 -04:00
|
|
|
it('should find the dts declaration that has the same relative path to the source file',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const class1 = getDeclaration(
|
2019-12-18 09:03:05 -05:00
|
|
|
bundle.program, _('/ep/src/class1.js'), 'Class1', ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host = createHost(
|
|
|
|
bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(class1);
|
2020-04-03 09:37:45 -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);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/ep/src/func1.js'));
|
|
|
|
const dts = makeTestDtsBundleProgram(_('/ep/typings/func1.d.ts'), _('/'));
|
|
|
|
const mooFn = getDeclaration(
|
|
|
|
bundle.program, _('/ep/src/func1.js'), 'mooFn', ts.isFunctionDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
const dtsDeclaration = host.getDtsDeclaration(mooFn);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts'));
|
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);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/ep/src/index.js'));
|
|
|
|
const dts = makeTestDtsBundleProgram(_('/ep/typings/index.d.ts'), _('/'));
|
2019-06-06 15:22:32 -04:00
|
|
|
const missingClass = getDeclaration(
|
2019-12-18 09:03:05 -05:00
|
|
|
bundle.program, _('/ep/src/class1.js'), 'MissingClass1', ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(host.getDtsDeclaration(missingClass)).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should return null if there is no matching dts file', () => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/ep/src/index.js'));
|
|
|
|
const dts = makeTestDtsBundleProgram(_('/ep/typings/index.d.ts'), _('/'));
|
2019-06-06 15:22:32 -04:00
|
|
|
const missingClass = getDeclaration(
|
2019-12-18 09:03:05 -05:00
|
|
|
bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2',
|
2019-12-18 09:03:04 -05:00
|
|
|
ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
expect(host.getDtsDeclaration(missingClass)).toBe(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js'));
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
2019-06-06 15:22:32 -04:00
|
|
|
const class1 = getDeclaration(
|
2019-12-18 09:03:05 -05:00
|
|
|
bundle.program, _('/ep/src/flat-file.js'), 'Class1', ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host = createHost(
|
|
|
|
bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(class1);
|
2020-04-03 09:37:45 -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);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js'));
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
|
|
|
const sourceClass = getDeclaration(
|
|
|
|
bundle.program, _('/ep/src/flat-file.js'), 'SourceClass', ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
2019-12-18 09:03:05 -05:00
|
|
|
const dtsDeclaration = host.getDtsDeclaration(sourceClass);
|
|
|
|
if (dtsDeclaration === null) {
|
|
|
|
return fail('Expected dts class to be found');
|
|
|
|
}
|
|
|
|
if (!isNamedClassDeclaration(dtsDeclaration)) {
|
|
|
|
return fail('Expected a named class to be found.');
|
|
|
|
}
|
|
|
|
expect(dtsDeclaration.name.text).toEqual('TypingsClass');
|
|
|
|
expect(_(dtsDeclaration.getSourceFile().fileName))
|
|
|
|
.toEqual(_('/ep/typings/typings-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 class is not publicly exported',
|
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]);
|
2019-06-06 15:22:32 -04:00
|
|
|
const internalClass = getDeclaration(
|
2019-12-18 09:03:05 -05:00
|
|
|
bundle.program, _('/ep/src/internal.js'), 'InternalClass',
|
|
|
|
ts.isVariableDeclaration);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host = createHost(
|
|
|
|
bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
|
|
|
const dtsDeclaration = host.getDtsDeclaration(internalClass);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(dtsDeclaration!.getSourceFile().fileName)
|
2019-12-18 09:03:05 -05:00
|
|
|
.toEqual(_('/ep/typings/internal.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
|
2019-12-18 09:03:05 -05:00
|
|
|
it('should match publicly and internal exported classes correctly, even if they have the same name',
|
2019-06-06 15:22:32 -04:00
|
|
|
() => {
|
|
|
|
loadTestFiles(TYPINGS_SRC_FILES);
|
|
|
|
loadTestFiles(TYPINGS_DTS_FILES);
|
2019-12-18 09:03:05 -05:00
|
|
|
const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]);
|
|
|
|
const dts = makeTestDtsBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0], _('/ep'));
|
2020-04-03 09:37:45 -04:00
|
|
|
const host = createHost(
|
|
|
|
bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle, dts));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
2019-12-18 09:03:05 -05:00
|
|
|
const class2 = getDeclaration(
|
|
|
|
bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const class2DtsDeclaration = host.getDtsDeclaration(class2);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(class2DtsDeclaration!.getSourceFile().fileName)
|
2019-12-18 09:03:05 -05:00
|
|
|
.toEqual(_('/ep/typings/class2.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
|
2019-12-18 09:03:05 -05:00
|
|
|
const internalClass2 = getDeclaration(
|
|
|
|
bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedVariableDeclaration);
|
2019-06-06 15:22:32 -04:00
|
|
|
const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2);
|
2020-04-03 09:37:45 -04:00
|
|
|
expect(internalClass2DtsDeclaration!.getSourceFile().fileName)
|
2019-12-18 09:03:05 -05:00
|
|
|
.toEqual(_('/ep/typings/internal.d.ts'));
|
2019-06-06 15:22:32 -04:00
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
|
2019-11-01 12:55:10 -04:00
|
|
|
describe('getInternalNameOfClass()', () => {
|
|
|
|
it('should return the name of the inner class declaration', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-11-01 12:55:10 -04:00
|
|
|
|
|
|
|
const emptyClass = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass');
|
|
|
|
|
|
|
|
const class1 = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1');
|
|
|
|
|
|
|
|
const class2 = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2');
|
|
|
|
|
|
|
|
const childClass = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getAdjacentNameOfClass()', () => {
|
|
|
|
it('should return the name of the inner class declaration', () => {
|
|
|
|
loadTestFiles([SIMPLE_CLASS_FILE]);
|
2019-12-18 09:03:04 -05:00
|
|
|
const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name);
|
2020-04-03 09:37:45 -04:00
|
|
|
const host =
|
|
|
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
2019-11-01 12:55:10 -04:00
|
|
|
|
|
|
|
const emptyClass = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass');
|
|
|
|
|
|
|
|
const class1 = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1');
|
|
|
|
|
|
|
|
const class2 = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2');
|
|
|
|
|
|
|
|
const childClass = getDeclaration(
|
2019-12-18 09:03:04 -05:00
|
|
|
bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration);
|
2019-11-01 12:55:10 -04:00
|
|
|
expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass');
|
|
|
|
});
|
|
|
|
});
|
2019-04-29 04:54:30 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|