diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
index 898133a63f..9ad75894a9 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts
@@ -512,6 +512,11 @@ class TcbDirectiveCtorOp extends TcbOp {
const inputs = getBoundInputs(this.dir, this.node, this.tcb);
for (const input of inputs) {
+ // Skip text attributes if configured to do so.
+ if (!this.tcb.env.config.checkTypeOfAttributes &&
+ input.attribute instanceof TmplAstTextAttribute) {
+ continue;
+ }
for (const fieldName of input.fieldNames) {
// Skip the field if an attribute has already been bound to it; we can't have a duplicate
// key in the type constructor call.
@@ -654,6 +659,12 @@ class TcbDirectiveInputsOp extends TcbOp {
}
addParseSpanInfo(assignment, input.attribute.sourceSpan);
+ // Ignore diagnostics for text attributes if configured to do so.
+ if (!this.tcb.env.config.checkTypeOfAttributes &&
+ input.attribute instanceof TmplAstTextAttribute) {
+ markIgnoreDiagnostics(assignment);
+ }
+
this.scope.addStatement(ts.createExpressionStatement(assignment));
}
@@ -1732,11 +1743,6 @@ function getBoundInputs(
return;
}
- // Skip text attributes if configured to do so.
- if (!tcb.env.config.checkTypeOfAttributes && attr instanceof TmplAstTextAttribute) {
- return;
- }
-
// Skip the attribute if the directive does not have an input for it.
const inputs = directive.inputs.getByBindingPropertyName(attr.name);
if (inputs === null) {
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts
index cf74e87a2f..464282fae3 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts
@@ -9,7 +9,7 @@
import {ASTWithSource, Binary, BindingPipe, Conditional, Interpolation, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate} from '@angular/compiler';
import * as ts from 'typescript';
-import {absoluteFrom, getSourceFileOrError} from '../../file_system';
+import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {ClassDeclaration} from '../../reflection';
import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, ReferenceSymbol, Symbol, SymbolKind, TemplateSymbol, TemplateTypeChecker, TypeCheckingConfig, VariableSymbol} from '../api';
@@ -66,44 +66,61 @@ runInEachFileSystem(() => {
expect(beforeSymbol).not.toBe(afterSymbol);
});
- it('should get a symbol for text attributes corresponding with a directive input', () => {
- const fileName = absoluteFrom('/main.ts');
- const dirFile = absoluteFrom('/dir.ts');
- const templateString = `
`;
- const {templateTypeChecker, program} = setup(
- [
- {
- fileName,
- templates: {'Cmp': templateString},
- declarations: [{
- name: 'NameDiv',
- selector: 'div[name]',
- file: dirFile,
- type: 'directive',
- inputs: {name: 'name'},
- }]
- },
- {
- fileName: dirFile,
- source: `export class NameDiv {name!: string;}`,
- templates: {},
- }
- ],
- );
- const sf = getSourceFileOrError(program, fileName);
- const cmp = getClass(sf, 'Cmp');
- const {attributes} = getAstElements(templateTypeChecker, cmp)[0];
+ describe('should get a symbol for text attributes corresponding with a directive input', () => {
+ let fileName: AbsoluteFsPath;
+ let targets: TypeCheckingTarget[];
+ beforeEach(() => {
+ fileName = absoluteFrom('/main.ts');
+ const dirFile = absoluteFrom('/dir.ts');
+ const templateString = ``;
+ targets = [
+ {
+ fileName,
+ templates: {'Cmp': templateString} as {[key: string]: string},
+ declarations: [{
+ name: 'NameDiv',
+ selector: 'div[name]',
+ file: dirFile,
+ type: 'directive' as const,
+ inputs: {name: 'name'},
+ }]
+ },
+ {
+ fileName: dirFile,
+ source: `export class NameDiv {name!: string;}`,
+ templates: {},
+ }
+ ];
+ });
- const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!;
- assertInputBindingSymbol(symbol);
- expect(
- (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText())
- .toEqual('name');
+ it('checkTypeOfAttributes = true', () => {
+ const {templateTypeChecker, program} = setup(targets, {checkTypeOfAttributes: true});
+ const sf = getSourceFileOrError(program, fileName);
+ const cmp = getClass(sf, 'Cmp');
+ const {attributes} = getAstElements(templateTypeChecker, cmp)[0];
+ const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!;
+ assertInputBindingSymbol(symbol);
+ expect(
+ (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText())
+ .toEqual('name');
- // Ensure we can go back to the original location using the shim location
- const mapping =
- templateTypeChecker.getTemplateMappingAtShimLocation(symbol.bindings[0].shimLocation)!;
- expect(mapping.span.toString()).toEqual('name');
+ // Ensure we can go back to the original location using the shim location
+ const mapping =
+ templateTypeChecker.getTemplateMappingAtShimLocation(symbol.bindings[0].shimLocation)!;
+ expect(mapping.span.toString()).toEqual('name');
+ });
+
+ it('checkTypeOfAttributes = false', () => {
+ const {templateTypeChecker, program} = setup(targets, {checkTypeOfAttributes: false});
+ const sf = getSourceFileOrError(program, fileName);
+ const cmp = getClass(sf, 'Cmp');
+ const {attributes} = getAstElements(templateTypeChecker, cmp)[0];
+ const symbol = templateTypeChecker.getSymbolOfNode(attributes[0], cmp)!;
+ assertInputBindingSymbol(symbol);
+ expect(
+ (symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText())
+ .toEqual('name');
+ });
});
describe('templates', () => {
diff --git a/packages/language-service/ivy/test/quick_info_spec.ts b/packages/language-service/ivy/test/quick_info_spec.ts
index 404b43531a..45bc6b3eda 100644
--- a/packages/language-service/ivy/test/quick_info_spec.ts
+++ b/packages/language-service/ivy/test/quick_info_spec.ts
@@ -104,363 +104,379 @@ function quickInfoSkeleton(): TestFile[] {
describe('quick info', () => {
let env: LanguageServiceTestEnvironment;
- beforeEach(() => {
- initMockFileSystem('Native');
- env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton());
- });
-
- describe('elements', () => {
- it('should work for native elements', () => {
- expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: '',
- expectedDisplayString: '(element) button: HTMLButtonElement'
- });
+ describe('strict templates (happy path)', () => {
+ beforeEach(() => {
+ initMockFileSystem('Native');
+ env = LanguageServiceTestEnvironment.setup(quickInfoSkeleton());
});
- it('should work for directives which match native element tags', () => {
- expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: '',
- expectedDisplayString: '(directive) AppModule.CompoundCustomButtonDirective'
- });
- });
- });
-
- describe('templates', () => {
- it('should return undefined for ng-templates', () => {
- const {documentation} = expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: '',
- expectedDisplayString: '(template) ng-template'
- });
- expect(toText(documentation))
- .toContain('The `` is an Angular element for rendering HTML.');
- });
- });
-
- describe('directives', () => {
- it('should work for directives', () => {
- expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'string-model',
- expectedDisplayString: '(directive) AppModule.StringModel'
- });
- });
-
- it('should work for components', () => {
- const {documentation} = expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: '',
- expectedDisplayString: '(component) AppModule.TestComponent'
- });
- expect(toText(documentation)).toBe('This Component provides the `test-comp` selector.');
- });
-
- it('should work for structural directives', () => {
- const {documentation} = expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'ngFor',
- expectedDisplayString: '(directive) NgForOf'
- });
- expect(toText(documentation)).toContain('A fake version of the NgFor directive.');
- });
-
- it('should work for directives with compound selectors, some of which are bindings', () => {
- expectQuickInfo({
- templateOverride: `{{hero}}`,
- expectedSpanText: 'ngFor',
- expectedDisplayString: '(directive) NgForOf'
- });
- });
-
- it('should work for data-let- syntax', () => {
- expectQuickInfo({
- templateOverride:
- `{{hero}}`,
- expectedSpanText: 'hero',
- expectedDisplayString: '(variable) hero: Hero'
- });
- });
- });
-
- describe('bindings', () => {
- describe('inputs', () => {
- it('should work for input providers', () => {
+ describe('elements', () => {
+ it('should work for native elements', () => {
expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'tcName',
- expectedDisplayString: '(property) TestComponent.name: string'
+ templateOverride: ``,
+ expectedSpanText: '',
+ expectedDisplayString: '(element) button: HTMLButtonElement'
});
});
- it('should work for bind- syntax', () => {
+ it('should work for directives which match native element tags', () => {
expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'tcName',
- expectedDisplayString: '(property) TestComponent.name: string'
+ templateOverride: ``,
+ expectedSpanText: '',
+ expectedDisplayString: '(directive) AppModule.CompoundCustomButtonDirective'
});
+ });
+ });
+
+ describe('templates', () => {
+ it('should return undefined for ng-templates', () => {
+ const {documentation} = expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: '',
+ expectedDisplayString: '(template) ng-template'
+ });
+ expect(toText(documentation))
+ .toContain('The `` is an Angular element for rendering HTML.');
+ });
+ });
+
+ describe('directives', () => {
+ it('should work for directives', () => {
expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'tcName',
- expectedDisplayString: '(property) TestComponent.name: string'
+ templateOverride: ``,
+ expectedSpanText: 'string-model',
+ expectedDisplayString: '(directive) AppModule.StringModel'
});
});
- it('should work for structural directive inputs ngForTrackBy', () => {
+ it('should work for components', () => {
+ const {documentation} = expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: '',
+ expectedDisplayString: '(component) AppModule.TestComponent'
+ });
+ expect(toText(documentation)).toBe('This Component provides the `test-comp` selector.');
+ });
+
+ it('should work for structural directives', () => {
+ const {documentation} = expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'ngFor',
+ expectedDisplayString: '(directive) NgForOf'
+ });
+ expect(toText(documentation)).toContain('A fake version of the NgFor directive.');
+ });
+
+ it('should work for directives with compound selectors, some of which are bindings', () => {
expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'trackBy',
+ templateOverride:
+ `{{hero}}`,
+ expectedSpanText: 'ngFor',
+ expectedDisplayString: '(directive) NgForOf'
+ });
+ });
+
+ it('should work for data-let- syntax', () => {
+ expectQuickInfo({
+ templateOverride:
+ `{{hero}}`,
+ expectedSpanText: 'hero',
+ expectedDisplayString: '(variable) hero: Hero'
+ });
+ });
+ });
+
+ describe('bindings', () => {
+ describe('inputs', () => {
+ it('should work for input providers', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'tcName',
+ expectedDisplayString: '(property) TestComponent.name: string'
+ });
+ });
+
+ it('should work for bind- syntax', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'tcName',
+ expectedDisplayString: '(property) TestComponent.name: string'
+ });
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'tcName',
+ expectedDisplayString: '(property) TestComponent.name: string'
+ });
+ });
+
+ it('should work for structural directive inputs ngForTrackBy', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'trackBy',
+ expectedDisplayString:
+ '(property) NgForOf.ngForTrackBy: TrackByFunction'
+ });
+ });
+
+ it('should work for structural directive inputs ngForOf', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'of',
+ expectedDisplayString:
+ '(property) NgForOf.ngForOf: Hero[] | (Hero[] & Iterable) | null | undefined'
+ });
+ });
+
+ it('should work for two-way binding providers', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'model',
+ expectedDisplayString: '(property) StringModel.model: string'
+ });
+ });
+ });
+
+ describe('outputs', () => {
+ it('should work for event providers', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'test',
+ expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter'
+ });
+ });
+
+ it('should work for on- syntax binding', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'test',
+ expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter'
+ });
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'test',
+ expectedDisplayString: '(event) TestComponent.testEvent: EventEmitter'
+ });
+ });
+
+ it('should work for $event from EventEmitter', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: '$event',
+ expectedDisplayString: '(parameter) $event: string'
+ });
+ });
+
+ it('should work for $event from native element', () => {
+ expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: '$event',
+ expectedDisplayString: '(parameter) $event: MouseEvent'
+ });
+ });
+ });
+ });
+
+ describe('references', () => {
+ it('should work for element reference declarations', () => {
+ const {documentation} = expectQuickInfo({
+ templateOverride: ``,
+ expectedSpanText: 'chart',
+ expectedDisplayString: '(reference) chart: HTMLDivElement'
+ });
+ expect(toText(documentation))
+ .toEqual(
+ 'Provides special properties (beyond the regular HTMLElement ' +
+ 'interface it also has available to it by inheritance) for manipulating
`);
+ const quickInfo = env.ngLS.getQuickInfoAtPosition(absoluteFrom('/app.html'), cursor);
+ const documentation = toText(quickInfo!.documentation);
+ expect(documentation).toBe('This is the title of the `AppCmp` Component.');
+ });
});
});
- describe('references', () => {
- it('should work for element reference declarations', () => {
- const {documentation} = expectQuickInfo({
- templateOverride: ``,
- expectedSpanText: 'chart',
- expectedDisplayString: '(reference) chart: HTMLDivElement'
- });
- expect(toText(documentation))
- .toEqual(
- 'Provides special properties (beyond the regular HTMLElement ' +
- 'interface it also has available to it by inheritance) for manipulating
`);
- const quickInfo = env.ngLS.getQuickInfoAtPosition(absoluteFrom('/app.html'), cursor);
- const documentation = toText(quickInfo!.documentation);
- expect(documentation).toBe('This is the title of the `AppCmp` Component.');
- });
});
function expectQuickInfo(