Previously, ngtsc would assume that a given directive/pipe being imported from an external package was importable using the same name by which it was declared. This isn't always true; sometimes a package will export a directive under a different name. For example, Angular frequently prefixes directive names with the 'ɵ' character to indicate that they're part of the package's private API, and not for public consumption. This commit introduces the TsReferenceResolver class which, given a declaration to import and a module name to import it from, can determine the exported name of the declared class within the module. This allows ngtsc to pick the correct name by which to import the class instead of making assumptions about how it was exported. This resolver is used to select a correct symbol name when creating an AbsoluteReference. FW-517 #resolve FW-536 #resolve PR Close #27743
71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
|
import {TsReferenceResolver} from '../../imports';
|
|
import {PartialEvaluator} from '../../partial_evaluator';
|
|
import {TypeScriptReflectionHost} from '../../reflection';
|
|
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
|
import {ResourceLoader} from '../src/api';
|
|
import {ComponentDecoratorHandler} from '../src/component';
|
|
import {SelectorScopeRegistry} from '../src/selector_scope';
|
|
|
|
export class NoopResourceLoader implements ResourceLoader {
|
|
load(url: string): string { throw new Error('Not implemented'); }
|
|
}
|
|
|
|
describe('ComponentDecoratorHandler', () => {
|
|
it('should produce a diagnostic when @Component has non-literal argument', () => {
|
|
const {program, options, host} = makeProgram([
|
|
{
|
|
name: 'node_modules/@angular/core/index.d.ts',
|
|
contents: 'export const Component: any;',
|
|
},
|
|
{
|
|
name: 'entry.ts',
|
|
contents: `
|
|
import {Component} from '@angular/core';
|
|
|
|
const TEST = '';
|
|
@Component(TEST) class TestCmp {}
|
|
`
|
|
},
|
|
]);
|
|
const checker = program.getTypeChecker();
|
|
const reflectionHost = new TypeScriptReflectionHost(checker);
|
|
const resolver = new TsReferenceResolver(program, checker, options, host);
|
|
const evaluator = new PartialEvaluator(reflectionHost, checker, resolver);
|
|
const handler = new ComponentDecoratorHandler(
|
|
reflectionHost, evaluator, new SelectorScopeRegistry(checker, reflectionHost, resolver),
|
|
false, new NoopResourceLoader(), [''], false, true);
|
|
const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', ts.isClassDeclaration);
|
|
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
|
if (detected === undefined) {
|
|
return fail('Failed to recognize @Component');
|
|
}
|
|
try {
|
|
handler.analyze(TestCmp, detected);
|
|
return fail('Analysis should have failed');
|
|
} catch (err) {
|
|
if (!(err instanceof FatalDiagnosticError)) {
|
|
return fail('Error should be a FatalDiagnosticError');
|
|
}
|
|
const diag = err.toDiagnostic();
|
|
expect(diag.code).toEqual(ivyCode(ErrorCode.DECORATOR_ARG_NOT_LITERAL));
|
|
expect(diag.file.fileName.endsWith('entry.ts')).toBe(true);
|
|
expect(diag.start).toBe(detected.args ![0].getStart());
|
|
}
|
|
});
|
|
});
|
|
|
|
function ivyCode(code: ErrorCode): number {
|
|
return Number('-99' + code.valueOf());
|
|
}
|