Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 goldens/public-api/compiler-cli/error_code.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum ErrorCode {
DUPLICATE_DECORATED_PROPERTIES = 1012,
DUPLICATE_VARIABLE_DECLARATION = 8006,
FORBIDDEN_REQUIRED_INITIALIZER_INVOCATION = 8118,
FOREIGN_COMPONENT_UNSUPPORTED_BINDING = 8025,
FORM_FIELD_UNSUPPORTED_BINDING = 8022,
HOST_BINDING_PARSE_ERROR = 5001,
HOST_DIRECTIVE_COMPONENT = 2015,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import {
PipeMeta,
Resource,
ResourceRegistry,
createForeignComponentMatcher,
} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../../perf';
Expand Down Expand Up @@ -180,6 +181,7 @@ import {
ComponentAnalysisData,
ComponentResolutionData,
DeferredComponentDependency,
ForeignComponentMeta,
} from './metadata';
import {
_extractTemplateStyleUrls,
Expand Down Expand Up @@ -607,7 +609,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
}

let resolvedImports: Reference<ClassDeclaration>[] | null = null;
let foreignImports: Reference<ClassDeclaration>[] | null = null;
let foreignImports: ForeignComponentMeta[] | null = null;
let resolvedDeferredImports: Reference<ClassDeclaration>[] | null = null;

let rawImports: ts.Expression | null = component.get('imports') ?? null;
Expand Down Expand Up @@ -1206,7 +1208,10 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
return;
}

const binder = new R3TargetBinder<TypeCheckableDirectiveMeta>(scope.matcher);
const binder = new R3TargetBinder<TypeCheckableDirectiveMeta>(
scope.matcher,
scope.foreignMatcher,
);
const templateContext: TemplateContext = {
nodes: meta.template.diagNodes,
pipes: scope.pipes,
Expand Down Expand Up @@ -1810,7 +1815,10 @@ export class ComponentDecoratorHandler implements DecoratorHandler<
}

// Set up the R3TargetBinder.
const binder = new R3TargetBinder(createMatcherFromScope(scope, this.hostDirectivesResolver));
const binder = new R3TargetBinder(
createMatcherFromScope(scope, this.hostDirectivesResolver),
createForeignComponentMatcher(analysis.foreignImports),
);
let allDependencies = dependencies;
let deferBlockBinder = binder;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ import {
SchemaMetadata,
TmplAstDeferredBlock,
ClassPropertyMapping,
SelectorlessMatcher,
} from '@angular/compiler';
import ts from 'typescript';

import {Reference} from '../../../imports';
import {
DirectiveResources,
DirectiveTypeCheckMeta,
ForeignComponentMeta,
HostDirectiveMeta,
InputMapping,
} from '../../../metadata';
export {ForeignComponentMeta} from '../../../metadata';
import {ClassDeclaration, Import} from '../../../reflection';
import {SubsetOfKeys} from '../../../util/src/typescript';

Expand Down Expand Up @@ -92,7 +95,7 @@ export interface ComponentAnalysisData {

rawImports: ts.Expression | null;
resolvedImports: Reference<ClassDeclaration>[] | null;
foreignImports: Reference<ClassDeclaration>[] | null;
foreignImports: ForeignComponentMeta[] | null;
rawDeferredImports: ts.Expression | null;
resolvedDeferredImports: Reference<ClassDeclaration>[] | null;

Expand Down
13 changes: 10 additions & 3 deletions 13 packages/compiler-cli/src/ngtsc/annotations/component/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
isNamedFunctionDeclaration,
} from '../../../reflection';
import {createValueHasWrongTypeError, getOriginNodeForDiagnostics} from '../../common';
import {ForeignComponentMeta} from './metadata';

