fix(compiler): Report references to non-exported symbols.

Includes fixes to places now reported as errors.

Part of #8310
This commit is contained in:
Chuck Jazdzewski 2016-08-02 14:38:31 -07:00
parent 6195a45ae2
commit 9925aa89dc
6 changed files with 57 additions and 5 deletions

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core'; import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
import {MapWrapper} from '../../facade/collection'; import {MapWrapper} from '../../facade/collection';
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../../facade/lang'; import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
const SELECT_MULTIPLE_VALUE_ACCESSOR = { export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor), useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true multi: true

View File

@ -35,7 +35,7 @@ import {NG_VALIDATORS, Validators} from '../validators';
*/ */
export interface Validator { validate(c: AbstractControl): {[key: string]: any}; } export interface Validator { validate(c: AbstractControl): {[key: string]: any}; }
const REQUIRED = Validators.required; export const REQUIRED = Validators.required;
export const REQUIRED_VALIDATOR: any = { export const REQUIRED_VALIDATOR: any = {
provide: NG_VALIDATORS, provide: NG_VALIDATORS,

View File

@ -594,6 +594,10 @@ function expandedMessage(error: any): string {
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F'; error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
return prefix + return prefix +
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function'; 'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
case 'Reference to a local symbol':
if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
}
} }
return error.message; return error.message;
} }

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core'; import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
import {MapWrapper} from '../facade/collection'; import {MapWrapper} from '../facade/collection';
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../facade/lang'; import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
const SELECT_MULTIPLE_VALUE_ACCESSOR = { export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor), useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true multi: true

View File

@ -297,13 +297,19 @@ export class MetadataCollector {
} else { } else {
varValue = errorSym('Variable not initialized', nameNode); varValue = errorSym('Variable not initialized', nameNode);
} }
let exported = false;
if (variableStatement.flags & ts.NodeFlags.Export || if (variableStatement.flags & ts.NodeFlags.Export ||
variableDeclaration.flags & ts.NodeFlags.Export) { variableDeclaration.flags & ts.NodeFlags.Export) {
if (!metadata) metadata = {}; if (!metadata) metadata = {};
metadata[nameNode.text] = varValue; metadata[nameNode.text] = varValue;
exported = true;
} }
if (isPrimitive(varValue)) { if (isPrimitive(varValue)) {
locals.define(nameNode.text, varValue); locals.define(nameNode.text, varValue);
} else if (!exported) {
locals.define(
nameNode.text,
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}));
} }
} else { } else {
// Destructuring (or binding) declarations are not supported, // Destructuring (or binding) declarations are not supported,

View File

@ -24,6 +24,7 @@ describe('Collector', () => {
'exported-functions.ts', 'exported-functions.ts',
'exported-enum.ts', 'exported-enum.ts',
'exported-consts.ts', 'exported-consts.ts',
'local-symbol-ref.ts',
're-exports.ts', 're-exports.ts',
'static-field-reference.ts', 'static-field-reference.ts',
'static-method.ts', 'static-method.ts',
@ -486,6 +487,28 @@ describe('Collector', () => {
{from: 'angular2/core'} {from: 'angular2/core'}
]); ]);
}); });
it('should collect an error symbol if collecting a reference to a non-exported symbol', () => {
let source = program.getSourceFile('/local-symbol-ref.ts');
let metadata = collector.getMetadata(source);
expect(metadata.metadata).toEqual({
REQUIRED_VALIDATOR: {
__symbolic: 'error',
message: 'Reference to a local symbol',
line: 3,
character: 9,
context: {name: 'REQUIRED'}
},
SomeComponent: {
__symbolic: 'class',
decorators: [{
__symbolic: 'call',
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
}]
}
});
});
}); });
// TODO: Do not use \` in a template literal as it confuses clang-format // TODO: Do not use \` in a template literal as it confuses clang-format
@ -799,6 +822,22 @@ const FILES: Directory = {
export {Foo as OtherModule} from './static-field-reference.ts'; export {Foo as OtherModule} from './static-field-reference.ts';
export * from 'angular2/core'; export * from 'angular2/core';
`, `,
'local-symbol-ref.ts': `
import {Component, Validators} from 'angular2/core';
const REQUIRED = Validators.required;
export const REQUIRED_VALIDATOR: any = {
provide: 'SomeToken',
useValue: REQUIRED,
multi: true
};
@Component({
providers: [REQUIRED_VALIDATOR]
})
export class SomeComponent {}
`,
'node_modules': { 'node_modules': {
'angular2': { 'angular2': {
'core.d.ts': ` 'core.d.ts': `
@ -849,6 +888,9 @@ const FILES: Directory = {
export interface OnInit { export interface OnInit {
ngOnInit(): any; ngOnInit(): any;
} }
export class Validators {
static required(): void;
}
`, `,
'common.d.ts': ` 'common.d.ts': `
export declare class NgFor { export declare class NgFor {