refactor(compiler-cli): identify structural directives (#40032)
This commit introduces an `isStructural` flag on directive metadata, which is `true` if the directive injects `TemplateRef` (and thus is at least theoretically usable as a structural directive). The flag is not used for anything currently, but will be utilized by the Language Service to offer better autocompletion results for structural directives. PR Close #40032
This commit is contained in:
parent
cbb6eae4a9
commit
c55bf4a4a3
|
@ -396,6 +396,7 @@ export class ComponentDecoratorHandler implements
|
||||||
baseClass: analysis.baseClass,
|
baseClass: analysis.baseClass,
|
||||||
...analysis.typeCheckMeta,
|
...analysis.typeCheckMeta,
|
||||||
isPoisoned: analysis.isPoisoned,
|
isPoisoned: analysis.isPoisoned,
|
||||||
|
isStructural: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.resourceRegistry.registerResources(analysis.resources, node);
|
this.resourceRegistry.registerResources(analysis.resources, node);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveDef, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveDef, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, R3ResolvedDependencyType, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
|
@ -42,6 +42,7 @@ export interface DirectiveHandlerData {
|
||||||
inputs: ClassPropertyMapping;
|
inputs: ClassPropertyMapping;
|
||||||
outputs: ClassPropertyMapping;
|
outputs: ClassPropertyMapping;
|
||||||
isPoisoned: boolean;
|
isPoisoned: boolean;
|
||||||
|
isStructural: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectiveDecoratorHandler implements
|
export class DirectiveDecoratorHandler implements
|
||||||
|
@ -109,6 +110,7 @@ export class DirectiveDecoratorHandler implements
|
||||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
|
typeCheckMeta: extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
|
||||||
providersRequiringFactory,
|
providersRequiringFactory,
|
||||||
isPoisoned: false,
|
isPoisoned: false,
|
||||||
|
isStructural: directiveResult.isStructural,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -129,6 +131,7 @@ export class DirectiveDecoratorHandler implements
|
||||||
baseClass: analysis.baseClass,
|
baseClass: analysis.baseClass,
|
||||||
...analysis.typeCheckMeta,
|
...analysis.typeCheckMeta,
|
||||||
isPoisoned: analysis.isPoisoned,
|
isPoisoned: analysis.isPoisoned,
|
||||||
|
isStructural: analysis.isStructural,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.injectableRegistry.registerInjectable(node);
|
this.injectableRegistry.registerInjectable(node);
|
||||||
|
@ -226,6 +229,7 @@ export function extractDirectiveMetadata(
|
||||||
metadata: R3DirectiveMetadata,
|
metadata: R3DirectiveMetadata,
|
||||||
inputs: ClassPropertyMapping,
|
inputs: ClassPropertyMapping,
|
||||||
outputs: ClassPropertyMapping,
|
outputs: ClassPropertyMapping,
|
||||||
|
isStructural: boolean;
|
||||||
}|undefined {
|
}|undefined {
|
||||||
let directive: Map<string, ts.Expression>;
|
let directive: Map<string, ts.Expression>;
|
||||||
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
||||||
|
@ -352,6 +356,17 @@ export function extractDirectiveMetadata(
|
||||||
ctorDeps = unwrapConstructorDependencies(rawCtorDeps);
|
ctorDeps = unwrapConstructorDependencies(rawCtorDeps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isStructural = ctorDeps !== null && ctorDeps !== 'invalid' && ctorDeps.some(dep => {
|
||||||
|
if (dep.resolved !== R3ResolvedDependencyType.Token || !(dep.token instanceof ExternalExpr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dep.token.value.moduleName !== '@angular/core' || dep.token.value.name !== 'TemplateRef') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// Detect if the component inherits from another class
|
// Detect if the component inherits from another class
|
||||||
const usesInheritance = reflector.hasBaseClass(clazz);
|
const usesInheritance = reflector.hasBaseClass(clazz);
|
||||||
const type = wrapTypeReference(reflector, clazz);
|
const type = wrapTypeReference(reflector, clazz);
|
||||||
|
@ -386,6 +401,7 @@ export function extractDirectiveMetadata(
|
||||||
metadata,
|
metadata,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
|
isStructural,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ runInEachFileSystem(() => {
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
name: 'Dir',
|
name: 'Dir',
|
||||||
selector: '[dir]',
|
selector: '[dir]',
|
||||||
|
isStructural: false,
|
||||||
};
|
};
|
||||||
matcher.addSelectables(CssSelector.parse('[dir]'), dirMeta);
|
matcher.addSelectables(CssSelector.parse('[dir]'), dirMeta);
|
||||||
|
|
||||||
|
@ -118,6 +119,30 @@ runInEachFileSystem(() => {
|
||||||
// and field names.
|
// and field names.
|
||||||
expect(propBindingConsumer).toBe(dirMeta);
|
expect(propBindingConsumer).toBe(dirMeta);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should identify a structural directive', () => {
|
||||||
|
const src = `
|
||||||
|
import {Directive, TemplateRef} from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: 'test-dir'})
|
||||||
|
export class TestDir {
|
||||||
|
constructor(private ref: TemplateRef) {}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const {program} = makeProgram([
|
||||||
|
{
|
||||||
|
name: _('/node_modules/@angular/core/index.d.ts'),
|
||||||
|
contents: 'export const Directive: any; export declare class TemplateRef {}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: _('/entry.ts'),
|
||||||
|
contents: src,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const analysis = analyzeDirective(program, 'TestDir');
|
||||||
|
expect(analysis.isStructural).toBeTrue();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
|
@ -54,6 +54,7 @@ export function getBoundTemplate(
|
||||||
inputs: ClassPropertyMapping.fromMappedObject({}),
|
inputs: ClassPropertyMapping.fromMappedObject({}),
|
||||||
outputs: ClassPropertyMapping.fromMappedObject({}),
|
outputs: ClassPropertyMapping.fromMappedObject({}),
|
||||||
exportAs: null,
|
exportAs: null,
|
||||||
|
isStructural: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const binder = new R3TargetBinder(matcher);
|
const binder = new R3TargetBinder(matcher);
|
||||||
|
|
|
@ -114,6 +114,11 @@ export interface DirectiveMeta extends T2DirectiveMeta, DirectiveTypeCheckMeta {
|
||||||
* and reliable metadata.
|
* and reliable metadata.
|
||||||
*/
|
*/
|
||||||
isPoisoned: boolean;
|
isPoisoned: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the directive is likely a structural directive (injects `TemplateRef`).
|
||||||
|
*/
|
||||||
|
isStructural: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Reference} from '../../imports';
|
import {Reference} from '../../imports';
|
||||||
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../reflection';
|
import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost, TypeValueReferenceKind} from '../../reflection';
|
||||||
|
|
||||||
import {DirectiveMeta, MetadataReader, NgModuleMeta, PipeMeta} from './api';
|
import {DirectiveMeta, MetadataReader, NgModuleMeta, PipeMeta} from './api';
|
||||||
import {ClassPropertyMapping} from './property_mapping';
|
import {ClassPropertyMapping} from './property_mapping';
|
||||||
|
@ -77,6 +77,19 @@ export class DtsMetadataReader implements MetadataReader {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isComponent = def.name === 'ɵcmp';
|
||||||
|
|
||||||
|
const ctorParams = this.reflector.getConstructorParameters(clazz);
|
||||||
|
|
||||||
|
// A directive is considered to be structural if:
|
||||||
|
// 1) it's a directive, not a component, and
|
||||||
|
// 2) it injects `TemplateRef`
|
||||||
|
const isStructural = !isComponent && ctorParams !== null && ctorParams.some(param => {
|
||||||
|
return param.typeValueReference.kind === TypeValueReferenceKind.IMPORTED &&
|
||||||
|
param.typeValueReference.moduleName === '@angular/core' &&
|
||||||
|
param.typeValueReference.importedName === 'TemplateRef';
|
||||||
|
});
|
||||||
|
|
||||||
const inputs =
|
const inputs =
|
||||||
ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[3]));
|
ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[3]));
|
||||||
const outputs =
|
const outputs =
|
||||||
|
@ -84,7 +97,7 @@ export class DtsMetadataReader implements MetadataReader {
|
||||||
return {
|
return {
|
||||||
ref,
|
ref,
|
||||||
name: clazz.name.text,
|
name: clazz.name.text,
|
||||||
isComponent: def.name === 'ɵcmp',
|
isComponent,
|
||||||
selector: readStringType(def.type.typeArguments[1]),
|
selector: readStringType(def.type.typeArguments[1]),
|
||||||
exportAs: readStringArrayType(def.type.typeArguments[2]),
|
exportAs: readStringArrayType(def.type.typeArguments[2]),
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -93,6 +106,7 @@ export class DtsMetadataReader implements MetadataReader {
|
||||||
...extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector),
|
...extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector),
|
||||||
baseClass: readBaseClass(clazz, this.checker, this.reflector),
|
baseClass: readBaseClass(clazz, this.checker, this.reflector),
|
||||||
isPoisoned: false,
|
isPoisoned: false,
|
||||||
|
isStructural,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ export function flattenInheritedDirectiveMetadata(
|
||||||
let isDynamic = false;
|
let isDynamic = false;
|
||||||
let inputs = ClassPropertyMapping.empty();
|
let inputs = ClassPropertyMapping.empty();
|
||||||
let outputs = ClassPropertyMapping.empty();
|
let outputs = ClassPropertyMapping.empty();
|
||||||
|
let isStructural: boolean = false;
|
||||||
|
|
||||||
const addMetadata = (meta: DirectiveMeta): void => {
|
const addMetadata = (meta: DirectiveMeta): void => {
|
||||||
if (meta.baseClass === 'dynamic') {
|
if (meta.baseClass === 'dynamic') {
|
||||||
|
@ -51,6 +52,8 @@ export function flattenInheritedDirectiveMetadata(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isStructural = isStructural || meta.isStructural;
|
||||||
|
|
||||||
inputs = ClassPropertyMapping.merge(inputs, meta.inputs);
|
inputs = ClassPropertyMapping.merge(inputs, meta.inputs);
|
||||||
outputs = ClassPropertyMapping.merge(outputs, meta.outputs);
|
outputs = ClassPropertyMapping.merge(outputs, meta.outputs);
|
||||||
|
|
||||||
|
@ -79,5 +82,6 @@ export function flattenInheritedDirectiveMetadata(
|
||||||
restrictedInputFields,
|
restrictedInputFields,
|
||||||
stringLiteralInputFields,
|
stringLiteralInputFields,
|
||||||
baseClass: isDynamic ? 'dynamic' : null,
|
baseClass: isDynamic ? 'dynamic' : null,
|
||||||
|
isStructural,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "test_lib",
|
||||||
|
testonly = True,
|
||||||
|
srcs = glob([
|
||||||
|
"**/*.ts",
|
||||||
|
]),
|
||||||
|
deps =
|
||||||
|
[
|
||||||
|
"//packages:types",
|
||||||
|
"//packages/compiler",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/testing",
|
||||||
|
"@npm//typescript",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
jasmine_node_test(
|
||||||
|
name = "test",
|
||||||
|
bootstrap = ["//tools/testing:node_no_angular_es5"],
|
||||||
|
data = [
|
||||||
|
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||||
|
],
|
||||||
|
deps =
|
||||||
|
[
|
||||||
|
":test_lib",
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC 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 {ExternalExpr} from '@angular/compiler';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||||
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
|
import {Reference} from '../../imports';
|
||||||
|
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
|
import {loadFakeCore, makeProgram} from '../../testing';
|
||||||
|
|
||||||
|
import {DtsMetadataReader} from '../src/dts';
|
||||||
|
|
||||||
|
runInEachFileSystem(() => {
|
||||||
|
beforeEach(() => {
|
||||||
|
loadFakeCore(getFileSystem());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DtsMetadataReader', () => {
|
||||||
|
it('should not assume directives are structural', () => {
|
||||||
|
const mainPath = absoluteFrom('/main.d.ts');
|
||||||
|
const {program} = makeProgram(
|
||||||
|
[{
|
||||||
|
name: mainPath,
|
||||||
|
contents: `
|
||||||
|
import {ViewContainerRef} from '@angular/core';
|
||||||
|
import * as i0 from '@angular/core';
|
||||||
|
|
||||||
|
export declare class TestDir {
|
||||||
|
constructor(p0: ViewContainerRef);
|
||||||
|
static ɵdir: i0.ɵɵDirectiveDefWithMeta<TestDir, "[test]", never, {}, {}, never>
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}],
|
||||||
|
{
|
||||||
|
skipLibCheck: true,
|
||||||
|
lib: ['es6', 'dom'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const sf = getSourceFileOrError(program, mainPath);
|
||||||
|
const clazz = sf.statements[2];
|
||||||
|
if (!isNamedClassDeclaration(clazz)) {
|
||||||
|
return fail('Expected class declaration');
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeChecker = program.getTypeChecker();
|
||||||
|
const dtsReader =
|
||||||
|
new DtsMetadataReader(typeChecker, new TypeScriptReflectionHost(typeChecker));
|
||||||
|
|
||||||
|
const meta = dtsReader.getDirectiveMetadata(new Reference(clazz))!;
|
||||||
|
expect(meta.isStructural).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should identify a structural directive by its constructor', () => {
|
||||||
|
const mainPath = absoluteFrom('/main.d.ts');
|
||||||
|
const {program} = makeProgram(
|
||||||
|
[{
|
||||||
|
name: mainPath,
|
||||||
|
contents: `
|
||||||
|
import {TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
import * as i0 from '@angular/core';
|
||||||
|
|
||||||
|
export declare class TestDir {
|
||||||
|
constructor(p0: ViewContainerRef, p1: TemplateRef);
|
||||||
|
static ɵdir: i0.ɵɵDirectiveDefWithMeta<TestDir, "[test]", never, {}, {}, never>
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}],
|
||||||
|
{
|
||||||
|
skipLibCheck: true,
|
||||||
|
lib: ['es6', 'dom'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const sf = getSourceFileOrError(program, mainPath);
|
||||||
|
const clazz = sf.statements[2];
|
||||||
|
if (!isNamedClassDeclaration(clazz)) {
|
||||||
|
return fail('Expected class declaration');
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeChecker = program.getTypeChecker();
|
||||||
|
const dtsReader =
|
||||||
|
new DtsMetadataReader(typeChecker, new TypeScriptReflectionHost(typeChecker));
|
||||||
|
|
||||||
|
const meta = dtsReader.getDirectiveMetadata(new Reference(clazz))!;
|
||||||
|
expect(meta.isStructural).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -36,12 +36,14 @@ export class TypeScriptReflectionHost implements ReflectionHost {
|
||||||
getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null {
|
getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null {
|
||||||
const tsClazz = castDeclarationToClassOrDie(clazz);
|
const tsClazz = castDeclarationToClassOrDie(clazz);
|
||||||
|
|
||||||
// First, find the constructor with a `body`. The constructors without a `body` are overloads
|
const isDeclaration = tsClazz.getSourceFile().isDeclarationFile;
|
||||||
// whereas we want the implementation since it's the one that'll be executed and which can
|
// For non-declaration files, we want to find the constructor with a `body`. The constructors
|
||||||
// have decorators.
|
// without a `body` are overloads whereas we want the implementation since it's the one that'll
|
||||||
|
// be executed and which can have decorators. For declaration files, we take the first one that
|
||||||
|
// we get.
|
||||||
const ctor = tsClazz.members.find(
|
const ctor = tsClazz.members.find(
|
||||||
(member): member is ts.ConstructorDeclaration =>
|
(member): member is ts.ConstructorDeclaration =>
|
||||||
ts.isConstructorDeclaration(member) && member.body !== undefined);
|
ts.isConstructorDeclaration(member) && (isDeclaration || member.body !== undefined));
|
||||||
if (ctor === undefined) {
|
if (ctor === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,6 +249,7 @@ function fakeDirective(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
||||||
isGeneric: false,
|
isGeneric: false,
|
||||||
baseClass: null,
|
baseClass: null,
|
||||||
isPoisoned: false,
|
isPoisoned: false,
|
||||||
|
isStructural: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import * as ts from 'typescript';
|
||||||
import {FullTemplateMapping, TypeCheckableDirectiveMeta} from './api';
|
import {FullTemplateMapping, TypeCheckableDirectiveMeta} from './api';
|
||||||
import {GlobalCompletion} from './completion';
|
import {GlobalCompletion} from './completion';
|
||||||
import {DirectiveInScope, PipeInScope} from './scope';
|
import {DirectiveInScope, PipeInScope} from './scope';
|
||||||
import {DirectiveSymbol, ElementSymbol, ShimLocation, Symbol} from './symbols';
|
import {DirectiveSymbol, ElementSymbol, ShimLocation, Symbol, TemplateSymbol} from './symbols';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to the Angular Template Type Checker to extract diagnostics and intelligence from the
|
* Interface to the Angular Template Type Checker to extract diagnostics and intelligence from the
|
||||||
|
@ -111,6 +111,7 @@ export interface TemplateTypeChecker {
|
||||||
* @see Symbol
|
* @see Symbol
|
||||||
*/
|
*/
|
||||||
getSymbolOfNode(node: TmplAstElement, component: ts.ClassDeclaration): ElementSymbol|null;
|
getSymbolOfNode(node: TmplAstElement, component: ts.ClassDeclaration): ElementSymbol|null;
|
||||||
|
getSymbolOfNode(node: TmplAstTemplate, component: ts.ClassDeclaration): TemplateSymbol|null;
|
||||||
getSymbolOfNode(node: AST|TmplAstNode, component: ts.ClassDeclaration): Symbol|null;
|
getSymbolOfNode(node: AST|TmplAstNode, component: ts.ClassDeclaration): Symbol|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,11 @@ export interface DirectiveInScope {
|
||||||
* `true` if this directive is a component.
|
* `true` if this directive is a component.
|
||||||
*/
|
*/
|
||||||
isComponent: boolean;
|
isComponent: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `true` if this directive is a structural directive.
|
||||||
|
*/
|
||||||
|
isStructural: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../r
|
||||||
import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope';
|
||||||
import {isShim} from '../../shims';
|
import {isShim} from '../../shims';
|
||||||
import {getSourceFileOrNull} from '../../util/src/typescript';
|
import {getSourceFileOrNull} from '../../util/src/typescript';
|
||||||
import {DirectiveInScope, ElementSymbol, FullTemplateMapping, GlobalCompletion, OptimizeFor, PipeInScope, ProgramTypeCheckAdapter, ShimLocation, Symbol, TemplateId, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
|
import {DirectiveInScope, ElementSymbol, FullTemplateMapping, GlobalCompletion, OptimizeFor, PipeInScope, ProgramTypeCheckAdapter, ShimLocation, Symbol, TemplateId, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig, TypeCheckingProgramStrategy, UpdateMode} from '../api';
|
||||||
import {TemplateDiagnostic} from '../diagnostics';
|
import {TemplateDiagnostic} from '../diagnostics';
|
||||||
|
|
||||||
import {CompletionEngine} from './completion';
|
import {CompletionEngine} from './completion';
|
||||||
|
@ -472,6 +472,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
||||||
}
|
}
|
||||||
return this.state.get(path)!;
|
return this.state.get(path)!;
|
||||||
}
|
}
|
||||||
|
getSymbolOfNode(node: TmplAstTemplate, component: ts.ClassDeclaration): TemplateSymbol|null;
|
||||||
getSymbolOfNode(node: TmplAstElement, component: ts.ClassDeclaration): ElementSymbol|null;
|
getSymbolOfNode(node: TmplAstElement, component: ts.ClassDeclaration): ElementSymbol|null;
|
||||||
getSymbolOfNode(node: AST|TmplAstNode, component: ts.ClassDeclaration): Symbol|null {
|
getSymbolOfNode(node: AST|TmplAstNode, component: ts.ClassDeclaration): Symbol|null {
|
||||||
const builder = this.getOrCreateSymbolBuilder(component);
|
const builder = this.getOrCreateSymbolBuilder(component);
|
||||||
|
@ -598,6 +599,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
|
||||||
|
|
||||||
data.directives.push({
|
data.directives.push({
|
||||||
isComponent: dir.isComponent,
|
isComponent: dir.isComponent,
|
||||||
|
isStructural: dir.isStructural,
|
||||||
selector: dir.selector,
|
selector: dir.selector,
|
||||||
tsSymbol,
|
tsSymbol,
|
||||||
ngModule,
|
ngModule,
|
||||||
|
|
|
@ -140,7 +140,8 @@ export class SymbolBuilder {
|
||||||
selector: meta.selector,
|
selector: meta.selector,
|
||||||
isComponent,
|
isComponent,
|
||||||
ngModule,
|
ngModule,
|
||||||
kind: SymbolKind.Directive
|
kind: SymbolKind.Directive,
|
||||||
|
isStructural: meta.isStructural,
|
||||||
};
|
};
|
||||||
return directiveSymbol;
|
return directiveSymbol;
|
||||||
})
|
})
|
||||||
|
@ -281,7 +282,7 @@ export class SymbolBuilder {
|
||||||
|
|
||||||
private getDirectiveSymbolForAccessExpression(
|
private getDirectiveSymbolForAccessExpression(
|
||||||
node: ts.ElementAccessExpression|ts.PropertyAccessExpression,
|
node: ts.ElementAccessExpression|ts.PropertyAccessExpression,
|
||||||
{isComponent, selector}: TypeCheckableDirectiveMeta): DirectiveSymbol|null {
|
{isComponent, selector, isStructural}: TypeCheckableDirectiveMeta): DirectiveSymbol|null {
|
||||||
// In either case, `_t1["index"]` or `_t1.index`, `node.expression` is _t1.
|
// In either case, `_t1["index"]` or `_t1.index`, `node.expression` is _t1.
|
||||||
// The retrieved symbol for _t1 will be the variable declaration.
|
// The retrieved symbol for _t1 will be the variable declaration.
|
||||||
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.expression);
|
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.expression);
|
||||||
|
@ -313,6 +314,7 @@ export class SymbolBuilder {
|
||||||
tsType: symbol.tsType,
|
tsType: symbol.tsType,
|
||||||
shimLocation: symbol.shimLocation,
|
shimLocation: symbol.shimLocation,
|
||||||
isComponent,
|
isComponent,
|
||||||
|
isStructural,
|
||||||
selector,
|
selector,
|
||||||
ngModule,
|
ngModule,
|
||||||
};
|
};
|
||||||
|
|
|
@ -505,6 +505,7 @@ function prepareDeclarations(
|
||||||
isGeneric: decl.isGeneric ?? false,
|
isGeneric: decl.isGeneric ?? false,
|
||||||
outputs: ClassPropertyMapping.fromMappedObject(decl.outputs || {}),
|
outputs: ClassPropertyMapping.fromMappedObject(decl.outputs || {}),
|
||||||
queries: decl.queries || [],
|
queries: decl.queries || [],
|
||||||
|
isStructural: false,
|
||||||
};
|
};
|
||||||
matcher.addSelectables(selector, meta);
|
matcher.addSelectables(selector, meta);
|
||||||
}
|
}
|
||||||
|
@ -567,6 +568,7 @@ function makeScope(program: ts.Program, sf: ts.SourceFile, decls: TestDeclaratio
|
||||||
undeclaredInputFields: new Set(decl.undeclaredInputFields ?? []),
|
undeclaredInputFields: new Set(decl.undeclaredInputFields ?? []),
|
||||||
isGeneric: decl.isGeneric ?? false,
|
isGeneric: decl.isGeneric ?? false,
|
||||||
isPoisoned: false,
|
isPoisoned: false,
|
||||||
|
isStructural: false,
|
||||||
});
|
});
|
||||||
} else if (decl.type === 'pipe') {
|
} else if (decl.type === 'pipe') {
|
||||||
scope.pipes.push({
|
scope.pipes.push({
|
||||||
|
|
|
@ -74,6 +74,8 @@ export interface DirectiveMeta {
|
||||||
* Null otherwise
|
* Null otherwise
|
||||||
*/
|
*/
|
||||||
exportAs: string[]|null;
|
exportAs: string[]|null;
|
||||||
|
|
||||||
|
isStructural: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,6 +38,7 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta> {
|
||||||
inputs: new IdentityInputMapping(['ngForOf']),
|
inputs: new IdentityInputMapping(['ngForOf']),
|
||||||
outputs: new IdentityInputMapping([]),
|
outputs: new IdentityInputMapping([]),
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
|
isStructural: true,
|
||||||
selector: '[ngFor][ngForOf]',
|
selector: '[ngFor][ngForOf]',
|
||||||
});
|
});
|
||||||
matcher.addSelectables(CssSelector.parse('[dir]'), {
|
matcher.addSelectables(CssSelector.parse('[dir]'), {
|
||||||
|
@ -46,6 +47,7 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta> {
|
||||||
inputs: new IdentityInputMapping([]),
|
inputs: new IdentityInputMapping([]),
|
||||||
outputs: new IdentityInputMapping([]),
|
outputs: new IdentityInputMapping([]),
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
|
isStructural: false,
|
||||||
selector: '[dir]'
|
selector: '[dir]'
|
||||||
});
|
});
|
||||||
matcher.addSelectables(CssSelector.parse('[hasOutput]'), {
|
matcher.addSelectables(CssSelector.parse('[hasOutput]'), {
|
||||||
|
@ -54,6 +56,7 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta> {
|
||||||
inputs: new IdentityInputMapping([]),
|
inputs: new IdentityInputMapping([]),
|
||||||
outputs: new IdentityInputMapping(['outputBinding']),
|
outputs: new IdentityInputMapping(['outputBinding']),
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
|
isStructural: false,
|
||||||
selector: '[hasOutput]'
|
selector: '[hasOutput]'
|
||||||
});
|
});
|
||||||
matcher.addSelectables(CssSelector.parse('[hasInput]'), {
|
matcher.addSelectables(CssSelector.parse('[hasInput]'), {
|
||||||
|
@ -62,6 +65,7 @@ function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta> {
|
||||||
inputs: new IdentityInputMapping(['inputBinding']),
|
inputs: new IdentityInputMapping(['inputBinding']),
|
||||||
outputs: new IdentityInputMapping([]),
|
outputs: new IdentityInputMapping([]),
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
|
isStructural: false,
|
||||||
selector: '[hasInput]'
|
selector: '[hasInput]'
|
||||||
});
|
});
|
||||||
return matcher;
|
return matcher;
|
||||||
|
@ -107,6 +111,7 @@ describe('t2 binding', () => {
|
||||||
inputs: new IdentityInputMapping([]),
|
inputs: new IdentityInputMapping([]),
|
||||||
outputs: new IdentityInputMapping([]),
|
outputs: new IdentityInputMapping([]),
|
||||||
isComponent: false,
|
isComponent: false,
|
||||||
|
isStructural: false,
|
||||||
selector: 'text[dir]'
|
selector: 'text[dir]'
|
||||||
});
|
});
|
||||||
const binder = new R3TargetBinder(matcher);
|
const binder = new R3TargetBinder(matcher);
|
||||||
|
|
Loading…
Reference in New Issue