/**
* Collect the animation names from the static evaluation result.
Expand Down Expand Up @@ -167,10 +168,10 @@ export function validateAndFlattenForeignImports(
imports: ResolvedValue,
expr: ts.Expression,
): {
foreignImports: Reference<ClassDeclaration>[];
foreignImports: ForeignComponentMeta[];
diagnostics: ts.Diagnostic[];
} {
const flattened: Reference<ClassDeclaration>[] = [];
const flattened: ForeignComponentMeta[] = [];
const errorMessage = `'foreignImports' must be an array of ForeignComponents.`;

if (!Array.isArray(imports)) {
Expand Down Expand Up @@ -200,7 +201,13 @@ export function validateAndFlattenForeignImports(
flattened.push(...childForeignImports);
diagnostics.push(...childDiagnostics);
} else if (ref instanceof Reference && isNamedFunctionDeclaration(ref.node)) {
flattened.push(ref as Reference<ClassDeclaration>);
// Use the local identity if available to account for import aliases.
const name = ref.getIdentityInExpression(refExpr)?.text ?? ref.node.name.getText();
flattened.push({
name,
ref: ref as Reference<ClassDeclaration>,
rawExpression: refExpr,
});
} else {
const {node: diagnosticNode, value: diagnosticValue} = getDiagnosticOrigin(
ref,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ runInEachFileSystem(() => {
import {foreignImport} from '@angular/core/src/render3/foreign_import';

function FancyButton() {}
function FancyMenu() {}

function frameworkImport(component: unknown) {
return foreignImport(() => {/* render component */});
Expand All @@ -1072,7 +1073,10 @@ runInEachFileSystem(() => {
@Component({
selector: 'main',
template: '',
foreignImports: [frameworkImport(FancyButton)],
foreignImports: [
frameworkImport(FancyButton),
frameworkImport(FancyMenu),
],
}) class TestCmp {}
`,
},
Expand All @@ -1089,8 +1093,66 @@ runInEachFileSystem(() => {
const {analysis, diagnostics} = handler.analyze(TestCmp, detected.metadata);

expect(diagnostics).toBeUndefined();
expect(analysis?.foreignImports?.length).toBe(1);
expect(analysis?.foreignImports![0].node.name.text).toBe('FancyButton');
expect(analysis?.foreignImports).toHaveSize(2);
expect(analysis!.foreignImports![0].name).toBe('FancyButton');
expect(analysis!.foreignImports![1].name).toBe('FancyMenu');
});

it('should support import aliases in foreignImports', () => {
const {program, options, host} = makeProgram([
{
name: _('/node_modules/@angular/core/index.d.ts'),
contents: `
export const Component: any;
`,
},
{
name: _('/node_modules/@angular/core/src/render3/foreign_import.ts'),
contents: `
export function foreignImport(render: any): any {}
`,
},
{
name: _('/original.ts'),
contents: `
export function Original() {}
`,
},
{
name: _('/entry.ts'),
contents: `
import {Component} from '@angular/core';
import {foreignImport} from '@angular/core/src/render3/foreign_import';
import {Original as Alias} from './original';

function frameworkImport(component: unknown) {
return foreignImport(() => {/* render component */});
}

@Component({
selector: 'main',
template: '',
foreignImports: [
frameworkImport(Alias),
],
}) class TestCmp {}
`,
},
]);
const {reflectionHost, handler} = setup(program, options, host);
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
const detected = handler.detect(
TestCmp,
reflectionHost.getDecoratorsOfDeclaration(TestCmp),
);
if (detected === undefined) {
return fail('Failed to recognize @Component');
}
const {analysis, diagnostics} = handler.analyze(TestCmp, detected.metadata);

expect(diagnostics).toBeUndefined();
expect(analysis?.foreignImports).toHaveSize(1);
expect(analysis!.foreignImports![0].name).toBe('Alias');
});

it('should produce diagnostic for imports in non-standalone component', () => {
Expand Down
5 changes: 5 additions & 0 deletions 5 packages/compiler-cli/src/ngtsc/diagnostics/src/error_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,11 @@ export enum ErrorCode {
*/
CONFLICTING_HOST_DIRECTIVE_BINDING = -8024,

/**
* Raised when a foreign component node has an unsupported Angular binding.
*/
FOREIGN_COMPONENT_UNSUPPORTED_BINDING = 8025,

/**
* A two way binding in a template has an incorrect syntax,
* parentheses outside brackets. For example:
Expand Down
1 change: 1 addition & 0 deletions 1 packages/compiler-cli/src/ngtsc/metadata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export {
hasInjectableFields,
CompoundMetadataReader,
isHostDirectiveMetaForGlobalMode,
createForeignComponentMatcher,
} from './src/util';
export {ExportedProviderStatusResolver} from './src/providers';
export {HostDirectivesResolver} from './src/host_directives_resolver';
9 changes: 8 additions & 1 deletion 9 packages/compiler-cli/src/ngtsc/metadata/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@ import {
InputOrOutput,
ClassPropertyMapping,
TemplateGuardMeta,
ForeignComponentMeta as T2ForeignComponentMeta,
} from '@angular/compiler';
import ts from 'typescript';

import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';

/** Metadata for a resolved foreign component import. */
export interface ForeignComponentMeta extends T2ForeignComponentMeta {
ref: Reference<ClassDeclaration>;
rawExpression: ts.Expression;
}

/**
* Metadata collected for an `NgModule`.
*/
Expand Down Expand Up @@ -256,7 +263,7 @@ export interface DirectiveMeta extends T2DirectiveMeta, DirectiveTypeCheckMeta {
* Note that while a foreign import is not likely to be a class, this type is used
* because it includes the expected identifier we'll need, making further code simpler.
*/
foreignImports: Reference<ClassDeclaration>[] | null;
foreignImports: ForeignComponentMeta[] | null;

/**
* Node declaring the `imports` of a standalone component. Used to produce diagnostics.
Expand Down
22 changes: 21 additions & 1 deletion 22 packages/compiler-cli/src/ngtsc/metadata/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@ import {
MetadataReader,
NgModuleMeta,
PipeMeta,
ForeignComponentMeta,
} from './api';
import {TypeEntityToDeclarationError} from '../../reflection/src/typescript';
import {ClassPropertyMapping, ClassPropertyName, TemplateGuardMeta} from '@angular/compiler';
import {
ClassPropertyMapping,
ClassPropertyName,
TemplateGuardMeta,
SelectorlessMatcher,
} from '@angular/compiler';

export function extractReferencesFromType(
checker: ts.TypeChecker,
Expand Down Expand Up @@ -355,3 +361,17 @@ export function isHostDirectiveMetaForGlobalMode(
): hostDirectiveMeta is HostDirectiveMetaForGlobalMode {
return hostDirectiveMeta.directive instanceof Reference;
}

/** Extracts foreign component names from foreignImports and creates a SelectorlessMatcher. */
export function createForeignComponentMatcher(
foreignImports: ForeignComponentMeta[] | null,
): SelectorlessMatcher<ForeignComponentMeta> | null {
if (foreignImports === null || foreignImports.length === 0) {
return null;
}
const registry = new Map<string, ForeignComponentMeta[]>();
for (const meta of foreignImports) {
registry.set(meta.name, [meta]);
}
return new SelectorlessMatcher(registry);
}
12 changes: 12 additions & 0 deletions 12 packages/compiler-cli/src/ngtsc/scope/src/typecheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SchemaMetadata,
SelectorlessMatcher,
SelectorMatcher,
ForeignComponentMeta,
} from '@angular/compiler';

import {Reference} from '../../imports';
Expand All @@ -23,6 +24,7 @@ import {
MetaKind,
NgModuleMeta,
PipeMeta,
createForeignComponentMatcher,
} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {ComponentScopeKind, ComponentScopeReader, SelectorlessScope} from './api';
Expand All @@ -37,6 +39,11 @@ export interface TypeCheckScope {
*/
matcher: DirectiveMatcher<DirectiveMeta> | null;

/**
* A `SelectorlessMatcher` instance that contains matched foreign components.
*/
foreignMatcher: SelectorlessMatcher<ForeignComponentMeta> | null;

/**
* All of the directives available in the compilation scope of the declaring NgModule.
*/
Expand Down Expand Up @@ -100,6 +107,7 @@ export class TypeCheckScopeRegistry {
if (scope === null) {
return {
matcher: null,
foreignMatcher: null,
directives,
pipes,
schemas: [],
Expand Down Expand Up @@ -152,8 +160,12 @@ export class TypeCheckScopeRegistry {
}
}

const foreignMatcher =
hostMeta !== null ? createForeignComponentMatcher(hostMeta.foreignImports) : null;

const typeCheckScope: TypeCheckScope = {
matcher,
foreignMatcher,
directives,
pipes,
schemas: scope.schemas,
Expand Down
9 changes: 9 additions & 0 deletions 9 packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {
AST,
ForeignComponentMeta,
LiteralPrimitive,
ParseSourceSpan,
PropertyRead,
Expand Down Expand Up @@ -333,6 +334,14 @@ export interface TemplateTypeChecker {
node: TmplAstElement | TmplAstTemplate,
): TypeCheckableDirectiveMeta[] | null;

/**
* Gets the foreign component that matched the given template element.
*/
getForeignComponent(
component: ts.ClassDeclaration,
element: TmplAstElement,
): ForeignComponentMeta | null;

/**
* Gets the directives that have been used in a component's template.
*/
Expand Down
10 changes: 10 additions & 0 deletions 10 packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {
AST,
BoundTarget,
ForeignComponentMeta,
CssSelector,
DomElementSchemaRegistry,
ExternalExpr,
Expand Down Expand Up @@ -469,6 +470,15 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
);
}

getForeignComponent(
component: ts.ClassDeclaration,
element: TmplAstElement,
): ForeignComponentMeta | null {
return (
this.getLatestComponentState(component).data?.boundTarget.getForeignComponent(element) ?? null
);
}

getUsedDirectives(component: ts.ClassDeclaration): TypeCheckableDirectiveMeta[] | null {
return this.getLatestComponentState(component).data?.boundTarget.getUsedDirectives() ?? null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export function adaptTypeCheckBlockMetadata(
const dirs = meta.boundTarget.getDirectivesOfNode(node);
return dirs ? dirs.map(convertDir) : null;
},
getForeignComponent: (element) => meta.boundTarget.getForeignComponent(element),
getReferenceTarget: (ref) => {
const target = meta.boundTarget.getReferenceTarget(ref);
if (target && 'directive' in target) {
Expand Down
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.